@peerigon/typescript-toolkit 2.0.0 → 2.2.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/README.md +11 -7
- package/dist/emitter/emitter.d.ts +60 -0
- package/dist/emitter/emitter.d.ts.map +1 -0
- package/dist/emitter/emitter.js +92 -0
- package/dist/emitter/emitter.js.map +1 -0
- package/dist/lib/create-prototype.d.ts +2 -0
- package/dist/lib/create-prototype.d.ts.map +1 -0
- package/dist/lib/create-prototype.js +13 -0
- package/dist/lib/create-prototype.js.map +1 -0
- package/dist/result/result.d.ts +159 -0
- package/dist/result/result.d.ts.map +1 -0
- package/dist/result/result.js +192 -0
- package/dist/result/result.js.map +1 -0
- package/dist/result/result.lib.d.ts +10 -0
- package/dist/result/result.lib.d.ts.map +1 -0
- package/dist/result/result.lib.js +13 -0
- package/dist/result/result.lib.js.map +1 -0
- package/dist/signals/signals.d.ts +19 -0
- package/dist/signals/signals.d.ts.map +1 -0
- package/dist/signals/signals.js +51 -0
- package/dist/signals/signals.js.map +1 -0
- package/dist/unwrap/unwrap.d.ts +36 -0
- package/dist/unwrap/unwrap.d.ts.map +1 -0
- package/dist/unwrap/unwrap.js +52 -0
- package/dist/unwrap/unwrap.js.map +1 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -37,13 +37,17 @@ import { assert } from "@peerigon/typescript-toolkit/assert";
|
|
|
37
37
|
|
|
38
38
|
## Utilities
|
|
39
39
|
|
|
40
|
-
| Module
|
|
41
|
-
|
|
|
42
|
-
| [`assert`](./src/assert/README.md)
|
|
43
|
-
| [`need`](./src/need/README.md)
|
|
44
|
-
| [`dedupe`](./src/dedupe/README.md)
|
|
45
|
-
| [`
|
|
46
|
-
| [`
|
|
40
|
+
| Module | Description | Docs |
|
|
41
|
+
| ------------------------------------ | -------------------------------------------------------------------------------- | ---------------------------- |
|
|
42
|
+
| [`assert`](./src/assert/README.md) | Assert a value is not `null` or `undefined`, with TypeScript narrowing | [→](./src/assert/README.md) |
|
|
43
|
+
| [`need`](./src/need/README.md) | Assert a value is not `null` or `undefined` and return it with a narrowed type | [→](./src/need/README.md) |
|
|
44
|
+
| [`dedupe`](./src/dedupe/README.md) | Remove duplicate values from an array while preserving first-occurrence order | [→](./src/dedupe/README.md) |
|
|
45
|
+
| [`emitter`](./src/emitter/README.md) | Minimal typed event emitter with payload objects per event | [→](./src/emitter/README.md) |
|
|
46
|
+
| [`enums`](./src/enums/README.md) | Lightweight string-enum alternative for `erasableSyntaxOnly` TypeScript projects | [→](./src/enums/README.md) |
|
|
47
|
+
| [`match`](./src/match/README.md) | Exhaustive pattern matching with compile-time case checks, similar to `switch` | [→](./src/match/README.md) |
|
|
48
|
+
| [`result`](./src/result/README.md) | Type-safe error handling with pending, success, and error states | [→](./src/result/README.md) |
|
|
49
|
+
| [`signals`](./src/signals/README.md) | Push-based reactive state with explicit subscribers and `signal.from` adapters | [→](./src/signals/README.md) |
|
|
50
|
+
| [`unwrap`](./src/unwrap/README.md) | Extract values from `Result` or nullable types, with optional fallback support | [→](./src/unwrap/README.md) |
|
|
47
51
|
|
|
48
52
|
## License
|
|
49
53
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal typed event emitter with payload objects.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* const emitter = new Emitter<{
|
|
7
|
+
* click: { x: number; y: number };
|
|
8
|
+
* error: { error: Error };
|
|
9
|
+
* }>();
|
|
10
|
+
*
|
|
11
|
+
* emitter.on("error", ({ error, target }) => {
|
|
12
|
+
* console.error(error, target);
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* emitter.emit("error", { error: new Error("Something went wrong") });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare class Emitter<Payloads extends Emitter.Payloads> implements Emitter.Events<Payloads> {
|
|
19
|
+
#private;
|
|
20
|
+
/**
|
|
21
|
+
* Register a listener for an event.
|
|
22
|
+
*
|
|
23
|
+
* @returns A function that removes the listener
|
|
24
|
+
*/
|
|
25
|
+
on<EventName extends keyof Payloads>(event: EventName, listener: (payload: Emitter.EventPayload<Payloads, EventName>) => void): Emitter.Unsubscribe;
|
|
26
|
+
/**
|
|
27
|
+
* Register a listener that runs at most once for an event.
|
|
28
|
+
*
|
|
29
|
+
* @returns A function that removes the listener before it runs
|
|
30
|
+
*/
|
|
31
|
+
once<EventName extends keyof Payloads>(event: EventName, listener: (payload: Emitter.EventPayload<Payloads, EventName>) => void): Emitter.Unsubscribe;
|
|
32
|
+
readonly events: Emitter.Events<Payloads>["events"];
|
|
33
|
+
/**
|
|
34
|
+
* Emit a payload to all listeners registered for an event.
|
|
35
|
+
*
|
|
36
|
+
* The `target` property is set to this emitter. For `error` events with no
|
|
37
|
+
* listeners, throws the emitted `error` property.
|
|
38
|
+
*/
|
|
39
|
+
emit<EventName extends keyof Payloads>(event: EventName, payload: Payloads[EventName]): void;
|
|
40
|
+
}
|
|
41
|
+
type ErrorEventPayload = Record<string, unknown> & {
|
|
42
|
+
error: Error;
|
|
43
|
+
};
|
|
44
|
+
export declare namespace Emitter {
|
|
45
|
+
type EventPayload<GivenEvents extends Payloads = Payloads, EventName extends keyof GivenEvents = keyof GivenEvents> = {
|
|
46
|
+
target: Emitter<GivenEvents>;
|
|
47
|
+
} & GivenEvents[EventName];
|
|
48
|
+
type Payloads = Record<string, Record<string, unknown>> & {
|
|
49
|
+
error?: ErrorEventPayload;
|
|
50
|
+
};
|
|
51
|
+
type Unsubscribe = (() => void) & Disposable;
|
|
52
|
+
interface Events<GivenPayloads extends Payloads = Payloads> {
|
|
53
|
+
readonly events: {
|
|
54
|
+
readonly on: Emitter<GivenPayloads>["on"];
|
|
55
|
+
readonly once: Emitter<GivenPayloads>["once"];
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export {};
|
|
60
|
+
//# sourceMappingURL=emitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../../src/emitter/emitter.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,OAAO,CAClB,QAAQ,SAAS,OAAO,CAAC,QAAQ,CACjC,YAAW,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;;IAMnC;;;;OAIG;IACH,EAAE,CAAC,SAAS,SAAS,MAAM,QAAQ,EACjC,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,IAAI,GACrE,OAAO,CAAC,WAAW;IAUtB;;;;OAIG;IACH,IAAI,CAAC,SAAS,SAAS,MAAM,QAAQ,EACnC,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,IAAI,GACrE,OAAO,CAAC,WAAW;IAStB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAGjD;IAEF;;;;;OAKG;IACH,IAAI,CAAC,SAAS,SAAS,MAAM,QAAQ,EACnC,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,GAC3B,IAAI;CAoDR;AAED,KAAK,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAEpE,yBAAiB,OAAO,CAAC;IACvB,KAAY,YAAY,CACtB,WAAW,SAAS,QAAQ,GAAG,QAAQ,EACvC,SAAS,SAAS,MAAM,WAAW,GAAG,MAAM,WAAW,IACrD;QAAE,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;KAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAE9D,KAAY,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG;QAC/D,KAAK,CAAC,EAAE,iBAAiB,CAAC;KAC3B,CAAC;IAEF,KAAY,WAAW,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,UAAU,CAAC;IAGpD,UAAiB,MAAM,CAAC,aAAa,SAAS,QAAQ,GAAG,QAAQ;QAC/D,QAAQ,CAAC,MAAM,EAAE;YACf,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC;YAC1C,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;SAC/C,CAAC;KACH;CACF"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Namespaces are only used to group related types together.
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-namespace -- group related types under Emitter */
|
|
3
|
+
/**
|
|
4
|
+
* Minimal typed event emitter with payload objects.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const emitter = new Emitter<{
|
|
9
|
+
* click: { x: number; y: number };
|
|
10
|
+
* error: { error: Error };
|
|
11
|
+
* }>();
|
|
12
|
+
*
|
|
13
|
+
* emitter.on("error", ({ error, target }) => {
|
|
14
|
+
* console.error(error, target);
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* emitter.emit("error", { error: new Error("Something went wrong") });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export class Emitter {
|
|
21
|
+
#listeners = new Map();
|
|
22
|
+
/**
|
|
23
|
+
* Register a listener for an event.
|
|
24
|
+
*
|
|
25
|
+
* @returns A function that removes the listener
|
|
26
|
+
*/
|
|
27
|
+
on(event, listener) {
|
|
28
|
+
const listeners = this.#getOrCreateListeners(event);
|
|
29
|
+
listeners.add(listener);
|
|
30
|
+
return this.#createUnsubscribe(() => {
|
|
31
|
+
listeners.delete(listener);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Register a listener that runs at most once for an event.
|
|
36
|
+
*
|
|
37
|
+
* @returns A function that removes the listener before it runs
|
|
38
|
+
*/
|
|
39
|
+
once(event, listener) {
|
|
40
|
+
const remove = this.on(event, (payload) => {
|
|
41
|
+
remove();
|
|
42
|
+
listener(payload);
|
|
43
|
+
});
|
|
44
|
+
return remove;
|
|
45
|
+
}
|
|
46
|
+
events = {
|
|
47
|
+
on: this.on.bind(this),
|
|
48
|
+
once: this.once.bind(this),
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Emit a payload to all listeners registered for an event.
|
|
52
|
+
*
|
|
53
|
+
* The `target` property is set to this emitter. For `error` events with no
|
|
54
|
+
* listeners, throws the emitted `error` property.
|
|
55
|
+
*/
|
|
56
|
+
emit(event, payload) {
|
|
57
|
+
const listeners = this.#getListeners(event);
|
|
58
|
+
if (!listeners || listeners.size === 0) {
|
|
59
|
+
if (event === "error" && "error" in payload) {
|
|
60
|
+
throw payload["error"];
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const eventPayload = {
|
|
65
|
+
...payload,
|
|
66
|
+
target: this,
|
|
67
|
+
};
|
|
68
|
+
for (const listener of listeners) {
|
|
69
|
+
listener(eventPayload);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
#createUnsubscribe(remove) {
|
|
73
|
+
const unsubscribe = () => {
|
|
74
|
+
remove();
|
|
75
|
+
};
|
|
76
|
+
unsubscribe[Symbol.dispose] = unsubscribe;
|
|
77
|
+
return unsubscribe;
|
|
78
|
+
}
|
|
79
|
+
#getListeners(event) {
|
|
80
|
+
return this.#listeners.get(event);
|
|
81
|
+
}
|
|
82
|
+
#getOrCreateListeners(event) {
|
|
83
|
+
const listeners = this.#listeners.get(event);
|
|
84
|
+
if (listeners === undefined) {
|
|
85
|
+
const listeners = new Set();
|
|
86
|
+
this.#listeners.set(event, listeners);
|
|
87
|
+
return listeners;
|
|
88
|
+
}
|
|
89
|
+
return listeners;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.js","sourceRoot":"","sources":["../../src/emitter/emitter.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,yFAAyF;AAEzF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,OAAO;IAGT,UAAU,GAAG,IAAI,GAAG,EAG1B,CAAC;IAEJ;;;;OAIG;IACH,EAAE,CACA,KAAgB,EAChB,QAAsE;QAEtE,MAAM,SAAS,GAAG,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;QAEpD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAExB,OAAO,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE;YAClC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,IAAI,CACF,KAAgB,EAChB,QAAsE;QAEtE,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE;YACxC,MAAM,EAAE,CAAC;YACT,QAAQ,CAAC,OAAO,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC;IAChB,CAAC;IAEQ,MAAM,GAAuC;QACpD,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;KAC3B,CAAC;IAEF;;;;;OAKG;IACH,IAAI,CACF,KAAgB,EAChB,OAA4B;QAE5B,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAE5C,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvC,IAAI,KAAK,KAAK,OAAO,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;gBAC5C,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;YACzB,CAAC;YAED,OAAO;QACT,CAAC;QAED,MAAM,YAAY,GAA8C;YAC9D,GAAG,OAAO;YACV,MAAM,EAAE,IAAI;SACb,CAAC;QAEF,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,kBAAkB,CAAC,MAAkB;QACnC,MAAM,WAAW,GAAG,GAAG,EAAE;YACvB,MAAM,EAAE,CAAC;QACX,CAAC,CAAC;QACF,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC;QAC1C,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,aAAa,CACX,KAAgB;QAIhB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAED,qBAAqB,CACnB,KAAgB;QAEhB,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAE7C,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,SAAS,GAAG,IAAI,GAAG,EAEtB,CAAC;YACJ,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YACtC,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;CACF","sourcesContent":["// Namespaces are only used to group related types together.\n/* eslint-disable @typescript-eslint/no-namespace -- group related types under Emitter */\n\n/**\n * Minimal typed event emitter with payload objects.\n *\n * @example\n * ```ts\n * const emitter = new Emitter<{\n * click: { x: number; y: number };\n * error: { error: Error };\n * }>();\n *\n * emitter.on(\"error\", ({ error, target }) => {\n * console.error(error, target);\n * });\n *\n * emitter.emit(\"error\", { error: new Error(\"Something went wrong\") });\n * ```\n */\nexport class Emitter<\n Payloads extends Emitter.Payloads,\n> implements Emitter.Events<Payloads> {\n readonly #listeners = new Map<\n keyof Payloads,\n Set<(payload: Emitter.EventPayload<Payloads, keyof Payloads>) => void>\n >();\n\n /**\n * Register a listener for an event.\n *\n * @returns A function that removes the listener\n */\n on<EventName extends keyof Payloads>(\n event: EventName,\n listener: (payload: Emitter.EventPayload<Payloads, EventName>) => void,\n ): Emitter.Unsubscribe {\n const listeners = this.#getOrCreateListeners(event);\n\n listeners.add(listener);\n\n return this.#createUnsubscribe(() => {\n listeners.delete(listener);\n });\n }\n\n /**\n * Register a listener that runs at most once for an event.\n *\n * @returns A function that removes the listener before it runs\n */\n once<EventName extends keyof Payloads>(\n event: EventName,\n listener: (payload: Emitter.EventPayload<Payloads, EventName>) => void,\n ): Emitter.Unsubscribe {\n const remove = this.on(event, (payload) => {\n remove();\n listener(payload);\n });\n\n return remove;\n }\n\n readonly events: Emitter.Events<Payloads>[\"events\"] = {\n on: this.on.bind(this),\n once: this.once.bind(this),\n };\n\n /**\n * Emit a payload to all listeners registered for an event.\n *\n * The `target` property is set to this emitter. For `error` events with no\n * listeners, throws the emitted `error` property.\n */\n emit<EventName extends keyof Payloads>(\n event: EventName,\n payload: Payloads[EventName],\n ): void {\n const listeners = this.#getListeners(event);\n\n if (!listeners || listeners.size === 0) {\n if (event === \"error\" && \"error\" in payload) {\n throw payload[\"error\"];\n }\n\n return;\n }\n\n const eventPayload: Emitter.EventPayload<Payloads, EventName> = {\n ...payload,\n target: this,\n };\n\n for (const listener of listeners) {\n listener(eventPayload);\n }\n }\n\n #createUnsubscribe(remove: () => void): Emitter.Unsubscribe {\n const unsubscribe = () => {\n remove();\n };\n unsubscribe[Symbol.dispose] = unsubscribe;\n return unsubscribe;\n }\n\n #getListeners<EventName extends keyof Payloads>(\n event: EventName,\n ):\n | Set<(payload: Emitter.EventPayload<Payloads, EventName>) => void>\n | undefined {\n return this.#listeners.get(event);\n }\n\n #getOrCreateListeners<EventName extends keyof Payloads>(\n event: EventName,\n ): Set<(payload: Emitter.EventPayload<Payloads, EventName>) => void> {\n const listeners = this.#listeners.get(event);\n\n if (listeners === undefined) {\n const listeners = new Set<\n (payload: Emitter.EventPayload<Payloads, keyof Payloads>) => void\n >();\n this.#listeners.set(event, listeners);\n return listeners;\n }\n\n return listeners;\n }\n}\n\ntype ErrorEventPayload = Record<string, unknown> & { error: Error };\n\nexport namespace Emitter {\n export type EventPayload<\n GivenEvents extends Payloads = Payloads,\n EventName extends keyof GivenEvents = keyof GivenEvents,\n > = { target: Emitter<GivenEvents> } & GivenEvents[EventName];\n\n export type Payloads = Record<string, Record<string, unknown>> & {\n error?: ErrorEventPayload;\n };\n\n export type Unsubscribe = (() => void) & Disposable;\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions\n export interface Events<GivenPayloads extends Payloads = Payloads> {\n readonly events: {\n readonly on: Emitter<GivenPayloads>[\"on\"];\n readonly once: Emitter<GivenPayloads>[\"once\"];\n };\n }\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const createPrototype: <EnumerableProperties extends Record<string, unknown>, NonEnumerableProperties extends Record<string, unknown>>(enumerableProperties: EnumerableProperties, nonEnumerableProperties: NonEnumerableProperties) => EnumerableProperties & NonEnumerableProperties;
|
|
2
|
+
//# sourceMappingURL=create-prototype.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-prototype.d.ts","sourceRoot":"","sources":["../../src/lib/create-prototype.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,GAC1B,oBAAoB,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpD,uBAAuB,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAEvD,sBAAsB,oBAAoB,EAC1C,yBAAyB,uBAAuB,KAc3C,oBAAoB,GAAG,uBAC7B,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const createPrototype = (enumerableProperties, nonEnumerableProperties) => {
|
|
2
|
+
return Object.create(Object.prototype, Object.fromEntries([
|
|
3
|
+
...Object.entries(enumerableProperties).map(([key, value]) => [
|
|
4
|
+
key,
|
|
5
|
+
{ value, enumerable: true },
|
|
6
|
+
]),
|
|
7
|
+
...Object.entries(nonEnumerableProperties).map(([key, value]) => [
|
|
8
|
+
key,
|
|
9
|
+
{ value, enumerable: false },
|
|
10
|
+
]),
|
|
11
|
+
]));
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=create-prototype.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-prototype.js","sourceRoot":"","sources":["../../src/lib/create-prototype.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,CAI7B,oBAA0C,EAC1C,uBAAgD,EAChD,EAAE;IACF,OAAO,MAAM,CAAC,MAAM,CAClB,MAAM,CAAC,SAAS,EAChB,MAAM,CAAC,WAAW,CAAC;QACjB,GAAG,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;YAC5D,GAAG;YACH,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;SAC5B,CAAC;QACF,GAAG,MAAM,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;YAC/D,GAAG;YACH,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE;SAC7B,CAAC;KACH,CAA2D,CACX,CAAC;AACtD,CAAC,CAAC","sourcesContent":["export const createPrototype = <\n EnumerableProperties extends Record<string, unknown>,\n NonEnumerableProperties extends Record<string, unknown>,\n>(\n enumerableProperties: EnumerableProperties,\n nonEnumerableProperties: NonEnumerableProperties,\n) => {\n return Object.create(\n Object.prototype,\n Object.fromEntries([\n ...Object.entries(enumerableProperties).map(([key, value]) => [\n key,\n { value, enumerable: true },\n ]),\n ...Object.entries(nonEnumerableProperties).map(([key, value]) => [\n key,\n { value, enumerable: false },\n ]),\n ]) as Record<keyof EnumerableProperties, PropertyDescriptor>,\n ) as EnumerableProperties & NonEnumerableProperties;\n};\n"]}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents some async data that could be in the following states:
|
|
3
|
+
*
|
|
4
|
+
* - initial (not implemented, see below)
|
|
5
|
+
* - pending
|
|
6
|
+
* - success
|
|
7
|
+
* - error
|
|
8
|
+
*
|
|
9
|
+
* This type is inspired by https://rametta.org/posts/elm-remote-data/ and has been
|
|
10
|
+
* designed to be aligned with tanstack query's useQuery() result.
|
|
11
|
+
* For instance, it can be used by a React component that wants to handle asynchronous
|
|
12
|
+
* data but does not want to call useQuery directly.
|
|
13
|
+
*
|
|
14
|
+
* The initial state has not been implemented because it's not compatible
|
|
15
|
+
* with tanstack query. If the initial state is needed,
|
|
16
|
+
* it can be represented as Result | undefined
|
|
17
|
+
*/
|
|
18
|
+
export type Result<Data = unknown, GivenError extends GenericError = GenericError> = Result.Pending<Data | undefined> | Result.Success<Data> | Result.Error<GivenError, Data | undefined>;
|
|
19
|
+
export declare const Result: {
|
|
20
|
+
readonly Status: {
|
|
21
|
+
readonly Pending: "pending";
|
|
22
|
+
readonly Success: "success";
|
|
23
|
+
readonly Error: "error";
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export declare namespace Result {
|
|
27
|
+
namespace Status {
|
|
28
|
+
type Pending = typeof Result.Status.Pending;
|
|
29
|
+
type Success = typeof Result.Status.Success;
|
|
30
|
+
type Error = typeof Result.Status.Error;
|
|
31
|
+
}
|
|
32
|
+
type Status = Status.Pending | Status.Success | Status.Error;
|
|
33
|
+
/**
|
|
34
|
+
* Represents a pending result where data is being loaded.
|
|
35
|
+
* Pending might have stale data, but there's new data inflight.
|
|
36
|
+
*/
|
|
37
|
+
type Pending<Data = undefined> = Readonly<{
|
|
38
|
+
status: Status.Pending;
|
|
39
|
+
/** Is true when there's data */
|
|
40
|
+
isSuccess: false;
|
|
41
|
+
/** Is true when there's an error */
|
|
42
|
+
isError: false;
|
|
43
|
+
/** Is true when there's no result yet */
|
|
44
|
+
isPending: true;
|
|
45
|
+
/** Potentially stale data from a previous result */
|
|
46
|
+
data: Data;
|
|
47
|
+
error: null;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Represents a successful result that holds some data.
|
|
51
|
+
*/
|
|
52
|
+
type Success<Data = unknown> = Readonly<{
|
|
53
|
+
status: Status.Success;
|
|
54
|
+
/** Is true when there's data */
|
|
55
|
+
isSuccess: true;
|
|
56
|
+
/** Is true when there's an error */
|
|
57
|
+
isError: false;
|
|
58
|
+
/** Is true when there's no result yet */
|
|
59
|
+
isPending: false;
|
|
60
|
+
data: Data;
|
|
61
|
+
error: null;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Represents a failed result that holds an error.
|
|
65
|
+
* Might also contain stale data from a previous result.
|
|
66
|
+
*/
|
|
67
|
+
type Error<GivenError extends GenericError = GenericError, Data = undefined> = Readonly<{
|
|
68
|
+
status: Status.Error;
|
|
69
|
+
/** Is true when there's data */
|
|
70
|
+
isSuccess: false;
|
|
71
|
+
/** Is true when there's an error */
|
|
72
|
+
isError: true;
|
|
73
|
+
/** Is true when there's no result yet */
|
|
74
|
+
isPending: false;
|
|
75
|
+
/** Potentially stale data from a previous result */
|
|
76
|
+
data: Data;
|
|
77
|
+
/** The error that occurred */
|
|
78
|
+
error: GivenError;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* Represents a synchronous result that can be either success or error (no pending state).
|
|
82
|
+
*/
|
|
83
|
+
type Sync<Data = unknown, GivenError extends GenericError = GenericError> = Success<Data> | Error<GivenError, Data>;
|
|
84
|
+
}
|
|
85
|
+
declare const getMetadata: <Data>(result: Result<Data>) => ResultMetadata;
|
|
86
|
+
/**
|
|
87
|
+
* Creates a result in the pending state.
|
|
88
|
+
*
|
|
89
|
+
* @param options.data - Potentially stale data from a previous result
|
|
90
|
+
* @param options.createdAt - The date when the result was created
|
|
91
|
+
* @returns The pending result
|
|
92
|
+
*/
|
|
93
|
+
declare const pending: <const Data = undefined>({ data, createdAt, }?: Pick<Partial<Result.Pending<Data>>, "data"> & Partial<ResultMetadata>) => Result.Pending<Data>;
|
|
94
|
+
/**
|
|
95
|
+
* Creates a result in the success state.
|
|
96
|
+
*
|
|
97
|
+
* @param options.data - The data to store in the result
|
|
98
|
+
* @param options.createdAt - The date when the result was created
|
|
99
|
+
* @returns The successful result
|
|
100
|
+
*/
|
|
101
|
+
declare const success: <const Data>({ data, createdAt, }: Pick<Result.Success<Data>, "data"> & Partial<ResultMetadata>) => Result.Success<Data>;
|
|
102
|
+
/**
|
|
103
|
+
* Creates a result in the error state.
|
|
104
|
+
*
|
|
105
|
+
* @param options.error - The error to store in the result
|
|
106
|
+
* @param options.data - Potentially stale data from a previous result
|
|
107
|
+
* @returns The failed result
|
|
108
|
+
*/
|
|
109
|
+
declare const error: <const GivenError extends Error, const Data = never>({ error: givenError, data, createdAt, }: Pick<Result.Error<GivenError, Data>, "error"> & Pick<Partial<Result.Error<GivenError, Data>>, "data"> & Partial<ResultMetadata>) => Result.Error<GivenError, Data>;
|
|
110
|
+
/**
|
|
111
|
+
* Calls the function and returns it as result.
|
|
112
|
+
* If the function throws an Error, an error result is returned.
|
|
113
|
+
* If the function throws anything else, it is rethrown.
|
|
114
|
+
*
|
|
115
|
+
* @param fn - The function to call
|
|
116
|
+
* @returns The result of the function
|
|
117
|
+
*/
|
|
118
|
+
declare const from: <Data>(fn: () => Data) => Result.Sync<Data>;
|
|
119
|
+
/**
|
|
120
|
+
* Calls and awaits the async function and returns it as result.
|
|
121
|
+
* If the function rejects the promise, an error result is returned.
|
|
122
|
+
* If the function rejects with anything else, the rejection is rethrown.
|
|
123
|
+
*
|
|
124
|
+
* @param fn - The async function to call and await
|
|
125
|
+
* @returns The result of the async function
|
|
126
|
+
*/
|
|
127
|
+
declare const fromAsync: <Data>(fn: () => Promise<Data>) => Promise<Result.Sync<Data>>;
|
|
128
|
+
type CaseHandlers<GivenResult extends Result | null | undefined, ReturnType = unknown> = {
|
|
129
|
+
pending?: ((maybeData: Extract<GivenResult, {
|
|
130
|
+
status: Result.Status.Pending;
|
|
131
|
+
}>["data"]) => ReturnType) | ReturnType;
|
|
132
|
+
success?: ((data: Extract<GivenResult, {
|
|
133
|
+
status: Result.Status.Success;
|
|
134
|
+
}>["data"]) => ReturnType) | ReturnType;
|
|
135
|
+
error?: ((error: Extract<GivenResult, {
|
|
136
|
+
status: Result.Status.Error;
|
|
137
|
+
}>["error"]) => ReturnType) | ReturnType;
|
|
138
|
+
else: ((result: GivenResult) => ReturnType) | ReturnType;
|
|
139
|
+
};
|
|
140
|
+
type ResultWrapper<GivenResult extends Result | null | undefined> = {
|
|
141
|
+
case: <ReturnType>(handlers: CaseHandlers<GivenResult, ReturnType>) => ReturnType;
|
|
142
|
+
};
|
|
143
|
+
type ResultFn = {
|
|
144
|
+
<GivenResult extends Result | null | undefined>(givenResult: GivenResult): ResultWrapper<GivenResult>;
|
|
145
|
+
from: typeof from;
|
|
146
|
+
fromAsync: typeof fromAsync;
|
|
147
|
+
pending: typeof pending;
|
|
148
|
+
success: typeof success;
|
|
149
|
+
error: typeof error;
|
|
150
|
+
metadata: typeof getMetadata;
|
|
151
|
+
};
|
|
152
|
+
export declare const result: ResultFn;
|
|
153
|
+
export { isResult } from "./result.lib.ts";
|
|
154
|
+
type GenericError = Error;
|
|
155
|
+
type ResultMetadata = {
|
|
156
|
+
/** The date when the result was created */
|
|
157
|
+
createdAt: Date;
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/result/result.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { createPrototype } from "../lib/create-prototype.js";
|
|
2
|
+
import { stringify } from "../lib/string.js";
|
|
3
|
+
import { symbolOfResult } from "./result.lib.js";
|
|
4
|
+
export const Result = {
|
|
5
|
+
Status: {
|
|
6
|
+
Pending: "pending",
|
|
7
|
+
Success: "success",
|
|
8
|
+
Error: "error",
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
const setMetadata = (result, metadata) => {
|
|
12
|
+
Object.defineProperty(result, symbolOfResult, {
|
|
13
|
+
value: metadata,
|
|
14
|
+
enumerable: false,
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
const getMetadata = (result) => {
|
|
18
|
+
return result[symbolOfResult];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Creates a result in the pending state.
|
|
22
|
+
*
|
|
23
|
+
* @param options.data - Potentially stale data from a previous result
|
|
24
|
+
* @param options.createdAt - The date when the result was created
|
|
25
|
+
* @returns The pending result
|
|
26
|
+
*/
|
|
27
|
+
const pending = ({ data, createdAt = new Date(), } = {}) => {
|
|
28
|
+
const pendingResult = Object.create(pendingPrototype, {
|
|
29
|
+
data: { value: data, enumerable: true },
|
|
30
|
+
});
|
|
31
|
+
setMetadata(pendingResult, { createdAt });
|
|
32
|
+
return pendingResult;
|
|
33
|
+
};
|
|
34
|
+
const pendingPrototype = createPrototype({
|
|
35
|
+
status: Result.Status.Pending,
|
|
36
|
+
isSuccess: false,
|
|
37
|
+
isError: false,
|
|
38
|
+
isPending: true,
|
|
39
|
+
data: undefined,
|
|
40
|
+
error: null,
|
|
41
|
+
}, {
|
|
42
|
+
toString() {
|
|
43
|
+
return `Result.Pending(${this.data === undefined ? "" : stringify(this.data)})`;
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Creates a result in the success state.
|
|
48
|
+
*
|
|
49
|
+
* @param options.data - The data to store in the result
|
|
50
|
+
* @param options.createdAt - The date when the result was created
|
|
51
|
+
* @returns The successful result
|
|
52
|
+
*/
|
|
53
|
+
const success = ({ data, createdAt = new Date(), }) => {
|
|
54
|
+
const successResult = Object.create(successPrototype, {
|
|
55
|
+
data: { value: data, enumerable: true },
|
|
56
|
+
});
|
|
57
|
+
setMetadata(successResult, { createdAt });
|
|
58
|
+
return successResult;
|
|
59
|
+
};
|
|
60
|
+
const successPrototype = createPrototype({
|
|
61
|
+
status: Result.Status.Success,
|
|
62
|
+
isSuccess: true,
|
|
63
|
+
isError: false,
|
|
64
|
+
isPending: false,
|
|
65
|
+
data: undefined,
|
|
66
|
+
error: null,
|
|
67
|
+
}, {
|
|
68
|
+
toString() {
|
|
69
|
+
return `Result.Success(${stringify(this.data)})`;
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Creates a result in the error state.
|
|
74
|
+
*
|
|
75
|
+
* @param options.error - The error to store in the result
|
|
76
|
+
* @param options.data - Potentially stale data from a previous result
|
|
77
|
+
* @returns The failed result
|
|
78
|
+
*/
|
|
79
|
+
const error = ({ error: givenError, data, createdAt = new Date(), }) => {
|
|
80
|
+
const errorResult = Object.create(errorPrototype, {
|
|
81
|
+
data: { value: data, enumerable: true },
|
|
82
|
+
error: { value: givenError, enumerable: true },
|
|
83
|
+
});
|
|
84
|
+
setMetadata(errorResult, { createdAt });
|
|
85
|
+
return errorResult;
|
|
86
|
+
};
|
|
87
|
+
const errorPrototype = createPrototype({
|
|
88
|
+
status: Result.Status.Error,
|
|
89
|
+
isSuccess: false,
|
|
90
|
+
isError: true,
|
|
91
|
+
isPending: false,
|
|
92
|
+
data: undefined,
|
|
93
|
+
error: new Error("Default error"),
|
|
94
|
+
}, {
|
|
95
|
+
toString() {
|
|
96
|
+
return `Result.Error(${stringify(this.error.message)})`;
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* Calls the function and returns it as result.
|
|
101
|
+
* If the function throws an Error, an error result is returned.
|
|
102
|
+
* If the function throws anything else, it is rethrown.
|
|
103
|
+
*
|
|
104
|
+
* @param fn - The function to call
|
|
105
|
+
* @returns The result of the function
|
|
106
|
+
*/
|
|
107
|
+
const from = (fn) => {
|
|
108
|
+
try {
|
|
109
|
+
return success({ data: fn() });
|
|
110
|
+
}
|
|
111
|
+
catch (caughtError) {
|
|
112
|
+
if (isError(caughtError)) {
|
|
113
|
+
return error({ error: caughtError });
|
|
114
|
+
}
|
|
115
|
+
throw caughtError;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Calls and awaits the async function and returns it as result.
|
|
120
|
+
* If the function rejects the promise, an error result is returned.
|
|
121
|
+
* If the function rejects with anything else, the rejection is rethrown.
|
|
122
|
+
*
|
|
123
|
+
* @param fn - The async function to call and await
|
|
124
|
+
* @returns The result of the async function
|
|
125
|
+
*/
|
|
126
|
+
const fromAsync = async (fn) => {
|
|
127
|
+
try {
|
|
128
|
+
return success({ data: await fn() });
|
|
129
|
+
}
|
|
130
|
+
catch (caughtError) {
|
|
131
|
+
if (isError(caughtError)) {
|
|
132
|
+
return error({ error: caughtError });
|
|
133
|
+
}
|
|
134
|
+
throw caughtError;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Checks if the given value is an Error. Doesn't use instanceof so that
|
|
139
|
+
* DOMException and errors from a different realm can be checked as well.
|
|
140
|
+
*/
|
|
141
|
+
const isError = (error) => {
|
|
142
|
+
if ("isError" in Error && typeof Error.isError === "function") {
|
|
143
|
+
const result = Error.isError(error);
|
|
144
|
+
if (typeof result === "boolean") {
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return (typeof error === "object" &&
|
|
149
|
+
error !== null &&
|
|
150
|
+
"name" in error &&
|
|
151
|
+
typeof error.name === "string" &&
|
|
152
|
+
"message" in error &&
|
|
153
|
+
typeof error.message === "string" &&
|
|
154
|
+
"stack" in error &&
|
|
155
|
+
typeof error.stack === "string");
|
|
156
|
+
};
|
|
157
|
+
export const result = Object.assign((givenResult) => {
|
|
158
|
+
return {
|
|
159
|
+
case: (handlers) => {
|
|
160
|
+
if (givenResult?.status === Result.Status.Pending &&
|
|
161
|
+
"pending" in handlers) {
|
|
162
|
+
return typeof handlers.pending === "function"
|
|
163
|
+
? handlers.pending(givenResult.data)
|
|
164
|
+
: handlers.pending;
|
|
165
|
+
}
|
|
166
|
+
if (givenResult?.status === Result.Status.Success &&
|
|
167
|
+
"success" in handlers) {
|
|
168
|
+
return typeof handlers.success === "function"
|
|
169
|
+
? handlers.success(givenResult.data)
|
|
170
|
+
: handlers.success;
|
|
171
|
+
}
|
|
172
|
+
if (givenResult?.status === Result.Status.Error &&
|
|
173
|
+
"error" in handlers) {
|
|
174
|
+
return typeof handlers.error === "function"
|
|
175
|
+
? handlers.error(givenResult.error)
|
|
176
|
+
: handlers.error;
|
|
177
|
+
}
|
|
178
|
+
return typeof handlers.else === "function"
|
|
179
|
+
? handlers.else(givenResult)
|
|
180
|
+
: handlers.else;
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}, {
|
|
184
|
+
from,
|
|
185
|
+
fromAsync,
|
|
186
|
+
pending,
|
|
187
|
+
success,
|
|
188
|
+
error,
|
|
189
|
+
metadata: getMetadata,
|
|
190
|
+
});
|
|
191
|
+
export { isResult } from "./result.lib.js";
|
|
192
|
+
//# sourceMappingURL=result.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/result/result.ts"],"names":[],"mappings":"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","sourcesContent":["import { createPrototype } from \"../lib/create-prototype.ts\";\nimport { stringify } from \"../lib/string.ts\";\nimport { symbolOfResult } from \"./result.lib.ts\";\n\n// Namespaces are only used to group related types together.\n/* eslint-disable @typescript-eslint/no-namespace */\n\n// This module uses prototypes to create objects for the Result.Pending, Result.Success and Result.Error types.\n// This way unimportant properties won't show up in the debugger and we keep the memory footprint low.\n\n/**\n * Represents some async data that could be in the following states:\n *\n * - initial (not implemented, see below)\n * - pending\n * - success\n * - error\n *\n * This type is inspired by https://rametta.org/posts/elm-remote-data/ and has been\n * designed to be aligned with tanstack query's useQuery() result.\n * For instance, it can be used by a React component that wants to handle asynchronous\n * data but does not want to call useQuery directly.\n *\n * The initial state has not been implemented because it's not compatible\n * with tanstack query. If the initial state is needed,\n * it can be represented as Result | undefined\n */\nexport type Result<\n Data = unknown,\n GivenError extends GenericError = GenericError,\n> =\n | Result.Pending<Data | undefined>\n | Result.Success<Data>\n | Result.Error<GivenError, Data | undefined>;\n\nexport const Result = {\n Status: {\n Pending: \"pending\",\n Success: \"success\",\n Error: \"error\",\n },\n} as const;\n\nexport namespace Result {\n export namespace Status {\n export type Pending = typeof Result.Status.Pending;\n export type Success = typeof Result.Status.Success;\n export type Error = typeof Result.Status.Error;\n }\n\n export type Status = Status.Pending | Status.Success | Status.Error;\n\n /**\n * Represents a pending result where data is being loaded.\n * Pending might have stale data, but there's new data inflight.\n */\n export type Pending<Data = undefined> = Readonly<{\n status: Status.Pending;\n /** Is true when there's data */\n isSuccess: false;\n /** Is true when there's an error */\n isError: false;\n /** Is true when there's no result yet */\n isPending: true;\n /** Potentially stale data from a previous result */\n data: Data;\n error: null;\n }>;\n\n /**\n * Represents a successful result that holds some data.\n */\n export type Success<Data = unknown> = Readonly<{\n status: Status.Success;\n /** Is true when there's data */\n isSuccess: true;\n /** Is true when there's an error */\n isError: false;\n /** Is true when there's no result yet */\n isPending: false;\n data: Data;\n error: null;\n }>;\n\n /**\n * Represents a failed result that holds an error.\n * Might also contain stale data from a previous result.\n */\n export type Error<\n GivenError extends GenericError = GenericError,\n Data = undefined,\n > = Readonly<{\n status: Status.Error;\n /** Is true when there's data */\n isSuccess: false;\n /** Is true when there's an error */\n isError: true;\n /** Is true when there's no result yet */\n isPending: false;\n /** Potentially stale data from a previous result */\n data: Data;\n /** The error that occurred */\n error: GivenError;\n }>;\n\n /**\n * Represents a synchronous result that can be either success or error (no pending state).\n */\n export type Sync<\n Data = unknown,\n GivenError extends GenericError = GenericError,\n > = Success<Data> | Error<GivenError, Data>;\n}\n\nconst setMetadata = <Data>(result: Result<Data>, metadata: ResultMetadata) => {\n Object.defineProperty(result, symbolOfResult, {\n value: metadata,\n enumerable: false,\n });\n};\n\nconst getMetadata = <Data>(result: Result<Data>): ResultMetadata => {\n return (result as { [symbolOfResult]?: ResultMetadata })[symbolOfResult]!;\n};\n\n/**\n * Creates a result in the pending state.\n *\n * @param options.data - Potentially stale data from a previous result\n * @param options.createdAt - The date when the result was created\n * @returns The pending result\n */\nconst pending = <const Data = undefined>({\n data,\n createdAt = new Date(),\n}: Pick<Partial<Result.Pending<Data>>, \"data\"> &\n Partial<ResultMetadata> = {}): Result.Pending<Data> => {\n const pendingResult: Result.Pending<Data> = Object.create(pendingPrototype, {\n data: { value: data, enumerable: true },\n });\n\n setMetadata(pendingResult, { createdAt });\n\n return pendingResult;\n};\n\nconst pendingPrototype: Result.Pending = createPrototype(\n {\n status: Result.Status.Pending,\n isSuccess: false,\n isError: false,\n isPending: true,\n data: undefined,\n error: null,\n } as const,\n {\n toString(this: Result.Pending<unknown>) {\n return `Result.Pending(${\n this.data === undefined ? \"\" : stringify(this.data)\n })`;\n },\n },\n);\n\n/**\n * Creates a result in the success state.\n *\n * @param options.data - The data to store in the result\n * @param options.createdAt - The date when the result was created\n * @returns The successful result\n */\nconst success = <const Data>({\n data,\n createdAt = new Date(),\n}: Pick<Result.Success<Data>, \"data\"> &\n Partial<ResultMetadata>): Result.Success<Data> => {\n const successResult: Result.Success<Data> = Object.create(successPrototype, {\n data: { value: data, enumerable: true },\n });\n\n setMetadata(successResult, { createdAt });\n\n return successResult;\n};\n\nconst successPrototype: Result.Success<undefined> = createPrototype(\n {\n status: Result.Status.Success,\n isSuccess: true,\n isError: false,\n isPending: false,\n data: undefined,\n error: null,\n } as const,\n {\n toString(this: Result.Success) {\n return `Result.Success(${stringify(this.data)})`;\n },\n },\n);\n\n/**\n * Creates a result in the error state.\n *\n * @param options.error - The error to store in the result\n * @param options.data - Potentially stale data from a previous result\n * @returns The failed result\n */\nconst error = <const GivenError extends Error, const Data = never>({\n error: givenError,\n data,\n createdAt = new Date(),\n}: Pick<Result.Error<GivenError, Data>, \"error\"> &\n Pick<Partial<Result.Error<GivenError, Data>>, \"data\"> &\n Partial<ResultMetadata>): Result.Error<GivenError, Data> => {\n const errorResult: Result.Error<GivenError, Data> = Object.create(\n errorPrototype,\n {\n data: { value: data, enumerable: true },\n error: { value: givenError, enumerable: true },\n },\n );\n\n setMetadata(errorResult, { createdAt });\n\n return errorResult;\n};\n\nconst errorPrototype: Result.Error = createPrototype(\n {\n status: Result.Status.Error,\n isSuccess: false,\n isError: true,\n isPending: false,\n data: undefined,\n error: new Error(\"Default error\"),\n } as const,\n {\n toString(this: Result.Error) {\n return `Result.Error(${stringify(this.error.message)})`;\n },\n },\n);\n\n/**\n * Calls the function and returns it as result.\n * If the function throws an Error, an error result is returned.\n * If the function throws anything else, it is rethrown.\n *\n * @param fn - The function to call\n * @returns The result of the function\n */\nconst from = <Data>(fn: () => Data): Result.Sync<Data> => {\n try {\n return success({ data: fn() });\n } catch (caughtError) {\n if (isError(caughtError)) {\n return error<GenericError>({ error: caughtError });\n }\n\n throw caughtError;\n }\n};\n\n/**\n * Calls and awaits the async function and returns it as result.\n * If the function rejects the promise, an error result is returned.\n * If the function rejects with anything else, the rejection is rethrown.\n *\n * @param fn - The async function to call and await\n * @returns The result of the async function\n */\nconst fromAsync = async <Data>(\n fn: () => Promise<Data>,\n): Promise<Result.Sync<Data>> => {\n try {\n return success({ data: await fn() });\n } catch (caughtError) {\n if (isError(caughtError)) {\n return error<GenericError>({ error: caughtError });\n }\n\n throw caughtError;\n }\n};\n\n/**\n * Checks if the given value is an Error. Doesn't use instanceof so that\n * DOMException and errors from a different realm can be checked as well.\n */\nconst isError = (error: unknown): error is GenericError => {\n if (\"isError\" in Error && typeof Error.isError === \"function\") {\n const result: unknown = Error.isError(error);\n\n if (typeof result === \"boolean\") {\n return result;\n }\n }\n\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"name\" in error &&\n typeof error.name === \"string\" &&\n \"message\" in error &&\n typeof error.message === \"string\" &&\n \"stack\" in error &&\n typeof error.stack === \"string\"\n );\n};\n\ntype CaseHandlers<\n GivenResult extends Result | null | undefined,\n ReturnType = unknown,\n> = {\n pending?:\n | ((\n maybeData: Extract<\n GivenResult,\n { status: Result.Status.Pending }\n >[\"data\"],\n ) => ReturnType)\n | ReturnType;\n success?:\n | ((\n data: Extract<GivenResult, { status: Result.Status.Success }>[\"data\"],\n ) => ReturnType)\n | ReturnType;\n error?:\n | ((\n error: Extract<GivenResult, { status: Result.Status.Error }>[\"error\"],\n ) => ReturnType)\n | ReturnType;\n else: ((result: GivenResult) => ReturnType) | ReturnType;\n};\n\ntype ResultWrapper<GivenResult extends Result | null | undefined> = {\n case: <ReturnType>(\n handlers: CaseHandlers<GivenResult, ReturnType>,\n ) => ReturnType;\n};\n\ntype ResultFn = {\n <GivenResult extends Result | null | undefined>(\n givenResult: GivenResult,\n ): ResultWrapper<GivenResult>;\n from: typeof from;\n fromAsync: typeof fromAsync;\n pending: typeof pending;\n success: typeof success;\n error: typeof error;\n metadata: typeof getMetadata;\n};\n\nexport const result: ResultFn = Object.assign(\n <GivenResult extends Result | null | undefined>(\n givenResult: GivenResult,\n ): ResultWrapper<GivenResult> => {\n return {\n case: <ReturnType>(\n handlers: CaseHandlers<GivenResult, ReturnType>,\n ): ReturnType => {\n if (\n givenResult?.status === Result.Status.Pending &&\n \"pending\" in handlers\n ) {\n return typeof handlers.pending === \"function\"\n ? (\n handlers.pending as Extract<\n typeof handlers.pending,\n (maybeData: unknown) => ReturnType\n >\n )(givenResult.data)\n : (handlers.pending as ReturnType);\n }\n if (\n givenResult?.status === Result.Status.Success &&\n \"success\" in handlers\n ) {\n return typeof handlers.success === \"function\"\n ? (\n handlers.success as Extract<\n typeof handlers.success,\n (data: unknown) => ReturnType\n >\n )(givenResult.data)\n : (handlers.success as ReturnType);\n }\n if (\n givenResult?.status === Result.Status.Error &&\n \"error\" in handlers\n ) {\n return typeof handlers.error === \"function\"\n ? (\n handlers.error as Extract<\n typeof handlers.error,\n (error: unknown) => ReturnType\n >\n )(givenResult.error)\n : (handlers.error as ReturnType);\n }\n return typeof handlers.else === \"function\"\n ? (\n handlers.else as Extract<\n typeof handlers.else,\n (result: unknown) => ReturnType\n >\n )(givenResult)\n : handlers.else;\n },\n };\n },\n {\n from,\n fromAsync,\n pending,\n success,\n error,\n metadata: getMetadata,\n },\n);\n\nexport { isResult } from \"./result.lib.ts\";\n\ntype GenericError = Error;\n\ntype ResultMetadata = {\n /** The date when the result was created */\n createdAt: Date;\n};\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Result } from "./result.ts";
|
|
2
|
+
export declare const symbolOfResult: unique symbol;
|
|
3
|
+
/**
|
|
4
|
+
* Checks if the given value is a result.
|
|
5
|
+
*
|
|
6
|
+
* @param maybeValue - The value to check
|
|
7
|
+
* @returns True if the value is a result, false otherwise
|
|
8
|
+
*/
|
|
9
|
+
export declare const isResult: (maybeValue: unknown) => maybeValue is Result;
|
|
10
|
+
//# sourceMappingURL=result.lib.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result.lib.d.ts","sourceRoot":"","sources":["../../src/result/result.lib.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,eAAO,MAAM,cAAc,eAAmB,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,YAAY,OAAO,KAAG,UAAU,IAAI,MAM5D,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const symbolOfResult = Symbol("Result");
|
|
2
|
+
/**
|
|
3
|
+
* Checks if the given value is a result.
|
|
4
|
+
*
|
|
5
|
+
* @param maybeValue - The value to check
|
|
6
|
+
* @returns True if the value is a result, false otherwise
|
|
7
|
+
*/
|
|
8
|
+
export const isResult = (maybeValue) => {
|
|
9
|
+
return (maybeValue !== null &&
|
|
10
|
+
typeof maybeValue === "object" &&
|
|
11
|
+
symbolOfResult in maybeValue);
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=result.lib.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result.lib.js","sourceRoot":"","sources":["../../src/result/result.lib.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,UAAmB,EAAwB,EAAE;IACpE,OAAO,CACL,UAAU,KAAK,IAAI;QACnB,OAAO,UAAU,KAAK,QAAQ;QAC9B,cAAc,IAAI,UAAU,CAC7B,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import type { Result } from \"./result.ts\";\n\nexport const symbolOfResult = Symbol(\"Result\");\n\n/**\n * Checks if the given value is a result.\n *\n * @param maybeValue - The value to check\n * @returns True if the value is a result, false otherwise\n */\nexport const isResult = (maybeValue: unknown): maybeValue is Result => {\n return (\n maybeValue !== null &&\n typeof maybeValue === \"object\" &&\n symbolOfResult in maybeValue\n );\n};\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ReadSignal<Value> = {
|
|
2
|
+
(): Value;
|
|
3
|
+
subscribe: (reader: SignalReader<Value>) => () => void;
|
|
4
|
+
};
|
|
5
|
+
export type WriteSignal<Value> = (value: Value) => void;
|
|
6
|
+
export type Signal<Value> = Disposable & {
|
|
7
|
+
read: ReadSignal<Value>;
|
|
8
|
+
write: WriteSignal<Value>;
|
|
9
|
+
subscribe: ReadSignal<Value>["subscribe"];
|
|
10
|
+
};
|
|
11
|
+
export declare const signal: {
|
|
12
|
+
<Value>(initialValue: Value): Signal<Value>;
|
|
13
|
+
from<Value>(readFromSource: () => Value, subscribeToSource: (notify: () => void) => () => void): Omit<Signal<Value>, "write">;
|
|
14
|
+
};
|
|
15
|
+
export type SignalReader<Value> = ({ value, previousValue, }: {
|
|
16
|
+
value: Value;
|
|
17
|
+
previousValue: Value;
|
|
18
|
+
}) => void;
|
|
19
|
+
//# sourceMappingURL=signals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../../src/signals/signals.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,CAAC,KAAK,IAAI;IAC9B,IAAI,KAAK,CAAC;IACV,SAAS,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,KAAK,MAAM,IAAI,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAExD,MAAM,MAAM,MAAM,CAAC,KAAK,IAAI,UAAU,GAAG;IACvC,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IACxB,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC;IAC1B,SAAS,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,CAAC;CAC3C,CAAC;AAEF,eAAO,MAAM,MAAM;KAAI,KAAK,gBAAgB,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;SA6BlD,KAAK,kBACF,MAAM,KAAK,qBACR,CAAC,MAAM,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,GACpD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAL9B,CAAC;AAoCF,MAAM,MAAM,YAAY,CAAC,KAAK,IAAI,CAAC,EACjC,KAAK,EACL,aAAa,GACd,EAAE;IACD,KAAK,EAAE,KAAK,CAAC;IACb,aAAa,EAAE,KAAK,CAAC;CACtB,KAAK,IAAI,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const signal = (initialValue) => {
|
|
2
|
+
const readers = new Set();
|
|
3
|
+
let value = initialValue;
|
|
4
|
+
const subscribe = (reader) => {
|
|
5
|
+
readers.add(reader);
|
|
6
|
+
return () => {
|
|
7
|
+
readers.delete(reader);
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
const read = () => value;
|
|
11
|
+
read.subscribe = subscribe;
|
|
12
|
+
const write = (newValue) => {
|
|
13
|
+
const previousValue = value;
|
|
14
|
+
value = newValue;
|
|
15
|
+
for (const reader of readers) {
|
|
16
|
+
reader({ value, previousValue });
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const dispose = () => {
|
|
20
|
+
readers.clear();
|
|
21
|
+
};
|
|
22
|
+
return { read, write, subscribe, [Symbol.dispose]: dispose };
|
|
23
|
+
};
|
|
24
|
+
signal.from = (readFromSource, subscribeToSource) => {
|
|
25
|
+
const derivedSignal = signal(readFromSource());
|
|
26
|
+
let readerCount = 0;
|
|
27
|
+
let unsubscribeFromSource;
|
|
28
|
+
const subscribe = (reader) => {
|
|
29
|
+
const unsubscribe = derivedSignal.subscribe(reader);
|
|
30
|
+
if (readerCount === 0) {
|
|
31
|
+
unsubscribeFromSource = subscribeToSource(() => {
|
|
32
|
+
derivedSignal.write(readFromSource());
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
readerCount++;
|
|
36
|
+
return () => {
|
|
37
|
+
unsubscribe();
|
|
38
|
+
readerCount--;
|
|
39
|
+
if (readerCount === 0) {
|
|
40
|
+
unsubscribeFromSource?.();
|
|
41
|
+
unsubscribeFromSource = undefined;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const dispose = () => {
|
|
46
|
+
unsubscribeFromSource?.();
|
|
47
|
+
derivedSignal[Symbol.dispose]();
|
|
48
|
+
};
|
|
49
|
+
return { read: derivedSignal.read, subscribe, [Symbol.dispose]: dispose };
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=signals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signals.js","sourceRoot":"","sources":["../../src/signals/signals.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,MAAM,GAAG,CAAQ,YAAmB,EAAiB,EAAE;IAClE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC/C,IAAI,KAAK,GAAG,YAAY,CAAC;IAEzB,MAAM,SAAS,GAAmC,CAAC,MAAM,EAAE,EAAE;QAC3D,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,IAAI,GAAsB,GAAG,EAAE,CAAC,KAAK,CAAC;IAC5C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAE3B,MAAM,KAAK,GAAuB,CAAC,QAAQ,EAAE,EAAE;QAC7C,MAAM,aAAa,GAAG,KAAK,CAAC;QAC5B,KAAK,GAAG,QAAQ,CAAC;QACjB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;QACnC,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,OAAO,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;AAC/D,CAAC,CAAC;AAEF,MAAM,CAAC,IAAI,GAAG,CACZ,cAA2B,EAC3B,iBAAqD,EACvB,EAAE;IAChC,MAAM,aAAa,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IAE/C,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,qBAA+C,CAAC;IACpD,MAAM,SAAS,GAAG,CAAC,MAA2B,EAAE,EAAE;QAChD,MAAM,WAAW,GAAG,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACpD,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;YACtB,qBAAqB,GAAG,iBAAiB,CAAC,GAAG,EAAE;gBAC7C,aAAa,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC;YACxC,CAAC,CAAC,CAAC;QACL,CAAC;QACD,WAAW,EAAE,CAAC;QACd,OAAO,GAAG,EAAE;YACV,WAAW,EAAE,CAAC;YACd,WAAW,EAAE,CAAC;YACd,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;gBACtB,qBAAqB,EAAE,EAAE,CAAC;gBAC1B,qBAAqB,GAAG,SAAS,CAAC;YACpC,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,qBAAqB,EAAE,EAAE,CAAC;QAC1B,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;IAClC,CAAC,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;AAC5E,CAAC,CAAC","sourcesContent":["export type ReadSignal<Value> = {\n (): Value;\n subscribe: (reader: SignalReader<Value>) => () => void;\n};\n\nexport type WriteSignal<Value> = (value: Value) => void;\n\nexport type Signal<Value> = Disposable & {\n read: ReadSignal<Value>;\n write: WriteSignal<Value>;\n subscribe: ReadSignal<Value>[\"subscribe\"];\n};\n\nexport const signal = <Value>(initialValue: Value): Signal<Value> => {\n const readers = new Set<SignalReader<Value>>();\n let value = initialValue;\n\n const subscribe: ReadSignal<Value>[\"subscribe\"] = (reader) => {\n readers.add(reader);\n return () => {\n readers.delete(reader);\n };\n };\n\n const read: ReadSignal<Value> = () => value;\n read.subscribe = subscribe;\n\n const write: WriteSignal<Value> = (newValue) => {\n const previousValue = value;\n value = newValue;\n for (const reader of readers) {\n reader({ value, previousValue });\n }\n };\n\n const dispose = () => {\n readers.clear();\n };\n\n return { read, write, subscribe, [Symbol.dispose]: dispose };\n};\n\nsignal.from = <Value>(\n readFromSource: () => Value,\n subscribeToSource: (notify: () => void) => () => void,\n): Omit<Signal<Value>, \"write\"> => {\n const derivedSignal = signal(readFromSource());\n\n let readerCount = 0;\n let unsubscribeFromSource: (() => void) | undefined;\n const subscribe = (reader: SignalReader<Value>) => {\n const unsubscribe = derivedSignal.subscribe(reader);\n if (readerCount === 0) {\n unsubscribeFromSource = subscribeToSource(() => {\n derivedSignal.write(readFromSource());\n });\n }\n readerCount++;\n return () => {\n unsubscribe();\n readerCount--;\n if (readerCount === 0) {\n unsubscribeFromSource?.();\n unsubscribeFromSource = undefined;\n }\n };\n };\n\n const dispose = () => {\n unsubscribeFromSource?.();\n derivedSignal[Symbol.dispose]();\n };\n\n return { read: derivedSignal.read, subscribe, [Symbol.dispose]: dispose };\n};\n\nexport type SignalReader<Value> = ({\n value,\n previousValue,\n}: {\n value: Value;\n previousValue: Value;\n}) => void;\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Result } from "../result/result.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Unwrap a value from Result or nullable types, returning the underlying value or throwing an error.
|
|
4
|
+
*
|
|
5
|
+
* This function safely extracts values from wrapped types like Result,
|
|
6
|
+
* handling null/undefined values and providing fallback mechanisms.
|
|
7
|
+
*
|
|
8
|
+
* @param maybeValue - The value to unwrap (can be a plain value or Result)
|
|
9
|
+
* @param fallback - Optional fallback value to return instead of throwing
|
|
10
|
+
* @returns The unwrapped value or fallback
|
|
11
|
+
* @throws {TypeError} When the value cannot be unwrapped and no fallback is provided
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // Basic unwrapping
|
|
16
|
+
* const value = unwrap("hello"); // "hello"
|
|
17
|
+
* const nullValue = unwrap(null, "default"); // "default"
|
|
18
|
+
*
|
|
19
|
+
* // With Result types
|
|
20
|
+
* const success = result.success({ data: "success" });
|
|
21
|
+
* const data = unwrap(success); // "success"
|
|
22
|
+
*
|
|
23
|
+
* const error = result.error({ error: new Error("failed") });
|
|
24
|
+
* const fallback = unwrap(error, "default"); // "default"
|
|
25
|
+
*
|
|
26
|
+
* // With Promise.allSettled results
|
|
27
|
+
* const settled = { status: "fulfilled", value: "done" } as const;
|
|
28
|
+
* unwrap(settled); // "done"
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function unwrap<Value, GivenError extends Error>(maybeValue: Value | Result<Value, GivenError> | PromiseFulfilledResult<Value>): Value;
|
|
32
|
+
export declare function unwrap<Value>(maybeValue: Result.Success<Value> | Result.Pending<Value>, fallback: unknown): Value;
|
|
33
|
+
export declare function unwrap<Value, GivenError extends Error, const Fallback>(maybeValue: Result.Error<GivenError, Value>, fallback: Fallback): Fallback;
|
|
34
|
+
export declare function unwrap<const Fallback>(maybeValue: PromiseRejectedResult, fallback: Fallback): Fallback;
|
|
35
|
+
export declare function unwrap<Value, GivenError extends Error, const Fallback>(maybeValue: Value | Result<Value, GivenError> | PromiseSettledResult<Value>, fallback: Fallback): NonNullable<Value> | Fallback;
|
|
36
|
+
//# sourceMappingURL=unwrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unwrap.d.ts","sourceRoot":"","sources":["../../src/unwrap/unwrap.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,SAAS,KAAK,EACpD,UAAU,EAAE,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,sBAAsB,CAAC,KAAK,CAAC,GAC5E,KAAK,CAAC;AACT,wBAAgB,MAAM,CAAC,KAAK,EAC1B,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EACzD,QAAQ,EAAE,OAAO,GAChB,KAAK,CAAC;AACT,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,SAAS,KAAK,EAAE,KAAK,CAAC,QAAQ,EACpE,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,EAC3C,QAAQ,EAAE,QAAQ,GACjB,QAAQ,CAAC;AACZ,wBAAgB,MAAM,CAAC,KAAK,CAAC,QAAQ,EACnC,UAAU,EAAE,qBAAqB,EACjC,QAAQ,EAAE,QAAQ,GACjB,QAAQ,CAAC;AACZ,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,SAAS,KAAK,EAAE,KAAK,CAAC,QAAQ,EACpE,UAAU,EAAE,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,oBAAoB,CAAC,KAAK,CAAC,EAC3E,QAAQ,EAAE,QAAQ,GACjB,WAAW,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { stringify } from "../lib/string.js";
|
|
2
|
+
import { isResult } from "../result/result.lib.js";
|
|
3
|
+
import {} from "../result/result.js";
|
|
4
|
+
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
5
|
+
export function unwrap(maybeValue, fallback) {
|
|
6
|
+
const hasFallback = arguments.length > 1;
|
|
7
|
+
if (maybeValue === null || maybeValue === undefined) {
|
|
8
|
+
if (hasFallback)
|
|
9
|
+
return fallback;
|
|
10
|
+
throw new TypeError(`${errorPrefix}Value is ${stringify(maybeValue)}`);
|
|
11
|
+
}
|
|
12
|
+
if (isResult(maybeValue)) {
|
|
13
|
+
// Handle pending state
|
|
14
|
+
if ("isPending" in maybeValue && maybeValue.isPending) {
|
|
15
|
+
if (maybeValue.data === undefined) {
|
|
16
|
+
if (hasFallback)
|
|
17
|
+
return fallback;
|
|
18
|
+
throw new TypeError(typeErrorMessageForResult(maybeValue), {
|
|
19
|
+
cause: maybeValue,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return maybeValue.data;
|
|
23
|
+
}
|
|
24
|
+
// Handle error state
|
|
25
|
+
if (maybeValue.isError) {
|
|
26
|
+
if (hasFallback)
|
|
27
|
+
return fallback;
|
|
28
|
+
throw new TypeError(typeErrorMessageForResult(maybeValue), {
|
|
29
|
+
cause: maybeValue,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// Handle success state
|
|
33
|
+
return maybeValue.data;
|
|
34
|
+
}
|
|
35
|
+
if (isPromiseSettledResult(maybeValue)) {
|
|
36
|
+
if (maybeValue.status === "fulfilled") {
|
|
37
|
+
return maybeValue.value;
|
|
38
|
+
}
|
|
39
|
+
if (hasFallback)
|
|
40
|
+
return fallback;
|
|
41
|
+
throw maybeValue.reason;
|
|
42
|
+
}
|
|
43
|
+
return maybeValue;
|
|
44
|
+
}
|
|
45
|
+
const isPromiseSettledResult = (value) => typeof value === "object" &&
|
|
46
|
+
value !== null &&
|
|
47
|
+
"status" in value &&
|
|
48
|
+
((value.status === "fulfilled" && "value" in value) ||
|
|
49
|
+
(value.status === "rejected" && "reason" in value));
|
|
50
|
+
const errorPrefix = "Cannot unwrap: ";
|
|
51
|
+
const typeErrorMessageForResult = (maybeValue) => `${errorPrefix}${String(maybeValue)} is not a success and there is no fallback`;
|
|
52
|
+
//# sourceMappingURL=unwrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unwrap.js","sourceRoot":"","sources":["../../src/unwrap/unwrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAe,MAAM,qBAAqB,CAAC;AAkDlD,+DAA+D;AAC/D,MAAM,UAAU,MAAM,CACpB,UAA2E,EAC3E,QAAgB;IAEhB,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IAEzC,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QACpD,IAAI,WAAW;YAAE,OAAO,QAAS,CAAC;QAClC,MAAM,IAAI,SAAS,CAAC,GAAG,WAAW,YAAY,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACzB,uBAAuB;QACvB,IAAI,WAAW,IAAI,UAAU,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;YACtD,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAClC,IAAI,WAAW;oBAAE,OAAO,QAAS,CAAC;gBAClC,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,UAAU,CAAC,EAAE;oBACzD,KAAK,EAAE,UAAU;iBAClB,CAAC,CAAC;YACL,CAAC;YACD,OAAO,UAAU,CAAC,IAAI,CAAC;QACzB,CAAC;QAED,qBAAqB;QACrB,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YACvB,IAAI,WAAW;gBAAE,OAAO,QAAS,CAAC;YAClC,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,UAAU,CAAC,EAAE;gBACzD,KAAK,EAAE,UAAU;aAClB,CAAC,CAAC;QACL,CAAC;QAED,uBAAuB;QACvB,OAAO,UAAU,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,IAAI,sBAAsB,CAAC,UAAU,CAAC,EAAE,CAAC;QACvC,IAAI,UAAU,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YACtC,OAAO,UAAU,CAAC,KAAK,CAAC;QAC1B,CAAC;QAED,IAAI,WAAW;YAAE,OAAO,QAAS,CAAC;QAClC,MAAM,UAAU,CAAC,MAAM,CAAC;IAC1B,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,sBAAsB,GAAG,CAC7B,KAAc,EAC0B,EAAE,CAC1C,OAAO,KAAK,KAAK,QAAQ;IACzB,KAAK,KAAK,IAAI;IACd,QAAQ,IAAI,KAAK;IACjB,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,WAAW,IAAI,OAAO,IAAI,KAAK,CAAC;QACjD,CAAC,KAAK,CAAC,MAAM,KAAK,UAAU,IAAI,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC;AAExD,MAAM,WAAW,GAAG,iBAAiB,CAAC;AACtC,MAAM,yBAAyB,GAAG,CAAC,UAAmB,EAAE,EAAE,CACxD,GAAG,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,4CAA4C,CAAC","sourcesContent":["import { stringify } from \"../lib/string.ts\";\nimport { isResult } from \"../result/result.lib.ts\";\nimport { type Result } from \"../result/result.ts\";\n\n/**\n * Unwrap a value from Result or nullable types, returning the underlying value or throwing an error.\n *\n * This function safely extracts values from wrapped types like Result,\n * handling null/undefined values and providing fallback mechanisms.\n *\n * @param maybeValue - The value to unwrap (can be a plain value or Result)\n * @param fallback - Optional fallback value to return instead of throwing\n * @returns The unwrapped value or fallback\n * @throws {TypeError} When the value cannot be unwrapped and no fallback is provided\n *\n * @example\n * ```ts\n * // Basic unwrapping\n * const value = unwrap(\"hello\"); // \"hello\"\n * const nullValue = unwrap(null, \"default\"); // \"default\"\n *\n * // With Result types\n * const success = result.success({ data: \"success\" });\n * const data = unwrap(success); // \"success\"\n *\n * const error = result.error({ error: new Error(\"failed\") });\n * const fallback = unwrap(error, \"default\"); // \"default\"\n *\n * // With Promise.allSettled results\n * const settled = { status: \"fulfilled\", value: \"done\" } as const;\n * unwrap(settled); // \"done\"\n * ```\n */\nexport function unwrap<Value, GivenError extends Error>(\n maybeValue: Value | Result<Value, GivenError> | PromiseFulfilledResult<Value>,\n): Value;\nexport function unwrap<Value>(\n maybeValue: Result.Success<Value> | Result.Pending<Value>,\n fallback: unknown,\n): Value;\nexport function unwrap<Value, GivenError extends Error, const Fallback>(\n maybeValue: Result.Error<GivenError, Value>,\n fallback: Fallback,\n): Fallback;\nexport function unwrap<const Fallback>(\n maybeValue: PromiseRejectedResult,\n fallback: Fallback,\n): Fallback;\nexport function unwrap<Value, GivenError extends Error, const Fallback>(\n maybeValue: Value | Result<Value, GivenError> | PromiseSettledResult<Value>,\n fallback: Fallback,\n): NonNullable<Value> | Fallback;\n// eslint-disable-next-line prefer-arrow/prefer-arrow-functions\nexport function unwrap<Value, GivenError extends Error, Fallback>(\n maybeValue: Value | Result<Value, GivenError> | PromiseSettledResult<Value>,\n fallback?: Value,\n): Value | Fallback {\n const hasFallback = arguments.length > 1;\n\n if (maybeValue === null || maybeValue === undefined) {\n if (hasFallback) return fallback!;\n throw new TypeError(`${errorPrefix}Value is ${stringify(maybeValue)}`);\n }\n\n if (isResult(maybeValue)) {\n // Handle pending state\n if (\"isPending\" in maybeValue && maybeValue.isPending) {\n if (maybeValue.data === undefined) {\n if (hasFallback) return fallback!;\n throw new TypeError(typeErrorMessageForResult(maybeValue), {\n cause: maybeValue,\n });\n }\n return maybeValue.data;\n }\n\n // Handle error state\n if (maybeValue.isError) {\n if (hasFallback) return fallback!;\n throw new TypeError(typeErrorMessageForResult(maybeValue), {\n cause: maybeValue,\n });\n }\n\n // Handle success state\n return maybeValue.data;\n }\n\n if (isPromiseSettledResult(maybeValue)) {\n if (maybeValue.status === \"fulfilled\") {\n return maybeValue.value;\n }\n\n if (hasFallback) return fallback!;\n throw maybeValue.reason;\n }\n\n return maybeValue;\n}\n\nconst isPromiseSettledResult = (\n value: unknown,\n): value is PromiseSettledResult<unknown> =>\n typeof value === \"object\" &&\n value !== null &&\n \"status\" in value &&\n ((value.status === \"fulfilled\" && \"value\" in value) ||\n (value.status === \"rejected\" && \"reason\" in value));\n\nconst errorPrefix = \"Cannot unwrap: \";\nconst typeErrorMessageForResult = (maybeValue: unknown) =>\n `${errorPrefix}${String(maybeValue)} is not a success and there is no fallback`;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerigon/typescript-toolkit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "🔧✨ Tiny helpers for TypeScript applications",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/peerigon/typescript-toolkit#readme",
|
|
@@ -19,9 +19,13 @@
|
|
|
19
19
|
".": "./dist/main.js",
|
|
20
20
|
"./assert": "./dist/assert/assert.js",
|
|
21
21
|
"./dedupe": "./dist/dedupe/dedupe.js",
|
|
22
|
+
"./emitter": "./dist/emitter/emitter.js",
|
|
22
23
|
"./enums": "./dist/enums/enums.js",
|
|
23
24
|
"./match": "./dist/match/match.js",
|
|
24
|
-
"./need": "./dist/need/need.js"
|
|
25
|
+
"./need": "./dist/need/need.js",
|
|
26
|
+
"./result": "./dist/result/result.js",
|
|
27
|
+
"./signals": "./dist/signals/signals.js",
|
|
28
|
+
"./unwrap": "./dist/unwrap/unwrap.js"
|
|
25
29
|
},
|
|
26
30
|
"main": "./dist/main.js",
|
|
27
31
|
"files": [
|
|
@@ -54,6 +58,7 @@
|
|
|
54
58
|
"@peerigon/configs": "^15.4.0",
|
|
55
59
|
"@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
|
|
56
60
|
"@size-limit/preset-small-lib": "^12.1.0",
|
|
61
|
+
"@tanstack/query-core": "^5.100.11",
|
|
57
62
|
"@types/node": "^25.8.0",
|
|
58
63
|
"@vitest/coverage-v8": "^4.1.6",
|
|
59
64
|
"eslint": "^9.39.2",
|