@pistonite/pure 0.29.0 → 0.29.1

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.
Files changed (37) hide show
  1. package/dist/_dts_/src/log/index.d.ts +37 -0
  2. package/dist/_dts_/src/log/index.d.ts.map +1 -0
  3. package/dist/_dts_/src/log/logger.d.ts +27 -0
  4. package/dist/_dts_/src/log/logger.d.ts.map +1 -0
  5. package/dist/_dts_/src/memory/cell.d.ts +25 -0
  6. package/dist/_dts_/src/memory/cell.d.ts.map +1 -0
  7. package/dist/_dts_/src/memory/emp.d.ts +87 -0
  8. package/dist/_dts_/src/memory/emp.d.ts.map +1 -0
  9. package/dist/_dts_/src/memory/idgen.d.ts +18 -0
  10. package/dist/_dts_/src/memory/idgen.d.ts.map +1 -0
  11. package/dist/_dts_/src/memory/index.d.ts +10 -0
  12. package/dist/_dts_/src/memory/index.d.ts.map +1 -0
  13. package/dist/_dts_/src/memory/persist.d.ts +38 -0
  14. package/dist/_dts_/src/memory/persist.d.ts.map +1 -0
  15. package/dist/_dts_/src/result/index.d.ts +191 -0
  16. package/dist/_dts_/src/result/index.d.ts.map +1 -0
  17. package/dist/_dts_/src/sync/RwLock.d.ts +30 -0
  18. package/dist/_dts_/src/sync/RwLock.d.ts.map +1 -0
  19. package/dist/_dts_/src/sync/batch.d.ts +112 -0
  20. package/dist/_dts_/src/sync/batch.d.ts.map +1 -0
  21. package/dist/_dts_/src/sync/capture.d.ts +11 -0
  22. package/dist/_dts_/src/sync/capture.d.ts.map +1 -0
  23. package/dist/_dts_/src/sync/debounce.d.ts +105 -0
  24. package/dist/_dts_/src/sync/debounce.d.ts.map +1 -0
  25. package/dist/_dts_/src/sync/index.d.ts +15 -0
  26. package/dist/_dts_/src/sync/index.d.ts.map +1 -0
  27. package/dist/_dts_/src/sync/latest.d.ts +86 -0
  28. package/dist/_dts_/src/sync/latest.d.ts.map +1 -0
  29. package/dist/_dts_/src/sync/mutex.d.ts +14 -0
  30. package/dist/_dts_/src/sync/mutex.d.ts.map +1 -0
  31. package/dist/_dts_/src/sync/once.d.ts +84 -0
  32. package/dist/_dts_/src/sync/once.d.ts.map +1 -0
  33. package/dist/_dts_/src/sync/serial.d.ts +162 -0
  34. package/dist/_dts_/src/sync/serial.d.ts.map +1 -0
  35. package/dist/_dts_/src/sync/util.d.ts +19 -0
  36. package/dist/_dts_/src/sync/util.d.ts.map +1 -0
  37. package/package.json +1 -1
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Client side log util
3
+ *
4
+ * This is rather simple logging stuff with the primary focus
5
+ * being easy-to-debug, instead of optimized for bundle size or performance.
6
+ *
7
+ * Because this library doesn't have global states, there is no "global" logger
8
+ * or any global logger settings. Instead, each library or component of the
9
+ * application can create their own instance of the logger, which stores
10
+ * settings like the name, color and level of that logger.
11
+ *
12
+ * ```typescript
13
+ * import { logger } from "@pistonite/pure";
14
+ *
15
+ * export const myLogger = logger("my-library", {
16
+ * color: "#ff8800", // any CSS color (note that styling only works in browser)
17
+ * });
18
+ * ```
19
+ *
20
+ * It's recommended that a library exports the logger object
21
+ * so downstream app can modify the logging level if needed for debugging.
22
+ * ```typescript
23
+ * import { myLogger } from "my-library";
24
+ *
25
+ * myLogger.setLevel("debug");
26
+ * ```
27
+ *
28
+ * Due to the nature of JS, all logging calls, even when turned off, will incur
29
+ * some small runtime overhead. While we could remove debug calls
30
+ * for release build, that's currently not done (and it would require
31
+ * bundler to inline the call to remove the call completely, which might
32
+ * not be the case)
33
+ *
34
+ * @module
35
+ */
36
+ export { type LogLevelStr, type LoggerConstructor, type Logger, logger } from "./logger.ts";
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/log/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,27 @@
1
+ export type LogLevelStr = "off" | "default" | "info" | "debug";
2
+ /** Args for constructing a logger */
3
+ export interface LoggerConstructor {
4
+ /** CSS Color for the logger, default is 'gray' */
5
+ color?: string;
6
+ /**
7
+ * Logging level, default is "default".
8
+ * The level can still be changed later with setLevel
9
+ */
10
+ level?: LogLevelStr;
11
+ }
12
+ /** The logger type */
13
+ export interface Logger {
14
+ /** Set the level of the logger */
15
+ setLevel(level: LogLevelStr): void;
16
+ /** Log a debug message */
17
+ debug(obj: unknown): void;
18
+ /** Log an info message */
19
+ info(obj: unknown): void;
20
+ /** Log a warning message */
21
+ warn(obj: unknown): void;
22
+ /** Log an error message */
23
+ error(obj: unknown): void;
24
+ }
25
+ /** Create a logger creator. Use the factory methods to finish making the logger */
26
+ export declare const logger: (name: string, args: LoggerConstructor) => Logger;
27
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../../src/log/logger.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAS/D,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAC9B,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,sBAAsB;AACtB,MAAM,WAAW,MAAM;IACnB,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IACnC,0BAA0B;IAC1B,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,0BAA0B;IAC1B,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,4BAA4B;IAC5B,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,2BAA2B;IAC3B,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;CAC7B;AAED,mFAAmF;AACnF,eAAO,MAAM,MAAM,GAAI,MAAM,MAAM,EAAE,MAAM,iBAAiB,KAAG,MAS9D,CAAC"}
@@ -0,0 +1,25 @@
1
+ /** Create a {@link Cell} */
2
+ export declare const cell: <T>(args: CellConstructor<T>) => Cell<T>;
3
+ /** Args for constructing a cell */
4
+ export interface CellConstructor<T> {
5
+ /** Initial value */
6
+ initial: T;
7
+ }
8
+ /**
9
+ * A light weight storage wrapper around a value
10
+ * that can be subscribed to for changes
11
+ *
12
+ * Created via `cell()`
13
+ *
14
+ * ```typescript
15
+ * import { cell } from "@pistonite/pure/memory";
16
+ *
17
+ * const myCell = cell(true);
18
+ * ```
19
+ */
20
+ export interface Cell<T> {
21
+ get(): T;
22
+ set(value: T): void;
23
+ subscribe(callback: (value: T) => void, notifyImmediately?: boolean): () => void;
24
+ }
25
+ //# sourceMappingURL=cell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cell.d.ts","sourceRoot":"","sources":["../../../../src/memory/cell.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,eAAO,MAAM,IAAI,GAAI,CAAC,EAAE,MAAM,eAAe,CAAC,CAAC,CAAC,KAAG,IAAI,CAAC,CAAC,CAExD,CAAC;AAEF,mCAAmC;AACnC,MAAM,WAAW,eAAe,CAAC,CAAC;IAC9B,oBAAoB;IACpB,OAAO,EAAE,CAAC,CAAC;CACd;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,IAAI,CAAC,CAAC;IACnB,GAAG,IAAI,CAAC,CAAC;IACT,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACpB,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE,iBAAiB,CAAC,EAAE,OAAO,GAAG,MAAM,IAAI,CAAC;CACpF"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * A non-null, **E**ngine-**m**anaged **P**ointer
3
+ *
4
+ * This uses the ECMA FinalizationRegistry, to free the resource,
5
+ * once this object is garbage-collected.
6
+ *
7
+ * The free function may be async.
8
+ *
9
+ * ## Use case
10
+ * When JS interoperates with a system language like C/C++ or Rust,
11
+ * it is often needed to transfer objects into JS context. If the object
12
+ * is big, copy-transfer could be expensive, so the more ideal solution
13
+ * is to transfer a "handle", or a pointer, to JS, and leaving the actual
14
+ * object in the native memory. Then, JS code and pass the pointer to external
15
+ * code to use or extract data from the object when needed.
16
+ *
17
+ * This approach is just like passing raw pointers in C, which means it is
18
+ * extremely succeptible to memory corruption bugs like double-free or
19
+ * use-after-free. Unlike C++ or Rust, JS also doesn't have destructors
20
+ * that run when an object leaves the scope (proposal for the `using` keyword exists,
21
+ * but you can actually double-free the object with `using`).
22
+ *
23
+ * C-style manual memory management might be sufficient for simple bindings,
24
+ * but for more complex scenarios, automatic memory management is needed,
25
+ * by tying the free call to the GC of a JS object.
26
+ *
27
+ * ## Recommended practice
28
+ * 1. The external code should transfer the ownership of the object
29
+ * to JS when passing it to JS. JS will then put the handle into an Emp
30
+ * to be automatically managed. This means the external code should now
31
+ * never free the object, and let JS free it instead.
32
+ * 2. The inner value of the Emp must only be used for calling
33
+ * external code, and the Emp must be kept alive for all runtimes, including
34
+ * those with heavy optimization that may reclaim the Emp during the call,
35
+ * if it's not referenced afterwards. See {@link scopedCapture}.
36
+ * The inner value must not dangle around outside of the Emp that owns it.
37
+ *
38
+ * ## Pointer Size
39
+ * In 32-bit context like WASM32, the inner value can be a `number`.
40
+ * In 64-bit context, `number` might be fine for some systems, but `bigint`
41
+ * is recommended.
42
+ *
43
+ * ## Usage
44
+ *
45
+ * ```typescript
46
+ * // First create a marker to distinguish between different Emp types for TypeScript.
47
+ * // This is not used at runtime
48
+ * const MyNativeType = Symbol("MyNativeType");
49
+ * export type MyNativeType = typeof MyNativeType;
50
+ *
51
+ * // Then use makeEmpType to create a factory function
52
+ * const makeMyNativeTypeEmp = makeEmpType({
53
+ * marker: MyNativeType,
54
+ * free: (ptr) => freeMyNativeType(ptr),
55
+ * });
56
+ *
57
+ * // Now acquire ownership of an object from external native code
58
+ * // and assign it to an Emp
59
+ * const myObjRawPtr = allocMyNativeType();
60
+ * const myObj = makeMyNativeTypeEmp(myObjRawPtr);
61
+ *
62
+ * // when myObj is GC'ed, it will be freed by calling freeMyNativeType
63
+ * ```
64
+ */
65
+ export interface Emp<T, TRepr> {
66
+ /** The type marker for T. This only marks the type for TypeScript and does not exist at runtime */
67
+ readonly __phantom: T;
68
+ /** The underlying pointer value */
69
+ readonly value: TRepr;
70
+ }
71
+ /** Args for constructing a Emp type */
72
+ export interface EmpConstructor<T, TRepr> {
73
+ /**
74
+ * The marker for the Emp type, used to distinguish between multiple Emp types
75
+ * in TypeScript
76
+ */
77
+ marker?: T;
78
+ /**
79
+ * Function to free the underlying object. Called when this Emp is garbage-collected
80
+ */
81
+ free: (ptr: TRepr) => void | Promise<void>;
82
+ }
83
+ /**
84
+ * Create a factory function for an Emp type. See {@link Emp}
85
+ */
86
+ export declare const makeEmpType: <T, TRepr>(args: EmpConstructor<T, TRepr>) => ((ptr: TRepr) => Emp<T, TRepr>);
87
+ //# sourceMappingURL=emp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emp.d.ts","sourceRoot":"","sources":["../../../../src/memory/emp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,MAAM,WAAW,GAAG,CAAC,CAAC,EAAE,KAAK;IACzB,mGAAmG;IACnG,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IACtB,mCAAmC;IACnC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACzB;AAED,uCAAuC;AACvC,MAAM,WAAW,cAAc,CAAC,CAAC,EAAE,KAAK;IACpC;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC;IAEX;;OAEG;IACH,IAAI,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,EAAE,KAAK,EAChC,MAAM,cAAc,CAAC,CAAC,EAAE,KAAK,CAAC,KAC/B,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAQhC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Return an id generator that will generate ids in order
3
+ * from 1 to n, wrapping around to 1 when it's n
4
+ */
5
+ export declare const safeidgen: (n: number) => (() => number);
6
+ /**
7
+ * Return an id generator that returns bigint,
8
+ * starting from 1n, and will always return a new bigint
9
+ */
10
+ export declare const bidgen: () => (() => bigint);
11
+ /**
12
+ * Returns an id generator that returns number staring from 1
13
+ * and always increasing. The number could become inaccurate
14
+ * (not integer) when exceeding Number.MAX_SAFE_INTEGER (which
15
+ * is 2^53 - 1
16
+ */
17
+ export declare const idgen: () => (() => number);
18
+ //# sourceMappingURL=idgen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idgen.d.ts","sourceRoot":"","sources":["../../../../src/memory/idgen.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,KAAG,CAAC,MAAM,MAAM,CASlD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,MAAM,QAAO,CAAC,MAAM,MAAM,CAMtC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,KAAK,QAAO,CAAC,MAAM,MAAM,CAMrC,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Memory utilities
3
+ *
4
+ * @module
5
+ */
6
+ export { cell, type CellConstructor, type Cell } from "./cell.ts";
7
+ export { persist, type PersistConstructor, type Persist } from "./persist.ts";
8
+ export * from "./emp.ts";
9
+ export * from "./idgen.ts";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/memory/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,IAAI,EAAE,KAAK,eAAe,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAC9E,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { type Cell, type CellConstructor } from "./cell.ts";
2
+ /**
3
+ * Create a cell that persists its value to a web storage
4
+ */
5
+ export declare function persist<T>(args: PersistConstructor<T>): Persist<T>;
6
+ /** Args for creating a persisted cell */
7
+ export interface PersistConstructor<T> extends CellConstructor<T> {
8
+ /** The web storage to use */
9
+ storage: Storage;
10
+ /** The key to use in the storage */
11
+ key: string;
12
+ /**
13
+ * Serialize the value to store in the storage
14
+ *
15
+ * By default, it will use `JSON.stringify`
16
+ */
17
+ serialize?(value: T): string;
18
+ /**
19
+ * Deserialize the value from the storage
20
+ *
21
+ * By default, it will use `JSON.parse` wrapped with try-catch
22
+ */
23
+ deserialize?(value: string): T | null;
24
+ }
25
+ /** A cell that also persists its value */
26
+ export interface Persist<T> extends Cell<T> {
27
+ /**
28
+ * Load the value initially, and notify all the current subscribers
29
+ *
30
+ * Optionally, you can pass an initial value to override the current value
31
+ */
32
+ init(initial?: T): T;
33
+ /** Clear the value from the storage */
34
+ clear(): void;
35
+ /** Clear the value and disable the persistence */
36
+ disable(): void;
37
+ }
38
+ //# sourceMappingURL=persist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../../../src/memory/persist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,IAAI,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAElE;;GAEG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAYlE;AAED,yCAAyC;AACzC,MAAM,WAAW,kBAAkB,CAAC,CAAC,CAAE,SAAQ,eAAe,CAAC,CAAC,CAAC;IAC7D,6BAA6B;IAC7B,OAAO,EAAE,OAAO,CAAC;IAEjB,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;OAIG;IACH,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC;IAC7B;;;;OAIG;IACH,WAAW,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;CACzC;AAED,0CAA0C;AAC1C,MAAM,WAAW,OAAO,CAAC,CAAC,CAAE,SAAQ,IAAI,CAAC,CAAC,CAAC;IACvC;;;;OAIG;IACH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACrB,uCAAuC;IACvC,KAAK,IAAI,IAAI,CAAC;IACd,kDAAkD;IAClD,OAAO,IAAI,IAAI,CAAC;CACnB"}
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Rust-like `Result<T, E>` type and error handling utils
3
+ *
4
+ * **I once had a fancy error object with TypeScript magic that tries
5
+ * to reduce allocation while maintaining Result-safety. It turns out
6
+ * that was slower than allocating plain objects for every return, because
7
+ * of how V8 optimizes things.**
8
+ *
9
+ * Don't even use `isErr()` helper functions to abstract. They are slower than
10
+ * directly property access in my testing.
11
+ *
12
+ * ## Function that can fail
13
+ * Instead of having functions `throw`, make it `return` instead.
14
+ * ```typescript
15
+ * // Instead of
16
+ * function doSomethingCanFail() {
17
+ * if (Math.random() < 0.5) {
18
+ * return 42;
19
+ * }
20
+ * throw "oops";
21
+ * }
22
+ * // Do this
23
+ * import type { Result } from "pure/result";
24
+ *
25
+ * function doSomethingCanFail(): Result<number, string> {
26
+ * if (Math.random() < 0.5) {
27
+ * return { val: 42 };
28
+ * }
29
+ * return { err: "oops" };
30
+ * }
31
+ * ```
32
+ * This is similar to Rust:
33
+ * ```rust
34
+ * fn do_something_can_fail() -> Result<u32, String> {
35
+ * if ... {
36
+ * return Ok(42);
37
+ * }
38
+ *
39
+ * Err("oops".to_string())
40
+ * }
41
+ * ```
42
+ *
43
+ * ## Calling function that can fail
44
+ * The recommended pattern is
45
+ * ```typescript
46
+ * const x = doTheCall(); // x is Result<T, E>;
47
+ * if (x.err) {
48
+ * // x.err is E, handle it
49
+ * return ...
50
+ * }
51
+ * // x.val is T
52
+ * // ...
53
+ * ```
54
+ * If your `E` type covers falsy values that are valid, use `"err" in x` instead of `x.err`.
55
+ * A well-known case is `Result<T, unknown>`. `if(r.err)` cannot narrow the else case to `Ok`,
56
+ * but `if("err" in r)` can.
57
+ *
58
+ * A full example:
59
+ * ```typescript
60
+ * function getParam(name: string): Result<number, Error> {
61
+ * if (name === "a") {
62
+ * return { val: 13 };
63
+ * }
64
+ * if (name === "b") {
65
+ * return { val: 42 };
66
+ * }
67
+ * return { err: new Error("bad name") };
68
+ * }
69
+ *
70
+ * function multiplyFormat(
71
+ * name1: string,
72
+ * name2: string,
73
+ * prefix: string
74
+ * ): Result<string, Error> {
75
+ * const v1 = getParam(name1);
76
+ * if (v1.err) {
77
+ * console.error(v1.err);
78
+ * return v1;
79
+ * }
80
+ * const v2 = getParam(name1);
81
+ * if (v2.err) {
82
+ * console.error(v2.err);
83
+ * return v2;
84
+ * }
85
+ *
86
+ * const formatted = `${prefix}${v1.val * v2.val}`;
87
+ * return { val: formatted };
88
+ * }
89
+ * ```
90
+ *
91
+ * ## Interop with throwing functions
92
+ * This library also has `tryCatch` to interop with throwing functions,
93
+ * and `tryAsync` for async functions.
94
+ *
95
+ * ```typescript
96
+ * import { tryCatch, tryAsync } from "pure/result";
97
+ *
98
+ * // synchronous
99
+ * const result1: Result<MyData, unknown> = tryCatch(() => JSON.parse<MyData>(...));
100
+ * // or you can specify the error type:
101
+ * const result2 = tryCatch<MyData, SyntaxError>(() => JSON.parse(...));
102
+ *
103
+ * // asynchronous
104
+ * async function doSomethingCanFail() {
105
+ * if (Math.random() < 0.5) {
106
+ * return 42;
107
+ * }
108
+ * throw "oops";
109
+ * }
110
+ * const result = await tryAsync<number, string>(() => doStuff);
111
+ * ```
112
+ *
113
+ * ## Returning void
114
+ * Use `Void<E>` as the return type if the function returns `void` on success
115
+ * ```typescript
116
+ * const x = doSomethingThatVoidsOnSuccess();
117
+ * if (x.err) {
118
+ * return x;
119
+ * }
120
+ * // type of x is Record<string, never>, i.e. empty object
121
+ * ```
122
+ *
123
+ * ## Why is there no `match`/`map`/`mapErr`, etc?
124
+ *
125
+ * If you are thinking this is a great idea:
126
+ * ```typescript
127
+ * const result = foo(bar);
128
+ * match(result,
129
+ * (okValue) => {
130
+ * // handle ok case
131
+ * },
132
+ * (errValue) => {
133
+ * // handle err case
134
+ * },
135
+ * );
136
+ * ```
137
+ * The vanilla `if` doesn't allocate the closures, and has less code, and you can
138
+ * control the flow properly inside the blocks with `return`/`break`/`continue`
139
+ * ```typescript
140
+ * const result = foo(bar);
141
+ * if (result.err) {
142
+ * // handle err case
143
+ * } else {
144
+ * // handle ok case
145
+ * }
146
+ * ```
147
+ *
148
+ * As for the other utility functions from Rust's Result type, they really only benefit
149
+ * because you can early return with `?` AND those abstractions are zero-cost in Rust.
150
+ * Neither is true in JavaScript.
151
+ *
152
+ * You can also easily write them yourself if you really want to.
153
+ *
154
+ * @module
155
+ */
156
+ /**
157
+ * A value that either a success (Ok) or an error (Err)
158
+ *
159
+ * Construct a success with { val: ... } and an error with { err: ... }
160
+ */
161
+ export type Result<T, E> = Ok<T> | Err<E>;
162
+ /** A success value */
163
+ export interface Ok<T> {
164
+ val: T;
165
+ err?: never;
166
+ }
167
+ /** An error value */
168
+ export interface Err<E> {
169
+ err: E;
170
+ val?: never;
171
+ }
172
+ /**
173
+ * A value that is either `void` or an error
174
+ *
175
+ * Construct success with `{}` and an error with `{ err: ... }`
176
+ */
177
+ export type Void<E> = {
178
+ val?: never;
179
+ err?: never;
180
+ } | {
181
+ err: E;
182
+ };
183
+ /** A value that is a success `void` */
184
+ export type VoidOk = Record<string, never>;
185
+ /** Wrap a function with try-catch and return a Result. */
186
+ export declare function tryCatch<T, E = Error>(fn: () => T): Result<T, E>;
187
+ /** Wrap an async function with try-catch and return a Promise<Result>. */
188
+ export declare function tryAsync<T, E = Error>(fn: () => Promise<T>): Promise<Result<T, E>>;
189
+ /** Try best effort converting an error to a string */
190
+ export declare function errstr(e: unknown, recursing?: boolean): string;
191
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/result/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0JG;AAEH;;;;GAIG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAK1C,sBAAsB;AACtB,MAAM,WAAW,EAAE,CAAC,CAAC;IACjB,GAAG,EAAE,CAAC,CAAC;IACP,GAAG,CAAC,EAAE,KAAK,CAAC;CACf;AACD,qBAAqB;AACrB,MAAM,WAAW,GAAG,CAAC,CAAC;IAClB,GAAG,EAAE,CAAC,CAAC;IACP,GAAG,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC;IAAC,GAAG,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,CAAC,CAAA;CAAE,CAAC;AAChE,uCAAuC;AACvC,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE3C,0DAA0D;AAC1D,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAMhE;AAED,0EAA0E;AAC1E,wBAAsB,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAMxF;AAED,sDAAsD;AACtD,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAkC9D"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Ensure you have exclusive access in concurrent code
3
+ *
4
+ * Only guaranteed if no one else has reference to the inner object
5
+ *
6
+ * It can take a second type parameter to specify interface with write methods
7
+ *
8
+ * @deprecated unstable API
9
+ */
10
+ export declare class RwLock<TRead, TWrite extends TRead = TRead> {
11
+ /**
12
+ * This is public so inner object can be accessed directly
13
+ * ONLY SAFE in sync context
14
+ */
15
+ inner: TWrite;
16
+ private readers;
17
+ private isWriting;
18
+ private readWaiters;
19
+ private writeWaiters;
20
+ constructor(t: TWrite);
21
+ /** Acquire a read (shared) lock and call fn with the value. Release the lock when fn returns or throws. */
22
+ scopedRead<R>(fn: (t: TRead) => Promise<R>): Promise<R>;
23
+ /**
24
+ * Acquire a write (exclusive) lock and call fn with the value. Release the lock when fn returns or throws.
25
+ *
26
+ * fn takes a setter function as second parameter, which you can use to update the value like `x = set(newX)`
27
+ */
28
+ scopedWrite<R>(fn: (t: TWrite, setter: (t: TWrite) => TWrite) => Promise<R>): Promise<R>;
29
+ }
30
+ //# sourceMappingURL=RwLock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RwLock.d.ts","sourceRoot":"","sources":["../../../../src/sync/RwLock.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,qBAAa,MAAM,CAAC,KAAK,EAAE,MAAM,SAAS,KAAK,GAAG,KAAK;IACnD;;;OAGG;IACI,KAAK,EAAE,MAAM,CAAC;IAErB,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,YAAY,CAAkC;gBAE1C,CAAC,EAAE,MAAM;IAIrB,2GAA2G;IAC9F,UAAU,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAkCpE;;;;OAIG;IACU,WAAW,CAAC,CAAC,EACtB,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7D,OAAO,CAAC,CAAC,CAAC;CA+BhB"}
@@ -0,0 +1,112 @@
1
+ import { type AnyFn, type AwaitRet } from "./util.ts";
2
+ /** Factory for batched function. See {@link BatchConstructor} for usage. */
3
+ export declare function batch<TFn extends AnyFn>(args: BatchConstructor<TFn>): (...args: Parameters<TFn>) => Promise<AwaitRet<TFn>>;
4
+ /**
5
+ * Options to construct a `batch` function
6
+ *
7
+ * A batch function is an async event wrapper that allows multiple calls in an interval
8
+ * to be batched together, and only call the underlying function once.
9
+ *
10
+ * Optionally, the output can be unbatched to match the inputs.
11
+ *
12
+ * ## Example
13
+ * The API is a lot like `debounce`, but with an additional `batch` function
14
+ * and an optional `unbatch` function.
15
+ * ```typescript
16
+ * import { batch } from "@pistonite/pure/sync";
17
+ *
18
+ * const execute = batch({
19
+ * fn: (n: number) => {
20
+ * console.log(n);
21
+ * },
22
+ * interval: 100,
23
+ * // batch receives all the inputs and returns a single input
24
+ * // here we just sums the inputs
25
+ * batch: (args: [number][]): [number] => [args.reduce((acc, [n]) => acc + n, 0)],
26
+ * });
27
+ *
28
+ * await execute(1); // logs 1 immediately
29
+ * const p1 = execute(2); // will be resolved at 100ms
30
+ * const p2 = execute(3); // will be resolved at 100ms
31
+ * await Promise.all([p1, p2]); // logs 5 after 100ms
32
+ * ```
33
+ *
34
+ * ## Unbatching
35
+ * The optional `unbatch` function allows the output to be unbatched,
36
+ * so the promises are resolved as if the underlying function is called
37
+ * directly.
38
+ *
39
+ * Note that unbatching is usually slow and not required.
40
+ *
41
+ * ```typescript
42
+ * import { batch } from "@pistonite/pure/sync";
43
+ *
44
+ * type Message = {
45
+ * id: number;
46
+ * payload: string;
47
+ * }
48
+ *
49
+ * const execute = batch({
50
+ * fn: (messages: Message[]): Message[] => {
51
+ * console.log(messages.length);
52
+ * return messages.map((m) => ({
53
+ * id: m.id,
54
+ * payload: m.payload + "out",
55
+ * }));
56
+ * },
57
+ * batch: (args: [Message[]][]): [Message[]] => {
58
+ * const out: Message[] = [];
59
+ * for (const [messages] of args) {
60
+ * out.push(...messages);
61
+ * }
62
+ * return [out];
63
+ * },
64
+ * unbatch: (inputs: [Message[]][], output: Message[]): Message[][] => {
65
+ * // not efficient, but just for demonstration
66
+ * const idToOutput = new Map();
67
+ * for (const o of output) {
68
+ * idToOutput.set(o.id, o);
69
+ * }
70
+ * return inputs.map(([messages]) => {
71
+ * return messages.map(({id}) => {
72
+ * return idToOutput.get(m.id)!;
73
+ * });
74
+ * });
75
+ * },
76
+ * interval: 100,
77
+ * });
78
+ *
79
+ * const r1 = await execute([{id: 1, payload: "a"}]); // logs 1 immediately
80
+ * // r1 is [ {id: 1, payload: "aout"} ]
81
+ *
82
+ * const p1 = execute([{id: 2, payload: "b"}]); // will be resolved at 100ms
83
+ * const p2 = execute([{id: 3, payload: "c"}]); // will be resolved at 100ms
84
+ *
85
+ * const r2 = await p2; // 2 is logged
86
+ * // r1 is [ {id: 2, payload: "bout"} ]
87
+ * const r3 = await p3; // nothing is logged, as it's already resolved
88
+ * // r2 is [ {id: 3, payload: "cout"} ]
89
+ *
90
+ * ```
91
+ *
92
+ */
93
+ export interface BatchConstructor<TFn extends AnyFn> {
94
+ /** Function to be wrapped */
95
+ fn: TFn;
96
+ /** Function to batch the inputs across multiple calls */
97
+ batch: (args: Parameters<TFn>[]) => Parameters<TFn>;
98
+ /**
99
+ * If provided, unbatch the output according to the inputs,
100
+ * so each call receives its own output.
101
+ *
102
+ * By default, each input will receive the same output from the batched call
103
+ */
104
+ unbatch?: (inputs: Parameters<TFn>[], output: AwaitRet<TFn>) => AwaitRet<TFn>[];
105
+ /**
106
+ * Interval between each batched call
107
+ */
108
+ interval: number;
109
+ /** See `debounce` for more information */
110
+ disregardExecutionTime?: boolean;
111
+ }
112
+ //# sourceMappingURL=batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../../../src/sync/batch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAmC,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEvF,4EAA4E;AAC5E,wBAAgB,KAAK,CAAC,GAAG,SAAS,KAAK,EAAE,IAAI,EAAE,gBAAgB,CAAC,GAAG,CAAC,IAGxD,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,4BACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AACH,MAAM,WAAW,gBAAgB,CAAC,GAAG,SAAS,KAAK;IAC/C,6BAA6B;IAC7B,EAAE,EAAE,GAAG,CAAC;IACR,yDAAyD;IACzD,KAAK,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,KAAK,UAAU,CAAC,GAAG,CAAC,CAAC;IAEpD;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;IAEhF;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB,0CAA0C;IAC1C,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACpC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Execute an async closure `fn`, and guarantee that `obj` will not be
3
+ * garbage-collected, until the promise is resolved.
4
+ */
5
+ export declare const scopedCapture: <T>(fn: () => Promise<T>, obj: unknown) => Promise<T>;
6
+ /**
7
+ * Execute a closure `fn`, and guarantee that `obj` will not be
8
+ * garbage-collected during the execution
9
+ */
10
+ export declare const scopedCaptureSync: <T>(fn: () => T, obj: unknown) => T;
11
+ //# sourceMappingURL=capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../../../src/sync/capture.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAU,CAAC,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,KAAG,OAAO,CAAC,CAAC,CAYpF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EAAE,IAAI,MAAM,CAAC,EAAE,KAAK,OAAO,KAAG,CAQhE,CAAC"}
@@ -0,0 +1,105 @@
1
+ import { type AnyFn, type AwaitRet } from "./util.ts";
2
+ /** Factory for debounced function. See {@link DebounceConstructor} for usage */
3
+ export declare function debounce<TFn extends AnyFn>(args: DebounceConstructor<TFn>): (...args: Parameters<TFn>) => Promise<AwaitRet<TFn>>;
4
+ /**
5
+ * Options for `debounce` function
6
+ *
7
+ * A debounced function is an async event wrapper that is guaranteed to:
8
+ * - Not re-fire in a minimal interval after it's initialially fired.
9
+ * - All calls will eventually fire
10
+ *
11
+ * The caller will get a promise that resolves the next time the event is fired
12
+ * and resolved.
13
+ *
14
+ * Unlike the naive implementation with a setTimeout, this implementation
15
+ * will not starve the event. If it's constantly being called,
16
+ * it will keep firing the event at at least the minimum interval (might
17
+ * take longer if the underlying function takes longer to execute
18
+ *
19
+ * ## Simple Example
20
+ *
21
+ * Multiple calls will be debounced to the minimum interval
22
+ * ```typescript
23
+ * import { debounce } from "@pistonite/pure/sync";
24
+ *
25
+ * const execute = debounce({
26
+ * fn: () => {
27
+ * console.log("called");
28
+ * }
29
+ * interval: 100,
30
+ * });
31
+ * await execute(); // resolved immediately
32
+ * await execute(); // resolved after 100ms
33
+ * ```
34
+ *
35
+ * ## Discarding extra calls
36
+ * When making multiple calls, if the call is currently being debounced
37
+ * (i.e. executed and the minimum interval hasn't passed), new calls
38
+ * will replace the previous call.
39
+ *
40
+ * If you want to the in-between calls to be preserved,
41
+ * use `batch` instead.
42
+ *
43
+ * ```typescript
44
+ * import { debounce } from "@pistonite/pure/sync";
45
+ *
46
+ * const execute = debounce({
47
+ * fn: (n: number) => {
48
+ * console.log(n);
49
+ * }
50
+ * interval: 100,
51
+ * });
52
+ * await execute(1); // logs 1 immediately
53
+ * const p1 = execute(2); // will be resolved at 100ms
54
+ * await new Promise((resolve) => setTimeout(resolve, 50));
55
+ * await Promise.all[p1, execute(3)]; // will be resolved at 100ms, discarding the 2nd call
56
+ * // 1, 3 will be logged
57
+ * ```
58
+ *
59
+ * ## Slow function
60
+ * By default, the debouncer takes into account the time
61
+ * it takes for the underlying function to execute. It starts
62
+ * the next cycle as soon as both the minimul interval has passed
63
+ * and the function has finished executing. This ensures only
64
+ * 1 call is being executed at a time.
65
+ *
66
+ * However, if you want the debouncer to always debounce at the set interval,
67
+ * regardless of if the previous call has finished, set `disregardExecutionTime`
68
+ * to true.
69
+ *
70
+ * ```typescript
71
+ * import { debounce } from "@pistonite/pure/sync";
72
+ *
73
+ * const execute = debounce({
74
+ * fn: async (n: number) => {
75
+ * await new Promise((resolve) => setTimeout(resolve, 150));
76
+ * console.log(n);
77
+ * },
78
+ * interval: 100,
79
+ * // without this, will debounce at the interval of 150ms
80
+ * disregardExecutionTime: true,
81
+ * });
82
+ * ```
83
+ */
84
+ export interface DebounceConstructor<TFn> {
85
+ /** Function to be debounced */
86
+ fn: TFn;
87
+ /**
88
+ * Minimum interval between each call
89
+ *
90
+ * Setting this to <= 0 will make the debounce function
91
+ * a pure pass-through, not actually debouncing the function
92
+ */
93
+ interval: number;
94
+ /**
95
+ * By default, the debouncer takes in account the time
96
+ * the underlying function executes. i.e. the actual debounce
97
+ * interval is `max(interval, executionTime)`. This default
98
+ * behavior guanrantees that no 2 calls will be executed concurrently.
99
+ *
100
+ * If you want the debouncer to always debounce at the set interval,
101
+ * set this to true.
102
+ */
103
+ disregardExecutionTime?: boolean;
104
+ }
105
+ //# sourceMappingURL=debounce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debounce.d.ts","sourceRoot":"","sources":["../../../../src/sync/debounce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAmC,MAAM,WAAW,CAAC;AAEvF,gFAAgF;AAChF,wBAAgB,QAAQ,CAAC,GAAG,SAAS,KAAK,EAAE,IAAI,EAAE,mBAAmB,CAAC,GAAG,CAAC,IAG9D,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,4BACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+EG;AACH,MAAM,WAAW,mBAAmB,CAAC,GAAG;IACpC,+BAA+B;IAC/B,EAAE,EAAE,GAAG,CAAC;IACR;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACpC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Synchronization utilities
3
+ *
4
+ * @module
5
+ */
6
+ export { serial, type SerialConstructor, type SerialEventCancelCallback, type SerialCancelToken, } from "./serial.ts";
7
+ export { latest, type LatestConstructor, type LatestUpdateArgsFn } from "./latest.ts";
8
+ export { debounce, type DebounceConstructor } from "./debounce.ts";
9
+ export { batch, type BatchConstructor } from "./batch.ts";
10
+ export { once, type OnceConstructor } from "./once.ts";
11
+ export { makePromise, type PromiseHandle } from "./util.ts";
12
+ export { scopedCapture, scopedCaptureSync } from "./capture.ts";
13
+ export type { AnyFn, AwaitRet } from "./util.ts";
14
+ export { RwLock } from "./RwLock.ts";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACH,MAAM,EACN,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,GACzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,KAAK,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGhE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,86 @@
1
+ import { type AnyFn, type AwaitRet } from "./util.ts";
2
+ /**
3
+ * Factory for `latest`. See {@link LatestConstructor} for usage.
4
+ */
5
+ export declare function latest<TFn extends AnyFn>(args: LatestConstructor<TFn>): (...args: Parameters<TFn>) => Promise<AwaitRet<TFn>>;
6
+ /**
7
+ * Args for constructing `latest`
8
+ *
9
+ * `latest` is an async event wrapper that always resolve to the result of the latest
10
+ * call
11
+ *
12
+ * ## Example
13
+ * In the example below, both call will return the result
14
+ * of the second call (2)
15
+ * ```typescript
16
+ * import { latest } from "@pistonite/pure/sync";
17
+ *
18
+ * let counter = 0;
19
+ *
20
+ * const execute = latest({
21
+ * fn: async () => {
22
+ * counter++;
23
+ * await new Promise((resolve) => setTimeout(() => {
24
+ * resolve(counter);
25
+ * }, 1000));
26
+ * }
27
+ * });
28
+ *
29
+ * const result1 = execute();
30
+ * const result2 = execute();
31
+ * console.log(await result1); // 2
32
+ * console.log(await result2); // 2
33
+ * ```
34
+ *
35
+ * ## Advanced Usage
36
+ * See the constructor options for more advanced usage, for example,
37
+ * control how arguments are updated when new calls are made.
38
+ */
39
+ export interface LatestConstructor<TFn extends AnyFn> {
40
+ /** Function to be wrapped */
41
+ fn: TFn;
42
+ /**
43
+ * Optional function to compare if arguments of 2 calls are equal.
44
+ *
45
+ * By default, separate calls are considered different, and the result
46
+ * of the latest call will be returned. However, if the function is pure,
47
+ * and the argument of a new call is the same as the call being executed,
48
+ * then the result of the call being executed will be returned. In other words,
49
+ * the new call will not result in another execution of the function.
50
+ */
51
+ areArgsEqual?: (a: Parameters<TFn>, b: Parameters<TFn>) => boolean;
52
+ /**
53
+ * Optional function to update the arguments.
54
+ *
55
+ * By default, when new calls are made while the previous call is being executed,
56
+ * The function will be executed again with the latest arguments. This function
57
+ * is used to change this behavior and is called when new calls are made. In other words,
58
+ * the default value for this function is `(_current, _middle, latest) => latest`.
59
+ *
60
+ * The arguments are:
61
+ * - `current`: The arguments of the call currently being executed
62
+ * - `latest`: The argument of this new call
63
+ * - `middle`: If more than one call is made while the previous call is being executed,
64
+ * this array contains arguments of the calls between `current` and `latest`
65
+ * - `next`: This is the returned value of the previous call to updateArgs, i.e. the args
66
+ * to be executed next.
67
+ *
68
+ */
69
+ updateArgs?: LatestUpdateArgsFn<TFn>;
70
+ }
71
+ /** See {@link LatestConstructor} */
72
+ export type LatestUpdateArgsFn<TFn extends AnyFn> = (current: Parameters<TFn>, middle: Parameters<TFn>[], latest: Parameters<TFn>, next: Parameters<TFn> | undefined) => Parameters<TFn>;
73
+ export declare class LatestImpl<TFn extends AnyFn> {
74
+ private fn;
75
+ private pending?;
76
+ /** current arguments. undefined means no current call */
77
+ private currentArgs?;
78
+ /** next arguments. undefined means no newer call */
79
+ private nextArgs?;
80
+ private middleArgs;
81
+ private areArgsEqual;
82
+ private updateArgs;
83
+ constructor(fn: TFn, areArgsEqual?: (a: Parameters<TFn>, b: Parameters<TFn>) => boolean, updateArgs?: LatestUpdateArgsFn<TFn>);
84
+ invoke(...args: Parameters<TFn>): Promise<AwaitRet<TFn>>;
85
+ }
86
+ //# sourceMappingURL=latest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"latest.d.ts","sourceRoot":"","sources":["../../../../src/sync/latest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAmC,MAAM,WAAW,CAAC;AAEvF;;GAEG;AACH,wBAAgB,MAAM,CAAC,GAAG,SAAS,KAAK,EAAE,IAAI,EAAE,iBAAiB,CAAC,GAAG,CAAC,IAG1D,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,4BACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,WAAW,iBAAiB,CAAC,GAAG,SAAS,KAAK;IAChD,6BAA6B;IAC7B,EAAE,EAAE,GAAG,CAAC;IAER;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC;IAEnE;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CAAC,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC;CACxC;AACD,oCAAoC;AACpC,MAAM,MAAM,kBAAkB,CAAC,GAAG,SAAS,KAAK,IAAI,CAChD,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC,EACxB,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,EACzB,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,EACvB,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,GAAG,SAAS,KAChC,UAAU,CAAC,GAAG,CAAC,CAAC;AACrB,qBAAa,UAAU,CAAC,GAAG,SAAS,KAAK;IAcjC,OAAO,CAAC,EAAE;IAbd,OAAO,CAAC,OAAO,CAAC,CAA+B;IAE/C,yDAAyD;IACzD,OAAO,CAAC,WAAW,CAAC,CAAkB;IACtC,oDAAoD;IACpD,OAAO,CAAC,QAAQ,CAAC,CAAkB;IAEnC,OAAO,CAAC,UAAU,CAAoB;IAEtC,OAAO,CAAC,YAAY,CAAsD;IAC1E,OAAO,CAAC,UAAU,CAA0B;gBAGhC,EAAE,EAAE,GAAG,EACf,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,KAAK,OAAO,EAClE,UAAU,CAAC,EAAE,kBAAkB,CAAC,GAAG,CAAC;IAO3B,MAAM,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;CAyCxE"}
@@ -0,0 +1,14 @@
1
+ export {};
2
+ /**
3
+ * Non-reentrant mutex
4
+ *
5
+ * This allows only one context to enter a block at a time in a FIFO manner.
6
+ *
7
+ * This mutex is non-reentrant. Trying to lock it again while the same
8
+ * context already owns the lock will cause a dead lock.
9
+ *
10
+ * While a context id can be used to implement reentrant locks,
11
+ * it is very cumbersome to use. https://github.com/tc39/proposal-async-context
12
+ * will allow for a cleaner implementation.
13
+ */
14
+ //# sourceMappingURL=mutex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutex.d.ts","sourceRoot":"","sources":["../../../../src/sync/mutex.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG"}
@@ -0,0 +1,84 @@
1
+ import { type AnyFn, type AwaitRet } from "./util.ts";
2
+ /**
3
+ * Factory function for `once`. See {@link OnceConstructor} for usage
4
+ */
5
+ export declare function once<TFn extends AnyFn>(args: OnceConstructor<TFn>): (...args: Parameters<TFn>) => Promise<AwaitRet<TFn>>;
6
+ /**
7
+ * Args for constructing a `once`
8
+ *
9
+ * `once` is an async event wrapper that ensures an async initialization is only ran once.
10
+ * Any subsequent calls after the first call will return a promise that resolves/rejects
11
+ * with the result of the first call.
12
+ *
13
+ * ## Example
14
+ * ```typescript
15
+ * import { once } from "@pistonite/pure/sync";
16
+ *
17
+ * const getLuckyNumber = once({
18
+ * fn: async () => {
19
+ * console.log("running expensive initialization...")
20
+ * await new Promise((resolve) => setTimeout(resolve, 100));
21
+ * console.log("done")
22
+ * return 42;
23
+ * }
24
+ * });
25
+ *
26
+ * const result1 = getLuckyNumber();
27
+ * const result2 = getLuckyNumber();
28
+ * console.log(await result1);
29
+ * console.log(await result2);
30
+ * // logs:
31
+ * // running expensive initialization...
32
+ * // done
33
+ * // 42
34
+ * // 42
35
+ * ```
36
+ *
37
+ * ## Caveat with HMR
38
+ * Some initialization might require clean up, such as unregister
39
+ * event handlers and/or timers. In this case, a production build might
40
+ * work fine but a HMR (Hot Module Reload) development server might not
41
+ * do this for you automatically.
42
+ *
43
+ * One way to work around this during development is to store the cleanup
44
+ * as a global object
45
+ * ```typescript
46
+ * const getResourceThatNeedsCleanup = once({
47
+ * fn: async () => {
48
+ * if (__DEV__) { // Configure your bundler to inject this
49
+ * // await if you need async clean up
50
+ * await (window as any).cleanupMyResource?.();
51
+ * }
52
+ *
53
+ * let resource: MyResource;
54
+ * if (__DEV__) {
55
+ * (window as any).cleanupMyResource = async () => {
56
+ * await resource?.cleanup();
57
+ * };
58
+ * }
59
+ *
60
+ * resource = await initResource();
61
+ * return resource;
62
+ * }
63
+ * });
64
+ * ```
65
+ *
66
+ * An alternative solution is to not use `once` but instead tie the initialization
67
+ * of the resource to some other lifecycle event that gets cleaned up during HMR.
68
+ * For example, A framework that supports HMR for React components might unmount
69
+ * the component before reloading, which gives you a chance to clean up the resource.
70
+ *
71
+ * This is not an issue if the resource doesn't leak other resources,
72
+ * since it will eventually be GC'd.
73
+ */
74
+ export interface OnceConstructor<TFn> {
75
+ /** Function to be called only once */
76
+ fn: TFn;
77
+ }
78
+ export declare class OnceImpl<TFn extends AnyFn> {
79
+ private fn;
80
+ private promise;
81
+ constructor(fn: TFn);
82
+ invoke(...args: Parameters<TFn>): Promise<AwaitRet<TFn>>;
83
+ }
84
+ //# sourceMappingURL=once.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"once.d.ts","sourceRoot":"","sources":["../../../../src/sync/once.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAe,MAAM,WAAW,CAAC;AAEnE;;GAEG;AACH,wBAAgB,IAAI,CAAC,GAAG,SAAS,KAAK,EAAE,IAAI,EAAE,eAAe,CAAC,GAAG,CAAC,IAEtD,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,4BACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AACH,MAAM,WAAW,eAAe,CAAC,GAAG;IAChC,sCAAsC;IACtC,EAAE,EAAE,GAAG,CAAC;CACX;AAED,qBAAa,QAAQ,CAAC,GAAG,SAAS,KAAK;IAGvB,OAAO,CAAC,EAAE;IAFtB,OAAO,CAAC,OAAO,CAAqC;gBAEhC,EAAE,EAAE,GAAG;IAId,MAAM,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;CAexE"}
@@ -0,0 +1,162 @@
1
+ import type { Result } from "../result/index.ts";
2
+ import type { AnyFn } from "./util.ts";
3
+ /**
4
+ * Factory function for `serial`. See {@link SerialConstructor} for usage
5
+ */
6
+ export declare const serial: <TFn extends AnyFn>(args: SerialConstructor<TFn>) => (...args: Parameters<TFn>) => Promise<Result<Awaited<ReturnType<TFn>>, "cancel">>;
7
+ /**
8
+ * Options for `serial` function
9
+ *
10
+ * `serial` is an async event wrapper that is cancelled when a new one starts.
11
+ * When a new event is started, the previous caller will receive a
12
+ * cancellation error, instead of being hung up indefinitely.
13
+ *
14
+ * If you want every caller to receive the latest result
15
+ * instead of a cancellation error, use `latest` instead.
16
+ *
17
+ * ## Example
18
+ *
19
+ * ```typescript
20
+ * import { serial } from "@pistonite/pure/sync";
21
+ *
22
+ * // helper function to simulate async work
23
+ * const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
24
+ *
25
+ * // Create the wrapped function
26
+ * const execute = serial({
27
+ * // This has to be curried for type inferrence
28
+ * fn: (checkCancel) => async () => {
29
+ * for (let i = 0; i < 10; i++) {
30
+ * await wait(1000);
31
+ * // The cancellation mechanism throws an error if is cancelled
32
+ * checkCancel();
33
+ * }
34
+ * return 42;
35
+ * }
36
+ * });
37
+ *
38
+ * // execute it the first time
39
+ * const promise1 = execute();
40
+ * await wait(3000);
41
+ *
42
+ * // calling event.run a second time will cause `checkCancel` to return false
43
+ * // the next time it's called by the first event
44
+ * const promise2 = execute();
45
+ *
46
+ * console.log(await promise1); // { err: "cancel" }
47
+ * console.log(await promise2); // { val: 42 }
48
+ * ```
49
+ *
50
+ * ## Passing in arguments
51
+ * TypeScript magic is used to ensure full type-safety when passing in arguments.
52
+ *
53
+ * ```typescript
54
+ * import { serial, type SerialCancelToken } from "@pistonite/pure/sync";
55
+ * import type { Result } from "@pistonite/pure/result";
56
+ *
57
+ * const execute = serial({
58
+ * fn: (checkCancel) => async (arg1: number, arg2: string) => {
59
+ *
60
+ * // do something with arg1 and arg2
61
+ * console.log(arg1, arg2);
62
+ *
63
+ * // ...
64
+ * }
65
+ * });
66
+ *
67
+ * expectTypeOf(execute)
68
+ * .toEqualTypeOf<
69
+ * (arg1: number, arg2: string) => Promise<Result<void, SerialCancelToken>>
70
+ * >();
71
+ *
72
+ * await execute(42, "hello"); // no type error!
73
+ * ```
74
+ *
75
+ * ## Getting the current serial number
76
+ * The serial number has type `bigint` and is incremented every time `run` is called.
77
+ *
78
+ * You can have an extra argument after `checkCancel`, that will receive the current serial number,
79
+ * if you need it for some reason.
80
+ * ```typescript
81
+ * import { serial } from "@pistonite/pure/sync";
82
+ *
83
+ * const execute = serial({
84
+ * fn: (checkCancel, serial) => () => { console.log(serial); }
85
+ * });
86
+ *
87
+ * await execute(); // 1n
88
+ * await execute(); // 2n
89
+ * ```
90
+ *
91
+ * ## Checking for cancel
92
+ * It's the event handler's responsibility to check if the event is cancelled by
93
+ * calling the `checkCancel` function. This function will throw if the event
94
+ * is cancelled, and the error will be caught by the wrapper and returned as an `Err`
95
+ *
96
+ * Note that even if you don't check it, there is one final check before the result is returned.
97
+ * So you will never get a result from a cancelled event. Also note that you only need to check
98
+ * after any `await` calls. If there's no `await`, everything is executed synchronously,
99
+ * and it's theoretically impossible to cancel the event. However, this depends on
100
+ * the runtime's implementation of promises.
101
+ *
102
+ * ## Handling cancelled event
103
+ * To check if an event is completed or cancelled, simply `await`
104
+ * on the promise check the `err`
105
+ * ```typescript
106
+ * import { serial } from "@pistonite/pure/sync";
107
+ *
108
+ * const execute = serial({
109
+ * fn: (checkCancel) => async () => {
110
+ * // your code here ...
111
+ * }
112
+ * });
113
+ * const result = await execute();
114
+ * if (result.err === "cancel") {
115
+ * console.log("event was cancelled");
116
+ * } else {
117
+ * console.log("event completed");
118
+ * }
119
+ * ```
120
+ *
121
+ * You can also pass in a callback to the constructor, which will be called
122
+ * when the event is cancelled. The cancel callback is guaranteed to only fire at most once per run
123
+ * ```typescript
124
+ * import { serial } from "@pistonite/pure/sync";
125
+ *
126
+ * const onCancel = (current: bigint, latest: bigint) => {
127
+ * console.log(`Event with serial ${current} is cancelled because the latest serial is ${latest}`);
128
+ * };
129
+ *
130
+ * const execute = new Serial({
131
+ * fn: ...,
132
+ * onCancel,
133
+ * });
134
+ * ```
135
+ *
136
+ * ## Exception handling
137
+ *
138
+ * If the underlying function throws, the exception will be re-thrown to the caller.
139
+ */
140
+ export interface SerialConstructor<TFn> {
141
+ /**
142
+ * Function creator that returns the async function to be wrapped
143
+ */
144
+ fn: (checkCancel: CheckCancelFn, current: SerialId) => TFn;
145
+ /**
146
+ * Optional callback to be called when the event is cancelled
147
+ *
148
+ * This is guaranteed to be only called at most once per execution
149
+ */
150
+ onCancel?: SerialEventCancelCallback;
151
+ }
152
+ type SerialId = bigint;
153
+ type CheckCancelFn = () => void;
154
+ /**
155
+ * The callback type passed to SerialEvent constructor to be called
156
+ * when the event is cancelled
157
+ */
158
+ export type SerialEventCancelCallback = (current: SerialId, latest: SerialId) => void;
159
+ /** The error type received by caller when an event is cancelled */
160
+ export type SerialCancelToken = "cancel";
161
+ export {};
162
+ //# sourceMappingURL=serial.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serial.d.ts","sourceRoot":"","sources":["../../../../src/sync/serial.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAEvC;;GAEG;AACH,eAAO,MAAM,MAAM,GAAI,GAAG,SAAS,KAAK,EAAE,MAAM,iBAAiB,CAAC,GAAG,CAAC,MAG1D,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,wDACnC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoIG;AACH,MAAM,WAAW,iBAAiB,CAAC,GAAG;IAClC;;OAEG;IACH,EAAE,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,KAAK,GAAG,CAAC;IAC3D;;;;OAIG;IACH,QAAQ,CAAC,EAAE,yBAAyB,CAAC;CACxC;AA8CD,KAAK,QAAQ,GAAG,MAAM,CAAC;AACvB,KAAK,aAAa,GAAG,MAAM,IAAI,CAAC;AAGhC;;;GAGG;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;AAEtF,mEAAmE;AACnE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Make a {@link PromiseHandle} with the promise object separate from
3
+ * its resolve and reject methods
4
+ */
5
+ export declare const makePromise: <T>() => PromiseHandle<T>;
6
+ /**
7
+ * A handle of the promise that breaks down the promise object
8
+ * and its resolve and reject functions
9
+ */
10
+ export interface PromiseHandle<T> {
11
+ promise: Promise<T>;
12
+ resolve: (value: T | PromiseLike<T>) => void;
13
+ reject: (reason?: unknown) => void;
14
+ }
15
+ /** Shorthand for Awaited<ReturnType<T>> */
16
+ export type AwaitRet<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;
17
+ /** Type for any function */
18
+ export type AnyFn = (...args: any[]) => any;
19
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../../src/sync/util.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,OAAK,aAAa,CAAC,CAAC,CAUhD,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC7C,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACtC;AAED,2CAA2C;AAE3C,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AAErF,4BAA4B;AAE5B,MAAM,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pistonite/pure",
3
- "version": "0.29.0",
3
+ "version": "0.29.1",
4
4
  "type": "module",
5
5
  "description": "Pure TypeScript libraries for my projects",
6
6
  "homepage": "https://github.com/Pistonite/pure",