svelte-realtime 0.5.10 → 0.5.11

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/vite.d.ts +43 -43
  3. package/vite.js +52 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-realtime",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "publishConfig": {
5
5
  "tag": "latest"
6
6
  },
package/vite.d.ts CHANGED
@@ -1,43 +1,43 @@
1
- import type { Plugin } from 'vite';
2
-
3
- export interface SvelteRealtimeOptions {
4
- /**
5
- * Directory containing live modules.
6
- * @default 'src/live'
7
- */
8
- dir?: string;
9
-
10
- /**
11
- * Generate `$types.d.ts` in the live directory for typed `$live/` imports.
12
- * Strips the `ctx` parameter from live functions and wraps streams in `Readable<T>`.
13
- * @default true
14
- */
15
- typedImports?: boolean;
16
-
17
- /**
18
- * Enable the in-browser DevTools overlay in dev mode.
19
- * Toggle with Ctrl+Shift+L. Shows active streams, pending RPCs, and connection status.
20
- * Stripped from production builds.
21
- * @default true
22
- */
23
- devtools?: boolean;
24
- }
25
-
26
- /**
27
- * Vite plugin for svelte-realtime.
28
- *
29
- * Resolves `$live/` imports to virtual modules:
30
- * - On the server (SSR): re-exports the real module from `src/live/`
31
- * - On the client: generates lightweight stubs that call `__rpc()` / `__stream()`
32
- *
33
- * @example
34
- * ```js
35
- * // vite.config.js
36
- * import { sveltekit } from '@sveltejs/kit/vite';
37
- * import uws from 'svelte-adapter-uws/vite';
38
- * import realtime from 'svelte-realtime/vite';
39
- *
40
- * export default { plugins: [sveltekit(), uws(), realtime()] };
41
- * ```
42
- */
43
- export default function svelteRealtime(options?: SvelteRealtimeOptions): Plugin;
1
+ import type { Plugin } from 'vite';
2
+
3
+ export interface SvelteRealtimeOptions {
4
+ /**
5
+ * Directory containing live modules.
6
+ * @default 'src/live'
7
+ */
8
+ dir?: string;
9
+
10
+ /**
11
+ * Generate `$types.d.ts` in the live directory for typed `$live/` imports.
12
+ * Strips the `ctx` parameter from live functions and wraps streams in `Readable<T>`.
13
+ * @default true
14
+ */
15
+ typedImports?: boolean;
16
+
17
+ /**
18
+ * Enable the in-browser DevTools overlay in dev mode.
19
+ * Toggle with Ctrl+Shift+L. Shows active streams, pending RPCs, and connection status.
20
+ * Stripped from production builds.
21
+ * @default true
22
+ */
23
+ devtools?: boolean;
24
+ }
25
+
26
+ /**
27
+ * Vite plugin for svelte-realtime.
28
+ *
29
+ * Resolves `$live/` imports to virtual modules:
30
+ * - On the server (SSR): re-exports the real module from `src/live/`
31
+ * - On the client: generates lightweight stubs that call `__rpc()` / `__stream()`
32
+ *
33
+ * @example
34
+ * ```js
35
+ * // vite.config.js
36
+ * import { sveltekit } from '@sveltejs/kit/vite';
37
+ * import uws from 'svelte-adapter-uws/vite';
38
+ * import realtime from 'svelte-realtime/vite';
39
+ *
40
+ * export default { plugins: [sveltekit(), uws(), realtime()] };
41
+ * ```
42
+ */
43
+ export default function svelteRealtime(options?: SvelteRealtimeOptions): Plugin;
package/vite.js CHANGED
@@ -4,6 +4,9 @@ import { resolve, relative, dirname, sep, posix } from 'path';
4
4
 
5
5
  const VIRTUAL_PREFIX = '\0live:';
6
6
  const REGISTRY_ID = '\0live:__registry';
7
+ // Public specifier user code (the injected hooks import) can reference; the
8
+ // resolveId hook maps it to REGISTRY_ID.
9
+ const REGISTRY_PUBLIC_ID = '/@svelte-realtime-registry';
7
10
  const LIVE_EXPORT_RE = /export\s+const\s+(\w+)\s*=\s*live\s*\(/g;
8
11
  const VALIDATED_EXPORT_RE = /export\s+const\s+(\w+)\s*=\s*live\.validated\s*\(/g;
9
12
  const STREAM_EXPORT_RE = /export\s+const\s+(\w+)\s*=\s*live\.stream\s*\(/g;
@@ -692,7 +695,7 @@ export default function svelteRealtime(options) {
692
695
 
693
696
  resolveId(id) {
694
697
  if (id === REGISTRY_ID) return REGISTRY_ID;
695
- if (id === '/@svelte-realtime-registry') return REGISTRY_ID;
698
+ if (id === REGISTRY_PUBLIC_ID) return REGISTRY_ID;
696
699
  if (id.startsWith('$live/')) {
697
700
  const modulePath = id.slice(6); // strip '$live/'
698
701
  return VIRTUAL_PREFIX + modulePath;
@@ -742,6 +745,39 @@ export default function svelteRealtime(options) {
742
745
  return null;
743
746
  },
744
747
 
748
+ transform(code, id, options) {
749
+ // Co-locate the live registry with the WebSocket hooks module in
750
+ // dev. svelte-adapter-uws loads src/hooks.ws.* via ssrLoadModule and
751
+ // binds its `message` handler to whatever `svelte-realtime/server`
752
+ // instance that load produced. On a cold `npm run dev`, Vite's
753
+ // first-run dependency optimization tears down and rebuilds the SSR
754
+ // module graph, so the registry load fired on the server's
755
+ // 'listening' event can land in a DIFFERENT `svelte-realtime/server`
756
+ // instance than the one the adapter's `message` reads - leaving the
757
+ // registry empty and every RPC / stream failing with "no such live
758
+ // function registered" until a restart with a warm cache. Importing
759
+ // the registry FROM the hooks module makes it a dependency in the
760
+ // exact same graph, so the handler and its registrations always share
761
+ // one instance - the first `npm run dev` behaves identically to every
762
+ // one after it. Production build packaging uses the SSR-input path in
763
+ // config() below, so this dev-only seam never touches built output.
764
+ if (!isDev) return null;
765
+ // Only the server (SSR) graph loads hooks.ws; gating on ssr keeps
766
+ // the server-side registry import out of any client bundle even if
767
+ // a client module ever pulled the hooks file in.
768
+ const ssr = options?.ssr ?? isSsr;
769
+ if (!ssr) return null;
770
+ const clean = id.split('?')[0].split(sep).join('/');
771
+ // Scope to THIS project's src/hooks.ws.{js,ts,mjs} (root-anchored so a
772
+ // sibling package's hooks file in a monorepo is never rewritten with
773
+ // our registry). Mirrors the .js/.ts/.mjs set the adapter discovers.
774
+ const rootNorm = root.split(sep).join('/');
775
+ if (!clean.startsWith(rootNorm + '/')) return null;
776
+ if (!/\/src\/hooks\.ws\.(?:js|ts|mjs)$/.test(clean)) return null;
777
+ if (code.includes(REGISTRY_PUBLIC_ID)) return null;
778
+ return { code: `import ${JSON.stringify(REGISTRY_PUBLIC_ID)};\n` + code, map: null };
779
+ },
780
+
745
781
  config(config, { command }) {
746
782
  // During SSR build, inject the registry as an additional input
747
783
  if (command === 'build' && config.build?.ssr) {
@@ -2093,7 +2129,7 @@ function _findLiveFiles(dir) {
2093
2129
  }
2094
2130
 
2095
2131
  /**
2096
- * Check that src/hooks.ws.{js,ts} exists and exports the `message` handler.
2132
+ * Check that src/hooks.ws.{js,ts,mjs} exists and exports the `message` handler.
2097
2133
  * Warns at build/dev startup if the file is missing or misconfigured.
2098
2134
  * @param {string} root
2099
2135
  * @param {string} liveDir
@@ -2104,9 +2140,11 @@ function _checkHooksFile(root, liveDir, dir) {
2104
2140
  if (files.length === 0) return;
2105
2141
 
2106
2142
  const hooksPath = resolve(root, 'src/hooks.ws');
2107
- const hooksJs = hooksPath + '.js';
2108
- const hooksTs = hooksPath + '.ts';
2109
- const found = existsSync(hooksJs) ? hooksJs : existsSync(hooksTs) ? hooksTs : null;
2143
+ // Match the extension set the adapter discovers (js, ts, mjs) so a project
2144
+ // using hooks.ws.mjs is not falsely told its hooks file is missing.
2145
+ const found = ['.js', '.ts', '.mjs']
2146
+ .map((ext) => hooksPath + ext)
2147
+ .find((p) => existsSync(p)) || null;
2110
2148
 
2111
2149
  if (!found) {
2112
2150
  console.warn(
@@ -2123,11 +2161,18 @@ function _checkHooksFile(root, liveDir, dir) {
2123
2161
  let source;
2124
2162
  try { source = readFileSync(found, 'utf-8'); } catch { return; }
2125
2163
 
2126
- const hasMessage = /export\s*\{[^}]*\bmessage\b[^}]*\}\s*from\s+['"]svelte-realtime\/server['"]/.test(source)
2164
+ // Recognise every way a hooks file can export `message`:
2165
+ // - direct re-export: export { message } from 'svelte-realtime/server'
2166
+ // - import-then-export: import { message } from '...'; export { message }
2167
+ // (the scaffold and e2e fixture both use this two-statement form)
2168
+ // - local declaration: export const/function message = ...
2169
+ // The specifier-list regex covers the first two without needing a `from`
2170
+ // clause; a leading `from` (the direct re-export) still satisfies it.
2171
+ const hasMessage = /export\s*\{[^}]*\bmessage\b[^}]*\}/.test(source)
2127
2172
  || /export\s+(?:const|function|async\s+function)\s+message\b/.test(source);
2128
2173
 
2129
2174
  if (!hasMessage) {
2130
- const name = found.endsWith('.ts') ? 'src/hooks.ws.ts' : 'src/hooks.ws.js';
2175
+ const name = 'src/hooks.ws' + (found.match(/\.(?:js|ts|mjs)$/)?.[0] || '.js');
2131
2176
  console.warn(
2132
2177
  `[svelte-realtime] ${name} exists but does not export a \`message\` handler - ` +
2133
2178
  `WebSocket RPC calls from ${dir}/ will go unhandled.\n` +