svelte-realtime 0.5.6 → 0.5.7
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/package.json +1 -1
- package/server.js +138 -99
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -3461,15 +3461,6 @@ let _bus = null;
|
|
|
3461
3461
|
*/
|
|
3462
3462
|
let _cronBus = null;
|
|
3463
3463
|
|
|
3464
|
-
/**
|
|
3465
|
-
* Sentinel attached to platforms that have been wrapped with the
|
|
3466
|
-
* process-wide bus. Lets the framework detect already-wrapped inputs
|
|
3467
|
-
* and skip re-wrapping, so a user who passes a manually `bus.wrap`-ed
|
|
3468
|
-
* platform via `createMessage({ platform })` is not double-wrapped by
|
|
3469
|
-
* the auto-wrap path.
|
|
3470
|
-
*/
|
|
3471
|
-
const _BUS_WRAPPED = Symbol.for('svelte-realtime.busWrapped');
|
|
3472
|
-
|
|
3473
3464
|
/**
|
|
3474
3465
|
* Write the process-wide bus. Validated like `configureCron({ bus })`
|
|
3475
3466
|
* - must expose `.wrap(platform)` or be `null`. Mirrored into the
|
|
@@ -5166,43 +5157,73 @@ export function __registerDerived(path, fn) {
|
|
|
5166
5157
|
* triggered externally when the platform fires publish.
|
|
5167
5158
|
* @param {import('svelte-adapter-uws').Platform} platform
|
|
5168
5159
|
*/
|
|
5169
|
-
/**
|
|
5160
|
+
/**
|
|
5161
|
+
* Tracks platforms whose `publish` has been swapped to `derivedPublish`
|
|
5162
|
+
* by `_wrapPlatformPublish`. WeakSet so per-connection platform clones
|
|
5163
|
+
* inherit the mutation via prototype chain without forcing the base
|
|
5164
|
+
* platform to live longer than the adapter intends - entries clear
|
|
5165
|
+
* naturally when the platform itself becomes GC-eligible. The WeakSet
|
|
5166
|
+
* is the single source of truth for "is this platform's publish path
|
|
5167
|
+
* framework-owned?" - consulted by `_ensureWrap` (the universal idempotent
|
|
5168
|
+
* installer), referenced indirectly by every publish surface (RPC, cron,
|
|
5169
|
+
* reactive, top-level `publish()`).
|
|
5170
|
+
*
|
|
5171
|
+
* @type {WeakSet<object>}
|
|
5172
|
+
*/
|
|
5170
5173
|
const _activatedPlatforms = new WeakSet();
|
|
5171
5174
|
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5175
|
+
/**
|
|
5176
|
+
* Universal install point for the framework's publish wrap. Idempotent
|
|
5177
|
+
* against `_activatedPlatforms`, safe under HMR, called from every site
|
|
5178
|
+
* that captures or first sees a platform reference:
|
|
5179
|
+
* - `setCronPlatform(platform)` - call from `realtime().init` or
|
|
5180
|
+
* directly from `hooks.ws.js`'s `init({ platform })`.
|
|
5181
|
+
* - `_activateDerived(platform)` - same call site, alternative entry.
|
|
5182
|
+
* - The default `message` hook + `createMessage` returned hook - first
|
|
5183
|
+
* message per platform installs the wrap, so apps that wire only
|
|
5184
|
+
* `setBus(bus)` and re-export `message` (no init hook, no
|
|
5185
|
+
* `_activateDerived` call) still get cluster routing on first RPC.
|
|
5186
|
+
*
|
|
5187
|
+
* Single install site eliminates the entire class of "outer wrap stacks
|
|
5188
|
+
* on inner wrap" bugs: there is only ONE `bus.wrap(...)` call in the
|
|
5189
|
+
* whole framework (inside `_wrapPlatformPublish`'s `_refreshBusCache`)
|
|
5190
|
+
* and it's composed with everything else (reactive watchers, batched
|
|
5191
|
+
* fast path, replay routing) at publish time via the mutated
|
|
5192
|
+
* `derivedPublish` / `derivedPublishBatched`.
|
|
5193
|
+
*
|
|
5194
|
+
* @param {any} platform
|
|
5195
|
+
*/
|
|
5196
|
+
function _ensureWrap(platform) {
|
|
5197
|
+
if (!platform) return;
|
|
5193
5198
|
// svelte-adapter-uws hands hooks a per-connection platform created via
|
|
5194
|
-
// Object.create(basePlatform). Wrapping that per-connection object
|
|
5195
|
-
// every other connection's inherited publish / publishBatched
|
|
5196
|
-
// because their lookups walk the prototype chain to the
|
|
5197
|
-
// Resolve to the base prototype so the wrap is visible
|
|
5198
|
-
// that share it. Test mocks pass plain objects whose
|
|
5199
|
-
// Object.prototype - in that case wrap the object itself.
|
|
5199
|
+
// Object.create(basePlatform). Wrapping that per-connection object would
|
|
5200
|
+
// leave every other connection's inherited publish / publishBatched
|
|
5201
|
+
// untouched, because their lookups walk the prototype chain to the
|
|
5202
|
+
// original base. Resolve to the base prototype so the wrap is visible
|
|
5203
|
+
// to all connections that share it. Test mocks pass plain objects whose
|
|
5204
|
+
// proto is Object.prototype - in that case wrap the object itself.
|
|
5200
5205
|
const target = _resolveWrapTarget(platform);
|
|
5201
5206
|
if (_activatedPlatforms.has(target)) return;
|
|
5202
5207
|
_activatedPlatforms.add(target);
|
|
5203
5208
|
_wrapPlatformPublish(target);
|
|
5204
5209
|
}
|
|
5205
5210
|
|
|
5211
|
+
export function _activateDerived(platform) {
|
|
5212
|
+
_derivedPlatform = platform;
|
|
5213
|
+
_activateDerivedCalled = true;
|
|
5214
|
+
// Install the framework's publish wrap unconditionally. Pre-0.5.7 this
|
|
5215
|
+
// was gated on "any reactive primitives registered?" to avoid wrap
|
|
5216
|
+
// overhead on apps that didn't use derived/effect/aggregate. With the
|
|
5217
|
+
// wrap now also responsible for bus routing (every publish surface
|
|
5218
|
+
// consults `_getBus()` via `derivedPublish`), gating would create a
|
|
5219
|
+
// window where a publish escapes routing - the late-activation race
|
|
5220
|
+
// from the 0.5.6 audit. The per-publish overhead of an empty wrap is
|
|
5221
|
+
// one function call plus a `Map.has` check on an empty Map (`O(1)`,
|
|
5222
|
+
// branch-predicted to false); the install cost is one closure scope
|
|
5223
|
+
// per platform, paid once at init.
|
|
5224
|
+
_ensureWrap(platform);
|
|
5225
|
+
}
|
|
5226
|
+
|
|
5206
5227
|
/**
|
|
5207
5228
|
* Install the publish wrap retroactively if `_activateDerived(platform)`
|
|
5208
5229
|
* was called against an empty registry and a registration has now landed
|
|
@@ -5221,10 +5242,7 @@ export function _activateDerived(platform) {
|
|
|
5221
5242
|
*/
|
|
5222
5243
|
function _maybeLateActivate() {
|
|
5223
5244
|
if (!_derivedPlatform) return;
|
|
5224
|
-
|
|
5225
|
-
if (_activatedPlatforms.has(target)) return;
|
|
5226
|
-
_activatedPlatforms.add(target);
|
|
5227
|
-
_wrapPlatformPublish(target);
|
|
5245
|
+
_ensureWrap(_derivedPlatform);
|
|
5228
5246
|
}
|
|
5229
5247
|
|
|
5230
5248
|
/**
|
|
@@ -5282,10 +5300,6 @@ function _wrapPlatformPublish(platform) {
|
|
|
5282
5300
|
surrogate.publish = derivedPublishLocal;
|
|
5283
5301
|
if (originalPublishBatched) surrogate.publishBatched = derivedPublishBatchedLocal;
|
|
5284
5302
|
const wrapped = bus.wrap(surrogate);
|
|
5285
|
-
// Tag so a downstream auto-wrap pass (e.g. message hook) can
|
|
5286
|
-
// detect "already wrapped by us" and skip re-wrapping. The tag
|
|
5287
|
-
// records the bus identity so a later swap re-wraps cleanly.
|
|
5288
|
-
/** @type {any} */ (wrapped)[_BUS_WRAPPED] = bus;
|
|
5289
5303
|
_busPublish = typeof wrapped.publish === 'function' ? wrapped.publish.bind(wrapped) : null;
|
|
5290
5304
|
_busPublishBatched = typeof /** @type {any} */ (wrapped).publishBatched === 'function'
|
|
5291
5305
|
? /** @type {any} */ (wrapped).publishBatched.bind(wrapped)
|
|
@@ -5648,6 +5662,12 @@ export function setCronPlatform(platform) {
|
|
|
5648
5662
|
// Re-arm the dedup so a subsequent platform-loss (defensive only --
|
|
5649
5663
|
// platform never goes null in practice) gets one fresh warning.
|
|
5650
5664
|
_cronPlatformWarnFired = false;
|
|
5665
|
+
// Install the framework's publish wrap here too: pure-cron apps that
|
|
5666
|
+
// never call `_activateDerived` (no reactive primitives wired) still
|
|
5667
|
+
// need cluster routing when a bus is configured. The wrap is idempotent
|
|
5668
|
+
// via `_activatedPlatforms`, so when `realtime().init` calls both
|
|
5669
|
+
// `setCronPlatform` and `_activateDerived` the second call is a no-op.
|
|
5670
|
+
if (platform) _ensureWrap(platform);
|
|
5651
5671
|
}
|
|
5652
5672
|
|
|
5653
5673
|
/**
|
|
@@ -6115,17 +6135,15 @@ export async function _tickCron() {
|
|
|
6115
6135
|
}
|
|
6116
6136
|
return;
|
|
6117
6137
|
}
|
|
6118
|
-
// Cluster fan-out
|
|
6119
|
-
//
|
|
6120
|
-
//
|
|
6121
|
-
//
|
|
6122
|
-
//
|
|
6123
|
-
//
|
|
6124
|
-
//
|
|
6125
|
-
//
|
|
6126
|
-
|
|
6127
|
-
// happy path).
|
|
6128
|
-
const cronPub = _cronBus ? _cronBus.wrap(_cronPlatform) : _cronPlatform;
|
|
6138
|
+
// Cluster fan-out is the framework's publish wrap's job
|
|
6139
|
+
// now (one wrap site for the whole framework, installed
|
|
6140
|
+
// by `_ensureWrap` from `setCronPlatform`). The cron tick
|
|
6141
|
+
// uses the captured `_cronPlatform` directly - its
|
|
6142
|
+
// `publish` is `derivedPublish`, which consults the
|
|
6143
|
+
// process-wide bus at publish time. No outer `bus.wrap(...)`
|
|
6144
|
+
// here, which eliminates the 0.5.6 double-relay class of
|
|
6145
|
+
// bugs by construction.
|
|
6146
|
+
const cronPub = _cronPlatform;
|
|
6129
6147
|
const _h = _getCtxHelpers(cronPub);
|
|
6130
6148
|
const ctx = _buildCtx(null, null, cronPub, _h, null);
|
|
6131
6149
|
const result = await entry.fn(ctx);
|
|
@@ -8181,50 +8199,43 @@ export function close(ws, { platform, subscriptions }) {
|
|
|
8181
8199
|
}
|
|
8182
8200
|
|
|
8183
8201
|
/**
|
|
8184
|
-
*
|
|
8185
|
-
*
|
|
8186
|
-
* the
|
|
8187
|
-
*
|
|
8188
|
-
*
|
|
8189
|
-
*
|
|
8202
|
+
* One-shot dev-mode flag for the "createMessage({ platform: callback })
|
|
8203
|
+
* is redundant" warning. A user-supplied `platform` callback in
|
|
8204
|
+
* `createMessage` was the pre-0.5.6 way to wire bus.wrap into the RPC
|
|
8205
|
+
* hook. With 0.5.7+ the framework installs a single publish wrap on the
|
|
8206
|
+
* adapter platform (via `_ensureWrap`, called from
|
|
8207
|
+
* `setCronPlatform` / `_activateDerived` / first message), and that
|
|
8208
|
+
* wrap is the sole `bus.wrap(...)` site. A manual callback that wraps
|
|
8209
|
+
* with `bus.wrap` stacks an outer relay on top of the inner one and
|
|
8210
|
+
* double-delivers every RPC publish to other replicas. We can't detect
|
|
8211
|
+
* the manual-wrap case from the callback's output (user-built wraps
|
|
8212
|
+
* don't carry our sentinel), but the input platform is the activated
|
|
8213
|
+
* adapter platform, so we warn at receive time when both conditions
|
|
8214
|
+
* hold. Module-level so a user creating multiple message hooks sees
|
|
8215
|
+
* one warning total.
|
|
8190
8216
|
*/
|
|
8191
|
-
|
|
8217
|
+
let _manualPlatformCallbackWarnFired = false;
|
|
8192
8218
|
|
|
8193
8219
|
/**
|
|
8194
|
-
*
|
|
8195
|
-
*
|
|
8196
|
-
*
|
|
8197
|
-
* platform is wrapped on first use and memoized for subsequent
|
|
8198
|
-
* messages on the same platform. When the user manually pre-wraps via
|
|
8199
|
-
* `createMessage({ platform })`, this is bypassed (their callback
|
|
8200
|
-
* runs first) so we never double-wrap.
|
|
8201
|
-
*
|
|
8202
|
-
* @param {import('svelte-adapter-uws').Platform} platform
|
|
8203
|
-
* @returns {import('svelte-adapter-uws').Platform}
|
|
8220
|
+
* Reset the one-shot dev-warn flag for tests. Production deployments
|
|
8221
|
+
* don't need this - the warning is meant to fire once per process and
|
|
8222
|
+
* the flag never needs resetting outside test isolation.
|
|
8204
8223
|
*/
|
|
8205
|
-
function
|
|
8206
|
-
|
|
8207
|
-
if (!bus) return platform;
|
|
8208
|
-
// Idempotence: if the input has already been wrapped by this
|
|
8209
|
-
// framework against the current bus, return it untouched.
|
|
8210
|
-
if (/** @type {any} */ (platform)[_BUS_WRAPPED] === bus) return platform;
|
|
8211
|
-
const entry = _rpcBusWrapCache.get(/** @type {any} */ (platform));
|
|
8212
|
-
if (entry && entry.bus === bus && entry.epoch === _busEpoch) return entry.wrapped;
|
|
8213
|
-
const wrapped = bus.wrap(platform);
|
|
8214
|
-
/** @type {any} */ (wrapped)[_BUS_WRAPPED] = bus;
|
|
8215
|
-
_rpcBusWrapCache.set(/** @type {any} */ (platform), { bus, wrapped, epoch: _busEpoch });
|
|
8216
|
-
return wrapped;
|
|
8224
|
+
export function _resetManualPlatformCallbackWarn() {
|
|
8225
|
+
_manualPlatformCallbackWarnFired = false;
|
|
8217
8226
|
}
|
|
8218
8227
|
|
|
8219
8228
|
/**
|
|
8220
|
-
* Ready-made message hook. Re-export from hooks.ws.js for zero-config
|
|
8229
|
+
* Ready-made message hook. Re-export from hooks.ws.js for zero-config
|
|
8230
|
+
* RPC routing.
|
|
8221
8231
|
*
|
|
8222
|
-
*
|
|
8223
|
-
* `
|
|
8224
|
-
*
|
|
8225
|
-
*
|
|
8226
|
-
*
|
|
8227
|
-
* overhead
|
|
8232
|
+
* First call per platform installs the framework's publish wrap via
|
|
8233
|
+
* `_ensureWrap` (idempotent), so apps that wire `setBus(bus)` and
|
|
8234
|
+
* re-export `message` but never call `_activateDerived` /
|
|
8235
|
+
* `setCronPlatform` themselves still get cluster routing on first
|
|
8236
|
+
* RPC. Subsequent calls are no-ops on the wrap path. Without a bus,
|
|
8237
|
+
* the wrap's per-publish overhead is one function call plus a
|
|
8238
|
+
* `Map.has` check on an empty Map - well below noise.
|
|
8228
8239
|
*
|
|
8229
8240
|
* Signature matches the adapter's message hook exactly.
|
|
8230
8241
|
*
|
|
@@ -8232,7 +8243,8 @@ function _autoBusWrap(platform) {
|
|
|
8232
8243
|
* @param {{ data: ArrayBuffer, platform: import('svelte-adapter-uws').Platform }} ctx
|
|
8233
8244
|
*/
|
|
8234
8245
|
export function message(ws, { data, platform }) {
|
|
8235
|
-
|
|
8246
|
+
_ensureWrap(platform);
|
|
8247
|
+
handleRpc(ws, data, platform);
|
|
8236
8248
|
}
|
|
8237
8249
|
|
|
8238
8250
|
/**
|
|
@@ -8253,12 +8265,39 @@ export function createMessage(options) {
|
|
|
8253
8265
|
const hasRpcOpts = beforeExecute || onError;
|
|
8254
8266
|
|
|
8255
8267
|
return function customMessage(ws, { data, platform }) {
|
|
8256
|
-
//
|
|
8257
|
-
//
|
|
8258
|
-
//
|
|
8259
|
-
//
|
|
8260
|
-
//
|
|
8261
|
-
|
|
8268
|
+
// Install the framework's publish wrap on the platform (idempotent
|
|
8269
|
+
// per platform). After this returns, `platform.publish` is
|
|
8270
|
+
// `derivedPublish`, which is the single bus-routing site for the
|
|
8271
|
+
// whole framework. Done BEFORE any transform callback so the
|
|
8272
|
+
// callback sees the wrapped publish path (correct ordering for
|
|
8273
|
+
// non-bus transforms like metrics instrumentation; double-wrap
|
|
8274
|
+
// detected and warned for legacy bus.wrap callbacks).
|
|
8275
|
+
_ensureWrap(platform);
|
|
8276
|
+
let p;
|
|
8277
|
+
if (transformPlatform) {
|
|
8278
|
+
// Dev-only nudge: a `platform` callback against an
|
|
8279
|
+
// already-activated platform with a process-wide bus wired
|
|
8280
|
+
// almost always means a legacy `(p) => bus.wrap(p)` callback
|
|
8281
|
+
// is layered on top of `derivedPublish`'s inner bus.wrap,
|
|
8282
|
+
// which double-relays every RPC publish. Warn once per
|
|
8283
|
+
// process; users with a non-bus transform (e.g. metrics
|
|
8284
|
+
// instrumentation) can ignore.
|
|
8285
|
+
if (_IS_DEV
|
|
8286
|
+
&& !_manualPlatformCallbackWarnFired
|
|
8287
|
+
&& _getBus()
|
|
8288
|
+
) {
|
|
8289
|
+
_manualPlatformCallbackWarnFired = true;
|
|
8290
|
+
console.warn(
|
|
8291
|
+
"[svelte-realtime] createMessage({ platform: callback }) is redundant when `setBus(...)` is wired: " +
|
|
8292
|
+
"the framework already routes ctx.publish through the bus, so a manual `bus.wrap(p)` callback double-relays every RPC publish to other replicas. " +
|
|
8293
|
+
"Drop the `platform` option to fix. If your callback does a non-bus transform (e.g. metrics) you can ignore this warning.\n" +
|
|
8294
|
+
" See: https://svti.me/cluster-relay"
|
|
8295
|
+
);
|
|
8296
|
+
}
|
|
8297
|
+
p = transformPlatform(platform);
|
|
8298
|
+
} else {
|
|
8299
|
+
p = platform;
|
|
8300
|
+
}
|
|
8262
8301
|
const handled = handleRpc(ws, data, p, hasRpcOpts ? rpcOpts : undefined);
|
|
8263
8302
|
if (!handled && onUnhandled) {
|
|
8264
8303
|
onUnhandled(ws, data, p);
|