web_lib

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

web.ts (68941B)


      1 import type {
      2     IFarmCreate,
      3     IFarmCreateResolve,
      4     IFarmDelete,
      5     IFarmDeleteResolve,
      6     IFarmFindMany,
      7     IFarmFindManyResolve,
      8     IFarmFindOne,
      9     IFarmFindOneResolve,
     10     IFarmUpdate,
     11     IFarmUpdateResolve,
     12     IFarmGcsLocationCreate,
     13     IFarmGcsLocationCreateResolve,
     14     IFarmGcsLocationDelete,
     15     IFarmGcsLocationDeleteResolve,
     16     IFarmGcsLocationFindMany,
     17     IFarmGcsLocationFindManyResolve,
     18     IFarmGcsLocationFindOne,
     19     IFarmGcsLocationFindOneResolve,
     20     IFarmGcsLocationUpdate,
     21     IFarmGcsLocationUpdateResolve,
     22     IFarmMemberClaimCreate,
     23     IFarmMemberClaimCreateResolve,
     24     IFarmMemberClaimDelete,
     25     IFarmMemberClaimDeleteResolve,
     26     IFarmMemberClaimFindMany,
     27     IFarmMemberClaimFindManyResolve,
     28     IFarmMemberClaimFindOne,
     29     IFarmMemberClaimFindOneResolve,
     30     IFarmMemberClaimUpdate,
     31     IFarmMemberClaimUpdateResolve,
     32     IFarmMemberCreate,
     33     IFarmMemberCreateResolve,
     34     IFarmMemberDelete,
     35     IFarmMemberDeleteResolve,
     36     IFarmMemberFindMany,
     37     IFarmMemberFindManyResolve,
     38     IFarmMemberFindOne,
     39     IFarmMemberFindOneResolve,
     40     IFarmMemberUpdate,
     41     IFarmMemberUpdateResolve,
     42     IFarmTagCreate,
     43     IFarmTagCreateResolve,
     44     IFarmTagDelete,
     45     IFarmTagDeleteResolve,
     46     IFarmTagFindMany,
     47     IFarmTagFindManyResolve,
     48     IFarmTagFindOne,
     49     IFarmTagFindOneResolve,
     50     IFarmTagUpdate,
     51     IFarmTagUpdateResolve,
     52     IGcsLocationCreate,
     53     IGcsLocationCreateResolve,
     54     IGcsLocationDelete,
     55     IGcsLocationDeleteResolve,
     56     IGcsLocationFindMany,
     57     IGcsLocationFindManyResolve,
     58     IGcsLocationFindOne,
     59     IGcsLocationFindOneResolve,
     60     IGcsLocationUpdate,
     61     IGcsLocationUpdateResolve,
     62     ILogErrorCreate,
     63     ILogErrorCreateResolve,
     64     ILogErrorDelete,
     65     ILogErrorDeleteResolve,
     66     ILogErrorFindMany,
     67     ILogErrorFindManyResolve,
     68     ILogErrorFindOne,
     69     ILogErrorFindOneResolve,
     70     ILogErrorUpdate,
     71     ILogErrorUpdateResolve,
     72     IMediaImageCreate,
     73     IMediaImageCreateResolve,
     74     IMediaImageDelete,
     75     IMediaImageDeleteResolve,
     76     IMediaImageFindMany,
     77     IMediaImageFindManyResolve,
     78     IMediaImageFindOne,
     79     IMediaImageFindOneResolve,
     80     IMediaImageUpdate,
     81     IMediaImageUpdateResolve,
     82     INostrEventStateCreate,
     83     INostrEventStateCreateResolve,
     84     INostrEventStateDelete,
     85     INostrEventStateDeleteResolve,
     86     INostrEventStateFindMany,
     87     INostrEventStateFindManyResolve,
     88     INostrEventStateFindOne,
     89     INostrEventStateFindOneResolve,
     90     INostrEventStateUpdate,
     91     INostrEventStateUpdateResolve,
     92     INostrProfileCreate,
     93     INostrProfileCreateResolve,
     94     INostrProfileDelete,
     95     INostrProfileDeleteResolve,
     96     INostrProfileFindMany,
     97     INostrProfileFindManyResolve,
     98     INostrProfileFindOne,
     99     INostrProfileFindOneResolve,
    100     INostrProfileUpdate,
    101     INostrProfileUpdateResolve,
    102     INostrRelayCreate,
    103     INostrRelayCreateResolve,
    104     INostrRelayDelete,
    105     INostrRelayDeleteResolve,
    106     INostrRelayFindMany,
    107     INostrRelayFindManyResolve,
    108     INostrRelayFindOne,
    109     INostrRelayFindOneResolve,
    110     INostrRelayUpdate,
    111     INostrRelayUpdateResolve,
    112     IPlotCreate,
    113     IPlotCreateResolve,
    114     IPlotDelete,
    115     IPlotDeleteResolve,
    116     IPlotFindMany,
    117     IPlotFindManyResolve,
    118     IPlotFindOne,
    119     IPlotFindOneResolve,
    120     IPlotGcsLocationCreate,
    121     IPlotGcsLocationCreateResolve,
    122     IPlotGcsLocationDelete,
    123     IPlotGcsLocationDeleteResolve,
    124     IPlotGcsLocationFindMany,
    125     IPlotGcsLocationFindManyResolve,
    126     IPlotGcsLocationFindOne,
    127     IPlotGcsLocationFindOneResolve,
    128     IPlotGcsLocationUpdate,
    129     IPlotGcsLocationUpdateResolve,
    130     IPlotTagCreate,
    131     IPlotTagCreateResolve,
    132     IPlotTagDelete,
    133     IPlotTagDeleteResolve,
    134     IPlotTagFindMany,
    135     IPlotTagFindManyResolve,
    136     IPlotTagFindOne,
    137     IPlotTagFindOneResolve,
    138     IPlotTagUpdate,
    139     IPlotTagUpdateResolve,
    140     IPlotUpdate,
    141     IPlotUpdateResolve,
    142     ITradeProductCreate,
    143     ITradeProductCreateResolve,
    144     ITradeProductDelete,
    145     ITradeProductDeleteResolve,
    146     ITradeProductFindMany,
    147     ITradeProductFindManyResolve,
    148     ITradeProductFindOne,
    149     ITradeProductFindOneResolve,
    150     ITradeProductUpdate,
    151     ITradeProductUpdateResolve,
    152     INostrProfileRelayRelation,
    153     INostrProfileRelayResolve,
    154     ITradeProductLocationRelation,
    155     ITradeProductLocationResolve,
    156     ITradeProductMediaRelation,
    157     ITradeProductMediaResolve
    158 } from "@radroots/replica-db-schema-bindings";
    159 import init_wasm, {
    160     replica_db_farm_create,
    161     replica_db_farm_delete,
    162     replica_db_farm_find_many,
    163     replica_db_farm_find_one,
    164     replica_db_farm_update,
    165     replica_db_plot_create,
    166     replica_db_plot_delete,
    167     replica_db_plot_find_many,
    168     replica_db_plot_find_one,
    169     replica_db_plot_update,
    170     replica_db_gcs_location_create,
    171     replica_db_gcs_location_delete,
    172     replica_db_gcs_location_find_many,
    173     replica_db_gcs_location_find_one,
    174     replica_db_gcs_location_update,
    175     replica_db_farm_gcs_location_create,
    176     replica_db_farm_gcs_location_delete,
    177     replica_db_farm_gcs_location_find_many,
    178     replica_db_farm_gcs_location_find_one,
    179     replica_db_farm_gcs_location_update,
    180     replica_db_plot_gcs_location_create,
    181     replica_db_plot_gcs_location_delete,
    182     replica_db_plot_gcs_location_find_many,
    183     replica_db_plot_gcs_location_find_one,
    184     replica_db_plot_gcs_location_update,
    185     replica_db_farm_tag_create,
    186     replica_db_farm_tag_delete,
    187     replica_db_farm_tag_find_many,
    188     replica_db_farm_tag_find_one,
    189     replica_db_farm_tag_update,
    190     replica_db_plot_tag_create,
    191     replica_db_plot_tag_delete,
    192     replica_db_plot_tag_find_many,
    193     replica_db_plot_tag_find_one,
    194     replica_db_plot_tag_update,
    195     replica_db_farm_member_create,
    196     replica_db_farm_member_delete,
    197     replica_db_farm_member_find_many,
    198     replica_db_farm_member_find_one,
    199     replica_db_farm_member_update,
    200     replica_db_farm_member_claim_create,
    201     replica_db_farm_member_claim_delete,
    202     replica_db_farm_member_claim_find_many,
    203     replica_db_farm_member_claim_find_one,
    204     replica_db_farm_member_claim_update,
    205     replica_db_log_error_create,
    206     replica_db_log_error_delete,
    207     replica_db_log_error_find_many,
    208     replica_db_log_error_find_one,
    209     replica_db_log_error_update,
    210     replica_db_media_image_create,
    211     replica_db_media_image_delete,
    212     replica_db_media_image_find_many,
    213     replica_db_media_image_find_one,
    214     replica_db_media_image_update,
    215     replica_db_nostr_event_state_create,
    216     replica_db_nostr_event_state_delete,
    217     replica_db_nostr_event_state_find_many,
    218     replica_db_nostr_event_state_find_one,
    219     replica_db_nostr_event_state_update,
    220     replica_db_nostr_profile_create,
    221     replica_db_nostr_profile_delete,
    222     replica_db_nostr_profile_find_many,
    223     replica_db_nostr_profile_find_one,
    224     replica_db_nostr_profile_update,
    225     replica_db_nostr_relay_create,
    226     replica_db_nostr_relay_delete,
    227     replica_db_nostr_relay_find_many,
    228     replica_db_nostr_relay_find_one,
    229     replica_db_nostr_relay_update,
    230     replica_db_trade_product_create,
    231     replica_db_trade_product_delete,
    232     replica_db_trade_product_find_many,
    233     replica_db_trade_product_find_one,
    234     replica_db_trade_product_update,
    235     replica_db_nostr_profile_relay_set,
    236     replica_db_nostr_profile_relay_unset,
    237     replica_db_trade_product_location_set,
    238     replica_db_trade_product_location_unset,
    239     replica_db_trade_product_media_set,
    240     replica_db_trade_product_media_unset,
    241     replica_db_reset_database,
    242     replica_db_run_migrations,
    243     replica_db_export_begin,
    244     replica_db_export_finish,
    245     replica_db_export_json,
    246     replica_db_import_json
    247 } from "@radroots/replica-db-wasm";
    248 import init_replica_sync_wasm, {
    249     replica_sync_ingest_event,
    250     replica_sync_sync_all
    251 } from "@radroots/replica-sync-wasm";
    252 import {
    253     nostr_context_create,
    254     nostr_event_sign,
    255     nostr_public_key_from_secret,
    256     nostr_publish,
    257     nostr_relays_clear,
    258     nostr_relays_open,
    259     type NostrContext
    260 } from "@radroots/nostr";
    261 import type { IError } from "@radroots/types-bindings";
    262 import { err_msg, handle_err, type IdbClientConfig } from "@radroots/utils";
    263 import { IDB_CONFIG_REPLICA } from "../idb/config.js";
    264 import type { SqlJsMigrationRow, SqlJsMigrationState, WebSqlEngineConfig } from "../sql/types.js";
    265 import { WebSqlEngine } from "../sql/web.js";
    266 import { radroots_sql_install_bridges } from "./bridge.js";
    267 import { cl_replica_error } from "./error.js";
    268 import type { IWebReplicaDatabase } from "./types.js";
    269 
    270 export type ReplicaDatabaseSchemaEntry = {
    271     object_type: string;
    272     name: string;
    273     table_name?: string;
    274     sql?: string;
    275 };
    276 
    277 export type ReplicaDatabaseMigrationEntry = {
    278     name: string;
    279     up_sql: string;
    280     down_sql: string;
    281 };
    282 
    283 export type ReplicaDatabaseJsonExport = {
    284     format_version: string;
    285     replica_db_version: string;
    286     schema: ReplicaDatabaseSchemaEntry[];
    287     data: {
    288         name: string;
    289         rows: Record<string, unknown>[];
    290     }[];
    291     migrations: ReplicaDatabaseMigrationEntry[];
    292 };
    293 
    294 export type ReplicaDatabaseExportManifestRs = {
    295     export_version: string;
    296     replica_db_version: string;
    297     backup_format_version: string;
    298     schema_hash: string;
    299     schema: ReplicaDatabaseSchemaEntry[];
    300     migrations: ReplicaDatabaseMigrationEntry[];
    301     table_counts: {
    302         name: string;
    303         row_count: number;
    304     }[];
    305 };
    306 
    307 export type NostrEventEnvelope = {
    308     id: string;
    309     pubkey: string;
    310     created_at: number;
    311     kind: number;
    312     tags: string[][];
    313     content: string;
    314     sig: string;
    315 };
    316 
    317 export type ReplicaDatabaseExportManifestTs = {
    318     app_name: string;
    319     app_version: string;
    320     exported_at: string;
    321     db_sha256: string;
    322     db_size_bytes: number;
    323     store_key: string;
    324     nostr_event?: NostrEventEnvelope;
    325 };
    326 
    327 export type ReplicaDatabaseExportManifest = {
    328     rust: ReplicaDatabaseExportManifestRs;
    329     client: ReplicaDatabaseExportManifestTs;
    330 };
    331 
    332 export type ReplicaDatabaseExportSnapshot = {
    333     manifest_rs: ReplicaDatabaseExportManifestRs;
    334     db_bytes: Uint8Array;
    335 };
    336 
    337 export type ReplicaDatabaseExportSignRequest = {
    338     db_sha256: string;
    339     manifest: ReplicaDatabaseExportManifest;
    340 };
    341 
    342 export type ReplicaDatabaseExportSigner = (opts: ReplicaDatabaseExportSignRequest) => Promise<NostrEventEnvelope | null>;
    343 
    344 export type ReplicaDatabaseExportOptions = {
    345     app_name: string;
    346     app_version: string;
    347     store_key?: string;
    348     signer?: ReplicaDatabaseExportSigner;
    349 };
    350 
    351 export type ReplicaNostrSyncSigner = {
    352     secret_key: string;
    353 };
    354 
    355 export type ReplicaNostrEventDraft = {
    356     kind: number;
    357     author: string;
    358     content: string;
    359     tags: string[][];
    360 };
    361 
    362 export type ReplicaNostrSyncBundle = {
    363     version: number;
    364     events: ReplicaNostrEventDraft[];
    365 };
    366 
    367 export type ReplicaNostrSyncOptions = {
    368     relays: string[];
    369     signers: ReplicaNostrSyncSigner[];
    370     publish_timeout_ms?: number;
    371     context?: NostrContext;
    372 };
    373 
    374 export type ReplicaNostrSyncSummary = {
    375     events_total: number;
    376     events_published: number;
    377     events_failed: number;
    378     events_skipped: number;
    379     missing_signers: string[];
    380 };
    381 
    382 export type WebReplicaDatabaseConfig = {
    383     store_key?: string;
    384     idb_config?: IdbClientConfig;
    385     cipher_config?: IdbClientConfig | null;
    386     sql_wasm_path?: string;
    387 };
    388 
    389 const is_record = (value: unknown): value is Record<string, unknown> =>
    390     typeof value === "object" && value !== null && !Array.isArray(value);
    391 
    392 const is_sql_migration_row = (value: unknown): value is SqlJsMigrationRow => {
    393     if (!is_record(value)) return false;
    394     return typeof value.id === "number"
    395         && Number.isFinite(value.id)
    396         && typeof value.name === "string"
    397         && typeof value.applied_at === "string";
    398 };
    399 
    400 const is_sql_migration_row_list = (value: unknown): value is SqlJsMigrationRow[] =>
    401     Array.isArray(value) && value.every(is_sql_migration_row);
    402 
    403 const is_schema_entry = (value: unknown): value is ReplicaDatabaseSchemaEntry => {
    404     if (!is_record(value)) return false;
    405     if (typeof value.object_type !== "string") return false;
    406     if (typeof value.name !== "string") return false;
    407     if ("table_name" in value && typeof value.table_name !== "undefined" && typeof value.table_name !== "string") return false;
    408     if ("sql" in value && typeof value.sql !== "undefined" && typeof value.sql !== "string") return false;
    409     return true;
    410 };
    411 
    412 const is_json_export_data_entry = (value: unknown): value is ReplicaDatabaseJsonExport["data"][number] => {
    413     if (!is_record(value)) return false;
    414     if (typeof value.name !== "string") return false;
    415     if (!Array.isArray(value.rows)) return false;
    416     if (!value.rows.every(is_record)) return false;
    417     return true;
    418 };
    419 
    420 const is_migration_entry = (value: unknown): value is ReplicaDatabaseMigrationEntry => {
    421     if (!is_record(value)) return false;
    422     return typeof value.name === "string"
    423         && typeof value.up_sql === "string"
    424         && typeof value.down_sql === "string";
    425 };
    426 
    427 const is_table_count_entry = (value: unknown): value is ReplicaDatabaseExportManifestRs["table_counts"][number] => {
    428     if (!is_record(value)) return false;
    429     if (typeof value.name !== "string") return false;
    430     if (typeof value.row_count !== "number" || !Number.isFinite(value.row_count)) return false;
    431     return true;
    432 };
    433 
    434 const is_replica_database_json_export = (value: unknown): value is ReplicaDatabaseJsonExport => {
    435     if (!is_record(value)) return false;
    436     if (typeof value.format_version !== "string") return false;
    437     if (typeof value.replica_db_version !== "string") return false;
    438     if (!Array.isArray(value.schema) || !value.schema.every(is_schema_entry)) return false;
    439     if (!Array.isArray(value.data) || !value.data.every(is_json_export_data_entry)) return false;
    440     if (!Array.isArray(value.migrations) || !value.migrations.every(is_migration_entry)) return false;
    441     return true;
    442 };
    443 
    444 const is_export_manifest_rs = (value: unknown): value is ReplicaDatabaseExportManifestRs => {
    445     if (!is_record(value)) return false;
    446     if (typeof value.export_version !== "string") return false;
    447     if (typeof value.replica_db_version !== "string") return false;
    448     if (typeof value.backup_format_version !== "string") return false;
    449     if (typeof value.schema_hash !== "string") return false;
    450     if (!Array.isArray(value.schema) || !value.schema.every(is_schema_entry)) return false;
    451     if (!Array.isArray(value.migrations) || !value.migrations.every(is_migration_entry)) return false;
    452     if (!Array.isArray(value.table_counts) || !value.table_counts.every(is_table_count_entry)) return false;
    453     return true;
    454 };
    455 
    456 const is_export_snapshot = (value: unknown): value is ReplicaDatabaseExportSnapshot => {
    457     if (!is_record(value)) return false;
    458     if (!("manifest_rs" in value) || !is_export_manifest_rs(value.manifest_rs)) return false;
    459     if (!("db_bytes" in value) || !(value.db_bytes instanceof Uint8Array)) return false;
    460     return true;
    461 };
    462 
    463 const is_string_list = (value: unknown): value is string[] =>
    464     Array.isArray(value) && value.every((item) => typeof item === "string");
    465 
    466 const is_tag_list = (value: unknown): value is string[][] =>
    467     Array.isArray(value) && value.every(is_string_list);
    468 
    469 const is_replica_nostr_event_draft = (value: unknown): value is ReplicaNostrEventDraft => {
    470     if (!is_record(value)) return false;
    471     if (typeof value.kind !== "number" || !Number.isFinite(value.kind)) return false;
    472     if (typeof value.author !== "string") return false;
    473     if (typeof value.content !== "string") return false;
    474     if (!is_tag_list(value.tags)) return false;
    475     return true;
    476 };
    477 
    478 const is_replica_nostr_sync_bundle = (value: unknown): value is ReplicaNostrSyncBundle => {
    479     if (!is_record(value)) return false;
    480     if (typeof value.version !== "number" || !Number.isFinite(value.version)) return false;
    481     if (!Array.isArray(value.events) || !value.events.every(is_replica_nostr_event_draft)) return false;
    482     return true;
    483 };
    484 
    485 const parse_replica_nostr_sync_bundle = (value: unknown): ReplicaNostrSyncBundle | IError<string> => {
    486     let parsed: unknown = value;
    487     if (typeof value === "string") {
    488         try {
    489             parsed = JSON.parse(value);
    490         } catch {
    491             return err_msg(cl_replica_error.parse_failure);
    492         }
    493     }
    494     if (!is_replica_nostr_sync_bundle(parsed)) return err_msg(cl_replica_error.invalid_response);
    495     return parsed;
    496 };
    497 
    498 const is_ingest_outcome = (value: unknown): value is "applied" | "skipped" =>
    499     value === "applied" || value === "skipped";
    500 
    501 const replica_sync_event_d_tag = (tags: string[][]): string => {
    502     const match = tags.find((tag) => tag[0] === "d");
    503     const value = match?.[1];
    504     return typeof value === "string" ? value : "";
    505 };
    506 
    507 const replica_sync_event_key = (draft: ReplicaNostrEventDraft): string =>
    508     `${draft.kind}:${draft.author}:${replica_sync_event_d_tag(draft.tags)}`;
    509 
    510 const build_signer_map = (signers: ReplicaNostrSyncSigner[]): Record<string, string> => {
    511     const map: Record<string, string> = {};
    512     for (const signer of signers) {
    513         const secret_key = signer.secret_key;
    514         if (!secret_key || typeof secret_key !== "string") continue;
    515         const pubkey = nostr_public_key_from_secret(secret_key);
    516         map[pubkey] = secret_key;
    517     }
    518     return map;
    519 };
    520 
    521 const publish_results_has_success = (results: Record<string, { status?: string }>): boolean =>
    522     Object.values(results).some((result) => result.status === "success");
    523 
    524 type ZipEntry = {
    525     name: string;
    526     data: Uint8Array;
    527 };
    528 
    529 type ZipEntryPrepared = {
    530     name_bytes: Uint8Array;
    531     data: Uint8Array;
    532     crc32: number;
    533     size: number;
    534 };
    535 
    536 type ZipFilePickerOptions = {
    537     suggestedName?: string;
    538     types?: {
    539         description?: string;
    540         accept: Record<string, string[]>;
    541     }[];
    542 };
    543 
    544 type ZipFileHandle = {
    545     createWritable(): Promise<ZipFileWritable>;
    546 };
    547 
    548 type ZipFileWritable = {
    549     write(data: Uint8Array): Promise<void>;
    550     close(): Promise<void>;
    551 };
    552 
    553 type ZipFilePicker = (options?: ZipFilePickerOptions) => Promise<ZipFileHandle>;
    554 
    555 const ZIP_CRC_TABLE = (() => {
    556     const table = new Uint32Array(256);
    557     for (let i = 0; i < 256; i++) {
    558         let c = i;
    559         for (let k = 0; k < 8; k++) {
    560             if (c & 1) c = 0xedb88320 ^ (c >>> 1);
    561             else c >>>= 1;
    562         }
    563         table[i] = c >>> 0;
    564     }
    565     return table;
    566 })();
    567 
    568 const crc32 = (data: Uint8Array): number => {
    569     let crc = 0xffffffff;
    570     for (let i = 0; i < data.length; i++) {
    571         const idx = (crc ^ data[i]) & 0xff;
    572         crc = ZIP_CRC_TABLE[idx] ^ (crc >>> 8);
    573     }
    574     return (crc ^ 0xffffffff) >>> 0;
    575 };
    576 
    577 const zip_prepare_entries = (entries: ZipEntry[]): ZipEntryPrepared[] => {
    578     const enc = new TextEncoder();
    579     return entries.map((entry) => ({
    580         name_bytes: enc.encode(entry.name),
    581         data: entry.data,
    582         crc32: crc32(entry.data),
    583         size: entry.data.length
    584     }));
    585 };
    586 
    587 const zip_dos_time = (date: Date): { time: number; date: number } => {
    588     const year = Math.max(1980, date.getFullYear());
    589     const month = date.getMonth() + 1;
    590     const day = date.getDate();
    591     const hours = date.getHours();
    592     const minutes = date.getMinutes();
    593     const seconds = Math.floor(date.getSeconds() / 2);
    594     const time = (hours << 11) | (minutes << 5) | seconds;
    595     const date_val = ((year - 1980) << 9) | (month << 5) | day;
    596     return { time, date: date_val };
    597 };
    598 
    599 const zip_local_header = (entry: ZipEntryPrepared, time: number, date: number): Uint8Array => {
    600     const name_len = entry.name_bytes.length;
    601     const buffer = new ArrayBuffer(30 + name_len);
    602     const view = new DataView(buffer);
    603     view.setUint32(0, 0x04034b50, true);
    604     view.setUint16(4, 20, true);
    605     view.setUint16(6, 0, true);
    606     view.setUint16(8, 0, true);
    607     view.setUint16(10, time, true);
    608     view.setUint16(12, date, true);
    609     view.setUint32(14, entry.crc32, true);
    610     view.setUint32(18, entry.size, true);
    611     view.setUint32(22, entry.size, true);
    612     view.setUint16(26, name_len, true);
    613     view.setUint16(28, 0, true);
    614     const out = new Uint8Array(buffer);
    615     out.set(entry.name_bytes, 30);
    616     return out;
    617 };
    618 
    619 const zip_central_header = (
    620     entry: ZipEntryPrepared,
    621     time: number,
    622     date: number,
    623     offset: number
    624 ): Uint8Array => {
    625     const name_len = entry.name_bytes.length;
    626     const buffer = new ArrayBuffer(46 + name_len);
    627     const view = new DataView(buffer);
    628     view.setUint32(0, 0x02014b50, true);
    629     view.setUint16(4, 20, true);
    630     view.setUint16(6, 20, true);
    631     view.setUint16(8, 0, true);
    632     view.setUint16(10, 0, true);
    633     view.setUint16(12, time, true);
    634     view.setUint16(14, date, true);
    635     view.setUint32(16, entry.crc32, true);
    636     view.setUint32(20, entry.size, true);
    637     view.setUint32(24, entry.size, true);
    638     view.setUint16(28, name_len, true);
    639     view.setUint16(30, 0, true);
    640     view.setUint16(32, 0, true);
    641     view.setUint16(34, 0, true);
    642     view.setUint16(36, 0, true);
    643     view.setUint32(38, 0, true);
    644     view.setUint32(42, offset, true);
    645     const out = new Uint8Array(buffer);
    646     out.set(entry.name_bytes, 46);
    647     return out;
    648 };
    649 
    650 const zip_end_record = (entry_count: number, central_size: number, central_offset: number): Uint8Array => {
    651     const buffer = new ArrayBuffer(22);
    652     const view = new DataView(buffer);
    653     view.setUint32(0, 0x06054b50, true);
    654     view.setUint16(4, 0, true);
    655     view.setUint16(6, 0, true);
    656     view.setUint16(8, entry_count, true);
    657     view.setUint16(10, entry_count, true);
    658     view.setUint32(12, central_size, true);
    659     view.setUint32(16, central_offset, true);
    660     view.setUint16(20, 0, true);
    661     return new Uint8Array(buffer);
    662 };
    663 
    664 const zip_build_bytes = (entries: ZipEntry[]): Uint8Array => {
    665     const prepared = zip_prepare_entries(entries);
    666     const { time, date } = zip_dos_time(new Date());
    667     const local_parts: Uint8Array[] = [];
    668     const central_parts: Uint8Array[] = [];
    669     let offset = 0;
    670 
    671     for (const entry of prepared) {
    672         const local = zip_local_header(entry, time, date);
    673         local_parts.push(local, entry.data);
    674         const central = zip_central_header(entry, time, date, offset);
    675         central_parts.push(central);
    676         offset += local.length + entry.data.length;
    677     }
    678 
    679     let central_size = 0;
    680     for (const part of central_parts) central_size += part.length;
    681     const end = zip_end_record(prepared.length, central_size, offset);
    682     const total = offset + central_size + end.length;
    683     const out = new Uint8Array(total);
    684     let cursor = 0;
    685     for (const part of local_parts) {
    686         out.set(part, cursor);
    687         cursor += part.length;
    688     }
    689     for (const part of central_parts) {
    690         out.set(part, cursor);
    691         cursor += part.length;
    692     }
    693     out.set(end, cursor);
    694     return out;
    695 };
    696 
    697 const zip_write_stream = async (stream: ZipFileWritable, entries: ZipEntry[]): Promise<void> => {
    698     const prepared = zip_prepare_entries(entries);
    699     const { time, date } = zip_dos_time(new Date());
    700     const central_parts: Uint8Array[] = [];
    701     let offset = 0;
    702 
    703     for (const entry of prepared) {
    704         const local = zip_local_header(entry, time, date);
    705         await stream.write(local);
    706         await stream.write(entry.data);
    707         central_parts.push(zip_central_header(entry, time, date, offset));
    708         offset += local.length + entry.data.length;
    709     }
    710 
    711     let central_size = 0;
    712     for (const part of central_parts) central_size += part.length;
    713     for (const part of central_parts) await stream.write(part);
    714     const end = zip_end_record(prepared.length, central_size, offset);
    715     await stream.write(end);
    716     await stream.close();
    717 };
    718 
    719 type TarEntry = {
    720     name: string;
    721     data: Uint8Array;
    722 };
    723 
    724 const TAR_BLOCK_SIZE = 512;
    725 
    726 const tar_pad_size = (size: number): number => {
    727     const rem = size % TAR_BLOCK_SIZE;
    728     return rem === 0 ? 0 : TAR_BLOCK_SIZE - rem;
    729 };
    730 
    731 const tar_write_string = (buf: Uint8Array, offset: number, length: number, value: string): void => {
    732     const enc = new TextEncoder();
    733     const bytes = enc.encode(value);
    734     const slice = bytes.length > length ? bytes.slice(0, length) : bytes;
    735     buf.set(slice, offset);
    736 };
    737 
    738 const tar_write_octal = (buf: Uint8Array, offset: number, length: number, value: number): void => {
    739     const str = value.toString(8).padStart(length - 1, "0");
    740     tar_write_string(buf, offset, length - 1, str);
    741     buf[offset + length - 1] = 0;
    742 };
    743 
    744 const tar_header = (entry: TarEntry, mtime: number): Uint8Array => {
    745     if (entry.name.length > 100) throw new Error("tar entry name too long");
    746     const header = new Uint8Array(TAR_BLOCK_SIZE);
    747     tar_write_string(header, 0, 100, entry.name);
    748     tar_write_octal(header, 100, 8, 0o644);
    749     tar_write_octal(header, 108, 8, 0);
    750     tar_write_octal(header, 116, 8, 0);
    751     tar_write_octal(header, 124, 12, entry.data.length);
    752     tar_write_octal(header, 136, 12, mtime);
    753     for (let i = 148; i < 156; i++) header[i] = 32;
    754     header[156] = 48;
    755     tar_write_string(header, 257, 6, "ustar");
    756     tar_write_string(header, 263, 2, "00");
    757     let checksum = 0;
    758     for (let i = 0; i < header.length; i++) checksum += header[i];
    759     const chk = checksum.toString(8).padStart(6, "0");
    760     tar_write_string(header, 148, 6, chk);
    761     header[154] = 0;
    762     header[155] = 32;
    763     return header;
    764 };
    765 
    766 const tar_build_bytes = (entries: TarEntry[], mtime: number): Uint8Array => {
    767     const parts: Uint8Array[] = [];
    768     let total = 0;
    769     for (const entry of entries) {
    770         const header = tar_header(entry, mtime);
    771         const pad = tar_pad_size(entry.data.length);
    772         parts.push(header, entry.data);
    773         total += header.length + entry.data.length;
    774         if (pad) {
    775             const padding = new Uint8Array(pad);
    776             parts.push(padding);
    777             total += padding.length;
    778         }
    779     }
    780     const end = new Uint8Array(TAR_BLOCK_SIZE * 2);
    781     parts.push(end);
    782     total += end.length;
    783     const out = new Uint8Array(total);
    784     let offset = 0;
    785     for (const part of parts) {
    786         out.set(part, offset);
    787         offset += part.length;
    788     }
    789     return out;
    790 };
    791 
    792 const tar_stream = (entries: TarEntry[], mtime: number): ReadableStream<Uint8Array> => {
    793     return new ReadableStream({
    794         start(controller) {
    795             for (const entry of entries) {
    796                 const header = tar_header(entry, mtime);
    797                 controller.enqueue(header);
    798                 controller.enqueue(entry.data);
    799                 const pad = tar_pad_size(entry.data.length);
    800                 if (pad) controller.enqueue(new Uint8Array(pad));
    801             }
    802             controller.enqueue(new Uint8Array(TAR_BLOCK_SIZE * 2));
    803             controller.close();
    804         }
    805     });
    806 };
    807 
    808 const gzip_bytes = async (bytes: Uint8Array): Promise<Uint8Array> => {
    809     if (typeof CompressionStream === "undefined") {
    810         throw new Error("replica export requires gzip support");
    811     }
    812     const stream = new CompressionStream("gzip");
    813     const writer = stream.writable.getWriter();
    814     writer.write(bytes);
    815     await writer.close();
    816     const buffer = await new Response(stream.readable).arrayBuffer();
    817     return new Uint8Array(buffer);
    818 };
    819 
    820 const bytes_to_hex = (bytes: Uint8Array): string => {
    821     const hex: string[] = [];
    822     for (let i = 0; i < bytes.length; i++) {
    823         hex.push(bytes[i].toString(16).padStart(2, "0"));
    824     }
    825     return hex.join("");
    826 };
    827 
    828 const sha256_hex = async (bytes: Uint8Array): Promise<string> => {
    829     if (!globalThis.crypto || !globalThis.crypto.subtle) throw new Error(cl_replica_error.crypto_unavailable);
    830     const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
    831     return bytes_to_hex(new Uint8Array(digest));
    832 };
    833 
    834 const filename_slug = (value: string): string => {
    835     const slug = value
    836         .toLowerCase()
    837         .replace(/[^a-z0-9]+/g, "-")
    838         .replace(/^-+|-+$/g, "");
    839     if (slug.startsWith("radroots-")) return slug;
    840     return `radroots-${slug}`;
    841 };
    842 
    843 const export_filename = (app_name: string, app_version: string): string => {
    844     const base = filename_slug(app_name);
    845     return `${base}-${app_version}-backup.tar.gz`;
    846 };
    847 
    848 const get_zip_file_picker = (): ZipFilePicker | undefined => {
    849     if (typeof window === "undefined") return undefined;
    850     const picker = (window as Window & { showSaveFilePicker?: ZipFilePicker }).showSaveFilePicker;
    851     return picker;
    852 };
    853 
    854 const user_activation_is_active = (): boolean => {
    855     if (typeof navigator === "undefined") return false;
    856     const nav = navigator as Navigator & { userActivation?: { isActive?: boolean } };
    857     if (!nav.userActivation) return true;
    858     return nav.userActivation.isActive === true;
    859 };
    860 
    861 const is_permission_error = (err: unknown): boolean => {
    862     if (!err) return false;
    863     if (typeof err === "string") {
    864         const msg = err.toLowerCase();
    865         return msg.includes("permission") || msg.includes("denied") || msg.includes("not allowed");
    866     }
    867     if (!is_record(err)) return false;
    868     const name = typeof err.name === "string" ? err.name.toLowerCase() : "";
    869     const message = typeof err.message === "string" ? err.message.toLowerCase() : "";
    870     if (name.includes("notallowed") || name.includes("abort")) return true;
    871     return message.includes("permission") || message.includes("denied") || message.includes("not allowed");
    872 };
    873 
    874 const can_share_file = (file: File): boolean => {
    875     if (typeof navigator === "undefined") return false;
    876     const nav = navigator as Navigator & { canShare?: (data: { files?: File[] }) => boolean };
    877     if (!nav.canShare) return false;
    878     return nav.canShare({ files: [file] });
    879 };
    880 
    881 const share_file = async (file: File): Promise<boolean> => {
    882     if (typeof navigator === "undefined") return false;
    883     const nav = navigator as Navigator & { share?: (data: { files?: File[]; title?: string }) => Promise<void> };
    884     if (!nav.share) return false;
    885     await nav.share({ files: [file], title: file.name });
    886     return true;
    887 };
    888 
    889 const download_blob = (blob: Blob, filename: string): void => {
    890     if (typeof document === "undefined") return;
    891     const url = URL.createObjectURL(blob);
    892     const anchor = document.createElement("a");
    893     anchor.href = url;
    894     anchor.download = filename;
    895     anchor.click();
    896     URL.revokeObjectURL(url);
    897 };
    898 
    899 const export_tar_gz = async (filename: string, entries: TarEntry[], mtime: number): Promise<void> => {
    900     const picker = get_zip_file_picker();
    901     if (picker && user_activation_is_active() && typeof CompressionStream !== "undefined") {
    902         try {
    903             const handle = await picker({
    904                 suggestedName: filename,
    905                 types: [
    906                     {
    907                         description: "Radroots replica export",
    908                         accept: { "application/gzip": [".tar.gz"] }
    909                     }
    910                 ]
    911             });
    912             const stream = await handle.createWritable();
    913             const writable_stream = new WritableStream<Uint8Array>({
    914                 write: (chunk) => stream.write(chunk),
    915                 close: () => stream.close()
    916             });
    917             await tar_stream(entries, mtime)
    918                 .pipeThrough(new CompressionStream("gzip"))
    919                 .pipeTo(writable_stream);
    920             return;
    921         } catch (e) {
    922             if (!is_permission_error(e)) throw e;
    923         }
    924     }
    925     const tar_bytes = tar_build_bytes(entries, mtime);
    926     const gz_bytes = await gzip_bytes(tar_bytes);
    927     const blob = new Blob([gz_bytes], { type: "application/gzip" });
    928     const file = new File([blob], filename, { type: "application/gzip" });
    929     if (can_share_file(file) && user_activation_is_active()) {
    930         try {
    931             const shared = await share_file(file);
    932             if (shared) return;
    933         } catch (e) {
    934             if (!is_permission_error(e)) throw e;
    935         }
    936     }
    937     download_blob(blob, filename);
    938 };
    939 
    940 const DEFAULT_REPLICA_STORE_KEY = "radroots-pwa-v1-replica-db";
    941 const DEFAULT_REPLICA_IDB_CONFIG: IdbClientConfig = IDB_CONFIG_REPLICA;
    942 let wasm_init_promise: Promise<void> | null = null;
    943 
    944 const runtime_available = (): boolean => {
    945     return typeof window !== "undefined" || typeof self !== "undefined";
    946 };
    947 
    948 const wasm_init_once = async (): Promise<void> => {
    949     if (!wasm_init_promise) {
    950         wasm_init_promise = (async () => {
    951             await init_wasm();
    952             await init_replica_sync_wasm();
    953         })();
    954     }
    955     try {
    956         await wasm_init_promise;
    957     } catch (e) {
    958         wasm_init_promise = null;
    959         throw e;
    960     }
    961 };
    962 
    963 export class WebReplicaDatabase implements IWebReplicaDatabase {
    964     private engine: WebSqlEngine | null = null;
    965     private readonly store_key: string;
    966     private readonly idb_config: IdbClientConfig;
    967     private readonly cipher_config: IdbClientConfig | null;
    968     private readonly sql_wasm_path: string | undefined;
    969     private init_promise: Promise<void> | null = null;
    970 
    971     constructor(config?: WebReplicaDatabaseConfig) {
    972         this.store_key = config?.store_key ?? DEFAULT_REPLICA_STORE_KEY;
    973         this.idb_config = config?.idb_config ?? DEFAULT_REPLICA_IDB_CONFIG;
    974         this.cipher_config = config?.cipher_config ?? null;
    975         this.sql_wasm_path = config?.sql_wasm_path;
    976     }
    977 
    978     get_store_key(): string {
    979         return this.store_key;
    980     }
    981 
    982     private serialize<T>(opts: T): string {
    983         return JSON.stringify(opts);
    984     }
    985 
    986     private deserialize<T>(data: string): T | IError<string> {
    987         try {
    988             return JSON.parse(data);
    989         } catch {
    990             return err_msg(cl_replica_error.parse_failure);
    991         }
    992     }
    993 
    994     private get_engine_config(): WebSqlEngineConfig {
    995         return {
    996             store_key: this.store_key,
    997             idb_config: this.idb_config,
    998             cipher_config: this.cipher_config,
    999             sql_wasm_path: this.sql_wasm_path
   1000         };
   1001     }
   1002 
   1003     private async ensure_ready(): Promise<void> {
   1004         await this.init();
   1005         if (!this.engine) throw new Error(cl_replica_error.init_failure);
   1006     }
   1007 
   1008     async init(): Promise<void> {
   1009         if (this.engine) return;
   1010         if (!runtime_available()) throw new Error(cl_replica_error.runtime_unavailable);
   1011         if (!this.init_promise) {
   1012             this.init_promise = (async () => {
   1013                 await wasm_init_once();
   1014                 this.engine = await WebSqlEngine.create(this.get_engine_config());
   1015                 radroots_sql_install_bridges(this.engine);
   1016                 replica_db_run_migrations();
   1017             })();
   1018         }
   1019         try {
   1020             await this.init_promise;
   1021         } catch (e) {
   1022             this.engine = null;
   1023             this.init_promise = null;
   1024             throw e;
   1025         }
   1026     }
   1027 
   1028     async close(): Promise<void> {
   1029         if (this.engine) await this.engine.close();
   1030         this.engine = null;
   1031         this.init_promise = null;
   1032     }
   1033 
   1034     async migration_state(): Promise<SqlJsMigrationState | IError<string>> {
   1035         try {
   1036             await this.ensure_ready();
   1037             const parsed = this.engine?.query("select id, name, applied_at from __migrations order by id asc", []) ?? [];
   1038             if (!is_sql_migration_row_list(parsed)) return err_msg(cl_replica_error.invalid_response);
   1039             const names = parsed.map((row) => row.name);
   1040             return { applied_names: names, applied_count: names.length };
   1041         } catch (e) {
   1042             return handle_err(e);
   1043         }
   1044     }
   1045 
   1046     async reset(): Promise<SqlJsMigrationState | IError<string>> {
   1047         try {
   1048             await this.ensure_ready();
   1049             replica_db_reset_database();
   1050             replica_db_run_migrations();
   1051             return this.migration_state();
   1052         } catch (e) {
   1053             return handle_err(e);
   1054         }
   1055     }
   1056 
   1057     async reinit(): Promise<SqlJsMigrationState | IError<string>> {
   1058         try {
   1059             await this.ensure_ready();
   1060             if (this.engine) {
   1061                 await this.engine.purge_storage();
   1062                 await this.engine.close();
   1063             }
   1064             this.engine = await WebSqlEngine.create(this.get_engine_config());
   1065             radroots_sql_install_bridges(this.engine);
   1066             replica_db_run_migrations();
   1067             return this.migration_state();
   1068         } catch (e) {
   1069             return handle_err(e);
   1070         }
   1071     }
   1072 
   1073     async export_json(): Promise<ReplicaDatabaseJsonExport | IError<string>> {
   1074         try {
   1075             await this.ensure_ready();
   1076             const res = await replica_db_export_json();
   1077             let parsed: unknown = res;
   1078             if (typeof res === "string") {
   1079                 try {
   1080                     parsed = JSON.parse(res);
   1081                 } catch {
   1082                     return err_msg(cl_replica_error.parse_failure);
   1083                 }
   1084             }
   1085             if (!is_replica_database_json_export(parsed)) return err_msg(cl_replica_error.invalid_response);
   1086             return parsed;
   1087         } catch (e) {
   1088             return handle_err(e);
   1089         }
   1090     }
   1091 
   1092     async import_json(backup: ReplicaDatabaseJsonExport): Promise<void | IError<string>> {
   1093         try {
   1094             await this.ensure_ready();
   1095             replica_db_import_json(this.serialize(backup));
   1096             return;
   1097         } catch (e) {
   1098             return handle_err(e);
   1099         }
   1100     }
   1101 
   1102     async export_database(opts: ReplicaDatabaseExportOptions): Promise<void | IError<string>> {
   1103         try {
   1104             if (opts.store_key && opts.store_key !== this.store_key) {
   1105                 const alt_db = new WebReplicaDatabase({
   1106                     store_key: opts.store_key,
   1107                     idb_config: this.idb_config,
   1108                     cipher_config: this.cipher_config,
   1109                     sql_wasm_path: this.sql_wasm_path
   1110                 });
   1111                 const res = await alt_db.export_database(opts);
   1112                 await alt_db.close();
   1113                 return res;
   1114             }
   1115             await this.export_database_inner(opts);
   1116             return;
   1117         } catch (e) {
   1118             return handle_err(e);
   1119         }
   1120     }
   1121 
   1122     async nostr_sync_all(opts: ReplicaNostrSyncOptions): Promise<ReplicaNostrSyncSummary | IError<string>> {
   1123         try {
   1124             await this.ensure_ready();
   1125             const relays = Array.from(new Set(opts.relays.map((relay) => relay.trim()).filter((relay) => relay.length)));
   1126             if (!relays.length) return err_msg(`replica sync requires relays`);
   1127             if (!opts.signers.length) return err_msg(`replica sync requires signers`);
   1128             const signer_map = build_signer_map(opts.signers);
   1129             if (!Object.keys(signer_map).length) return err_msg(`replica sync requires valid signers`);
   1130 
   1131             const farms = await this.farm_find_many();
   1132             if ("err" in farms) return farms;
   1133             const event_map: Record<string, ReplicaNostrEventDraft> = {};
   1134             for (const farm of farms.results) {
   1135                 const bundle_raw = replica_sync_sync_all(this.serialize({
   1136                     farm: { id: farm.id },
   1137                     options: null
   1138                 }));
   1139                 const bundle = parse_replica_nostr_sync_bundle(bundle_raw);
   1140                 if ("err" in bundle) return bundle;
   1141                 for (const draft of bundle.events) {
   1142                     const key = replica_sync_event_key(draft);
   1143                     if (!event_map[key]) event_map[key] = draft;
   1144                 }
   1145             }
   1146 
   1147             const event_keys = Object.keys(event_map);
   1148             event_keys.sort();
   1149             if (!event_keys.length) {
   1150                 return {
   1151                     events_total: 0,
   1152                     events_published: 0,
   1153                     events_failed: 0,
   1154                     events_skipped: 0,
   1155                     missing_signers: []
   1156                 };
   1157             }
   1158 
   1159             const context = opts.context ?? nostr_context_create();
   1160             const context_owned = !opts.context;
   1161             let events_published = 0;
   1162             let events_failed = 0;
   1163             let events_skipped = 0;
   1164             const missing_signers = new Set<string>();
   1165 
   1166             try {
   1167                 nostr_relays_open(context, relays);
   1168                 for (const key of event_keys) {
   1169                     const draft = event_map[key];
   1170                     const secret_key = signer_map[draft.author];
   1171                     if (!secret_key) {
   1172                         missing_signers.add(draft.author);
   1173                         events_skipped += 1;
   1174                         continue;
   1175                     }
   1176                     const event = nostr_event_sign({
   1177                         secret_key,
   1178                         event: {
   1179                             kind: draft.kind,
   1180                             created_at: Math.floor(Date.now() / 1000),
   1181                             tags: draft.tags,
   1182                             content: draft.content
   1183                         }
   1184                     });
   1185                     const publish_results = await nostr_publish({
   1186                         event,
   1187                         relays,
   1188                         context,
   1189                         timeout: opts.publish_timeout_ms
   1190                     });
   1191                     if (!publish_results_has_success(publish_results)) {
   1192                         events_failed += 1;
   1193                         continue;
   1194                     }
   1195                     const ingest_result = replica_sync_ingest_event(this.serialize(event));
   1196                     if (!is_ingest_outcome(ingest_result)) {
   1197                         events_failed += 1;
   1198                         continue;
   1199                     }
   1200                     events_published += 1;
   1201                 }
   1202             } finally {
   1203                 if (context_owned) nostr_relays_clear(context);
   1204             }
   1205 
   1206             const summary: ReplicaNostrSyncSummary = {
   1207                 events_total: event_keys.length,
   1208                 events_published,
   1209                 events_failed,
   1210                 events_skipped,
   1211                 missing_signers: Array.from(missing_signers)
   1212             };
   1213             if (summary.missing_signers.length) return err_msg(`replica sync missing signers: ${summary.missing_signers.join(", ")}`);
   1214             if (summary.events_failed) return err_msg(`replica sync publish failed (${summary.events_failed}/${summary.events_total})`);
   1215             return summary;
   1216         } catch (e) {
   1217             return handle_err(e);
   1218         }
   1219     }
   1220 
   1221     private async export_database_inner(opts: ReplicaDatabaseExportOptions): Promise<void> {
   1222         await this.ensure_ready();
   1223         const app_name = opts.app_name;
   1224         const app_version = opts.app_version;
   1225         const store_key = this.store_key;
   1226         let export_active = false;
   1227 
   1228         try {
   1229             const snapshot_raw = await replica_db_export_begin();
   1230             export_active = true;
   1231             if (!is_export_snapshot(snapshot_raw)) throw new Error(cl_replica_error.invalid_response);
   1232             const manifest_rs = snapshot_raw.manifest_rs;
   1233             const db_bytes = snapshot_raw.db_bytes;
   1234             const db_sha256 = await sha256_hex(db_bytes);
   1235             const exported_at = new Date().toISOString();
   1236 
   1237             const manifest_ts_base: ReplicaDatabaseExportManifestTs = {
   1238                 app_name,
   1239                 app_version,
   1240                 exported_at,
   1241                 db_sha256,
   1242                 db_size_bytes: db_bytes.byteLength,
   1243                 store_key
   1244             };
   1245 
   1246             let manifest: ReplicaDatabaseExportManifest = {
   1247                 rust: manifest_rs,
   1248                 client: manifest_ts_base
   1249             };
   1250 
   1251             if (opts.signer) {
   1252                 const nostr_event = await opts.signer({ db_sha256, manifest });
   1253                 if (nostr_event) {
   1254                     manifest = {
   1255                         rust: manifest_rs,
   1256                         client: {
   1257                             ...manifest_ts_base,
   1258                             nostr_event
   1259                         }
   1260                     };
   1261                 }
   1262             }
   1263 
   1264             const manifest_json = JSON.stringify(manifest, null, 2);
   1265             const manifest_bytes = new TextEncoder().encode(manifest_json);
   1266             const filename = export_filename(app_name, app_version);
   1267             const mtime = Math.floor(Date.parse(exported_at) / 1000);
   1268             await export_tar_gz(filename, [
   1269                 { name: "manifest.json", data: manifest_bytes },
   1270                 { name: "replica.db", data: db_bytes }
   1271             ], mtime);
   1272         } finally {
   1273             if (export_active) replica_db_export_finish();
   1274         }
   1275     }
   1276 
   1277     async farm_create(opts: IFarmCreate): Promise<IFarmCreateResolve | IError<string>> {
   1278         await this.ensure_ready();
   1279         const res = await replica_db_farm_create(this.serialize(opts));
   1280         return this.deserialize<IFarmCreateResolve>(res);
   1281     }
   1282 
   1283     async farm_find_one(opts: IFarmFindOne): Promise<IFarmFindOneResolve | IError<string>> {
   1284         await this.ensure_ready();
   1285         const res = await replica_db_farm_find_one(this.serialize(opts));
   1286         return this.deserialize<IFarmFindOneResolve>(res);
   1287     }
   1288 
   1289     async farm_find_many(opts?: IFarmFindMany): Promise<IFarmFindManyResolve | IError<string>> {
   1290         await this.ensure_ready();
   1291         const res = await replica_db_farm_find_many(this.serialize(opts ?? {}));
   1292         return this.deserialize<IFarmFindManyResolve>(res);
   1293     }
   1294 
   1295     async farm_delete(opts: IFarmDelete): Promise<IFarmDeleteResolve | IError<string>> {
   1296         await this.ensure_ready();
   1297         const res = await replica_db_farm_delete(this.serialize(opts));
   1298         return this.deserialize<IFarmDeleteResolve>(res);
   1299     }
   1300 
   1301     async farm_update(opts: IFarmUpdate): Promise<IFarmUpdateResolve | IError<string>> {
   1302         await this.ensure_ready();
   1303         const res = await replica_db_farm_update(this.serialize(opts));
   1304         return this.deserialize<IFarmUpdateResolve>(res);
   1305     }
   1306 
   1307     async plot_create(opts: IPlotCreate): Promise<IPlotCreateResolve | IError<string>> {
   1308         await this.ensure_ready();
   1309         const res = await replica_db_plot_create(this.serialize(opts));
   1310         return this.deserialize<IPlotCreateResolve>(res);
   1311     }
   1312 
   1313     async plot_find_one(opts: IPlotFindOne): Promise<IPlotFindOneResolve | IError<string>> {
   1314         await this.ensure_ready();
   1315         const res = await replica_db_plot_find_one(this.serialize(opts));
   1316         return this.deserialize<IPlotFindOneResolve>(res);
   1317     }
   1318 
   1319     async plot_find_many(opts?: IPlotFindMany): Promise<IPlotFindManyResolve | IError<string>> {
   1320         await this.ensure_ready();
   1321         const res = await replica_db_plot_find_many(this.serialize(opts ?? {}));
   1322         return this.deserialize<IPlotFindManyResolve>(res);
   1323     }
   1324 
   1325     async plot_delete(opts: IPlotDelete): Promise<IPlotDeleteResolve | IError<string>> {
   1326         await this.ensure_ready();
   1327         const res = await replica_db_plot_delete(this.serialize(opts));
   1328         return this.deserialize<IPlotDeleteResolve>(res);
   1329     }
   1330 
   1331     async plot_update(opts: IPlotUpdate): Promise<IPlotUpdateResolve | IError<string>> {
   1332         await this.ensure_ready();
   1333         const res = await replica_db_plot_update(this.serialize(opts));
   1334         return this.deserialize<IPlotUpdateResolve>(res);
   1335     }
   1336 
   1337     async gcs_location_create(opts: IGcsLocationCreate): Promise<IGcsLocationCreateResolve | IError<string>> {
   1338         await this.ensure_ready();
   1339         const res = await replica_db_gcs_location_create(this.serialize(opts));
   1340         return this.deserialize<IGcsLocationCreateResolve>(res);
   1341     }
   1342 
   1343     async gcs_location_find_one(opts: IGcsLocationFindOne): Promise<IGcsLocationFindOneResolve | IError<string>> {
   1344         await this.ensure_ready();
   1345         const res = await replica_db_gcs_location_find_one(this.serialize(opts));
   1346         return this.deserialize<IGcsLocationFindOneResolve>(res);
   1347     }
   1348 
   1349     async gcs_location_find_many(opts?: IGcsLocationFindMany): Promise<IGcsLocationFindManyResolve | IError<string>> {
   1350         await this.ensure_ready();
   1351         const res = await replica_db_gcs_location_find_many(this.serialize(opts ?? {}));
   1352         return this.deserialize<IGcsLocationFindManyResolve>(res);
   1353     }
   1354 
   1355     async gcs_location_delete(opts: IGcsLocationDelete): Promise<IGcsLocationDeleteResolve | IError<string>> {
   1356         await this.ensure_ready();
   1357         const res = await replica_db_gcs_location_delete(this.serialize(opts));
   1358         return this.deserialize<IGcsLocationDeleteResolve>(res);
   1359     }
   1360 
   1361     async gcs_location_update(opts: IGcsLocationUpdate): Promise<IGcsLocationUpdateResolve | IError<string>> {
   1362         await this.ensure_ready();
   1363         const res = await replica_db_gcs_location_update(this.serialize(opts));
   1364         return this.deserialize<IGcsLocationUpdateResolve>(res);
   1365     }
   1366 
   1367     async farm_gcs_location_create(opts: IFarmGcsLocationCreate): Promise<IFarmGcsLocationCreateResolve | IError<string>> {
   1368         await this.ensure_ready();
   1369         const res = await replica_db_farm_gcs_location_create(this.serialize(opts));
   1370         return this.deserialize<IFarmGcsLocationCreateResolve>(res);
   1371     }
   1372 
   1373     async farm_gcs_location_find_one(opts: IFarmGcsLocationFindOne): Promise<IFarmGcsLocationFindOneResolve | IError<string>> {
   1374         await this.ensure_ready();
   1375         const res = await replica_db_farm_gcs_location_find_one(this.serialize(opts));
   1376         return this.deserialize<IFarmGcsLocationFindOneResolve>(res);
   1377     }
   1378 
   1379     async farm_gcs_location_find_many(opts?: IFarmGcsLocationFindMany): Promise<IFarmGcsLocationFindManyResolve | IError<string>> {
   1380         await this.ensure_ready();
   1381         const res = await replica_db_farm_gcs_location_find_many(this.serialize(opts ?? {}));
   1382         return this.deserialize<IFarmGcsLocationFindManyResolve>(res);
   1383     }
   1384 
   1385     async farm_gcs_location_delete(opts: IFarmGcsLocationDelete): Promise<IFarmGcsLocationDeleteResolve | IError<string>> {
   1386         await this.ensure_ready();
   1387         const res = await replica_db_farm_gcs_location_delete(this.serialize(opts));
   1388         return this.deserialize<IFarmGcsLocationDeleteResolve>(res);
   1389     }
   1390 
   1391     async farm_gcs_location_update(opts: IFarmGcsLocationUpdate): Promise<IFarmGcsLocationUpdateResolve | IError<string>> {
   1392         await this.ensure_ready();
   1393         const res = await replica_db_farm_gcs_location_update(this.serialize(opts));
   1394         return this.deserialize<IFarmGcsLocationUpdateResolve>(res);
   1395     }
   1396 
   1397     async plot_gcs_location_create(opts: IPlotGcsLocationCreate): Promise<IPlotGcsLocationCreateResolve | IError<string>> {
   1398         await this.ensure_ready();
   1399         const res = await replica_db_plot_gcs_location_create(this.serialize(opts));
   1400         return this.deserialize<IPlotGcsLocationCreateResolve>(res);
   1401     }
   1402 
   1403     async plot_gcs_location_find_one(opts: IPlotGcsLocationFindOne): Promise<IPlotGcsLocationFindOneResolve | IError<string>> {
   1404         await this.ensure_ready();
   1405         const res = await replica_db_plot_gcs_location_find_one(this.serialize(opts));
   1406         return this.deserialize<IPlotGcsLocationFindOneResolve>(res);
   1407     }
   1408 
   1409     async plot_gcs_location_find_many(opts?: IPlotGcsLocationFindMany): Promise<IPlotGcsLocationFindManyResolve | IError<string>> {
   1410         await this.ensure_ready();
   1411         const res = await replica_db_plot_gcs_location_find_many(this.serialize(opts ?? {}));
   1412         return this.deserialize<IPlotGcsLocationFindManyResolve>(res);
   1413     }
   1414 
   1415     async plot_gcs_location_delete(opts: IPlotGcsLocationDelete): Promise<IPlotGcsLocationDeleteResolve | IError<string>> {
   1416         await this.ensure_ready();
   1417         const res = await replica_db_plot_gcs_location_delete(this.serialize(opts));
   1418         return this.deserialize<IPlotGcsLocationDeleteResolve>(res);
   1419     }
   1420 
   1421     async plot_gcs_location_update(opts: IPlotGcsLocationUpdate): Promise<IPlotGcsLocationUpdateResolve | IError<string>> {
   1422         await this.ensure_ready();
   1423         const res = await replica_db_plot_gcs_location_update(this.serialize(opts));
   1424         return this.deserialize<IPlotGcsLocationUpdateResolve>(res);
   1425     }
   1426 
   1427     async farm_tag_create(opts: IFarmTagCreate): Promise<IFarmTagCreateResolve | IError<string>> {
   1428         await this.ensure_ready();
   1429         const res = await replica_db_farm_tag_create(this.serialize(opts));
   1430         return this.deserialize<IFarmTagCreateResolve>(res);
   1431     }
   1432 
   1433     async farm_tag_find_one(opts: IFarmTagFindOne): Promise<IFarmTagFindOneResolve | IError<string>> {
   1434         await this.ensure_ready();
   1435         const res = await replica_db_farm_tag_find_one(this.serialize(opts));
   1436         return this.deserialize<IFarmTagFindOneResolve>(res);
   1437     }
   1438 
   1439     async farm_tag_find_many(opts?: IFarmTagFindMany): Promise<IFarmTagFindManyResolve | IError<string>> {
   1440         await this.ensure_ready();
   1441         const res = await replica_db_farm_tag_find_many(this.serialize(opts ?? {}));
   1442         return this.deserialize<IFarmTagFindManyResolve>(res);
   1443     }
   1444 
   1445     async farm_tag_delete(opts: IFarmTagDelete): Promise<IFarmTagDeleteResolve | IError<string>> {
   1446         await this.ensure_ready();
   1447         const res = await replica_db_farm_tag_delete(this.serialize(opts));
   1448         return this.deserialize<IFarmTagDeleteResolve>(res);
   1449     }
   1450 
   1451     async farm_tag_update(opts: IFarmTagUpdate): Promise<IFarmTagUpdateResolve | IError<string>> {
   1452         await this.ensure_ready();
   1453         const res = await replica_db_farm_tag_update(this.serialize(opts));
   1454         return this.deserialize<IFarmTagUpdateResolve>(res);
   1455     }
   1456 
   1457     async plot_tag_create(opts: IPlotTagCreate): Promise<IPlotTagCreateResolve | IError<string>> {
   1458         await this.ensure_ready();
   1459         const res = await replica_db_plot_tag_create(this.serialize(opts));
   1460         return this.deserialize<IPlotTagCreateResolve>(res);
   1461     }
   1462 
   1463     async plot_tag_find_one(opts: IPlotTagFindOne): Promise<IPlotTagFindOneResolve | IError<string>> {
   1464         await this.ensure_ready();
   1465         const res = await replica_db_plot_tag_find_one(this.serialize(opts));
   1466         return this.deserialize<IPlotTagFindOneResolve>(res);
   1467     }
   1468 
   1469     async plot_tag_find_many(opts?: IPlotTagFindMany): Promise<IPlotTagFindManyResolve | IError<string>> {
   1470         await this.ensure_ready();
   1471         const res = await replica_db_plot_tag_find_many(this.serialize(opts ?? {}));
   1472         return this.deserialize<IPlotTagFindManyResolve>(res);
   1473     }
   1474 
   1475     async plot_tag_delete(opts: IPlotTagDelete): Promise<IPlotTagDeleteResolve | IError<string>> {
   1476         await this.ensure_ready();
   1477         const res = await replica_db_plot_tag_delete(this.serialize(opts));
   1478         return this.deserialize<IPlotTagDeleteResolve>(res);
   1479     }
   1480 
   1481     async plot_tag_update(opts: IPlotTagUpdate): Promise<IPlotTagUpdateResolve | IError<string>> {
   1482         await this.ensure_ready();
   1483         const res = await replica_db_plot_tag_update(this.serialize(opts));
   1484         return this.deserialize<IPlotTagUpdateResolve>(res);
   1485     }
   1486 
   1487     async farm_member_create(opts: IFarmMemberCreate): Promise<IFarmMemberCreateResolve | IError<string>> {
   1488         await this.ensure_ready();
   1489         const res = await replica_db_farm_member_create(this.serialize(opts));
   1490         return this.deserialize<IFarmMemberCreateResolve>(res);
   1491     }
   1492 
   1493     async farm_member_find_one(opts: IFarmMemberFindOne): Promise<IFarmMemberFindOneResolve | IError<string>> {
   1494         await this.ensure_ready();
   1495         const res = await replica_db_farm_member_find_one(this.serialize(opts));
   1496         return this.deserialize<IFarmMemberFindOneResolve>(res);
   1497     }
   1498 
   1499     async farm_member_find_many(opts?: IFarmMemberFindMany): Promise<IFarmMemberFindManyResolve | IError<string>> {
   1500         await this.ensure_ready();
   1501         const res = await replica_db_farm_member_find_many(this.serialize(opts ?? {}));
   1502         return this.deserialize<IFarmMemberFindManyResolve>(res);
   1503     }
   1504 
   1505     async farm_member_delete(opts: IFarmMemberDelete): Promise<IFarmMemberDeleteResolve | IError<string>> {
   1506         await this.ensure_ready();
   1507         const res = await replica_db_farm_member_delete(this.serialize(opts));
   1508         return this.deserialize<IFarmMemberDeleteResolve>(res);
   1509     }
   1510 
   1511     async farm_member_update(opts: IFarmMemberUpdate): Promise<IFarmMemberUpdateResolve | IError<string>> {
   1512         await this.ensure_ready();
   1513         const res = await replica_db_farm_member_update(this.serialize(opts));
   1514         return this.deserialize<IFarmMemberUpdateResolve>(res);
   1515     }
   1516 
   1517     async farm_member_claim_create(opts: IFarmMemberClaimCreate): Promise<IFarmMemberClaimCreateResolve | IError<string>> {
   1518         await this.ensure_ready();
   1519         const res = await replica_db_farm_member_claim_create(this.serialize(opts));
   1520         return this.deserialize<IFarmMemberClaimCreateResolve>(res);
   1521     }
   1522 
   1523     async farm_member_claim_find_one(opts: IFarmMemberClaimFindOne): Promise<IFarmMemberClaimFindOneResolve | IError<string>> {
   1524         await this.ensure_ready();
   1525         const res = await replica_db_farm_member_claim_find_one(this.serialize(opts));
   1526         return this.deserialize<IFarmMemberClaimFindOneResolve>(res);
   1527     }
   1528 
   1529     async farm_member_claim_find_many(opts?: IFarmMemberClaimFindMany): Promise<IFarmMemberClaimFindManyResolve | IError<string>> {
   1530         await this.ensure_ready();
   1531         const res = await replica_db_farm_member_claim_find_many(this.serialize(opts ?? {}));
   1532         return this.deserialize<IFarmMemberClaimFindManyResolve>(res);
   1533     }
   1534 
   1535     async farm_member_claim_delete(opts: IFarmMemberClaimDelete): Promise<IFarmMemberClaimDeleteResolve | IError<string>> {
   1536         await this.ensure_ready();
   1537         const res = await replica_db_farm_member_claim_delete(this.serialize(opts));
   1538         return this.deserialize<IFarmMemberClaimDeleteResolve>(res);
   1539     }
   1540 
   1541     async farm_member_claim_update(opts: IFarmMemberClaimUpdate): Promise<IFarmMemberClaimUpdateResolve | IError<string>> {
   1542         await this.ensure_ready();
   1543         const res = await replica_db_farm_member_claim_update(this.serialize(opts));
   1544         return this.deserialize<IFarmMemberClaimUpdateResolve>(res);
   1545     }
   1546 
   1547     async log_error_create(opts: ILogErrorCreate): Promise<ILogErrorCreateResolve | IError<string>> {
   1548         await this.ensure_ready();
   1549         const res = await replica_db_log_error_create(this.serialize(opts));
   1550         return this.deserialize<ILogErrorCreateResolve>(res);
   1551     }
   1552 
   1553     async log_error_find_one(opts: ILogErrorFindOne): Promise<ILogErrorFindOneResolve | IError<string>> {
   1554         await this.ensure_ready();
   1555         const res = await replica_db_log_error_find_one(this.serialize(opts));
   1556         return this.deserialize<ILogErrorFindOneResolve>(res);
   1557     }
   1558 
   1559     async log_error_find_many(opts?: ILogErrorFindMany): Promise<ILogErrorFindManyResolve | IError<string>> {
   1560         await this.ensure_ready();
   1561         const res = await replica_db_log_error_find_many(this.serialize(opts ?? {}));
   1562         return this.deserialize<ILogErrorFindManyResolve>(res);
   1563     }
   1564 
   1565     async log_error_delete(opts: ILogErrorDelete): Promise<ILogErrorDeleteResolve | IError<string>> {
   1566         await this.ensure_ready();
   1567         const res = await replica_db_log_error_delete(this.serialize(opts));
   1568         return this.deserialize<ILogErrorDeleteResolve>(res);
   1569     }
   1570 
   1571     async log_error_update(opts: ILogErrorUpdate): Promise<ILogErrorUpdateResolve | IError<string>> {
   1572         await this.ensure_ready();
   1573         const res = await replica_db_log_error_update(this.serialize(opts));
   1574         return this.deserialize<ILogErrorUpdateResolve>(res);
   1575     }
   1576 
   1577     async media_image_create(opts: IMediaImageCreate): Promise<IMediaImageCreateResolve | IError<string>> {
   1578         await this.ensure_ready();
   1579         const res = await replica_db_media_image_create(this.serialize(opts));
   1580         return this.deserialize<IMediaImageCreateResolve>(res);
   1581     }
   1582 
   1583     async media_image_find_one(opts: IMediaImageFindOne): Promise<IMediaImageFindOneResolve | IError<string>> {
   1584         await this.ensure_ready();
   1585         const res = await replica_db_media_image_find_one(this.serialize(opts));
   1586         return this.deserialize<IMediaImageFindOneResolve>(res);
   1587     }
   1588 
   1589     async media_image_find_many(opts?: IMediaImageFindMany): Promise<IMediaImageFindManyResolve | IError<string>> {
   1590         await this.ensure_ready();
   1591         const res = await replica_db_media_image_find_many(this.serialize(opts ?? {}));
   1592         return this.deserialize<IMediaImageFindManyResolve>(res);
   1593     }
   1594 
   1595     async media_image_delete(opts: IMediaImageDelete): Promise<IMediaImageDeleteResolve | IError<string>> {
   1596         await this.ensure_ready();
   1597         const res = await replica_db_media_image_delete(this.serialize(opts));
   1598         return this.deserialize<IMediaImageDeleteResolve>(res);
   1599     }
   1600 
   1601     async media_image_update(opts: IMediaImageUpdate): Promise<IMediaImageUpdateResolve | IError<string>> {
   1602         await this.ensure_ready();
   1603         const res = await replica_db_media_image_update(this.serialize(opts));
   1604         return this.deserialize<IMediaImageUpdateResolve>(res);
   1605     }
   1606 
   1607     async nostr_profile_create(opts: INostrProfileCreate): Promise<INostrProfileCreateResolve | IError<string>> {
   1608         await this.ensure_ready();
   1609         const res = await replica_db_nostr_profile_create(this.serialize(opts));
   1610         return this.deserialize<INostrProfileCreateResolve>(res);
   1611     }
   1612 
   1613     async nostr_profile_find_one(opts: INostrProfileFindOne): Promise<INostrProfileFindOneResolve | IError<string>> {
   1614         await this.ensure_ready();
   1615         const res = await replica_db_nostr_profile_find_one(this.serialize(opts));
   1616         return this.deserialize<INostrProfileFindOneResolve>(res);
   1617     }
   1618 
   1619     async nostr_profile_find_many(opts?: INostrProfileFindMany): Promise<INostrProfileFindManyResolve | IError<string>> {
   1620         await this.ensure_ready();
   1621         const res = await replica_db_nostr_profile_find_many(this.serialize(opts ?? {}));
   1622         return this.deserialize<INostrProfileFindManyResolve>(res);
   1623     }
   1624 
   1625     async nostr_profile_delete(opts: INostrProfileDelete): Promise<INostrProfileDeleteResolve | IError<string>> {
   1626         await this.ensure_ready();
   1627         const res = await replica_db_nostr_profile_delete(this.serialize(opts));
   1628         return this.deserialize<INostrProfileDeleteResolve>(res);
   1629     }
   1630 
   1631     async nostr_profile_update(opts: INostrProfileUpdate): Promise<INostrProfileUpdateResolve | IError<string>> {
   1632         await this.ensure_ready();
   1633         const res = await replica_db_nostr_profile_update(this.serialize(opts));
   1634         return this.deserialize<INostrProfileUpdateResolve>(res);
   1635     }
   1636 
   1637     async nostr_event_state_create(opts: INostrEventStateCreate): Promise<INostrEventStateCreateResolve | IError<string>> {
   1638         await this.ensure_ready();
   1639         const res = await replica_db_nostr_event_state_create(this.serialize(opts));
   1640         return this.deserialize<INostrEventStateCreateResolve>(res);
   1641     }
   1642 
   1643     async nostr_event_state_find_one(opts: INostrEventStateFindOne): Promise<INostrEventStateFindOneResolve | IError<string>> {
   1644         await this.ensure_ready();
   1645         const res = await replica_db_nostr_event_state_find_one(this.serialize(opts));
   1646         return this.deserialize<INostrEventStateFindOneResolve>(res);
   1647     }
   1648 
   1649     async nostr_event_state_find_many(opts?: INostrEventStateFindMany): Promise<INostrEventStateFindManyResolve | IError<string>> {
   1650         await this.ensure_ready();
   1651         const res = await replica_db_nostr_event_state_find_many(this.serialize(opts ?? {}));
   1652         return this.deserialize<INostrEventStateFindManyResolve>(res);
   1653     }
   1654 
   1655     async nostr_event_state_delete(opts: INostrEventStateDelete): Promise<INostrEventStateDeleteResolve | IError<string>> {
   1656         await this.ensure_ready();
   1657         const res = await replica_db_nostr_event_state_delete(this.serialize(opts));
   1658         return this.deserialize<INostrEventStateDeleteResolve>(res);
   1659     }
   1660 
   1661     async nostr_event_state_update(opts: INostrEventStateUpdate): Promise<INostrEventStateUpdateResolve | IError<string>> {
   1662         await this.ensure_ready();
   1663         const res = await replica_db_nostr_event_state_update(this.serialize(opts));
   1664         return this.deserialize<INostrEventStateUpdateResolve>(res);
   1665     }
   1666 
   1667     async nostr_relay_create(opts: INostrRelayCreate): Promise<INostrRelayCreateResolve | IError<string>> {
   1668         await this.ensure_ready();
   1669         const res = await replica_db_nostr_relay_create(this.serialize(opts));
   1670         return this.deserialize<INostrRelayCreateResolve>(res);
   1671     }
   1672 
   1673     async nostr_relay_find_one(opts: INostrRelayFindOne): Promise<INostrRelayFindOneResolve | IError<string>> {
   1674         await this.ensure_ready();
   1675         const res = await replica_db_nostr_relay_find_one(this.serialize(opts));
   1676         return this.deserialize<INostrRelayFindOneResolve>(res);
   1677     }
   1678 
   1679     async nostr_relay_find_many(opts?: INostrRelayFindMany): Promise<INostrRelayFindManyResolve | IError<string>> {
   1680         await this.ensure_ready();
   1681         const res = await replica_db_nostr_relay_find_many(this.serialize(opts ?? {}));
   1682         return this.deserialize<INostrRelayFindManyResolve>(res);
   1683     }
   1684 
   1685     async nostr_relay_delete(opts: INostrRelayDelete): Promise<INostrRelayDeleteResolve | IError<string>> {
   1686         await this.ensure_ready();
   1687         const res = await replica_db_nostr_relay_delete(this.serialize(opts));
   1688         return this.deserialize<INostrRelayDeleteResolve>(res);
   1689     }
   1690 
   1691     async nostr_relay_update(opts: INostrRelayUpdate): Promise<INostrRelayUpdateResolve | IError<string>> {
   1692         await this.ensure_ready();
   1693         const res = await replica_db_nostr_relay_update(this.serialize(opts));
   1694         return this.deserialize<INostrRelayUpdateResolve>(res);
   1695     }
   1696 
   1697     async trade_product_create(opts: ITradeProductCreate): Promise<ITradeProductCreateResolve | IError<string>> {
   1698         await this.ensure_ready();
   1699         const res = await replica_db_trade_product_create(this.serialize(opts));
   1700         return this.deserialize<ITradeProductCreateResolve>(res);
   1701     }
   1702 
   1703     async trade_product_find_one(opts: ITradeProductFindOne): Promise<ITradeProductFindOneResolve | IError<string>> {
   1704         await this.ensure_ready();
   1705         const res = await replica_db_trade_product_find_one(this.serialize(opts));
   1706         return this.deserialize<ITradeProductFindOneResolve>(res);
   1707     }
   1708 
   1709     async trade_product_find_many(opts?: ITradeProductFindMany): Promise<ITradeProductFindManyResolve | IError<string>> {
   1710         await this.ensure_ready();
   1711         const res = await replica_db_trade_product_find_many(this.serialize(opts ?? {}));
   1712         return this.deserialize<ITradeProductFindManyResolve>(res);
   1713     }
   1714 
   1715     async trade_product_delete(opts: ITradeProductDelete): Promise<ITradeProductDeleteResolve | IError<string>> {
   1716         await this.ensure_ready();
   1717         const res = await replica_db_trade_product_delete(this.serialize(opts));
   1718         return this.deserialize<ITradeProductDeleteResolve>(res);
   1719     }
   1720 
   1721     async trade_product_update(opts: ITradeProductUpdate): Promise<ITradeProductUpdateResolve | IError<string>> {
   1722         await this.ensure_ready();
   1723         const res = await replica_db_trade_product_update(this.serialize(opts));
   1724         return this.deserialize<ITradeProductUpdateResolve>(res);
   1725     }
   1726 
   1727     async nostr_profile_relay_set(opts: INostrProfileRelayRelation): Promise<INostrProfileRelayResolve | IError<string>> {
   1728         await this.ensure_ready();
   1729         const res = await replica_db_nostr_profile_relay_set(this.serialize(opts));
   1730         return this.deserialize<INostrProfileRelayResolve>(res);
   1731     }
   1732 
   1733     async nostr_profile_relay_unset(opts: INostrProfileRelayRelation): Promise<INostrProfileRelayResolve | IError<string>> {
   1734         await this.ensure_ready();
   1735         const res = await replica_db_nostr_profile_relay_unset(this.serialize(opts));
   1736         return this.deserialize<INostrProfileRelayResolve>(res);
   1737     }
   1738 
   1739     async trade_product_location_set(opts: ITradeProductLocationRelation): Promise<ITradeProductLocationResolve | IError<string>> {
   1740         await this.ensure_ready();
   1741         const res = await replica_db_trade_product_location_set(this.serialize(opts));
   1742         return this.deserialize<ITradeProductLocationResolve>(res);
   1743     }
   1744 
   1745     async trade_product_location_unset(opts: ITradeProductLocationRelation): Promise<ITradeProductLocationResolve | IError<string>> {
   1746         await this.ensure_ready();
   1747         const res = await replica_db_trade_product_location_unset(this.serialize(opts));
   1748         return this.deserialize<ITradeProductLocationResolve>(res);
   1749     }
   1750 
   1751     async trade_product_media_set(opts: ITradeProductMediaRelation): Promise<ITradeProductMediaResolve | IError<string>> {
   1752         await this.ensure_ready();
   1753         const res = await replica_db_trade_product_media_set(this.serialize(opts));
   1754         return this.deserialize<ITradeProductMediaResolve>(res);
   1755     }
   1756 
   1757     async trade_product_media_unset(opts: ITradeProductMediaRelation): Promise<ITradeProductMediaResolve | IError<string>> {
   1758         await this.ensure_ready();
   1759         const res = await replica_db_trade_product_media_unset(this.serialize(opts));
   1760         return this.deserialize<ITradeProductMediaResolve>(res);
   1761     }
   1762 
   1763 }
   1764 
   1765 export const web_replica_database_create = async (config?: WebReplicaDatabaseConfig): Promise<WebReplicaDatabase> => {
   1766     const db = new WebReplicaDatabase(config);
   1767     await db.init();
   1768     return db;
   1769 };