svelte-realtime 0.4.11 → 0.4.13

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
@@ -634,9 +634,9 @@ Call live functions from `+page.server.js` to load data server-side, then hydrat
634
634
 
635
635
  ```js
636
636
  // src/routes/chat/+page.server.js
637
- export async function load({ platform }) {
637
+ export async function load({ platform, locals }) {
638
638
  const { messages } = await import('$live/chat');
639
- const data = await messages.load(platform);
639
+ const data = await messages.load(platform, { user: locals.user });
640
640
  return { messages: data };
641
641
  }
642
642
  ```
@@ -884,14 +884,47 @@ Call `configure()` once at app startup. The hooks fire on state transitions only
884
884
  When using svelte-realtime from a client that runs on a different origin (Svelte Native, React Native, or any standalone app), pass the `url` option to point at your SvelteKit backend:
885
885
 
886
886
  ```js
887
- import { configure } from 'svelte-realtime/client';
887
+ import { configure, __rpc, __stream } from 'svelte-realtime/client';
888
888
 
889
- configure({
890
- url: 'wss://my-sveltekit-app.com/ws'
891
- });
889
+ configure({ url: 'wss://my-sveltekit-app.com/ws' });
890
+
891
+ // Call a live function (equivalent to $live/chat.sendMessage, but untyped)
892
+ const sendMessage = __rpc('chat/sendMessage');
893
+ await sendMessage('hello');
894
+
895
+ // Subscribe to a stream (returns a Svelte store)
896
+ const messages = __stream('chat/messages', { merge: 'crud', key: 'id' });
897
+ messages.subscribe((value) => console.log(value));
898
+ ```
899
+
900
+ The typed `$live/*` imports and stream hydration are generated by the Vite plugin and only work inside a SvelteKit project. Outside SvelteKit, use `__rpc()` and `__stream()` directly. You get the same reconnection, offline queue, and batching -- just without codegen and types.
901
+
902
+ When `url` is set, the default same-origin WebSocket URL is bypassed entirely. Requires `svelte-adapter-uws` 0.4.8+.
903
+
904
+ Browser clients authenticate via cookies set during login. Native clients typically use a token instead. Your upgrade hook can support both:
905
+
906
+ ```js
907
+ // src/hooks.ws.js
908
+ export { message } from 'svelte-realtime/server';
909
+
910
+ export function upgrade({ cookies, url }) {
911
+ // Browser -- cookie auth
912
+ const session = cookies.session_id;
913
+ if (session) return validateSession(session);
914
+
915
+ // Native app -- token auth via query string
916
+ const token = new URL(url, 'http://n').searchParams.get('token');
917
+ if (token) return validateToken(token);
918
+
919
+ return false;
920
+ }
892
921
  ```
893
922
 
894
- When `url` is set, the default same-origin WebSocket URL is bypassed entirely. All RPC calls, streams, and pub/sub work the same way. Requires `svelte-adapter-uws` 0.4.8+.
923
+ The native client passes the token in the URL:
924
+
925
+ ```js
926
+ configure({ url: 'wss://my-sveltekit-app.com/ws?token=...' });
927
+ ```
895
928
 
896
929
  ---
897
930
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-realtime",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
 
3
3
  const textDecoder = new TextDecoder();
4
- const _validPathRe = /^[a-zA-Z0-9_]+(?:\/[a-zA-Z0-9_]+)+$/;
4
+ const _validPathRe = /^[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)+$/;
5
5
  const _validSegmentRe = /^[a-zA-Z0-9_]+$/;
6
6
 
7
7
  /** @type {Map<string, Function>} */
@@ -13,6 +13,9 @@ const guards = new Map();
13
13
  /** @type {Set<Function>} Streams with onUnsubscribe hooks (for iterating static matches in close) */
14
14
  const _streamsWithUnsubscribe = new Set();
15
15
 
16
+ /** @type {Set<string>} Paths that already warned about null ctx.user in __directCall */
17
+ const _directCallWarned = new Set();
18
+
16
19
  /**
17
20
  * Tag a topic function with __topicUsesCtx by inspecting its first parameter name.
18
21
  *
@@ -2472,7 +2475,13 @@ export async function __directCall(path, args, platform, options) {
2472
2475
  // Run module guard
2473
2476
  const modulePath = /** @type {any} */ (fn).__modulePath || path.substring(0, path.lastIndexOf('/'));
2474
2477
  const guardFn = await _resolveGuard(modulePath);
2475
- if (guardFn) await guardFn(ctx);
2478
+ if (guardFn) {
2479
+ if (ctx.user === null && typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production' && !_directCallWarned.has(path)) {
2480
+ _directCallWarned.add(path);
2481
+ console.warn(`[svelte-realtime] .load() is calling guard for '${path}' with ctx.user = null. Pass { user } in the options to provide user data:\n stream.load(platform, { user: locals.user })\n See: https://svti.me/ssr`);
2482
+ }
2483
+ await guardFn(ctx);
2484
+ }
2476
2485
 
2477
2486
  if (/** @type {any} */ (fn).__isStream) {
2478
2487
  if (/** @type {any} */ (fn).__isGated) {
package/vite.js CHANGED
@@ -1604,7 +1604,7 @@ function _generateTypeDeclarations(liveDir, dir) {
1604
1604
  if (isTS) {
1605
1605
  const returnType = _extractStreamReturnType(source, name);
1606
1606
  const storeType = `StreamStore<${returnType} | undefined | { error: RpcError }>`;
1607
- const loadSig = `{ load(platform: any, options?: { args?: any[] }): Promise<${returnType}> }`;
1607
+ const loadSig = `{ load(platform: any, options?: { args?: any[]; user?: any }): Promise<${returnType}> }`;
1608
1608
  if (isDynamic) {
1609
1609
  const factoryParams = _extractDynamicFactoryParams(source, name, 'live\\.stream');
1610
1610
  exports.push(` export const ${name}: (${factoryParams} => ${storeType}) & ${loadSig};`);
@@ -1612,7 +1612,7 @@ function _generateTypeDeclarations(liveDir, dir) {
1612
1612
  exports.push(` export const ${name}: ${storeType} & ${loadSig};`);
1613
1613
  }
1614
1614
  } else {
1615
- const loadSig = `{ load(platform: any, options?: { args?: any[] }): Promise<any> }`;
1615
+ const loadSig = `{ load(platform: any, options?: { args?: any[]; user?: any }): Promise<any> }`;
1616
1616
  if (isDynamic) {
1617
1617
  exports.push(` export const ${name}: ((...args: any[]) => StreamStore<any>) & ${loadSig};`);
1618
1618
  } else {
@@ -1629,7 +1629,7 @@ function _generateTypeDeclarations(liveDir, dir) {
1629
1629
  if (!exports.some(e => e.includes(`export const ${name}:`))) {
1630
1630
  needsStreamStore = true;
1631
1631
  const isDynamic = _isDynamicExport(source, name, 'live\\.channel');
1632
- const loadSig = `{ load(platform: any, options?: { args?: any[] }): Promise<any> }`;
1632
+ const loadSig = `{ load(platform: any, options?: { args?: any[]; user?: any }): Promise<any> }`;
1633
1633
  if (isDynamic) {
1634
1634
  if (isTS) {
1635
1635
  const factoryParams = _extractDynamicFactoryParams(source, name, 'live\\.channel');
@@ -1650,7 +1650,7 @@ function _generateTypeDeclarations(liveDir, dir) {
1650
1650
  handledNames.add(name);
1651
1651
  if (!exports.some(e => e.includes(`export const ${name}:`))) {
1652
1652
  needsStreamStore = true;
1653
- exports.push(` export const ${name}: StreamStore<any> & { load(platform: any, options?: { args?: any[] }): Promise<any> };`);
1653
+ exports.push(` export const ${name}: StreamStore<any> & { load(platform: any, options?: { args?: any[]; user?: any }): Promise<any> };`);
1654
1654
  }
1655
1655
  }
1656
1656
 
@@ -1661,7 +1661,7 @@ function _generateTypeDeclarations(liveDir, dir) {
1661
1661
  handledNames.add(name);
1662
1662
  if (!exports.some(e => e.includes(`export const ${name}:`))) {
1663
1663
  needsStreamStore = true;
1664
- exports.push(` export const ${name}: StreamStore<any> & { load(platform: any, options?: { args?: any[] }): Promise<any> };`);
1664
+ exports.push(` export const ${name}: StreamStore<any> & { load(platform: any, options?: { args?: any[]; user?: any }): Promise<any> };`);
1665
1665
  }
1666
1666
  }
1667
1667