svelte-realtime 0.1.7 → 0.1.9
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 +17 -0
- package/package.json +1 -1
- package/server.d.ts +37 -1
- package/server.js +13 -3
- package/vite.js +48 -2
package/README.md
CHANGED
|
@@ -75,6 +75,8 @@ export function upgrade({ cookies }) {
|
|
|
75
75
|
|
|
76
76
|
`message` is a ready-made hook that routes incoming WebSocket messages to your live functions. `upgrade` decides who can connect and attaches user data to the connection.
|
|
77
77
|
|
|
78
|
+
> **This file is required.** The Vite plugin will warn at startup if it finds live modules in `src/live/` but no `src/hooks.ws.js` (or `.ts`). Without it, WebSocket messages have nothing on the server side to route them, and all RPC calls will silently time out.
|
|
79
|
+
|
|
78
80
|
### Step 5: Write a server function
|
|
79
81
|
|
|
80
82
|
Create the `src/live/` directory. Every `.js` file in this directory becomes a module of callable server functions.
|
|
@@ -1025,6 +1027,21 @@ export const stats = live.stream('stats', async (ctx) => {
|
|
|
1025
1027
|
}, { merge: 'set' });
|
|
1026
1028
|
```
|
|
1027
1029
|
|
|
1030
|
+
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:
|
|
1031
|
+
|
|
1032
|
+
```js
|
|
1033
|
+
export const cleanup = live.cron('0 * * * *', 'boards', async (ctx) => {
|
|
1034
|
+
const stale = await listStaleBoards();
|
|
1035
|
+
for (const board of stale) {
|
|
1036
|
+
await deleteBoard(board.board_id);
|
|
1037
|
+
ctx.publish('boards', 'deleted', { board_id: board.board_id });
|
|
1038
|
+
}
|
|
1039
|
+
// returning undefined skips the automatic 'set' publish
|
|
1040
|
+
});
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
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.
|
|
1044
|
+
|
|
1028
1045
|
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
1046
|
|
|
1030
1047
|
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.
|
package/package.json
CHANGED
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
|
|
1300
|
-
|
|
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);
|
package/vite.js
CHANGED
|
@@ -76,8 +76,11 @@ export default function svelteRealtime(options) {
|
|
|
76
76
|
console.warn(
|
|
77
77
|
`[svelte-realtime] Plugin loaded but no live modules found in ${dir}/`
|
|
78
78
|
);
|
|
79
|
-
} else
|
|
80
|
-
|
|
79
|
+
} else {
|
|
80
|
+
if (typedImports) {
|
|
81
|
+
_writeTypeDeclarations(liveDir, dir);
|
|
82
|
+
}
|
|
83
|
+
_checkHooksFile(root, liveDir, dir);
|
|
81
84
|
}
|
|
82
85
|
},
|
|
83
86
|
|
|
@@ -940,6 +943,49 @@ function _findLiveFiles(dir) {
|
|
|
940
943
|
return results;
|
|
941
944
|
}
|
|
942
945
|
|
|
946
|
+
/**
|
|
947
|
+
* Check that src/hooks.ws.{js,ts} exists and exports the `message` handler.
|
|
948
|
+
* Warns at build/dev startup if the file is missing or misconfigured.
|
|
949
|
+
* @param {string} root
|
|
950
|
+
* @param {string} liveDir
|
|
951
|
+
* @param {string} dir
|
|
952
|
+
*/
|
|
953
|
+
function _checkHooksFile(root, liveDir, dir) {
|
|
954
|
+
const files = _findLiveFiles(liveDir);
|
|
955
|
+
if (files.length === 0) return;
|
|
956
|
+
|
|
957
|
+
const hooksPath = resolve(root, 'src/hooks.ws');
|
|
958
|
+
const hooksJs = hooksPath + '.js';
|
|
959
|
+
const hooksTs = hooksPath + '.ts';
|
|
960
|
+
const found = existsSync(hooksJs) ? hooksJs : existsSync(hooksTs) ? hooksTs : null;
|
|
961
|
+
|
|
962
|
+
if (!found) {
|
|
963
|
+
console.warn(
|
|
964
|
+
`[svelte-realtime] Found live modules in ${dir}/ but no src/hooks.ws.js -- ` +
|
|
965
|
+
`WebSocket RPC will not work without it.\n` +
|
|
966
|
+
` Create src/hooks.ws.js with at minimum:\n` +
|
|
967
|
+
` export { message } from 'svelte-realtime/server';\n` +
|
|
968
|
+
` export function upgrade() { return {}; }`
|
|
969
|
+
);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
let source;
|
|
974
|
+
try { source = readFileSync(found, 'utf-8'); } catch { return; }
|
|
975
|
+
|
|
976
|
+
const hasMessage = /export\s*\{[^}]*\bmessage\b[^}]*\}\s*from\s+['"]svelte-realtime\/server['"]/.test(source)
|
|
977
|
+
|| /export\s+(?:const|function|async\s+function)\s+message\b/.test(source);
|
|
978
|
+
|
|
979
|
+
if (!hasMessage) {
|
|
980
|
+
const name = found.endsWith('.ts') ? 'src/hooks.ws.ts' : 'src/hooks.ws.js';
|
|
981
|
+
console.warn(
|
|
982
|
+
`[svelte-realtime] ${name} exists but does not export a \`message\` handler -- ` +
|
|
983
|
+
`WebSocket RPC calls from ${dir}/ will go unhandled.\n` +
|
|
984
|
+
` Add: export { message } from 'svelte-realtime/server';`
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
943
989
|
/**
|
|
944
990
|
* Generate and write type declarations for all $live/ modules.
|
|
945
991
|
* Creates `$types.d.ts` in the live directory with ambient module declarations.
|