@pistonite/pure 0.0.13 → 0.0.14
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/package.json +6 -2
- package/src/pref/dark.ts +21 -47
- package/src/pref/locale.ts +20 -44
- package/src/sync/batch.ts +233 -0
- package/src/sync/cell.ts +60 -0
- package/src/sync/debounce.ts +179 -0
- package/src/sync/index.ts +7 -4
- package/src/sync/latest.ts +85 -0
- package/src/sync/persist.ts +122 -0
- package/src/sync/serial.ts +220 -0
- package/src/sync/util.ts +23 -0
- package/src/sync/Debounce.ts +0 -35
- package/src/sync/Latest.ts +0 -75
- package/src/sync/Serial.ts +0 -170
package/src/sync/Latest.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { Result } from "../result/index.ts";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Latest is a synchronization utility to allow
|
|
5
|
-
* only one async operation to be executed at a time,
|
|
6
|
-
* and any call will only return the result of the latest
|
|
7
|
-
* operation.
|
|
8
|
-
*
|
|
9
|
-
* ## Example
|
|
10
|
-
* In the example below, both call will return the result
|
|
11
|
-
* of the second call (2)
|
|
12
|
-
* ```typescript
|
|
13
|
-
* import { Latest } from "@pistonite/pure/sync";
|
|
14
|
-
*
|
|
15
|
-
* let counter = 0;
|
|
16
|
-
*
|
|
17
|
-
* const operation = async () => {
|
|
18
|
-
* counter++;
|
|
19
|
-
* await new Promise((resolve) => setTimeout(() => {
|
|
20
|
-
* resolve(counter);
|
|
21
|
-
* }, 1000));
|
|
22
|
-
* }
|
|
23
|
-
*
|
|
24
|
-
* const call = new Latest(operation);
|
|
25
|
-
*
|
|
26
|
-
* const result1 = call.execute();
|
|
27
|
-
* const result2 = call.execute();
|
|
28
|
-
* console.log(await result1); // 2
|
|
29
|
-
* console.log(await result2); // 2
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export class Latest<TResult> {
|
|
33
|
-
private isRunning = false;
|
|
34
|
-
private pending: {
|
|
35
|
-
resolve: (result: TResult) => void;
|
|
36
|
-
reject: (error: unknown) => void;
|
|
37
|
-
}[] = [];
|
|
38
|
-
|
|
39
|
-
constructor(private fn: () => Promise<TResult>) {}
|
|
40
|
-
|
|
41
|
-
public async execute(): Promise<TResult> {
|
|
42
|
-
if (this.isRunning) {
|
|
43
|
-
return new Promise<TResult>((resolve, reject) => {
|
|
44
|
-
this.pending.push({ resolve, reject });
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
this.isRunning = true;
|
|
48
|
-
const alreadyPending = [];
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
-
let result: Result<TResult, unknown> = { err: "not executed" };
|
|
51
|
-
// eslint-disable-next-line no-constant-condition
|
|
52
|
-
while (true) {
|
|
53
|
-
try {
|
|
54
|
-
const fn = this.fn;
|
|
55
|
-
result = { val: await fn() };
|
|
56
|
-
} catch (error) {
|
|
57
|
-
result = { err: error };
|
|
58
|
-
}
|
|
59
|
-
if (this.pending.length) {
|
|
60
|
-
alreadyPending.push(...this.pending);
|
|
61
|
-
this.pending = [];
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
this.isRunning = false;
|
|
67
|
-
if ("err" in result) {
|
|
68
|
-
alreadyPending.forEach(({ reject }) => reject(result.err));
|
|
69
|
-
throw result.err;
|
|
70
|
-
} else {
|
|
71
|
-
alreadyPending.forEach(({ resolve }) => resolve(result.val));
|
|
72
|
-
return result.val;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
package/src/sync/Serial.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import type { Void, Err, VoidOk } from "../result/index.ts";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* An async event that can be cancelled when a new one starts
|
|
5
|
-
*
|
|
6
|
-
* ## Example
|
|
7
|
-
*
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { Serial } from "@pistonite/pure/sync";
|
|
10
|
-
*
|
|
11
|
-
* // helper function to simulate async work
|
|
12
|
-
* const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
-
*
|
|
14
|
-
* // Create the event
|
|
15
|
-
* const event = new Serial();
|
|
16
|
-
*
|
|
17
|
-
* // The cancellation system uses the Result type
|
|
18
|
-
* // and returns an error when it is cancelled
|
|
19
|
-
* const promise1 = event.run(async (shouldCancel) => {
|
|
20
|
-
* for (let i = 0; i < 10; i++) {
|
|
21
|
-
* await wait(1000);
|
|
22
|
-
* const cancelResult = shouldCancel();
|
|
23
|
-
* if (cancelResult.err) {
|
|
24
|
-
* return cancelResult;
|
|
25
|
-
* }
|
|
26
|
-
* }
|
|
27
|
-
* return { val: 42 };
|
|
28
|
-
* });
|
|
29
|
-
*
|
|
30
|
-
* await wait(3000);
|
|
31
|
-
*
|
|
32
|
-
* // calling event.run a second time will cause `shouldCancel` to return false
|
|
33
|
-
* // the next time it's called by the first event
|
|
34
|
-
* const promise2 = event.run(async (shouldCancel) => {
|
|
35
|
-
* for (let i = 0; i < 10; i++) {
|
|
36
|
-
* await wait(1000);
|
|
37
|
-
* const cancelResult = shouldCancel();
|
|
38
|
-
* if (cancelResult.err) {
|
|
39
|
-
* return cancelResult;
|
|
40
|
-
* }
|
|
41
|
-
* }
|
|
42
|
-
* return { val: 43 };
|
|
43
|
-
* });
|
|
44
|
-
*
|
|
45
|
-
* console.log(await promise1); // { err: "cancel" }
|
|
46
|
-
* console.log(await promise2); // { val: 43 }
|
|
47
|
-
* ```
|
|
48
|
-
*
|
|
49
|
-
* ## Getting the current serial number
|
|
50
|
-
* The serial number has type `bigint` and is incremented every time `run` is called.
|
|
51
|
-
*
|
|
52
|
-
* The callback function receives the current serial number as the second argument, if you need it
|
|
53
|
-
* ```typescript
|
|
54
|
-
* import { Serial } from "@pistonite/pure/sync";
|
|
55
|
-
*
|
|
56
|
-
* const event = new Serial();
|
|
57
|
-
* const promise = event.run(async (shouldCancel, serial) => {
|
|
58
|
-
* console.log(serial);
|
|
59
|
-
* return {};
|
|
60
|
-
* });
|
|
61
|
-
* ```
|
|
62
|
-
*
|
|
63
|
-
* ## Checking for cancel
|
|
64
|
-
* It's the event handler's responsibility to check if the event is cancelled by
|
|
65
|
-
* calling the `shouldCancel` function. This function returns an `Err` if it should be cancelled.
|
|
66
|
-
*
|
|
67
|
-
* ```typescript
|
|
68
|
-
* import { Serial } from "@pistonite/pure/sync";
|
|
69
|
-
*
|
|
70
|
-
* const event = new Serial();
|
|
71
|
-
* await event.run(async (shouldCancel, serial) => {
|
|
72
|
-
* // do some operations
|
|
73
|
-
* ...
|
|
74
|
-
*
|
|
75
|
-
* const cancelResult = shouldCancel();
|
|
76
|
-
* if (cancelResult.err) {
|
|
77
|
-
* return cancelResult;
|
|
78
|
-
* }
|
|
79
|
-
*
|
|
80
|
-
* // not cancelled, continue
|
|
81
|
-
* ...
|
|
82
|
-
* });
|
|
83
|
-
* ```
|
|
84
|
-
* It's possible the operation is cheap enough that an outdated event should probably be let finish.
|
|
85
|
-
* It's ok in that case to not call `shouldCancel`. The `Serial` class checks it one
|
|
86
|
-
* last time before returning the result after the callback finishes.
|
|
87
|
-
*
|
|
88
|
-
* ## Handling cancelled event
|
|
89
|
-
* To check if an event is completed or cancelled, simply `await`
|
|
90
|
-
* on the promise returned by `event.run` and check the `err`
|
|
91
|
-
* ```typescript
|
|
92
|
-
* import { Serial } from "@pistonite/pure/sync";
|
|
93
|
-
*
|
|
94
|
-
* const event = new Serial();
|
|
95
|
-
* const result = await event.run(async (shouldCancel) => {
|
|
96
|
-
* // your code here ...
|
|
97
|
-
* );
|
|
98
|
-
* if (result.err === "cancel") {
|
|
99
|
-
* console.log("event was cancelled");
|
|
100
|
-
* } else {
|
|
101
|
-
* console.log("event completed");
|
|
102
|
-
* }
|
|
103
|
-
* ```
|
|
104
|
-
*
|
|
105
|
-
* You can also pass in a callback to the constructor, which will be called
|
|
106
|
-
* when the event is cancelled. This event is guaranteed to fire at most once per run
|
|
107
|
-
* ```typescript
|
|
108
|
-
* import { Serial } from "@pistonite/pure/sync";
|
|
109
|
-
*
|
|
110
|
-
* const event = new Serial((current, latest) => {
|
|
111
|
-
* console.log(`Event with serial ${current} is cancelled because the latest serial is ${latest}`);
|
|
112
|
-
* });
|
|
113
|
-
* ```
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*/
|
|
117
|
-
export class Serial {
|
|
118
|
-
private serial: bigint;
|
|
119
|
-
private onCancel: SerialEventCancelCallback;
|
|
120
|
-
|
|
121
|
-
constructor(onCancel?: SerialEventCancelCallback) {
|
|
122
|
-
this.serial = 0n;
|
|
123
|
-
if (onCancel) {
|
|
124
|
-
this.onCancel = onCancel;
|
|
125
|
-
} else {
|
|
126
|
-
this.onCancel = () => {};
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
public async run<T = VoidOk>(
|
|
131
|
-
callback: SerialEventCallback<T>,
|
|
132
|
-
): Promise<T | Err<SerialEventCancelToken>> {
|
|
133
|
-
let cancelled = false;
|
|
134
|
-
const currentSerial = ++this.serial;
|
|
135
|
-
const shouldCancel = () => {
|
|
136
|
-
if (currentSerial !== this.serial) {
|
|
137
|
-
if (!cancelled) {
|
|
138
|
-
cancelled = true;
|
|
139
|
-
this.onCancel(currentSerial, this.serial);
|
|
140
|
-
}
|
|
141
|
-
return { err: "cancel" as const };
|
|
142
|
-
}
|
|
143
|
-
return {};
|
|
144
|
-
};
|
|
145
|
-
const result = await callback(shouldCancel, currentSerial);
|
|
146
|
-
const cancelResult = shouldCancel();
|
|
147
|
-
if (cancelResult.err) {
|
|
148
|
-
return cancelResult;
|
|
149
|
-
}
|
|
150
|
-
return result;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* The callback type passed to SerialEvent constructor to be called
|
|
156
|
-
* when the event is cancelled
|
|
157
|
-
*/
|
|
158
|
-
export type SerialEventCancelCallback = (
|
|
159
|
-
current: bigint,
|
|
160
|
-
latest: bigint,
|
|
161
|
-
) => void;
|
|
162
|
-
|
|
163
|
-
/** The callback type passed to SerialEvent.run */
|
|
164
|
-
export type SerialEventCallback<T = VoidOk> = (
|
|
165
|
-
shouldCancel: () => Void<SerialEventCancelToken>,
|
|
166
|
-
current: bigint,
|
|
167
|
-
) => Promise<T | Err<SerialEventCancelToken>>;
|
|
168
|
-
|
|
169
|
-
/** The error type received by caller when an event is cancelled */
|
|
170
|
-
export type SerialEventCancelToken = "cancel";
|