svelte-realtime 0.1.4

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/server.d.ts ADDED
@@ -0,0 +1,815 @@
1
+ import type { Platform, WebSocket } from 'svelte-adapter-uws';
2
+
3
+ /**
4
+ * Context passed to every `live()` and `live.stream()` function.
5
+ */
6
+ export interface LiveContext<UserData = unknown> {
7
+ /** User data attached during the WebSocket upgrade handshake. */
8
+ user: UserData;
9
+ /** The raw WebSocket connection. */
10
+ ws: WebSocket<UserData>;
11
+ /** The platform API (publish, send, topic helpers). */
12
+ platform: Platform;
13
+ /** Shorthand for `platform.publish` -- delegates to whatever platform was passed in. */
14
+ publish: Platform['publish'];
15
+ /** Cursor value sent by the client for paginated stream requests. `null` if not paginated. */
16
+ cursor: any;
17
+ /** Throttled publish -- sends at most once per `ms` milliseconds. */
18
+ throttle(topic: string, event: string, data: any, ms: number): void;
19
+ /** Debounced publish -- sends after `ms` milliseconds of silence. */
20
+ debounce(topic: string, event: string, data: any, ms: number): void;
21
+ /** Send a point-to-point signal to a specific user. */
22
+ signal(userId: string, event: string, data: any): void;
23
+ }
24
+
25
+ /**
26
+ * Options for `live.stream()`.
27
+ */
28
+ export interface StreamOptions {
29
+ /**
30
+ * Merge strategy for live updates.
31
+ * - `'crud'` -- append/update/delete by key (default)
32
+ * - `'latest'` -- ring buffer of last N events
33
+ * - `'set'` -- replace entire value
34
+ * - `'presence'` -- presence join/leave tracking by key
35
+ * - `'cursor'` -- cursor position tracking by key
36
+ * @default 'crud'
37
+ */
38
+ merge?: 'crud' | 'latest' | 'set' | 'presence' | 'cursor';
39
+
40
+ /**
41
+ * Key field for `crud` merge mode.
42
+ * @default 'id'
43
+ */
44
+ key?: string;
45
+
46
+ /**
47
+ * Prepend new items instead of appending (crud mode only).
48
+ * @default false
49
+ */
50
+ prepend?: boolean;
51
+
52
+ /**
53
+ * Maximum items to keep (latest mode only).
54
+ * @default 50
55
+ */
56
+ max?: number;
57
+
58
+ /**
59
+ * Enable seq-based replay for gap-free reconnection.
60
+ * Requires the replay extension from svelte-adapter-uws-extensions.
61
+ * @default false
62
+ */
63
+ replay?: boolean | { size?: number };
64
+
65
+ /**
66
+ * Called when a client subscribes to this stream.
67
+ * Receives the context and resolved topic string.
68
+ */
69
+ onSubscribe?(ctx: LiveContext, topic: string): void | Promise<void>;
70
+
71
+ /**
72
+ * Called when a client disconnects from this stream.
73
+ * Fires for both static and dynamic topics.
74
+ */
75
+ onUnsubscribe?(ctx: LiveContext, topic: string): void | Promise<void>;
76
+
77
+ /**
78
+ * Subscribe-time access predicate. Checked once when a client subscribes.
79
+ * Return `false` to deny the subscription with an "Access denied" error.
80
+ * For per-event filtering, use `pipe.filter()`.
81
+ */
82
+ filter?(ctx: LiveContext): boolean;
83
+
84
+ /**
85
+ * Subscribe-time access predicate (alias for `filter`).
86
+ * Use `live.access` helpers to build predicates.
87
+ */
88
+ access?(ctx: LiveContext): boolean;
89
+
90
+ /**
91
+ * Schema version number. Increment when the data shape changes.
92
+ * Clients send their version on reconnect; the server applies migrations if behind.
93
+ */
94
+ version?: number;
95
+
96
+ /**
97
+ * Migration functions keyed by the version to migrate FROM.
98
+ * E.g., `{ 1: (item) => ({ ...item, newField: 'default' }) }` migrates v1 to v2.
99
+ */
100
+ migrate?: Record<number, (item: any) => any>;
101
+
102
+ /**
103
+ * Delta sync configuration for efficient reconnection.
104
+ * When configured, the server sends only changes since the client's last known version.
105
+ */
106
+ delta?: {
107
+ /** Return the current version/hash of the data. Must be fast. */
108
+ version(): any | Promise<any>;
109
+ /** Return only the items that changed since `sinceVersion`. Return null to force full refetch. */
110
+ diff(sinceVersion: any): any[] | Promise<any[] | null> | null;
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Options for `handleRpc()`.
116
+ */
117
+ export interface HandleRpcOptions {
118
+ /**
119
+ * Optional async hook that runs after the guard but before the live function.
120
+ * Throw `LiveError` to reject the call.
121
+ * Use for rate limiting, logging, metrics.
122
+ */
123
+ beforeExecute?(ws: WebSocket<any>, rpcPath: string, args: any[]): Promise<void> | void;
124
+
125
+ /**
126
+ * Called when an RPC handler throws a non-LiveError.
127
+ * Use for error reporting (Sentry, logging, etc.).
128
+ */
129
+ onError?(path: string, error: unknown, ctx: LiveContext): void;
130
+ }
131
+
132
+ /**
133
+ * Options for `createMessage()`.
134
+ */
135
+ export interface CreateMessageOptions {
136
+ /**
137
+ * Transform the platform before passing to `handleRpc`.
138
+ * Use for wrapping with Redis pub/sub bus.
139
+ *
140
+ * @example
141
+ * ```js
142
+ * createMessage({ platform: (p) => bus.wrap(p) })
143
+ * ```
144
+ */
145
+ platform?(platform: Platform): Platform;
146
+
147
+ /**
148
+ * Optional async hook that runs before each RPC call.
149
+ * Throw `LiveError` to reject.
150
+ */
151
+ beforeExecute?(ws: WebSocket<any>, rpcPath: string, args: any[]): Promise<void> | void;
152
+
153
+ /**
154
+ * Called when an RPC handler throws a non-LiveError.
155
+ * Use for error reporting (Sentry, logging, etc.).
156
+ */
157
+ onError?(path: string, error: unknown, ctx: LiveContext): void;
158
+
159
+ /**
160
+ * Called when a message is not an RPC request.
161
+ * Use for mixing RPC with custom message handling.
162
+ */
163
+ onUnhandled?(ws: WebSocket<any>, data: ArrayBuffer, platform: Platform): void;
164
+ }
165
+
166
+ /**
167
+ * Mark a function as RPC-callable over WebSocket.
168
+ *
169
+ * The first argument is always `ctx: LiveContext`. Additional arguments
170
+ * come from the client call.
171
+ *
172
+ * @example
173
+ * ```js
174
+ * export const sendMessage = live(async (ctx, text) => {
175
+ * const msg = await db.messages.insert({ userId: ctx.user.id, text });
176
+ * ctx.publish('messages', 'created', msg);
177
+ * return msg;
178
+ * });
179
+ * ```
180
+ */
181
+ export function live<T extends (ctx: LiveContext, ...args: any[]) => any>(fn: T): T;
182
+
183
+ export namespace live {
184
+ /**
185
+ * Mark a function as a stream provider with a static topic.
186
+ *
187
+ * @param topic - Pub/sub topic name
188
+ * @param initFn - Function that returns the initial data
189
+ * @param options - Merge strategy and options
190
+ *
191
+ * @example
192
+ * ```js
193
+ * export const messages = live.stream('messages', async (ctx) => {
194
+ * return db.messages.latest(50);
195
+ * }, { merge: 'crud', key: 'id', prepend: true });
196
+ * ```
197
+ */
198
+ function stream<T extends (ctx: LiveContext, ...args: any[]) => any>(
199
+ topic: string,
200
+ initFn: T,
201
+ options?: StreamOptions
202
+ ): T;
203
+
204
+ /**
205
+ * Mark a function as a stream provider with a dynamic topic.
206
+ *
207
+ * The topic function receives the same context and arguments as the init function,
208
+ * enabling per-entity streams (e.g., per-room, per-user).
209
+ *
210
+ * @param topicFn - Function that computes the topic from context and arguments
211
+ * @param initFn - Function that returns the initial data
212
+ * @param options - Merge strategy and options
213
+ *
214
+ * @example
215
+ * ```js
216
+ * export const roomMessages = live.stream(
217
+ * (ctx, roomId) => 'chat:' + roomId,
218
+ * async (ctx, roomId) => db.messages.forRoom(roomId),
219
+ * { merge: 'crud', key: 'id' }
220
+ * );
221
+ * ```
222
+ */
223
+ function stream<T extends (ctx: LiveContext, ...args: any[]) => any>(
224
+ topicFn: (ctx: LiveContext, ...args: any[]) => string,
225
+ initFn: T,
226
+ options?: StreamOptions
227
+ ): T;
228
+
229
+ /**
230
+ * Create an ephemeral pub/sub channel with no database initialization.
231
+ * Channels have no initFn -- clients subscribe and receive live events immediately.
232
+ *
233
+ * @param topic - Static topic string
234
+ * @param options - Merge strategy and options
235
+ *
236
+ * @example
237
+ * ```js
238
+ * export const typing = live.channel('typing:lobby', { merge: 'presence' });
239
+ * ```
240
+ */
241
+ function channel(
242
+ topic: string,
243
+ options?: { merge?: 'crud' | 'latest' | 'set' | 'presence' | 'cursor'; key?: string; max?: number }
244
+ ): Function;
245
+
246
+ /**
247
+ * Create an ephemeral pub/sub channel with a dynamic topic.
248
+ *
249
+ * @param topicFn - Function that computes the topic
250
+ * @param options - Merge strategy and options
251
+ *
252
+ * @example
253
+ * ```js
254
+ * export const cursors = live.channel(
255
+ * (ctx, docId) => 'cursors:' + docId,
256
+ * { merge: 'cursor' }
257
+ * );
258
+ * ```
259
+ */
260
+ function channel(
261
+ topicFn: (ctx: LiveContext, ...args: any[]) => string,
262
+ options?: { merge?: 'crud' | 'latest' | 'set' | 'presence' | 'cursor'; key?: string; max?: number }
263
+ ): Function;
264
+
265
+ /**
266
+ * Mark a function as a binary RPC handler.
267
+ * The handler receives `(ctx, buffer, ...jsonArgs)` where buffer is the raw ArrayBuffer.
268
+ *
269
+ * @param fn - Handler function (ctx, buffer, ...jsonArgs)
270
+ *
271
+ * @example
272
+ * ```js
273
+ * export const uploadAvatar = live.binary(async (ctx, buffer, filename) => {
274
+ * await storage.put(filename, buffer);
275
+ * return { url: `/avatars/${filename}` };
276
+ * });
277
+ * ```
278
+ */
279
+ function binary<T extends (ctx: LiveContext, buffer: ArrayBuffer, ...args: any[]) => any>(
280
+ fn: T
281
+ ): T;
282
+
283
+ /**
284
+ * Register a global middleware that runs before per-module guards for every RPC/stream call.
285
+ * Middleware receives `(ctx, next)` -- call `next()` to continue the chain.
286
+ * Throw a LiveError to reject the call.
287
+ *
288
+ * @param fn - Middleware function
289
+ *
290
+ * @example
291
+ * ```js
292
+ * live.middleware(async (ctx, next) => {
293
+ * const start = Date.now();
294
+ * const result = await next();
295
+ * console.log(`RPC took ${Date.now() - start}ms`);
296
+ * return result;
297
+ * });
298
+ * ```
299
+ */
300
+ function middleware(fn: (ctx: LiveContext, next: () => Promise<any>) => Promise<any>): void;
301
+
302
+ /**
303
+ * Wrap a stream with a server-side gate predicate.
304
+ * If the predicate returns false, the client receives a graceful no-op
305
+ * (`{ data: null, gated: true }`) instead of an error.
306
+ *
307
+ * @param predicate - Synchronous function checked before subscribing
308
+ * @param fn - The stream function to gate
309
+ *
310
+ * @example
311
+ * ```js
312
+ * export const betaFeed = live.gate(
313
+ * (ctx) => ctx.user?.flags?.includes('beta'),
314
+ * live.stream('beta-feed', async (ctx) => db.betaFeed.latest(50))
315
+ * );
316
+ * ```
317
+ */
318
+ function gate<T extends Function>(
319
+ predicate: (ctx: LiveContext, ...args: any[]) => boolean,
320
+ fn: T
321
+ ): T;
322
+
323
+ /**
324
+ * Declarative per-function rate limiting.
325
+ * Wraps a live() function with a sliding window rate limiter.
326
+ *
327
+ * @param config - Rate limit configuration
328
+ * @param fn - Handler function (ctx, ...args)
329
+ *
330
+ * @example
331
+ * ```js
332
+ * export const sendMessage = live.rateLimit({ points: 5, window: 10000 }, async (ctx, text) => {
333
+ * const msg = await db.messages.insert({ userId: ctx.user.id, text });
334
+ * ctx.publish('messages', 'created', msg);
335
+ * return msg;
336
+ * });
337
+ * ```
338
+ */
339
+ function rateLimit<T extends (ctx: LiveContext, ...args: any[]) => any>(
340
+ config: {
341
+ /** Maximum number of calls allowed within the window. */
342
+ points: number;
343
+ /** Time window in milliseconds. */
344
+ window: number;
345
+ /** Custom key function. Defaults to `ctx.user.id`. */
346
+ key?(ctx: LiveContext): string;
347
+ },
348
+ fn: T
349
+ ): T;
350
+
351
+ /**
352
+ * Mark a function as RPC-callable with schema validation.
353
+ * Validates args[0] against the schema before calling fn.
354
+ * Supports Zod and Valibot schemas.
355
+ *
356
+ * @param schema - Zod or Valibot schema
357
+ * @param fn - Handler function (ctx, validatedInput, ...rest)
358
+ *
359
+ * @example
360
+ * ```js
361
+ * const SendSchema = z.object({ text: z.string().min(1) });
362
+ * export const send = live.validated(SendSchema, async (ctx, input) => {
363
+ * // input is validated and typed
364
+ * });
365
+ * ```
366
+ */
367
+ function validated<S, T extends (ctx: LiveContext, input: any, ...args: any[]) => any>(
368
+ schema: S,
369
+ fn: T
370
+ ): T;
371
+
372
+ /**
373
+ * Create a server-side scheduled function that publishes to a topic on a cron schedule.
374
+ *
375
+ * @param schedule - Cron expression (5 fields: minute hour day month weekday)
376
+ * @param topic - Topic to publish results to
377
+ * @param fn - Async function to run on schedule
378
+ *
379
+ * @example
380
+ * ```js
381
+ * export const refreshStats = live.cron('*\/5 * * * *', 'stats', async () => {
382
+ * return db.stats();
383
+ * });
384
+ * ```
385
+ */
386
+ function cron<T extends () => any>(
387
+ schedule: string,
388
+ topic: string,
389
+ fn: T
390
+ ): T;
391
+
392
+ /**
393
+ * Create a real-time incremental aggregation over a source topic.
394
+ *
395
+ * @param source - Topic to watch
396
+ * @param reducers - Field definitions with init/reduce/compute functions
397
+ * @param options - Output topic, optional snapshot, debounce
398
+ *
399
+ * @example
400
+ * ```js
401
+ * export const orderStats = live.aggregate('orders', {
402
+ * count: { init: () => 0, reduce: (acc, event) => event === 'created' ? acc + 1 : acc },
403
+ * avgValue: { compute: (state) => state.count > 0 ? state.total / state.count : 0 }
404
+ * }, { topic: 'order-stats' });
405
+ * ```
406
+ */
407
+ function aggregate(
408
+ source: string,
409
+ reducers: Record<string, {
410
+ init?(): any;
411
+ reduce?(acc: any, event: string, data: any): any;
412
+ compute?(state: Record<string, any>): any;
413
+ }>,
414
+ options: {
415
+ topic: string;
416
+ snapshot?(): Promise<Record<string, any>>;
417
+ debounce?: number;
418
+ }
419
+ ): Function;
420
+
421
+ /**
422
+ * Create a server-side reactive side effect.
423
+ * Effects fire when source topics publish. Fire-and-forget -- no data, no topic.
424
+ *
425
+ * @param sources - Topic names to watch
426
+ * @param fn - Async function called on each matching publish
427
+ * @param options - Debounce settings
428
+ *
429
+ * @example
430
+ * ```js
431
+ * export const orderNotifications = live.effect(['orders'], async (event, data, platform) => {
432
+ * if (event === 'created') {
433
+ * await email.send(data.userEmail, 'Order confirmed', templates.orderConfirm(data));
434
+ * }
435
+ * });
436
+ * ```
437
+ */
438
+ function effect(
439
+ sources: string[],
440
+ fn: (event: string, data: any, platform: Platform) => void | Promise<void>,
441
+ options?: { debounce?: number }
442
+ ): Function;
443
+
444
+ /**
445
+ * Create a server-side computed stream that recomputes when any source topic publishes.
446
+ *
447
+ * @param sources - Topic names to watch for changes
448
+ * @param fn - Async function that computes the derived value
449
+ * @param options - Merge mode and debounce settings
450
+ *
451
+ * @example
452
+ * ```js
453
+ * export const summary = live.derived(['orders', 'inventory'], async () => {
454
+ * return { totalOrders: await db.orders.count(), totalItems: await db.inventory.count() };
455
+ * });
456
+ * ```
457
+ */
458
+ function derived<T extends () => any>(
459
+ sources: string[],
460
+ fn: T,
461
+ options?: { merge?: string; debounce?: number }
462
+ ): T;
463
+
464
+ /**
465
+ * Create a collaborative room that bundles data stream, presence, cursors, and scoped RPC.
466
+ *
467
+ * @param config - Room configuration
468
+ *
469
+ * @example
470
+ * ```js
471
+ * export const board = live.room({
472
+ * topic: (ctx, boardId) => 'board:' + boardId,
473
+ * init: async (ctx, boardId) => db.boards.get(boardId),
474
+ * presence: (ctx) => ({ name: ctx.user.name }),
475
+ * cursors: true,
476
+ * actions: {
477
+ * addCard: async (ctx, title) => { ... }
478
+ * }
479
+ * });
480
+ * ```
481
+ */
482
+ function room(config: RoomConfig): RoomExport;
483
+
484
+ /**
485
+ * Create a webhook-to-stream bridge.
486
+ * The returned handler can be used in a SvelteKit +server.js POST endpoint.
487
+ *
488
+ * @param topic - Topic to publish events to
489
+ * @param config - Verification and transformation functions
490
+ *
491
+ * @example
492
+ * ```js
493
+ * export const stripeEvents = live.webhook('payments', {
494
+ * verify: ({ body, headers }) => stripe.webhooks.constructEvent(body, headers['stripe-signature'], secret),
495
+ * transform: (event) => ({ event: event.type, data: event.data.object })
496
+ * });
497
+ * ```
498
+ */
499
+ function webhook(topic: string, config: WebhookConfig): WebhookHandler;
500
+
501
+ /**
502
+ * Declarative access control helpers for subscribe-time gating.
503
+ * For per-event filtering, use `pipe.filter()`.
504
+ */
505
+ const access: {
506
+ /** Only allow subscription if `ctx.user[field]` is present. Default field: `'id'`. */
507
+ owner(field?: string): (ctx: LiveContext) => boolean;
508
+ /** Role-based access: map role names to boolean or predicate. */
509
+ role(map: Record<string, true | ((ctx: LiveContext) => boolean)>): (ctx: LiveContext) => boolean;
510
+ /** Only allow subscription if `ctx.user.teamId` is present. */
511
+ team(): (ctx: LiveContext) => boolean;
512
+ /** OR logic: any predicate returning true allows the subscription. */
513
+ any(...predicates: Array<(ctx: LiveContext) => boolean>): (ctx: LiveContext) => boolean;
514
+ /** AND logic: all predicates must return true. */
515
+ all(...predicates: Array<(ctx: LiveContext) => boolean>): (ctx: LiveContext) => boolean;
516
+ };
517
+ }
518
+
519
+ /**
520
+ * Room configuration for `live.room()`.
521
+ */
522
+ export interface RoomConfig {
523
+ /** Function that computes the room topic from context and args. */
524
+ topic: (ctx: LiveContext, ...args: any[]) => string;
525
+ /** Function that returns initial data for the room. */
526
+ init: (ctx: LiveContext, ...args: any[]) => Promise<any>;
527
+ /** Function that returns presence data for the connecting user. */
528
+ presence?: (ctx: LiveContext) => any;
529
+ /** Enable cursor tracking. Pass `true` or `{ throttle: ms }`. */
530
+ cursors?: boolean | { throttle?: number };
531
+ /** Room-scoped RPC actions. */
532
+ actions?: Record<string, (ctx: LiveContext, ...args: any[]) => any>;
533
+ /** Guard function run before data access and actions. */
534
+ guard?: (ctx: LiveContext, ...args: any[]) => void | Promise<void>;
535
+ /** Called when a user joins the room. */
536
+ onJoin?: (ctx: LiveContext, ...args: any[]) => void | Promise<void>;
537
+ /** Called when a user leaves the room. */
538
+ onLeave?: (ctx: LiveContext) => void | Promise<void>;
539
+ /** Merge strategy for the data stream. @default 'crud' */
540
+ merge?: string;
541
+ /** Key field for the data stream. @default 'id' */
542
+ key?: string;
543
+ }
544
+
545
+ /**
546
+ * Return type of `live.room()`.
547
+ */
548
+ export interface RoomExport {
549
+ __isRoom: true;
550
+ __dataStream: any;
551
+ __topicFn: Function;
552
+ __hasPresence: boolean;
553
+ __hasCursors: boolean;
554
+ __presenceStream?: any;
555
+ __cursorStream?: any;
556
+ __actions?: Record<string, any>;
557
+ }
558
+
559
+ /**
560
+ * Webhook configuration for `live.webhook()`.
561
+ */
562
+ export interface WebhookConfig {
563
+ /** Verify the incoming request. Throw to reject. */
564
+ verify(req: { body: string; headers: Record<string, string> }): any;
565
+ /** Transform the verified event. Return null to ignore. */
566
+ transform(event: any): { event: string; data: any } | null;
567
+ }
568
+
569
+ /**
570
+ * Return type of `live.webhook()`.
571
+ */
572
+ export interface WebhookHandler {
573
+ __isWebhook: true;
574
+ /** Handle an incoming webhook request. */
575
+ handle(req: { body: string; headers: Record<string, string>; platform: Platform }): Promise<{ status: number; body?: string }>;
576
+ }
577
+
578
+ /**
579
+ * A stream transform step created by `pipe.filter`, `pipe.sort`, etc.
580
+ */
581
+ export interface PipeTransform {
582
+ transformInit?(data: any[], ctx: LiveContext): any[] | Promise<any[]>;
583
+ transformEvent?(ctx: LiveContext, event: string, data: any): boolean;
584
+ }
585
+
586
+ /**
587
+ * Compose stream transforms that apply to initial data and live events.
588
+ *
589
+ * @param stream - The stream function to wrap
590
+ * @param transforms - Transform steps
591
+ *
592
+ * @example
593
+ * ```js
594
+ * export const notifications = pipe(
595
+ * live.stream('notifications', async (ctx) => db.notifications.all()),
596
+ * pipe.filter((ctx, item) => !item.dismissed),
597
+ * pipe.sort('createdAt', 'desc'),
598
+ * pipe.limit(20)
599
+ * );
600
+ * ```
601
+ */
602
+ export function pipe<T extends Function>(stream: T, ...transforms: PipeTransform[]): T;
603
+ export namespace pipe {
604
+ /** Filter items from initial data and drop non-matching live events. */
605
+ function filter(predicate: (ctx: LiveContext, item: any) => boolean): PipeTransform;
606
+ /** Sort initial data by a field. */
607
+ function sort(field: string, direction?: 'asc' | 'desc'): PipeTransform;
608
+ /** Cap initial data to N items. */
609
+ function limit(n: number): PipeTransform;
610
+ /** Enrich each item by resolving a field via an async function. */
611
+ function join(field: string, resolver: (value: any) => Promise<any>, as: string): PipeTransform;
612
+ }
613
+
614
+ /**
615
+ * Create a per-module guard that runs before every `live()` in the same module.
616
+ *
617
+ * @example
618
+ * ```js
619
+ * export const _guard = guard((ctx) => {
620
+ * if (ctx.user?.role !== 'admin') throw new LiveError('FORBIDDEN', 'Admin only');
621
+ * });
622
+ * ```
623
+ */
624
+ export function guard(
625
+ ...fns: Array<(ctx: LiveContext) => void | Promise<void>>
626
+ ): (ctx: LiveContext) => void | Promise<void>;
627
+
628
+ /**
629
+ * Typed error that propagates `code` and `message` to the client.
630
+ * Use this for expected errors (auth failures, validation, etc.).
631
+ * Raw `Error` throws are caught and replaced with a generic `INTERNAL_ERROR`.
632
+ */
633
+ export class LiveError extends Error {
634
+ code: string;
635
+ constructor(code: string, message?: string);
636
+ }
637
+
638
+ /**
639
+ * Check whether a raw WebSocket message is an RPC request and handle it.
640
+ *
641
+ * Returns `true` if the message was an RPC request (handled), `false` otherwise
642
+ * (pass through to your own logic).
643
+ *
644
+ * @param ws - The WebSocket connection
645
+ * @param data - Raw message data from the adapter's message hook
646
+ * @param platform - The platform API
647
+ * @param options - Optional hooks (beforeExecute)
648
+ *
649
+ * @example
650
+ * ```js
651
+ * export function message(ws, { data, platform }) {
652
+ * if (handleRpc(ws, data, platform)) return;
653
+ * // custom non-RPC handling here
654
+ * }
655
+ * ```
656
+ */
657
+ export function handleRpc(
658
+ ws: WebSocket<any>,
659
+ data: ArrayBuffer,
660
+ platform: Platform,
661
+ options?: HandleRpcOptions
662
+ ): boolean;
663
+
664
+ /**
665
+ * Ready-made message hook for zero-config RPC routing.
666
+ *
667
+ * Signature matches the adapter's `message` hook exactly.
668
+ * Just re-export it from your `hooks.ws.js`.
669
+ *
670
+ * @example
671
+ * ```js
672
+ * export { message } from 'svelte-realtime/server';
673
+ * ```
674
+ */
675
+ export function message(
676
+ ws: WebSocket<any>,
677
+ ctx: { data: ArrayBuffer; isBinary?: boolean; platform: Platform }
678
+ ): void;
679
+
680
+ /**
681
+ * Create a custom message hook with options baked in.
682
+ *
683
+ * @example
684
+ * ```js
685
+ * export const message = createMessage({
686
+ * platform: (p) => bus.wrap(p),
687
+ * async beforeExecute(ws, rpcPath) {
688
+ * const { allowed } = await limiter.consume(ws);
689
+ * if (!allowed) throw new LiveError('RATE_LIMITED', 'Too many requests');
690
+ * }
691
+ * });
692
+ * ```
693
+ */
694
+ export function createMessage(
695
+ options?: CreateMessageOptions
696
+ ): (ws: WebSocket<any>, ctx: { data: ArrayBuffer; isBinary?: boolean; platform: Platform }) => void;
697
+
698
+ /**
699
+ * Execute a live function directly (in-process), without WebSocket.
700
+ * Used by SSR load functions to call live functions server-side.
701
+ *
702
+ * @param path - RPC path (e.g. 'chat/messages')
703
+ * @param args - Arguments to pass (excluding ctx)
704
+ * @param platform - The platform API
705
+ * @param options - Optional user data
706
+ *
707
+ * @internal
708
+ */
709
+ export function __directCall(
710
+ path: string,
711
+ args: any[],
712
+ platform: Platform,
713
+ options?: { user?: any }
714
+ ): Promise<any>;
715
+
716
+ /**
717
+ * Capture a platform reference for cron jobs.
718
+ * Call this in your `open` hook if you use `live.cron()`.
719
+ */
720
+ export function setCronPlatform(platform: Platform): void;
721
+
722
+ /**
723
+ * Register a live function. Called by the Vite-generated registry module.
724
+ * @internal
725
+ */
726
+ export function __register(path: string, fn: Function): void;
727
+
728
+ /**
729
+ * Register a module guard. Called by the Vite-generated registry module.
730
+ * @internal
731
+ */
732
+ export function __registerGuard(modulePath: string, fn: Function): void;
733
+
734
+ /**
735
+ * Register a cron job. Called by the Vite-generated registry module.
736
+ * @internal
737
+ */
738
+ export function __registerCron(path: string, fn: Function): void;
739
+
740
+ /**
741
+ * Register a derived stream. Called by the Vite-generated registry module.
742
+ * @internal
743
+ */
744
+ export function __registerDerived(path: string, fn: Function): void;
745
+
746
+ /**
747
+ * Register an effect. Called by the Vite-generated registry module.
748
+ * @internal
749
+ */
750
+ export function __registerEffect(path: string, fn: Function): void;
751
+
752
+ /**
753
+ * Register an aggregate. Called by the Vite-generated registry module.
754
+ * @internal
755
+ */
756
+ export function __registerAggregate(path: string, fn: Function): void;
757
+
758
+ /**
759
+ * Register room actions lazily. Called by the Vite-generated registry module.
760
+ * @internal
761
+ */
762
+ export function __registerRoomActions(basePath: string, loader: Function): void;
763
+
764
+ /**
765
+ * Activate derived stream listeners after platform is available.
766
+ * Wraps `platform.publish` to detect source topic events and trigger recomputation.
767
+ */
768
+ export function _activateDerived(platform: Platform): void;
769
+
770
+ /**
771
+ * Clear all cron timers and registry entries.
772
+ * Called during HMR to prevent orphan intervals.
773
+ */
774
+ export function _clearCron(): void;
775
+
776
+ /**
777
+ * Set a global error handler for cron job failures.
778
+ * Without this, cron errors are logged in dev and silently swallowed in production.
779
+ *
780
+ * @param handler - Receives the cron path and the thrown error
781
+ *
782
+ * @example
783
+ * ```js
784
+ * onCronError((path, error) => {
785
+ * sentry.captureException(error, { tags: { cron: path } });
786
+ * });
787
+ * ```
788
+ */
789
+ export function onCronError(handler: (path: string, error: unknown) => void): void;
790
+
791
+ /**
792
+ * Handle a WebSocket close event. Fires `onUnsubscribe` lifecycle hooks
793
+ * for stream functions that define them.
794
+ *
795
+ * Re-export from your `hooks.ws.js`:
796
+ * ```js
797
+ * export { close } from 'svelte-realtime/server';
798
+ * ```
799
+ */
800
+ /**
801
+ * Subscribe a WebSocket to its user's signal topic.
802
+ * Call in your `open` hook to enable signal delivery.
803
+ *
804
+ * @example
805
+ * ```js
806
+ * import { enableSignals } from 'svelte-realtime/server';
807
+ * export function open(ws) { enableSignals(ws); }
808
+ * ```
809
+ */
810
+ export function enableSignals(ws: WebSocket<any>, options?: { idField?: string }): void;
811
+
812
+ export function close(
813
+ ws: WebSocket<any>,
814
+ ctx: { platform: Platform }
815
+ ): void;