web_lib

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

commit 6ecfdab4d5b362bdc8272dcdde6c1776c42b39fe
parent bd8fba32906b776cad81fb5165a570be011f2f15
Author: triesap <triesap@radroots.dev>
Date:   Sat, 27 Dec 2025 17:39:37 +0000

websql: allow configurable sql.js wasm path resolution

- Add optional sql_wasm_path to WebSqlEngine and WebTangleDatabase configs
- Introduce resolve_wasm_path helper and export via utils index
- Use resolve_wasm_path for sql.js locateFile in client and geocoder
- Default wasm path to /assets/sql-wasm.wasm when unspecified

Diffstat:
Mclient/src/sql/types.ts | 1+
Mclient/src/sql/web.ts | 7+++++--
Mclient/src/tangle/web.ts | 6+++++-
Mgeocoder/src/geocoder.ts | 7++++---
Mgeocoder/src/types.ts | 2+-
Mnostr/src/events/profile/lib.ts | 10+++++-----
Mnostr/src/events/profile/parse.ts | 10+++++-----
Mnostr/src/events/profile/tags.ts | 29++++++++++++++---------------
Mutils/src/index.ts | 1+
Autils/src/path/index.ts | 6++++++
10 files changed, 47 insertions(+), 32 deletions(-)

diff --git a/client/src/sql/types.ts b/client/src/sql/types.ts @@ -28,6 +28,7 @@ export type WebSqlEngineConfig = { store_key: string; idb_config: IdbClientConfig; cipher_config?: IdbClientConfig | null; + sql_wasm_path?: string; }; export interface IClientSqlEncryptedStore { diff --git a/client/src/sql/web.ts b/client/src/sql/web.ts @@ -1,4 +1,4 @@ -import { handle_err, type IdbClientConfig, type ResolveError } from "@radroots/utils"; +import { 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"; @@ -13,6 +13,7 @@ import { cl_sql_error } from "./error.js"; import type { IClientSqlEncryptedStore, IWebSqlEngine, SqlJsExecOutcome, SqlJsParams, SqlJsResultRow, WebSqlEngineConfig } from "./types.js"; const DEFAULT_SQL_CIPHER_CONFIG: IdbClientConfig = IDB_CONFIG_CIPHER_SQL; +const DEFAULT_SQL_WASM_PATH = "/assets/sql-wasm.wasm"; const resolve_or_throw = <T>(value: ResolveError<T>): T => { if (is_error(value)) throw new Error(value.err); return value; @@ -99,7 +100,9 @@ export class WebSqlEngine implements IWebSqlEngine { } static async create(config: WebSqlEngineConfig): Promise<WebSqlEngine> { - const sql = await init_sql_js({ locateFile: f => `/assets/${f}` }); + const sql = await init_sql_js({ + locateFile: wasm_file => resolve_wasm_path(config.sql_wasm_path, wasm_file, DEFAULT_SQL_WASM_PATH) + }); const store = new WebSqlEngineEncryptedStore(config); const existing = await store.load(); const db = existing ? new sql.Database(existing) : new sql.Database(); diff --git a/client/src/tangle/web.ts b/client/src/tangle/web.ts @@ -161,6 +161,7 @@ export type WebTangleDatabaseConfig = { store_key?: string; idb_config?: IdbClientConfig; cipher_config?: IdbClientConfig | null; + sql_wasm_path?: string; }; const is_record = (value: unknown): value is Record<string, unknown> => @@ -238,12 +239,14 @@ export class WebTangleDatabase implements IWebTangleDatabase { private readonly store_key: string; private readonly idb_config: IdbClientConfig; private readonly cipher_config: IdbClientConfig | null; + private readonly sql_wasm_path: string | undefined; private init_promise: Promise<void> | null = null; constructor(config?: WebTangleDatabaseConfig) { this.store_key = config?.store_key ?? DEFAULT_TANGLE_STORE_KEY; this.idb_config = config?.idb_config ?? DEFAULT_TANGLE_IDB_CONFIG; this.cipher_config = config?.cipher_config ?? null; + this.sql_wasm_path = config?.sql_wasm_path; } get_store_key(): string { @@ -266,7 +269,8 @@ export class WebTangleDatabase implements IWebTangleDatabase { return { store_key: this.store_key, idb_config: this.idb_config, - cipher_config: this.cipher_config + cipher_config: this.cipher_config, + sql_wasm_path: this.sql_wasm_path }; } diff --git a/geocoder/src/geocoder.ts b/geocoder/src/geocoder.ts @@ -1,10 +1,11 @@ import type { GeolocationPoint } from "@radroots/geo"; -import { err_msg } from "@radroots/utils"; +import { err_msg, resolve_wasm_path } from "@radroots/utils"; import type { Database } from "sql.js"; import type { 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 } from "./utils.js"; const KM_PER_DEGREE_LATITUDE = 111; +const DEFAULT_SQL_WASM_PATH = `/assets/sql-wasm.wasm`; export class Geocoder implements IGeocoder { private _db: Database | null = null; @@ -15,11 +16,11 @@ export class Geocoder implements IGeocoder { this._database_name = database_name || `/geonames/geonames.db`; } - public async connect(wasm_dir: string = `/assets`): Promise<IGeocoderConnectResolve> { + public async connect(wasm_path?: string): Promise<IGeocoderConnectResolve> { try { const init_sqljs = await import(`sql.js`); const sql = await init_sqljs.default({ - locateFile: wasm_file => `${wasm_dir}/${wasm_file}` + locateFile: wasm_file => resolve_wasm_path(wasm_path, wasm_file, DEFAULT_SQL_WASM_PATH) }); const database_res = await fetch(this._database_name); const database_buffer = await database_res.arrayBuffer(); diff --git a/geocoder/src/types.ts b/geocoder/src/types.ts @@ -39,7 +39,7 @@ export type IGeocoderCountryListResolve = ResultsList<IGeocoderCountryListResult export type IGeocoderCountryCenterResolve = ResultObj<GeolocationPoint> | IError<GeocoderIError>; export type IGeocoder = { - connect(wasm_path: string): Promise<IGeocoderConnectResolve>; + connect(wasm_path?: string): Promise<IGeocoderConnectResolve>; reverse(point: GeolocationPoint, opts?: IGeocoderReverseOpts): Promise<IGeocoderReverseResolve>; country(opts: IGeocoderCountryCenter): Promise<IGeocoderCountryResolve>; country_list(): Promise<IGeocoderCountryListResolve>; diff --git a/nostr/src/events/profile/lib.ts b/nostr/src/events/profile/lib.ts @@ -1,16 +1,16 @@ -import type { RadrootsActorType, RadrootsProfile } from "@radroots/events-bindings"; +import type { RadrootsProfile, RadrootsProfileType } from "@radroots/events-bindings"; import type { NostrEventFigure, NostrSignedEvent } from "../../types/nostr.js"; import { nostr_event_create } from "../lib.js"; -import { tags_profile_actor } from "./tags.js"; +import { tags_profile_type } from "./tags.js"; export const KIND_RADROOTS_PROFILE = 0; export type KindRadrootsProfile = typeof KIND_RADROOTS_PROFILE; export const nostr_event_profile = async ( - opts: NostrEventFigure<{ data: RadrootsProfile; actor?: RadrootsActorType }>, + opts: NostrEventFigure<{ data: RadrootsProfile; profile_type?: RadrootsProfileType }>, ): Promise<NostrSignedEvent | undefined> => { - const { data, actor, ...event_opts } = opts; - const tags = tags_profile_actor(actor); + const { data, profile_type, ...event_opts } = opts; + const tags = tags_profile_type(profile_type); return nostr_event_create({ ...event_opts, basis: { diff --git a/nostr/src/events/profile/parse.ts b/nostr/src/events/profile/parse.ts @@ -1,14 +1,14 @@ -import type { RadrootsActorType, RadrootsProfile } from "@radroots/events-bindings"; +import type { RadrootsProfile, RadrootsProfileType } from "@radroots/events-bindings"; import { radroots_profile_schema } from "@radroots/events-bindings"; import type { NostrEvent } from "../../types/nostr.js"; import { parse_nostr_event_basis } from "../lib.js"; import type { NostrEventBasis } from "../subscription.js"; import { KIND_RADROOTS_PROFILE, type KindRadrootsProfile } from "./lib.js"; -import { parse_profile_actor_tag } from "./tags.js"; +import { parse_profile_type_tag } from "./tags.js"; export type RadrootsProfileNostrEvent = NostrEventBasis<KindRadrootsProfile> & { profile: RadrootsProfile; - actor_type?: RadrootsActorType; + profile_type?: RadrootsProfileType; }; export const parse_nostr_profile_event = ( @@ -19,8 +19,8 @@ export const parse_nostr_profile_event = ( try { const parsed = JSON.parse(event.content); const profile = radroots_profile_schema.parse(parsed); - const actor_type = parse_profile_actor_tag(event.tags); - return actor_type ? { ...ev, profile, actor_type } : { ...ev, profile }; + const profile_type = parse_profile_type_tag(event.tags); + return profile_type ? { ...ev, profile, profile_type } : { ...ev, profile }; } catch { return undefined; } diff --git a/nostr/src/events/profile/tags.ts b/nostr/src/events/profile/tags.ts @@ -1,25 +1,24 @@ -import type { RadrootsActorType } from "@radroots/events-bindings"; +import type { RadrootsProfileType } from "@radroots/events-bindings"; import type { NostrEventTags } from "../../types/lib.js"; -const ACTOR_TAG_KEY = "t"; -const ACTOR_TAG_PREFIX = "radroots:actor:"; +const TYPE_TAG_KEY = "t"; +const TYPE_TAG_PREFIX = "radroots:type:"; -const is_actor_type = (value: string): value is RadrootsActorType => - value === "person" || value === "farm"; +export const radroots_profile_type_tag_value = (profile_type: RadrootsProfileType): string => + `${TYPE_TAG_PREFIX}${profile_type}`; -export const radroots_actor_tag_value = (actor: RadrootsActorType): string => - `${ACTOR_TAG_PREFIX}${actor}`; +export const tags_profile_type = (profile_type?: RadrootsProfileType): NostrEventTags => + profile_type ? [[TYPE_TAG_KEY, radroots_profile_type_tag_value(profile_type)]] : []; -export const tags_profile_actor = (actor?: RadrootsActorType): NostrEventTags => - actor ? [[ACTOR_TAG_KEY, radroots_actor_tag_value(actor)]] : []; - -export const parse_profile_actor_tag = (tags: NostrEventTags): RadrootsActorType | undefined => { +export const parse_profile_type_tag = ( + tags: NostrEventTags, +): RadrootsProfileType | undefined => { for (const tag of tags) { - if (tag[0] !== ACTOR_TAG_KEY) continue; + if (tag[0] !== TYPE_TAG_KEY) continue; const value = tag[1]; - if (!value || !value.startsWith(ACTOR_TAG_PREFIX)) continue; - const actor = value.slice(ACTOR_TAG_PREFIX.length); - if (is_actor_type(actor)) return actor; + if (!value || !value.startsWith(TYPE_TAG_PREFIX)) continue; + const profile_type = value.slice(TYPE_TAG_PREFIX.length); + if (profile_type === "individual" || profile_type === "farm") return profile_type; } return undefined; }; diff --git a/utils/src/index.ts b/utils/src/index.ts @@ -8,6 +8,7 @@ export * from "./media/index.js"; export * from "./model/index.js"; export * from "./numbers/index.js"; export * from "./object/index.js"; +export * from "./path/index.js"; export * from "./text/index.js"; export * from "./time/index.js"; export * from "./types/index.js"; diff --git a/utils/src/path/index.ts b/utils/src/path/index.ts @@ -0,0 +1,6 @@ +export const resolve_wasm_path = (wasm_path: string | undefined, wasm_file: string, default_wasm_path: string): string => { + const resolved_wasm_path = wasm_path ?? default_wasm_path; + if (resolved_wasm_path.endsWith(".wasm")) return resolved_wasm_path; + const base_path = resolved_wasm_path.endsWith("/") ? resolved_wasm_path.slice(0, -1) : resolved_wasm_path; + return `${base_path}/${wasm_file}`; +};