svelte-realtime 0.1.9 → 0.4.1
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 +142 -6
- package/cli-utils.js +77 -0
- package/cli.js +205 -0
- package/client.d.ts +1 -1
- package/client.js +405 -110
- package/devtools.js +6 -5
- package/package.json +11 -3
- package/server.d.ts +853 -851
- package/server.js +765 -469
- package/test.d.ts +110 -110
- package/test.js +489 -330
- package/vite.js +1184 -139
package/README.md
CHANGED
|
@@ -6,7 +6,19 @@ Write server functions. Import them in components. Call them over WebSocket. No
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx svelte-realtime my-app
|
|
13
|
+
cd my-app
|
|
14
|
+
npm run dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This creates a SvelteKit project with svelte-realtime fully wired: adapter, vite plugins, WebSocket hooks, and a working counter example you can open in your browser right away.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Manual setup
|
|
10
22
|
|
|
11
23
|
Starting from a SvelteKit project. If you do not have one yet, run `npx sv create my-app && cd my-app && npm install` first.
|
|
12
24
|
|
|
@@ -19,7 +31,7 @@ npm install -D ws
|
|
|
19
31
|
```
|
|
20
32
|
|
|
21
33
|
What each package does:
|
|
22
|
-
- `svelte-adapter-uws` -- the SvelteKit adapter that runs your app on uWebSockets.js with built-in WebSocket support
|
|
34
|
+
- `svelte-adapter-uws` (>=0.4.0) -- the SvelteKit adapter that runs your app on uWebSockets.js with built-in WebSocket support
|
|
23
35
|
- `svelte-realtime` -- this library (RPC + streams on top of the adapter)
|
|
24
36
|
- `uWebSockets.js` -- the native C++ HTTP/WebSocket server (installed from GitHub, not npm)
|
|
25
37
|
- `ws` -- dev dependency used by the adapter during `npm run dev` (not needed in production)
|
|
@@ -164,6 +176,9 @@ The `ctx` object passed to every server function contains:
|
|
|
164
176
|
| `ctx.throttle` | `(topic, event, data, ms)` -- publish at most once per `ms` ms |
|
|
165
177
|
| `ctx.debounce` | `(topic, event, data, ms)` -- publish after `ms` ms of silence |
|
|
166
178
|
| `ctx.signal` | `(userId, event, data)` -- point-to-point message |
|
|
179
|
+
| `ctx.batch` | `(messages)` -- publish multiple messages in one call via `platform.batch()` |
|
|
180
|
+
|
|
181
|
+
Note: `ctx.user` may contain adapter-injected properties (`__subscriptions`, `remoteAddress`) in addition to whatever your `upgrade()` function returned. These are stripped automatically by the adapter before broadcasting to other clients.
|
|
167
182
|
|
|
168
183
|
---
|
|
169
184
|
|
|
@@ -387,6 +402,16 @@ try {
|
|
|
387
402
|
}
|
|
388
403
|
```
|
|
389
404
|
|
|
405
|
+
### Terminal close codes
|
|
406
|
+
|
|
407
|
+
When the adapter's `ready()` promise rejects (terminal close codes 1008, 4401, 4403, exhausted retries, or explicit `close()`), svelte-realtime:
|
|
408
|
+
|
|
409
|
+
- Rejects all pending RPCs immediately with `RpcError('CONNECTION_CLOSED', ...)`
|
|
410
|
+
- Sets an `{ error }` state on all active stream stores
|
|
411
|
+
- Drains the offline queue with errors
|
|
412
|
+
|
|
413
|
+
RPCs called after a terminal close reject immediately without sending.
|
|
414
|
+
|
|
390
415
|
### Reusable error boundary component
|
|
391
416
|
|
|
392
417
|
For Svelte 5, you can build a reusable boundary that handles all three stream states:
|
|
@@ -651,6 +676,20 @@ const [board, column] = await batch(() => [
|
|
|
651
676
|
|
|
652
677
|
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.
|
|
653
678
|
|
|
679
|
+
### Server-side batching
|
|
680
|
+
|
|
681
|
+
Use `ctx.batch()` inside RPC handlers to publish multiple messages in a single call:
|
|
682
|
+
|
|
683
|
+
```js
|
|
684
|
+
export const resetBoard = live(async (ctx, boardId) => {
|
|
685
|
+
await db.boards.reset(boardId);
|
|
686
|
+
ctx.batch([
|
|
687
|
+
{ topic: `board:${boardId}`, event: 'set', data: [] },
|
|
688
|
+
{ topic: `board:${boardId}:presence`, event: 'set', data: [] }
|
|
689
|
+
]);
|
|
690
|
+
});
|
|
691
|
+
```
|
|
692
|
+
|
|
654
693
|
---
|
|
655
694
|
|
|
656
695
|
## Optimistic updates
|
|
@@ -913,13 +952,13 @@ export const presence = live.stream('room:lobby', async (ctx) => {
|
|
|
913
952
|
});
|
|
914
953
|
```
|
|
915
954
|
|
|
916
|
-
`onSubscribe` fires after `ws.subscribe(topic)` and the initial data fetch. `onUnsubscribe` fires when the WebSocket closes
|
|
955
|
+
`onSubscribe` fires after `ws.subscribe(topic)` and the initial data fetch. `onUnsubscribe` fires in real time when a client unsubscribes from a topic (adapter 0.4.0+), and also when the WebSocket closes for any remaining topics. Export both hooks from your `hooks.ws.js`:
|
|
917
956
|
|
|
918
957
|
```js
|
|
919
|
-
export { message, close } from 'svelte-realtime/server';
|
|
958
|
+
export { message, close, unsubscribe } from 'svelte-realtime/server';
|
|
920
959
|
```
|
|
921
960
|
|
|
922
|
-
`onUnsubscribe` fires for both static and dynamic topics. For dynamic topics, the server tracks which stream produced each subscription and
|
|
961
|
+
`onUnsubscribe` fires for both static and dynamic topics. For dynamic topics, the server tracks which stream produced each subscription and fires the correct hook. The `unsubscribe` hook fires as soon as the client drops a topic; `close` only fires for topics still active at disconnect time. There is no double-firing.
|
|
923
962
|
|
|
924
963
|
---
|
|
925
964
|
|
|
@@ -1007,6 +1046,49 @@ export const message = createMessage({
|
|
|
1007
1046
|
|
|
1008
1047
|
---
|
|
1009
1048
|
|
|
1049
|
+
## Prometheus metrics
|
|
1050
|
+
|
|
1051
|
+
Opt-in instrumentation for RPC calls, stream subscriptions, and cron executions. Zero overhead if not called.
|
|
1052
|
+
|
|
1053
|
+
```js
|
|
1054
|
+
import { live } from 'svelte-realtime/server';
|
|
1055
|
+
import { createMetricsRegistry } from 'svelte-adapter-uws-extensions/prometheus';
|
|
1056
|
+
|
|
1057
|
+
const registry = createMetricsRegistry();
|
|
1058
|
+
live.metrics(registry);
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
This registers counters/histograms for:
|
|
1062
|
+
- `svelte_realtime_rpc_total` -- RPC call count by path and status
|
|
1063
|
+
- `svelte_realtime_rpc_duration_seconds` -- RPC latency by path
|
|
1064
|
+
- `svelte_realtime_rpc_errors_total` -- RPC errors by path and code
|
|
1065
|
+
- `svelte_realtime_stream_subscriptions` -- active stream subscription gauge by topic
|
|
1066
|
+
- `svelte_realtime_cron_total` -- cron execution count by path and status
|
|
1067
|
+
- `svelte_realtime_cron_errors_total` -- cron errors by path
|
|
1068
|
+
|
|
1069
|
+
---
|
|
1070
|
+
|
|
1071
|
+
## Circuit breaker
|
|
1072
|
+
|
|
1073
|
+
Wrap a stream or RPC init function with a circuit breaker from `svelte-adapter-uws-extensions`. When the breaker is open, returns a fallback value or throws `SERVICE_UNAVAILABLE`.
|
|
1074
|
+
|
|
1075
|
+
```js
|
|
1076
|
+
import { live } from 'svelte-realtime/server';
|
|
1077
|
+
import { createBreaker } from 'svelte-adapter-uws-extensions/breaker';
|
|
1078
|
+
|
|
1079
|
+
const dbBreaker = createBreaker({ threshold: 5, resetMs: 30000 });
|
|
1080
|
+
|
|
1081
|
+
export const items = live.stream('items',
|
|
1082
|
+
live.breaker({ breaker: dbBreaker, fallback: [] }, async (ctx) => {
|
|
1083
|
+
return db.items.list();
|
|
1084
|
+
})
|
|
1085
|
+
);
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
If `fallback` is omitted and the circuit is open, the call throws `LiveError('SERVICE_UNAVAILABLE', ...)`.
|
|
1089
|
+
|
|
1090
|
+
---
|
|
1091
|
+
|
|
1010
1092
|
## Cron scheduling
|
|
1011
1093
|
|
|
1012
1094
|
Use `live.cron()` to run server-side functions on a schedule and publish results to a topic.
|
|
@@ -1275,6 +1357,17 @@ On the client, the room export becomes an object with sub-streams and actions. R
|
|
|
1275
1357
|
<button onclick={() => board.addCard(boardId, 'New card')}>Add</button>
|
|
1276
1358
|
```
|
|
1277
1359
|
|
|
1360
|
+
### Room hooks shortcut
|
|
1361
|
+
|
|
1362
|
+
Rooms expose a `.hooks` property for one-liner wiring in `hooks.ws.js`:
|
|
1363
|
+
|
|
1364
|
+
```js
|
|
1365
|
+
// src/hooks.ws.js
|
|
1366
|
+
import { board } from './live/collab.js';
|
|
1367
|
+
|
|
1368
|
+
export const { message, close, unsubscribe } = board.hooks;
|
|
1369
|
+
```
|
|
1370
|
+
|
|
1278
1371
|
---
|
|
1279
1372
|
|
|
1280
1373
|
## Webhooks
|
|
@@ -1411,6 +1504,8 @@ export const feed = live.stream('feed', async (ctx) => {
|
|
|
1411
1504
|
|
|
1412
1505
|
Replay requires the replay extension from `svelte-adapter-uws-extensions`. When replay is not available or the gap is too large, the client falls back to a full refetch automatically.
|
|
1413
1506
|
|
|
1507
|
+
With adapter 0.4.0+, the replay end marker sends `{ reqId }` (replay complete) or `{ reqId, truncated: true }` (cache miss). When truncated, the client automatically resets its sequence number and triggers a full refetch.
|
|
1508
|
+
|
|
1414
1509
|
---
|
|
1415
1510
|
|
|
1416
1511
|
## Redis multi-instance
|
|
@@ -1716,11 +1811,14 @@ Import from `svelte-realtime/server`.
|
|
|
1716
1811
|
| `message` | Ready-made message hook |
|
|
1717
1812
|
| `createMessage(options?)` | Custom message hook factory |
|
|
1718
1813
|
| `pipe(stream, ...transforms)` | Composable stream transforms |
|
|
1719
|
-
| `close` | Ready-made close hook (fires onUnsubscribe) |
|
|
1814
|
+
| `close` | Ready-made close hook (fires onUnsubscribe for remaining topics) |
|
|
1815
|
+
| `unsubscribe` | Ready-made unsubscribe hook (fires onUnsubscribe in real time) |
|
|
1720
1816
|
| `setCronPlatform(platform)` | Capture platform for cron jobs |
|
|
1721
1817
|
| `onCronError(handler)` | Global cron error handler |
|
|
1722
1818
|
| `enableSignals(ws)` | Enable point-to-point signal delivery |
|
|
1723
1819
|
| `_activateDerived(platform)` | Enable derived stream listeners |
|
|
1820
|
+
| `live.metrics(registry)` | Opt-in Prometheus metrics |
|
|
1821
|
+
| `live.breaker(options, fn)` | Circuit breaker wrapper |
|
|
1724
1822
|
|
|
1725
1823
|
---
|
|
1726
1824
|
|
|
@@ -1735,6 +1833,7 @@ Import from `svelte-realtime/client`.
|
|
|
1735
1833
|
| `configure(config)` | Connection hooks and offline queue setup |
|
|
1736
1834
|
| `combine(...stores, fn)` | Multi-store composition |
|
|
1737
1835
|
| `onSignal(userId, callback)` | Listen for point-to-point signals |
|
|
1836
|
+
| `onDerived` | Re-exported from adapter: reactive derived topic subscription |
|
|
1738
1837
|
|
|
1739
1838
|
**Stream store methods** (on `$live/` stream imports):
|
|
1740
1839
|
|
|
@@ -1810,6 +1909,43 @@ npm test
|
|
|
1810
1909
|
|
|
1811
1910
|
---
|
|
1812
1911
|
|
|
1912
|
+
## Tauri and Capacitor
|
|
1913
|
+
|
|
1914
|
+
svelte-realtime works with Tauri and Capacitor without any static build or architectural changes.
|
|
1915
|
+
|
|
1916
|
+
Both runtimes let you point their webview at a live URL instead of local files. Your SvelteKit app runs on the server as normal -- SSR, WebSocket hydration, live stores, RPC -- and the native wrapper adds platform APIs (camera, push notifications, filesystem, etc.) on top.
|
|
1917
|
+
|
|
1918
|
+
**Capacitor** -- `capacitor.config.ts`:
|
|
1919
|
+
|
|
1920
|
+
```ts
|
|
1921
|
+
import { CapacitorConfig } from '@capacitor/cli';
|
|
1922
|
+
|
|
1923
|
+
const config: CapacitorConfig = {
|
|
1924
|
+
appId: 'com.example.app',
|
|
1925
|
+
appName: 'My App',
|
|
1926
|
+
server: {
|
|
1927
|
+
url: 'https://yourapp.com'
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
|
|
1931
|
+
export default config;
|
|
1932
|
+
```
|
|
1933
|
+
|
|
1934
|
+
**Tauri** -- `tauri.conf.json`:
|
|
1935
|
+
|
|
1936
|
+
```json
|
|
1937
|
+
{
|
|
1938
|
+
"build": {
|
|
1939
|
+
"devPath": "https://yourapp.com",
|
|
1940
|
+
"distDir": "https://yourapp.com"
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
```
|
|
1944
|
+
|
|
1945
|
+
The webview loads your server directly. No static adapter, no URL configuration in the client, nothing special in your SvelteKit code.
|
|
1946
|
+
|
|
1947
|
+
---
|
|
1948
|
+
|
|
1813
1949
|
## License
|
|
1814
1950
|
|
|
1815
1951
|
MIT
|
package/cli-utils.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
const VALID_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
4
|
+
const VALID_TEMPLATES = ['minimal', 'example', 'demo'];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} [ua]
|
|
8
|
+
* @returns {'npm' | 'pnpm' | 'yarn' | 'bun'}
|
|
9
|
+
*/
|
|
10
|
+
export function detectAgent(ua) {
|
|
11
|
+
const agent = ua ?? '';
|
|
12
|
+
if (agent.startsWith('pnpm')) return 'pnpm';
|
|
13
|
+
if (agent.startsWith('yarn')) return 'yarn';
|
|
14
|
+
if (agent.startsWith('bun')) return 'bun';
|
|
15
|
+
return 'npm';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parse and validate CLI arguments. Returns an object or an error string.
|
|
20
|
+
* Parses in one pass so that flag values (e.g. --template minimal) are consumed
|
|
21
|
+
* and not mistaken for positional args.
|
|
22
|
+
* @param {string[]} argv - arguments (without node/script prefix)
|
|
23
|
+
* @param {{ dirExists?: (name: string) => boolean }} [opts]
|
|
24
|
+
* @returns {{ help: true } | { error: string } | { name?: string, template?: string }}
|
|
25
|
+
*/
|
|
26
|
+
export function parseArgs(argv, opts) {
|
|
27
|
+
const positional = [];
|
|
28
|
+
let template;
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < argv.length; i++) {
|
|
31
|
+
const arg = argv[i];
|
|
32
|
+
|
|
33
|
+
if (arg === '--help' || arg === '-h') {
|
|
34
|
+
return { help: true };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (arg === '--template') {
|
|
38
|
+
const next = argv[i + 1];
|
|
39
|
+
if (next === undefined || next.startsWith('-')) {
|
|
40
|
+
if (next === '--help' || next === '-h') return { help: true };
|
|
41
|
+
return { error: '--template requires a value: minimal, example, or demo.' };
|
|
42
|
+
}
|
|
43
|
+
template = argv[++i];
|
|
44
|
+
if (!VALID_TEMPLATES.includes(template)) {
|
|
45
|
+
return { error: `Unknown template: "${template}". Use minimal, example, or demo.` };
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (arg.startsWith('--template=')) {
|
|
51
|
+
template = arg.slice('--template='.length);
|
|
52
|
+
if (!template) {
|
|
53
|
+
return { error: '--template requires a value: minimal, example, or demo.' };
|
|
54
|
+
}
|
|
55
|
+
if (!VALID_TEMPLATES.includes(template)) {
|
|
56
|
+
return { error: `Unknown template: "${template}". Use minimal, example, or demo.` };
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (arg.startsWith('-')) continue;
|
|
62
|
+
|
|
63
|
+
positional.push(arg);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const name = positional[0];
|
|
67
|
+
if (name !== undefined) {
|
|
68
|
+
if (!VALID_NAME_RE.test(name)) {
|
|
69
|
+
return { error: `Invalid project name: "${name}". Use only letters, numbers, hyphens, and underscores.` };
|
|
70
|
+
}
|
|
71
|
+
if (opts?.dirExists?.(name)) {
|
|
72
|
+
return { error: `Directory "${name}" already exists.` };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { name, template };
|
|
77
|
+
}
|
package/cli.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
5
|
+
import { resolve, join } from 'path';
|
|
6
|
+
import * as p from '@clack/prompts';
|
|
7
|
+
import { detectAgent, parseArgs } from './cli-utils.js';
|
|
8
|
+
|
|
9
|
+
const DEMO_REPO = 'https://github.com/lanteanio/svelte-realtime-demo.git';
|
|
10
|
+
|
|
11
|
+
const parsed = parseArgs(process.argv.slice(2), {
|
|
12
|
+
dirExists: (name) => existsSync(resolve(process.cwd(), name))
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if ('help' in parsed) {
|
|
16
|
+
console.log(`
|
|
17
|
+
Usage: npx svelte-realtime [project-name] [--template minimal|example|demo]
|
|
18
|
+
|
|
19
|
+
Scaffolds a SvelteKit project with svelte-realtime wired up and ready to go.
|
|
20
|
+
`);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if ('error' in parsed) {
|
|
25
|
+
console.error(parsed.error);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
p.intro('svelte-realtime');
|
|
30
|
+
|
|
31
|
+
const name =
|
|
32
|
+
parsed.name ||
|
|
33
|
+
/** @type {string} */ (
|
|
34
|
+
await p.text({
|
|
35
|
+
message: 'Project name',
|
|
36
|
+
placeholder: 'my-app',
|
|
37
|
+
validate(value) {
|
|
38
|
+
if (!value) return 'Required.';
|
|
39
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(value))
|
|
40
|
+
return 'Use only letters, numbers, hyphens, and underscores.';
|
|
41
|
+
if (existsSync(resolve(process.cwd(), value))) return `Directory "${value}" already exists.`;
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (p.isCancel(name)) {
|
|
47
|
+
p.cancel('Cancelled.');
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const dest = resolve(process.cwd(), name);
|
|
52
|
+
|
|
53
|
+
const template =
|
|
54
|
+
parsed.template ||
|
|
55
|
+
/** @type {string} */ (
|
|
56
|
+
await p.select({
|
|
57
|
+
message: 'Which template would you like?',
|
|
58
|
+
options: [
|
|
59
|
+
{
|
|
60
|
+
value: 'minimal',
|
|
61
|
+
label: 'Wiring only',
|
|
62
|
+
hint: 'SvelteKit + svelte-realtime, no example code'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
value: 'example',
|
|
66
|
+
label: 'Barebones example',
|
|
67
|
+
hint: 'SvelteKit + svelte-realtime with a working counter'
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
value: 'demo',
|
|
71
|
+
label: 'Full demo app',
|
|
72
|
+
hint: 'clone svelte-realtime-demo'
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (p.isCancel(template)) {
|
|
79
|
+
p.cancel('Cancelled.');
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const agent = detectAgent(process.env.npm_config_user_agent);
|
|
84
|
+
|
|
85
|
+
if (template === 'demo') {
|
|
86
|
+
const s = p.spinner();
|
|
87
|
+
s.start('Cloning demo repository');
|
|
88
|
+
run(`git clone ${DEMO_REPO} "${name}"`);
|
|
89
|
+
s.stop('Cloned.');
|
|
90
|
+
|
|
91
|
+
s.start('Installing dependencies');
|
|
92
|
+
run(`${agent} install`, dest);
|
|
93
|
+
s.stop('Installed.');
|
|
94
|
+
|
|
95
|
+
p.outro(`Done. cd ${name} && ${agent} run dev`);
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const s = p.spinner();
|
|
100
|
+
|
|
101
|
+
s.start('Creating SvelteKit project');
|
|
102
|
+
run(`npx -y sv create "${name}" --template minimal --types ts`);
|
|
103
|
+
s.stop('Project created.');
|
|
104
|
+
|
|
105
|
+
s.start('Installing dependencies');
|
|
106
|
+
const add = agent === 'npm' ? 'install' : 'add';
|
|
107
|
+
run(`${agent} ${add} svelte-adapter-uws svelte-realtime`, dest);
|
|
108
|
+
run(`${agent} ${add} uNetworking/uWebSockets.js#v20.60.0`, dest);
|
|
109
|
+
run(`${agent} ${add} -D ws`, dest);
|
|
110
|
+
s.stop('Dependencies installed.');
|
|
111
|
+
|
|
112
|
+
s.start('Configuring svelte-realtime');
|
|
113
|
+
|
|
114
|
+
writeFileSync(
|
|
115
|
+
join(dest, 'svelte.config.js'),
|
|
116
|
+
`import adapter from 'svelte-adapter-uws';
|
|
117
|
+
import { vitePreprocess } from '@sveltejs/kit/vite';
|
|
118
|
+
|
|
119
|
+
export default {
|
|
120
|
+
\tkit: {
|
|
121
|
+
\t\tadapter: adapter({ websocket: true })
|
|
122
|
+
\t},
|
|
123
|
+
\tpreprocess: [vitePreprocess()]
|
|
124
|
+
};
|
|
125
|
+
`
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
writeFileSync(
|
|
129
|
+
join(dest, 'vite.config.ts'),
|
|
130
|
+
`import { sveltekit } from '@sveltejs/kit/vite';
|
|
131
|
+
import uws from 'svelte-adapter-uws/vite';
|
|
132
|
+
import realtime from 'svelte-realtime/vite';
|
|
133
|
+
import { defineConfig } from 'vite';
|
|
134
|
+
|
|
135
|
+
export default defineConfig({
|
|
136
|
+
\tplugins: [sveltekit(), uws(), realtime()]
|
|
137
|
+
});
|
|
138
|
+
`
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
writeFileSync(
|
|
142
|
+
join(dest, 'src', 'hooks.ws.ts'),
|
|
143
|
+
`import { message } from 'svelte-realtime/server';
|
|
144
|
+
export { message };
|
|
145
|
+
|
|
146
|
+
export function upgrade() {
|
|
147
|
+
\treturn { id: crypto.randomUUID() };
|
|
148
|
+
}
|
|
149
|
+
`
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (template === 'example') {
|
|
153
|
+
mkdirSync(join(dest, 'src', 'live'), { recursive: true });
|
|
154
|
+
|
|
155
|
+
writeFileSync(
|
|
156
|
+
join(dest, 'src', 'live', 'counter.ts'),
|
|
157
|
+
`import { live } from 'svelte-realtime/server';
|
|
158
|
+
|
|
159
|
+
let count = 0;
|
|
160
|
+
|
|
161
|
+
export const increment = live((ctx) => {
|
|
162
|
+
\tcount++;
|
|
163
|
+
\tctx.publish('count', 'set', count);
|
|
164
|
+
\treturn count;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export const counter = live.stream('count', () => {
|
|
168
|
+
\treturn count;
|
|
169
|
+
}, { merge: 'set' });
|
|
170
|
+
`
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
writeFileSync(
|
|
174
|
+
join(dest, 'src', 'routes', '+page.svelte'),
|
|
175
|
+
`<script lang="ts">
|
|
176
|
+
\timport { increment, counter } from '$live/counter';
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
<h1>svelte-realtime</h1>
|
|
180
|
+
|
|
181
|
+
{#if $counter === undefined}
|
|
182
|
+
\t<p>Connecting...</p>
|
|
183
|
+
{:else}
|
|
184
|
+
\t<p>Count: {$counter}</p>
|
|
185
|
+
{/if}
|
|
186
|
+
|
|
187
|
+
<button onclick={() => increment()}>+1</button>
|
|
188
|
+
`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
s.stop('Configured.');
|
|
193
|
+
|
|
194
|
+
p.outro(`Done. cd ${name} && ${agent} run dev`);
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
function run(cmd, cwd) {
|
|
199
|
+
try {
|
|
200
|
+
execSync(cmd, { cwd, stdio: 'pipe' });
|
|
201
|
+
} catch (e) {
|
|
202
|
+
p.cancel(`Command failed: ${cmd}\n${e.stderr || e.message}`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
}
|
package/client.d.ts
CHANGED
|
@@ -129,7 +129,7 @@ export function batch<T extends Promise<any>[]>(
|
|
|
129
129
|
*
|
|
130
130
|
* @internal
|
|
131
131
|
*/
|
|
132
|
-
export function __binaryRpc(path: string): (buffer: ArrayBuffer, ...args: any[]) => Promise<any>;
|
|
132
|
+
export function __binaryRpc(path: string): (buffer: ArrayBuffer | ArrayBufferView, ...args: any[]) => Promise<any>;
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
135
|
* Configure client-side connection hooks.
|