applesauce-loaders 5.0.2 → 5.1.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A collection of functional loading methods to make common event loading patterns easier.
4
4
 
5
- [Documentation](https://hzrd149.github.io/applesauce/loaders/package.html) [typedoc](https://hzrd149.github.io/applesauce/typedoc/modules/applesauce-loaders.html)
5
+ [Documentation](https://applesauce.build/loaders/package.html) [typedoc](https://applesauce.build/typedoc/modules/applesauce-loaders.html)
6
6
 
7
7
  ## Address Loader
8
8
 
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Resolves multiple promises concurrently, similar to RxJS combineLatest but for async operations.
3
+ * Each promise races against the provided timeout. If a promise doesn't resolve within the timeout,
4
+ * its value will be `undefined` in the returned object.
5
+ *
6
+ * @param map - An object where each value is a Promise
7
+ * @param timeout - Global timeout in milliseconds for all fields. If a field doesn't resolve within this time, it will be `undefined`
8
+ * @returns A promise that resolves to an object with the same keys, where each value is either the resolved value or `undefined`
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const { profile, mailboxes, notes } = await loadAsyncMap(
13
+ * {
14
+ * profile: user.profile$.$first(),
15
+ * mailboxes: user.mailboxes$.$first(1000),
16
+ * notes: lastValueFrom(someObservable),
17
+ * },
18
+ * 30 * 1000, // 30 second timeout
19
+ * );
20
+ * ```
21
+ */
22
+ export declare function loadAsyncMap<T extends Record<string, Promise<any>>>(map: T, timeout: number): Promise<{
23
+ [K in keyof T]: Awaited<T[K]> | undefined;
24
+ }>;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Resolves multiple promises concurrently, similar to RxJS combineLatest but for async operations.
3
+ * Each promise races against the provided timeout. If a promise doesn't resolve within the timeout,
4
+ * its value will be `undefined` in the returned object.
5
+ *
6
+ * @param map - An object where each value is a Promise
7
+ * @param timeout - Global timeout in milliseconds for all fields. If a field doesn't resolve within this time, it will be `undefined`
8
+ * @returns A promise that resolves to an object with the same keys, where each value is either the resolved value or `undefined`
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const { profile, mailboxes, notes } = await loadAsyncMap(
13
+ * {
14
+ * profile: user.profile$.$first(),
15
+ * mailboxes: user.mailboxes$.$first(1000),
16
+ * notes: lastValueFrom(someObservable),
17
+ * },
18
+ * 30 * 1000, // 30 second timeout
19
+ * );
20
+ * ```
21
+ */
22
+ export async function loadAsyncMap(map, timeout) {
23
+ // Create a timeout promise that resolves with a special marker after the timeout
24
+ const TIMEOUT_MARKER = Symbol("timeout");
25
+ const createTimeoutPromise = () => {
26
+ return new Promise((resolve) => {
27
+ setTimeout(() => resolve(TIMEOUT_MARKER), timeout);
28
+ });
29
+ };
30
+ // Race each promise against the timeout
31
+ // If the promise resolves first, use its value
32
+ // If the timeout happens first, use undefined
33
+ // If the promise rejects, catch it and return undefined
34
+ const entries = Object.entries(map);
35
+ const results = await Promise.allSettled(entries.map(async ([key, promise]) => {
36
+ // Wrap promise to handle rejections gracefully and prevent unhandled rejections
37
+ const safePromise = promise
38
+ .then((value) => ({ type: "resolved", value }))
39
+ .catch(() => ({ type: "rejected" }));
40
+ const result = await Promise.race([
41
+ safePromise,
42
+ createTimeoutPromise().then(() => ({ type: "timeout" })),
43
+ ]);
44
+ if (result.type === "timeout" || result.type === "rejected") {
45
+ return [key, undefined];
46
+ }
47
+ else {
48
+ return [key, result.value];
49
+ }
50
+ }));
51
+ // Extract values from settled results, defaulting to undefined if anything went wrong
52
+ const extractedResults = results.map((settled, index) => {
53
+ if (settled.status === "fulfilled") {
54
+ return settled.value;
55
+ }
56
+ else {
57
+ // If the outer promise somehow rejected, return undefined for that key
58
+ const key = entries[index][0];
59
+ return [key, undefined];
60
+ }
61
+ });
62
+ // Reconstruct the object with the same keys
63
+ return Object.fromEntries(extractedResults);
64
+ }
@@ -1,6 +1,7 @@
1
- export * from "./dns-identity.js";
1
+ export * from "./address-pointer.js";
2
+ export * from "./async-map.js";
2
3
  export * from "./cache.js";
4
+ export * from "./dns-identity.js";
3
5
  export * from "./event-pointer.js";
4
- export * from "./address-pointer.js";
5
6
  export * from "./loaders.js";
6
7
  export * from "./upstream.js";
@@ -1,6 +1,7 @@
1
- export * from "./dns-identity.js";
1
+ export * from "./address-pointer.js";
2
+ export * from "./async-map.js";
2
3
  export * from "./cache.js";
4
+ export * from "./dns-identity.js";
3
5
  export * from "./event-pointer.js";
4
- export * from "./address-pointer.js";
5
6
  export * from "./loaders.js";
6
7
  export * from "./upstream.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-loaders",
3
- "version": "5.0.2",
3
+ "version": "5.1.0",
4
4
  "description": "A collection of observable based loaders built on rx-nostr",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -52,13 +52,13 @@
52
52
  }
53
53
  },
54
54
  "dependencies": {
55
- "applesauce-core": "^5.0.0",
55
+ "applesauce-core": "^5.1.0",
56
56
  "nanoid": "^5.0.9",
57
57
  "rxjs": "^7.8.1"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@hirez_io/observer-spy": "^2.2.0",
61
- "applesauce-signers": "^5.0.0",
61
+ "applesauce-signers": "^5.1.0",
62
62
  "rimraf": "^6.0.1",
63
63
  "typescript": "^5.8.3",
64
64
  "vitest": "^4.0.15",