@poolse/sdk 0.2.0-alpha.7 → 1.0.0-rc.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/dist/index.d.cts CHANGED
@@ -1,120 +1,5 @@
1
1
  import { Channel } from 'phoenix';
2
2
 
3
- /**
4
- * Hosted poolse API URL. Used as the default for `PoolseConfig.apiUrl`
5
- * when you don't pass one — appropriate for the vast majority of
6
- * integrations that target the official poolse cloud. Self-hosted /
7
- * staging deployments override via the `apiUrl` field.
8
- */
9
- declare const POOLSE_API_URL = "https://api.poolse.dev";
10
- /**
11
- * SDK configuration passed to `new Poolse(config)`.
12
- */
13
- interface PoolseConfig {
14
- /**
15
- * Base URL of the poolse REST API. Defaults to the hosted endpoint
16
- * at `https://api.poolse.dev`. Override only for self-hosted /
17
- * staging deployments. MUST NOT include the `/v1` path — the SDK
18
- * adds that itself.
19
- */
20
- apiUrl?: string;
21
- /**
22
- * Async hook the SDK calls every time it needs an `Authorization:
23
- * Bearer <jwt>` header. Most apps refresh the JWT from their own
24
- * backend here — the SDK never talks to poolse's `POST
25
- * /v1/users/:user_id/tokens` itself (that endpoint is API-key-authed
26
- * and lives on the Customer's BACKEND, not the End User's device).
27
- *
28
- * Return `null` to deliberately make an unauthenticated request — the
29
- * server will reject it, but the SDK won't error inside `getToken`.
30
- */
31
- getToken: () => Promise<string | null> | string | null;
32
- /**
33
- * Optional fetch override. Browsers and Node 22+ both ship a global
34
- * `fetch`, but tests can inject a mock here; bundlers in restricted
35
- * environments can supply a polyfill.
36
- */
37
- fetch?: typeof globalThis.fetch;
38
- /**
39
- * Retry budget for transient failures (network + 5xx + 429). Defaults
40
- * to 3 attempts after the initial request. Set to 0 to disable.
41
- */
42
- maxRetries?: number;
43
- /**
44
- * Base for the exponential backoff, in milliseconds. Each retry waits
45
- * `min(maxBackoffMs, baseBackoffMs * 2^attempt)` plus jitter, OR honours
46
- * the `Retry-After` header if present. Default 250 ms.
47
- */
48
- baseBackoffMs?: number;
49
- /** Hard cap on a single retry delay. Default 30_000 ms. */
50
- maxBackoffMs?: number;
51
- /**
52
- * Override the idempotency-key generator. Defaults to
53
- * `crypto.randomUUID()`. Most apps don't need to override this — the
54
- * generator is exposed mainly for deterministic tests.
55
- */
56
- generateIdempotencyKey?: () => string;
57
- /**
58
- * Override the WebSocket URL. Defaults to `apiUrl` with `http(s)://`
59
- * swapped to `ws(s)://`, suitable when the realtime gateway shares
60
- * its origin with the REST API. Set explicitly for split-host
61
- * deployments (`https://api.example.com` REST + `wss://realtime.example.com` WS).
62
- */
63
- wsUrl?: string;
64
- /**
65
- * Path the WebSocket is mounted on. Defaults to `/socket` — matches
66
- * `CaasRealtimeWeb.UserSocket`'s mount point.
67
- */
68
- socketPath?: string;
69
- /**
70
- * Called when the underlying socket encounters a non-fatal error
71
- * (Phoenix retries internally). Useful for surfacing reconnect
72
- * banners in the UI without coupling to socket internals.
73
- */
74
- onSocketError?: (err: Error) => void;
75
- }
76
- /** Internal resolved config — all the defaults filled in. */
77
- interface ResolvedConfig {
78
- apiUrl: string;
79
- getToken: PoolseConfig['getToken'];
80
- fetch: typeof globalThis.fetch;
81
- maxRetries: number;
82
- baseBackoffMs: number;
83
- maxBackoffMs: number;
84
- generateIdempotencyKey: () => string;
85
- wsUrl: string | undefined;
86
- socketPath: string;
87
- onSocketError: ((err: Error) => void) | undefined;
88
- }
89
-
90
- type Fetcher = PoolseConfig['getToken'];
91
- interface GetTokenOptions {
92
- /** Bypass the cache and force a fresh call to the consumer's `getToken`. */
93
- forceRefresh?: boolean;
94
- }
95
- declare class TokenCache {
96
- private readonly fetcher;
97
- private token;
98
- private expMs;
99
- private inFlight;
100
- constructor(fetcher: Fetcher);
101
- /**
102
- * Synchronously return the cached token without triggering a fetch.
103
- * Returns `null` if the cache is empty OR if the cached token is
104
- * within the refresh window (treating near-expiry tokens as stale
105
- * keeps the realtime layer from handshaking with an about-to-expire
106
- * JWT when a refresh is already due).
107
- *
108
- * Exists for callers like Phoenix.js's `params` callback that the
109
- * library invokes synchronously and does NOT await — see
110
- * `phoenix/priv/static/phoenix.mjs::endPointURL()`.
111
- */
112
- peekToken(): string | null;
113
- getToken(opts?: GetTokenOptions): Promise<string | null>;
114
- invalidate(): void;
115
- private fetchAndStore;
116
- }
117
-
118
3
  type Uuid = string;
119
4
  type IsoDateTime = string;
120
5
  interface Me {
@@ -262,6 +147,30 @@ interface Message {
262
147
  inserted_at: IsoDateTime;
263
148
  updated_at: IsoDateTime;
264
149
  }
150
+ /**
151
+ * Customer-supplied user metadata — the SDK doesn't know your users'
152
+ * names or where their avatars live. Customers wire a
153
+ * `PoolseConfig.userResolver` that maps a poolse `Uuid` to whatever
154
+ * their app already stores: a display name, an avatar URL.
155
+ *
156
+ * The SDK caches resolved profiles in-memory (deduplicating
157
+ * concurrent lookups) so a 50-message render only fires one
158
+ * resolver call per unique sender.
159
+ */
160
+ interface PoolseUserProfile {
161
+ /**
162
+ * The name shown in sender labels, mention dropdowns, member
163
+ * lists, and read-receipt tooltips. Customers usually pass their
164
+ * app's `display_name` / `username` / `full_name`.
165
+ */
166
+ displayName: string;
167
+ /**
168
+ * Square avatar URL (any size — the UI scales). Null = render the
169
+ * fallback `<Avatar>` with initials. Optional so customers that
170
+ * don't track avatars can omit the field.
171
+ */
172
+ avatarUrl?: string | null;
173
+ }
265
174
  /**
266
175
  * Compact preview of a quoted message, embedded on the quoting
267
176
  * message when `Message.quoted_message_id` is set. Just enough for
@@ -368,6 +277,150 @@ interface ErrorEnvelope {
368
277
  };
369
278
  }
370
279
 
280
+ /**
281
+ * Hosted poolse API URL. Used as the default for `PoolseConfig.apiUrl`
282
+ * when you don't pass one — appropriate for the vast majority of
283
+ * integrations that target the official poolse cloud. Self-hosted /
284
+ * staging deployments override via the `apiUrl` field.
285
+ */
286
+ declare const POOLSE_API_URL = "https://api.poolse.dev";
287
+
288
+ /**
289
+ * SDK configuration passed to `new Poolse(config)`.
290
+ */
291
+ interface PoolseConfig {
292
+ /**
293
+ * Base URL of the poolse REST API. Defaults to the hosted endpoint
294
+ * at `https://api.poolse.dev`. Override only for self-hosted /
295
+ * staging deployments. MUST NOT include the `/v1` path — the SDK
296
+ * adds that itself.
297
+ */
298
+ apiUrl?: string;
299
+ /**
300
+ * Async hook the SDK calls every time it needs an `Authorization:
301
+ * Bearer <jwt>` header. Most apps refresh the JWT from their own
302
+ * backend here — the SDK never talks to poolse's `POST
303
+ * /v1/users/:user_id/tokens` itself (that endpoint is API-key-authed
304
+ * and lives on the Customer's BACKEND, not the End User's device).
305
+ *
306
+ * Return `null` to deliberately make an unauthenticated request — the
307
+ * server will reject it, but the SDK won't error inside `getToken`.
308
+ */
309
+ getToken: () => Promise<string | null> | string | null;
310
+ /**
311
+ * Optional fetch override. Browsers and Node 22+ both ship a global
312
+ * `fetch`, but tests can inject a mock here; bundlers in restricted
313
+ * environments can supply a polyfill.
314
+ */
315
+ fetch?: typeof globalThis.fetch;
316
+ /**
317
+ * Retry budget for transient failures (network + 5xx + 429). Defaults
318
+ * to 3 attempts after the initial request. Set to 0 to disable.
319
+ */
320
+ maxRetries?: number;
321
+ /**
322
+ * Base for the exponential backoff, in milliseconds. Each retry waits
323
+ * `min(maxBackoffMs, baseBackoffMs * 2^attempt)` plus jitter, OR honours
324
+ * the `Retry-After` header if present. Default 250 ms.
325
+ */
326
+ baseBackoffMs?: number;
327
+ /** Hard cap on a single retry delay. Default 30_000 ms. */
328
+ maxBackoffMs?: number;
329
+ /**
330
+ * Override the idempotency-key generator. Defaults to
331
+ * `crypto.randomUUID()`. Most apps don't need to override this — the
332
+ * generator is exposed mainly for deterministic tests.
333
+ */
334
+ generateIdempotencyKey?: () => string;
335
+ /**
336
+ * Override the WebSocket URL. Defaults to `apiUrl` with `http(s)://`
337
+ * swapped to `ws(s)://`, suitable when the realtime gateway shares
338
+ * its origin with the REST API. Set explicitly for split-host
339
+ * deployments (`https://api.example.com` REST + `wss://realtime.example.com` WS).
340
+ */
341
+ wsUrl?: string;
342
+ /**
343
+ * Path the WebSocket is mounted on. Defaults to `/socket` — matches
344
+ * `CaasRealtimeWeb.UserSocket`'s mount point.
345
+ */
346
+ socketPath?: string;
347
+ /**
348
+ * Called when the underlying socket encounters a non-fatal error
349
+ * (Phoenix retries internally). Useful for surfacing reconnect
350
+ * banners in the UI without coupling to socket internals.
351
+ */
352
+ onSocketError?: (err: Error) => void;
353
+ /**
354
+ * Resolve a poolse `user_id` to the customer's own user metadata
355
+ * (display name + avatar). Called by `chat.users.get(userId)` and
356
+ * the `useUser(userId)` React hook whenever a UI component needs
357
+ * to render a participant.
358
+ *
359
+ * The SDK caches results in-memory and dedupes concurrent calls,
360
+ * so a busy chat with 50 messages from 5 senders fires the
361
+ * resolver 5 times — once per unique sender — not 50.
362
+ *
363
+ * Customers typically hit their OWN backend here:
364
+ *
365
+ * userResolver: async (userId) => {
366
+ * const u = await fetch(`/api/users/by-poolse-id/${userId}`)
367
+ * .then((r) => r.json());
368
+ * return { displayName: u.full_name, avatarUrl: u.avatar_url };
369
+ * }
370
+ *
371
+ * Sync returns are fine when the customer already has the user
372
+ * data in memory (e.g., from a directory loaded at app boot):
373
+ *
374
+ * userResolver: (userId) => directory[userId] ?? null
375
+ *
376
+ * Return `null` when the user can't be found — components fall
377
+ * back to a userId-derived label and an initials avatar.
378
+ */
379
+ userResolver?: (userId: string) => Promise<PoolseUserProfile | null> | PoolseUserProfile | null;
380
+ }
381
+ /** Internal resolved config — all the defaults filled in. */
382
+ interface ResolvedConfig {
383
+ apiUrl: string;
384
+ getToken: PoolseConfig['getToken'];
385
+ fetch: typeof globalThis.fetch;
386
+ maxRetries: number;
387
+ baseBackoffMs: number;
388
+ maxBackoffMs: number;
389
+ generateIdempotencyKey: () => string;
390
+ wsUrl: string | undefined;
391
+ socketPath: string;
392
+ onSocketError: ((err: Error) => void) | undefined;
393
+ userResolver: PoolseConfig['userResolver'];
394
+ }
395
+
396
+ type Fetcher = PoolseConfig['getToken'];
397
+ interface GetTokenOptions {
398
+ /** Bypass the cache and force a fresh call to the consumer's `getToken`. */
399
+ forceRefresh?: boolean;
400
+ }
401
+ declare class TokenCache {
402
+ private readonly fetcher;
403
+ private token;
404
+ private expMs;
405
+ private inFlight;
406
+ constructor(fetcher: Fetcher);
407
+ /**
408
+ * Synchronously return the cached token without triggering a fetch.
409
+ * Returns `null` if the cache is empty OR if the cached token is
410
+ * within the refresh window (treating near-expiry tokens as stale
411
+ * keeps the realtime layer from handshaking with an about-to-expire
412
+ * JWT when a refresh is already due).
413
+ *
414
+ * Exists for callers like Phoenix.js's `params` callback that the
415
+ * library invokes synchronously and does NOT await — see
416
+ * `phoenix/priv/static/phoenix.mjs::endPointURL()`.
417
+ */
418
+ peekToken(): string | null;
419
+ getToken(opts?: GetTokenOptions): Promise<string | null>;
420
+ invalidate(): void;
421
+ private fetchAndStore;
422
+ }
423
+
371
424
  /** `message:new` / `message:updated` push payloads. */
372
425
  type MessageNewEvent = Message;
373
426
  type MessageUpdatedEvent = Message;
@@ -612,6 +665,23 @@ interface AttachmentUploadInput {
612
665
  /** Options accepted by every attachment method. */
613
666
  interface AttachmentOptions {
614
667
  signal?: AbortSignal;
668
+ /**
669
+ * Progress callback for `upload()`. Called periodically during the
670
+ * PUT phase (NOT during the presigned-URL request — that's a small
671
+ * JSON round-trip). When set, the SDK switches to XHR for the PUT
672
+ * since the standard `fetch` doesn't expose upload progress events.
673
+ *
674
+ * Customers using a custom `config.fetch` (e.g. node-fetch polyfill)
675
+ * lose progress reporting and the callback never fires — XHR is
676
+ * a browser-only API.
677
+ */
678
+ onProgress?: (event: AttachmentProgressEvent) => void;
679
+ }
680
+ interface AttachmentProgressEvent {
681
+ /** Bytes uploaded so far. */
682
+ loaded: number;
683
+ /** Total bytes — equals `input.byteSize` (passed back for convenience). */
684
+ total: number;
615
685
  }
616
686
  /** Top-level `/v1/attachments` collection. */
617
687
  declare class AttachmentsResource {
@@ -794,6 +864,43 @@ declare class MeResource {
794
864
  show(signal?: AbortSignal): Promise<Me>;
795
865
  }
796
866
 
867
+ type Listener = () => void;
868
+ declare class UsersResource {
869
+ private readonly config;
870
+ private readonly cache;
871
+ private readonly pending;
872
+ private readonly listeners;
873
+ constructor(config: ResolvedConfig);
874
+ /**
875
+ * Get the cached value if present. Returns `undefined` to mean
876
+ * "not in cache yet" (different from `null`, which means "resolver
877
+ * ran and the user wasn't found").
878
+ */
879
+ peek(userId: string): PoolseUserProfile | null | undefined;
880
+ /**
881
+ * Resolve a user, hitting the customer's `userResolver` on cache
882
+ * miss. Concurrent calls for the same id share one Promise.
883
+ */
884
+ get(userId: string): Promise<PoolseUserProfile | null>;
885
+ /**
886
+ * Subscribe to changes for a single user id. The listener fires
887
+ * when the resolver lands (or when the entry is invalidated).
888
+ * Returns an unsubscribe.
889
+ *
890
+ * `useUser` in @poolse/react uses this with `useSyncExternalStore`.
891
+ */
892
+ subscribe(userId: string, listener: Listener): () => void;
893
+ /** Drop a single cached entry — next `get` re-fetches via the resolver. */
894
+ invalidate(userId: string): void;
895
+ /**
896
+ * Drop the entire cache. Use after a sign-out, tenant swap, or any
897
+ * other event that invalidates every cached profile (e.g., the
898
+ * customer just renamed every user in bulk).
899
+ */
900
+ invalidateAll(): void;
901
+ private notify;
902
+ }
903
+
797
904
  declare class Poolse {
798
905
  /** `/v1/me` — current End User. */
799
906
  readonly me: MeResource;
@@ -803,6 +910,17 @@ declare class Poolse {
803
910
  readonly messages: MessagesResource;
804
911
  /** `/v1/attachments/*` — presigned-URL uploads/downloads. */
805
912
  readonly attachments: AttachmentsResource;
913
+ /**
914
+ * Customer-supplied user metadata, cached + dedup'd.
915
+ * `chat.users.get(userId)` returns `{ displayName, avatarUrl }`
916
+ * via the optional `config.userResolver`. UI components
917
+ * (`MessageBubble`, `MemberList`, `TypingIndicator`) pick this up
918
+ * automatically via the `useUser` hook in `@poolse/react`.
919
+ *
920
+ * If no resolver is configured, `get` always returns `null` and
921
+ * UI falls back to the userId slice + initials avatar.
922
+ */
923
+ readonly users: UsersResource;
806
924
  /**
807
925
  * Low-level REST client. Exposed for advanced use cases (custom endpoints,
808
926
  * raw retry/headers control). Most callers should use the resources above.
@@ -871,6 +989,6 @@ declare class AuthError extends ApiError {
871
989
  constructor(envelope: ErrorEnvelope['error']);
872
990
  }
873
991
 
874
- declare const version = "0.0.1";
992
+ declare const version = "1.0.0-rc.0";
875
993
 
876
- export { type AddMemberOptions, ApiError, type Attachment, type AttachmentDownloadResponse, AttachmentHandle, type AttachmentOptions, type AttachmentStatus, type AttachmentUploadInput, type AttachmentUploadRequest, type AttachmentUploadResponse, AttachmentsResource, AuthError, type Conversation, ConversationChannel, type ConversationCreateRequest, type ConversationCreatedEvent, ConversationHandle, type ConversationList, ConversationMessages, type ConversationType, type ConversationUpdateRequest, ConversationsResource, type ErrorEnvelope, type IsoDateTime, type Me, MeResource, type MemberReadEvent, type MemberRole, type Membership, type MembershipCreateRequest, type MembershipList, type MentionEvent, type Message, type MessageCreateRequest, type MessageDeletedEvent, MessageHandle, type MessageList, type MessageNewEvent, type MessageType, type MessageUpdateRequest, type MessageUpdatedEvent, MessagesResource, NetworkError, POOLSE_API_URL, Poolse, type PoolseConfig, PoolseError, PoolseRealtime, type PresenceSnapshot, type QuotedMessagePreview, RateLimitedError, type ReactionEvent, type ReactionRequest, type ReadRequest, type RealtimeStatus, type TypingEvent, type Unsubscribe, UserChannel, type Uuid, version };
994
+ export { type AddMemberOptions, ApiError, type Attachment, type AttachmentDownloadResponse, AttachmentHandle, type AttachmentOptions, type AttachmentProgressEvent, type AttachmentStatus, type AttachmentUploadInput, type AttachmentUploadRequest, type AttachmentUploadResponse, AttachmentsResource, AuthError, type Conversation, ConversationChannel, type ConversationCreateRequest, type ConversationCreatedEvent, ConversationHandle, type ConversationList, ConversationMessages, type ConversationType, type ConversationUpdateRequest, ConversationsResource, type ErrorEnvelope, type IsoDateTime, type Me, MeResource, type MemberReadEvent, type MemberRole, type Membership, type MembershipCreateRequest, type MembershipList, type MentionEvent, type Message, type MessageCreateRequest, type MessageDeletedEvent, MessageHandle, type MessageList, type MessageNewEvent, type MessageType, type MessageUpdateRequest, type MessageUpdatedEvent, MessagesResource, NetworkError, POOLSE_API_URL, Poolse, type PoolseConfig, PoolseError, PoolseRealtime, type PoolseUserProfile, type PresenceSnapshot, type QuotedMessagePreview, RateLimitedError, type ReactionEvent, type ReactionRequest, type ReadRequest, type RealtimeStatus, type TypingEvent, type Unsubscribe, UserChannel, UsersResource, type Uuid, version };