svelte-realtime 0.4.15 → 0.4.16
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 +35 -1
- package/package.json +1 -1
- package/server.d.ts +19 -1
- package/server.js +206 -14
- package/vite.js +22 -6
package/README.md
CHANGED
|
@@ -1222,6 +1222,40 @@ On the client, derived streams work like regular streams:
|
|
|
1222
1222
|
<p>Orders: {$dashboardStats?.totalOrders}</p>
|
|
1223
1223
|
```
|
|
1224
1224
|
|
|
1225
|
+
### Dynamic derived streams
|
|
1226
|
+
|
|
1227
|
+
When source topics depend on runtime arguments (e.g., an org ID, a room ID), pass a source factory function instead of a static array. The factory receives the same args the client passes at subscribe time:
|
|
1228
|
+
|
|
1229
|
+
```js
|
|
1230
|
+
export const orgStats = live.derived(
|
|
1231
|
+
(orgId) => [`memberships:${orgId}`, `emails:${orgId}`, `audit:${orgId}`],
|
|
1232
|
+
async (ctx, orgId) => {
|
|
1233
|
+
const [members, emails, auditCount] = await Promise.all([
|
|
1234
|
+
db.query('SELECT count(*) FROM memberships WHERE org_id = $1', [orgId]),
|
|
1235
|
+
db.query('SELECT count(*) FROM emails WHERE org_id = $1', [orgId]),
|
|
1236
|
+
db.query('SELECT count(*) FROM audit_log WHERE org_id = $1', [orgId])
|
|
1237
|
+
]);
|
|
1238
|
+
return { members, emails, auditCount };
|
|
1239
|
+
},
|
|
1240
|
+
{ debounce: 100 }
|
|
1241
|
+
);
|
|
1242
|
+
```
|
|
1243
|
+
|
|
1244
|
+
On the client, dynamic derived streams are called like functions:
|
|
1245
|
+
|
|
1246
|
+
```svelte
|
|
1247
|
+
<script>
|
|
1248
|
+
import { orgStats } from '$live/dashboard';
|
|
1249
|
+
let { orgId } = $props();
|
|
1250
|
+
</script>
|
|
1251
|
+
|
|
1252
|
+
<p>Members: {$orgStats(orgId)?.members}</p>
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
Each unique set of args creates an independent instance with its own source subscriptions. Instances are created when the first subscriber connects and cleaned up when the last subscriber disconnects.
|
|
1256
|
+
|
|
1257
|
+
### Activation
|
|
1258
|
+
|
|
1225
1259
|
Call `_activateDerived(platform)` in your `open` hook to enable derived stream listeners:
|
|
1226
1260
|
|
|
1227
1261
|
```js
|
|
@@ -1916,7 +1950,7 @@ Import from `svelte-realtime/server`.
|
|
|
1916
1950
|
| `live.binary(fn, options?)` | Mark a function as a binary RPC handler (`maxSize` limits payload, default 10MB) |
|
|
1917
1951
|
| `live.validated(schema, fn)` | RPC with Zod/Valibot input validation |
|
|
1918
1952
|
| `live.cron(schedule, topic, fn)` | Server-side scheduled function |
|
|
1919
|
-
| `live.derived(sources, fn, options?)` | Server-side computed stream |
|
|
1953
|
+
| `live.derived(sources, fn, options?)` | Server-side computed stream (static or dynamic sources) |
|
|
1920
1954
|
| `live.effect(sources, fn, options?)` | Server-side reactive side effect |
|
|
1921
1955
|
| `live.aggregate(source, reducers, options)` | Real-time incremental aggregation |
|
|
1922
1956
|
| `live.room(config)` | Collaborative room (data + presence + cursors + actions) |
|
package/package.json
CHANGED
package/server.d.ts
CHANGED
|
@@ -477,17 +477,35 @@ export namespace live {
|
|
|
477
477
|
/**
|
|
478
478
|
* Create a server-side computed stream that recomputes when any source topic publishes.
|
|
479
479
|
*
|
|
480
|
-
*
|
|
480
|
+
* Static form: pass an array of topic names to watch.
|
|
481
|
+
* Dynamic form: pass a function that receives runtime args and returns topic names.
|
|
482
|
+
*
|
|
483
|
+
* @param sources - Topic names to watch, or a factory function that receives runtime args
|
|
481
484
|
* @param fn - Async function that computes the derived value
|
|
482
485
|
* @param options - Merge mode and debounce settings
|
|
483
486
|
*
|
|
484
487
|
* @example
|
|
485
488
|
* ```js
|
|
489
|
+
* // Static sources
|
|
486
490
|
* export const summary = live.derived(['orders', 'inventory'], async () => {
|
|
487
491
|
* return { totalOrders: await db.orders.count(), totalItems: await db.inventory.count() };
|
|
488
492
|
* });
|
|
493
|
+
*
|
|
494
|
+
* // Dynamic sources (parameterized)
|
|
495
|
+
* export const stats = live.derived(
|
|
496
|
+
* (orgId) => [`memberships:${orgId}`, `emails:${orgId}`],
|
|
497
|
+
* async (ctx, orgId) => {
|
|
498
|
+
* return { members: await countMembers(orgId), emails: await countEmails(orgId) };
|
|
499
|
+
* },
|
|
500
|
+
* { debounce: 100 }
|
|
501
|
+
* );
|
|
489
502
|
* ```
|
|
490
503
|
*/
|
|
504
|
+
function derived<T extends (...args: any[]) => any>(
|
|
505
|
+
sources: (...args: any[]) => string[],
|
|
506
|
+
fn: T,
|
|
507
|
+
options?: { merge?: string; debounce?: number }
|
|
508
|
+
): T;
|
|
491
509
|
function derived<T extends () => any>(
|
|
492
510
|
sources: string[],
|
|
493
511
|
fn: T,
|
package/server.js
CHANGED
|
@@ -108,6 +108,14 @@ function _copyStreamMeta(target, source) {
|
|
|
108
108
|
if (source.__streamVersion !== undefined) target.__streamVersion = source.__streamVersion;
|
|
109
109
|
if (source.__streamMigrate) target.__streamMigrate = source.__streamMigrate;
|
|
110
110
|
if (source.__isChannel) target.__isChannel = source.__isChannel;
|
|
111
|
+
if (source.__isDerived) target.__isDerived = source.__isDerived;
|
|
112
|
+
if (source.__derivedDynamic) {
|
|
113
|
+
target.__derivedDynamic = source.__derivedDynamic;
|
|
114
|
+
target.__derivedSourceFactory = source.__derivedSourceFactory;
|
|
115
|
+
target.__derivedTopicArgs = source.__derivedTopicArgs;
|
|
116
|
+
target.__derivedDebounce = source.__derivedDebounce;
|
|
117
|
+
}
|
|
118
|
+
if (source.__derivedSources) target.__derivedSources = source.__derivedSources;
|
|
111
119
|
if (source.__isGated) {
|
|
112
120
|
target.__isGated = true;
|
|
113
121
|
target.__gatePredicate = source.__gatePredicate;
|
|
@@ -720,28 +728,69 @@ const derivedRegistry = new Map();
|
|
|
720
728
|
/**
|
|
721
729
|
* Create a server-side computed stream that recomputes when any source topic publishes.
|
|
722
730
|
*
|
|
723
|
-
*
|
|
731
|
+
* Static form: sources is a string[] of topic names.
|
|
732
|
+
* Dynamic form: sources is a function (...args) => string[] that resolves topics at subscribe time.
|
|
733
|
+
*
|
|
734
|
+
* @param {string[] | Function} sources - Topic names to watch, or a factory that receives runtime args
|
|
724
735
|
* @param {Function} fn - Async function that computes the derived value
|
|
725
736
|
* @param {{ merge?: string, debounce?: number }} [options]
|
|
726
737
|
* @returns {Function}
|
|
727
738
|
*/
|
|
728
739
|
live.derived = function derived(sources, fn, options) {
|
|
729
|
-
const
|
|
740
|
+
const baseTopic = /** @type {any} */ (fn).__derivedTopic || ('__derived:' + (_derivedIdCounter++));
|
|
730
741
|
const merge = options?.merge || 'set';
|
|
731
742
|
const debounce = options?.debounce || 0;
|
|
743
|
+
const dynamic = typeof sources === 'function';
|
|
732
744
|
|
|
733
745
|
/** @type {any} */ (fn).__isDerived = true;
|
|
734
746
|
/** @type {any} */ (fn).__isStream = true;
|
|
735
747
|
/** @type {any} */ (fn).__isLive = true;
|
|
736
|
-
/** @type {any} */ (fn).__streamTopic = topic;
|
|
737
748
|
/** @type {any} */ (fn).__streamOptions = { merge, key: 'id' };
|
|
738
|
-
/** @type {any} */ (fn).__derivedSources = sources;
|
|
739
749
|
/** @type {any} */ (fn).__derivedDebounce = debounce;
|
|
750
|
+
|
|
751
|
+
if (dynamic) {
|
|
752
|
+
/** @type {any} */ (fn).__derivedDynamic = true;
|
|
753
|
+
/** @type {any} */ (fn).__derivedSourceFactory = sources;
|
|
754
|
+
/** @type {Map<string, any[]>} */
|
|
755
|
+
const topicArgs = new Map();
|
|
756
|
+
const topicFn = (...args) => {
|
|
757
|
+
const t = baseTopic + '\x00' + args.map(a => String(a).replace(/\x00/g, '')).join('\x00');
|
|
758
|
+
topicArgs.set(t, args);
|
|
759
|
+
if (topicArgs.size > 10000) {
|
|
760
|
+
const iter = topicArgs.keys();
|
|
761
|
+
topicArgs.delete(iter.next().value);
|
|
762
|
+
}
|
|
763
|
+
return t;
|
|
764
|
+
};
|
|
765
|
+
/** @type {any} */ (topicFn).__topicUsesCtx = false;
|
|
766
|
+
/** @type {any} */ (fn).__streamTopic = topicFn;
|
|
767
|
+
/** @type {any} */ (fn).__derivedTopicArgs = topicArgs;
|
|
768
|
+
|
|
769
|
+
/** @type {any} */ (fn).__onSubscribe = function (_ctx, resolvedTopic) {
|
|
770
|
+
_activateDynamicDerived(fn, resolvedTopic);
|
|
771
|
+
};
|
|
772
|
+
/** @type {any} */ (fn).__onUnsubscribe = function (_ctx, resolvedTopic) {
|
|
773
|
+
_deactivateDynamicDerived(fn, resolvedTopic);
|
|
774
|
+
};
|
|
775
|
+
} else {
|
|
776
|
+
/** @type {any} */ (fn).__streamTopic = baseTopic;
|
|
777
|
+
/** @type {any} */ (fn).__derivedSources = sources;
|
|
778
|
+
}
|
|
779
|
+
|
|
740
780
|
return fn;
|
|
741
781
|
};
|
|
742
782
|
|
|
743
783
|
let _derivedIdCounter = 0;
|
|
744
784
|
|
|
785
|
+
/** @type {boolean} Whether any dynamic derived streams have been registered */
|
|
786
|
+
let _hasDynamicDerived = false;
|
|
787
|
+
|
|
788
|
+
/** @type {Map<Function, object>} O(1) lookup from fn reference to dynamic derived registry entry */
|
|
789
|
+
const _dynamicDerivedByFn = new Map();
|
|
790
|
+
|
|
791
|
+
/** @type {import('svelte-adapter-uws').Platform | null} Captured platform for dynamic derived recomputation */
|
|
792
|
+
let _derivedPlatform = null;
|
|
793
|
+
|
|
745
794
|
/** @type {Map<string, { sources: string[], fn: Function, debounce: number, timer: ReturnType<typeof setTimeout> | null }>} */
|
|
746
795
|
const effectRegistry = new Map();
|
|
747
796
|
|
|
@@ -1316,6 +1365,20 @@ export function __registerDerived(path, fn) {
|
|
|
1316
1365
|
_lazyQueue.push({ type: 'derived', path, loader: fn });
|
|
1317
1366
|
return;
|
|
1318
1367
|
}
|
|
1368
|
+
|
|
1369
|
+
if (/** @type {any} */ (fn).__derivedDynamic) {
|
|
1370
|
+
const sourceFactory = /** @type {any} */ (fn).__derivedSourceFactory;
|
|
1371
|
+
const debounce = /** @type {any} */ (fn).__derivedDebounce || 0;
|
|
1372
|
+
const entry = {
|
|
1373
|
+
sources: null, sourceFactory, fn, topic: /** @type {any} */ (fn).__streamTopic,
|
|
1374
|
+
debounce, timer: null, dynamic: true, instances: new Map()
|
|
1375
|
+
};
|
|
1376
|
+
derivedRegistry.set(path, entry);
|
|
1377
|
+
_dynamicDerivedByFn.set(fn, entry);
|
|
1378
|
+
_hasDynamicDerived = true;
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1319
1382
|
const sources = /** @type {any} */ (fn).__derivedSources;
|
|
1320
1383
|
const topic = /** @type {any} */ (fn).__streamTopic;
|
|
1321
1384
|
const debounce = /** @type {any} */ (fn).__derivedDebounce || 0;
|
|
@@ -1339,14 +1402,19 @@ export function __registerDerived(path, fn) {
|
|
|
1339
1402
|
const _activatedPlatforms = new WeakSet();
|
|
1340
1403
|
|
|
1341
1404
|
export function _activateDerived(platform) {
|
|
1405
|
+
_derivedPlatform = platform;
|
|
1342
1406
|
if (_activatedPlatforms.has(platform)) return;
|
|
1343
1407
|
|
|
1344
1408
|
// Only wrap platform.publish if there are actual reactive registrations
|
|
1345
|
-
if (_derivedBySource.size === 0 && _effectBySource.size === 0 && _aggregateBySource.size === 0) {
|
|
1409
|
+
if (_derivedBySource.size === 0 && _effectBySource.size === 0 && _aggregateBySource.size === 0 && !_hasDynamicDerived) {
|
|
1346
1410
|
return;
|
|
1347
1411
|
}
|
|
1348
1412
|
|
|
1349
1413
|
_activatedPlatforms.add(platform);
|
|
1414
|
+
_wrapPlatformPublish(platform);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function _wrapPlatformPublish(platform) {
|
|
1350
1418
|
|
|
1351
1419
|
const originalPublish = platform.publish.bind(platform);
|
|
1352
1420
|
|
|
@@ -1426,20 +1494,121 @@ export function _activateDerived(platform) {
|
|
|
1426
1494
|
|
|
1427
1495
|
/**
|
|
1428
1496
|
* Recompute a derived stream and publish the result.
|
|
1429
|
-
*
|
|
1497
|
+
* For dynamic instances, entry.args holds the runtime args and a ctx is built from the platform.
|
|
1498
|
+
* @param {{ fn: Function, topic: string, args?: any[] }} entry
|
|
1430
1499
|
* @param {import('svelte-adapter-uws').Platform} platform
|
|
1431
1500
|
*/
|
|
1432
1501
|
async function _recomputeDerived(entry, platform) {
|
|
1433
1502
|
try {
|
|
1434
|
-
|
|
1503
|
+
let result;
|
|
1504
|
+
if (entry.args) {
|
|
1505
|
+
const _h = _getCtxHelpers(platform);
|
|
1506
|
+
const ctx = _buildCtx(null, null, platform, _h, null);
|
|
1507
|
+
result = await entry.fn(ctx, ...entry.args);
|
|
1508
|
+
} else {
|
|
1509
|
+
result = await entry.fn();
|
|
1510
|
+
}
|
|
1435
1511
|
platform.publish(entry.topic, 'set', result);
|
|
1436
1512
|
} catch (err) {
|
|
1437
|
-
if (
|
|
1513
|
+
if (_serverErrorHandler) {
|
|
1514
|
+
try { _serverErrorHandler('derived', err); } catch {}
|
|
1515
|
+
} else if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'production') {
|
|
1438
1516
|
console.error(`[svelte-realtime] Derived stream '${entry.topic}' error:`, err);
|
|
1439
1517
|
}
|
|
1440
1518
|
}
|
|
1441
1519
|
}
|
|
1442
1520
|
|
|
1521
|
+
/**
|
|
1522
|
+
* Activate a dynamic derived instance for a resolved topic.
|
|
1523
|
+
* Wires the instance's resolved sources into _derivedBySource so publishes trigger recomputation.
|
|
1524
|
+
* @param {Function} fn - The derived compute function
|
|
1525
|
+
* @param {string} resolvedTopic - The resolved output topic (e.g. '__derived:5:org_123')
|
|
1526
|
+
*/
|
|
1527
|
+
function _activateDynamicDerived(fn, resolvedTopic) {
|
|
1528
|
+
const entry = _dynamicDerivedByFn.get(fn);
|
|
1529
|
+
if (!entry) return;
|
|
1530
|
+
|
|
1531
|
+
const existing = entry.instances.get(resolvedTopic);
|
|
1532
|
+
if (existing) {
|
|
1533
|
+
existing.refCount++;
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// Late activation: if _activateDerived returned early before dynamic entries existed,
|
|
1538
|
+
// wrap platform.publish now that we have something to watch.
|
|
1539
|
+
if (_derivedPlatform && !_activatedPlatforms.has(_derivedPlatform)) {
|
|
1540
|
+
_activatedPlatforms.add(_derivedPlatform);
|
|
1541
|
+
_wrapPlatformPublish(_derivedPlatform);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
const topicArgs = /** @type {any} */ (fn).__derivedTopicArgs;
|
|
1545
|
+
const args = topicArgs && topicArgs.get(resolvedTopic);
|
|
1546
|
+
if (!args) return;
|
|
1547
|
+
|
|
1548
|
+
const resolvedSources = entry.sourceFactory(...args);
|
|
1549
|
+
if (!Array.isArray(resolvedSources) || resolvedSources.length === 0) {
|
|
1550
|
+
if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'production') {
|
|
1551
|
+
console.warn(`[svelte-realtime] Dynamic derived sourceFactory returned empty sources for topic '${resolvedTopic}'`);
|
|
1552
|
+
}
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
const instance = {
|
|
1557
|
+
fn: entry.fn,
|
|
1558
|
+
args,
|
|
1559
|
+
topic: resolvedTopic,
|
|
1560
|
+
resolvedSources,
|
|
1561
|
+
debounce: entry.debounce,
|
|
1562
|
+
timer: null,
|
|
1563
|
+
refCount: 1
|
|
1564
|
+
};
|
|
1565
|
+
|
|
1566
|
+
entry.instances.set(resolvedTopic, instance);
|
|
1567
|
+
|
|
1568
|
+
for (const src of resolvedSources) {
|
|
1569
|
+
let set = _derivedBySource.get(src);
|
|
1570
|
+
if (!set) { set = new Set(); _derivedBySource.set(src, set); }
|
|
1571
|
+
set.add(instance);
|
|
1572
|
+
_watchedTopics.add(src);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
/**
|
|
1577
|
+
* Deactivate a dynamic derived instance when the last subscriber disconnects.
|
|
1578
|
+
* Removes the instance from _derivedBySource and cleans up.
|
|
1579
|
+
* @param {Function} fn - The derived compute function
|
|
1580
|
+
* @param {string} resolvedTopic - The resolved output topic
|
|
1581
|
+
*/
|
|
1582
|
+
function _deactivateDynamicDerived(fn, resolvedTopic) {
|
|
1583
|
+
const entry = _dynamicDerivedByFn.get(fn);
|
|
1584
|
+
if (!entry) return;
|
|
1585
|
+
|
|
1586
|
+
const instance = entry.instances.get(resolvedTopic);
|
|
1587
|
+
if (!instance) return;
|
|
1588
|
+
|
|
1589
|
+
instance.refCount--;
|
|
1590
|
+
if (instance.refCount > 0) return;
|
|
1591
|
+
|
|
1592
|
+
if (instance.timer) clearTimeout(instance.timer);
|
|
1593
|
+
|
|
1594
|
+
for (const src of instance.resolvedSources) {
|
|
1595
|
+
const set = _derivedBySource.get(src);
|
|
1596
|
+
if (set) {
|
|
1597
|
+
set.delete(instance);
|
|
1598
|
+
if (set.size === 0) {
|
|
1599
|
+
_derivedBySource.delete(src);
|
|
1600
|
+
if (!_effectBySource.has(src) && !_aggregateBySource.has(src)) {
|
|
1601
|
+
_watchedTopics.delete(src);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
entry.instances.delete(resolvedTopic);
|
|
1608
|
+
const topicArgs = /** @type {any} */ (fn).__derivedTopicArgs;
|
|
1609
|
+
if (topicArgs) topicArgs.delete(resolvedTopic);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1443
1612
|
/**
|
|
1444
1613
|
* Fire an effect handler. Errors are caught and routed to the error handler.
|
|
1445
1614
|
* @param {{ fn: Function }} entry
|
|
@@ -1606,7 +1775,12 @@ export function _prepareHmr() {
|
|
|
1606
1775
|
};
|
|
1607
1776
|
|
|
1608
1777
|
// Clear debounce timers
|
|
1609
|
-
for (const e of derivedRegistry.values()) {
|
|
1778
|
+
for (const e of derivedRegistry.values()) {
|
|
1779
|
+
if (e.timer) clearTimeout(e.timer);
|
|
1780
|
+
if (e.instances) {
|
|
1781
|
+
for (const inst of e.instances.values()) { if (inst.timer) clearTimeout(inst.timer); }
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1610
1784
|
for (const e of effectRegistry.values()) { if (e.timer) clearTimeout(e.timer); }
|
|
1611
1785
|
for (const e of aggregateRegistry.values()) { if (e.timer) clearTimeout(e.timer); }
|
|
1612
1786
|
|
|
@@ -1636,6 +1810,8 @@ export function _prepareHmr() {
|
|
|
1636
1810
|
_aggregateByTopic.clear();
|
|
1637
1811
|
_watchedTopics.clear();
|
|
1638
1812
|
_streamsWithUnsubscribe.clear();
|
|
1813
|
+
_hasDynamicDerived = false;
|
|
1814
|
+
_dynamicDerivedByFn.clear();
|
|
1639
1815
|
|
|
1640
1816
|
return snap;
|
|
1641
1817
|
}
|
|
@@ -1662,11 +1838,27 @@ export function _restoreHmr(snap) {
|
|
|
1662
1838
|
for (const [k, v] of snap.derived) {
|
|
1663
1839
|
v.timer = null;
|
|
1664
1840
|
derivedRegistry.set(k, v);
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1841
|
+
if (v.dynamic) {
|
|
1842
|
+
_hasDynamicDerived = true;
|
|
1843
|
+
_dynamicDerivedByFn.set(v.fn, v);
|
|
1844
|
+
if (v.instances) {
|
|
1845
|
+
for (const inst of v.instances.values()) {
|
|
1846
|
+
inst.timer = null;
|
|
1847
|
+
for (const src of inst.resolvedSources) {
|
|
1848
|
+
let set = _derivedBySource.get(src);
|
|
1849
|
+
if (!set) { set = new Set(); _derivedBySource.set(src, set); }
|
|
1850
|
+
set.add(inst);
|
|
1851
|
+
_watchedTopics.add(src);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
} else {
|
|
1856
|
+
for (const src of v.sources) {
|
|
1857
|
+
let set = _derivedBySource.get(src);
|
|
1858
|
+
if (!set) { set = new Set(); _derivedBySource.set(src, set); }
|
|
1859
|
+
set.add(v);
|
|
1860
|
+
_watchedTopics.add(src);
|
|
1861
|
+
}
|
|
1670
1862
|
}
|
|
1671
1863
|
}
|
|
1672
1864
|
|
package/vite.js
CHANGED
|
@@ -12,6 +12,7 @@ const DYNAMIC_STREAM_RE = /export\s+const\s+(\w+)\s*=\s*live\.stream\s*\(\s*(?:\
|
|
|
12
12
|
const CRON_EXPORT_RE = /export\s+const\s+(\w+)\s*=\s*live\.cron\s*\(/g;
|
|
13
13
|
const BINARY_EXPORT_RE = /export\s+const\s+(\w+)\s*=\s*live\.binary\s*\(/g;
|
|
14
14
|
const DERIVED_EXPORT_RE = /export\s+const\s+(\w+)\s*=\s*live\.derived\s*\(/g;
|
|
15
|
+
const DYNAMIC_DERIVED_RE = /export\s+const\s+(\w+)\s*=\s*live\.derived\s*\(\s*(?:\([^)]*\)|[a-zA-Z_$][\w$]*)\s*=>/g;
|
|
15
16
|
const ROOM_EXPORT_RE = /export\s+const\s+(\w+)\s*=\s*live\.room\s*\(/g;
|
|
16
17
|
const WEBHOOK_EXPORT_RE = /export\s+const\s+(\w+)\s*=\s*live\.webhook\s*\(/g;
|
|
17
18
|
const CHANNEL_EXPORT_RE = /export\s+const\s+(\w+)\s*=\s*live\.channel\s*\(/g;
|
|
@@ -518,8 +519,8 @@ function _generateSsrStubs(filePath, modulePath) {
|
|
|
518
519
|
const dynamicNames = new Set();
|
|
519
520
|
let match;
|
|
520
521
|
|
|
521
|
-
// Detect dynamic (function-returning) streams and
|
|
522
|
-
for (const re of [DYNAMIC_STREAM_RE, DYNAMIC_CHANNEL_RE]) {
|
|
522
|
+
// Detect dynamic (function-returning) streams, channels, and derived
|
|
523
|
+
for (const re of [DYNAMIC_STREAM_RE, DYNAMIC_CHANNEL_RE, DYNAMIC_DERIVED_RE]) {
|
|
523
524
|
re.lastIndex = 0;
|
|
524
525
|
while ((match = re.exec(source)) !== null) {
|
|
525
526
|
dynamicNames.add(match[1]);
|
|
@@ -683,8 +684,12 @@ function _generateClientStubs(filePath, modulePath, dir) {
|
|
|
683
684
|
if (!exportedNames.has(name)) {
|
|
684
685
|
exportedNames.add(name);
|
|
685
686
|
imports.add('__stream');
|
|
686
|
-
|
|
687
|
-
|
|
687
|
+
const isDynamic = _isDynamicExport(source, name, 'live\\.derived');
|
|
688
|
+
if (isDynamic) {
|
|
689
|
+
lines.push(`export const ${name} = __stream('${modulePath}/${name}', ${JSON.stringify({ merge: 'set', key: 'id' })}, true);`);
|
|
690
|
+
} else {
|
|
691
|
+
lines.push(`export const ${name} = __stream('${modulePath}/${name}', ${JSON.stringify({ merge: 'set', key: 'id' })});`);
|
|
692
|
+
}
|
|
688
693
|
}
|
|
689
694
|
}
|
|
690
695
|
|
|
@@ -1645,14 +1650,25 @@ function _generateTypeDeclarations(liveDir, dir) {
|
|
|
1645
1650
|
}
|
|
1646
1651
|
}
|
|
1647
1652
|
|
|
1648
|
-
// Detect live.derived() exports (read-only stream)
|
|
1653
|
+
// Detect live.derived() exports (read-only stream, static or dynamic)
|
|
1649
1654
|
DERIVED_EXPORT_RE.lastIndex = 0;
|
|
1650
1655
|
while ((match = DERIVED_EXPORT_RE.exec(source)) !== null) {
|
|
1651
1656
|
const name = match[1];
|
|
1652
1657
|
handledNames.add(name);
|
|
1653
1658
|
if (!exports.some(e => e.includes(`export const ${name}:`))) {
|
|
1654
1659
|
needsStreamStore = true;
|
|
1655
|
-
|
|
1660
|
+
const isDynamic = _isDynamicExport(source, name, 'live\\.derived');
|
|
1661
|
+
const loadSig = `{ load(platform: any, options?: { args?: any[]; user?: any }): Promise<any> }`;
|
|
1662
|
+
if (isDynamic) {
|
|
1663
|
+
if (isTS) {
|
|
1664
|
+
const factoryParams = _extractDynamicFactoryParams(source, name, 'live\\.derived');
|
|
1665
|
+
exports.push(` export const ${name}: (${factoryParams} => StreamStore<any>) & ${loadSig};`);
|
|
1666
|
+
} else {
|
|
1667
|
+
exports.push(` export const ${name}: ((...args: any[]) => StreamStore<any>) & ${loadSig};`);
|
|
1668
|
+
}
|
|
1669
|
+
} else {
|
|
1670
|
+
exports.push(` export const ${name}: StreamStore<any> & ${loadSig};`);
|
|
1671
|
+
}
|
|
1656
1672
|
}
|
|
1657
1673
|
}
|
|
1658
1674
|
|