sliftutils 1.7.104 → 1.7.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/treeSummary.ts.cache +2 -2
  2. package/index.d.ts +3 -2
  3. package/misc/dist/environment.ts.cache +2 -2
  4. package/misc/dist/getSecret.ts.cache +2 -2
  5. package/misc/dist/strings.ts.cache +2 -2
  6. package/misc/dist/zip.ts.cache +2 -2
  7. package/misc/https/dist/certs.ts.cache +2 -2
  8. package/misc/https/dist/persistentLocalStorage.ts.cache +2 -2
  9. package/package.json +1 -1
  10. package/render-utils/dist/observer.tsx.cache +2 -2
  11. package/storage/StreamingLogs.ts +5 -3
  12. package/storage/dist/ArchivesDisk.ts.cache +2 -2
  13. package/storage/dist/FileFolderAPI.tsx.cache +2 -2
  14. package/storage/dist/IArchives.ts.cache +2 -2
  15. package/storage/dist/IndexedDBFileFolderAPI.ts.cache +2 -2
  16. package/storage/dist/JSONStorage.ts.cache +2 -2
  17. package/storage/dist/PendingManager.tsx.cache +2 -2
  18. package/storage/dist/StreamingLogs.ts.cache +8 -6
  19. package/storage/dist/TransactionFile.ts.cache +2 -2
  20. package/storage/dist/TransactionStorage.ts.cache +2 -2
  21. package/storage/dist/archiveHelpers.ts.cache +2 -2
  22. package/storage/dist/backblaze.ts.cache +2 -2
  23. package/storage/dist/fileSystemPointer.ts.cache +2 -2
  24. package/storage/dist/remoteFileStorage.ts.cache +2 -2
  25. package/storage/remoteStorage/dist/ArchivesDelayed.ts.cache +2 -2
  26. package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +2 -2
  27. package/storage/remoteStorage/dist/ArchivesUrl.ts.cache +2 -2
  28. package/storage/remoteStorage/dist/accessStats.ts.cache +2 -2
  29. package/storage/remoteStorage/dist/blobStore.ts.cache +2 -2
  30. package/storage/remoteStorage/dist/bucketDisk.ts.cache +2 -2
  31. package/storage/remoteStorage/dist/certTrustModal.tsx.cache +2 -2
  32. package/storage/remoteStorage/dist/chainStartup.ts.cache +2 -2
  33. package/storage/remoteStorage/dist/cliArgs.ts.cache +2 -2
  34. package/storage/remoteStorage/dist/createArchives.ts.cache +2 -2
  35. package/storage/remoteStorage/dist/deployTakeover.ts.cache +2 -2
  36. package/storage/remoteStorage/dist/intermediateManagement.ts.cache +2 -2
  37. package/storage/remoteStorage/dist/intermediateSources.ts.cache +2 -2
  38. package/storage/remoteStorage/dist/remoteConfig.ts.cache +2 -2
  39. package/storage/remoteStorage/dist/serverConfig.ts.cache +2 -2
  40. package/storage/remoteStorage/dist/sourceWrapper.ts.cache +2 -2
  41. package/storage/remoteStorage/dist/sourcesList.ts.cache +2 -2
  42. package/storage/remoteStorage/dist/storageClientController.ts.cache +2 -2
  43. package/storage/remoteStorage/dist/storageController.ts.cache +2 -2
  44. package/storage/remoteStorage/dist/storageLogs.ts.cache +45 -35
  45. package/storage/remoteStorage/dist/storageServerState.ts.cache +2 -2
  46. package/storage/remoteStorage/dist/storeConfig.ts.cache +2 -2
  47. package/storage/remoteStorage/dist/storePlan.ts.cache +2 -2
  48. package/storage/remoteStorage/dist/storeSources.ts.cache +2 -2
  49. package/storage/remoteStorage/dist/storeSync.ts.cache +29 -29
  50. package/storage/remoteStorage/dist/validation.ts.cache +2 -2
  51. package/storage/remoteStorage/storageLogs.d.ts +3 -2
  52. package/storage/remoteStorage/storageLogs.ts +37 -26
  53. package/storage/remoteStorage/storeSync.ts +26 -25
@@ -1,4 +1,4 @@
1
- "use strict"; var console = (function shimConsole() { let baseConsole = globalThis.console; if (typeof window !== "undefined") return baseConsole; let name = __filename.replaceAll("\\", "/").split("/").pop(); let dirName = __dirname.replaceAll("\\", "/").split("/").pop(); let context = { __FILE__: __filename, __DIR__: __dirname, __NAME__: name, __DIRNAME__: dirName }; let newConsole = { ...baseConsole }; for (let shim of ["log", "info", "warn", "error"]) { newConsole[shim] = (...args) => { args.push(context); baseConsole[shim](...args); }; } newConsole.__context = context; return newConsole; })(); "use strict";
1
+ "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
3
3
  //exports.assertValidArgs = exports.assertValidPath = exports.assertValidSourceName = exports.assertValidName = void 0;
4
4
  function assertValidName(value, kind) {
@@ -47,4 +47,4 @@ function assertValidArgs(target, key, descriptor) {
47
47
  }
48
48
  exports.assertValidArgs = assertValidArgs;
49
49
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInZhbGlkYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsU0FBZ0IsZUFBZSxDQUFDLEtBQWEsRUFBRSxJQUFZO0lBQ3ZELElBQUksQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDL0IsTUFBTSxJQUFJLEtBQUssQ0FBQyxXQUFXLElBQUksSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQywrREFBK0QsQ0FBQyxDQUFDO0lBQzdILENBQUM7QUFDTCxDQUFDO0FBSkQsMENBSUM7QUFFRCx1VEFBdVQ7QUFDdlQsU0FBZ0IscUJBQXFCLENBQUMsS0FBYTtJQUMvQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDaEMsTUFBTSxJQUFJLEtBQUssQ0FBQyx1QkFBdUIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsbUVBQW1FLENBQUMsQ0FBQztJQUNySSxDQUFDO0lBQ0QsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDdEIsTUFBTSxJQUFJLEtBQUssQ0FBQyx1QkFBdUIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsb0VBQW9FLENBQUMsQ0FBQztJQUN0SSxDQUFDO0FBQ0wsQ0FBQztBQVBELHNEQU9DO0FBRUQsU0FBZ0IsZUFBZSxDQUFDLElBQVk7SUFDeEMsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQyxNQUFNLEdBQUcsSUFBSSxFQUFFLENBQUM7UUFDMUMsTUFBTSxJQUFJLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSxDQUFDLE1BQU0sNkJBQTZCLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUNwRyxDQUFDO0lBQ0QsSUFBSSxDQUFDLElBQUksSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztRQUM3SCxNQUFNLElBQUksS0FBSyxDQUFDLGdCQUFnQixJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDLDhGQUE4RixDQUFDLENBQUM7SUFDdEssQ0FBQztJQUNELElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLEtBQUssR0FBRyxJQUFJLElBQUksS0FBSyxJQUFJLENBQUMsRUFBRSxDQUFDO1FBQzlELE1BQU0sSUFBSSxLQUFLLENBQUMsZ0JBQWdCLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUMseUNBQXlDLENBQUMsQ0FBQztJQUNqSCxDQUFDO0FBQ0wsQ0FBQztBQVZELDBDQVVDO0FBRUQsNFdBQTRXO0FBQzVXLFNBQWdCLGVBQWUsQ0FBQyxNQUFlLEVBQUUsR0FBVyxFQUFFLFVBQThCO0lBQ3hGLElBQUksUUFBUSxHQUFHLFVBQVUsQ0FBQyxLQUF3QyxDQUFDO0lBQ25FLFVBQVUsQ0FBQyxLQUFLLEdBQUcsVUFBVSxHQUFHLElBQWU7UUFDM0MsSUFBSSxNQUFNLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBeUUsQ0FBQztRQUM3RixJQUFJLE1BQU0sSUFBSSxPQUFPLE1BQU0sS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUN2QyxJQUFJLFNBQVMsSUFBSSxNQUFNO2dCQUFFLGVBQWUsQ0FBQyxNQUFNLENBQUMsT0FBaUIsRUFBRSxTQUFTLENBQUMsQ0FBQztZQUM5RSxJQUFJLFlBQVksSUFBSSxNQUFNO2dCQUFFLGVBQWUsQ0FBQyxNQUFNLENBQUMsVUFBb0IsRUFBRSxhQUFhLENBQUMsQ0FBQztZQUN4RixJQUFJLE1BQU0sSUFBSSxNQUFNO2dCQUFFLGVBQWUsQ0FBQyxNQUFNLENBQUMsSUFBYyxDQUFDLENBQUM7UUFDakUsQ0FBQztRQUNELE9BQU8sUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDdEMsQ0FBQyxDQUFDO0FBQ04sQ0FBQztBQVhELDBDQVdDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGZ1bmN0aW9uIGFzc2VydFZhbGlkTmFtZSh2YWx1ZTogc3RyaW5nLCBraW5kOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBpZiAoIS9eW1xcdy1dezEsNjR9JC8udGVzdCh2YWx1ZSkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkICR7a2luZH0gJHtKU09OLnN0cmluZ2lmeSh2YWx1ZSl9LCBleHBlY3RlZCAxLTY0IGNoYXJhY3RlcnMgb2YgbGV0dGVycy9udW1iZXJzL3VuZGVyc2NvcmUvZGFzaGApO1xuICAgIH1cbn1cblxuLyoqIEEgc3RvcmUncyBuYW1lIChzZWUgQ29tbW9uQ29uZmlnLm5hbWUpLCB3aGljaCBhbHNvIGFsbG93cyBkb3RzIC0gYSBuYW1lIGlzIG9mdGVuIGEgaG9zdCBvciBhIHZlcnNpb24sIGFuZCBib3RoIHJlYWQgd3Jvbmcgd2l0aG91dCB0aGVtLiBJdCBpcyBvbmUgcGF0aCBzZWdtZW50IG9mIHRoZSBzdG9yZSdzIGZvbGRlciwgc28gdGhlIHR3byBuYW1lcyB0aGF0IHdvdWxkIG1lYW4gYSBkaWZmZXJlbnQgZm9sZGVyIGVudGlyZWx5IGFyZSByZWplY3RlZDogZXZlcnl0aGluZyBlbHNlIGNvbnRhaW5pbmcgZG90cyBpcyBqdXN0IGEgbmFtZS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhc3NlcnRWYWxpZFNvdXJjZU5hbWUodmFsdWU6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmICghL15bXFx3Li1dezEsNjR9JC8udGVzdCh2YWx1ZSkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIHNvdXJjZSBuYW1lICR7SlNPTi5zdHJpbmdpZnkodmFsdWUpfSwgZXhwZWN0ZWQgMS02NCBjaGFyYWN0ZXJzIG9mIGxldHRlcnMvbnVtYmVycy91bmRlcnNjb3JlL2Rhc2gvZG90YCk7XG4gICAgfVxuICAgIGlmICgvXlxcLiskLy50ZXN0KHZhbHVlKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgc291cmNlIG5hbWUgJHtKU09OLnN0cmluZ2lmeSh2YWx1ZSl9OiBhIG5hbWUgb2Ygb25seSBkb3RzIHJlZmVycyB0byBhIGRpcmVjdG9yeSByYXRoZXIgdGhhbiBuYW1pbmcgb25lYCk7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gYXNzZXJ0VmFsaWRQYXRoKHBhdGg6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmIChCdWZmZXIuZnJvbShwYXRoLCBcInV0ZjhcIikubGVuZ3RoID4gMTAwMCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFBhdGggdG9vIGxvbmc6ICR7cGF0aC5sZW5ndGh9IGNoYXJhY3RlcnMgPiAxMDAwLiBQYXRoOiAke3BhdGguc2xpY2UoMCwgMjAwKX1gKTtcbiAgICB9XG4gICAgaWYgKCFwYXRoIHx8IHBhdGguc3RhcnRzV2l0aChcIi9cIikgfHwgcGF0aC5lbmRzV2l0aChcIi9cIikgfHwgcGF0aC5pbmNsdWRlcyhcIi8vXCIpIHx8IHBhdGguaW5jbHVkZXMoXCJcXFxcXCIpIHx8IHBhdGguaW5jbHVkZXMoXCJcXHgwMFwiKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgcGF0aCAke0pTT04uc3RyaW5naWZ5KHBhdGguc2xpY2UoMCwgMjAwKSl9LCBwYXRocyBjYW5ub3QgYmUgZW1wdHksIHN0YXJ0IG9yIGVuZCB3aXRoIC8sIG9yIGNvbnRhaW4gLy8sIGJhY2tzbGFzaGVzLCBvciBudWxsIGNoYXJhY3RlcnNgKTtcbiAgICB9XG4gICAgaWYgKHBhdGguc3BsaXQoXCIvXCIpLnNvbWUocGFydCA9PiBwYXJ0ID09PSBcIi5cIiB8fCBwYXJ0ID09PSBcIi4uXCIpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBwYXRoICR7SlNPTi5zdHJpbmdpZnkocGF0aC5zbGljZSgwLCAyMDApKX0sIHBhdGhzIGNhbm5vdCBjb250YWluIC4gb3IgLi4gc2VnbWVudHNgKTtcbiAgICB9XG59XG5cbi8qKiBNZXRob2QgZGVjb3JhdG9yOiB2YWxpZGF0ZXMgdGhlIHdlbGwta25vd24gZmllbGRzIG9mIHRoZSBtZXRob2QncyBzaW5nbGUgY29uZmlnLW9iamVjdCBhcmd1bWVudCAtIGFjY291bnQvYnVja2V0TmFtZSBhcyBuYW1lcywgcGF0aCBhcyBhIHBhdGggLSBiZWZvcmUgdGhlIG1ldGhvZCBydW5zLiBGaWVsZHMgdGhlIGNvbmZpZyBkb2Vzbid0IGhhdmUgYXJlIHNraXBwZWQsIHNvIGl0IGFwcGxpZXMgdG8gZXZlcnkgQVBJIG1ldGhvZCB1bmlmb3JtbHkuIHByZWZpeCBpcyBkZWxpYmVyYXRlbHkgTk9UIHZhbGlkYXRlZDogcHJlZml4ZXMgbWF5IGJlIGVtcHR5IG9yIGVuZCB3aXRoIFwiL1wiLCBib3RoIGludmFsaWQgZm9yIHBhdGhzLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFzc2VydFZhbGlkQXJncyh0YXJnZXQ6IHVua25vd24sIGtleTogc3RyaW5nLCBkZXNjcmlwdG9yOiBQcm9wZXJ0eURlc2NyaXB0b3IpOiB2b2lkIHtcbiAgICBsZXQgb3JpZ2luYWwgPSBkZXNjcmlwdG9yLnZhbHVlIGFzICguLi5hcmdzOiB1bmtub3duW10pID0+IHVua25vd247XG4gICAgZGVzY3JpcHRvci52YWx1ZSA9IGZ1bmN0aW9uICguLi5hcmdzOiB1bmtub3duW10pOiB1bmtub3duIHtcbiAgICAgICAgbGV0IGNvbmZpZyA9IGFyZ3NbMF0gYXMgeyBhY2NvdW50Pzogc3RyaW5nOyBidWNrZXROYW1lPzogc3RyaW5nOyBwYXRoPzogc3RyaW5nIH0gfCB1bmRlZmluZWQ7XG4gICAgICAgIGlmIChjb25maWcgJiYgdHlwZW9mIGNvbmZpZyA9PT0gXCJvYmplY3RcIikge1xuICAgICAgICAgICAgaWYgKFwiYWNjb3VudFwiIGluIGNvbmZpZykgYXNzZXJ0VmFsaWROYW1lKGNvbmZpZy5hY2NvdW50IGFzIHN0cmluZywgXCJhY2NvdW50XCIpO1xuICAgICAgICAgICAgaWYgKFwiYnVja2V0TmFtZVwiIGluIGNvbmZpZykgYXNzZXJ0VmFsaWROYW1lKGNvbmZpZy5idWNrZXROYW1lIGFzIHN0cmluZywgXCJidWNrZXQgbmFtZVwiKTtcbiAgICAgICAgICAgIGlmIChcInBhdGhcIiBpbiBjb25maWcpIGFzc2VydFZhbGlkUGF0aChjb25maWcucGF0aCBhcyBzdHJpbmcpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvcmlnaW5hbC5hcHBseSh0aGlzLCBhcmdzKTtcbiAgICB9O1xufVxuIl19
50
- /* _JS_SOURCE_HASH = "3c47af61d817d76651ac4b91cc2d83a30a45619b8072517ce152b34b6075b1d8"; */
50
+ /* _JS_SOURCE_HASH = "52f73d0ef7e2f65848acc68373c50e34bd39c017c3ff908220fc66a6f3d0296f"; */
@@ -2,7 +2,7 @@
2
2
  /// <reference types="node" />
3
3
  import { LogFileInfo } from "../StreamingLogs";
4
4
  export declare const LOGS_FOLDER_NAME = "logs";
5
- /** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. */
5
+ /** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. Also printed to the console. */
6
6
  export declare function logMutation(entry: {
7
7
  op: string;
8
8
  account: string;
@@ -15,13 +15,14 @@ export declare function logMutation(entry: {
15
15
  callerId?: string;
16
16
  internal?: boolean;
17
17
  }): void;
18
- /** A synchronization key point: scans and full syncs starting/finishing, reconciles, boundary scans - what an operator greps for to see whether the fleet is converging. */
18
+ /** A synchronization key point: scans and full syncs starting/finishing, reconciles, boundary scans - what an operator greps for to see whether the fleet is converging. Also printed to the console. */
19
19
  export declare function logSyncEvent(entry: {
20
20
  event: string;
21
21
  store: string;
22
22
  source?: string;
23
23
  [key: string]: unknown;
24
24
  }): void;
25
+ /** One console.error is all an error takes (console.error and console.warn are HOOKED to feed the stream) - this just guarantees the hook is installed first, for very-early callers. */
25
26
  export declare function logStorageError(message: string): void;
26
27
  /** The log files this server holds - see StreamingLogs.listFiles. Empty on processes with no storage folder. */
27
28
  export declare function listStorageLogFiles(): Promise<LogFileInfo[]>;
@@ -16,14 +16,8 @@ const getLogs = lazy((): StreamingLogs | undefined => {
16
16
  } catch {
17
17
  return undefined;
18
18
  }
19
- let threadId: string | undefined;
20
- try {
21
- threadId = getOwnThreadId(getStorageServerConfigOptional()?.rootDomain || "");
22
- } catch {
23
- threadId = undefined;
24
- }
25
19
  hookErrorLogging();
26
- return new StreamingLogs({ folder, threadId });
20
+ return new StreamingLogs({ folder, threadId: getThreadIdSafe() });
27
21
  });
28
22
 
29
23
  function firstExternalIPv4(): string | undefined {
@@ -35,48 +29,65 @@ function firstExternalIPv4(): string | undefined {
35
29
  return undefined;
36
30
  }
37
31
 
32
+ function getThreadIdSafe(): string | undefined {
33
+ try {
34
+ return getOwnThreadId(getStorageServerConfigOptional()?.rootDomain || "");
35
+ } catch {
36
+ return undefined;
37
+ }
38
+ }
39
+
38
40
  // Computed once: none of it changes over a process's lifetime
39
41
  const baseFields = lazy(() => ({
40
42
  pid: process.pid,
43
+ threadId: getThreadIdSafe(),
41
44
  entryPoint: process.argv[1],
42
45
  ip: firstExternalIPv4(),
43
46
  domain: getStorageServerConfigOptional()?.domain,
44
47
  }));
45
48
 
46
- function write(kind: string, entry: { [key: string]: unknown }): void {
49
+ // Everything logged here goes BOTH to the storage log stream and the console - one call, never two. The console line carries only the entry (the base fields are constant per process, so they would just be noise there); the stream gets everything.
50
+ function write(kind: string, entry: { [key: string]: unknown }, alsoConsole: boolean): void {
51
+ if (alsoConsole) {
52
+ console.log(`[${kind}] ${JSON.stringify(entry)}`);
53
+ }
47
54
  let logs = getLogs();
48
55
  if (!logs) return;
49
56
  logs.log({ kind, time: Date.now(), ...baseFields(), ...entry });
50
57
  }
51
58
 
52
- /** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. */
59
+ /** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. Also printed to the console. */
53
60
  export function logMutation(entry: { op: string; account: string; bucketName: string; store?: string; path: string; toPath?: string; size?: number; writeTime?: number; callerId?: string; internal?: boolean }): void {
54
- write("mutation", entry);
61
+ write("mutation", entry, true);
55
62
  }
56
63
 
57
- /** A synchronization key point: scans and full syncs starting/finishing, reconciles, boundary scans - what an operator greps for to see whether the fleet is converging. */
64
+ /** A synchronization key point: scans and full syncs starting/finishing, reconciles, boundary scans - what an operator greps for to see whether the fleet is converging. Also printed to the console. */
58
65
  export function logSyncEvent(entry: { event: string; store: string; source?: string; [key: string]: unknown }): void {
59
- write("sync", entry);
66
+ write("sync", entry, true);
60
67
  }
61
68
 
69
+ /** One console.error is all an error takes (console.error and console.warn are HOOKED to feed the stream) - this just guarantees the hook is installed first, for very-early callers. */
62
70
  export function logStorageError(message: string): void {
63
- write("error", { message });
71
+ getLogs();
72
+ console.error(message);
64
73
  }
65
74
 
66
- // Everything console.errored also lands in the log stream (with a reentrancy guard: a failure INSIDE logging must not log itself forever)
67
- let writingError = false;
75
+ // Everything console.errored or console.warned also lands in the log stream (with a reentrancy guard: a failure INSIDE logging must not log itself forever)
76
+ let writingHooked = false;
68
77
  const hookErrorLogging = lazy(() => {
69
- let original = console.error.bind(console);
70
- console.error = (...args: unknown[]) => {
71
- original(...args);
72
- if (writingError) return;
73
- writingError = true;
74
- try {
75
- logStorageError(args.map(x => typeof x === "string" && x || ((x as Error)?.stack ?? JSON.stringify(x))).join(" ").slice(0, 5000));
76
- } catch { } finally {
77
- writingError = false;
78
- }
79
- };
78
+ for (let level of ["error", "warn"] as const) {
79
+ let original = console[level].bind(console);
80
+ console[level] = (...args: unknown[]) => {
81
+ original(...args);
82
+ if (writingHooked) return;
83
+ writingHooked = true;
84
+ try {
85
+ write(level, { message: args.map(x => typeof x === "string" && x || ((x as Error)?.stack ?? JSON.stringify(x))).join(" ").slice(0, 5000) }, false);
86
+ } catch { } finally {
87
+ writingHooked = false;
88
+ }
89
+ };
90
+ }
80
91
  });
81
92
 
82
93
  /** The log files this server holds - see StreamingLogs.listFiles. Empty on processes with no storage folder. */
@@ -75,10 +75,6 @@ type ScanTally = Record<ScanOutcome, number>;
75
75
  function newScanTally(): ScanTally {
76
76
  return { filtered: 0, new: 0, updated: 0, tombstone: 0, unchanged: 0 };
77
77
  }
78
- function formatScanTally(tally: ScanTally, total: number): string {
79
- let pct = (n: number) => `${Math.round(n / Math.max(total, 1) * 1000) / 10}%`;
80
- return `${tally.new} new paths (${pct(tally.new)}), ${tally.updated} newer writes (${pct(tally.updated)}), ${tally.tombstone} deletions (${pct(tally.tombstone)}), ${tally.unchanged} unchanged (${pct(tally.unchanged)}), ${tally.filtered} outside route (${pct(tally.filtered)})`;
81
- }
82
78
 
83
79
  export class StoreSync {
84
80
  // Per source slot, in step with the store's sources array. Slots are never spliced out - a removed one is marked dead - so a slot number held by a running loop always means the same source.
@@ -226,7 +222,7 @@ export class StoreSync {
226
222
  /** A boundary scan of the node that owned (part of) our route in the valid window before ours, when that node is different storage (a disk rescan can't see its writes): just its changes since the boundary neighborhood, with matching values pulled onto our own disk. */
227
223
  public async boundaryScanRemote(source: IArchives, config: { since: number; route?: [number, number] }): Promise<void> {
228
224
  let scanStart = Date.now();
229
- console.log(`Boundary scan of ${source.getDebugName()} starting: changes since ${new Date(config.since).toISOString()}, route ${JSON.stringify(config.route || FULL_ROUTE)} (store ${this.store.folder})`);
225
+ logSyncEvent({ event: "boundaryScanStart", store: this.store.folder, source: source.getDebugName(), since: config.since, route: config.route || FULL_ROUTE });
230
226
  let changes = await source.getChangesAfter2({ time: config.since, routes: config.route && [config.route] || undefined });
231
227
  let tally = newScanTally();
232
228
  for (let file of changes) {
@@ -246,7 +242,10 @@ export class StoreSync {
246
242
  continue;
247
243
  }
248
244
  let copied = await copyArchiveFile({ from: source, to: this.store.sources[0].source, path: file.path, size: file.size, writeTime: file.createTime, forceSetImmutable: true, noChecks: true, internal: true });
249
- if (!copied) continue;
245
+ if (!copied) {
246
+ console.warn(`Boundary scan could not copy ${file.path} from ${source.getDebugName()} (store ${this.store.folder}): its change feed listed it (${file.size} bytes, writeTime ${new Date(file.createTime).toISOString()}) but the read found nothing`);
247
+ continue;
248
+ }
250
249
  this.store.noteSyncTransfer("sync get", file.path, copied.size);
251
250
  if (copied.size === 0) {
252
251
  this.store.setIndexDeleted(file.path, copied.writeTime);
@@ -260,8 +259,7 @@ export class StoreSync {
260
259
  tally.new++;
261
260
  }
262
261
  }
263
- console.log(`Boundary scan of ${source.getDebugName()} finished in ${Math.round((Date.now() - scanStart) / 1000)}s (store ${this.store.folder}): ${changes.length} changes: ${formatScanTally(tally, changes.length)}`);
264
- logSyncEvent({ event: "boundaryScanFinish", store: this.store.folder, source: source.getDebugName(), durationMs: Date.now() - scanStart, since: config.since, changes: changes.length, newPaths: tally.new, updated: tally.updated, tombstones: tally.tombstone });
262
+ logSyncEvent({ event: "boundaryScanFinish", store: this.store.folder, source: source.getDebugName(), durationMs: Date.now() - scanStart, since: config.since, changes: changes.length, newPaths: tally.new, updated: tally.updated, tombstones: tally.tombstone, unchanged: tally.unchanged });
265
263
  }
266
264
 
267
265
  // ── per-source loops ──
@@ -270,6 +268,8 @@ export class StoreSync {
270
268
  await this.store.registerSlot(sourceIndex);
271
269
  let { source } = this.store.sources[sourceIndex];
272
270
  let state = this.states[sourceIndex];
271
+ // If a slot ever gets TWO of these, its loops are doubled - every poll and full sync runs twice
272
+ logSyncEvent({ event: "sourceSyncStart", store: this.store.folder, source: source.getDebugName(), sourceIndex, url: this.store.sources[sourceIndex].url, intermediate: this.store.sources[sourceIndex].intermediate });
273
273
  // Read live for every pass, not captured - the store's source list can change while loops run
274
274
  let noFullSync = () => this.store.sources[sourceIndex].noFullSync;
275
275
  let listing: Map<string, number> | undefined;
@@ -329,7 +329,6 @@ export class StoreSync {
329
329
  let scanStart = Date.now();
330
330
  let activity: SyncActivity = { type: "metadataScan", sourceDebugName: source.getDebugName(), startTime: scanStart };
331
331
  this.activities.add(activity);
332
- console.log(`Metadata scan of ${source.getDebugName()} starting (store ${this.store.folder})`);
333
332
  logSyncEvent({ event: "scanStart", store: this.store.folder, source: source.getDebugName() });
334
333
  let progressTimer = setInterval(() => {
335
334
  console.log(`Metadata scan of ${source.getDebugName()} still running (${Math.round((Date.now() - scanStart) / 1000)}s, store ${this.store.folder})`);
@@ -392,11 +391,7 @@ export class StoreSync {
392
391
  if (!routeContains(route, getRoute(key))) continue;
393
392
  missingOnSource++;
394
393
  }
395
- // Percentages are of the union of both sides (our index + their listing), so every count has a stable denominator
396
- let union = indexSizeBefore + newPaths;
397
- let pct = (n: number) => `${Math.round(n / Math.max(union, 1) * 1000) / 10}%`;
398
- console.log(`Metadata scan of ${source.getDebugName()} finished in ${Math.round((Date.now() - scanStart) / 1000)}s (store ${this.store.folder}): ${files.length} listed vs ${indexSizeBefore} indexed (union ${union}): ${formatScanTally(tally, union)}, ${missingOnSource} in index but missing on source (${pct(missingOnSource)}), ${removedFromIndex} removed from index (${pct(removedFromIndex)})`);
399
- logSyncEvent({ event: "scanFinish", store: this.store.folder, source: source.getDebugName(), durationMs: Date.now() - scanStart, listed: files.length, indexedBefore: indexSizeBefore, newPaths: tally.new, updated: tally.updated, tombstones: tally.tombstone, missingOnSource, removedFromIndex });
394
+ logSyncEvent({ event: "scanFinish", store: this.store.folder, source: source.getDebugName(), durationMs: Date.now() - scanStart, listed: files.length, indexedBefore: indexSizeBefore, newPaths: tally.new, updated: tally.updated, tombstones: tally.tombstone, unchanged: tally.unchanged, outsideRoute: tally.filtered, missingOnSource, removedFromIndex });
400
395
  state.changesAfterTime = Math.max(state.changesAfterTime, scanStart - CHANGES_POLL_OVERLAP);
401
396
  return seen;
402
397
  }
@@ -462,8 +457,6 @@ export class StoreSync {
462
457
  }
463
458
  if (failed) {
464
459
  console.error(`Reconciling sync source ${source.getDebugName()} (store ${this.store.folder}): pushed ${pushed} files, ${failed} failed${aborted && ` before aborting the pass (${consecutiveFailures} consecutive failures - the source looks down; the next scan cycle retries)` || ""}. First errors: ${errors.join(" | ")}`);
465
- } else if (pushed) {
466
- console.log(`Reconciled sync source ${source.getDebugName()} (store ${this.store.folder}): pushed ${pushed} files it was missing or held older copies of`);
467
460
  }
468
461
  if (pushed || failed) {
469
462
  logSyncEvent({ event: "reconcileFinish", store: this.store.folder, source: source.getDebugName(), pushed, failed, aborted });
@@ -503,9 +496,9 @@ export class StoreSync {
503
496
  for (let file of changes) {
504
497
  tally[this.updateScanIndex(sourceIndex, file)]++;
505
498
  }
506
- // Polls run constantly, so only the ones that actually changed the index get a line
499
+ // Polls run constantly, so only the ones that actually changed the index get an entry
507
500
  if (tally.new || tally.updated || tally.tombstone) {
508
- console.log(`Changes poll of ${source.getDebugName()} (store ${this.store.folder}): ${changes.length} changes: ${formatScanTally(tally, changes.length)}`);
501
+ logSyncEvent({ event: "changesPoll", store: this.store.folder, source: source.getDebugName(), changes: changes.length, newPaths: tally.new, updated: tally.updated, tombstones: tally.tombstone, unchanged: tally.unchanged });
509
502
  }
510
503
  state.scannedCount += changes.length;
511
504
  state.changesAfterTime = pollStart - CHANGES_POLL_OVERLAP;
@@ -535,7 +528,7 @@ export class StoreSync {
535
528
  totalBytes,
536
529
  };
537
530
  this.activities.add(activity);
538
- logSyncEvent({ event: "fullSyncStart", store: this.store.folder, source: source.getDebugName(), files: pending.length, bytes: totalBytes });
531
+ logSyncEvent({ event: "fullSyncStart", store: this.store.folder, source: source.getDebugName(), diffBased: state.supportsChangesAfter, files: pending.length, bytes: totalBytes });
539
532
  let progressLogged = false;
540
533
  let logProgress = () => {
541
534
  progressLogged = true;
@@ -557,12 +550,15 @@ export class StoreSync {
557
550
  console.warn(`Full sync from ${source.getDebugName()} (store ${this.store.folder}) has been running for ${formatTime(elapsed)}: ${doneFiles}/${pending.length} files (${(doneFiles / pending.length * 100).toFixed(1)}%), ${formatNumber(doneBytes)}B/${formatNumber(totalBytes)}B (${(totalBytes && doneBytes / totalBytes * 100 || 100).toFixed(1)}%), ${formatNumber(bytesPerSecond)}B/s. Estimated ${etaText}.`);
558
551
  }, FULL_SYNC_SLOW_ERROR_INTERVAL);
559
552
  (slowErrorTimer as { unref?: () => void }).unref?.();
553
+ let failed = 0;
554
+ let copiedFiles = 0;
555
+ let copiedBytes = 0;
556
+ let missingOnSource = 0;
557
+ let aborted = false;
560
558
  try {
561
559
  let nextIndex = 0;
562
- let failed = 0;
563
560
  let consecutiveFailures = 0;
564
561
  let errors: string[] = [];
565
- let aborted = false;
566
562
  let copyWorker = async () => {
567
563
  while (!aborted && !this.store.stopped.stop && !state.stopped.stop) {
568
564
  let index = nextIndex++;
@@ -571,11 +567,16 @@ export class StoreSync {
571
567
  try {
572
568
  let copied = await copyArchiveFile({ from: source, to: this.store.sources[0].source, path: key, size: entry.size, writeTime: entry.writeTime, forceSetImmutable: true, noChecks: true, internal: true });
573
569
  if (copied) {
570
+ copiedFiles++;
571
+ copiedBytes += copied.size;
574
572
  this.store.noteSyncTransfer("sync get", key, copied.size);
575
- // Only move the entry's source if it wasn't changed while we copied
576
- if (this.entryUnchanged(key, entry)) {
577
- this.store.setIndexEntry(key, { writeTime: copied.writeTime, size: copied.size, sourcesListIndex: this.store.sourcesListIndexOfSlot(0) });
573
+ // The copy carries the source's write time, and the index commits it under the normal ordering rule (>= the current time wins) - setIndexEntry refusing means a NEWER write landed while we copied, which must be said, not swallowed
574
+ if (!this.store.setIndexEntry(key, { writeTime: copied.writeTime, size: copied.size, sourcesListIndex: this.store.sourcesListIndexOfSlot(0) })) {
575
+ console.warn(`Full sync copied ${key} from ${source.getDebugName()} (store ${this.store.folder}) with writeTime ${copied.writeTime} (${new Date(copied.writeTime).toISOString()}), but the index now has ${this.store.currentWriteTime(key)} (${new Date(this.store.currentWriteTime(key)).toISOString()}) - something wrote it while we were syncing; keeping the newer entry`);
578
576
  }
577
+ } else {
578
+ missingOnSource++;
579
+ console.warn(`Full sync could not copy ${key} from ${source.getDebugName()} (store ${this.store.folder}): the source answered but no longer has it (expected ${entry.size} bytes, writeTime ${new Date(entry.writeTime).toISOString()})`);
579
580
  }
580
581
  consecutiveFailures = 0;
581
582
  } catch (e) {
@@ -612,7 +613,7 @@ export class StoreSync {
612
613
  if (progressLogged) {
613
614
  logProgress();
614
615
  }
615
- logSyncEvent({ event: "fullSyncFinish", store: this.store.folder, source: source.getDebugName(), durationMs: Date.now() - activity.startTime, copiedFiles: activity.doneFiles, copiedBytes: activity.doneBytes, totalFiles: pending.length, totalBytes });
616
+ logSyncEvent({ event: "fullSyncFinish", store: this.store.folder, source: source.getDebugName(), diffBased: state.supportsChangesAfter, durationMs: Date.now() - activity.startTime, totalFiles: pending.length, totalBytes, processedFiles: activity.doneFiles, copiedFiles, copiedBytes, missingOnSource, failed, aborted });
616
617
  }
617
618
  }
618
619