diff --git a/src/lib/ae_core/core__countries.ts b/src/lib/ae_core/core__countries.ts index 5f41fa87..74132b49 100644 --- a/src/lib/ae_core/core__countries.ts +++ b/src/lib/ae_core/core__countries.ts @@ -1,65 +1,64 @@ -import type { key_val } from '$lib/stores/ae_stores'; import { api } from '$lib/api/api'; +import { db_lookups, LOOKUP_TTL_MS } from '$lib/ae_core/db_lookups'; -import { db_core } from '$lib/ae_core/db_core'; +/** + * Country lookup — IDB-backed SWR helper. + * + * Calling this function triggers a background API refresh if IDB is empty or + * older than 24 hours. The function returns immediately without awaiting the + * refresh. Components subscribe to db_lookups.lu_country via liveQuery and + * receive automatic updates when the refresh completes. + * + * Updated 2026-03-23 — replaced localStorage pattern with IDB + 24h TTL + */ -const ae_promises: key_val = {}; - -// Updated 2024-10-14 -export async function load_ae_obj_li__country({ +async function _refresh_lu_country_background({ api_cfg, - // account_id, - enabled = 'enabled', - hidden = 'not_hidden', - limit = 275, // There are roughly 249 as of 2026-02 - offset = 0, - order_by_li = { sort: 'DESC', english_short_name: 'ASC', alpha_2_code: 'ASC' } as const, - params = {}, - try_cache = true, log_lvl = 0 }: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any api_cfg: any; - // account_id: string, - enabled?: 'enabled' | 'all' | 'not_enabled' | undefined; - hidden?: 'hidden' | 'all' | 'not_hidden' | undefined; - limit?: number; - offset?: number; - order_by_li?: key_val; - params?: key_val; - try_cache?: boolean; log_lvl?: number; }) { - if (log_lvl) { - console.log(`*** load_ae_obj_li__country() ***`); - } - - const params_json: key_val = {}; - - // console.log('params_json:', params_json); - - ae_promises.load__country_li = await api - .get_ae_obj_li_for_lu({ - api_cfg: api_cfg, + if (log_lvl) console.log('*** _refresh_lu_country_background() ***'); + try { + const result = await api.get_ae_obj_li_for_lu({ + api_cfg, for_lu_type: 'country', - enabled: enabled, - hidden: hidden, - limit: limit, - offset: offset, - params: params, - log_lvl: log_lvl - }) - .then(function (country_li_get_result) { - if (country_li_get_result) { - // handle_db_save_ae_obj_li__country({obj_type: 'country', obj_li: country_li_get_result}); - return country_li_get_result; - } else { - return []; - } - }) - .catch(function (error: any) { - console.log('No results returned or failed.', error); + enabled: 'enabled', + hidden: 'not_hidden', + limit: 275, + log_lvl }); - - console.log('ae_promises.load__country_li:', ae_promises.load__country_li); - return ae_promises.load__country_li; + if (result?.length) { + await db_lookups.lu_country.clear(); + await db_lookups.lu_country.bulkPut(result); + await db_lookups.lu_cache_meta.put({ lu_type: 'country', refreshed_at: Date.now() }); + if (log_lvl) console.log(`lu_country: saved ${result.length} records to IDB`); + } + } catch (error) { + console.error('lu_country refresh failed:', error); + } +} + +export async function load_ae_obj_li__country({ + api_cfg, + log_lvl = 0 +}: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + api_cfg: any; + log_lvl?: number; +}) { + if (log_lvl) console.log('*** load_ae_obj_li__country() ***'); + + const count = await db_lookups.lu_country.count(); + const meta = await db_lookups.lu_cache_meta.get('country'); + const is_stale = !meta || Date.now() - meta.refreshed_at > LOOKUP_TTL_MS; + + if (count === 0 || is_stale) { + // Fire-and-forget — liveQuery subscribers receive updates when IDB is written + _refresh_lu_country_background({ api_cfg, log_lvl }); + } else if (log_lvl) { + console.log(`lu_country: IDB fresh (${count} records), skipping refresh`); + } } diff --git a/src/lib/ae_core/core__country_subdivisions.ts b/src/lib/ae_core/core__country_subdivisions.ts index c9efabac..b9364276 100644 --- a/src/lib/ae_core/core__country_subdivisions.ts +++ b/src/lib/ae_core/core__country_subdivisions.ts @@ -1,68 +1,66 @@ -import type { key_val } from '$lib/stores/ae_stores'; import { api } from '$lib/api/api'; +import { db_lookups, LOOKUP_TTL_MS } from '$lib/ae_core/db_lookups'; -import { db_core } from '$lib/ae_core/db_core'; +/** + * Country subdivision lookup — IDB-backed SWR helper. + * + * Calling this function triggers a background API refresh if IDB is empty or + * older than 24 hours. Components subscribe to db_lookups.lu_country_subdivision + * via liveQuery and receive automatic updates when the refresh completes. + * + * Updated 2026-03-23 — replaced localStorage pattern with IDB + 24h TTL + */ -const ae_promises: key_val = {}; - -// Updated 2024-10-14 -export async function load_ae_obj_li__country_subdivision({ +async function _refresh_lu_country_subdivision_background({ api_cfg, - // account_id, - enabled = 'enabled', - hidden = 'not_hidden', - limit = 3500, // There are roughly 3434 as of 2026-02 - offset = 0, - order_by_li = { sort: 'DESC', name: 'ASC', code: 'ASC' } as const, - params = {}, - try_cache = true, log_lvl = 0 }: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any api_cfg: any; - // account_id: string, - enabled?: 'enabled' | 'all' | 'not_enabled' | undefined; - hidden?: 'hidden' | 'all' | 'not_hidden' | undefined; - limit?: number; - offset?: number; - order_by_li?: key_val; - params?: key_val; - try_cache?: boolean; log_lvl?: number; }) { - if (log_lvl) { - console.log(`*** load_ae_obj_li__country_subdivision() ***`); - } - - const params_json: key_val = {}; - - // console.log('params_json:', params_json); - - ae_promises.load__country_subdivision_li = await api - .get_ae_obj_li_for_lu({ - api_cfg: api_cfg, + if (log_lvl) console.log('*** _refresh_lu_country_subdivision_background() ***'); + try { + const result = await api.get_ae_obj_li_for_lu({ + api_cfg, for_lu_type: 'country_subdivision', - enabled: enabled, - hidden: hidden, - limit: limit, - offset: offset, - params: params, - log_lvl: log_lvl - }) - .then(function (country_subdivision_li_get_result) { - if (country_subdivision_li_get_result) { - // handle_db_save_ae_obj_li__country_subdivision({obj_type: 'country_subdivision', obj_li: country_subdivision_li_get_result}); - return country_subdivision_li_get_result; - } else { - return []; - } - }) - .catch(function (error: any) { - console.log('No results returned or failed.', error); + enabled: 'enabled', + hidden: 'not_hidden', + limit: 3500, + log_lvl }); - - console.log( - 'ae_promises.load__country_subdivision_li:', - ae_promises.load__country_subdivision_li - ); - return ae_promises.load__country_subdivision_li; + if (result?.length) { + await db_lookups.lu_country_subdivision.clear(); + await db_lookups.lu_country_subdivision.bulkPut(result); + await db_lookups.lu_cache_meta.put({ + lu_type: 'country_subdivision', + refreshed_at: Date.now() + }); + if (log_lvl) + console.log(`lu_country_subdivision: saved ${result.length} records to IDB`); + } + } catch (error) { + console.error('lu_country_subdivision refresh failed:', error); + } +} + +export async function load_ae_obj_li__country_subdivision({ + api_cfg, + log_lvl = 0 +}: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + api_cfg: any; + log_lvl?: number; +}) { + if (log_lvl) console.log('*** load_ae_obj_li__country_subdivision() ***'); + + const count = await db_lookups.lu_country_subdivision.count(); + const meta = await db_lookups.lu_cache_meta.get('country_subdivision'); + const is_stale = !meta || Date.now() - meta.refreshed_at > LOOKUP_TTL_MS; + + if (count === 0 || is_stale) { + _refresh_lu_country_subdivision_background({ api_cfg, log_lvl }); + } else if (log_lvl) { + console.log(`lu_country_subdivision: IDB fresh (${count} records), skipping refresh`); + } } diff --git a/src/lib/ae_core/core__time_zones.ts b/src/lib/ae_core/core__time_zones.ts index 945d720d..b116414c 100644 --- a/src/lib/ae_core/core__time_zones.ts +++ b/src/lib/ae_core/core__time_zones.ts @@ -1,70 +1,67 @@ -import type { key_val } from '$lib/stores/ae_stores'; import { api } from '$lib/api/api'; +import { db_lookups, LOOKUP_TTL_MS } from '$lib/ae_core/db_lookups'; -import { db_core } from '$lib/ae_core/db_core'; +/** + * Time zone lookup — IDB-backed SWR helper. + * + * Fetches priority timezones (only_priority=true, ~72 records). Calling this + * function triggers a background API refresh if IDB is empty or older than + * 24 hours. Components subscribe to db_lookups.lu_time_zone via liveQuery and + * receive automatic updates when the refresh completes. + * + * Updated 2026-03-23 — replaced $ae_loc + localStorage pattern with IDB + 24h TTL + */ -const ae_promises: key_val = {}; - -// Updated 2026-02-20 -export async function load_ae_obj_li__time_zone({ +async function _refresh_lu_time_zone_background({ api_cfg, - // account_id, - enabled = 'enabled', - hidden = 'not_hidden', - limit = 1800, // There are roughly 1780 as of 2026-02 - offset = 0, - // order_by_li = {'priority': 'DESC', 'group': 'ASC', 'sort': 'DESC', 'name': 'ASC'}, - order_by_li = { priority: 'DESC', sort: 'DESC', name: 'ASC' } as const, - params = {}, - only_priority = false, - try_cache = true, log_lvl = 0 }: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any api_cfg: any; - // account_id: string, - enabled?: 'enabled' | 'all' | 'not_enabled' | undefined; - hidden?: 'hidden' | 'all' | 'not_hidden' | undefined; - limit?: number; - offset?: number; - order_by_li?: key_val; - params?: key_val; - only_priority?: boolean; - try_cache?: boolean; log_lvl?: number; }) { - if (log_lvl) { - console.log(`*** load_ae_obj_li__time_zone() *** only_priority=${only_priority}`); - } - - const params_json: key_val = {}; - - // console.log('params_json:', params_json); - - ae_promises.load__time_zone_li = await api - .get_ae_obj_li_for_lu({ - api_cfg: api_cfg, + if (log_lvl) console.log('*** _refresh_lu_time_zone_background() ***'); + try { + const result = await api.get_ae_obj_li_for_lu({ + api_cfg, for_lu_type: 'time_zone', - enabled: enabled, - hidden: hidden, - limit: limit, - offset: offset, - order_by_li: order_by_li, - params: params, - only_priority: only_priority, - log_lvl: log_lvl - }) - .then(function (time_zone_li_get_result) { - if (time_zone_li_get_result) { - // handle_db_save_ae_obj_li__time_zone({obj_type: 'time_zone', obj_li: time_zone_li_get_result}); - return time_zone_li_get_result; - } else { - return []; - } - }) - .catch(function (error: any) { - console.log('No results returned or failed.', error); + enabled: 'enabled', + hidden: 'not_hidden', + only_priority: true, // ~72 priority timezone records + limit: 1800, + log_lvl }); - - console.log('ae_promises.load__time_zone_li:', ae_promises.load__time_zone_li); - return ae_promises.load__time_zone_li; + if (result?.length) { + await db_lookups.lu_time_zone.clear(); + await db_lookups.lu_time_zone.bulkPut(result); + await db_lookups.lu_cache_meta.put({ + lu_type: 'time_zone', + refreshed_at: Date.now() + }); + if (log_lvl) console.log(`lu_time_zone: saved ${result.length} records to IDB`); + } + } catch (error) { + console.error('lu_time_zone refresh failed:', error); + } +} + +export async function load_ae_obj_li__time_zone({ + api_cfg, + log_lvl = 0 +}: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + api_cfg: any; + log_lvl?: number; +}) { + if (log_lvl) console.log('*** load_ae_obj_li__time_zone() ***'); + + const count = await db_lookups.lu_time_zone.count(); + const meta = await db_lookups.lu_cache_meta.get('time_zone'); + const is_stale = !meta || Date.now() - meta.refreshed_at > LOOKUP_TTL_MS; + + if (count === 0 || is_stale) { + _refresh_lu_time_zone_background({ api_cfg, log_lvl }); + } else if (log_lvl) { + console.log(`lu_time_zone: IDB fresh (${count} records), skipping refresh`); + } } diff --git a/src/lib/ae_core/db_lookups.ts b/src/lib/ae_core/db_lookups.ts new file mode 100644 index 00000000..1cfbfdfe --- /dev/null +++ b/src/lib/ae_core/db_lookups.ts @@ -0,0 +1,81 @@ +import Dexie, { type Table } from 'dexie'; + +/** + * Lookup DB — IDB-backed cache for V3 Uniform Lookup System reference data. + * + * These tables store the deduplicated, priority-ranked list returned by + * GET /v3/lookup/{lu_type}/list. Data is refreshed automatically on a 24-hour + * TTL via the core__*.ts load helpers; components subscribe via liveQuery. + * + * Updated 2026-03-23 + */ + +export interface LuCountry { + id: number; + group: string; // dedup key = alpha_2_code (e.g. "US") + alpha_2_code: string; + name: string; + english_short_name?: string; + name_override?: string; + enable?: number; + hide?: number; + priority?: number; + sort?: number; + account_id?: number | null; + [key: string]: unknown; // allow extra fields from API without TS errors +} + +export interface LuCountrySubdivision { + id: number; + group: string; // dedup key = code (e.g. "US-NY") + code: string; + name: string; + country_alpha_2_code?: string; + name_override?: string; + enable?: number; + hide?: number; + priority?: number; + sort?: number; + account_id?: number | null; + [key: string]: unknown; +} + +export interface LuTimeZone { + id: number; + group: string; // dedup key = name (IANA identifier, e.g. "US/Eastern") + name: string; + name_override?: string; // display label override; prefer this over name when set + enable?: number; + hide?: number; + priority?: number; + sort?: number; + account_id?: number | null; + [key: string]: unknown; +} + +export interface LuCacheMeta { + lu_type: 'country' | 'country_subdivision' | 'time_zone'; + refreshed_at: number; // Unix timestamp ms — used for 24h TTL check +} + +class LookupsDexie extends Dexie { + lu_country!: Table; + lu_country_subdivision!: Table; + lu_time_zone!: Table; + lu_cache_meta!: Table; + + constructor() { + super('ae_lookups_db'); + this.version(1).stores({ + lu_country: 'id, alpha_2_code, group', + lu_country_subdivision: 'id, code, country_alpha_2_code, group', + lu_time_zone: 'id, name, group', + lu_cache_meta: 'lu_type' + }); + } +} + +export const db_lookups = new LookupsDexie(); + +/** 24-hour TTL in milliseconds */ +export const LOOKUP_TTL_MS = 24 * 60 * 60 * 1000; diff --git a/src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_content_obj_id_edit.svelte b/src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_content_obj_id_edit.svelte index ea99ec04..23ac177e 100644 --- a/src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_content_obj_id_edit.svelte +++ b/src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_content_obj_id_edit.svelte @@ -7,6 +7,8 @@ import type { key_val } from '$lib/stores/ae_stores'; import { ae_util } from '$lib/ae_utils/ae_utils'; import { core_func } from '$lib/ae_core/ae_core_functions'; + import { liveQuery } from 'dexie'; + import { db_lookups } from '$lib/ae_core/db_lookups'; import { ae_snip, ae_loc, @@ -106,56 +108,21 @@ ); } - let lu_time_zone_list: any = $state( - localStorage.getItem('lu_time_zone_list') - ? JSON.parse(localStorage.getItem('lu_time_zone_list') as string) - : [] + // Timezone lookup — reactive IDB query; background refresh handled by liveQuery + TTL + // Sort: sort DESC (higher = first, NULL=0 last), then name ASC — matches Aether backend convention. + const lq__lu_time_zone = liveQuery(() => + db_lookups.lu_time_zone.toArray().then(arr => + arr.sort((a, b) => { + const s_diff = Number(b['sort'] ?? 0) - Number(a['sort'] ?? 0); + if (s_diff !== 0) return s_diff; + return (a.name ?? '').localeCompare(b.name ?? ''); + }) + ) ); + onMount(() => { - $ae_loc.lu_time_zone_list = []; - // $ae_loc.lu_time_zone_list = []; - // lu_time_zone_list = []; - if (lu_time_zone_list && lu_time_zone_list.length > 0) { - // console.log('Already have time zone list!', lu_time_zone_list); - } else { - console.log('No time zone list'); - - let lu_time_zone_li_get_promise = core_func - .load_ae_obj_li__time_zone({ - api_cfg: $ae_api, - only_priority: true, - log_lvl: log_lvl - }) - .then(function (lu_time_zone_li_get_result) { - /* We need to save the time zone list to localStore */ - if (lu_time_zone_li_get_result) { - lu_time_zone_list = lu_time_zone_li_get_result; - localStorage.setItem( - 'lu_time_zone_list', - JSON.stringify(lu_time_zone_li_get_result) - ); - if (log_lvl) { - console.log(`Time zone list:`, lu_time_zone_list); - } - } else { - console.log(`No time zones returned!`); - // $ae_loc.lu_time_zone_list = []; - } - - if (lu_time_zone_li_get_result) { - lu_time_zone_list = lu_time_zone_li_get_result; - console.log(`Time zone list:`, lu_time_zone_list); - console.log(lu_time_zone_list[0]); - console.log(lu_time_zone_list[10]); - } else { - console.log(`No time zones returned!`); - lu_time_zone_list = []; - } - }) - .catch(function (error: any) { - console.log('No results returned or failed.', error); - }); - } + // Trigger background IDB refresh if stale/empty; liveQuery reacts automatically + core_func.load_ae_obj_li__time_zone({ api_cfg: $ae_api, log_lvl }); }); function prevent_default(fn: (event: T) => void) { @@ -873,7 +840,7 @@