@pistonite/pure 0.28.0 → 0.29.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/LICENSE +1 -1
- package/README.md +23 -4
- package/dist/log/index.js +57 -0
- package/dist/log/index.js.map +1 -0
- package/dist/memory/index.js +92 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/result/index.js +29 -0
- package/dist/result/index.js.map +1 -0
- package/dist/sync/index.js +252 -0
- package/dist/sync/index.js.map +1 -0
- package/package.json +22 -13
- package/src/env.d.ts +1 -0
- package/src/log/index.ts +36 -11
- package/src/log/logger.ts +93 -115
- package/src/memory/cell.ts +21 -11
- package/src/memory/emp.ts +34 -23
- package/src/memory/idgen.test.ts +1 -1
- package/src/memory/index.ts +1 -4
- package/src/memory/persist.ts +9 -12
- package/src/result/index.ts +12 -4
- package/src/sync/batch.test.ts +1 -1
- package/src/sync/batch.ts +12 -17
- package/src/sync/capture.ts +2 -0
- package/src/sync/debounce.test.ts +1 -1
- package/src/sync/debounce.ts +12 -15
- package/src/sync/index.ts +2 -3
- package/src/sync/latest.test.ts +1 -1
- package/src/sync/latest.ts +19 -16
- package/src/sync/once.test.ts +1 -1
- package/src/sync/once.ts +13 -8
- package/src/sync/serial.test.ts +1 -1
- package/src/sync/serial.ts +14 -12
- package/src/sync/util.ts +2 -2
- package/src/fs/FsError.ts +0 -55
- package/src/fs/FsFile.ts +0 -67
- package/src/fs/FsFileImpl.ts +0 -219
- package/src/fs/FsFileMgr.ts +0 -29
- package/src/fs/FsFileStandalone.ts +0 -21
- package/src/fs/FsFileStandaloneImplFileAPI.ts +0 -54
- package/src/fs/FsFileStandaloneImplHandleAPI.ts +0 -147
- package/src/fs/FsFileSystem.ts +0 -71
- package/src/fs/FsFileSystemInternal.ts +0 -30
- package/src/fs/FsImplEntryAPI.ts +0 -149
- package/src/fs/FsImplFileAPI.ts +0 -116
- package/src/fs/FsImplHandleAPI.ts +0 -199
- package/src/fs/FsOpen.ts +0 -271
- package/src/fs/FsOpenFile.ts +0 -256
- package/src/fs/FsPath.ts +0 -137
- package/src/fs/FsSave.ts +0 -216
- package/src/fs/FsSupportStatus.ts +0 -87
- package/src/fs/index.ts +0 -123
- package/src/log/internal.ts +0 -14
- package/src/memory/async_erc.ts +0 -186
- package/src/memory/erc.test.ts +0 -258
- package/src/memory/erc.ts +0 -320
- package/src/pref/dark.ts +0 -151
- package/src/pref/device.ts +0 -118
- package/src/pref/index.ts +0 -13
- package/src/pref/inject_style.ts +0 -22
- package/src/pref/locale.ts +0 -296
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
# pure
|
|
2
2
|
|
|
3
|
-
Pure TypeScript utility
|
|
3
|
+
Pure TypeScript utility libraries for my projects
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
are not stable by any means. However, feel free to use or reference them.
|
|
5
|
+
## Documentation
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
https://pure.pistonite.dev
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
Install the package from NPM
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @pistonite/pure
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For React hooks
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @pistonite/pure-react
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Import the things you need
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import type { Result } from "@pistonite/pure/result";
|
|
27
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { errstr as e } from "../result/index.js";
|
|
2
|
+
//#region src/log/logger.ts
|
|
3
|
+
var t = {
|
|
4
|
+
off: 0,
|
|
5
|
+
default: 1,
|
|
6
|
+
info: 2,
|
|
7
|
+
debug: 3
|
|
8
|
+
}, n = (e, n) => {
|
|
9
|
+
let { color: a, level: o } = n, s = t[o || "off"] || t.off;
|
|
10
|
+
return globalThis.process ? new r(e, s) : new i(e, a || "gray", s);
|
|
11
|
+
}, r = class {
|
|
12
|
+
constructor(e, t) {
|
|
13
|
+
this.name = e, this.level = t;
|
|
14
|
+
}
|
|
15
|
+
setLevel(e) {
|
|
16
|
+
this.level = t[e] || t.off;
|
|
17
|
+
}
|
|
18
|
+
debug(e) {
|
|
19
|
+
this.level === t.debug && console.debug(`DEBUG [${this.name}] ${e}`);
|
|
20
|
+
}
|
|
21
|
+
info(e) {
|
|
22
|
+
this.level < t.info || console.info(`INFO [${this.name}] ${e}`);
|
|
23
|
+
}
|
|
24
|
+
warn(e) {
|
|
25
|
+
this.level < t.default || console.warn(`WARN [${this.name}] ${e}`);
|
|
26
|
+
}
|
|
27
|
+
error(n) {
|
|
28
|
+
if (this.level < t.default) return;
|
|
29
|
+
let r = e(n);
|
|
30
|
+
console.error(`ERROR [${this.name}] ${r}`), r !== n && console.error(n);
|
|
31
|
+
}
|
|
32
|
+
}, i = class {
|
|
33
|
+
constructor(e, t, n) {
|
|
34
|
+
this.name = e, this.color = t, this.level = n;
|
|
35
|
+
}
|
|
36
|
+
setLevel(e) {
|
|
37
|
+
this.level = t[e] || t.off;
|
|
38
|
+
}
|
|
39
|
+
debug(e) {
|
|
40
|
+
this.level === t.debug && console.debug(`%cDEBUG%c${this.name}%c ${e}`, "background:gray;color:white;padding:0 3px", this.color, "color:inherit;background:inherit");
|
|
41
|
+
}
|
|
42
|
+
info(e) {
|
|
43
|
+
this.level < t.info || console.info(`%cINFO%c${this.name}%c ${e}`, "background:green;color:white;padding:0 3px", this.color, "color:inherit;background:inherit");
|
|
44
|
+
}
|
|
45
|
+
warn(e) {
|
|
46
|
+
this.level < t.default || console.warn(`%cWARN%c${this.name}%c ${e}`, "background:orange;color:white;padding:0 3px", this.color, "color:inherit;background:inherit");
|
|
47
|
+
}
|
|
48
|
+
error(n) {
|
|
49
|
+
if (this.level < t.default) return;
|
|
50
|
+
let r = e(n);
|
|
51
|
+
console.error(`%cERROR%c${this.name}%c ${r}`, "background:darkred;color:white;padding:0 3px", this.color, "color:inherit;background:inherit"), r !== n && console.error(n);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
export { n as logger };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/log/logger.ts"],"sourcesContent":["import { errstr } from \"../result/index.ts\";\n\n/**\n * String-enum for logging levels\n *\n * off - no logging at all\n * default - warning and errors only\n * info - warning, errors, info\n * debug - warning, errors, info, debug\n */\n\nconst LogLevel = { off: 0, default: 1, info: 2, debug: 3 } as const;\nexport type LogLevelStr = \"off\" | \"default\" | \"info\" | \"debug\";\nif (import.meta.vitest) {\n const { test, expectTypeOf } = import.meta.vitest;\n test(\"type LogLevelStr\", () => {\n expectTypeOf<LogLevelStr>().toEqualTypeOf<keyof typeof LogLevel>();\n });\n}\ntype LogLevel = (typeof LogLevel)[LogLevelStr];\n\n/** Args for constructing a logger */\nexport interface LoggerConstructor {\n /** CSS Color for the logger, default is 'gray' */\n color?: string;\n /**\n * Logging level, default is \"default\".\n * The level can still be changed later with setLevel\n */\n level?: LogLevelStr;\n}\n\n/** The logger type */\nexport interface Logger {\n /** Set the level of the logger */\n setLevel(level: LogLevelStr): void;\n /** Log a debug message */\n debug(obj: unknown): void;\n /** Log an info message */\n info(obj: unknown): void;\n /** Log a warning message */\n warn(obj: unknown): void;\n /** Log an error message */\n error(obj: unknown): void;\n}\n\n/** Create a logger creator. Use the factory methods to finish making the logger */\nexport const logger = (name: string, args: LoggerConstructor): Logger => {\n const { color, level } = args;\n const levelObj = LogLevel[level || \"off\"] || LogLevel.off;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if ((globalThis as any).process) {\n return new BareLoggerImpl(name, levelObj);\n }\n const color2 = color || \"gray\";\n return new CssLoggerImpl(name, color2, levelObj);\n};\n\nclass BareLoggerImpl implements Logger {\n constructor(\n private name: string,\n private level: LogLevel,\n ) {}\n setLevel(level: LogLevelStr): void {\n this.level = LogLevel[level] || LogLevel[\"off\"];\n }\n debug(obj: unknown): void {\n if (this.level !== LogLevel.debug) {\n return;\n }\n console.debug(`DEBUG [${this.name}] ${obj}`);\n }\n info(obj: unknown): void {\n if (this.level < LogLevel.info) {\n return;\n }\n console.info(`INFO [${this.name}] ${obj}`);\n }\n warn(obj: unknown): void {\n if (this.level < LogLevel.default) {\n return;\n }\n console.warn(`WARN [${this.name}] ${obj}`);\n }\n error(obj: unknown): void {\n if (this.level < LogLevel.default) {\n return;\n }\n const msg = errstr(obj);\n console.error(`ERROR [${this.name}] ${msg}`);\n if (msg !== obj) {\n console.error(obj);\n }\n }\n}\n\nclass CssLoggerImpl implements Logger {\n constructor(\n private name: string,\n private color: string,\n private level: LogLevel,\n ) {}\n setLevel(level: LogLevelStr): void {\n this.level = LogLevel[level] || LogLevel[\"off\"];\n }\n debug(obj: unknown): void {\n if (this.level !== LogLevel.debug) {\n return;\n }\n console.debug(\n `%cDEBUG%c${this.name}%c ${obj}`,\n \"background:gray;color:white;padding:0 3px\",\n this.color,\n \"color:inherit;background:inherit\",\n );\n }\n info(obj: unknown): void {\n if (this.level < LogLevel.info) {\n return;\n }\n console.info(\n `%cINFO%c${this.name}%c ${obj}`,\n \"background:green;color:white;padding:0 3px\",\n this.color,\n \"color:inherit;background:inherit\",\n );\n }\n warn(obj: unknown): void {\n if (this.level < LogLevel.default) {\n return;\n }\n console.warn(\n `%cWARN%c${this.name}%c ${obj}`,\n \"background:orange;color:white;padding:0 3px\",\n this.color,\n \"color:inherit;background:inherit\",\n );\n }\n error(obj: unknown): void {\n if (this.level < LogLevel.default) {\n return;\n }\n const msg = errstr(obj);\n console.error(\n `%cERROR%c${this.name}%c ${msg}`,\n \"background:darkred;color:white;padding:0 3px\",\n this.color,\n \"color:inherit;background:inherit\",\n );\n if (msg !== obj) {\n console.error(obj);\n }\n }\n}\n"],"mappings":";;AAWA,IAAM,IAAW;CAAE,KAAK;CAAG,SAAS;CAAG,MAAM;CAAG,OAAO;CAAG,EAoC7C,KAAU,GAAc,MAAoC;CACrE,IAAM,EAAE,UAAO,aAAU,GACnB,IAAW,EAAS,KAAS,UAAU,EAAS;AAMtD,QAJK,WAAmB,UACb,IAAI,EAAe,GAAM,EAAS,GAGtC,IAAI,EAAc,GADV,KAAS,QACe,EAAS;GAG9C,IAAN,MAAuC;CACnC,YACI,GACA,GACF;AADU,EADA,KAAA,OAAA,GACA,KAAA,QAAA;;CAEZ,SAAS,GAA0B;AAC/B,OAAK,QAAQ,EAAS,MAAU,EAAS;;CAE7C,MAAM,GAAoB;AAClB,OAAK,UAAU,EAAS,SAG5B,QAAQ,MAAM,UAAU,KAAK,KAAK,IAAI,IAAM;;CAEhD,KAAK,GAAoB;AACjB,OAAK,QAAQ,EAAS,QAG1B,QAAQ,KAAK,SAAS,KAAK,KAAK,IAAI,IAAM;;CAE9C,KAAK,GAAoB;AACjB,OAAK,QAAQ,EAAS,WAG1B,QAAQ,KAAK,SAAS,KAAK,KAAK,IAAI,IAAM;;CAE9C,MAAM,GAAoB;AACtB,MAAI,KAAK,QAAQ,EAAS,QACtB;EAEJ,IAAM,IAAM,EAAO,EAAI;AAEvB,EADA,QAAQ,MAAM,UAAU,KAAK,KAAK,IAAI,IAAM,EACxC,MAAQ,KACR,QAAQ,MAAM,EAAI;;GAKxB,IAAN,MAAsC;CAClC,YACI,GACA,GACA,GACF;AADU,EAFA,KAAA,OAAA,GACA,KAAA,QAAA,GACA,KAAA,QAAA;;CAEZ,SAAS,GAA0B;AAC/B,OAAK,QAAQ,EAAS,MAAU,EAAS;;CAE7C,MAAM,GAAoB;AAClB,OAAK,UAAU,EAAS,SAG5B,QAAQ,MACJ,YAAY,KAAK,KAAK,KAAK,KAC3B,6CACA,KAAK,OACL,mCACH;;CAEL,KAAK,GAAoB;AACjB,OAAK,QAAQ,EAAS,QAG1B,QAAQ,KACJ,WAAW,KAAK,KAAK,KAAK,KAC1B,8CACA,KAAK,OACL,mCACH;;CAEL,KAAK,GAAoB;AACjB,OAAK,QAAQ,EAAS,WAG1B,QAAQ,KACJ,WAAW,KAAK,KAAK,KAAK,KAC1B,+CACA,KAAK,OACL,mCACH;;CAEL,MAAM,GAAoB;AACtB,MAAI,KAAK,QAAQ,EAAS,QACtB;EAEJ,IAAM,IAAM,EAAO,EAAI;AAOvB,EANA,QAAQ,MACJ,YAAY,KAAK,KAAK,KAAK,KAC3B,gDACA,KAAK,OACL,mCACH,EACG,MAAQ,KACR,QAAQ,MAAM,EAAI"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//#region src/memory/cell.ts
|
|
2
|
+
var e = (e) => new t(e.initial), t = class {
|
|
3
|
+
subscribers = [];
|
|
4
|
+
constructor(e) {
|
|
5
|
+
this.value = e;
|
|
6
|
+
}
|
|
7
|
+
get() {
|
|
8
|
+
return this.value;
|
|
9
|
+
}
|
|
10
|
+
set(e) {
|
|
11
|
+
if (this.value === e) return;
|
|
12
|
+
this.value = e;
|
|
13
|
+
let t = this.subscribers.length;
|
|
14
|
+
for (let n = 0; n < t; n++) {
|
|
15
|
+
let t = this.subscribers[n];
|
|
16
|
+
t(e);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
subscribe(e, t) {
|
|
20
|
+
return this.subscribers.push(e), t && e(this.value), () => {
|
|
21
|
+
let t = this.subscribers.indexOf(e);
|
|
22
|
+
t >= 0 && this.subscribers.splice(t, 1);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/memory/persist.ts
|
|
28
|
+
function n(e) {
|
|
29
|
+
let { storage: t, key: n, serialize: i = JSON.stringify, deserialize: a, initial: o } = e;
|
|
30
|
+
return new r(t, n, i, a ?? ((e) => {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(e);
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}), o);
|
|
37
|
+
}
|
|
38
|
+
var r = class {
|
|
39
|
+
cell;
|
|
40
|
+
unsubscribe;
|
|
41
|
+
constructor(t, n, r, i, a) {
|
|
42
|
+
this.storage = t, this.key = n, this.serialize = r, this.deserialize = i, this.cell = e({ initial: a }), this.unsubscribe = () => {};
|
|
43
|
+
}
|
|
44
|
+
init(e) {
|
|
45
|
+
let t = this.serialize, n = this.deserialize, r;
|
|
46
|
+
r = e === void 0 ? this.cell.get() : e;
|
|
47
|
+
try {
|
|
48
|
+
let e = this.storage.getItem(this.key);
|
|
49
|
+
if (e !== null) {
|
|
50
|
+
let t = n(e);
|
|
51
|
+
t !== null && (r = t);
|
|
52
|
+
}
|
|
53
|
+
} catch {}
|
|
54
|
+
return this.cell.set(r), this.unsubscribe = this.cell.subscribe((e) => {
|
|
55
|
+
this.storage.setItem(this.key, t(e));
|
|
56
|
+
}), r;
|
|
57
|
+
}
|
|
58
|
+
get() {
|
|
59
|
+
return this.cell.get();
|
|
60
|
+
}
|
|
61
|
+
set(e) {
|
|
62
|
+
this.cell.set(e);
|
|
63
|
+
}
|
|
64
|
+
subscribe(e, t) {
|
|
65
|
+
return this.cell.subscribe(e, t);
|
|
66
|
+
}
|
|
67
|
+
clear() {
|
|
68
|
+
this.storage.removeItem(this.key);
|
|
69
|
+
}
|
|
70
|
+
disable() {
|
|
71
|
+
this.clear(), this.unsubscribe();
|
|
72
|
+
}
|
|
73
|
+
}, i = (e) => {
|
|
74
|
+
let { free: t } = e, n = new FinalizationRegistry(t);
|
|
75
|
+
return (e) => {
|
|
76
|
+
let t = Object.freeze({ value: e });
|
|
77
|
+
return n.register(t, e), t;
|
|
78
|
+
};
|
|
79
|
+
}, a = (e) => {
|
|
80
|
+
let t = 1;
|
|
81
|
+
return () => (t += 1, t >= e && (t = 1), t);
|
|
82
|
+
}, o = () => {
|
|
83
|
+
let e = 1n;
|
|
84
|
+
return () => (e += 1n, e);
|
|
85
|
+
}, s = () => {
|
|
86
|
+
let e = 1;
|
|
87
|
+
return () => (e += 1, e);
|
|
88
|
+
};
|
|
89
|
+
//#endregion
|
|
90
|
+
export { o as bidgen, e as cell, s as idgen, i as makeEmpType, n as persist, a as safeidgen };
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/memory/cell.ts","../../src/memory/persist.ts","../../src/memory/emp.ts","../../src/memory/idgen.ts"],"sourcesContent":["/** Create a {@link Cell} */\nexport const cell = <T>(args: CellConstructor<T>): Cell<T> => {\n return new CellImpl(args.initial);\n};\n\n/** Args for constructing a cell */\nexport interface CellConstructor<T> {\n /** Initial value */\n initial: T;\n}\n\n/**\n * A light weight storage wrapper around a value\n * that can be subscribed to for changes\n *\n * Created via `cell()`\n *\n * ```typescript\n * import { cell } from \"@pistonite/pure/memory\";\n *\n * const myCell = cell(true);\n * ```\n */\nexport interface Cell<T> {\n get(): T;\n set(value: T): void;\n subscribe(callback: (value: T) => void, notifyImmediately?: boolean): () => void;\n}\n\nclass CellImpl<T> implements Cell<T> {\n private subscribers: ((value: T) => void)[] = [];\n\n constructor(private value: T) {}\n\n public get(): T {\n return this.value;\n }\n\n public set(value: T): void {\n if (this.value === value) {\n return;\n }\n this.value = value;\n const len = this.subscribers.length;\n for (let i = 0; i < len; i++) {\n const callback = this.subscribers[i];\n callback(value);\n }\n }\n\n public subscribe(callback: (value: T) => void, notifyImmediately?: boolean): () => void {\n this.subscribers.push(callback);\n const unsubscribe = () => {\n const index = this.subscribers.indexOf(callback);\n if (index >= 0) {\n this.subscribers.splice(index, 1);\n }\n };\n if (notifyImmediately) {\n callback(this.value);\n }\n return unsubscribe;\n }\n}\n","import { cell, type Cell, type CellConstructor } from \"./cell.ts\";\n\n/**\n * Create a cell that persists its value to a web storage\n */\nexport function persist<T>(args: PersistConstructor<T>): Persist<T> {\n const { storage, key, serialize = JSON.stringify, deserialize, initial } = args;\n const deser =\n deserialize ??\n ((value: string) => {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n });\n return new PersistImpl(storage, key, serialize, deser, initial);\n}\n\n/** Args for creating a persisted cell */\nexport interface PersistConstructor<T> extends CellConstructor<T> {\n /** The web storage to use */\n storage: Storage;\n\n /** The key to use in the storage */\n key: string;\n\n /**\n * Serialize the value to store in the storage\n *\n * By default, it will use `JSON.stringify`\n */\n serialize?(value: T): string;\n /**\n * Deserialize the value from the storage\n *\n * By default, it will use `JSON.parse` wrapped with try-catch\n */\n deserialize?(value: string): T | null;\n}\n\n/** A cell that also persists its value */\nexport interface Persist<T> extends Cell<T> {\n /**\n * Load the value initially, and notify all the current subscribers\n *\n * Optionally, you can pass an initial value to override the current value\n */\n init(initial?: T): T;\n /** Clear the value from the storage */\n clear(): void;\n /** Clear the value and disable the persistence */\n disable(): void;\n}\n\nclass PersistImpl<T> implements Persist<T> {\n private cell: Cell<T>;\n private unsubscribe: () => void;\n\n constructor(\n private storage: Storage,\n private key: string,\n private serialize: (value: T) => string,\n private deserialize: (value: string) => T | null,\n initial: T,\n ) {\n this.cell = cell({ initial });\n this.unsubscribe = () => {};\n }\n\n public init(initial?: T): T {\n const serialize = this.serialize;\n const deserialize = this.deserialize;\n let value: T;\n if (initial !== undefined) {\n value = initial;\n } else {\n value = this.cell.get();\n }\n try {\n const data = this.storage.getItem(this.key);\n if (data !== null) {\n const loadedValue = deserialize(data);\n if (loadedValue !== null) {\n value = loadedValue;\n }\n }\n } catch {\n /* ignore */\n }\n this.cell.set(value);\n this.unsubscribe = this.cell.subscribe((value) => {\n this.storage.setItem(this.key, serialize(value));\n });\n return value;\n }\n\n public get(): T {\n return this.cell.get();\n }\n\n public set(value: T): void {\n this.cell.set(value);\n }\n\n public subscribe(callback: (value: T) => void, notifyImmediately?: boolean): () => void {\n return this.cell.subscribe(callback, notifyImmediately);\n }\n\n public clear() {\n this.storage.removeItem(this.key);\n }\n\n public disable() {\n this.clear();\n this.unsubscribe();\n }\n}\n","/**\n * A non-null, **E**ngine-**m**anaged **P**ointer\n *\n * This uses the ECMA FinalizationRegistry, to free the resource,\n * once this object is garbage-collected.\n *\n * The free function may be async.\n *\n * ## Use case\n * When JS interoperates with a system language like C/C++ or Rust,\n * it is often needed to transfer objects into JS context. If the object\n * is big, copy-transfer could be expensive, so the more ideal solution\n * is to transfer a \"handle\", or a pointer, to JS, and leaving the actual\n * object in the native memory. Then, JS code and pass the pointer to external\n * code to use or extract data from the object when needed.\n *\n * This approach is just like passing raw pointers in C, which means it is\n * extremely succeptible to memory corruption bugs like double-free or\n * use-after-free. Unlike C++ or Rust, JS also doesn't have destructors\n * that run when an object leaves the scope (proposal for the `using` keyword exists,\n * but you can actually double-free the object with `using`).\n *\n * C-style manual memory management might be sufficient for simple bindings,\n * but for more complex scenarios, automatic memory management is needed,\n * by tying the free call to the GC of a JS object.\n *\n * ## Recommended practice\n * 1. The external code should transfer the ownership of the object\n * to JS when passing it to JS. JS will then put the handle into an Emp\n * to be automatically managed. This means the external code should now\n * never free the object, and let JS free it instead.\n * 2. The inner value of the Emp must only be used for calling\n * external code, and the Emp must be kept alive for all runtimes, including\n * those with heavy optimization that may reclaim the Emp during the call,\n * if it's not referenced afterwards. See {@link scopedCapture}.\n * The inner value must not dangle around outside of the Emp that owns it.\n *\n * ## Pointer Size\n * In 32-bit context like WASM32, the inner value can be a `number`.\n * In 64-bit context, `number` might be fine for some systems, but `bigint`\n * is recommended.\n *\n * ## Usage\n *\n * ```typescript\n * // First create a marker to distinguish between different Emp types for TypeScript.\n * // This is not used at runtime\n * const MyNativeType = Symbol(\"MyNativeType\");\n * export type MyNativeType = typeof MyNativeType;\n *\n * // Then use makeEmpType to create a factory function\n * const makeMyNativeTypeEmp = makeEmpType({\n * marker: MyNativeType,\n * free: (ptr) => freeMyNativeType(ptr),\n * });\n *\n * // Now acquire ownership of an object from external native code\n * // and assign it to an Emp\n * const myObjRawPtr = allocMyNativeType();\n * const myObj = makeMyNativeTypeEmp(myObjRawPtr);\n *\n * // when myObj is GC'ed, it will be freed by calling freeMyNativeType\n * ```\n */\nexport interface Emp<T, TRepr> {\n /** The type marker for T. This only marks the type for TypeScript and does not exist at runtime */\n readonly __phantom: T;\n /** The underlying pointer value */\n readonly value: TRepr;\n}\n\n/** Args for constructing a Emp type */\nexport interface EmpConstructor<T, TRepr> {\n /**\n * The marker for the Emp type, used to distinguish between multiple Emp types\n * in TypeScript\n */\n marker?: T;\n\n /**\n * Function to free the underlying object. Called when this Emp is garbage-collected\n */\n free: (ptr: TRepr) => void | Promise<void>;\n}\n\n/**\n * Create a factory function for an Emp type. See {@link Emp}\n */\nexport const makeEmpType = <T, TRepr>(\n args: EmpConstructor<T, TRepr>,\n): ((ptr: TRepr) => Emp<T, TRepr>) => {\n const { free } = args;\n const registry = new FinalizationRegistry(free);\n return (ptr: TRepr) => {\n const obj = Object.freeze({ value: ptr });\n registry.register(obj, ptr);\n return obj as Emp<T, TRepr>;\n };\n};\n","/**\n * Return an id generator that will generate ids in order\n * from 1 to n, wrapping around to 1 when it's n\n */\nexport const safeidgen = (n: number): (() => number) => {\n let x = 1;\n return () => {\n x += 1;\n if (x >= n) {\n x = 1;\n }\n return x;\n };\n};\n\n/**\n * Return an id generator that returns bigint,\n * starting from 1n, and will always return a new bigint\n */\nexport const bidgen = (): (() => bigint) => {\n let x = 1n;\n return () => {\n x += 1n;\n return x;\n };\n};\n\n/**\n * Returns an id generator that returns number staring from 1\n * and always increasing. The number could become inaccurate\n * (not integer) when exceeding Number.MAX_SAFE_INTEGER (which\n * is 2^53 - 1\n */\nexport const idgen = (): (() => number) => {\n let x = 1;\n return () => {\n x += 1;\n return x;\n };\n};\n"],"mappings":";AACA,IAAa,KAAW,MACb,IAAI,EAAS,EAAK,QAAQ,EA2B/B,IAAN,MAAqC;CACjC,cAA8C,EAAE;CAEhD,YAAY,GAAkB;AAAV,OAAA,QAAA;;CAEpB,MAAgB;AACZ,SAAO,KAAK;;CAGhB,IAAW,GAAgB;AACvB,MAAI,KAAK,UAAU,EACf;AAEJ,OAAK,QAAQ;EACb,IAAM,IAAM,KAAK,YAAY;AAC7B,OAAK,IAAI,IAAI,GAAG,IAAI,GAAK,KAAK;GAC1B,IAAM,IAAW,KAAK,YAAY;AAClC,KAAS,EAAM;;;CAIvB,UAAiB,GAA8B,GAAyC;AAWpF,SAVA,KAAK,YAAY,KAAK,EAAS,EAO3B,KACA,EAAS,KAAK,MAAM,QAPE;GACtB,IAAM,IAAQ,KAAK,YAAY,QAAQ,EAAS;AAChD,GAAI,KAAS,KACT,KAAK,YAAY,OAAO,GAAO,EAAE;;;;;;AClDjD,SAAgB,EAAW,GAAyC;CAChE,IAAM,EAAE,YAAS,QAAK,eAAY,KAAK,WAAW,gBAAa,eAAY;AAU3E,QAAO,IAAI,EAAY,GAAS,GAAK,GARjC,OACE,MAAkB;AAChB,MAAI;AACA,UAAO,KAAK,MAAM,EAAM;UACpB;AACJ,UAAO;;KAGoC,EAAQ;;AAuCnE,IAAM,IAAN,MAA2C;CACvC;CACA;CAEA,YACI,GACA,GACA,GACA,GACA,GACF;AAEE,EAPQ,KAAA,UAAA,GACA,KAAA,MAAA,GACA,KAAA,YAAA,GACA,KAAA,cAAA,GAGR,KAAK,OAAO,EAAK,EAAE,YAAS,CAAC,EAC7B,KAAK,oBAAoB;;CAG7B,KAAY,GAAgB;EACxB,IAAM,IAAY,KAAK,WACjB,IAAc,KAAK,aACrB;AACJ,EACI,IADA,MAAY,KAAA,IAGJ,KAAK,KAAK,KAAK,GAFf;AAIZ,MAAI;GACA,IAAM,IAAO,KAAK,QAAQ,QAAQ,KAAK,IAAI;AAC3C,OAAI,MAAS,MAAM;IACf,IAAM,IAAc,EAAY,EAAK;AACrC,IAAI,MAAgB,SAChB,IAAQ;;UAGZ;AAOR,SAJA,KAAK,KAAK,IAAI,EAAM,EACpB,KAAK,cAAc,KAAK,KAAK,WAAW,MAAU;AAC9C,QAAK,QAAQ,QAAQ,KAAK,KAAK,EAAU,EAAM,CAAC;IAClD,EACK;;CAGX,MAAgB;AACZ,SAAO,KAAK,KAAK,KAAK;;CAG1B,IAAW,GAAgB;AACvB,OAAK,KAAK,IAAI,EAAM;;CAGxB,UAAiB,GAA8B,GAAyC;AACpF,SAAO,KAAK,KAAK,UAAU,GAAU,EAAkB;;CAG3D,QAAe;AACX,OAAK,QAAQ,WAAW,KAAK,IAAI;;CAGrC,UAAiB;AAEb,EADA,KAAK,OAAO,EACZ,KAAK,aAAa;;GC3Bb,KACT,MACkC;CAClC,IAAM,EAAE,YAAS,GACX,IAAW,IAAI,qBAAqB,EAAK;AAC/C,SAAQ,MAAe;EACnB,IAAM,IAAM,OAAO,OAAO,EAAE,OAAO,GAAK,CAAC;AAEzC,SADA,EAAS,SAAS,GAAK,EAAI,EACpB;;GC5FF,KAAa,MAA8B;CACpD,IAAI,IAAI;AACR,eACI,KAAK,GACD,KAAK,MACL,IAAI,IAED;GAQF,UAA+B;CACxC,IAAI,IAAI;AACR,eACI,KAAK,IACE;GAUF,UAA8B;CACvC,IAAI,IAAI;AACR,eACI,KAAK,GACE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/result/index.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
try {
|
|
4
|
+
return { val: e() };
|
|
5
|
+
} catch (e) {
|
|
6
|
+
return { err: e };
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
async function t(e) {
|
|
10
|
+
try {
|
|
11
|
+
return { val: await e() };
|
|
12
|
+
} catch (e) {
|
|
13
|
+
return { err: e };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function n(e, t) {
|
|
17
|
+
if (typeof e == "string") return e;
|
|
18
|
+
if (!e) return `${e}`;
|
|
19
|
+
if (typeof e == "object" && "message" in e) return t ? `${e.message}` : n(e.message, !0);
|
|
20
|
+
if (typeof e == "object" && "toString" in e) {
|
|
21
|
+
let r = e.toString();
|
|
22
|
+
return t ? `${r}` : n(r, !0);
|
|
23
|
+
}
|
|
24
|
+
return typeof e == "object" && "msg" in e ? t ? `${e.msg}` : n(e.msg, !0) : typeof e == "object" && "code" in e ? t ? `${e.code}` : `error code: ${n(e.code, !0)}` : `${e}`;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { n as errstr, t as tryAsync, e as tryCatch };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/result/index.ts"],"sourcesContent":["/**\n * Rust-like `Result<T, E>` type and error handling utils\n *\n * **I once had a fancy error object with TypeScript magic that tries\n * to reduce allocation while maintaining Result-safety. It turns out\n * that was slower than allocating plain objects for every return, because\n * of how V8 optimizes things.**\n *\n * Don't even use `isErr()` helper functions to abstract. They are slower than\n * directly property access in my testing.\n *\n * ## Function that can fail\n * Instead of having functions `throw`, make it `return` instead.\n * ```typescript\n * // Instead of\n * function doSomethingCanFail() {\n * if (Math.random() < 0.5) {\n * return 42;\n * }\n * throw \"oops\";\n * }\n * // Do this\n * import type { Result } from \"pure/result\";\n *\n * function doSomethingCanFail(): Result<number, string> {\n * if (Math.random() < 0.5) {\n * return { val: 42 };\n * }\n * return { err: \"oops\" };\n * }\n * ```\n * This is similar to Rust:\n * ```rust\n * fn do_something_can_fail() -> Result<u32, String> {\n * if ... {\n * return Ok(42);\n * }\n *\n * Err(\"oops\".to_string())\n * }\n * ```\n *\n * ## Calling function that can fail\n * The recommended pattern is\n * ```typescript\n * const x = doTheCall(); // x is Result<T, E>;\n * if (x.err) {\n * // x.err is E, handle it\n * return ...\n * }\n * // x.val is T\n * // ...\n * ```\n * If your `E` type covers falsy values that are valid, use `\"err\" in x` instead of `x.err`.\n * A well-known case is `Result<T, unknown>`. `if(r.err)` cannot narrow the else case to `Ok`,\n * but `if(\"err\" in r)` can.\n *\n * A full example:\n * ```typescript\n * function getParam(name: string): Result<number, Error> {\n * if (name === \"a\") {\n * return { val: 13 };\n * }\n * if (name === \"b\") {\n * return { val: 42 };\n * }\n * return { err: new Error(\"bad name\") };\n * }\n *\n * function multiplyFormat(\n * name1: string,\n * name2: string,\n * prefix: string\n * ): Result<string, Error> {\n * const v1 = getParam(name1);\n * if (v1.err) {\n * console.error(v1.err);\n * return v1;\n * }\n * const v2 = getParam(name1);\n * if (v2.err) {\n * console.error(v2.err);\n * return v2;\n * }\n *\n * const formatted = `${prefix}${v1.val * v2.val}`;\n * return { val: formatted };\n * }\n * ```\n *\n * ## Interop with throwing functions\n * This library also has `tryCatch` to interop with throwing functions,\n * and `tryAsync` for async functions.\n *\n * ```typescript\n * import { tryCatch, tryAsync } from \"pure/result\";\n *\n * // synchronous\n * const result1: Result<MyData, unknown> = tryCatch(() => JSON.parse<MyData>(...));\n * // or you can specify the error type:\n * const result2 = tryCatch<MyData, SyntaxError>(() => JSON.parse(...));\n *\n * // asynchronous\n * async function doSomethingCanFail() {\n * if (Math.random() < 0.5) {\n * return 42;\n * }\n * throw \"oops\";\n * }\n * const result = await tryAsync<number, string>(() => doStuff);\n * ```\n *\n * ## Returning void\n * Use `Void<E>` as the return type if the function returns `void` on success\n * ```typescript\n * const x = doSomethingThatVoidsOnSuccess();\n * if (x.err) {\n * return x;\n * }\n * // type of x is Record<string, never>, i.e. empty object\n * ```\n *\n * ## Why is there no `match`/`map`/`mapErr`, etc?\n *\n * If you are thinking this is a great idea:\n * ```typescript\n * const result = foo(bar);\n * match(result,\n * (okValue) => {\n * // handle ok case\n * },\n * (errValue) => {\n * // handle err case\n * },\n * );\n * ```\n * The vanilla `if` doesn't allocate the closures, and has less code, and you can\n * control the flow properly inside the blocks with `return`/`break`/`continue`\n * ```typescript\n * const result = foo(bar);\n * if (result.err) {\n * // handle err case\n * } else {\n * // handle ok case\n * }\n * ```\n *\n * As for the other utility functions from Rust's Result type, they really only benefit\n * because you can early return with `?` AND those abstractions are zero-cost in Rust.\n * Neither is true in JavaScript.\n *\n * You can also easily write them yourself if you really want to.\n *\n * @module\n */\n\n/**\n * A value that either a success (Ok) or an error (Err)\n *\n * Construct a success with { val: ... } and an error with { err: ... }\n */\nexport type Result<T, E> = Ok<T> | Err<E>;\n\n// If these look weird, it's because TypeScript is weird\n// This is to get type narrowing to work most of the time\n\n/** A success value */\nexport interface Ok<T> {\n val: T;\n err?: never;\n}\n/** An error value */\nexport interface Err<E> {\n err: E;\n val?: never;\n}\n\n/**\n * A value that is either `void` or an error\n *\n * Construct success with `{}` and an error with `{ err: ... }`\n */\nexport type Void<E> = { val?: never; err?: never } | { err: E };\n/** A value that is a success `void` */\nexport type VoidOk = Record<string, never>;\n\n/** Wrap a function with try-catch and return a Result. */\nexport function tryCatch<T, E = Error>(fn: () => T): Result<T, E> {\n try {\n return { val: fn() };\n } catch (e) {\n return { err: e as E };\n }\n}\n\n/** Wrap an async function with try-catch and return a Promise<Result>. */\nexport async function tryAsync<T, E = Error>(fn: () => Promise<T>): Promise<Result<T, E>> {\n try {\n return { val: await fn() };\n } catch (e) {\n return { err: e as E };\n }\n}\n\n/** Try best effort converting an error to a string */\nexport function errstr(e: unknown, recursing?: boolean): string {\n if (typeof e === \"string\") {\n return e;\n }\n if (!e) {\n return `${e}`;\n }\n if (typeof e === \"object\" && \"message\" in e) {\n if (!recursing) {\n return errstr(e.message, true);\n }\n return `${e.message}`;\n }\n if (typeof e === \"object\" && \"toString\" in e) {\n const s = e.toString();\n if (!recursing) {\n return errstr(s, true);\n }\n return `${s}`;\n }\n // try less-likely fields\n if (typeof e === \"object\" && \"msg\" in e) {\n if (!recursing) {\n return errstr(e.msg, true);\n }\n return `${e.msg}`;\n }\n if (typeof e === \"object\" && \"code\" in e) {\n if (!recursing) {\n return `error code: ${errstr(e.code, true)}`;\n }\n return `${e.code}`;\n }\n return `${e}`;\n}\n"],"mappings":";AA2LA,SAAgB,EAAuB,GAA2B;AAC9D,KAAI;AACA,SAAO,EAAE,KAAK,GAAI,EAAE;UACf,GAAG;AACR,SAAO,EAAE,KAAK,GAAQ;;;AAK9B,eAAsB,EAAuB,GAA6C;AACtF,KAAI;AACA,SAAO,EAAE,KAAK,MAAM,GAAI,EAAE;UACrB,GAAG;AACR,SAAO,EAAE,KAAK,GAAQ;;;AAK9B,SAAgB,EAAO,GAAY,GAA6B;AAC5D,KAAI,OAAO,KAAM,SACb,QAAO;AAEX,KAAI,CAAC,EACD,QAAO,GAAG;AAEd,KAAI,OAAO,KAAM,YAAY,aAAa,EAItC,QAHK,IAGE,GAAG,EAAE,YAFD,EAAO,EAAE,SAAS,GAAK;AAItC,KAAI,OAAO,KAAM,YAAY,cAAc,GAAG;EAC1C,IAAM,IAAI,EAAE,UAAU;AAItB,SAHK,IAGE,GAAG,MAFC,EAAO,GAAG,GAAK;;AAiB9B,QAZI,OAAO,KAAM,YAAY,SAAS,IAC7B,IAGE,GAAG,EAAE,QAFD,EAAO,EAAE,KAAK,GAAK,GAI9B,OAAO,KAAM,YAAY,UAAU,IAC9B,IAGE,GAAG,EAAE,SAFD,eAAe,EAAO,EAAE,MAAM,GAAK,KAI3C,GAAG"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import e from "denque";
|
|
2
|
+
//#region src/sync/serial.ts
|
|
3
|
+
var t = (e) => {
|
|
4
|
+
let { fn: t, onCancel: r } = e, i = new n(t, r);
|
|
5
|
+
return (...e) => i.invoke(...e);
|
|
6
|
+
}, n = class {
|
|
7
|
+
serial;
|
|
8
|
+
fn;
|
|
9
|
+
onCancel;
|
|
10
|
+
constructor(e, t) {
|
|
11
|
+
this.fn = e, this.serial = 0n, t ? this.onCancel = t : this.onCancel = () => {};
|
|
12
|
+
}
|
|
13
|
+
async invoke(...e) {
|
|
14
|
+
let t = !1, n = ++this.serial, r = () => {
|
|
15
|
+
if (n !== this.serial) throw t || (t = !0, this.onCancel(n, this.serial)), Error("cancelled");
|
|
16
|
+
}, i = this.fn;
|
|
17
|
+
try {
|
|
18
|
+
let t = await i(r, n)(...e);
|
|
19
|
+
return r(), { val: t };
|
|
20
|
+
} catch (e) {
|
|
21
|
+
if (n !== this.serial) return { err: "cancel" };
|
|
22
|
+
throw e;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}, r = () => {
|
|
26
|
+
let e, t;
|
|
27
|
+
return {
|
|
28
|
+
promise: new Promise((n, r) => {
|
|
29
|
+
e = n, t = r;
|
|
30
|
+
}),
|
|
31
|
+
resolve: e,
|
|
32
|
+
reject: t
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/sync/latest.ts
|
|
37
|
+
function i(e) {
|
|
38
|
+
let { fn: t, areArgsEqual: n, updateArgs: r } = e, i = new a(t, n, r);
|
|
39
|
+
return (...e) => i.invoke(...e);
|
|
40
|
+
}
|
|
41
|
+
var a = class {
|
|
42
|
+
pending;
|
|
43
|
+
currentArgs;
|
|
44
|
+
nextArgs;
|
|
45
|
+
middleArgs;
|
|
46
|
+
areArgsEqual;
|
|
47
|
+
updateArgs;
|
|
48
|
+
constructor(e, t, n) {
|
|
49
|
+
this.fn = e, this.middleArgs = [], this.areArgsEqual = t || (() => !1), this.updateArgs = n || ((e, t, n) => n);
|
|
50
|
+
}
|
|
51
|
+
async invoke(...e) {
|
|
52
|
+
if (this.pending) {
|
|
53
|
+
let t = this.currentArgs, n = this.updateArgs(t, this.middleArgs, e, this.nextArgs);
|
|
54
|
+
return this.areArgsEqual(n, t) ? this.nextArgs = void 0 : this.nextArgs = n, this.pending.promise;
|
|
55
|
+
}
|
|
56
|
+
this.nextArgs = e, this.pending = r();
|
|
57
|
+
let t, n;
|
|
58
|
+
for (; this.nextArgs;) {
|
|
59
|
+
this.currentArgs = this.nextArgs, this.nextArgs = void 0;
|
|
60
|
+
try {
|
|
61
|
+
let e = this.fn;
|
|
62
|
+
n = await e(...this.currentArgs);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
t = e;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
this.currentArgs = void 0;
|
|
68
|
+
let i = this.pending;
|
|
69
|
+
if (this.pending = void 0, t) throw i.reject(t), t;
|
|
70
|
+
return i.resolve(n), n;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/sync/debounce.ts
|
|
75
|
+
function o(e) {
|
|
76
|
+
let { fn: t, interval: n, disregardExecutionTime: r } = e, i = new s(t, n, !!r);
|
|
77
|
+
return (...e) => i.invoke(...e);
|
|
78
|
+
}
|
|
79
|
+
var s = class {
|
|
80
|
+
idle;
|
|
81
|
+
next;
|
|
82
|
+
constructor(e, t, n) {
|
|
83
|
+
this.fn = e, this.interval = t, this.disregardExecutionTime = n, this.idle = !0;
|
|
84
|
+
}
|
|
85
|
+
invoke(...e) {
|
|
86
|
+
return this.idle ? (this.idle = !1, this.execute(...e)) : (this.next ? this.next.args = e : this.next = {
|
|
87
|
+
args: e,
|
|
88
|
+
...r()
|
|
89
|
+
}, this.next.promise);
|
|
90
|
+
}
|
|
91
|
+
scheduleNext() {
|
|
92
|
+
let e = this.next;
|
|
93
|
+
if (e) {
|
|
94
|
+
this.next = void 0;
|
|
95
|
+
let { args: t, resolve: n, reject: r } = e;
|
|
96
|
+
this.execute(...t).then(n, r);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.idle = !0;
|
|
100
|
+
}
|
|
101
|
+
async execute(...e) {
|
|
102
|
+
let t = this.fn, n = this.disregardExecutionTime;
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
n ? this.scheduleNext() : n = !0;
|
|
105
|
+
}, this.interval);
|
|
106
|
+
try {
|
|
107
|
+
return await t(...e);
|
|
108
|
+
} finally {
|
|
109
|
+
this.disregardExecutionTime || (n ? this.scheduleNext() : n = !0);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/sync/batch.ts
|
|
115
|
+
function c(e) {
|
|
116
|
+
let { fn: t, batch: n, unbatch: r, interval: i, disregardExecutionTime: a } = e, o = new l(t, n, r, i, !!a);
|
|
117
|
+
return (...e) => o.invoke(...e);
|
|
118
|
+
}
|
|
119
|
+
var l = class {
|
|
120
|
+
idle;
|
|
121
|
+
scheduled;
|
|
122
|
+
constructor(e, t, n, r, i) {
|
|
123
|
+
this.fn = e, this.batch = t, this.unbatch = n, this.interval = r, this.disregardExecutionTime = i, this.idle = !0, this.scheduled = [];
|
|
124
|
+
}
|
|
125
|
+
invoke(...e) {
|
|
126
|
+
if (this.idle) return this.idle = !1, this.execute(...e);
|
|
127
|
+
let { promise: t, resolve: n, reject: i } = r();
|
|
128
|
+
return this.scheduled.push({
|
|
129
|
+
input: e,
|
|
130
|
+
promise: t,
|
|
131
|
+
resolve: n,
|
|
132
|
+
reject: i
|
|
133
|
+
}), t;
|
|
134
|
+
}
|
|
135
|
+
async scheduleNext() {
|
|
136
|
+
let e = this.scheduled;
|
|
137
|
+
if (e.length) {
|
|
138
|
+
this.scheduled = [];
|
|
139
|
+
let t = this.batch, n = e.map(({ input: e }) => e), r = e.length > 1 ? t(n) : n[0];
|
|
140
|
+
try {
|
|
141
|
+
let t = await this.execute(...r), i = this.unbatch;
|
|
142
|
+
if (i && n.length > 1) {
|
|
143
|
+
let r = i(n, t);
|
|
144
|
+
for (let t = 0; t < e.length; t++) {
|
|
145
|
+
let { resolve: n } = e[t];
|
|
146
|
+
n(r[t]);
|
|
147
|
+
}
|
|
148
|
+
} else for (let n = 0; n < e.length; n++) {
|
|
149
|
+
let { resolve: r } = e[n];
|
|
150
|
+
r(t);
|
|
151
|
+
}
|
|
152
|
+
} catch (t) {
|
|
153
|
+
for (let n = 0; n < e.length; n++) {
|
|
154
|
+
let { reject: r } = e[n];
|
|
155
|
+
r(t);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.idle = !0;
|
|
161
|
+
}
|
|
162
|
+
async execute(...e) {
|
|
163
|
+
let t = this.fn, n = this.disregardExecutionTime;
|
|
164
|
+
setTimeout(() => {
|
|
165
|
+
n ? this.scheduleNext() : n = !0;
|
|
166
|
+
}, this.interval);
|
|
167
|
+
try {
|
|
168
|
+
return await t(...e);
|
|
169
|
+
} finally {
|
|
170
|
+
this.disregardExecutionTime || (n ? this.scheduleNext() : n = !0);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/sync/once.ts
|
|
176
|
+
function u(e) {
|
|
177
|
+
let t = new d(e.fn);
|
|
178
|
+
return (...e) => t.invoke(...e);
|
|
179
|
+
}
|
|
180
|
+
var d = class {
|
|
181
|
+
promise;
|
|
182
|
+
constructor(e) {
|
|
183
|
+
this.fn = e, this.fn = e;
|
|
184
|
+
}
|
|
185
|
+
async invoke(...e) {
|
|
186
|
+
if (this.promise) return this.promise;
|
|
187
|
+
let { promise: t, resolve: n, reject: i } = r();
|
|
188
|
+
this.promise = t;
|
|
189
|
+
try {
|
|
190
|
+
let t = await this.fn(...e);
|
|
191
|
+
return n(t), t;
|
|
192
|
+
} catch (e) {
|
|
193
|
+
throw i(e), e;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}, f = /* @__PURE__ */ new Set(), p = async (e, t) => {
|
|
197
|
+
f.add(t);
|
|
198
|
+
try {
|
|
199
|
+
return await e();
|
|
200
|
+
} finally {
|
|
201
|
+
f.delete(t);
|
|
202
|
+
}
|
|
203
|
+
}, m = (e, t) => {
|
|
204
|
+
f.add(t);
|
|
205
|
+
try {
|
|
206
|
+
return e();
|
|
207
|
+
} finally {
|
|
208
|
+
f.delete(t);
|
|
209
|
+
}
|
|
210
|
+
}, h = class {
|
|
211
|
+
inner;
|
|
212
|
+
readers = 0;
|
|
213
|
+
isWriting = !1;
|
|
214
|
+
readWaiters = new e();
|
|
215
|
+
writeWaiters = new e();
|
|
216
|
+
constructor(e) {
|
|
217
|
+
this.inner = e;
|
|
218
|
+
}
|
|
219
|
+
async scopedRead(e) {
|
|
220
|
+
this.isWriting && await new Promise((e) => {
|
|
221
|
+
if (this.isWriting) {
|
|
222
|
+
this.readWaiters.push(e);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
e();
|
|
226
|
+
}), this.readers++;
|
|
227
|
+
try {
|
|
228
|
+
return await e(this.inner);
|
|
229
|
+
} finally {
|
|
230
|
+
if (this.readers--, this.writeWaiters.length > 0) this.readers === 0 && this.writeWaiters.shift()();
|
|
231
|
+
else for (; this.readWaiters.length > 0;) this.readWaiters.shift()();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async scopedWrite(e) {
|
|
235
|
+
(this.isWriting || this.readers > 0) && await new Promise((e) => {
|
|
236
|
+
if (this.isWriting || this.readers > 0) {
|
|
237
|
+
this.writeWaiters.push(e);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
e();
|
|
241
|
+
}), this.isWriting = !0;
|
|
242
|
+
try {
|
|
243
|
+
return await e(this.inner, (e) => (this.inner = e, e));
|
|
244
|
+
} finally {
|
|
245
|
+
this.isWriting = !1, this.readWaiters.length > 0 ? this.readWaiters.shift()() : this.writeWaiters.length > 0 && this.writeWaiters.shift()();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
//#endregion
|
|
250
|
+
export { h as RwLock, c as batch, o as debounce, i as latest, r as makePromise, u as once, p as scopedCapture, m as scopedCaptureSync, t as serial };
|
|
251
|
+
|
|
252
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/sync/serial.ts","../../src/sync/util.ts","../../src/sync/latest.ts","../../src/sync/debounce.ts","../../src/sync/batch.ts","../../src/sync/once.ts","../../src/sync/capture.ts","../../src/sync/RwLock.ts"],"sourcesContent":["import type { Result } from \"../result/index.ts\";\nimport type { AnyFn } from \"./util.ts\";\n\n/**\n * Factory function for `serial`. See {@link SerialConstructor} for usage\n */\nexport const serial = <TFn extends AnyFn>(args: SerialConstructor<TFn>) => {\n const { fn, onCancel } = args;\n const impl = new SerialImpl(fn, onCancel);\n return (...args: Parameters<TFn>) => impl.invoke(...args);\n};\n\n/**\n * Options for `serial` function\n *\n * `serial` is an async event wrapper that is cancelled when a new one starts.\n * When a new event is started, the previous caller will receive a\n * cancellation error, instead of being hung up indefinitely.\n *\n * If you want every caller to receive the latest result\n * instead of a cancellation error, use `latest` instead.\n *\n * ## Example\n *\n * ```typescript\n * import { serial } from \"@pistonite/pure/sync\";\n *\n * // helper function to simulate async work\n * const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n *\n * // Create the wrapped function\n * const execute = serial({\n * // This has to be curried for type inferrence\n * fn: (checkCancel) => async () => {\n * for (let i = 0; i < 10; i++) {\n * await wait(1000);\n * // The cancellation mechanism throws an error if is cancelled\n * checkCancel();\n * }\n * return 42;\n * }\n * });\n *\n * // execute it the first time\n * const promise1 = execute();\n * await wait(3000);\n *\n * // calling event.run a second time will cause `checkCancel` to return false\n * // the next time it's called by the first event\n * const promise2 = execute();\n *\n * console.log(await promise1); // { err: \"cancel\" }\n * console.log(await promise2); // { val: 42 }\n * ```\n *\n * ## Passing in arguments\n * TypeScript magic is used to ensure full type-safety when passing in arguments.\n *\n * ```typescript\n * import { serial, type SerialCancelToken } from \"@pistonite/pure/sync\";\n * import type { Result } from \"@pistonite/pure/result\";\n *\n * const execute = serial({\n * fn: (checkCancel) => async (arg1: number, arg2: string) => {\n *\n * // do something with arg1 and arg2\n * console.log(arg1, arg2);\n *\n * // ...\n * }\n * });\n *\n * expectTypeOf(execute)\n * .toEqualTypeOf<\n * (arg1: number, arg2: string) => Promise<Result<void, SerialCancelToken>>\n * >();\n *\n * await execute(42, \"hello\"); // no type error!\n * ```\n *\n * ## Getting the current serial number\n * The serial number has type `bigint` and is incremented every time `run` is called.\n *\n * You can have an extra argument after `checkCancel`, that will receive the current serial number,\n * if you need it for some reason.\n * ```typescript\n * import { serial } from \"@pistonite/pure/sync\";\n *\n * const execute = serial({\n * fn: (checkCancel, serial) => () => { console.log(serial); }\n * });\n *\n * await execute(); // 1n\n * await execute(); // 2n\n * ```\n *\n * ## Checking for cancel\n * It's the event handler's responsibility to check if the event is cancelled by\n * calling the `checkCancel` function. This function will throw if the event\n * is cancelled, and the error will be caught by the wrapper and returned as an `Err`\n *\n * Note that even if you don't check it, there is one final check before the result is returned.\n * So you will never get a result from a cancelled event. Also note that you only need to check\n * after any `await` calls. If there's no `await`, everything is executed synchronously,\n * and it's theoretically impossible to cancel the event. However, this depends on\n * the runtime's implementation of promises.\n *\n * ## Handling cancelled event\n * To check if an event is completed or cancelled, simply `await`\n * on the promise check the `err`\n * ```typescript\n * import { serial } from \"@pistonite/pure/sync\";\n *\n * const execute = serial({\n * fn: (checkCancel) => async () => {\n * // your code here ...\n * }\n * });\n * const result = await execute();\n * if (result.err === \"cancel\") {\n * console.log(\"event was cancelled\");\n * } else {\n * console.log(\"event completed\");\n * }\n * ```\n *\n * You can also pass in a callback to the constructor, which will be called\n * when the event is cancelled. The cancel callback is guaranteed to only fire at most once per run\n * ```typescript\n * import { serial } from \"@pistonite/pure/sync\";\n *\n * const onCancel = (current: bigint, latest: bigint) => {\n * console.log(`Event with serial ${current} is cancelled because the latest serial is ${latest}`);\n * };\n *\n * const execute = new Serial({\n * fn: ...,\n * onCancel,\n * });\n * ```\n *\n * ## Exception handling\n *\n * If the underlying function throws, the exception will be re-thrown to the caller.\n */\nexport interface SerialConstructor<TFn> {\n /**\n * Function creator that returns the async function to be wrapped\n */\n fn: (checkCancel: CheckCancelFn, current: SerialId) => TFn;\n /**\n * Optional callback to be called when the event is cancelled\n *\n * This is guaranteed to be only called at most once per execution\n */\n onCancel?: SerialEventCancelCallback;\n}\n\nclass SerialImpl<TFn extends AnyFn> {\n private serial: SerialId;\n private fn: SerialFnCreator<TFn>;\n private onCancel: SerialEventCancelCallback;\n\n constructor(fn: SerialFnCreator<TFn>, onCancel?: SerialEventCancelCallback) {\n this.fn = fn;\n this.serial = 0n;\n if (onCancel) {\n this.onCancel = onCancel;\n } else {\n this.onCancel = () => {};\n }\n }\n\n public async invoke(\n ...args: Parameters<TFn>\n ): Promise<Result<Awaited<ReturnType<TFn>>, SerialCancelToken>> {\n let cancelled = false;\n const currentSerial = ++this.serial;\n const checkCancel = () => {\n if (currentSerial !== this.serial) {\n if (!cancelled) {\n cancelled = true;\n this.onCancel(currentSerial, this.serial);\n }\n throw new Error(\"cancelled\");\n }\n };\n const fn = this.fn;\n // note: no typechecking for \"result\"\n try {\n const result = await fn(checkCancel, currentSerial)(...args);\n checkCancel();\n return { val: result };\n } catch (e) {\n if (currentSerial !== this.serial) {\n return { err: \"cancel\" };\n }\n throw e;\n }\n }\n}\n\ntype SerialId = bigint;\ntype CheckCancelFn = () => void;\ntype SerialFnCreator<T> = (checkCancel: CheckCancelFn, serial: SerialId) => T;\n\n/**\n * The callback type passed to SerialEvent constructor to be called\n * when the event is cancelled\n */\nexport type SerialEventCancelCallback = (current: SerialId, latest: SerialId) => void;\n\n/** The error type received by caller when an event is cancelled */\nexport type SerialCancelToken = \"cancel\";\n","/**\n * Make a {@link PromiseHandle} with the promise object separate from\n * its resolve and reject methods\n */\nexport const makePromise = <T>(): PromiseHandle<T> => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let resolve: any;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let reject: any;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n return { promise, resolve, reject };\n};\n\n/**\n * A handle of the promise that breaks down the promise object\n * and its resolve and reject functions\n */\nexport interface PromiseHandle<T> {\n promise: Promise<T>;\n resolve: (value: T | PromiseLike<T>) => void;\n reject: (reason?: unknown) => void;\n}\n\n/** Shorthand for Awaited<ReturnType<T>> */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AwaitRet<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;\n\n/** Type for any function */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyFn = (...args: any[]) => any;\n","import { type AnyFn, type AwaitRet, makePromise, type PromiseHandle } from \"./util.ts\";\n\n/**\n * Factory for `latest`. See {@link LatestConstructor} for usage.\n */\nexport function latest<TFn extends AnyFn>(args: LatestConstructor<TFn>) {\n const { fn, areArgsEqual, updateArgs } = args;\n const impl = new LatestImpl(fn, areArgsEqual, updateArgs);\n return (...args: Parameters<TFn>) => impl.invoke(...args);\n}\n\n/**\n * Args for constructing `latest`\n *\n * `latest` is an async event wrapper that always resolve to the result of the latest\n * call\n *\n * ## Example\n * In the example below, both call will return the result\n * of the second call (2)\n * ```typescript\n * import { latest } from \"@pistonite/pure/sync\";\n *\n * let counter = 0;\n *\n * const execute = latest({\n * fn: async () => {\n * counter++;\n * await new Promise((resolve) => setTimeout(() => {\n * resolve(counter);\n * }, 1000));\n * }\n * });\n *\n * const result1 = execute();\n * const result2 = execute();\n * console.log(await result1); // 2\n * console.log(await result2); // 2\n * ```\n *\n * ## Advanced Usage\n * See the constructor options for more advanced usage, for example,\n * control how arguments are updated when new calls are made.\n */\nexport interface LatestConstructor<TFn extends AnyFn> {\n /** Function to be wrapped */\n fn: TFn;\n\n /**\n * Optional function to compare if arguments of 2 calls are equal.\n *\n * By default, separate calls are considered different, and the result\n * of the latest call will be returned. However, if the function is pure,\n * and the argument of a new call is the same as the call being executed,\n * then the result of the call being executed will be returned. In other words,\n * the new call will not result in another execution of the function.\n */\n areArgsEqual?: (a: Parameters<TFn>, b: Parameters<TFn>) => boolean;\n\n /**\n * Optional function to update the arguments.\n *\n * By default, when new calls are made while the previous call is being executed,\n * The function will be executed again with the latest arguments. This function\n * is used to change this behavior and is called when new calls are made. In other words,\n * the default value for this function is `(_current, _middle, latest) => latest`.\n *\n * The arguments are:\n * - `current`: The arguments of the call currently being executed\n * - `latest`: The argument of this new call\n * - `middle`: If more than one call is made while the previous call is being executed,\n * this array contains arguments of the calls between `current` and `latest`\n * - `next`: This is the returned value of the previous call to updateArgs, i.e. the args\n * to be executed next.\n *\n */\n updateArgs?: LatestUpdateArgsFn<TFn>;\n}\n/** See {@link LatestConstructor} */\nexport type LatestUpdateArgsFn<TFn extends AnyFn> = (\n current: Parameters<TFn>,\n middle: Parameters<TFn>[],\n latest: Parameters<TFn>,\n next: Parameters<TFn> | undefined,\n) => Parameters<TFn>;\nexport class LatestImpl<TFn extends AnyFn> {\n private pending?: PromiseHandle<AwaitRet<TFn>>;\n\n /** current arguments. undefined means no current call */\n private currentArgs?: Parameters<TFn>;\n /** next arguments. undefined means no newer call */\n private nextArgs?: Parameters<TFn>;\n\n private middleArgs: Parameters<TFn>[];\n\n private areArgsEqual: (a: Parameters<TFn>, b: Parameters<TFn>) => boolean;\n private updateArgs: LatestUpdateArgsFn<TFn>;\n\n constructor(\n private fn: TFn,\n areArgsEqual?: (a: Parameters<TFn>, b: Parameters<TFn>) => boolean,\n updateArgs?: LatestUpdateArgsFn<TFn>,\n ) {\n this.middleArgs = [];\n this.areArgsEqual = areArgsEqual || (() => false);\n this.updateArgs = updateArgs || ((_current, _middle, latest) => latest);\n }\n\n public async invoke(...args: Parameters<TFn>): Promise<AwaitRet<TFn>> {\n if (this.pending) {\n // pending means currentArgs is not undefined\n const currentArgs = this.currentArgs as Parameters<TFn>;\n const nextArgs = this.updateArgs(currentArgs, this.middleArgs, args, this.nextArgs);\n if (this.areArgsEqual(nextArgs, currentArgs)) {\n // do not schedule new call\n this.nextArgs = undefined;\n } else {\n this.nextArgs = nextArgs;\n }\n return this.pending.promise;\n }\n\n // assign to next args to make the loop cleaner\n this.nextArgs = args;\n\n this.pending = makePromise();\n let error = undefined;\n let result;\n while (this.nextArgs) {\n this.currentArgs = this.nextArgs;\n this.nextArgs = undefined;\n try {\n const fn = this.fn;\n result = await fn(...this.currentArgs);\n } catch (e) {\n error = e;\n }\n }\n this.currentArgs = undefined;\n const pending = this.pending;\n this.pending = undefined;\n if (error) {\n pending.reject(error);\n throw error;\n } else {\n pending.resolve(result);\n return result;\n }\n }\n}\n","import { type AnyFn, type AwaitRet, makePromise, type PromiseHandle } from \"./util.ts\";\n\n/** Factory for debounced function. See {@link DebounceConstructor} for usage */\nexport function debounce<TFn extends AnyFn>(args: DebounceConstructor<TFn>) {\n const { fn, interval, disregardExecutionTime } = args;\n const impl = new DebounceImpl(fn, interval, !!disregardExecutionTime);\n return (...args: Parameters<TFn>) => impl.invoke(...args);\n}\n\n/**\n * Options for `debounce` function\n *\n * A debounced function is an async event wrapper that is guaranteed to:\n * - Not re-fire in a minimal interval after it's initialially fired.\n * - All calls will eventually fire\n *\n * The caller will get a promise that resolves the next time the event is fired\n * and resolved.\n *\n * Unlike the naive implementation with a setTimeout, this implementation\n * will not starve the event. If it's constantly being called,\n * it will keep firing the event at at least the minimum interval (might\n * take longer if the underlying function takes longer to execute\n *\n * ## Simple Example\n *\n * Multiple calls will be debounced to the minimum interval\n * ```typescript\n * import { debounce } from \"@pistonite/pure/sync\";\n *\n * const execute = debounce({\n * fn: () => {\n * console.log(\"called\");\n * }\n * interval: 100,\n * });\n * await execute(); // resolved immediately\n * await execute(); // resolved after 100ms\n * ```\n *\n * ## Discarding extra calls\n * When making multiple calls, if the call is currently being debounced\n * (i.e. executed and the minimum interval hasn't passed), new calls\n * will replace the previous call.\n *\n * If you want to the in-between calls to be preserved,\n * use `batch` instead.\n *\n * ```typescript\n * import { debounce } from \"@pistonite/pure/sync\";\n *\n * const execute = debounce({\n * fn: (n: number) => {\n * console.log(n);\n * }\n * interval: 100,\n * });\n * await execute(1); // logs 1 immediately\n * const p1 = execute(2); // will be resolved at 100ms\n * await new Promise((resolve) => setTimeout(resolve, 50));\n * await Promise.all[p1, execute(3)]; // will be resolved at 100ms, discarding the 2nd call\n * // 1, 3 will be logged\n * ```\n *\n * ## Slow function\n * By default, the debouncer takes into account the time\n * it takes for the underlying function to execute. It starts\n * the next cycle as soon as both the minimul interval has passed\n * and the function has finished executing. This ensures only\n * 1 call is being executed at a time.\n *\n * However, if you want the debouncer to always debounce at the set interval,\n * regardless of if the previous call has finished, set `disregardExecutionTime`\n * to true.\n *\n * ```typescript\n * import { debounce } from \"@pistonite/pure/sync\";\n *\n * const execute = debounce({\n * fn: async (n: number) => {\n * await new Promise((resolve) => setTimeout(resolve, 150));\n * console.log(n);\n * },\n * interval: 100,\n * // without this, will debounce at the interval of 150ms\n * disregardExecutionTime: true,\n * });\n * ```\n */\nexport interface DebounceConstructor<TFn> {\n /** Function to be debounced */\n fn: TFn;\n /**\n * Minimum interval between each call\n *\n * Setting this to <= 0 will make the debounce function\n * a pure pass-through, not actually debouncing the function\n */\n interval: number;\n\n /**\n * By default, the debouncer takes in account the time\n * the underlying function executes. i.e. the actual debounce\n * interval is `max(interval, executionTime)`. This default\n * behavior guanrantees that no 2 calls will be executed concurrently.\n *\n * If you want the debouncer to always debounce at the set interval,\n * set this to true.\n */\n disregardExecutionTime?: boolean;\n}\n\nclass DebounceImpl<TFn extends AnyFn> {\n private idle: boolean;\n private next?: PromiseHandle<AwaitRet<TFn>> & { args: Parameters<TFn> };\n constructor(\n private fn: TFn,\n private interval: number,\n private disregardExecutionTime: boolean,\n ) {\n this.idle = true;\n }\n\n public invoke(...args: Parameters<TFn>): Promise<AwaitRet<TFn>> {\n if (this.idle) {\n this.idle = false;\n return this.execute(...args);\n }\n if (!this.next) {\n this.next = { args, ...makePromise<AwaitRet<TFn>>() };\n } else {\n this.next.args = args;\n }\n return this.next.promise;\n }\n\n private scheduleNext() {\n const next = this.next;\n if (next) {\n this.next = undefined;\n const { args, resolve, reject } = next;\n void this.execute(...args).then(resolve, reject);\n return;\n }\n this.idle = true;\n }\n\n private async execute(...args: Parameters<TFn>): Promise<AwaitRet<TFn>> {\n const fn = this.fn;\n let done = this.disregardExecutionTime;\n setTimeout(() => {\n if (done) {\n this.scheduleNext();\n } else {\n done = true;\n }\n }, this.interval);\n try {\n return await fn(...args);\n } finally {\n if (!this.disregardExecutionTime) {\n if (done) {\n // interval already passed, we need to call it\n this.scheduleNext();\n } else {\n done = true;\n }\n }\n }\n }\n}\n","import { type AnyFn, makePromise, type PromiseHandle, type AwaitRet } from \"./util.ts\";\n\n/** Factory for batched function. See {@link BatchConstructor} for usage. */\nexport function batch<TFn extends AnyFn>(args: BatchConstructor<TFn>) {\n const { fn, batch, unbatch, interval, disregardExecutionTime } = args;\n const impl = new BatchImpl(fn, batch, unbatch, interval, !!disregardExecutionTime);\n return (...args: Parameters<TFn>) => impl.invoke(...args);\n}\n\n/**\n * Options to construct a `batch` function\n *\n * A batch function is an async event wrapper that allows multiple calls in an interval\n * to be batched together, and only call the underlying function once.\n *\n * Optionally, the output can be unbatched to match the inputs.\n *\n * ## Example\n * The API is a lot like `debounce`, but with an additional `batch` function\n * and an optional `unbatch` function.\n * ```typescript\n * import { batch } from \"@pistonite/pure/sync\";\n *\n * const execute = batch({\n * fn: (n: number) => {\n * console.log(n);\n * },\n * interval: 100,\n * // batch receives all the inputs and returns a single input\n * // here we just sums the inputs\n * batch: (args: [number][]): [number] => [args.reduce((acc, [n]) => acc + n, 0)],\n * });\n *\n * await execute(1); // logs 1 immediately\n * const p1 = execute(2); // will be resolved at 100ms\n * const p2 = execute(3); // will be resolved at 100ms\n * await Promise.all([p1, p2]); // logs 5 after 100ms\n * ```\n *\n * ## Unbatching\n * The optional `unbatch` function allows the output to be unbatched,\n * so the promises are resolved as if the underlying function is called\n * directly.\n *\n * Note that unbatching is usually slow and not required.\n *\n * ```typescript\n * import { batch } from \"@pistonite/pure/sync\";\n *\n * type Message = {\n * id: number;\n * payload: string;\n * }\n *\n * const execute = batch({\n * fn: (messages: Message[]): Message[] => {\n * console.log(messages.length);\n * return messages.map((m) => ({\n * id: m.id,\n * payload: m.payload + \"out\",\n * }));\n * },\n * batch: (args: [Message[]][]): [Message[]] => {\n * const out: Message[] = [];\n * for (const [messages] of args) {\n * out.push(...messages);\n * }\n * return [out];\n * },\n * unbatch: (inputs: [Message[]][], output: Message[]): Message[][] => {\n * // not efficient, but just for demonstration\n * const idToOutput = new Map();\n * for (const o of output) {\n * idToOutput.set(o.id, o);\n * }\n * return inputs.map(([messages]) => {\n * return messages.map(({id}) => {\n * return idToOutput.get(m.id)!;\n * });\n * });\n * },\n * interval: 100,\n * });\n *\n * const r1 = await execute([{id: 1, payload: \"a\"}]); // logs 1 immediately\n * // r1 is [ {id: 1, payload: \"aout\"} ]\n *\n * const p1 = execute([{id: 2, payload: \"b\"}]); // will be resolved at 100ms\n * const p2 = execute([{id: 3, payload: \"c\"}]); // will be resolved at 100ms\n *\n * const r2 = await p2; // 2 is logged\n * // r1 is [ {id: 2, payload: \"bout\"} ]\n * const r3 = await p3; // nothing is logged, as it's already resolved\n * // r2 is [ {id: 3, payload: \"cout\"} ]\n *\n * ```\n *\n */\nexport interface BatchConstructor<TFn extends AnyFn> {\n /** Function to be wrapped */\n fn: TFn;\n /** Function to batch the inputs across multiple calls */\n batch: (args: Parameters<TFn>[]) => Parameters<TFn>;\n\n /**\n * If provided, unbatch the output according to the inputs,\n * so each call receives its own output.\n *\n * By default, each input will receive the same output from the batched call\n */\n unbatch?: (inputs: Parameters<TFn>[], output: AwaitRet<TFn>) => AwaitRet<TFn>[];\n\n /**\n * Interval between each batched call\n */\n interval: number;\n\n /** See `debounce` for more information */\n disregardExecutionTime?: boolean;\n}\n\nclass BatchImpl<TFn extends AnyFn> {\n private idle: boolean;\n private scheduled: (PromiseHandle<AwaitRet<TFn>> & {\n input: Parameters<TFn>;\n })[];\n\n constructor(\n private fn: TFn,\n private batch: (inputs: Parameters<TFn>[]) => Parameters<TFn>,\n private unbatch:\n | ((input: Parameters<TFn>[], output: AwaitRet<TFn>) => AwaitRet<TFn>[])\n | undefined,\n private interval: number,\n private disregardExecutionTime: boolean,\n ) {\n this.idle = true;\n this.scheduled = [];\n }\n\n public invoke(...args: Parameters<TFn>): Promise<AwaitRet<TFn>> {\n if (this.idle) {\n this.idle = false;\n return this.execute(...args);\n }\n const { promise, resolve, reject } = makePromise<AwaitRet<TFn>>();\n this.scheduled.push({ input: args, promise, resolve, reject });\n\n return promise;\n }\n\n private async scheduleNext() {\n const next = this.scheduled;\n if (next.length) {\n this.scheduled = [];\n const batch = this.batch;\n const inputs = next.map(({ input }) => input);\n const input = next.length > 1 ? batch(inputs) : inputs[0];\n try {\n const output = await this.execute(...input);\n const unbatch = this.unbatch;\n if (unbatch && inputs.length > 1) {\n const outputs = unbatch(inputs, output);\n for (let i = 0; i < next.length; i++) {\n const { resolve } = next[i];\n resolve(outputs[i]);\n }\n } else {\n for (let i = 0; i < next.length; i++) {\n const { resolve } = next[i];\n resolve(output);\n }\n }\n } catch (e) {\n for (let i = 0; i < next.length; i++) {\n const { reject } = next[i];\n reject(e);\n }\n }\n return;\n }\n this.idle = true;\n }\n\n private async execute(...args: Parameters<TFn>): Promise<AwaitRet<TFn>> {\n const fn = this.fn;\n let done = this.disregardExecutionTime;\n setTimeout(() => {\n if (done) {\n void this.scheduleNext();\n } else {\n done = true;\n }\n }, this.interval);\n try {\n return await fn(...args);\n } finally {\n if (!this.disregardExecutionTime) {\n if (done) {\n // interval already passed, we need to call it\n void this.scheduleNext();\n } else {\n done = true;\n }\n }\n }\n }\n}\n","import { type AnyFn, type AwaitRet, makePromise } from \"./util.ts\";\n\n/**\n * Factory function for `once`. See {@link OnceConstructor} for usage\n */\nexport function once<TFn extends AnyFn>(args: OnceConstructor<TFn>) {\n const impl = new OnceImpl(args.fn);\n return (...args: Parameters<TFn>) => impl.invoke(...args);\n}\n\n/**\n * Args for constructing a `once`\n *\n * `once` is an async event wrapper that ensures an async initialization is only ran once.\n * Any subsequent calls after the first call will return a promise that resolves/rejects\n * with the result of the first call.\n *\n * ## Example\n * ```typescript\n * import { once } from \"@pistonite/pure/sync\";\n *\n * const getLuckyNumber = once({\n * fn: async () => {\n * console.log(\"running expensive initialization...\")\n * await new Promise((resolve) => setTimeout(resolve, 100));\n * console.log(\"done\")\n * return 42;\n * }\n * });\n *\n * const result1 = getLuckyNumber();\n * const result2 = getLuckyNumber();\n * console.log(await result1);\n * console.log(await result2);\n * // logs:\n * // running expensive initialization...\n * // done\n * // 42\n * // 42\n * ```\n *\n * ## Caveat with HMR\n * Some initialization might require clean up, such as unregister\n * event handlers and/or timers. In this case, a production build might\n * work fine but a HMR (Hot Module Reload) development server might not\n * do this for you automatically.\n *\n * One way to work around this during development is to store the cleanup\n * as a global object\n * ```typescript\n * const getResourceThatNeedsCleanup = once({\n * fn: async () => {\n * if (__DEV__) { // Configure your bundler to inject this\n * // await if you need async clean up\n * await (window as any).cleanupMyResource?.();\n * }\n *\n * let resource: MyResource;\n * if (__DEV__) {\n * (window as any).cleanupMyResource = async () => {\n * await resource?.cleanup();\n * };\n * }\n *\n * resource = await initResource();\n * return resource;\n * }\n * });\n * ```\n *\n * An alternative solution is to not use `once` but instead tie the initialization\n * of the resource to some other lifecycle event that gets cleaned up during HMR.\n * For example, A framework that supports HMR for React components might unmount\n * the component before reloading, which gives you a chance to clean up the resource.\n *\n * This is not an issue if the resource doesn't leak other resources,\n * since it will eventually be GC'd.\n */\nexport interface OnceConstructor<TFn> {\n /** Function to be called only once */\n fn: TFn;\n}\n\nexport class OnceImpl<TFn extends AnyFn> {\n private promise: Promise<AwaitRet<TFn>> | undefined;\n\n constructor(private fn: TFn) {\n this.fn = fn;\n }\n\n public async invoke(...args: Parameters<TFn>): Promise<AwaitRet<TFn>> {\n if (this.promise) {\n return this.promise;\n }\n const { promise, resolve, reject } = makePromise<AwaitRet<TFn>>();\n this.promise = promise;\n try {\n const result = await this.fn(...args);\n resolve(result);\n return result;\n } catch (e) {\n reject(e);\n throw e;\n }\n }\n}\n","// this is an exception to the global state rule - since it doesn't really matter\n// if this is duplicated\nconst captured = new Set<unknown>();\n\n/**\n * Execute an async closure `fn`, and guarantee that `obj` will not be\n * garbage-collected, until the promise is resolved.\n */\nexport const scopedCapture = async <T>(fn: () => Promise<T>, obj: unknown): Promise<T> => {\n // captures the object\n // technically, this is not needed, as the delete() call above\n // should make sure the captured object is not GC'ed.\n // However, making it reachable from a global object will definitely\n // prevent GC even with crazy optimization from any runtime\n captured.add(obj);\n try {\n return await fn();\n } finally {\n captured.delete(obj);\n }\n};\n\n/**\n * Execute a closure `fn`, and guarantee that `obj` will not be\n * garbage-collected during the execution\n */\nexport const scopedCaptureSync = <T>(fn: () => T, obj: unknown): T => {\n // captures the object\n captured.add(obj);\n try {\n return fn();\n } finally {\n captured.delete(obj);\n }\n};\n","import Deque from \"denque\";\n\n/**\n * Ensure you have exclusive access in concurrent code\n *\n * Only guaranteed if no one else has reference to the inner object\n *\n * It can take a second type parameter to specify interface with write methods\n *\n * @deprecated unstable API\n */\nexport class RwLock<TRead, TWrite extends TRead = TRead> {\n /**\n * This is public so inner object can be accessed directly\n * ONLY SAFE in sync context\n */\n public inner: TWrite;\n\n private readers: number = 0;\n private isWriting: boolean = false;\n private readWaiters: Deque<() => void> = new Deque();\n private writeWaiters: Deque<() => void> = new Deque();\n\n constructor(t: TWrite) {\n this.inner = t;\n }\n\n /** Acquire a read (shared) lock and call fn with the value. Release the lock when fn returns or throws. */\n public async scopedRead<R>(fn: (t: TRead) => Promise<R>): Promise<R> {\n if (this.isWriting) {\n await new Promise<void>((resolve) => {\n // need to check again to make sure it's not already done\n if (this.isWriting) {\n this.readWaiters.push(resolve);\n return;\n }\n resolve();\n });\n }\n // acquired\n this.readers++;\n try {\n return await fn(this.inner);\n } finally {\n this.readers--;\n if (this.writeWaiters.length > 0) {\n if (this.readers === 0) {\n // notify one writer\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.writeWaiters.shift()!();\n }\n // don't notify anyone if there are still readers\n } else {\n // notify all readers\n while (this.readWaiters.length > 0) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.readWaiters.shift()!();\n }\n }\n }\n }\n\n /**\n * Acquire a write (exclusive) lock and call fn with the value. Release the lock when fn returns or throws.\n *\n * fn takes a setter function as second parameter, which you can use to update the value like `x = set(newX)`\n */\n public async scopedWrite<R>(\n fn: (t: TWrite, setter: (t: TWrite) => TWrite) => Promise<R>,\n ): Promise<R> {\n if (this.isWriting || this.readers > 0) {\n await new Promise<void>((resolve) => {\n // need to check again to make sure it's not already done\n if (this.isWriting || this.readers > 0) {\n this.writeWaiters.push(resolve);\n return;\n }\n resolve();\n });\n }\n // acquired\n this.isWriting = true;\n try {\n return await fn(this.inner, (t: TWrite) => {\n this.inner = t;\n return t;\n });\n } finally {\n this.isWriting = false;\n if (this.readWaiters.length > 0) {\n // notify one reader\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.readWaiters.shift()!();\n } else if (this.writeWaiters.length > 0) {\n // notify one writer\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.writeWaiters.shift()!();\n }\n }\n }\n}\n"],"mappings":"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"}
|