svelte-realtime 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -647,7 +647,7 @@ const [board, column] = await batch(() => [
647
647
  ], { sequential: true });
648
648
  ```
649
649
 
650
- Each call resolves or rejects independently -- one failure does not cancel the others. Batches are limited to 50 calls per frame.
650
+ Each call resolves or rejects independently -- one failure does not cancel the others. Batches are limited to 50 calls -- enforced both client-side (rejects before sending) and server-side.
651
651
 
652
652
  ---
653
653
 
@@ -1025,6 +1025,21 @@ export const stats = live.stream('stats', async (ctx) => {
1025
1025
  }, { merge: 'set' });
1026
1026
  ```
1027
1027
 
1028
+ The function receives a `ctx` object with `publish`, `throttle`, `debounce`, and `signal` -- the same helpers available in RPC handlers (minus `user` and `ws`, since cron runs outside a connection). Use `ctx.publish` for fine-grained control, e.g. publishing individual `created`/`deleted` events on a crud stream:
1029
+
1030
+ ```js
1031
+ export const cleanup = live.cron('0 * * * *', 'boards', async (ctx) => {
1032
+ const stale = await listStaleBoards();
1033
+ for (const board of stale) {
1034
+ await deleteBoard(board.board_id);
1035
+ ctx.publish('boards', 'deleted', { board_id: board.board_id });
1036
+ }
1037
+ // returning undefined skips the automatic 'set' publish
1038
+ });
1039
+ ```
1040
+
1041
+ If the function returns a value, it is published as a `set` event (same as before). If it returns `undefined`, no automatic publish happens -- this lets you use `ctx.publish` exclusively without an unwanted `set` event overwriting your crud updates.
1042
+
1028
1043
  Cron expressions use 5 fields: `minute hour day month weekday`. Supported syntax: `*`, single values, ranges (`9-17`), lists (`0,15,30`), and steps (`*/5`).
1029
1044
 
1030
1045
  The platform is captured automatically from the first RPC call. If your app starts cron jobs before any WebSocket connections, call `setCronPlatform(platform)` in your `open` hook.
@@ -1511,7 +1526,7 @@ The adapter's `sendQueued()` drops the oldest item if the queue exceeds 1000. Un
1511
1526
 
1512
1527
  ### Batch size (max 50 calls)
1513
1528
 
1514
- A single `batch()` call is limited to 50 RPC calls. If exceeded, the server rejects the entire batch. Split into multiple `batch()` calls if you need more.
1529
+ A single `batch()` call is limited to 50 RPC calls. The client rejects before sending if the limit is exceeded, and the server enforces the same limit as a safety net. Split into multiple `batch()` calls if you need more.
1515
1530
 
1516
1531
  ### ws.subscribe() vs the subscribe hook
1517
1532
 
@@ -1773,6 +1788,14 @@ What gets measured:
1773
1788
 
1774
1789
  Merge strategies use an internal `Map<key, index>` for O(1) lookups instead of linear scans. Updates and upserts on keyed strategies (crud, presence, cursor) are constant-time regardless of array size. Deletes and prepends require an index rebuild (linear), which matches the cost of the delete itself.
1775
1790
 
1791
+ ### Event batching (browser)
1792
+
1793
+ In the browser, incoming pub/sub events are queued and flushed once per `requestAnimationFrame` instead of triggering a Svelte store update per event. This is automatic -- no configuration needed.
1794
+
1795
+ With high-frequency streams (e.g. 1000 cursors at 20 updates/sec), this reduces reactive store updates from ~20,000/sec to ~60/sec (one per frame). All merge operations still run, but Svelte only diffs and re-renders once per frame.
1796
+
1797
+ In Node/SSR (tests, `__directCall`, etc.), events apply synchronously -- no batching overhead.
1798
+
1776
1799
  These benchmarks run in-process with mock objects (no real network). They isolate the framework overhead from network latency. See [bench/rpc.js](bench/rpc.js) for the full source.
1777
1800
 
1778
1801
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-realtime",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Realtime RPC and reactive subscriptions for SvelteKit, built on svelte-adapter-uws",
5
5
  "author": "Kevin Radziszewski",
6
6
  "license": "MIT",
package/server.d.ts CHANGED
@@ -1,5 +1,22 @@
1
1
  import type { Platform, WebSocket } from 'svelte-adapter-uws';
2
2
 
3
+ /**
4
+ * Context passed to `live.cron()` functions.
5
+ * No `user` or `ws` since cron jobs run outside a connection.
6
+ */
7
+ export interface CronContext {
8
+ /** The platform API (publish, send, topic helpers). */
9
+ platform: Platform;
10
+ /** Shorthand for `platform.publish` -- delegates to whatever platform was passed in. */
11
+ publish: Platform['publish'];
12
+ /** Throttled publish -- sends at most once per `ms` milliseconds. */
13
+ throttle(topic: string, event: string, data: any, ms: number): void;
14
+ /** Debounced publish -- sends after `ms` milliseconds of silence. */
15
+ debounce(topic: string, event: string, data: any, ms: number): void;
16
+ /** Send a point-to-point signal to a specific user. */
17
+ signal(userId: string, event: string, data: any): void;
18
+ }
19
+
3
20
  /**
4
21
  * Context passed to every `live()` and `live.stream()` function.
5
22
  */
@@ -372,18 +389,32 @@ export namespace live {
372
389
  /**
373
390
  * Create a server-side scheduled function that publishes to a topic on a cron schedule.
374
391
  *
392
+ * The function receives a `ctx` object with `publish`, `throttle`, `debounce`, and `signal`.
393
+ * If the function returns a value, it is published as a `set` event on the topic.
394
+ * If the function returns `undefined`, no automatic publish happens (use `ctx.publish` instead).
395
+ *
375
396
  * @param schedule - Cron expression (5 fields: minute hour day month weekday)
376
397
  * @param topic - Topic to publish results to
377
398
  * @param fn - Async function to run on schedule
378
399
  *
379
400
  * @example
380
401
  * ```js
402
+ * // Return a value -- published as 'set' automatically
381
403
  * export const refreshStats = live.cron('*\/5 * * * *', 'stats', async () => {
382
404
  * return db.stats();
383
405
  * });
406
+ *
407
+ * // Use ctx.publish for fine-grained control (e.g. crud streams)
408
+ * export const cleanup = live.cron('0 * * * *', 'boards', async (ctx) => {
409
+ * const stale = await listStaleBoards();
410
+ * for (const board of stale) {
411
+ * await deleteBoard(board.board_id);
412
+ * ctx.publish('boards', 'deleted', { board_id: board.board_id });
413
+ * }
414
+ * });
384
415
  * ```
385
416
  */
386
- function cron<T extends () => any>(
417
+ function cron<T extends ((ctx: CronContext) => any) | (() => any)>(
387
418
  schedule: string,
388
419
  topic: string,
389
420
  fn: T
@@ -773,6 +804,11 @@ export function _activateDerived(platform: Platform): void;
773
804
  */
774
805
  export function _clearCron(): void;
775
806
 
807
+ /**
808
+ * Run all matching cron jobs for the current minute. Exported for testing.
809
+ */
810
+ export function _tickCron(): Promise<void>;
811
+
776
812
  /**
777
813
  * Set a global error handler for cron job failures.
778
814
  * Without this, cron errors are logged in dev and silently swallowed in production.
package/server.js CHANGED
@@ -1270,7 +1270,7 @@ export function _restoreHmr(snap) {
1270
1270
  }
1271
1271
  }
1272
1272
 
1273
- async function _tickCron() {
1273
+ export async function _tickCron() {
1274
1274
  if (_lazyQueue.length) await _resolveAllLazy();
1275
1275
  const now = new Date();
1276
1276
  const minute = now.getMinutes();
@@ -1296,8 +1296,18 @@ async function _tickCron() {
1296
1296
  }
1297
1297
  return;
1298
1298
  }
1299
- const result = await entry.fn();
1300
- _cronPlatform.publish(entry.topic, 'set', result);
1299
+ const _h = _getCtxHelpers(_cronPlatform);
1300
+ const ctx = {
1301
+ platform: _cronPlatform,
1302
+ publish: _h.publish,
1303
+ throttle: _h.throttle,
1304
+ debounce: _h.debounce,
1305
+ signal: _h.signal
1306
+ };
1307
+ const result = await entry.fn(ctx);
1308
+ if (result !== undefined) {
1309
+ _cronPlatform.publish(entry.topic, 'set', result);
1310
+ }
1301
1311
  } catch (err) {
1302
1312
  if (_cronErrorHandler) {
1303
1313
  _cronErrorHandler(path, err);