svelte-realtime 0.4.19 → 0.4.20
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 +4 -0
- package/package.json +1 -1
- package/server.js +25 -6
package/README.md
CHANGED
|
@@ -1281,6 +1281,10 @@ export function open(ws, { platform }) {
|
|
|
1281
1281
|
}
|
|
1282
1282
|
```
|
|
1283
1283
|
|
|
1284
|
+
Without this call, derived streams will still serve their initial SSR data but will never receive live updates. In dev mode, a console warning is emitted when a client subscribes to a derived stream and `_activateDerived` has not been called.
|
|
1285
|
+
|
|
1286
|
+
Dynamic derived compute functions receive `ctx.user` from the subscribing client, so auth checks like `if (orgId !== ctx.user.organization_id) throw new LiveError("FORBIDDEN")` work the same as they do in regular stream handlers.
|
|
1287
|
+
|
|
1284
1288
|
| Option | Default | Description |
|
|
1285
1289
|
|---|---|---|
|
|
1286
1290
|
| `merge` | `'set'` | Merge strategy for the derived topic |
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -778,7 +778,7 @@ live.derived = function derived(sources, fn, options) {
|
|
|
778
778
|
/** @type {Map<string, any[]>} */
|
|
779
779
|
const topicArgs = new Map();
|
|
780
780
|
const topicFn = (...args) => {
|
|
781
|
-
const t = baseTopic + '
|
|
781
|
+
const t = baseTopic + '~' + args.map(a => String(a).replace(/~/g, '')).join('~');
|
|
782
782
|
topicArgs.set(t, args);
|
|
783
783
|
if (topicArgs.size > 10000) {
|
|
784
784
|
const iter = topicArgs.keys();
|
|
@@ -791,7 +791,7 @@ live.derived = function derived(sources, fn, options) {
|
|
|
791
791
|
/** @type {any} */ (fn).__derivedTopicArgs = topicArgs;
|
|
792
792
|
|
|
793
793
|
/** @type {any} */ (fn).__onSubscribe = function (_ctx, resolvedTopic) {
|
|
794
|
-
_activateDynamicDerived(fn, resolvedTopic);
|
|
794
|
+
_activateDynamicDerived(fn, resolvedTopic, _ctx && _ctx.user);
|
|
795
795
|
};
|
|
796
796
|
/** @type {any} */ (fn).__onUnsubscribe = function (_ctx, resolvedTopic) {
|
|
797
797
|
_deactivateDynamicDerived(fn, resolvedTopic);
|
|
@@ -815,6 +815,12 @@ const _dynamicDerivedByFn = new Map();
|
|
|
815
815
|
/** @type {import('svelte-adapter-uws').Platform | null} Captured platform for dynamic derived recomputation */
|
|
816
816
|
let _derivedPlatform = null;
|
|
817
817
|
|
|
818
|
+
/** @type {boolean} Whether _activateDerived has been called at least once */
|
|
819
|
+
let _activateDerivedCalled = false;
|
|
820
|
+
|
|
821
|
+
/** @type {boolean} Whether the missing _activateDerived warning has already fired */
|
|
822
|
+
let _warnedActivateDerived = false;
|
|
823
|
+
|
|
818
824
|
/** @type {Map<string, { sources: string[], fn: Function, debounce: number, timer: ReturnType<typeof setTimeout> | null }>} */
|
|
819
825
|
const effectRegistry = new Map();
|
|
820
826
|
|
|
@@ -1387,6 +1393,7 @@ live.breaker = function breaker(options, fn) {
|
|
|
1387
1393
|
export function __registerDerived(path, fn) {
|
|
1388
1394
|
if (/** @type {any} */ (fn).__lazy) {
|
|
1389
1395
|
_lazyQueue.push({ type: 'derived', path, loader: fn });
|
|
1396
|
+
_hasDynamicDerived = true;
|
|
1390
1397
|
return;
|
|
1391
1398
|
}
|
|
1392
1399
|
|
|
@@ -1397,7 +1404,7 @@ export function __registerDerived(path, fn) {
|
|
|
1397
1404
|
/** @type {Map<string, any[]>} */
|
|
1398
1405
|
const topicArgs = new Map();
|
|
1399
1406
|
const topicFn = (...args) => {
|
|
1400
|
-
const t = path + '
|
|
1407
|
+
const t = path + '~' + args.map(a => String(a).replace(/~/g, '')).join('~');
|
|
1401
1408
|
topicArgs.set(t, args);
|
|
1402
1409
|
if (topicArgs.size > 10000) {
|
|
1403
1410
|
const iter = topicArgs.keys();
|
|
@@ -1443,6 +1450,7 @@ const _activatedPlatforms = new WeakSet();
|
|
|
1443
1450
|
|
|
1444
1451
|
export function _activateDerived(platform) {
|
|
1445
1452
|
_derivedPlatform = platform;
|
|
1453
|
+
_activateDerivedCalled = true;
|
|
1446
1454
|
if (_activatedPlatforms.has(platform)) return;
|
|
1447
1455
|
|
|
1448
1456
|
// Only wrap platform.publish if there are actual reactive registrations
|
|
@@ -1543,7 +1551,7 @@ async function _recomputeDerived(entry, platform) {
|
|
|
1543
1551
|
let result;
|
|
1544
1552
|
if (entry.args) {
|
|
1545
1553
|
const _h = _getCtxHelpers(platform);
|
|
1546
|
-
const ctx = _buildCtx(null, null, platform, _h, null);
|
|
1554
|
+
const ctx = _buildCtx(entry.user || null, null, platform, _h, null);
|
|
1547
1555
|
result = await entry.fn(ctx, ...entry.args);
|
|
1548
1556
|
} else {
|
|
1549
1557
|
result = await entry.fn();
|
|
@@ -1563,8 +1571,9 @@ async function _recomputeDerived(entry, platform) {
|
|
|
1563
1571
|
* Wires the instance's resolved sources into _derivedBySource so publishes trigger recomputation.
|
|
1564
1572
|
* @param {Function} fn - The derived compute function
|
|
1565
1573
|
* @param {string} resolvedTopic - The resolved output topic (e.g. '__derived:5:org_123')
|
|
1574
|
+
* @param {any} [user] - The subscribing client's user data, used for ctx during recomputation
|
|
1566
1575
|
*/
|
|
1567
|
-
function _activateDynamicDerived(fn, resolvedTopic) {
|
|
1576
|
+
function _activateDynamicDerived(fn, resolvedTopic, user) {
|
|
1568
1577
|
const entry = _dynamicDerivedByFn.get(fn);
|
|
1569
1578
|
if (!entry) return;
|
|
1570
1579
|
|
|
@@ -1600,7 +1609,8 @@ function _activateDynamicDerived(fn, resolvedTopic) {
|
|
|
1600
1609
|
resolvedSources,
|
|
1601
1610
|
debounce: entry.debounce,
|
|
1602
1611
|
timer: null,
|
|
1603
|
-
refCount: 1
|
|
1612
|
+
refCount: 1,
|
|
1613
|
+
user: user || null
|
|
1604
1614
|
};
|
|
1605
1615
|
|
|
1606
1616
|
entry.instances.set(resolvedTopic, instance);
|
|
@@ -1852,6 +1862,8 @@ export function _prepareHmr() {
|
|
|
1852
1862
|
_streamsWithUnsubscribe.clear();
|
|
1853
1863
|
_hasDynamicDerived = false;
|
|
1854
1864
|
_dynamicDerivedByFn.clear();
|
|
1865
|
+
_activateDerivedCalled = false;
|
|
1866
|
+
_warnedActivateDerived = false;
|
|
1855
1867
|
|
|
1856
1868
|
return snap;
|
|
1857
1869
|
}
|
|
@@ -2334,6 +2346,13 @@ async function _executeSingleRpc(ws, msg, platform, options) {
|
|
|
2334
2346
|
try { await /** @type {any} */ (fn).__onSubscribe(ctx, topic); } catch {}
|
|
2335
2347
|
}
|
|
2336
2348
|
|
|
2349
|
+
if (/** @type {any} */ (fn).__isDerived && !_activateDerivedCalled && !_warnedActivateDerived) {
|
|
2350
|
+
if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'production') {
|
|
2351
|
+
_warnedActivateDerived = true;
|
|
2352
|
+
console.warn('[svelte-realtime] live.derived() subscribed but _activateDerived(platform) was never called. Derived streams will not receive live updates.\n Call _activateDerived(platform) in your WebSocket open hook.\n See: https://svti.me/derived');
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2337
2356
|
// Channel fast-path
|
|
2338
2357
|
if (/** @type {any} */ (fn).__isChannel) {
|
|
2339
2358
|
const emptyValue = streamOpts.merge === 'set' ? null : [];
|