socket-function 1.2.1 → 1.2.4

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 (51) hide show
  1. package/SocketFunction.d.ts +1 -1
  2. package/SocketFunctionTypes.d.ts +1 -1
  3. package/hot/HotReloadController.d.ts +3 -3
  4. package/index.d.ts +25 -10
  5. package/package.json +1 -1
  6. package/require/CSSShim.d.ts +1 -1
  7. package/require/RequireController.d.ts +2 -2
  8. package/require/compileFlags.d.ts +1 -1
  9. package/require/require.d.ts +1 -1
  10. package/src/callManager.d.ts +1 -1
  11. package/test.ts +5 -11
  12. package/time/trueTimeShim.d.ts +16 -1
  13. package/time/trueTimeShim.ts +121 -34
  14. package/dist/SetProcessVariables.ts.cache +0 -9
  15. package/dist/SocketFunction.ts.cache +0 -467
  16. package/hot/dist/HotReloadController.ts.cache +0 -252
  17. package/require/dist/RequireController.ts.cache +0 -386
  18. package/require/dist/compileFlags.ts.cache +0 -41
  19. package/require/dist/extMapper.ts.cache +0 -368
  20. package/require/dist/require.ts.cache +0 -676
  21. package/src/JSONLACKS/dist/JSONLACKS.ts.cache +0 -469
  22. package/src/dist/CallFactory.ts.cache +0 -930
  23. package/src/dist/Zip.ts.cache +0 -156
  24. package/src/dist/batching.ts.cache +0 -377
  25. package/src/dist/bits.ts.cache +0 -121
  26. package/src/dist/buffers.ts.cache +0 -76
  27. package/src/dist/caching.ts.cache +0 -302
  28. package/src/dist/callHTTPHandler.ts.cache +0 -264
  29. package/src/dist/callManager.ts.cache +0 -159
  30. package/src/dist/certStore.ts.cache +0 -59
  31. package/src/dist/createSingleton.ts.cache +0 -62
  32. package/src/dist/fixLargeNetworkCalls.ts.cache +0 -17
  33. package/src/dist/forwardPort.ts.cache +0 -222
  34. package/src/dist/https.ts.cache +0 -141
  35. package/src/dist/misc.ts.cache +0 -499
  36. package/src/dist/nodeCache.ts.cache +0 -136
  37. package/src/dist/nodeProxy.ts.cache +0 -57
  38. package/src/dist/protocolNegotiation.ts.cache +0 -101
  39. package/src/dist/tlsParsing.ts.cache +0 -85
  40. package/src/dist/types.ts.cache +0 -9
  41. package/src/dist/webSocketServer.ts.cache +0 -365
  42. package/src/dist/websocketFactory.ts.cache +0 -53
  43. package/src/formatting/dist/colors.ts.cache +0 -98
  44. package/src/formatting/dist/format.ts.cache +0 -396
  45. package/src/formatting/dist/logColors.ts.cache +0 -28
  46. package/src/lz4/dist/LZ4.ts.cache +0 -53
  47. package/src/profiling/dist/getOwnTime.ts.cache +0 -100
  48. package/src/profiling/dist/measure.ts.cache +0 -338
  49. package/src/profiling/dist/stats.ts.cache +0 -202
  50. package/src/profiling/dist/statsFormat.ts.cache +0 -45
  51. package/time/dist/trueTimeShim.ts.cache +0 -656
@@ -1,6 +1,6 @@
1
+ /// <reference path="require/RequireController.d.ts" />
1
2
  /// <reference types="node" />
2
3
  /// <reference types="node" />
3
- /// <reference types="socket-function/require/requirecontroller" />
4
4
  import { SocketExposedInterface, SocketFunctionHook, SocketFunctionClientHook, SocketExposedShape, SocketRegistered, CallerContext, FullCallType, SocketRegisterType } from "./SocketFunctionTypes";
5
5
  import { SocketServerConfig } from "./src/webSocketServer";
6
6
  import { Args, MaybePromise } from "./src/types";
@@ -1,4 +1,4 @@
1
- /// <reference types="socket-function/require/requirecontroller" />
1
+ /// <reference path="require/RequireController.d.ts" />
2
2
  import { getCallObj } from "./src/nodeProxy";
3
3
  import { Args, MaybePromise } from "./src/types";
4
4
  export declare const socket: unique symbol;
@@ -1,5 +1,5 @@
1
- /// <reference types="typenode" />
2
- /// <reference types="socket-function/require/requirecontroller" />
1
+ /// <reference path="../../typenode/index.d.ts" />
2
+ /// <reference path="../require/RequireController.d.ts" />
3
3
  /** Enables some hot reload functionality.
4
4
  * - Triggers a refresh clientside
5
5
  * - Triggers a reload server, for modules marked with `module.hotreload`
@@ -28,7 +28,7 @@ export declare function isHotReloading(): boolean;
28
28
  export declare function hotReloadingGuard(): true;
29
29
  export declare function setExternalHotReloading(value: boolean): void;
30
30
  export declare function onHotReload(callback: (modules: NodeJS.Module[]) => void): void;
31
- export declare const HotReloadController: import("socket-function/SocketFunctionTypes").SocketRegistered<{
31
+ export declare const HotReloadController: import("../SocketFunctionTypes").SocketRegistered<{
32
32
  watchFiles: () => Promise<void>;
33
33
  fileUpdated: (files: string[], changeTime: number) => Promise<void>;
34
34
  }>;
package/index.d.ts CHANGED
@@ -7,9 +7,9 @@ declare module "socket-function/SetProcessVariables" {
7
7
  }
8
8
 
9
9
  declare module "socket-function/SocketFunction" {
10
+ /// <reference path="require/RequireController.d.ts" />
10
11
  /// <reference types="node" />
11
12
  /// <reference types="node" />
12
- /// <reference types="socket-function/require/requirecontroller" />
13
13
  import { SocketExposedInterface, SocketFunctionHook, SocketFunctionClientHook, SocketExposedShape, SocketRegistered, CallerContext, FullCallType, SocketRegisterType } from "socket-function/SocketFunctionTypes";
14
14
  import { SocketServerConfig } from "socket-function/src/webSocketServer";
15
15
  import { Args, MaybePromise } from "socket-function/src/types";
@@ -128,7 +128,7 @@ declare module "socket-function/SocketFunction" {
128
128
  }
129
129
 
130
130
  declare module "socket-function/SocketFunctionTypes" {
131
- /// <reference types="socket-function/require/requirecontroller" />
131
+ /// <reference path="require/RequireController.d.ts" />
132
132
  import { getCallObj } from "socket-function/src/nodeProxy";
133
133
  import { Args, MaybePromise } from "socket-function/src/types";
134
134
  export declare const socket: unique symbol;
@@ -218,8 +218,8 @@ declare module "socket-function/SocketFunctionTypes" {
218
218
  }
219
219
 
220
220
  declare module "socket-function/hot/HotReloadController" {
221
- /// <reference types="typenode" />
222
- /// <reference types="socket-function/require/requirecontroller" />
221
+ /// <reference path="../../typenode/index.d.ts" />
222
+ /// <reference path="../require/RequireController.d.ts" />
223
223
  /** Enables some hot reload functionality.
224
224
  * - Triggers a refresh clientside
225
225
  * - Triggers a reload server, for modules marked with `module.hotreload`
@@ -307,15 +307,15 @@ declare module "socket-function/mobx/promiseToObservable" {
307
307
  }
308
308
 
309
309
  declare module "socket-function/require/CSSShim" {
310
- /// <reference types="socket-function/require/requirecontroller" />
310
+ /// <reference path="RequireController.d.ts" />
311
311
  export {};
312
312
 
313
313
  }
314
314
 
315
315
  declare module "socket-function/require/RequireController" {
316
+ /// <reference path="../../typenode/index.d.ts" />
316
317
  /// <reference types="node" />
317
318
  /// <reference types="node" />
318
- /// <reference types="typenode" />
319
319
  declare global {
320
320
  namespace NodeJS {
321
321
  interface Module {
@@ -430,8 +430,8 @@ declare module "socket-function/require/RequireController" {
430
430
  }
431
431
 
432
432
  declare module "socket-function/require/compileFlags" {
433
+ /// <reference path="RequireController.d.ts" />
433
434
  /// <reference types="node" />
434
- /// <reference types="socket-function/require/requirecontroller" />
435
435
  /**
436
436
  * Adds a global function setFlag(require, "typescript", flag) which sets a flag on the client
437
437
  * - Ex, setFlag(require, "typescript", "allowclient") so allowclient = true on the typescript module.
@@ -453,7 +453,7 @@ declare module "socket-function/require/extMapper" {
453
453
  }
454
454
 
455
455
  declare module "socket-function/require/require" {
456
- /// <reference types="socket-function/src/src" />
456
+ /// <reference path="../src/src.d.ts" />
457
457
  declare global {
458
458
  var onProgressHandler: undefined | ((progress: {
459
459
  type: string;
@@ -789,7 +789,7 @@ declare module "socket-function/src/callHTTPHandler" {
789
789
  }
790
790
 
791
791
  declare module "socket-function/src/callManager" {
792
- /// <reference types="socket-function/hot/hotreloadcontroller" />
792
+ /// <reference path="../hot/HotReloadController.d.ts" />
793
793
  import { CallerContext, CallType, ClientHookContext, FullCallType, FunctionFlags, HookContext, SocketExposedInterface, SocketExposedShape, SocketFunctionClientHook, SocketFunctionHook, SocketRegistered } from "socket-function/SocketFunctionTypes";
794
794
  export declare function getCallFlags(call: CallType): FunctionFlags | undefined;
795
795
  export declare function shouldCompressCall(call: CallType): boolean | "LZ4" | undefined;
@@ -1560,13 +1560,28 @@ declare module "socket-function/time/trueTimeShim" {
1560
1560
  fromTime: number;
1561
1561
  toTime: number;
1562
1562
  };
1563
+ export declare function computeTweenedOffset(components: {
1564
+ systemTime: number;
1565
+ fromOffset: number;
1566
+ toOffset: number;
1567
+ fromTime: number;
1568
+ }): number;
1563
1569
  export declare function getTimeComponents(): {
1564
1570
  systemTime: number;
1565
1571
  offset: number;
1566
1572
  };
1567
1573
  export declare function getTrueTime(): number;
1568
1574
  export declare function getTrueTimeOffset(): number;
1569
- export declare function getTrueTimeProof(): TimeOffsetProof | undefined;
1575
+ export type TrueTimeProof = {
1576
+ systemTime: number;
1577
+ fromOffset: number;
1578
+ toOffset: number;
1579
+ fromTime: number;
1580
+ toTime: number;
1581
+ offset: number;
1582
+ measurement?: TimeOffsetProof;
1583
+ };
1584
+ export declare function getTrueTimeProof(): TrueTimeProof | undefined;
1570
1585
  export declare function waitForFirstTimeSync(): Promise<void> | undefined;
1571
1586
  declare global {
1572
1587
  var TRUE_TIME_ALREADY_SHIMMED: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -1,2 +1,2 @@
1
- /// <reference types="socket-function/require/requirecontroller" />
1
+ /// <reference path="RequireController.d.ts" />
2
2
  export {};
@@ -1,6 +1,6 @@
1
+ /// <reference path="../../typenode/index.d.ts" />
1
2
  /// <reference types="node" />
2
3
  /// <reference types="node" />
3
- /// <reference types="typenode" />
4
4
  declare global {
5
5
  namespace NodeJS {
6
6
  interface Module {
@@ -85,7 +85,7 @@ declare global {
85
85
  /** @deprecated, not needed, as this defaults to ".", which is a lot easier to reason about anyways. */
86
86
  export declare function setRequireBootRequire(dir: string): void;
87
87
  export declare function allowAllNodeModules(): void;
88
- export declare const RequireController: import("socket-function/SocketFunctionTypes").SocketRegistered<{
88
+ export declare const RequireController: import("../SocketFunctionTypes").SocketRegistered<{
89
89
  rootResolvePath: "Function has implementation but is not exposed in the SocketFunction.register call";
90
90
  requireHTML: (config?: {
91
91
  requireCalls?: string[];
@@ -1,5 +1,5 @@
1
+ /// <reference path="RequireController.d.ts" />
1
2
  /// <reference types="node" />
2
- /// <reference types="socket-function/require/requirecontroller" />
3
3
  /**
4
4
  * Adds a global function setFlag(require, "typescript", flag) which sets a flag on the client
5
5
  * - Ex, setFlag(require, "typescript", "allowclient") so allowclient = true on the typescript module.
@@ -1,4 +1,4 @@
1
- /// <reference types="socket-function/src/src" />
1
+ /// <reference path="../src/src.d.ts" />
2
2
  declare global {
3
3
  var onProgressHandler: undefined | ((progress: {
4
4
  type: string;
@@ -1,4 +1,4 @@
1
- /// <reference types="socket-function/hot/hotreloadcontroller" />
1
+ /// <reference path="../hot/HotReloadController.d.ts" />
2
2
  import { CallerContext, CallType, ClientHookContext, FullCallType, FunctionFlags, HookContext, SocketExposedInterface, SocketExposedShape, SocketFunctionClientHook, SocketFunctionHook, SocketRegistered } from "../SocketFunctionTypes";
3
3
  export declare function getCallFlags(call: CallType): FunctionFlags | undefined;
4
4
  export declare function shouldCompressCall(call: CallType): boolean | "LZ4" | undefined;
package/test.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { getTimeComponentsDetailed, waitForFirstTimeSync } from "./time/trueTimeShim";
1
+ import { computeTweenedOffset, getTimeComponentsDetailed, waitForFirstTimeSync } from "./time/trueTimeShim";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
4
 
@@ -19,7 +19,6 @@ async function sampleMode() {
19
19
  toOffset: number;
20
20
  fromTime: number;
21
21
  toTime: number;
22
- fraction: number;
23
22
  };
24
23
 
25
24
  const samples: Sample[] = [];
@@ -28,11 +27,8 @@ async function sampleMode() {
28
27
  for (let i = 0; i < SAMPLE_COUNT; i++) {
29
28
  const detailed = getTimeComponentsDetailed();
30
29
 
31
- // Calculate smearing using the same systemTime
32
- const elapsed = detailed.systemTime - detailed.fromTime;
33
- const duration = detailed.toTime - detailed.fromTime;
34
- const fraction = duration > 0 ? Math.min(1, elapsed / duration) : 0;
35
- const offset = detailed.fromOffset + (detailed.toOffset - detailed.fromOffset) * fraction;
30
+ // Calculate the tween using the same systemTime
31
+ const offset = computeTweenedOffset(detailed);
36
32
 
37
33
  samples.push({
38
34
  id: TEST_RUN_ID,
@@ -42,7 +38,6 @@ async function sampleMode() {
42
38
  toOffset: detailed.toOffset,
43
39
  fromTime: detailed.fromTime,
44
40
  toTime: detailed.toTime,
45
- fraction,
46
41
  });
47
42
 
48
43
  if (SAMPLE_INTERVAL_MS > 0) {
@@ -88,7 +83,6 @@ async function verifyMode() {
88
83
  toOffset: number;
89
84
  fromTime: number;
90
85
  toTime: number;
91
- fraction: number;
92
86
  file: string;
93
87
  };
94
88
 
@@ -126,13 +120,13 @@ async function verifyMode() {
126
120
  console.error(` systemTime: ${prev.systemTime}`);
127
121
  console.error(` offset: ${prev.offset}`);
128
122
  console.error(` trueTime: ${prevTrueTime}`);
129
- console.error(` smearing: ${prev.fromOffset} -> ${prev.toOffset} (${(prev.fraction * 100).toFixed(2)}%)`);
123
+ console.error(` tween: ${prev.fromOffset} -> ${prev.toOffset}`);
130
124
  console.error(` timeWindow: ${prev.fromTime} -> ${prev.toTime}`);
131
125
  console.error(` Current [ID: ${sample.id}, file: ${sample.file}]:`);
132
126
  console.error(` systemTime: ${sample.systemTime}`);
133
127
  console.error(` offset: ${sample.offset}`);
134
128
  console.error(` trueTime: ${trueTime}`);
135
- console.error(` smearing: ${sample.fromOffset} -> ${sample.toOffset} (${(sample.fraction * 100).toFixed(2)}%)`);
129
+ console.error(` tween: ${sample.fromOffset} -> ${sample.toOffset}`);
136
130
  console.error(` timeWindow: ${sample.fromTime} -> ${sample.toTime}`);
137
131
  console.error(` Difference: ${trueTime - lastTrueTime}ms`);
138
132
  }
@@ -15,13 +15,28 @@ export declare function getTimeComponentsDetailed(): {
15
15
  fromTime: number;
16
16
  toTime: number;
17
17
  };
18
+ export declare function computeTweenedOffset(components: {
19
+ systemTime: number;
20
+ fromOffset: number;
21
+ toOffset: number;
22
+ fromTime: number;
23
+ }): number;
18
24
  export declare function getTimeComponents(): {
19
25
  systemTime: number;
20
26
  offset: number;
21
27
  };
22
28
  export declare function getTrueTime(): number;
23
29
  export declare function getTrueTimeOffset(): number;
24
- export declare function getTrueTimeProof(): TimeOffsetProof | undefined;
30
+ export type TrueTimeProof = {
31
+ systemTime: number;
32
+ fromOffset: number;
33
+ toOffset: number;
34
+ fromTime: number;
35
+ toTime: number;
36
+ offset: number;
37
+ measurement?: TimeOffsetProof;
38
+ };
39
+ export declare function getTrueTimeProof(): TrueTimeProof | undefined;
25
40
  export declare function waitForFirstTimeSync(): Promise<void> | undefined;
26
41
  declare global {
27
42
  var TRUE_TIME_ALREADY_SHIMMED: boolean;
@@ -18,10 +18,18 @@ const FETCH_RETRY_MAX_DELAY = 1000 * 30;
18
18
  const FETCH_MAX_FAILURES = 8;
19
19
  const NTP_TIMEOUT = 1000 * 10;
20
20
 
21
+ // The on-disk LMDB database can become corrupt (ex, a process killed mid-write). After this many
22
+ // consecutive failed operations we stop just reopening the handle and delete the files to start
23
+ // fresh - the data is only a cached time offset, so wiping it merely forces a re-measurement.
24
+ const LMDB_MAX_CORRUPTION_FAILURES = 3;
25
+
21
26
  // Time can never go backwards, but we can run at a slower rate until the output time allows
22
27
  // the real time to catch up with it.
23
28
  const MINIMUM_TIME_RATE = 0.5;
24
29
 
30
+ // The offset tweens toward its target at this fraction of elapsed real time, then plateaus once it gets there. Must stay below 1 so output time keeps moving forward even while the offset is shrinking. Tweening across the whole transition window instead would mean a badly-off clock (many seconds) takes the entire window to converge, which breaks transactional consistency with other machines.
31
+ const OFFSET_TWEEN_RATE = 0.5;
32
+
25
33
  const THROW_ON_ERROR = false;
26
34
 
27
35
  // Hugely important as if we don't synchronize between processes, it means our logs are going to be confusing and out of order.
@@ -138,13 +146,24 @@ export function getTimeComponentsDetailed(): {
138
146
  }
139
147
  }
140
148
 
149
+ export function computeTweenedOffset(components: {
150
+ systemTime: number;
151
+ fromOffset: number;
152
+ toOffset: number;
153
+ fromTime: number;
154
+ }): number {
155
+ const elapsed = Math.max(0, components.systemTime - components.fromTime);
156
+ const delta = components.toOffset - components.fromOffset;
157
+ const maxChange = elapsed * OFFSET_TWEEN_RATE;
158
+ if (Math.abs(delta) <= maxChange) {
159
+ return components.toOffset;
160
+ }
161
+ return components.fromOffset + Math.sign(delta) * maxChange;
162
+ }
163
+
141
164
  export function getTimeComponents(): { systemTime: number; offset: number } {
142
165
  const detailed = getTimeComponentsDetailed();
143
- const elapsed = detailed.systemTime - detailed.fromTime;
144
- const duration = detailed.toTime - detailed.fromTime;
145
- const fraction = duration > 0 ? Math.min(1, elapsed / duration) : 0;
146
- const offset = detailed.fromOffset + (detailed.toOffset - detailed.fromOffset) * fraction;
147
- return { systemTime: detailed.systemTime, offset };
166
+ return { systemTime: detailed.systemTime, offset: computeTweenedOffset(detailed) };
148
167
  }
149
168
 
150
169
  export function getTrueTime() {
@@ -174,8 +193,30 @@ export function getTrueTimeOffset() {
174
193
  const { offset } = getTimeComponents();
175
194
  return offset;
176
195
  }
177
- export function getTrueTimeProof(): TimeOffsetProof | undefined {
178
- return cachedTimeOffsetData?.proof;
196
+ export type TrueTimeProof = {
197
+ systemTime: number;
198
+ // The tween endpoints the current offset is moving between. When offset !== toOffset we are still converging.
199
+ fromOffset: number;
200
+ toOffset: number;
201
+ fromTime: number;
202
+ toTime: number;
203
+ offset: number;
204
+ // The raw measurement that produced the newest offset. Undefined if the offset came from a caller-provided base that only returns numbers.
205
+ measurement?: TimeOffsetProof;
206
+ };
207
+ export function getTrueTimeProof(): TrueTimeProof | undefined {
208
+ const data = cachedTimeOffsetData;
209
+ if (!data) return undefined;
210
+ const detailed = getTimeComponentsDetailed();
211
+ return {
212
+ systemTime: detailed.systemTime,
213
+ fromOffset: detailed.fromOffset,
214
+ toOffset: detailed.toOffset,
215
+ fromTime: detailed.fromTime,
216
+ toTime: detailed.toTime,
217
+ offset: computeTweenedOffset(detailed),
218
+ measurement: data.proof,
219
+ };
179
220
  }
180
221
  export function waitForFirstTimeSync(): Promise<void> | undefined {
181
222
  if (didFirstTimeSync) return undefined;
@@ -283,67 +324,113 @@ type TimeOffsetStore = {
283
324
  putInitial(data: TimeOffsetData): Promise<boolean>;
284
325
  };
285
326
 
286
- let timeOffsetDb: import("lmdb").RootDatabase<TimeOffsetData, string> | undefined = undefined;
287
- async function getTimeOffsetDb() {
288
- if (!USE_LMDB_PROCESS_SYNC) return undefined;
289
- if (timeOffsetDb) return timeOffsetDb;
290
- if (!isNode()) return undefined;
327
+ type TimeOffsetDb = import("lmdb").RootDatabase<TimeOffsetData, string>;
328
+ let timeOffsetDb: TimeOffsetDb | undefined = undefined;
329
+ let timeOffsetDbPath: string | undefined = undefined;
291
330
 
331
+ async function openTimeOffsetDb(): Promise<TimeOffsetDb | undefined> {
292
332
  try {
293
333
  const lmdb = await import("lmdb");
294
334
  const path = await import("path");
295
335
  const os = await import("os");
296
336
 
297
- const dbPath = path.join(os.tmpdir(), "socket-function-time-offset-2");
298
- timeOffsetDb = lmdb.open<TimeOffsetData, string>({
299
- path: dbPath,
337
+ timeOffsetDbPath = path.join(os.tmpdir(), "socket-function-time-offset-2");
338
+ return lmdb.open<TimeOffsetData, string>({
339
+ path: timeOffsetDbPath,
300
340
  // Enable versioning for conditional writes
301
341
  useVersions: true,
302
342
  });
303
- return timeOffsetDb;
304
343
  } catch (e) {
305
- console.error("Error opening LMDB database:", e);
344
+ console.error("Error opening LMDB database:", (e as Error).stack ?? e);
306
345
  return undefined;
307
346
  }
308
347
  }
309
348
 
310
- function getLmdbStore(db: import("lmdb").RootDatabase<TimeOffsetData, string>): TimeOffsetStore {
349
+ async function getTimeOffsetDb() {
350
+ if (!USE_LMDB_PROCESS_SYNC) return undefined;
351
+ if (!isNode()) return undefined;
352
+ if (timeOffsetDb) return timeOffsetDb;
353
+ timeOffsetDb = await openTimeOffsetDb();
354
+ return timeOffsetDb;
355
+ }
356
+
357
+ // Closes the current handle (so the next op reopens a fresh one) and, when wipe is set, deletes the
358
+ // on-disk files so a corrupt database is replaced with an empty one. lmdb writes the main file at
359
+ // the path plus a sibling lock file, so we clear the path and its lock variants.
360
+ async function recreateTimeOffsetDb(config: { wipe: boolean }) {
361
+ let db = timeOffsetDb;
362
+ timeOffsetDb = undefined;
363
+ if (db) {
364
+ try {
365
+ await db.close();
366
+ } catch (e) {
367
+ console.error("Error closing LMDB database:", (e as Error).stack ?? e);
368
+ }
369
+ }
370
+ if (config.wipe && timeOffsetDbPath) {
371
+ try {
372
+ const fs = await import("fs");
373
+ for (let suffix of ["", "-lock", ".lock"]) {
374
+ await fs.promises.rm(timeOffsetDbPath + suffix, { recursive: true, force: true });
375
+ }
376
+ console.log(yellow(`Wiped corrupt time-offset LMDB database at ${timeOffsetDbPath}`));
377
+ } catch (e) {
378
+ console.error("Error wiping corrupt LMDB database:", (e as Error).stack ?? e);
379
+ }
380
+ }
381
+ }
382
+
383
+ // A corrupt database throws on every read and on the read-back inside every write, and we can't
384
+ // repair it. So we run each op through here: on success we reset the failure count, and on error
385
+ // we reopen the handle (wiping the files once we've hit the threshold) and return the fallback so
386
+ // callers degrade to running unsynced instead of throwing. Ops are serialized by updatingOffset,
387
+ // so there is no concurrent access to worry about.
388
+ let lmdbCorruptionFailures = 0;
389
+ async function runLmdbOp<T>(name: string, op: (db: TimeOffsetDb) => Promise<T>, fallback: T): Promise<T> {
390
+ const db = await getTimeOffsetDb();
391
+ if (!db) return fallback;
392
+ try {
393
+ const result = await op(db);
394
+ lmdbCorruptionFailures = 0;
395
+ return result;
396
+ } catch (e) {
397
+ lmdbCorruptionFailures++;
398
+ console.error(`Error during LMDB ${name} (failure ${lmdbCorruptionFailures}/${LMDB_MAX_CORRUPTION_FAILURES}):`, (e as Error).stack ?? e);
399
+ let wipe = lmdbCorruptionFailures >= LMDB_MAX_CORRUPTION_FAILURES;
400
+ await recreateTimeOffsetDb({ wipe });
401
+ if (wipe) lmdbCorruptionFailures = 0;
402
+ return fallback;
403
+ }
404
+ }
405
+
406
+ function getLmdbStore(): TimeOffsetStore {
311
407
  return {
312
408
  async getEntry() {
313
- try {
409
+ return runLmdbOp("getEntry", async (db) => {
314
410
  const entry = await db.getEntry("timeOffset"); // Gets {value, version} atomically
315
411
  if (!entry) return undefined;
316
412
  if (typeof entry.version !== "number" || !isValidTimeOffsetData(entry.value)) return undefined;
317
413
  return { data: entry.value, version: entry.version };
318
- } catch (e) {
319
- console.error("Error reading from LMDB database:", e);
320
- return undefined;
321
- }
414
+ }, undefined);
322
415
  },
323
416
  async putIfVersion(data, expectedVersion) {
324
- try {
417
+ return runLmdbOp("putIfVersion", async (db) => {
325
418
  // Use random version to minimize collision probability on retries
326
419
  const newVersion = Math.random();
327
420
  const success = await db.ifVersion("timeOffset", expectedVersion, () => {
328
421
  return db.put("timeOffset", data, newVersion);
329
422
  });
330
423
  return success !== undefined;
331
- } catch (e) {
332
- console.error("Error writing to LMDB database:", e);
333
- return false;
334
- }
424
+ }, false);
335
425
  },
336
426
  async putInitial(data) {
337
- try {
427
+ return runLmdbOp("putInitial", async (db) => {
338
428
  const newVersion = Math.random();
339
429
  await db.put("timeOffset", data, newVersion);
340
430
  // Read back to see what actually got written
341
431
  const actualEntry = await db.getEntry("timeOffset");
342
432
  return actualEntry?.version === newVersion;
343
- } catch (e) {
344
- console.error("Error writing to LMDB database:", e);
345
- return false;
346
- }
433
+ }, false);
347
434
  },
348
435
  };
349
436
  }
@@ -407,7 +494,7 @@ async function getTimeOffsetStore(): Promise<TimeOffsetStore | undefined> {
407
494
  if (isNode()) {
408
495
  const db = await getTimeOffsetDb();
409
496
  if (db) {
410
- timeOffsetStore = getLmdbStore(db);
497
+ timeOffsetStore = getLmdbStore();
411
498
  }
412
499
  } else {
413
500
  timeOffsetStore = getLocalStorageStore();
@@ -1,9 +0,0 @@
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";
2
- Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
3
- module.allowclient = true;
4
- const misc_1 = require("./src/misc");
5
- if (!(0, misc_1.isNode)()) {
6
- process.env.CBOR_NATIVE_ACCELERATION_DISABLED = "true";
7
- }
8
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2V0UHJvY2Vzc1ZhcmlhYmxlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIlNldFByb2Nlc3NWYXJpYWJsZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxNQUFNLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQztBQUMxQixxQ0FBb0M7QUFFcEMsSUFBSSxDQUFDLElBQUEsYUFBTSxHQUFFLEVBQUUsQ0FBQztJQUNaLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUNBQWlDLEdBQUcsTUFBTSxDQUFDO0FBQzNELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJtb2R1bGUuYWxsb3djbGllbnQgPSB0cnVlO1xuaW1wb3J0IHsgaXNOb2RlIH0gZnJvbSBcIi4vc3JjL21pc2NcIjtcblxuaWYgKCFpc05vZGUoKSkge1xuICAgIHByb2Nlc3MuZW52LkNCT1JfTkFUSVZFX0FDQ0VMRVJBVElPTl9ESVNBQkxFRCA9IFwidHJ1ZVwiO1xufSJdfQ=//=
9
- /* _JS_SOURCE_HASH = "249631df8c5cbb77a37aa988d0d9d058d467b254000e31ac2a58f9161b5a8626"; */