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:
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";