sliftutils 0.59.0 → 0.61.0

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/index.d.ts CHANGED
@@ -483,6 +483,16 @@ declare module "sliftutils/render-utils/asyncObservable" {
483
483
 
484
484
  }
485
485
 
486
+ declare module "sliftutils/render-utils/colors" {
487
+ export declare const redButton: string;
488
+ export declare const yellowButton: string;
489
+ export declare const greenButton: string;
490
+ export declare const errorMessage: string;
491
+ export declare const warnMessage: string;
492
+ export declare const AnchorClass: string;
493
+
494
+ }
495
+
486
496
  declare module "sliftutils/render-utils/mobxTyped" {
487
497
  export declare function configureMobxNextFrameScheduler(): void;
488
498
 
@@ -786,6 +796,7 @@ declare module "sliftutils/storage/IStorage" {
786
796
  lastModified: number;
787
797
  }>;
788
798
  reset(): Promise<void>;
799
+ watchResync?: (callback: () => void) => void;
789
800
  };
790
801
  export type IStorageRaw = {
791
802
  get(key: string): Promise<Buffer | undefined>;
@@ -904,6 +915,9 @@ declare module "sliftutils/storage/StorageObservable" {
904
915
  synced: {
905
916
  keySeqNum: number;
906
917
  };
918
+ resynced: {
919
+ seqNum: number;
920
+ };
907
921
  constructor(storage: IStorage<T>);
908
922
  get(key: string): T | undefined;
909
923
  set(key: string, value: T): void;
@@ -942,11 +956,14 @@ declare module "sliftutils/storage/TransactionStorage" {
942
956
  private debugName;
943
957
  private writeDelay;
944
958
  cache: Map<string, TransactionEntry>;
959
+ private diskFiles;
945
960
  private currentChunk;
946
961
  private entryCount;
947
962
  private static allStorage;
948
963
  constructor(rawStorage: IStorageRaw, debugName: string, writeDelay?: number);
949
964
  static compressAll(): Promise<void>;
965
+ private resyncCallbacks;
966
+ watchResync(callback: () => void): void;
950
967
  private init;
951
968
  private getCurrentChunk;
952
969
  private onAddToChunk;
@@ -963,6 +980,7 @@ declare module "sliftutils/storage/TransactionStorage" {
963
980
  pushAppend(entry: TransactionEntry): Promise<void>;
964
981
  private updatePendingAppends;
965
982
  getKeys(): Promise<string[]>;
983
+ checkDisk(): Promise<void>;
966
984
  private loadAllTransactions;
967
985
  private parseTransactionFile;
968
986
  private applyTransactionEntries;
@@ -1,4 +1,4 @@
1
- import { parseYAML } from "sliftutils/misc/yaml";
1
+ import { parseYAML } from "./yaml";
2
2
  import { retryFunctional } from "socket-function/src/batching";
3
3
  import { formatNumber } from "socket-function/src/formatting/format";
4
4
  import { getAPIKey } from "./apiKeys";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "0.59.0",
3
+ "version": "0.61.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -0,0 +1,6 @@
1
+ export declare const redButton: string;
2
+ export declare const yellowButton: string;
3
+ export declare const greenButton: string;
4
+ export declare const errorMessage: string;
5
+ export declare const warnMessage: string;
6
+ export declare const AnchorClass: string;
@@ -1,19 +1,17 @@
1
1
  import { css } from "typesafecss";
2
2
 
3
- export const redButton = css.hsl(0, 75, 50).bord(1, "hsl(0, 75%, 75%)").background("hsl(0, 75%, 75%)", "hover");
4
- export const yellowButton = css.hsl(50, 90, 50).bord(1, "hsl(40, 75%, 75%)").color("hsl(0, 0%, 16%)!important").background("hsl(40, 75%, 75%)", "hover");
5
- export const greenButton = css.hsl(110, 65, 45).bord(1, { h: 110, s: 65, l: 75 }).background("hsl(110, 65%, 90%)", "hover");
3
+ export const redButton = css.hsl(0, 75, 50).bord(1, "hsl(0, 75%, 75%)").background("hsl(0, 75%, 75%)", "hover") as string;
4
+ export const yellowButton = css.hsl(50, 90, 50).bord(1, "hsl(40, 75%, 75%)").color("hsl(0, 0%, 16%)!important").background("hsl(40, 75%, 75%)", "hover") as string;
5
+ export const greenButton = css.hsl(110, 65, 45).bord(1, { h: 110, s: 65, l: 75 }).background("hsl(110, 65%, 90%)", "hover") as string;
6
6
 
7
7
  export const errorMessage = css.hsl(0, 75, 50).color("white")
8
8
  .padding("4px 6px", "soft")
9
- .whiteSpace("pre-wrap").display("inline-block", "soft")
10
- ;
9
+ .whiteSpace("pre-wrap").display("inline-block", "soft") as string;
11
10
  export const warnMessage = css.hsl(50, 75, 50).color("hsl(0, 0%, 7%)", "important", "soft")
12
11
  .padding("4px 6px", "soft")
13
- .whiteSpace("pre-wrap").display("inline-block", "soft")
14
- ;
12
+ .whiteSpace("pre-wrap").display("inline-block", "soft") as string;
15
13
 
16
14
 
17
15
  export const AnchorClass = (
18
- css.textDecoration("none").color("hsl(210, 75%, 65%)").opacity(0.8, "hover")
16
+ css.textDecoration("none").color("hsl(210, 75%, 65%)").opacity(0.8, "hover") as string
19
17
  );
@@ -23,6 +23,7 @@ export type IStorage<T> = {
23
23
  lastModified: number;
24
24
  }>;
25
25
  reset(): Promise<void>;
26
+ watchResync?: (callback: () => void) => void;
26
27
  };
27
28
  export type IStorageRaw = {
28
29
  get(key: string): Promise<Buffer | undefined>;
@@ -22,6 +22,8 @@ export type IStorage<T> = {
22
22
  lastModified: number;
23
23
  }>;
24
24
  reset(): Promise<void>;
25
+ // Allows watching for when the storage detects and underlying changes, and resyncs all of it's data (which might
26
+ watchResync?: (callback: () => void) => void;
25
27
  };
26
28
  // NOTE: In the file system some characters are disallowed, and some characters do special things
27
29
  // (/ makes a folder). And there are even more rules, such as lengths per folder, etc, etc.
@@ -10,6 +10,9 @@ export declare class StorageSync<T> implements IStorageSync<T> {
10
10
  synced: {
11
11
  keySeqNum: number;
12
12
  };
13
+ resynced: {
14
+ seqNum: number;
15
+ };
13
16
  constructor(storage: IStorage<T>);
14
17
  get(key: string): T | undefined;
15
18
  set(key: string, value: T): void;
@@ -11,9 +11,16 @@ export class StorageSync<T> implements IStorageSync<T> {
11
11
  keySeqNum: 0,
12
12
  }, undefined, { deep: false });
13
13
 
14
- constructor(public storage: IStorage<T>) { }
14
+ resynced = observable({ seqNum: 0 });
15
+
16
+ constructor(public storage: IStorage<T>) {
17
+ storage.watchResync?.(() => {
18
+ this.resynced.seqNum++;
19
+ });
20
+ }
15
21
 
16
22
  public get(key: string): T | undefined {
23
+ this.resynced.seqNum;
17
24
  if (!this.cached.has(key)) {
18
25
  this.cached.set(key, undefined);
19
26
  void this.getPromise(key);
@@ -41,12 +48,14 @@ export class StorageSync<T> implements IStorageSync<T> {
41
48
  }
42
49
  private loadedKeys = false;
43
50
  public getKeys(): string[] {
51
+ this.resynced.seqNum;
44
52
  void this.getKeysPromise();
45
53
  this.synced.keySeqNum;
46
54
  return Array.from(this.keys);
47
55
  }
48
56
 
49
57
  public getInfo(key: string): { size: number; lastModified: number } | undefined {
58
+ this.resynced.seqNum;
50
59
  if (!this.infoCached.has(key)) {
51
60
  this.infoCached.set(key, { size: 0, lastModified: 0 });
52
61
  void this.storage.getInfo(key).then(info => {
@@ -12,11 +12,14 @@ export declare class TransactionStorage implements IStorage<Buffer> {
12
12
  private debugName;
13
13
  private writeDelay;
14
14
  cache: Map<string, TransactionEntry>;
15
+ private diskFiles;
15
16
  private currentChunk;
16
17
  private entryCount;
17
18
  private static allStorage;
18
19
  constructor(rawStorage: IStorageRaw, debugName: string, writeDelay?: number);
19
20
  static compressAll(): Promise<void>;
21
+ private resyncCallbacks;
22
+ watchResync(callback: () => void): void;
20
23
  private init;
21
24
  private getCurrentChunk;
22
25
  private onAddToChunk;
@@ -33,6 +36,7 @@ export declare class TransactionStorage implements IStorage<Buffer> {
33
36
  pushAppend(entry: TransactionEntry): Promise<void>;
34
37
  private updatePendingAppends;
35
38
  getKeys(): Promise<string[]>;
39
+ checkDisk(): Promise<void>;
36
40
  private loadAllTransactions;
37
41
  private parseTransactionFile;
38
42
  private applyTransactionEntries;
@@ -6,6 +6,7 @@ import { formatNumber, formatTime } from "socket-function/src/formatting/format"
6
6
  import { setPending } from "./PendingManager";
7
7
  import { isInBuild } from "../misc/environment";
8
8
  import { isNode } from "typesafecss";
9
+ import { runInfinitePoll } from "socket-function/src/batching";
9
10
 
10
11
  /*
11
12
  // Spec:
@@ -39,6 +40,7 @@ UPDATE now we use chunks, because append is too slow.
39
40
  IMPORTANT! If there are multiple writers, we clobber writes from other writers when we compress
40
41
  */
41
42
 
43
+ const DISK_CHECK_INTERVAL = timeInMinute * 5;
42
44
 
43
45
  const FILE_CHUNK_SIZE = 1024 * 1024;
44
46
  const FILE_MAX_LIFETIME = timeInMinute * 30;
@@ -76,6 +78,7 @@ function getNextChunkPath(): string {
76
78
 
77
79
  export class TransactionStorage implements IStorage<Buffer> {
78
80
  public cache: Map<string, TransactionEntry> = new Map();
81
+ private diskFiles: Set<string> = new Set();
79
82
  private currentChunk: {
80
83
  path: string;
81
84
  size: number;
@@ -91,6 +94,8 @@ export class TransactionStorage implements IStorage<Buffer> {
91
94
  private writeDelay = WRITE_DELAY
92
95
  ) {
93
96
  TransactionStorage.allStorage.push(this);
97
+ // VERY useful for debugging.
98
+ (globalThis as any)[`transactionStorage-${this.debugName}`] = this;
94
99
  }
95
100
  // Helps get rid of parse errors which constantly log. Also, uses less space
96
101
  public static async compressAll() {
@@ -101,6 +106,11 @@ export class TransactionStorage implements IStorage<Buffer> {
101
106
  });
102
107
  }
103
108
 
109
+ private resyncCallbacks: (() => void)[] = [];
110
+ public watchResync(callback: () => void): void {
111
+ this.resyncCallbacks.push(callback);
112
+ }
113
+
104
114
  private init: Promise<unknown> | undefined = this.loadAllTransactions();
105
115
 
106
116
  private getCurrentChunk(): string {
@@ -113,6 +123,7 @@ export class TransactionStorage implements IStorage<Buffer> {
113
123
  size: 0,
114
124
  timeCreated: Date.now()
115
125
  };
126
+ this.diskFiles.add(this.currentChunk.path);
116
127
  }
117
128
  return this.currentChunk.path;
118
129
  }
@@ -239,14 +250,35 @@ export class TransactionStorage implements IStorage<Buffer> {
239
250
  return Array.from(this.cache.keys());
240
251
  }
241
252
 
253
+ public async checkDisk(): Promise<void> {
254
+ if (this.init) await this.init;
255
+ const anyChanges = async () => {
256
+ let keys = await this.rawStorage.getKeys();
257
+ let diskFiles = keys.filter(key => key.endsWith(CHUNK_EXT));
258
+ let hasNew = diskFiles.some(file => !this.diskFiles.has(file));
259
+ return hasNew;
260
+ };
261
+ if (!await anyChanges()) return;
262
+
263
+ await fileLockSection(async () => {
264
+ if (!await anyChanges()) return;
265
+ await this.loadAllTransactions();
266
+ });
267
+ }
268
+
242
269
 
243
270
  // NOTE: This is either called in init (which blocks all other calls), or inside of the global file lock, so it is safe to load.
244
271
  private async loadAllTransactions(): Promise<string[]> {
245
272
  if (isInBuild()) return [];
246
273
 
274
+ if (this.init) {
275
+ runInfinitePoll(DISK_CHECK_INTERVAL, () => this.checkDisk());
276
+ }
277
+
247
278
  let time = Date.now();
248
279
  const keys = await this.rawStorage.getKeys();
249
280
  const transactionFiles = keys.filter(key => key.endsWith(CHUNK_EXT));
281
+ this.diskFiles = new Set(transactionFiles);
250
282
 
251
283
  let entryList: TransactionEntry[][] = [];
252
284
  for (let file of transactionFiles) {
@@ -330,25 +362,62 @@ export class TransactionStorage implements IStorage<Buffer> {
330
362
  }
331
363
  pendingWriteTimes.set(entry.key, entry.time);
332
364
  }
333
-
334
- sort(entries, x => x.time);
335
- for (let entry of entries) {
336
- let time = entry.time;
337
- let prevTime = pendingWriteTimes.get(entry.key);
338
- if (prevTime && prevTime > time) {
365
+ let latest = new Map<string, TransactionEntry>();
366
+ for (const entry of entries) {
367
+ let pendingTime = pendingWriteTimes.get(entry.key);
368
+ if (pendingTime && pendingTime > entry.time) {
369
+ continue;
370
+ }
371
+ let prev = latest.get(entry.key);
372
+ if (prev && prev.time > entry.time) {
339
373
  continue;
340
374
  }
375
+ latest.set(entry.key, entry);
376
+ }
341
377
 
378
+ let anyChanged = false;
379
+ for (let entry of latest.values()) {
342
380
  if (entry.value === undefined) {
381
+ if (!this.cache.has(entry.key)) continue;
382
+ anyChanged = true;
343
383
  this.cache.delete(entry.key);
344
384
  } else {
345
- let prev = this.cache.get(entry.key);
346
- if (prev && (prev.time > entry.time)) {
347
- continue;
385
+ if (!anyChanged) {
386
+ let prev = this.cache.get(entry.key);
387
+ if (!prev || prev.isZipped !== entry.isZipped) {
388
+ anyChanged = true;
389
+ } else {
390
+ if (!prev.value) {
391
+ if (entry.value) {
392
+ anyChanged = true;
393
+ }
394
+ } else {
395
+ if (!entry.value) {
396
+ anyChanged = true;
397
+ } else {
398
+ // Both values, so... it might not have changed
399
+ if (!prev.value.equals(entry.value)) {
400
+ anyChanged = true;
401
+ }
402
+ }
403
+ }
404
+ }
348
405
  }
349
406
  this.cache.set(entry.key, entry);
350
407
  }
351
408
  }
409
+
410
+ if (anyChanged) {
411
+ for (const callback of this.resyncCallbacks) {
412
+ try {
413
+ callback();
414
+ } catch (e) {
415
+ setImmediate(() => {
416
+ throw e;
417
+ });
418
+ }
419
+ }
420
+ }
352
421
  }
353
422
 
354
423
  private readTransactionEntry(buffer: Buffer, offset: number): {
@@ -511,6 +580,7 @@ export class TransactionStorage implements IStorage<Buffer> {
511
580
  // the other generations, which is annoying).
512
581
  for (const file of existingDiskEntries) {
513
582
  await this.rawStorage.remove(file);
583
+ this.diskFiles.delete(file);
514
584
  }
515
585
  } finally {
516
586
  this.compressing = false;
@@ -528,6 +598,7 @@ export class TransactionStorage implements IStorage<Buffer> {
528
598
 
529
599
  this.pendingAppends = [];
530
600
  this.cache.clear();
601
+ this.diskFiles.clear();
531
602
  this.currentChunk = undefined;
532
603
  this.entryCount = 0;
533
604
  });