@pageloop/client 0.5.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 (70) hide show
  1. package/LICENSE +94 -0
  2. package/README.md +97 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/.tsbuildinfo.preact +1 -0
  5. package/dist/.tsbuildinfo.react +1 -0
  6. package/dist/.tsbuildinfo.solid +1 -0
  7. package/dist/ApiClient.d.ts +121 -0
  8. package/dist/ApiClient.d.ts.map +1 -0
  9. package/dist/ApiClient.js +512 -0
  10. package/dist/ApiClient.js.map +1 -0
  11. package/dist/CommentEngine.d.ts +111 -0
  12. package/dist/CommentEngine.d.ts.map +1 -0
  13. package/dist/CommentEngine.js +277 -0
  14. package/dist/CommentEngine.js.map +1 -0
  15. package/dist/EventBus.d.ts +122 -0
  16. package/dist/EventBus.d.ts.map +1 -0
  17. package/dist/EventBus.js +34 -0
  18. package/dist/EventBus.js.map +1 -0
  19. package/dist/IdbCache.d.ts +22 -0
  20. package/dist/IdbCache.d.ts.map +1 -0
  21. package/dist/IdbCache.js +79 -0
  22. package/dist/IdbCache.js.map +1 -0
  23. package/dist/PageLoop.d.ts +424 -0
  24. package/dist/PageLoop.d.ts.map +1 -0
  25. package/dist/PageLoop.js +1092 -0
  26. package/dist/PageLoop.js.map +1 -0
  27. package/dist/PageTracker.d.ts +32 -0
  28. package/dist/PageTracker.d.ts.map +1 -0
  29. package/dist/PageTracker.js +105 -0
  30. package/dist/PageTracker.js.map +1 -0
  31. package/dist/UIRenderer.d.ts +218 -0
  32. package/dist/UIRenderer.d.ts.map +1 -0
  33. package/dist/UIRenderer.js +2 -0
  34. package/dist/UIRenderer.js.map +1 -0
  35. package/dist/auth/resolveInitialToken.d.ts +49 -0
  36. package/dist/auth/resolveInitialToken.d.ts.map +1 -0
  37. package/dist/auth/resolveInitialToken.js +97 -0
  38. package/dist/auth/resolveInitialToken.js.map +1 -0
  39. package/dist/errorCode.d.ts +12 -0
  40. package/dist/errorCode.d.ts.map +1 -0
  41. package/dist/errorCode.js +21 -0
  42. package/dist/errorCode.js.map +1 -0
  43. package/dist/index.d.ts +20 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +18 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/notifications/BrowserNotifications.d.ts +68 -0
  48. package/dist/notifications/BrowserNotifications.d.ts.map +1 -0
  49. package/dist/notifications/BrowserNotifications.js +147 -0
  50. package/dist/notifications/BrowserNotifications.js.map +1 -0
  51. package/dist/preact/index.d.ts +37 -0
  52. package/dist/preact/index.d.ts.map +1 -0
  53. package/dist/preact/index.js +150 -0
  54. package/dist/preact/index.js.map +1 -0
  55. package/dist/preact/style.css +12 -0
  56. package/dist/react/index.d.ts +52 -0
  57. package/dist/react/index.d.ts.map +1 -0
  58. package/dist/react/index.js +165 -0
  59. package/dist/react/index.js.map +1 -0
  60. package/dist/react/style.css +12 -0
  61. package/dist/safeStorage.d.ts +26 -0
  62. package/dist/safeStorage.d.ts.map +1 -0
  63. package/dist/safeStorage.js +78 -0
  64. package/dist/safeStorage.js.map +1 -0
  65. package/dist/solid/index.d.ts +40 -0
  66. package/dist/solid/index.d.ts.map +1 -0
  67. package/dist/solid/index.jsx +134 -0
  68. package/dist/solid/index.jsx.map +1 -0
  69. package/dist/solid/style.css +12 -0
  70. package/package.json +85 -0
@@ -0,0 +1,277 @@
1
+ import { ApiClient } from './ApiClient.js';
2
+ import { EventBus } from './EventBus.js';
3
+ /**
4
+ * Local comment store + CRUD API. Keeps in-memory maps keyed by ids, listens
5
+ * to server events to stay in sync, and emits client-side `comment:*` /
6
+ * `reply:*` / `reaction:*` events for UI modules.
7
+ *
8
+ * Replies and reactions are loaded lazily — most comments don't have either,
9
+ * and pre-fetching them all on bootstrap would inflate the payload.
10
+ */
11
+ export class CommentEngine {
12
+ api;
13
+ bus;
14
+ byPage = new Map();
15
+ /** commentId → replies. Populated on demand by loadReplies. */
16
+ repliesByComment = new Map();
17
+ /** "${kind}:${id}" → reactions. Populated on demand or via the reactions.changed event. */
18
+ reactionsByTarget = new Map();
19
+ constructor(api, bus) {
20
+ this.api = api;
21
+ this.bus = bus;
22
+ bus.on('comments.created', (event) => {
23
+ const c = event.comment;
24
+ const list = this.byPage.get(c.trackedPageId) ?? [];
25
+ if (!list.find((x) => x.id === c.id))
26
+ list.push(c);
27
+ this.byPage.set(c.trackedPageId, list);
28
+ this.bus.emit('comment:created', c);
29
+ });
30
+ bus.on('comments.updated', (event) => {
31
+ const c = event.comment;
32
+ const list = this.byPage.get(c.trackedPageId) ?? [];
33
+ const idx = list.findIndex((x) => x.id === c.id);
34
+ if (idx >= 0)
35
+ list[idx] = c;
36
+ this.byPage.set(c.trackedPageId, list);
37
+ this.bus.emit('comment:updated', c);
38
+ });
39
+ bus.on('comments.deleted', (event) => {
40
+ const list = this.byPage.get(event.trackedPageId) ?? [];
41
+ const next = list.filter((x) => x.id !== event.commentId);
42
+ this.byPage.set(event.trackedPageId, next);
43
+ this.repliesByComment.delete(event.commentId);
44
+ this.reactionsByTarget.delete(`comment:${event.commentId}`);
45
+ this.bus.emit('comment:deleted', { id: event.commentId });
46
+ });
47
+ bus.on('comments.bulkDeleted', (event) => {
48
+ this.byPage.set(event.trackedPageId, []);
49
+ this.bus.emit('comment:bulkDeleted', event);
50
+ });
51
+ bus.on('replies.created', (event) => {
52
+ const reply = event.reply;
53
+ const existing = this.repliesByComment.get(reply.commentId) ?? [];
54
+ if (!existing.find((r) => r.id === reply.id))
55
+ existing.push(reply);
56
+ this.repliesByComment.set(reply.commentId, existing);
57
+ this.bus.emit('reply:created', reply);
58
+ });
59
+ bus.on('replies.updated', (event) => {
60
+ const reply = event.reply;
61
+ const existing = this.repliesByComment.get(reply.commentId);
62
+ if (existing) {
63
+ const idx = existing.findIndex((r) => r.id === reply.id);
64
+ if (idx >= 0)
65
+ existing[idx] = reply;
66
+ this.repliesByComment.set(reply.commentId, existing);
67
+ }
68
+ this.bus.emit('reply:updated', reply);
69
+ });
70
+ bus.on('replies.deleted', (event) => {
71
+ const list = this.repliesByComment.get(event.commentId);
72
+ if (list) {
73
+ this.repliesByComment.set(event.commentId, list.filter((r) => r.id !== event.replyId));
74
+ }
75
+ this.bus.emit('reply:deleted', { id: event.replyId, commentId: event.commentId });
76
+ });
77
+ bus.on('reactions.changed', (event) => {
78
+ this.reactionsByTarget.set(`${event.targetKind}:${event.targetId}`, event.reactions);
79
+ this.bus.emit('reaction:changed', event);
80
+ });
81
+ }
82
+ load(pageId) {
83
+ return this.byPage.get(pageId) ?? [];
84
+ }
85
+ async loadFromServer(pageId) {
86
+ const comments = await this.api.call('comments.list', { trackedPageId: pageId });
87
+ this.byPage.set(pageId, comments);
88
+ this.bus.emit('comments:loaded', { pageId, comments });
89
+ return comments;
90
+ }
91
+ /** Create a comment + immediately fold the response into the local
92
+ * cache so the sidebar / bubble layer paint without waiting for
93
+ * a WS broadcast echo (comments.* events currently route to no
94
+ * topic, so the echo never arrives — the local-emit closes the
95
+ * refresh-required gap). */
96
+ async create(input) {
97
+ const created = await this.api.call('comments.create', input);
98
+ this.applyLocalCreate(created);
99
+ return created;
100
+ }
101
+ /** Append a new comment to the per-page cache + fire the derived
102
+ * `comment:created` event. De-dupes against the WS-arrival path
103
+ * so a future fan-out wouldn't double-render. */
104
+ applyLocalCreate(c) {
105
+ const list = this.byPage.get(c.trackedPageId) ?? [];
106
+ if (!list.find((x) => x.id === c.id)) {
107
+ list.push(c);
108
+ this.byPage.set(c.trackedPageId, list);
109
+ }
110
+ this.bus.emit('comment:created', c);
111
+ }
112
+ /** Edit a comment + fold the updated row into the local cache so the
113
+ * editor's own change repaints immediately. Same rationale as
114
+ * `create`/`delete`: `comments.updated` doesn't route back to the
115
+ * editing connection today, so without this local-emit the edited
116
+ * body/status sits stale until an unrelated refresh. */
117
+ async update(input) {
118
+ const updated = await this.api.call('comments.update', input);
119
+ this.applyLocalUpdate(updated);
120
+ return updated;
121
+ }
122
+ /** Promote/demote a comment to/from a task, or toggle its completion.
123
+ * Gated server-side on write access to the page (any commenter). Folds
124
+ * the result into the local cache so the sidebar repaints immediately. */
125
+ async setTask(input) {
126
+ const updated = await this.api.call('comments.setTask', input);
127
+ this.applyLocalUpdate(updated);
128
+ return updated;
129
+ }
130
+ /** Replace a cached comment in place + fire `comment:updated`. Locates
131
+ * the page bucket by the comment's own `trackedPageId`; de-dupes so a
132
+ * later WS echo re-renders with identical data instead of duplicating. */
133
+ applyLocalUpdate(c) {
134
+ const list = this.byPage.get(c.trackedPageId) ?? [];
135
+ const idx = list.findIndex((x) => x.id === c.id);
136
+ if (idx >= 0)
137
+ list[idx] = c;
138
+ else
139
+ list.push(c);
140
+ this.byPage.set(c.trackedPageId, list);
141
+ this.bus.emit('comment:updated', c);
142
+ }
143
+ async delete(commentId) {
144
+ await this.api.call('comments.delete', { commentId });
145
+ this.applyLocalDelete(commentId);
146
+ }
147
+ /** Mirror of `applyLocalCreate` for deletes. The WS `comments.deleted`
148
+ * fan-out doesn't route to the deleter's connection right now, so
149
+ * without this local-emit the sidebar + bubble layer hold the
150
+ * stale row until a page reload. Walks every cached page bucket
151
+ * to find the comment because `delete()` doesn't take a page id. */
152
+ applyLocalDelete(commentId) {
153
+ let trackedPageId = null;
154
+ for (const [pageId, list] of this.byPage) {
155
+ if (list.some((c) => c.id === commentId)) {
156
+ trackedPageId = pageId;
157
+ this.byPage.set(pageId, list.filter((c) => c.id !== commentId));
158
+ break;
159
+ }
160
+ }
161
+ this.repliesByComment.delete(commentId);
162
+ this.reactionsByTarget.delete(`comment:${commentId}`);
163
+ this.bus.emit('comment:deleted', { id: commentId, trackedPageId });
164
+ }
165
+ /** Post a reply + fold it into the cached reply list so the thread
166
+ * paints without waiting for a (currently-unrouted) `replies.created`
167
+ * echo. Mirrors the comment-create local-emit pattern. */
168
+ async reply(commentId, bodyMd, guestName) {
169
+ const created = await this.api.call('replies.create', { commentId, bodyMd, guestName });
170
+ this.applyLocalReplyCreate(created);
171
+ return created;
172
+ }
173
+ /** Edit a reply + repaint locally. */
174
+ async updateReply(replyId, bodyMd) {
175
+ const updated = await this.api.call('replies.update', { replyId, bodyMd });
176
+ this.applyLocalReplyUpdate(updated);
177
+ return updated;
178
+ }
179
+ /** Delete a reply + drop it from the cached list locally. `replyId` is
180
+ * all we have, so walk every cached comment's reply list to find it. */
181
+ async deleteReply(replyId) {
182
+ await this.api.call('replies.delete', { replyId });
183
+ this.applyLocalReplyDelete(replyId);
184
+ }
185
+ /** Append a reply to its comment's cached list (de-duped) + emit
186
+ * `reply:created`. Only mutates if the list was already loaded; an
187
+ * unloaded thread will fetch fresh on next open. */
188
+ applyLocalReplyCreate(reply) {
189
+ const existing = this.repliesByComment.get(reply.commentId);
190
+ if (existing) {
191
+ if (!existing.find((r) => r.id === reply.id))
192
+ existing.push(reply);
193
+ this.repliesByComment.set(reply.commentId, existing);
194
+ }
195
+ this.bus.emit('reply:created', reply);
196
+ }
197
+ /** Replace a reply in place within its comment's cached list + emit
198
+ * `reply:updated`. */
199
+ applyLocalReplyUpdate(reply) {
200
+ const existing = this.repliesByComment.get(reply.commentId);
201
+ if (existing) {
202
+ const idx = existing.findIndex((r) => r.id === reply.id);
203
+ if (idx >= 0)
204
+ existing[idx] = reply;
205
+ this.repliesByComment.set(reply.commentId, existing);
206
+ }
207
+ this.bus.emit('reply:updated', reply);
208
+ }
209
+ /** Remove a reply from whichever comment bucket holds it + emit
210
+ * `reply:deleted` with the owning commentId so the sidebar can target
211
+ * the right thread. */
212
+ applyLocalReplyDelete(replyId) {
213
+ let commentId = null;
214
+ for (const [cid, list] of this.repliesByComment) {
215
+ if (list.some((r) => r.id === replyId)) {
216
+ commentId = cid;
217
+ this.repliesByComment.set(cid, list.filter((r) => r.id !== replyId));
218
+ break;
219
+ }
220
+ }
221
+ this.bus.emit('reply:deleted', { id: replyId, commentId });
222
+ }
223
+ /** Cached replies for a comment (or `null` if never loaded). */
224
+ loadedReplies(commentId) {
225
+ return this.repliesByComment.get(commentId) ?? null;
226
+ }
227
+ /** Force-fetch replies for a comment from the server. */
228
+ async fetchReplies(commentId) {
229
+ const replies = await this.api.call('replies.list', { commentId });
230
+ this.repliesByComment.set(commentId, replies);
231
+ return replies;
232
+ }
233
+ /** Cached reactions for a comment or reply (may be stale; use `fetchReactions` to refresh). */
234
+ loadedReactions(targetKind, targetId) {
235
+ return this.reactionsByTarget.get(`${targetKind}:${targetId}`) ?? [];
236
+ }
237
+ async fetchReactions(targetKind, targetId) {
238
+ const reactions = await this.api.call('reactions.list', { targetKind, targetId });
239
+ this.reactionsByTarget.set(`${targetKind}:${targetId}`, reactions);
240
+ return reactions;
241
+ }
242
+ /**
243
+ * Toggle a reaction on a comment or reply. Returns the new reaction list
244
+ * so callers can update UI without waiting for the WS broadcast (the
245
+ * event also fires asynchronously).
246
+ */
247
+ async toggleReaction(targetKind, targetId, kind) {
248
+ const result = await this.api.call('reactions.toggle', { targetKind, targetId, kind });
249
+ this.reactionsByTarget.set(`${targetKind}:${targetId}`, result.reactions);
250
+ // Immediately notify listeners (renderer / chat panel) so the
251
+ // local tab repaints without waiting for the WS broadcast echo.
252
+ // The WS path also feeds this same channel — duplicate events
253
+ // are cheap (listeners just re-render with the same cached
254
+ // data) and the local emit guarantees the user who toggled
255
+ // sees their pill update.
256
+ this.bus.emit('reaction:changed', {
257
+ targetKind,
258
+ targetId,
259
+ reactions: result.reactions,
260
+ });
261
+ return result.reactions;
262
+ }
263
+ /** Bulk-clear all comments on a page (editor+ only). Locally drops
264
+ * the cached list + fires `comment:bulkDeleted` so the renderer
265
+ * repaints without waiting for a (currently-unrouted) WS echo. */
266
+ async bulkDeleteForPage(trackedPageId) {
267
+ const result = await this.api.call('comments.bulkDelete', { trackedPageId });
268
+ this.byPage.set(trackedPageId, []);
269
+ this.bus.emit('comment:bulkDeleted', { trackedPageId, deleted: result.deleted });
270
+ return result;
271
+ }
272
+ /** Replace the cached list (used from bootstrap). */
273
+ prime(pageId, comments) {
274
+ this.byPage.set(pageId, comments);
275
+ }
276
+ }
277
+ //# sourceMappingURL=CommentEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentEngine.js","sourceRoot":"","sources":["../src/CommentEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ import type { ServerEvent } from '@pageloop/shared';
2
+ export type ClientEventName = 'page:change' | 'page:resolved' | 'comments:loaded' | 'comment:created' | 'comment:updated' | 'comment:deleted' | 'comment:bulkDeleted' | 'reply:created' | 'reply:updated' | 'reply:deleted' | 'reaction:changed' | 'auth:changed' | 'project:updated' | 'connection:open' | 'connection:closed'
3
+ /** Emitted by ApiClient between a dropped WebSocket and the next dial
4
+ * attempt while exponential-backoff reconnect is in flight. Drives the
5
+ * 3-state connection pill (CR-8): connected / reconnecting / offline. */
6
+ | 'connection:reconnecting' | 'sidebar:toggle'
7
+ /** UI-only request from an empty-state CTA to start the element picker
8
+ * (CR-6). Renderer listens + toggles its picker; keeps the sidebar
9
+ * tab decoupled from the picker instance. */
10
+ | 'sidebar:request-pick' | 'bubbles:toggle'
11
+ /** User changed their bubble colour from the widget settings panel.
12
+ * Renderers re-tint the bubble CSS vars live; PageLoop updates its
13
+ * cached `bubbleColor()` so reopening the panel shows the new value.
14
+ * `color: null` clears back to the stylesheet default. */
15
+ | 'bubbles:recolor' | 'toast:show'
16
+ /** Structured error channel — every catch site that surfaces a user-
17
+ * visible failure should also emit this so embedders can wire it to
18
+ * their own observability layer. The toast is the user-facing
19
+ * artifact; `pl-error` is the machine-readable artifact. */
20
+ | 'pl-error'
21
+ /** Server-driven config changed (bootstrap revalidate or live admin
22
+ * edit). Renderers re-apply cheap knobs like theme; layout knobs
23
+ * take effect on the next reload. */
24
+ | 'config:changed'
25
+ /** Live-chat manager → renderer events. Payloads are intentionally
26
+ * `unknown` at the bus level (rich types live on ChatManager); the
27
+ * renderer casts at the listener site. */
28
+ | 'chat:room-changed' | 'chat:history-loaded' | 'chat:message-appended' | 'chat:presence-updated' | 'chat:settings-changed' | 'chat:known-rooms-changed' | 'chat:current-page-drift' | 'chat:typing-updated' | 'chat:older-loading' | 'chat:older-loaded' | 'chat:read-state-updated'
29
+ /** Fired by the bubble layer at the end of every anchor-resolve
30
+ * pass. Payload: `{ ids: Set<string> }` listing comments whose
31
+ * anchor couldn't be matched against the current DOM. Sidebar
32
+ * renderers subscribe to mark those comment rows as "no anchor". */
33
+ | 'bubbles:dangling' | ServerEvent['name'];
34
+ /**
35
+ * Payload of the `pl-error` bus event. Mirrors a typical observability
36
+ * sink shape (a `captureException`-style report) so embedders don't
37
+ * need to translate.
38
+ */
39
+ export interface PageLoopErrorPayload {
40
+ /** Where the error happened — short, stable identifier. Stable
41
+ * enough to use as an error-grouping fingerprint (e.g. `comment.create`,
42
+ * `reply.delete`, `versions.list`). */
43
+ operation: string;
44
+ /** Coarse classification — lets consumers route or filter. */
45
+ code?: 'network' | 'auth' | 'validation' | 'plan-limit' | 'rate-limit' | 'unknown';
46
+ /** Human-readable summary, safe to display to end users. */
47
+ message: string;
48
+ /** Underlying error object if available — preserved for stack
49
+ * traces / native message. */
50
+ cause?: unknown;
51
+ /** Free-form metadata. Avoid stuffing PII; this WILL ship to
52
+ * whatever sink the embedder configures. */
53
+ extra?: Record<string, unknown>;
54
+ }
55
+ export type ClientEventHandler = (payload: any) => void;
56
+ /**
57
+ * Type-level map from event name → payload shape. Adding an entry here
58
+ * gives every `bus.on(name, ...)` / `bus.emit(name, ...)` callsite
59
+ * strong static typing on the payload. Events not listed default to
60
+ * `unknown` so the bus stays back-compatible with the existing
61
+ * dozens of ad-hoc emitters — new emitters get safety, old ones
62
+ * still compile.
63
+ *
64
+ * Goal is to migrate every entry in `ClientEventName` here over time;
65
+ * this map is the foundation for that.
66
+ */
67
+ export interface EventMap {
68
+ 'connection:open': {
69
+ transport: 'ws' | 'sse' | 'rest';
70
+ };
71
+ 'connection:closed': {
72
+ transport?: 'ws' | 'sse' | 'rest';
73
+ reason?: string;
74
+ };
75
+ /** `attempt` is 1-based; `delayMs` is the backoff window before the
76
+ * next dial. Lets a UI surface a "retrying in Ns" affordance. */
77
+ 'connection:reconnecting': {
78
+ attempt: number;
79
+ delayMs: number;
80
+ };
81
+ 'sidebar:toggle': {
82
+ visible: boolean;
83
+ };
84
+ 'bubbles:toggle': {
85
+ visible: boolean;
86
+ };
87
+ 'bubbles:recolor': {
88
+ color: string | null;
89
+ };
90
+ 'toast:show': {
91
+ kind: 'success' | 'error' | 'info' | 'warn';
92
+ message: string;
93
+ title?: string;
94
+ };
95
+ 'pl-error': PageLoopErrorPayload;
96
+ 'page:change': {
97
+ url: string;
98
+ key: string;
99
+ };
100
+ }
101
+ /** Resolve a payload type for an event name — strict when known,
102
+ * `unknown` for ad-hoc events. */
103
+ export type PayloadOf<K extends string> = K extends keyof EventMap ? EventMap[K] : unknown;
104
+ /**
105
+ * Tiny pub/sub for cross-module communication on the client. Server-emitted
106
+ * events arrive via the ApiClient and are re-emitted here so UI modules can
107
+ * subscribe without knowing about the transport.
108
+ *
109
+ * Two overloaded call signatures:
110
+ * - Typed: `bus.on('connection:open', e => e.transport)` — `e` is
111
+ * `EventMap['connection:open']`.
112
+ * - Untyped: `bus.on('something:custom', e => ...)` — `e` is
113
+ * `unknown`; cast at the use site or add the event to `EventMap`.
114
+ */
115
+ export declare class EventBus {
116
+ private readonly listeners;
117
+ on<K extends keyof EventMap>(name: K, handler: (payload: EventMap[K]) => void): () => void;
118
+ on(name: ClientEventName, handler: ClientEventHandler): () => void;
119
+ emit<K extends keyof EventMap>(name: K, payload: EventMap[K]): void;
120
+ emit(name: ClientEventName, payload: unknown): void;
121
+ }
122
+ //# sourceMappingURL=EventBus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventBus.d.ts","sourceRoot":"","sources":["../src/EventBus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Tiny pub/sub for cross-module communication on the client. Server-emitted
3
+ * events arrive via the ApiClient and are re-emitted here so UI modules can
4
+ * subscribe without knowing about the transport.
5
+ *
6
+ * Two overloaded call signatures:
7
+ * - Typed: `bus.on('connection:open', e => e.transport)` — `e` is
8
+ * `EventMap['connection:open']`.
9
+ * - Untyped: `bus.on('something:custom', e => ...)` — `e` is
10
+ * `unknown`; cast at the use site or add the event to `EventMap`.
11
+ */
12
+ export class EventBus {
13
+ listeners = new Map();
14
+ on(name, handler) {
15
+ const set = this.listeners.get(name) ?? new Set();
16
+ set.add(handler);
17
+ this.listeners.set(name, set);
18
+ return () => set.delete(handler);
19
+ }
20
+ emit(name, payload) {
21
+ const set = this.listeners.get(name);
22
+ if (!set)
23
+ return;
24
+ for (const fn of set) {
25
+ try {
26
+ fn(payload);
27
+ }
28
+ catch {
29
+ // listener errors must not break the bus
30
+ }
31
+ }
32
+ }
33
+ }
34
+ //# sourceMappingURL=EventBus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventBus.js","sourceRoot":"","sources":["../src/EventBus.ts"],"names":[],"mappings":"AA4HA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,QAAQ;IACH,SAAS,GAAG,IAAI,GAAG,EAAmC,CAAC;IAIxE,EAAE,CAAC,IAAY,EAAE,OAA2B;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;QAClD,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACjB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC9B,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAID,IAAI,CAAC,IAAY,EAAE,OAAgB;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC;gBACJ,EAAE,CAAC,OAAO,CAAC,CAAC;YACb,CAAC;YAAC,MAAM,CAAC;gBACR,yCAAyC;YAC1C,CAAC;QACF,CAAC;IACF,CAAC;CACD"}
@@ -0,0 +1,22 @@
1
+ import type { Comment, TrackedPage } from '@pageloop/shared';
2
+ /**
3
+ * IndexedDB-backed read-through cache for pages and comments. The SDK consults
4
+ * the cache before hitting the network so cold reloads of the same page show
5
+ * comments instantly while a fresh fetch reconciles in the background.
6
+ *
7
+ * Keys are namespaced by projectId so a single browser handling multiple
8
+ * PageLoop deployments stays isolated. Entries carry a `cachedAt` timestamp;
9
+ * the SDK doesn't TTL them — cache is invalidated by server events instead.
10
+ */
11
+ export declare class IdbCache {
12
+ private dbPromise;
13
+ private dbName;
14
+ constructor(projectId: string);
15
+ private db;
16
+ getPage(key: string): Promise<TrackedPage | null>;
17
+ putPage(page: TrackedPage): Promise<void>;
18
+ getComments(pageId: string): Promise<Comment[] | null>;
19
+ putComments(pageId: string, comments: Comment[]): Promise<void>;
20
+ clear(): Promise<void>;
21
+ }
22
+ //# sourceMappingURL=IdbCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IdbCache.d.ts","sourceRoot":"","sources":["../src/IdbCache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE7D;;;;;;;;GAQG;AACH,qBAAa,QAAQ;IACpB,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,MAAM,CAAS;gBAEX,SAAS,EAAE,MAAM;IAI7B,OAAO,CAAC,EAAE;IAcJ,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAUjD,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IASzC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;IAUtD,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAS5B"}
@@ -0,0 +1,79 @@
1
+ import { openDB } from 'idb';
2
+ /**
3
+ * IndexedDB-backed read-through cache for pages and comments. The SDK consults
4
+ * the cache before hitting the network so cold reloads of the same page show
5
+ * comments instantly while a fresh fetch reconciles in the background.
6
+ *
7
+ * Keys are namespaced by projectId so a single browser handling multiple
8
+ * PageLoop deployments stays isolated. Entries carry a `cachedAt` timestamp;
9
+ * the SDK doesn't TTL them — cache is invalidated by server events instead.
10
+ */
11
+ export class IdbCache {
12
+ dbPromise = null;
13
+ dbName;
14
+ constructor(projectId) {
15
+ this.dbName = `pageloop:${projectId}`;
16
+ }
17
+ db() {
18
+ if (!this.dbPromise) {
19
+ this.dbPromise = openDB(this.dbName, 1, {
20
+ upgrade(db) {
21
+ if (!db.objectStoreNames.contains('pages'))
22
+ db.createObjectStore('pages', { keyPath: 'key' });
23
+ if (!db.objectStoreNames.contains('comments'))
24
+ db.createObjectStore('comments', { keyPath: 'pageId' });
25
+ },
26
+ });
27
+ }
28
+ return this.dbPromise;
29
+ }
30
+ async getPage(key) {
31
+ try {
32
+ const db = await this.db();
33
+ const row = await db.get('pages', key);
34
+ return row?.page ?? null;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ async putPage(page) {
41
+ try {
42
+ const db = await this.db();
43
+ await db.put('pages', { key: page.key, page, cachedAt: Date.now() });
44
+ }
45
+ catch {
46
+ // IDB unavailable (private mode, quota); silent fall-through.
47
+ }
48
+ }
49
+ async getComments(pageId) {
50
+ try {
51
+ const db = await this.db();
52
+ const row = await db.get('comments', pageId);
53
+ return row?.comments ?? null;
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ async putComments(pageId, comments) {
60
+ try {
61
+ const db = await this.db();
62
+ await db.put('comments', { pageId, comments, cachedAt: Date.now() });
63
+ }
64
+ catch {
65
+ // ignore
66
+ }
67
+ }
68
+ async clear() {
69
+ try {
70
+ const db = await this.db();
71
+ await db.clear('pages');
72
+ await db.clear('comments');
73
+ }
74
+ catch {
75
+ // ignore
76
+ }
77
+ }
78
+ }
79
+ //# sourceMappingURL=IdbCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IdbCache.js","sourceRoot":"","sources":["../src/IdbCache.ts"],"names":[],"mappings":"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"}