svelte-adapter-uws 0.6.0-next.13 → 0.6.0-next.15

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
@@ -442,6 +442,7 @@ These options control how the server handles misbehaving or slow clients at the
442
442
  - `maxConcurrent` caps how many upgrades may be in flight at once. Crossed requests get a fast `503 Service Unavailable` before any per-request work, so a connection storm can be shed without spending CPU on TLS, header parsing, or cookie decoding. Set this just above your steady-state in-flight count to act as a circuit breaker.
443
443
  - `perTickBudget` caps how many actual `res.upgrade()` calls run per Node.js event-loop tick. Once the budget is spent, subsequent calls are deferred via `setImmediate` so the loop is not starved by 10K synchronous handshakes from one I/O batch. Pre-upgrade work (rate limit, origin check, hook dispatch) still runs in the original tick; only the hand-off to the C++ upgrade path is paced. Start with `64` and adjust based on your peak burst envelope.
444
444
  - `waitingRoom` upgrades the over-capacity rejection from a bare `503` to a content-negotiated waiting room: a browser navigation gets a self-polling HTML holding page that auto-reloads when capacity frees, while a WebSocket upgrade or non-HTML client keeps a `503` with a jittered `Retry-After`. On by default once `maxConcurrent > 0`; set `waitingRoom: false` for the exact bare `503`. The page polls a read-only `/__admit-check` endpoint (`202` with a queue-depth/ETA body while full, `200` when capacity exists) that consumes no gate slot. Tune with `waitingRoom: { path, admitCheckPath, retryAfterSeconds, pollIntervalMs, template }`.
445
+ - `cursorLane` reserves a fraction of `maxConcurrent` (default `0.25`, at least one slot) for a deprioritised cursor-only upgrade lane - a second WebSocket that requests the `svelte-realtime-cursor` subprotocol. A cursor upgrade is admitted only while both the main ceiling and the cursor sub-budget have room, so a flood of cursor connects can never starve main-WebSocket admission; the main lane never waits on the cursor sub-budget. The cursor lane is refused first and, under `siege`, refused entirely - always with a bare `503` (never the holding page, since the cursor connection is not a browser). Omit `cursorLane` to disable the lane: the second counter never increments and admission is unchanged. Set it with `cursorLane: { fraction }`.
445
446
 
446
447
  ```js
447
448
  adapter({
@@ -0,0 +1,109 @@
1
+ // Injectable runtime environment for the BROWSER client bundle: the clock, RNG
2
+ // and timers that the client reads through named helpers instead of touching the
3
+ // native primitives directly. Same helper API and env shape as the server-side
4
+ // runtime module so call sites are identical, but backed by browser globals (no
5
+ // node: imports) so the client bundles can import it. A simulator running the
6
+ // client code under node still drives it via setRuntimeEnv.
7
+ //
8
+ // The browser clock is a DIRECT wall read (the clients never had a 1Hz cache,
9
+ // so a direct read preserves their existing behavior). `performance` is used for
10
+ // monotonic duration math when present; a fake-timer Date mock therefore
11
+ // propagates straight into now() with no extra bridge.
12
+
13
+ const _hasPerf = typeof globalThis.performance !== 'undefined' && typeof globalThis.performance.now === 'function';
14
+ // Snapshot at load: the wall time at performance.now() === 0. Adding
15
+ // performance.now() yields a monotonic ms-since-epoch value immune to clock steps.
16
+ const _processStartEpoch = _hasPerf ? Date.now() - globalThis.performance.now() : 0;
17
+ const _webcrypto = (typeof globalThis.crypto !== 'undefined') ? globalThis.crypto : undefined;
18
+
19
+ // v4-shaped fallback when Web Crypto randomUUID is unavailable (non-secure
20
+ // context / old browser). Uses the env RNG so a seeded run still reproduces it.
21
+ function _uuidFallback(rngFloat) {
22
+ let out = '';
23
+ for (let i = 0; i < 36; i++) {
24
+ if (i === 8 || i === 13 || i === 18 || i === 23) { out += '-'; continue; }
25
+ if (i === 14) { out += '4'; continue; }
26
+ const r = (rngFloat() * 16) | 0;
27
+ out += (i === 19 ? ((r & 0x3) | 0x8) : r).toString(16);
28
+ }
29
+ return out;
30
+ }
31
+
32
+ // One frozen environment object, one stable hidden class. In a real browser
33
+ // `current === defaultEnv` for the whole page lifetime (no override ever
34
+ // installs), so V8 sees a monomorphic shape and inlines the helpers to the
35
+ // native primitives - zero measurable overhead on the hot path.
36
+ const defaultEnv = Object.freeze({
37
+ clock: Object.freeze({
38
+ now: () => Date.now(), // exact wall clock; direct read
39
+ monotonic: _hasPerf ? () => _processStartEpoch + globalThis.performance.now() : () => Date.now(), // duration math
40
+ wallEpoch: () => Date.now() // exact wall clock; process-identity baseline
41
+ }),
42
+ rng: Object.freeze({
43
+ float: () => Math.random(),
44
+ u32: () => (Math.random() * 0x100000000) >>> 0,
45
+ uuid: (_webcrypto && typeof _webcrypto.randomUUID === 'function')
46
+ ? () => _webcrypto.randomUUID()
47
+ : () => _uuidFallback(() => Math.random()),
48
+ bytes: (_webcrypto && typeof _webcrypto.getRandomValues === 'function')
49
+ ? (n) => _webcrypto.getRandomValues(new Uint8Array(n))
50
+ : (n) => { const a = new Uint8Array(n); for (let i = 0; i < n; i++) a[i] = (Math.random() * 256) | 0; return a; }
51
+ }),
52
+ timers: Object.freeze({
53
+ set: (cb, ms, ...a) => setTimeout(cb, ms, ...a),
54
+ setInterval: (cb, ms, ...a) => setInterval(cb, ms, ...a),
55
+ // No setImmediate in the browser: a zero-delay macrotask is the closest.
56
+ setImmediate: (cb, ...a) => setTimeout(cb, 0, ...a),
57
+ clear: (h) => clearTimeout(h),
58
+ clearInterval: (h) => clearInterval(h),
59
+ queueMicrotask: (typeof globalThis.queueMicrotask === 'function')
60
+ ? (cb) => globalThis.queueMicrotask(cb)
61
+ : (cb) => Promise.resolve().then(cb)
62
+ }),
63
+ tz: undefined // effective timezone for cron evaluation; undefined = real local TZ
64
+ });
65
+
66
+ let current = defaultEnv;
67
+
68
+ // The named helpers are the ONLY thing client code imports. Each is a one-line
69
+ // read over `current` - monomorphic in a real browser, inlined by V8.
70
+ export const now = () => current.clock.now();
71
+ export const monotonicNow = () => current.clock.monotonic();
72
+ export const wallEpoch = () => current.clock.wallEpoch();
73
+ export const randomFloat = () => current.rng.float();
74
+ export const randomU32 = () => current.rng.u32();
75
+ export const randomUuid = () => current.rng.uuid();
76
+ export const randomBytes = (n) => current.rng.bytes(n);
77
+ export const setTimer = (cb, ms, ...a) => current.timers.set(cb, ms, ...a);
78
+ export const setIntervalTimer = (cb, ms, ...a) => current.timers.setInterval(cb, ms, ...a);
79
+ export const setImmediateTimer = (cb, ...a) => current.timers.setImmediate(cb, ...a);
80
+ export const clearTimer = (h) => current.timers.clear(h);
81
+ export const clearIntervalTimer = (h) => current.timers.clearInterval(h);
82
+ export const microtask = (cb) => current.timers.queueMicrotask(cb);
83
+ export const effectiveTimeZone = () => current.tz;
84
+
85
+ // Install a virtual environment (the simulator/test harness only). Refuses under
86
+ // a node production build unless explicitly forced, so a stray call can never
87
+ // swap the clock under a live deployment; in a real browser there is no process
88
+ // and nothing calls this anyway. A partial env merges over the native defaults,
89
+ // so a harness can override just the clock and keep native rng/timers.
90
+ export function setRuntimeEnv(env, opts) {
91
+ const force = opts && opts.force === true;
92
+ if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'production' && !force) {
93
+ throw new Error('client runtime: setRuntimeEnv refused in production (pass { force: true } only inside a controlled simulation harness)');
94
+ }
95
+ current = Object.freeze({
96
+ clock: Object.freeze({ ...defaultEnv.clock, ...(env && env.clock) }),
97
+ rng: Object.freeze({ ...defaultEnv.rng, ...(env && env.rng) }),
98
+ timers: Object.freeze({ ...defaultEnv.timers, ...(env && env.timers) }),
99
+ tz: env && Object.prototype.hasOwnProperty.call(env, 'tz') ? env.tz : defaultEnv.tz
100
+ });
101
+ return current;
102
+ }
103
+
104
+ // Restore the native environment. Cheap wholesale reassignment (no per-field
105
+ // mutation), so the hidden class stays stable.
106
+ export function resetRuntimeEnv() { current = defaultEnv; }
107
+
108
+ // Read-only accessor for the active env (test/sim introspection only).
109
+ export function getRuntimeEnv() { return current; }
package/client.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { writable, derived } from 'svelte/store';
2
2
  import { parseBinaryFrame, requestNFrame } from './files/wire.js';
3
+ import { now, randomFloat, setTimer, setIntervalTimer, clearTimer, clearIntervalTimer, microtask } from './client-runtime.js';
3
4
 
4
5
  /** @type {ReturnType<typeof createConnection> | null} */
5
6
  let singleton = null;
@@ -290,7 +291,7 @@ export function ready() {
290
291
  function cleanup() {
291
292
  if (settled) return;
292
293
  settled = true;
293
- queueMicrotask(() => {
294
+ microtask(() => {
294
295
  statusUnsub?.();
295
296
  permaUnsub?.();
296
297
  });
@@ -403,20 +404,20 @@ export function crud(topic, initial = [], options = {}) {
403
404
  let list = [...initial];
404
405
  /** @type {Map<string, number>} */
405
406
  const timestamps = new Map();
406
- const now = Date.now();
407
+ const seededAt = now();
407
408
  for (const item of initial) {
408
- timestamps.set(keyOf(item), now);
409
+ timestamps.set(keyOf(item), seededAt);
409
410
  }
410
411
 
411
412
  const output = writable(list);
412
413
  /** @type {(() => void) | null} */
413
414
  let sourceUnsub = null;
414
- /** @type {ReturnType<typeof setInterval> | null} */
415
+ /** @type {ReturnType<typeof setIntervalTimer> | null} */
415
416
  let sweepTimer = null;
416
417
  let subCount = 0;
417
418
 
418
419
  function sweep() {
419
- const cutoff = Date.now() - /** @type {number} */ (maxAge);
420
+ const cutoff = now() - /** @type {number} */ (maxAge);
420
421
  let changed = false;
421
422
  for (const [id, ts] of timestamps) {
422
423
  if (ts < cutoff) {
@@ -437,21 +438,21 @@ export function crud(topic, initial = [], options = {}) {
437
438
  if (data == null || typeof data !== 'object') return;
438
439
  const id = keyOf(data);
439
440
  if (evt === 'deleted') timestamps.delete(id);
440
- else timestamps.set(id, Date.now());
441
+ else timestamps.set(id, now());
441
442
  list = applyCrudReducer(list, evt, data, arrayCrudStorage, reducerOpts);
442
443
  output.set(list);
443
444
  });
444
- sweepTimer = setInterval(sweep, Math.max(maxAge / 2, 1000));
445
+ sweepTimer = setIntervalTimer(sweep, Math.max(maxAge / 2, 1000));
445
446
  }
446
447
 
447
448
  function stop() {
448
449
  if (sourceUnsub) { sourceUnsub(); sourceUnsub = null; }
449
- if (sweepTimer) { clearInterval(sweepTimer); sweepTimer = null; }
450
+ if (sweepTimer) { clearIntervalTimer(sweepTimer); sweepTimer = null; }
450
451
  list = [...initial];
451
- const now = Date.now();
452
+ const seededAt = now();
452
453
  timestamps.clear();
453
454
  for (const item of initial) {
454
- timestamps.set(keyOf(item), now);
455
+ timestamps.set(keyOf(item), seededAt);
455
456
  }
456
457
  output.set(list);
457
458
  }
@@ -508,20 +509,20 @@ export function lookup(topic, initial = [], options = {}) {
508
509
  let map = { ...initialMap };
509
510
  /** @type {Map<string, number>} */
510
511
  const timestamps = new Map();
511
- const now = Date.now();
512
+ const seededAt = now();
512
513
  for (const id in initialMap) {
513
- timestamps.set(id, now);
514
+ timestamps.set(id, seededAt);
514
515
  }
515
516
 
516
517
  const output = writable(map);
517
518
  /** @type {(() => void) | null} */
518
519
  let sourceUnsub = null;
519
- /** @type {ReturnType<typeof setInterval> | null} */
520
+ /** @type {ReturnType<typeof setIntervalTimer> | null} */
520
521
  let sweepTimer = null;
521
522
  let subCount = 0;
522
523
 
523
524
  function sweep() {
524
- const cutoff = Date.now() - /** @type {number} */ (maxAge);
525
+ const cutoff = now() - /** @type {number} */ (maxAge);
525
526
  let changed = false;
526
527
  for (const [id, ts] of timestamps) {
527
528
  if (ts < cutoff) {
@@ -544,7 +545,7 @@ export function lookup(topic, initial = [], options = {}) {
544
545
  if (data == null || typeof data !== 'object') return;
545
546
  const id = keyOf(data);
546
547
  if (evt === 'deleted') timestamps.delete(id);
547
- else timestamps.set(id, Date.now());
548
+ else timestamps.set(id, now());
548
549
  const next = applyCrudReducer(map, evt, data, recordCrudStorage, reducerOpts);
549
550
  if (next === map) return;
550
551
  map = next;
@@ -552,17 +553,17 @@ export function lookup(topic, initial = [], options = {}) {
552
553
  });
553
554
  // Sweep at half the maxAge interval for responsive cleanup
554
555
  // without burning cycles on very short intervals
555
- sweepTimer = setInterval(sweep, Math.max(maxAge / 2, 1000));
556
+ sweepTimer = setIntervalTimer(sweep, Math.max(maxAge / 2, 1000));
556
557
  }
557
558
 
558
559
  function stop() {
559
560
  if (sourceUnsub) { sourceUnsub(); sourceUnsub = null; }
560
- if (sweepTimer) { clearInterval(sweepTimer); sweepTimer = null; }
561
+ if (sweepTimer) { clearIntervalTimer(sweepTimer); sweepTimer = null; }
561
562
  map = { ...initialMap };
562
- const now = Date.now();
563
+ const seededAt = now();
563
564
  timestamps.clear();
564
565
  for (const id in initialMap) {
565
- timestamps.set(id, now);
566
+ timestamps.set(id, seededAt);
566
567
  }
567
568
  output.set(map);
568
569
  }
@@ -638,8 +639,8 @@ export function once(topic, event, options) {
638
639
  function cleanup() {
639
640
  if (settled) return;
640
641
  settled = true;
641
- if (timer) clearTimeout(timer);
642
- queueMicrotask(() => unsub());
642
+ if (timer) clearTimer(timer);
643
+ microtask(() => unsub());
643
644
  }
644
645
 
645
646
  const unsub = store.subscribe((data) => {
@@ -652,7 +653,7 @@ export function once(topic, event, options) {
652
653
  }
653
654
  });
654
655
  if (timeout !== undefined) {
655
- timer = setTimeout(() => {
656
+ timer = setTimer(() => {
656
657
  cleanup();
657
658
  reject(new Error(`once('${topic}'${event ? `, '${event}'` : ''}) timed out after ${timeout}ms`));
658
659
  }, timeout);
@@ -716,21 +717,23 @@ export function classifyCloseCode(code) {
716
717
  * cap by attempt 6) and gentle enough that a brief restart resolves
717
718
  * before the user notices.
718
719
  *
719
- * Pure: no I/O, no globals. Pass a deterministic `randFactor` for
720
- * reproducible assertions in tests.
720
+ * Pure given an explicit `randFactor`: no I/O, no globals. Pass a fixed
721
+ * value for reproducible assertions in tests.
721
722
  *
722
- * The default `Math.random()` is the correct primitive here: this value
723
- * is reconnect-backoff jitter, used to spread retries across a fleet so a
723
+ * The default `randFactor` is the runtime float source: this value is
724
+ * reconnect-backoff jitter, used to spread retries across a fleet so a
724
725
  * server restart does not hit a thundering-herd. Not security-relevant -
725
- * the randFactor never crosses a trust boundary.
726
+ * the randFactor never crosses a trust boundary - so the runtime source is
727
+ * the right primitive; routing it through the runtime also lets a seeded
728
+ * harness reproduce the reconnect schedule exactly.
726
729
  *
727
730
  * @param {number} base base interval in ms (e.g. 3000)
728
731
  * @param {number} maxDelay cap in ms (e.g. 300000)
729
732
  * @param {number} attempt zero-based attempt counter
730
- * @param {number} [randFactor] random factor in [0, 1); defaults to Math.random()
733
+ * @param {number} [randFactor] random factor in [0, 1); defaults to randomFloat()
731
734
  * @returns {number}
732
735
  */
733
- export function nextReconnectDelay(base, maxDelay, attempt, randFactor = Math.random()) {
736
+ export function nextReconnectDelay(base, maxDelay, attempt, randFactor = randomFloat()) {
734
737
  const capped = Math.min(base * Math.pow(2.2, attempt), maxDelay);
735
738
  return capped * (0.75 + randFactor * 0.5);
736
739
  }
@@ -758,9 +761,9 @@ function createConnection(options) {
758
761
  /** @type {WebSocket | null} */
759
762
  let ws = null;
760
763
 
761
- /** @type {ReturnType<typeof setTimeout> | null} */
764
+ /** @type {ReturnType<typeof setTimer> | null} */
762
765
  let reconnectTimer = null;
763
- /** @type {ReturnType<typeof setInterval> | null} */
766
+ /** @type {ReturnType<typeof setIntervalTimer> | null} */
764
767
  let activityTimer = null;
765
768
 
766
769
  /** @type {Promise<boolean> | null} deduped in-flight auth preflight */
@@ -777,7 +780,7 @@ function createConnection(options) {
777
780
  let hiddenDisconnect = false;
778
781
  // Timestamp of the last message received from the server. Used to detect
779
782
  // zombie connections - cases where onclose was suppressed by browser throttling.
780
- let lastServerMessage = Date.now();
783
+ let lastServerMessage = now();
781
784
  // 2.5x the server's 120s idle timeout. If the server has been completely
782
785
  // silent for this long while the socket appears open, it is likely a zombie.
783
786
  const SERVER_TIMEOUT_MS = 150000;
@@ -929,7 +932,7 @@ function createConnection(options) {
929
932
  let _onFlowDegraded = null;
930
933
 
931
934
  function _flowFresh() {
932
- return _flowAvail > 0 && Date.now() < _flowExpiresAt;
935
+ return _flowAvail > 0 && now() < _flowExpiresAt;
933
936
  }
934
937
  function _setFlowDegraded(d) {
935
938
  if (d === _flowDegraded) return;
@@ -963,7 +966,7 @@ function createConnection(options) {
963
966
  // Apply a fresh window and drain the queue in FIFO order.
964
967
  function _applyFlowWindow(count, ttlMs) {
965
968
  _flowActive = true;
966
- _flowExpiresAt = Date.now() + ttlMs;
969
+ _flowExpiresAt = now() + ttlMs;
967
970
  _flowAvail = count;
968
971
  // A fresh window clears the replenish latch so the next low-water
969
972
  // crossing can ask for more again.
@@ -1210,7 +1213,7 @@ function createConnection(options) {
1210
1213
 
1211
1214
  ws.onopen = () => {
1212
1215
  attempt = 0;
1213
- lastServerMessage = Date.now();
1216
+ lastServerMessage = now();
1214
1217
  failureStore.set(null);
1215
1218
  setStatusOpen();
1216
1219
  if (debug) console.log('[ws] connected');
@@ -1308,7 +1311,7 @@ function createConnection(options) {
1308
1311
  }
1309
1312
 
1310
1313
  ws.onmessage = (rawEvent) => {
1311
- lastServerMessage = Date.now();
1314
+ lastServerMessage = now();
1312
1315
  try {
1313
1316
  // Inbound binary demux, ahead of the JSON path. A 0x03 frame is
1314
1317
  // a binary topic PAYLOAD: resolve its numeric topic-id to a name,
@@ -1504,7 +1507,7 @@ function createConnection(options) {
1504
1507
  }
1505
1508
  const delay = nextReconnectDelay(reconnectInterval, maxReconnectInterval, attempt);
1506
1509
  attempt++;
1507
- reconnectTimer = setTimeout(() => {
1510
+ reconnectTimer = setTimer(() => {
1508
1511
  reconnectTimer = null;
1509
1512
  doConnect();
1510
1513
  }, delay);
@@ -1538,7 +1541,7 @@ function createConnection(options) {
1538
1541
  if (ws?.readyState !== WebSocket.OPEN) return;
1539
1542
  if (!pendingSubscribes) {
1540
1543
  pendingSubscribes = [];
1541
- queueMicrotask(flushPendingSubscribes);
1544
+ microtask(flushPendingSubscribes);
1542
1545
  }
1543
1546
  pendingSubscribes.push(topic);
1544
1547
  }
@@ -1649,7 +1652,7 @@ function createConnection(options) {
1649
1652
  // that never mount). Safe: if another wrapper for the same topic has
1650
1653
  // an active subscriber, topicRefCounts will be non-empty and we skip.
1651
1654
  const ownStore = store;
1652
- queueMicrotask(() => {
1655
+ microtask(() => {
1653
1656
  if (subs === 0 && !topicRefCounts.has(topic) && topicStores.get(topic) === ownStore) {
1654
1657
  topicStores.delete(topic);
1655
1658
  }
@@ -1698,7 +1701,7 @@ function createConnection(options) {
1698
1701
  store = writable(null);
1699
1702
  eventStores.set(key, store);
1700
1703
  const ownStore = store;
1701
- queueMicrotask(() => {
1704
+ microtask(() => {
1702
1705
  if (subs === 0 && !topicRefCounts.has(topic) && eventStores.get(key) === ownStore) {
1703
1706
  eventStores.delete(key);
1704
1707
  }
@@ -1800,11 +1803,11 @@ function createConnection(options) {
1800
1803
  intentionallyClosed = true;
1801
1804
  permaClosedStore.set(true);
1802
1805
  if (reconnectTimer) {
1803
- clearTimeout(reconnectTimer);
1806
+ clearTimer(reconnectTimer);
1804
1807
  reconnectTimer = null;
1805
1808
  }
1806
1809
  if (activityTimer) {
1807
- clearInterval(activityTimer);
1810
+ clearIntervalTimer(activityTimer);
1808
1811
  activityTimer = null;
1809
1812
  }
1810
1813
  if (visibilityHandler && typeof document !== 'undefined') {
@@ -1853,7 +1856,7 @@ function createConnection(options) {
1853
1856
  hiddenDisconnect = false;
1854
1857
  attempt = 0;
1855
1858
  if (reconnectTimer) {
1856
- clearTimeout(reconnectTimer);
1859
+ clearTimer(reconnectTimer);
1857
1860
  reconnectTimer = null;
1858
1861
  }
1859
1862
  doConnect();
@@ -1867,9 +1870,9 @@ function createConnection(options) {
1867
1870
  // on mobile after wake from sleep). Force a close so onclose fires and the
1868
1871
  // normal reconnect path takes over.
1869
1872
  if (typeof window !== 'undefined') {
1870
- activityTimer = setInterval(() => {
1871
- if (ws?.readyState === WebSocket.OPEN && Date.now() - lastServerMessage > SERVER_TIMEOUT_MS) {
1872
- if (debug) console.log('[ws] server silent for', Date.now() - lastServerMessage, 'ms, reconnecting');
1873
+ activityTimer = setIntervalTimer(() => {
1874
+ if (ws?.readyState === WebSocket.OPEN && now() - lastServerMessage > SERVER_TIMEOUT_MS) {
1875
+ if (debug) console.log('[ws] server silent for', now() - lastServerMessage, 'ms, reconnecting');
1873
1876
  ws.close();
1874
1877
  }
1875
1878
  }, 30000);
package/files/_init.js CHANGED
@@ -29,14 +29,14 @@ import fs from 'node:fs';
29
29
  import path from 'node:path';
30
30
  import { fileURLToPath } from 'node:url';
31
31
  import { Readable } from 'node:stream';
32
- import { performance } from 'node:perf_hooks';
32
+ import { monotonicNow } from './runtime.js';
33
33
  import { Server } from 'SERVER';
34
34
  import { manifest, base } from 'MANIFEST';
35
35
 
36
36
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
37
37
  const asset_dir = `${__dirname}/client${base}`;
38
38
 
39
- const _t_init = performance.now();
39
+ const _t_init = monotonicNow();
40
40
 
41
41
  /** @type {import('@sveltejs/kit').Server} */
42
42
  export const server = new Server(manifest);
@@ -46,4 +46,4 @@ await server.init({
46
46
  read: (file) => /** @type {ReadableStream} */ (Readable.toWeb(fs.createReadStream(`${asset_dir}/${file}`)))
47
47
  });
48
48
 
49
- console.log(`SvelteKit server initialized in ${(performance.now() - _t_init).toFixed(1)}ms`);
49
+ console.log(`SvelteKit server initialized in ${(monotonicNow() - _t_init).toFixed(1)}ms`);
package/files/cookies.js CHANGED
@@ -149,7 +149,7 @@ export function createCookies(cookieHeader) {
149
149
  delete(name, options = {}) {
150
150
  api.set(name, '', {
151
151
  ...options,
152
- expires: new Date(0),
152
+ expires: new Date(0), // determinism-allow: fixed Unix-epoch sentinel that forces immediate cookie deletion, not a wall-clock read
153
153
  maxAge: 0
154
154
  });
155
155
  delete parsed[name];