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 };