web_lib

Common web application libraries
git clone https://radroots.dev/git/web_lib.git
Log | Files | Refs | LICENSE

commit 0a429fe25fabb43e0b6316a0e9db775c39578076
parent 3d7855921f37bd08af0d799ffcd691b7110afc0c
Author: triesap <triesap@radroots.dev>
Date:   Tue, 13 Jan 2026 13:47:50 +0000

cache: preload wasm and cache app assets

- Initialize view-stack active_view from basis
- Add asset cache fetch helpers using Cache Storage
- Preload sql.js wasm bytes via force-cache and pass wasmBinary
- Fetch geocoder database via asset cache with force-cache

Diffstat:
Mapps-lib/src/lib/components/view-stack.svelte | 2+-
Mclient/src/sql/web.ts | 14++++++++++++--
Mgeocoder/src/geocoder.ts | 4++--
Autils/src/cache/index.ts | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutils/src/index.ts | 1+
5 files changed, 90 insertions(+), 5 deletions(-)

diff --git a/apps-lib/src/lib/components/view-stack.svelte b/apps-lib/src/lib/components/view-stack.svelte @@ -54,7 +54,7 @@ } = $props(); const view_context = writable<ViewContext<string>>({ - active_view: "", + active_view: basis.active_view, mode: "stack", fade: true, transition_ms: DEFAULT_TRANSITION_MS, diff --git a/client/src/sql/web.ts b/client/src/sql/web.ts @@ -1,4 +1,4 @@ -import { handle_err, resolve_wasm_path, type IdbClientConfig, type ResolveError } from "@radroots/utils"; +import { asset_cache_fetch_bytes, handle_err, resolve_wasm_path, type IdbClientConfig, type ResolveError } from "@radroots/utils"; import { del as idb_del, get as idb_get, set as idb_set, type UseStore } from "idb-keyval"; import type { BindParams, Database, SqlJsStatic, SqlValue, Statement } from "sql.js"; import init_sql_js from "sql.js/dist/sql-wasm.js"; @@ -100,6 +100,11 @@ export class WebSqlEngine implements IWebSqlEngine { } static async create(config: WebSqlEngineConfig): Promise<WebSqlEngine> { + const wasm_url = resolve_wasm_path( + config.sql_wasm_path, + "sql-wasm.wasm", + DEFAULT_SQL_WASM_PATH + ); const locate_wasm = (wasm_file: string): string => { const resolved = resolve_wasm_path( config.sql_wasm_path, @@ -122,8 +127,13 @@ export class WebSqlEngine implements IWebSqlEngine { idb_database: config.idb_config.database, idb_store: config.idb_config.store }); + let wasm_bytes: Uint8Array | null = null; + try { + wasm_bytes = await asset_cache_fetch_bytes(wasm_url, { request_init: { cache: "force-cache" } }); + } catch { } const sql = await init_sql_js({ - locateFile: locate_wasm + locateFile: locate_wasm, + wasmBinary: wasm_bytes ?? undefined }); const store = new WebSqlEngineEncryptedStore(config); const existing = await store.load(); diff --git a/geocoder/src/geocoder.ts b/geocoder/src/geocoder.ts @@ -1,5 +1,5 @@ import type { GeolocationPoint } from "@radroots/geo"; -import { err_msg, resolve_wasm_path } from "@radroots/utils"; +import { asset_cache_fetch, err_msg, resolve_wasm_path } from "@radroots/utils"; import type { Database } from "sql.js"; import type { GeocoderConfig, GeocoderConnectConfig, GeocoderReverseResult, IGeocoder, IGeocoderConnectResolve, IGeocoderCountryCenter, IGeocoderCountryCenterResolve, IGeocoderCountryListResolve, IGeocoderCountryListResult, IGeocoderCountryResolve, IGeocoderReverseOpts, IGeocoderReverseResolve } from "./types.js"; import { parse_geocode_country_center_result, parse_geocode_country_list_result, parse_geocode_reverse_result, resolve_geocoder_database_path } from "./utils.js"; @@ -32,7 +32,7 @@ export class Geocoder implements IGeocoder { const sql = await init_sqljs.default({ locateFile: wasm_file => resolve_wasm_path(connect_config.wasm_path, wasm_file, DEFAULT_SQL_WASM_PATH) }); - const database_res = await fetch(database_path); + const database_res = await asset_cache_fetch(database_path, { request_init: { cache: "force-cache" } }); if (!database_res.ok) return err_msg(`*`); const database_buffer = await database_res.arrayBuffer(); this._db = new sql.Database(new Uint8Array(database_buffer)); diff --git a/utils/src/cache/index.ts b/utils/src/cache/index.ts @@ -0,0 +1,74 @@ +type CacheLike = { + match: (request: string) => Promise<Response | undefined>; + put: (request: string, response: Response) => Promise<void>; +}; + +type CacheStorageLike = { + open: (cache_name: string) => Promise<CacheLike>; +}; + +declare const caches: CacheStorageLike | undefined; + +export const RADROOTS_ASSET_CACHE_NAME = "cache-app-assets-v1"; +export const RADROOTS_ASSET_CACHE_PREFIX = "cache-app-assets-v"; + +export type AssetCacheMode = + | "default" + | "no-store" + | "reload" + | "no-cache" + | "force-cache" + | "only-if-cached"; + +export type AssetCacheRequestInit = RequestInit & { + cache?: AssetCacheMode; +}; + +export type AssetCacheFetchConfig = { + cache_name?: string; + request_init?: AssetCacheRequestInit; +}; + +const cache_name_resolve = (config?: AssetCacheFetchConfig): string => + config?.cache_name ?? RADROOTS_ASSET_CACHE_NAME; + +const cache_key_resolve = (url: string): string => { + const hash_index = url.indexOf("#"); + return hash_index >= 0 ? url.slice(0, hash_index) : url; +}; + +const cache_read = async (cache_name: string, cache_key: string): Promise<Response | null> => { + if (typeof caches === "undefined") return null; + try { + const cache = await caches.open(cache_name); + const cached = await cache.match(cache_key); + return cached ?? null; + } catch { + return null; + } +}; + +const cache_write = async (cache_name: string, cache_key: string, response: Response): Promise<void> => { + if (typeof caches === "undefined") return; + try { + const cache = await caches.open(cache_name); + await cache.put(cache_key, response); + } catch { } +}; + +export const asset_cache_fetch = async (url: string, config?: AssetCacheFetchConfig): Promise<Response> => { + const cache_name = cache_name_resolve(config); + const cache_key = cache_key_resolve(url); + const cached = await cache_read(cache_name, cache_key); + if (cached) return cached; + const response = await fetch(url, config?.request_init); + if (response.ok || response.type === "opaque") await cache_write(cache_name, cache_key, response.clone()); + return response; +}; + +export const asset_cache_fetch_bytes = async (url: string, config?: AssetCacheFetchConfig): Promise<Uint8Array | null> => { + const response = await asset_cache_fetch(url, config); + if (!response.ok) return null; + const buffer = await response.arrayBuffer(); + return new Uint8Array(buffer); +}; diff --git a/utils/src/index.ts b/utils/src/index.ts @@ -1,4 +1,5 @@ export * from "./async/index.js"; +export * from "./cache/index.js"; export * from "./binary/index.js"; export * from "./currency/index.js"; export * from "./errors/index.js";