fix(idaa): fix country/subdivision/timezone dropdowns — switch to in-memory sort
- Country and state/province fields were showing as plain text inputs because liveQuery used orderBy() on non-indexed columns, causing silent Dexie errors that left the store as undefined indefinitely. - Fix: replaced orderBy() with toArray() + in-memory sort across all three lookup types (country, country_subdivision, time_zone). - Sort convention matches Aether backend: sort DESC (higher = first, NULL=0 last), then name ASC — puts priority entries at the top. - Added db_lookups.ts (IDB schema for lookup tables) and updated core__countries, core__country_subdivisions, core__time_zones to IDB-backed SWR pattern. - Affected: archive edit, archive content edit, recovery meeting edit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
81
src/lib/ae_core/db_lookups.ts
Normal file
81
src/lib/ae_core/db_lookups.ts
Normal file
@@ -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<LuCountry>;
|
||||
lu_country_subdivision!: Table<LuCountrySubdivision>;
|
||||
lu_time_zone!: Table<LuTimeZone>;
|
||||
lu_cache_meta!: Table<LuCacheMeta>;
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user