applesauce-core 5.2.0 → 6.0.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.
Files changed (78) hide show
  1. package/dist/casts/cast.d.ts +31 -0
  2. package/dist/casts/cast.js +67 -0
  3. package/dist/casts/index.d.ts +3 -0
  4. package/dist/casts/index.js +3 -0
  5. package/dist/casts/pubkey.d.ts +27 -0
  6. package/dist/casts/pubkey.js +49 -0
  7. package/dist/casts/user.d.ts +19 -0
  8. package/dist/casts/user.js +25 -0
  9. package/dist/factories/delete.d.ts +18 -0
  10. package/dist/factories/delete.js +23 -0
  11. package/dist/factories/event.d.ts +61 -0
  12. package/dist/factories/event.js +164 -0
  13. package/dist/factories/index.d.ts +5 -0
  14. package/dist/factories/index.js +5 -0
  15. package/dist/factories/mailboxes.d.ts +33 -0
  16. package/dist/factories/mailboxes.js +57 -0
  17. package/dist/factories/profile.d.ts +46 -0
  18. package/dist/factories/profile.js +80 -0
  19. package/dist/factories/types.d.ts +56 -0
  20. package/dist/helpers/event.d.ts +11 -2
  21. package/dist/helpers/event.js +2 -1
  22. package/dist/helpers/mailboxes.d.ts +5 -1
  23. package/dist/helpers/mailboxes.js +5 -0
  24. package/dist/helpers/pipeline.d.ts +14 -1
  25. package/dist/helpers/pipeline.js +17 -2
  26. package/dist/helpers/profile.d.ts +2 -2
  27. package/dist/helpers/regexp.d.ts +2 -0
  28. package/dist/helpers/regexp.js +8 -2
  29. package/dist/helpers/relays.d.ts +3 -1
  30. package/dist/helpers/relays.js +8 -10
  31. package/dist/helpers/url.d.ts +1 -4
  32. package/dist/helpers/url.js +1 -4
  33. package/dist/index.d.ts +3 -1
  34. package/dist/index.js +4 -1
  35. package/dist/observable/catch-error-inline.d.ts +3 -0
  36. package/dist/observable/catch-error-inline.js +5 -0
  37. package/dist/observable/chainable.d.ts +36 -0
  38. package/dist/observable/chainable.js +72 -0
  39. package/dist/observable/combine-latest-by-index.d.ts +10 -0
  40. package/dist/observable/combine-latest-by-index.js +121 -0
  41. package/dist/observable/combine-latest-by-key.d.ts +10 -0
  42. package/dist/observable/combine-latest-by-key.js +136 -0
  43. package/dist/observable/combine-latest-by-value.d.ts +10 -0
  44. package/dist/observable/combine-latest-by-value.js +117 -0
  45. package/dist/observable/combine-latest-by.d.ts +16 -0
  46. package/dist/observable/combine-latest-by.js +12 -0
  47. package/dist/observable/index.d.ts +10 -4
  48. package/dist/observable/index.js +10 -4
  49. package/dist/observable/timeout-with-ignore.d.ts +24 -0
  50. package/dist/observable/timeout-with-ignore.js +33 -0
  51. package/dist/operations/client.d.ts +1 -1
  52. package/dist/operations/client.js +2 -2
  53. package/dist/operations/content.d.ts +5 -2
  54. package/dist/operations/content.js +9 -7
  55. package/dist/operations/delete.d.ts +1 -1
  56. package/dist/operations/encrypted-content.d.ts +9 -3
  57. package/dist/operations/encrypted-content.js +9 -3
  58. package/dist/operations/event.d.ts +6 -4
  59. package/dist/operations/event.js +20 -12
  60. package/dist/operations/hidden-content.d.ts +8 -3
  61. package/dist/operations/hidden-content.js +11 -6
  62. package/dist/operations/mailboxes.d.ts +5 -1
  63. package/dist/operations/mailboxes.js +76 -0
  64. package/dist/operations/profile.d.ts +1 -1
  65. package/dist/operations/tag/common.d.ts +22 -7
  66. package/dist/operations/tag/common.js +33 -18
  67. package/dist/operations/tag/relay.d.ts +1 -1
  68. package/dist/operations/tags.d.ts +10 -4
  69. package/dist/operations/tags.js +19 -13
  70. package/package.json +20 -5
  71. package/dist/event-factory/event-factory.d.ts +0 -57
  72. package/dist/event-factory/event-factory.js +0 -94
  73. package/dist/event-factory/index.d.ts +0 -3
  74. package/dist/event-factory/index.js +0 -3
  75. package/dist/event-factory/methods.d.ts +0 -17
  76. package/dist/event-factory/methods.js +0 -44
  77. package/dist/event-factory/types.d.ts +0 -78
  78. /package/dist/{event-factory → factories}/types.js +0 -0
@@ -0,0 +1,80 @@
1
+ import { isKind } from "nostr-tools/kinds";
2
+ import { kinds } from "../helpers/event.js";
3
+ import { setProfile, updateProfile } from "../operations/profile.js";
4
+ import { blankEventTemplate, EventFactory, toEventTemplate } from "./event.js";
5
+ /** A factory class for building kind 0 profile/metadata events */
6
+ export class ProfileFactory extends EventFactory {
7
+ /**
8
+ * Creates a new profile factory
9
+ * @returns A new profile factory
10
+ */
11
+ static create() {
12
+ return new ProfileFactory((res) => res(blankEventTemplate(kinds.Metadata)));
13
+ }
14
+ /**
15
+ * Creates a new profile factory from an existing metadata event with validation
16
+ * @param event - The existing metadata event
17
+ * @returns A new profile factory
18
+ */
19
+ static modify(event) {
20
+ if (!isKind(event, kinds.Metadata))
21
+ throw new Error("Event is not a profile event");
22
+ return new ProfileFactory((res) => res(toEventTemplate(event)));
23
+ }
24
+ /** Sets the entire profile content, replacing any existing content */
25
+ override(content) {
26
+ return this.chain(setProfile(content));
27
+ }
28
+ /** Updates specific fields in the profile content, merging with existing content */
29
+ update(content) {
30
+ return this.chain(updateProfile(content));
31
+ }
32
+ /** Sets the display name */
33
+ name(name) {
34
+ return this.chain(updateProfile({ name }));
35
+ }
36
+ /** Sets the username */
37
+ username(username) {
38
+ return this.chain(updateProfile({ username }));
39
+ }
40
+ /** Sets the display name (alias for name) */
41
+ displayName(displayName) {
42
+ return this.chain(updateProfile({ display_name: displayName }));
43
+ }
44
+ /** Sets the about/bio text */
45
+ about(about) {
46
+ return this.chain(updateProfile({ about }));
47
+ }
48
+ /** Sets the profile picture URL */
49
+ picture(picture) {
50
+ return this.chain(updateProfile({ picture }));
51
+ }
52
+ /** Sets the banner image URL */
53
+ banner(banner) {
54
+ return this.chain(updateProfile({ banner }));
55
+ }
56
+ /** Sets the website URL */
57
+ website(website) {
58
+ return this.chain(updateProfile({ website }));
59
+ }
60
+ /** Sets the NIP-05 identifier */
61
+ nip05(nip05) {
62
+ return this.chain(updateProfile({ nip05 }));
63
+ }
64
+ /** Sets the Lightning Network address */
65
+ lnurlp(lnurlp) {
66
+ return this.chain(updateProfile({ lud06: lnurlp }));
67
+ }
68
+ /** Sets the Lightning Network address (LNURL) */
69
+ lightningAddress(address) {
70
+ return this.chain(updateProfile({ lud16: address }));
71
+ }
72
+ /** Sets the bot flag */
73
+ bot(bot) {
74
+ return this.chain(updateProfile({ bot }));
75
+ }
76
+ /** Sets the languages */
77
+ languages(languages) {
78
+ return this.chain(updateProfile({ languages }));
79
+ }
80
+ }
@@ -0,0 +1,56 @@
1
+ import { EventTemplate, NostrEvent, UnsignedEvent } from "../helpers/event.js";
2
+ import { AddressPointer } from "../helpers/pointers.js";
3
+ /** Nostr event signer */
4
+ export interface EventSigner {
5
+ getPublicKey: () => Promise<string> | string;
6
+ signEvent: (draft: EventTemplate | UnsignedEvent) => Promise<NostrEvent> | NostrEvent;
7
+ nip04?: {
8
+ encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
9
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
10
+ };
11
+ nip44?: {
12
+ encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
13
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
14
+ };
15
+ }
16
+ /** A single operation that transforms a value (context-free) */
17
+ export type Operation<I extends unknown = unknown, R extends unknown = unknown> = (value: I) => R | Promise<R>;
18
+ /** A single operation that modifies an events public or hidden tags array */
19
+ export type TagOperation = Operation<string[][], string[][]>;
20
+ /** A single operation that modifies an event */
21
+ export type EventOperation<I extends EventTemplate | UnsignedEvent | NostrEvent = EventTemplate, R extends EventTemplate | UnsignedEvent | NostrEvent = EventTemplate> = Operation<I, R>;
22
+ /** NIP-30 emoji type */
23
+ export type Emoji = {
24
+ /** The emoji shortcode (without the ::) */
25
+ shortcode: string;
26
+ /** The URL to the emoji image */
27
+ url: string;
28
+ /** The NIP-01 "a" tag address of the emoji pack this emoji belongs to */
29
+ address?: AddressPointer;
30
+ };
31
+ /** A context with optional methods for getting relay hints */
32
+ export interface RelayHintContext {
33
+ getEventRelayHint?: (event: string) => string | undefined | Promise<string> | Promise<undefined>;
34
+ getPubkeyRelayHint?: (pubkey: string) => string | undefined | Promise<string> | Promise<undefined>;
35
+ }
36
+ export interface EventFactoryClient {
37
+ name: string;
38
+ address?: Omit<AddressPointer, "kind" | "relays">;
39
+ }
40
+ export interface EmojiContext {
41
+ /** An array of custom emojis that will be used for text notes */
42
+ emojis?: Emoji[];
43
+ }
44
+ /** Services that can be provided to event operations */
45
+ export interface EventFactoryServices {
46
+ /** Event signer for signing and stamping events */
47
+ signer?: EventSigner;
48
+ /** Function to get relay hint for an event ID */
49
+ getEventRelayHint?: (eventId: string) => Promise<string | undefined>;
50
+ /** Function to get relay hint for a pubkey */
51
+ getPubkeyRelayHint?: (pubkey: string) => Promise<string | undefined>;
52
+ /** Custom emojis for NIP-30 emoji tags */
53
+ emojis?: Emoji[];
54
+ /** NIP-89 client pointer */
55
+ client?: EventFactoryClient;
56
+ }
@@ -1,14 +1,23 @@
1
1
  import { kinds } from "nostr-tools";
2
+ import { isKind } from "nostr-tools/kinds";
2
3
  import { isAddressableKind, isEphemeralKind, isRegularKind, isReplaceableKind } from "nostr-tools/kinds";
3
- import { NostrEvent, VerifiedEvent } from "nostr-tools/pure";
4
+ import { EventTemplate, NostrEvent, UnsignedEvent, VerifiedEvent } from "nostr-tools/pure";
4
5
  import { IAsyncEventStore, IEventStore } from "../event-store/interface.js";
5
6
  export { EventTemplate, finalizeEvent, getEventHash, NostrEvent, serializeEvent, UnsignedEvent, VerifiedEvent, verifiedSymbol, verifyEvent, } from "nostr-tools/pure";
6
7
  export { binarySearch, bytesToHex, hexToBytes, insertEventIntoAscendingList, insertEventIntoDescendingList, } from "nostr-tools/utils";
7
- export { isAddressableKind, isEphemeralKind, isRegularKind, isReplaceableKind, kinds };
8
+ export { isAddressableKind, isEphemeralKind, isRegularKind, isReplaceableKind, kinds, isKind };
8
9
  /** An event with a known kind. this is used to know if events have been validated */
9
10
  export type KnownEvent<K extends number> = Omit<NostrEvent, "kind"> & {
10
11
  kind: K;
11
12
  };
13
+ /** An event template with a known kind. used in event factories */
14
+ export type KnownEventTemplate<K extends number> = Omit<EventTemplate, "kind"> & {
15
+ kind: K;
16
+ };
17
+ /** An unsigned event with a known kind. used in event factories */
18
+ export type KnownUnsignedEvent<K extends number> = Omit<UnsignedEvent, "kind"> & {
19
+ kind: K;
20
+ };
12
21
  /** A symbol on an event that marks which event store its part of */
13
22
  export declare const EventStoreSymbol: unique symbol;
14
23
  export declare const EventUIDSymbol: unique symbol;
@@ -1,11 +1,12 @@
1
1
  import { kinds } from "nostr-tools";
2
+ import { isKind } from "nostr-tools/kinds";
2
3
  import { isAddressableKind, isEphemeralKind, isRegularKind, isReplaceableKind } from "nostr-tools/kinds";
3
4
  import { verifiedSymbol, verifyEvent } from "nostr-tools/pure";
4
5
  import { getOrComputeCachedValue } from "./cache.js";
5
6
  // Re-export types from nostr-tools
6
7
  export { finalizeEvent, getEventHash, serializeEvent, verifiedSymbol, verifyEvent, } from "nostr-tools/pure";
7
8
  export { binarySearch, bytesToHex, hexToBytes, insertEventIntoAscendingList, insertEventIntoDescendingList, } from "nostr-tools/utils";
8
- export { isAddressableKind, isEphemeralKind, isRegularKind, isReplaceableKind, kinds };
9
+ export { isAddressableKind, isEphemeralKind, isRegularKind, isReplaceableKind, kinds, isKind };
9
10
  /** A symbol on an event that marks which event store its part of */
10
11
  export const EventStoreSymbol = Symbol.for("event-store");
11
12
  export const EventUIDSymbol = Symbol.for("event-uid");
@@ -1,7 +1,11 @@
1
- import { NostrEvent } from "./event.js";
1
+ import { kinds, KnownEvent, NostrEvent } from "./event.js";
2
+ /** Type for NIP-65 relay list events */
3
+ export type MailboxesEvent = KnownEvent<kinds.RelayList>;
2
4
  export declare const MailboxesInboxesSymbol: unique symbol;
3
5
  export declare const MailboxesOutboxesSymbol: unique symbol;
4
6
  /** Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol */
5
7
  export declare function getInboxes(event: NostrEvent): string[];
6
8
  /** Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol */
7
9
  export declare function getOutboxes(event: NostrEvent): string[];
10
+ /** Checks if an event is a valid mailboxes event */
11
+ export declare function isMailboxesEvent(event: any): event is MailboxesEvent;
@@ -1,3 +1,4 @@
1
+ import { isKind, kinds } from "./event.js";
1
2
  import { getOrComputeCachedValue } from "./cache.js";
2
3
  import { isSafeRelayURL } from "./relays.js";
3
4
  import { isRTag } from "./tags.js";
@@ -47,3 +48,7 @@ export function getOutboxes(event) {
47
48
  return outboxes;
48
49
  });
49
50
  }
51
+ /** Checks if an event is a valid mailboxes event */
52
+ export function isMailboxesEvent(event) {
53
+ return isKind(event, kinds.RelayList);
54
+ }
@@ -1,4 +1,4 @@
1
- import { EventOperation, Operation, TagOperation } from "../event-factory/types.js";
1
+ import type { EventOperation, Operation, TagOperation } from "../factories/types.js";
2
2
  /** An array of Symbols to preserve when building events with {@link eventPipe} */
3
3
  export declare const PRESERVE_EVENT_SYMBOLS: Set<symbol>;
4
4
  export declare function identity<T>(x: T): T;
@@ -8,6 +8,19 @@ export declare function eventPipe(...operations: (EventOperation | undefined)[])
8
8
  export declare function tagPipe(...operations: (TagOperation | undefined)[]): TagOperation;
9
9
  /** A pipeline operation that does nothing */
10
10
  export declare function skip<T>(): (value: T) => T;
11
+ /**
12
+ * Pipe a value through a series of async operations
13
+ * @example
14
+ * ```ts
15
+ * const result = await pipe(
16
+ * draft,
17
+ * setContent("hello"),
18
+ * addTag("p", pubkey),
19
+ * sign(signer)
20
+ * );
21
+ * ```
22
+ */
23
+ export declare function pipe<T>(value: T, ...operations: Array<(v: any) => any | Promise<any>>): Promise<any>;
11
24
  /**
12
25
  * @param fns - An array of operations to pipe together
13
26
  * @param preserve - If set an array of symbols to keep, all other symbols will be removed
@@ -18,6 +18,21 @@ export function tagPipe(...operations) {
18
18
  export function skip() {
19
19
  return (value) => value;
20
20
  }
21
+ /**
22
+ * Pipe a value through a series of async operations
23
+ * @example
24
+ * ```ts
25
+ * const result = await pipe(
26
+ * draft,
27
+ * setContent("hello"),
28
+ * addTag("p", pubkey),
29
+ * sign(signer)
30
+ * );
31
+ * ```
32
+ */
33
+ export async function pipe(value, ...operations) {
34
+ return operations.reduce(async (prev, op) => op(await prev), Promise.resolve(value));
35
+ }
21
36
  /**
22
37
  * @param fns - An array of operations to pipe together
23
38
  * @param preserve - If set an array of symbols to keep, all other symbols will be removed
@@ -26,9 +41,9 @@ export function skip() {
26
41
  export function pipeFromAsyncArray(fns, preserve) {
27
42
  if (fns.length === 0)
28
43
  return identity;
29
- return async function piped(input, context) {
44
+ return async function piped(input) {
30
45
  return fns.reduce(async (prev, fn) => {
31
- const result = await fn(await prev, context);
46
+ const result = await fn(await prev);
32
47
  // Copy the symbols and fields if result is an object
33
48
  if (preserve && typeof result === "object" && result !== null && typeof prev === "object" && prev !== null) {
34
49
  const keys = Reflect.ownKeys(result).filter((key) => typeof key === "symbol");
@@ -19,10 +19,10 @@ export type ProfileContent = {
19
19
  banner?: string;
20
20
  /** A web URL related in any way to the event author */
21
21
  website?: string;
22
+ /** A LNURL-based Lightning address in the format `lnurlp://…` */
23
+ lud06?: string;
22
24
  /** Lightning address in the format `user@domain.com` (LUD-16 format) */
23
25
  lud16?: string;
24
- /** Lightning address in the format `user@domain.com` (LUD-06 format) */
25
- lud06?: string;
26
26
  /** DNS-based verification identifier in the format `_@domain.com` or `user@domain.com` */
27
27
  nip05?: string;
28
28
  /** Boolean to clarify that the content is entirely or partially the result of automation, such as with chatbots or newsfeeds */
@@ -6,6 +6,7 @@ export declare const Expressions: {
6
6
  readonly emoji: RegExp;
7
7
  readonly hashtag: RegExp;
8
8
  readonly lightning: RegExp;
9
+ readonly blossom: RegExp;
9
10
  };
10
11
  /** A list of Regular Expressions that match tokens surrounded by whitespace to avoid matching in URLs */
11
12
  export declare const Tokens: {
@@ -16,4 +17,5 @@ export declare const Tokens: {
16
17
  readonly emoji: RegExp;
17
18
  readonly hashtag: RegExp;
18
19
  readonly lightning: RegExp;
20
+ readonly blossom: RegExp;
19
21
  };
@@ -1,9 +1,9 @@
1
1
  export const Expressions = {
2
2
  get url() {
3
- return /(?:https?|wss?|ircs?|s?ftp):\/\/([a-zA-Z0-9\.\-]+\.[a-zA-Z]+(?::\d+)?)([\/\?#][\p{L}\p{N}\p{M}&\.-\/\?=#\-@%\+_,:!~*]*)?/gu;
3
+ return /(?:https?|wss?|ircs?|s?ftp):\/\/((?:(?:\d{1,3}\.){3}\d{1,3}|localhost|[a-zA-Z0-9\.\-]+\.[a-zA-Z]+)(?::\d+)?)([\/\?#][\p{L}\p{N}\p{M}&\.-\/\?=#\-@%\+_,:!~*]*)?/gu;
4
4
  },
5
5
  get link() {
6
- return /https?:\/\/([a-zA-Z0-9\.\-]+\.[a-zA-Z]+(?::\d+)?)([\/\?#][\p{L}\p{N}\p{M}&\.-\/\?=#\-@%\+_,:!~*]*)?/gu;
6
+ return /https?:\/\/((?:(?:\d{1,3}\.){3}\d{1,3}|localhost|[a-zA-Z0-9\.\-]+\.[a-zA-Z]+)(?::\d+)?)([\/\?#][\p{L}\p{N}\p{M}&\.-\/\?=#\-@%\+_,:!~*]*)?/gu;
7
7
  },
8
8
  get cashu() {
9
9
  return /(?:cashu:\/{0,2})?(cashu(?:A|B)[A-Za-z0-9_-]{100,}={0,3})/gi;
@@ -21,6 +21,9 @@ export const Expressions = {
21
21
  get lightning() {
22
22
  return /(?:lightning:)?(LNBC[A-Za-z0-9]+)/gim;
23
23
  },
24
+ get blossom() {
25
+ return /blossom:([0-9a-f]{64})\.([a-zA-Z0-9]+)(\?[^\s]*)?/g;
26
+ },
24
27
  };
25
28
  /** A list of Regular Expressions that match tokens surrounded by whitespace to avoid matching in URLs */
26
29
  export const Tokens = {
@@ -45,4 +48,7 @@ export const Tokens = {
45
48
  get lightning() {
46
49
  return new RegExp(`(?<=^|\\s)${Expressions.lightning.source}`, "gim");
47
50
  },
51
+ get blossom() {
52
+ return new RegExp(`(?<=^|\\s)${Expressions.blossom.source}`, "g");
53
+ },
48
54
  };
@@ -1,5 +1,7 @@
1
1
  import { NostrEvent } from "./event.js";
2
2
  export declare const SeenRelaysSymbol: unique symbol;
3
+ /** Normalizes a relay URL by using {@link normalizeURL} and {@link ensureWebSocketURL} */
4
+ export declare function normalizeRelayUrl(input: string): string;
3
5
  /** Marks an event as being seen on a relay */
4
6
  export declare function addSeenRelay(event: NostrEvent, relay: string): Set<string>;
5
7
  /** Returns the set of relays this event was seen on */
@@ -9,6 +11,6 @@ export declare function isFromRelay(event: NostrEvent, relay: string): boolean;
9
11
  /** A fast check to make sure relay hints are safe to connect to */
10
12
  export declare function isSafeRelayURL(relay: string): boolean;
11
13
  /** Merge multiple sets of relays and remove duplicates (ignores invalid URLs) */
12
- export declare function mergeRelaySets(...sources: (Iterable<string> | string | undefined)[]): string[];
14
+ export declare function mergeRelaySets(...sources: (Iterable<string> | string | undefined | null)[]): string[];
13
15
  /** Alias for {@link mergeRelaySets} */
14
16
  export declare const relaySet: typeof mergeRelaySets;
@@ -1,5 +1,9 @@
1
- import { normalizeURL } from "./url.js";
1
+ import { ensureWebSocketURL, normalizeURL } from "./url.js";
2
2
  export const SeenRelaysSymbol = Symbol.for("seen-relays");
3
+ /** Normalizes a relay URL by using {@link normalizeURL} and {@link ensureWebSocketURL} */
4
+ export function normalizeRelayUrl(input) {
5
+ return normalizeURL(ensureWebSocketURL(input));
6
+ }
3
7
  /** Marks an event as being seen on a relay */
4
8
  export function addSeenRelay(event, relay) {
5
9
  let seen = Reflect.get(event, SeenRelaysSymbol);
@@ -34,23 +38,17 @@ export function mergeRelaySets(...sources) {
34
38
  if (!src)
35
39
  continue;
36
40
  if (typeof src === "string") {
37
- // Source is a string
38
41
  try {
39
- const safe = normalizeURL(src).toString();
40
- if (safe)
41
- set.add(safe);
42
+ set.add(normalizeRelayUrl(src));
42
43
  }
43
44
  catch (error) {
44
45
  // failed to parse URL, ignore
45
46
  }
46
47
  }
47
- else {
48
- // Source is iterable
48
+ else if (Reflect.has(src, Symbol.iterator)) {
49
49
  for (const url of src) {
50
50
  try {
51
- const safe = normalizeURL(url).toString();
52
- if (safe)
53
- set.add(safe);
51
+ set.add(normalizeRelayUrl(url));
54
52
  }
55
53
  catch (error) {
56
54
  // failed to parse URL, ignore
@@ -20,8 +20,5 @@ export declare function ensureProtocol(url: string, protocol?: string): string;
20
20
  export declare function ensureWebSocketURL<T extends string | URL>(url: T): T;
21
21
  /** Converts a domain or WS URL to a HTTP URL */
22
22
  export declare function ensureHttpURL<T extends string | URL>(url: T): T;
23
- /**
24
- * Normalizes a string into a relay URL
25
- * Does not remove the trailing slash
26
- */
23
+ /** Normalizes a string into a cleaner URL by dropping port if its not needed */
27
24
  export declare function normalizeURL<T extends string | URL>(url: T): T;
@@ -80,10 +80,7 @@ export function ensureHttpURL(url) {
80
80
  // @ts-expect-error
81
81
  return typeof url === "string" ? p.toString() : p;
82
82
  }
83
- /**
84
- * Normalizes a string into a relay URL
85
- * Does not remove the trailing slash
86
- */
83
+ /** Normalizes a string into a cleaner URL by dropping port if its not needed */
87
84
  export function normalizeURL(url) {
88
85
  let p = new URL(url);
89
86
  // Remove any double slashes
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
- export * from "./event-factory/index.js";
1
+ export * from "./factories/index.js";
2
+ export * from "./casts/index.js";
2
3
  export * from "./event-store/index.js";
3
4
  export * from "./logger.js";
4
5
  export * from "./observable/index.js";
5
6
  export * as Helpers from "./helpers/index.js";
6
7
  export * as Models from "./models/index.js";
7
8
  export * as Operations from "./operations/index.js";
9
+ export * as Factories from "./factories/index.js";
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
- export * from "./event-factory/index.js";
1
+ // Export from factories
2
+ export * from "./factories/index.js";
3
+ export * from "./casts/index.js";
2
4
  export * from "./event-store/index.js";
3
5
  export * from "./logger.js";
4
6
  export * from "./observable/index.js";
5
7
  export * as Helpers from "./helpers/index.js";
6
8
  export * as Models from "./models/index.js";
7
9
  export * as Operations from "./operations/index.js";
10
+ export * as Factories from "./factories/index.js";
@@ -0,0 +1,3 @@
1
+ import { OperatorFunction } from "rxjs";
2
+ /** Catches any errors and includes them in the observable stream */
3
+ export declare function catchErrorInline<T extends unknown>(): OperatorFunction<T, T | Error>;
@@ -0,0 +1,5 @@
1
+ import { catchError, of } from "rxjs";
2
+ /** Catches any errors and includes them in the observable stream */
3
+ export function catchErrorInline() {
4
+ return catchError((err) => (err instanceof Error ? of(err) : of(new Error(err.message))));
5
+ }
@@ -0,0 +1,36 @@
1
+ import { Observable } from "rxjs";
2
+ /**
3
+ * Wraps an Observable in a Proxy that enables property chaining.
4
+ * When accessing a property ending with `$`, it uses switchMap to chain
5
+ * to that property's observable value.
6
+ * When accessing a non-observable property, it returns an Observable of that property's value.
7
+ */
8
+ export declare function chainable<T>(observable: Observable<T>): ChainableObservable<T>;
9
+ /**
10
+ * Helper type to extract nullable parts (undefined/null) from a type
11
+ */
12
+ type NullableParts<T> = Extract<T, undefined | null>;
13
+ /**
14
+ * Helper type to get the property type, preserving nullable parts from the parent type
15
+ */
16
+ type PropChain<T, K extends keyof NonNullable<T>> = NonNullable<T>[K] | NullableParts<T>;
17
+ /**
18
+ * A chainable Observable type that allows property chaining.
19
+ * This type maps all properties to chainable observables:
20
+ * - Properties ending with $: extracts inner type from Observable<U> → ChainableObservable<U>
21
+ * - Other properties: uses property type directly → ChainableObservable<PropertyType>
22
+ *
23
+ * Note: TypeScript has limitations inferring through Proxy types. For better
24
+ * type inference, you may need to explicitly type the result:
25
+ */
26
+ export type ChainableObservable<T> = Observable<T> & Omit<{
27
+ [K in keyof NonNullable<T> as K extends string ? K : never]: K extends `${infer _}$` ? NonNullable<T>[K] extends Observable<infer U> ? ChainableObservable<U | NullableParts<T>> : never : ChainableObservable<PropChain<T, K>>;
28
+ }, "$first" | "$last"> & {
29
+ /** Returns a promise that resolves with the first value or rejects with a timeout error */
30
+ $first(first?: number): Promise<NonNullable<T>>;
31
+ $first<V>(first?: number, fallback?: V): Promise<NonNullable<T> | V>;
32
+ /** Returns a promise that resolves with the last value or rejects with a timeout error */
33
+ $last(max?: number): Promise<NonNullable<T>>;
34
+ $last<V>(max?: number, fallback?: V): Promise<NonNullable<T> | V>;
35
+ };
36
+ export {};
@@ -0,0 +1,72 @@
1
+ import { filter, firstValueFrom, isObservable, lastValueFrom, map, of, switchMap, timeout } from "rxjs";
2
+ /**
3
+ * A symbol used to mark an Observable as chainable
4
+ */
5
+ const CHAINABLE_CACHE_SYMBOL = Symbol.for("chainable-cache");
6
+ /**
7
+ * Wraps an Observable in a Proxy that enables property chaining.
8
+ * When accessing a property ending with `$`, it uses switchMap to chain
9
+ * to that property's observable value.
10
+ * When accessing a non-observable property, it returns an Observable of that property's value.
11
+ */
12
+ export function chainable(observable) {
13
+ // Create a Proxy that intercepts property access
14
+ const proxy = new Proxy(observable, {
15
+ get(target$, prop) {
16
+ const cache = observable[CHAINABLE_CACHE_SYMBOL] ||
17
+ (observable[CHAINABLE_CACHE_SYMBOL] = {});
18
+ // Forward all Observable methods and properties
19
+ if (prop in target$ || typeof prop === "symbol") {
20
+ const value = target$[prop];
21
+ // If it's a function, bind it to the target
22
+ if (typeof value === "function")
23
+ return value.bind(target$);
24
+ return value;
25
+ }
26
+ if (typeof prop === "string") {
27
+ // Return cached observable if it exists
28
+ const cached = cache[prop];
29
+ if (cached)
30
+ return cached;
31
+ // Otherwise, create a new observable
32
+ let prop$;
33
+ // Extra observalbe helpers to make it easier to work with observables
34
+ if (prop === "$first") {
35
+ return (...args) => firstValueFrom(target$.pipe(filter((v) => v !== undefined && v !== null), args.length === 2
36
+ ? timeout({ first: args[0], with: () => of(args[1]) })
37
+ : timeout({ first: args[0] ?? 10_000 })));
38
+ }
39
+ else if (prop === "$last") {
40
+ return (...args) => lastValueFrom(target$.pipe(filter((v) => v !== undefined && v !== null), args.length === 2
41
+ ? timeout({ first: args[0], with: () => of(args[1]) })
42
+ : timeout({ first: args[0] ?? 10_000 })));
43
+ }
44
+ // Handle property access for properties ending with $
45
+ else if (prop.endsWith("$")) {
46
+ // Use switchMap to chain to the nested observable
47
+ prop$ = target$.pipe(switchMap((target) => {
48
+ const value = target === undefined || target === null ? target : target[prop];
49
+ // If value is an observable, return it
50
+ if (isObservable(value))
51
+ return value;
52
+ // Otherwise wrap it in an observable
53
+ else
54
+ return of(value);
55
+ }));
56
+ }
57
+ // For non-$ properties, return an Observable of the property value
58
+ else {
59
+ prop$ = target$.pipe(
60
+ // Access the property on the value if target is not undefined or null
61
+ map((target) => (target === undefined || target === null ? target : target[prop])));
62
+ }
63
+ // Make the chained observable chainable too
64
+ const observable = chainable(prop$);
65
+ cache[prop] = observable;
66
+ return observable;
67
+ }
68
+ throw new Error(`Unable to access property "${prop}" on chainable observable`);
69
+ },
70
+ });
71
+ return proxy;
72
+ }
@@ -0,0 +1,10 @@
1
+ import { Observable } from "rxjs";
2
+ /**
3
+ * Dynamic counterpart to `switchMap(() => combineLatest(...))` for arrays,
4
+ * with stable branches by index.
5
+ *
6
+ * Each array index gets its own long-lived branch (`of(value).pipe(...)`
7
+ * equivalent), and branches are only created/removed when indices are
8
+ * added/removed.
9
+ */
10
+ export declare function combineLatestByIndex<T, R>(project: (source$: Observable<T>, index: number) => Observable<R>): (source$: Observable<readonly T[]>) => Observable<R[]>;