sliftutils 0.55.0 → 0.57.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
@@ -231,7 +231,32 @@ declare module "sliftutils/render-utils/Input" {
231
231
 
232
232
  declare module "sliftutils/render-utils/InputLabel" {
233
233
  import preact from "preact";
234
- import { InputProps } from "./Input";
234
+ type InputProps = (preact.JSX.HTMLAttributes<HTMLInputElement> & {
235
+ /** ONLY throttles onChangeValue */
236
+ throttle?: number;
237
+ flavor?: "large" | "small" | "none";
238
+ focusOnMount?: boolean;
239
+ textarea?: boolean;
240
+ /** Update on key stroke, not on blur (just does onInput = onChange, as onInput already does this) */
241
+ hot?: boolean;
242
+ /** Updates arrow keys with modifier behavior to use larger numbers, instead of decimals. */
243
+ integer?: boolean;
244
+ /** Only works with number/integer */
245
+ reverseArrowKeyDirection?: boolean;
246
+ inputRef?: (x: HTMLInputElement | null) => void;
247
+ /** Don't blur on enter key */
248
+ noEnterKeyBlur?: boolean;
249
+ noFocusSelect?: boolean;
250
+ inputKey?: string;
251
+ fillWidth?: boolean;
252
+ autocompleteValues?: string[];
253
+ /** Forces the input to update when focused. Usually we hold updates, to prevent the user's
254
+ * typing to be interrupted by background updates.
255
+ * NOTE: "hot" is usually required when using this.
256
+ */
257
+ forceInputValueUpdatesWhenFocused?: boolean;
258
+ onChangeValue?: (value: string) => void;
259
+ });
235
260
  export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
236
261
  label?: preact.ComponentChild;
237
262
  number?: boolean;
@@ -266,6 +291,7 @@ declare module "sliftutils/render-utils/InputLabel" {
266
291
  }> {
267
292
  render(): preact.JSX.Element;
268
293
  }
294
+ export {};
269
295
 
270
296
  }
271
297
 
@@ -924,7 +950,8 @@ declare module "sliftutils/storage/TransactionStorage" {
924
950
  private updatePendingAppends;
925
951
  getKeys(): Promise<string[]>;
926
952
  private loadAllTransactions;
927
- private loadTransactionFile;
953
+ private parseTransactionFile;
954
+ private applyTransactionEntries;
928
955
  private readTransactionEntry;
929
956
  private serializeTransactionEntry;
930
957
  private getHeader;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "0.55.0",
3
+ "version": "0.57.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -9,6 +9,7 @@ import { throttleFunction } from "socket-function/src/misc";
9
9
  // This is useful for inputs which you want to run an action on, such as "add new item",
10
10
  // as it allows you to remove a local state value to cache the value, by just
11
11
  // doing the add on "onChangeValue".
12
+ // IMPORTANT! InputProps is in both InputLabel.tsx and Input.tsx, so the types export correctly
12
13
  export type InputProps = (
13
14
  preact.JSX.HTMLAttributes<HTMLInputElement>
14
15
  & {
@@ -1,5 +1,30 @@
1
1
  import preact from "preact";
2
- import { InputProps } from "./Input";
2
+ type InputProps = (preact.JSX.HTMLAttributes<HTMLInputElement> & {
3
+ /** ONLY throttles onChangeValue */
4
+ throttle?: number;
5
+ flavor?: "large" | "small" | "none";
6
+ focusOnMount?: boolean;
7
+ textarea?: boolean;
8
+ /** Update on key stroke, not on blur (just does onInput = onChange, as onInput already does this) */
9
+ hot?: boolean;
10
+ /** Updates arrow keys with modifier behavior to use larger numbers, instead of decimals. */
11
+ integer?: boolean;
12
+ /** Only works with number/integer */
13
+ reverseArrowKeyDirection?: boolean;
14
+ inputRef?: (x: HTMLInputElement | null) => void;
15
+ /** Don't blur on enter key */
16
+ noEnterKeyBlur?: boolean;
17
+ noFocusSelect?: boolean;
18
+ inputKey?: string;
19
+ fillWidth?: boolean;
20
+ autocompleteValues?: string[];
21
+ /** Forces the input to update when focused. Usually we hold updates, to prevent the user's
22
+ * typing to be interrupted by background updates.
23
+ * NOTE: "hot" is usually required when using this.
24
+ */
25
+ forceInputValueUpdatesWhenFocused?: boolean;
26
+ onChangeValue?: (value: string) => void;
27
+ });
3
28
  export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
4
29
  label?: preact.ComponentChild;
5
30
  number?: boolean;
@@ -34,3 +59,4 @@ export declare class InputLabelURL extends preact.Component<InputLabelProps & {
34
59
  }> {
35
60
  render(): preact.JSX.Element;
36
61
  }
62
+ export {};
@@ -1,10 +1,50 @@
1
1
  import preact from "preact";
2
- import { Input, InputProps } from "./Input";
2
+ import { Input } from "./Input";
3
3
  import { css } from "typesafecss";
4
4
  import { lazy } from "socket-function/src/caching";
5
5
  import { observer } from "./observer";
6
6
  import { observable } from "mobx";
7
7
 
8
+ // IMPORTANT! InputProps is in both InputLabel.tsx and Input.tsx, so the types export correctly
9
+ type InputProps = (
10
+ preact.JSX.HTMLAttributes<HTMLInputElement>
11
+ & {
12
+ /** ONLY throttles onChangeValue */
13
+ throttle?: number;
14
+
15
+ flavor?: "large" | "small" | "none";
16
+ focusOnMount?: boolean;
17
+ textarea?: boolean;
18
+ /** Update on key stroke, not on blur (just does onInput = onChange, as onInput already does this) */
19
+ hot?: boolean;
20
+ /** Updates arrow keys with modifier behavior to use larger numbers, instead of decimals. */
21
+ integer?: boolean;
22
+
23
+ /** Only works with number/integer */
24
+ reverseArrowKeyDirection?: boolean;
25
+
26
+ inputRef?: (x: HTMLInputElement | null) => void;
27
+ /** Don't blur on enter key */
28
+ noEnterKeyBlur?: boolean;
29
+ noFocusSelect?: boolean;
30
+ inputKey?: string;
31
+ fillWidth?: boolean;
32
+
33
+
34
+ autocompleteValues?: string[];
35
+
36
+ /** Forces the input to update when focused. Usually we hold updates, to prevent the user's
37
+ * typing to be interrupted by background updates.
38
+ * NOTE: "hot" is usually required when using this.
39
+ */
40
+ forceInputValueUpdatesWhenFocused?: boolean;
41
+
42
+ // NOTE: We trigger onChange (and onChangeValue) whenever
43
+ // e.ctrlKey && (e.code.startsWith("Key") || e.code === "Enter") || e.code === "Enter" && e.shiftKey
44
+ // This is because ctrl usually means a hotkey, and hotkeys usually want committed values.
45
+ onChangeValue?: (value: string) => void;
46
+ }
47
+ )
8
48
 
9
49
  export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
10
50
  label?: preact.ComponentChild;
@@ -34,7 +34,8 @@ export declare class TransactionStorage implements IStorage<Buffer> {
34
34
  private updatePendingAppends;
35
35
  getKeys(): Promise<string[]>;
36
36
  private loadAllTransactions;
37
- private loadTransactionFile;
37
+ private parseTransactionFile;
38
+ private applyTransactionEntries;
38
39
  private readTransactionEntry;
39
40
  private serializeTransactionEntry;
40
41
  private getHeader;
@@ -73,13 +73,6 @@ let seqNum = 0;
73
73
  function getNextChunkPath(): string {
74
74
  return `${Date.now()}_${seqNum++}_${ourId}.chunk`;
75
75
  }
76
- function sortChunks(chunks: string[]): string[] {
77
- function getChunkParts(chunk: string): unknown[] {
78
- const parts = chunk.split("_");
79
- return parts.map(part => +part);
80
- }
81
- return chunks.sort((a, b) => compareArray(getChunkParts(a), getChunkParts(b)));
82
- }
83
76
 
84
77
  export class TransactionStorage implements IStorage<Buffer> {
85
78
  public cache: Map<string, TransactionEntry> = new Map();
@@ -108,7 +101,7 @@ export class TransactionStorage implements IStorage<Buffer> {
108
101
  });
109
102
  }
110
103
 
111
- private init: Promise<void> | undefined = this.loadAllTransactions();
104
+ private init: Promise<unknown> | undefined = this.loadAllTransactions();
112
105
 
113
106
  private getCurrentChunk(): string {
114
107
  if (this.currentChunk && this.currentChunk.timeCreated < Date.now() - FILE_MAX_LIFETIME) {
@@ -247,33 +240,37 @@ export class TransactionStorage implements IStorage<Buffer> {
247
240
  }
248
241
 
249
242
 
250
- private async loadAllTransactions(): Promise<void> {
251
- if (isInBuild()) return;
243
+ // 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
+ private async loadAllTransactions(): Promise<string[]> {
245
+ if (isInBuild()) return [];
246
+
252
247
  let time = Date.now();
253
248
  const keys = await this.rawStorage.getKeys();
254
249
  const transactionFiles = keys.filter(key => key.endsWith(CHUNK_EXT));
255
250
 
256
- sortChunks(transactionFiles);
257
-
258
- let size = 0;
259
- for (const file of transactionFiles) {
260
- let curSize = await this.loadTransactionFile(file);
261
- size += curSize;
251
+ let entryList: TransactionEntry[][] = [];
252
+ for (let file of transactionFiles) {
253
+ entryList.push(await this.parseTransactionFile(file));
262
254
  }
255
+ let entries = entryList.flat();
256
+ this.applyTransactionEntries(entries);
257
+
263
258
  time = Date.now() - time;
264
259
  if (time > 50) {
265
- console.log(`Loaded ${this.debugName} in ${formatTime(time)}, ${formatNumber(this.cache.size)} keys, from ${formatNumber(transactionFiles.length)} files, ${formatNumber(size)}B`, transactionFiles);
260
+ console.log(`Loaded ${this.debugName} in ${formatTime(time)}, ${formatNumber(this.cache.size)} keys, from ${formatNumber(transactionFiles.length)} files, entries ${formatNumber(entries.length)}B`, transactionFiles);
266
261
  }
267
262
 
268
263
  this.init = undefined;
264
+ return transactionFiles;
269
265
  }
270
266
 
271
- private async loadTransactionFile(filename: string): Promise<number> {
267
+ // ONLY call this inside of loadAllTransactions
268
+ private async parseTransactionFile(filename: string): Promise<TransactionEntry[]> {
272
269
  const fullFile = await this.rawStorage.get(filename);
273
- if (!fullFile) return 0;
270
+ if (!fullFile) return [];
274
271
  if (fullFile.length < 4) {
275
272
  //console.error(`Transaction in ${this.debugName} file ${filename} is too small, skipping`);
276
- return 0;
273
+ return [];
277
274
  }
278
275
  let headerSize = fullFile.readUInt32LE(0);
279
276
  let headerBuffer = fullFile.slice(4, 4 + headerSize);
@@ -282,22 +279,13 @@ export class TransactionStorage implements IStorage<Buffer> {
282
279
  header = JSON.parse(headerBuffer.toString());
283
280
  } catch (e) {
284
281
  console.error(`Failed to parse header of transaction file in ${this.debugName}, ${filename}`);
285
- return 0;
282
+ return [];
286
283
  }
287
284
  let content = fullFile.slice(4 + headerSize);
288
285
  if (header.zipped) {
289
286
  content = await Zip.gunzip(content);
290
287
  }
291
288
 
292
- let pendingWriteTimes = new Map<string, number>();
293
- for (const entry of this.pendingAppends) {
294
- let prevTime = pendingWriteTimes.get(entry.key);
295
- if (prevTime && prevTime > entry.time) {
296
- continue;
297
- }
298
- pendingWriteTimes.set(entry.key, entry.time);
299
- }
300
-
301
289
  let offset = 0;
302
290
  let entries: TransactionEntry[] = [];
303
291
  while (offset < content.length) {
@@ -330,7 +318,21 @@ export class TransactionStorage implements IStorage<Buffer> {
330
318
  let { entry } = entryObj;
331
319
  offset = entryObj.offset;
332
320
  entries.push(entry);
321
+ }
322
+ return entries;
323
+ }
324
+ private applyTransactionEntries(entries: TransactionEntry[]): void {
325
+ let pendingWriteTimes = new Map<string, number>();
326
+ for (const entry of this.pendingAppends) {
327
+ let prevTime = pendingWriteTimes.get(entry.key);
328
+ if (prevTime && prevTime > entry.time) {
329
+ continue;
330
+ }
331
+ pendingWriteTimes.set(entry.key, entry.time);
332
+ }
333
333
 
334
+ sort(entries, x => x.time);
335
+ for (let entry of entries) {
334
336
  let time = entry.time;
335
337
  let prevTime = pendingWriteTimes.get(entry.key);
336
338
  if (prevTime && prevTime > time) {
@@ -347,7 +349,6 @@ export class TransactionStorage implements IStorage<Buffer> {
347
349
  this.cache.set(entry.key, entry);
348
350
  }
349
351
  }
350
- return fullFile.length;
351
352
  }
352
353
 
353
354
  private readTransactionEntry(buffer: Buffer, offset: number): {
@@ -469,11 +470,7 @@ export class TransactionStorage implements IStorage<Buffer> {
469
470
 
470
471
  // Load off disk, in case there are other writes. We still race with them, but at least
471
472
  // this reduces the race condition considerably
472
-
473
- sortChunks(existingDiskEntries);
474
- for (let entry of existingDiskEntries) {
475
- await this.loadTransactionFile(entry);
476
- }
473
+ existingDiskEntries = await this.loadAllTransactions();
477
474
 
478
475
  this.entryCount = this.cache.size;
479
476