Perf: replace JSON.stringify comparisons with efficient identity checks

JSON.stringify on large store objects (ae_loc, ae_api, slct, event lists)
was running on every navigation, comparing serialized strings of potentially
deep objects. Replace with targeted comparators:

- Root +layout.svelte: add shallow_equal() helper — O(n keys) key-by-key
  identity check instead of O(serialized bytes). Used for ae_api, ae_loc,
  and slct sync guards.

- Launcher +layout.svelte: ID-list join-compare for event_location_obj_li
  and id_li__event_location (O(n) string vs O(n*m) stringify). Refactor
  liveQuery closures to be pure (data-only, no store reads/writes inside
  the async Dexie context where Svelte reactivity tracking is undefined).
  Move store sync into separate $effects that compare updated_on + id
  (O(1)) or ID-join (O(n)) rather than full object serialization.
This commit is contained in:
Scott Idem
2026-03-10 14:20:57 -04:00
parent 2c0eba4130
commit ffc430a727
2 changed files with 79 additions and 41 deletions

View File

@@ -2,7 +2,7 @@
/** @type {import('./$types').LayoutData} */
// /** @type {import('./$types').LayoutProps} */
let log_lvl: number = 1;
let log_lvl: number = 0;
// *** Import Svelte specific
import { untrack } from 'svelte';
@@ -71,6 +71,20 @@
let last_reload_time = 0;
// Shallow equality guard — avoids triggering Svelte store updates when the merged
// object is functionally identical to the current one. Comparing JSON.stringify on
// large objects like $ae_loc (site config, device info, flags) is expensive and
// runs on every navigation. Key-by-key identity check is O(n keys), not O(n chars).
function shallow_equal(a: Record<string, any>, b: Record<string, any>): boolean {
const keys_a = Object.keys(a);
const keys_b = Object.keys(b);
if (keys_a.length !== keys_b.length) return false;
for (const k of keys_a) {
if (a[k] !== b[k]) return false;
}
return true;
}
// 1. CONSOLIDATED SYNC EFFECT (One single point of entry for store updates)
$effect(() => {
if (!browser || !ae_acct) return;
@@ -83,7 +97,7 @@
const new_api = { ...current_api, ...(ae_acct.api || {}) };
// Deep check for JWT specifically to avoid extra triggers
if (current_api.jwt !== $ae_loc.jwt) new_api.jwt = $ae_loc.jwt;
if (JSON.stringify(current_api) !== JSON.stringify(new_api)) {
if (!shallow_equal(current_api, new_api)) {
$ae_api = new_api;
}
@@ -93,13 +107,13 @@
if (!new_loc.sys_menu) new_loc.sys_menu = { hide: false, hide_access_type: false, expand_access_type: false, hide_edit_mode: false, expand_edit_mode: false, hide_user: false, expand_user: false, hide_theme: false, expand_theme: false, hide_cfg: false, expand_cfg: false };
if (!new_loc.debug_menu) new_loc.debug_menu = { hide: false, expand: false };
if (JSON.stringify(current_loc) !== JSON.stringify(new_loc)) {
if (!shallow_equal(current_loc, new_loc)) {
$ae_loc = new_loc;
}
const current_slct = $slct;
const new_slct = { ...current_slct, ...(ae_acct.slct || {}) };
if (JSON.stringify(current_slct) !== JSON.stringify(new_slct)) {
if (!shallow_equal(current_slct, new_slct)) {
$slct = new_slct;
}