@pistonite/pure 0.0.13 → 0.0.15

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.
@@ -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
- }
@@ -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";