web_lib

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

commit 851e7d76434a287154eda29b96087c50b51842df
parent 503c050534950bc7ea576d2b6ae45f8d11857be0
Author: triesap <tyson@radroots.org>
Date:   Thu, 11 Jun 2026 14:14:08 -0700

replica: align libraries with SDK bindings

Diffstat:
Mapps-lib-pwa/package.json | 2+-
Mapps-lib-pwa/src/lib/types/app.ts | 6+++---
Mapps-lib-pwa/src/lib/types/views/farms.ts | 2+-
Mapps-lib-pwa/src/lib/types/views/profile.ts | 2+-
Mapps-nostr/package.json | 8++++----
Mclient/package.json | 14+++++++-------
Mclient/src/error.ts | 2+-
Mclient/src/idb/config.ts | 8++++----
Rclient/src/tangle/bridge.ts -> client/src/replica/bridge.ts | 0
Aclient/src/replica/error.ts | 10++++++++++
Rclient/src/tangle/index.ts -> client/src/replica/index.ts | 0
Aclient/src/replica/types.ts | 263+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aclient/src/replica/web.ts | 1769+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dclient/src/tangle/error.ts | 10----------
Dclient/src/tangle/types.ts | 263-------------------------------------------------------------------------------
Dclient/src/tangle/web.ts | 1778-------------------------------------------------------------------------------
Mgeo/package.json | 2+-
Mgeo/src/gcs.ts | 2+-
Mlocales/src/messages/en/error.json | 4++--
Mnostr/package.json | 6+++---
Dnostr/src/domain/trade/lib.ts | 44--------------------------------------------
Dnostr/src/domain/trade/listing/accept/lib.ts | 63---------------------------------------------------------------
Dnostr/src/domain/trade/listing/conveyance/lib.ts | 64----------------------------------------------------------------
Dnostr/src/domain/trade/listing/fulfillment/lib.ts | 61-------------------------------------------------------------
Dnostr/src/domain/trade/listing/invoice/lib.ts | 71-----------------------------------------------------------------------
Dnostr/src/domain/trade/listing/order/lib.ts | 64----------------------------------------------------------------
Dnostr/src/domain/trade/listing/payment/lib.ts | 64----------------------------------------------------------------
Dnostr/src/domain/trade/listing/receipt/lib.ts | 64----------------------------------------------------------------
Dnostr/src/domain/trade/listing/tags.ts | 14--------------
Dnostr/src/domain/trade/tags.ts | 88-------------------------------------------------------------------------------
Mnostr/src/events/comment/parse.ts | 4++--
Mnostr/src/events/farm/parse.ts | 4++--
Mnostr/src/events/follow/parse.ts | 4++--
Mnostr/src/events/job/utils.ts | 29+----------------------------
Mnostr/src/events/list/parse.ts | 3+--
Mnostr/src/events/list_set/parse.ts | 3+--
Mnostr/src/events/listing/parse.ts | 104+++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mnostr/src/events/plot/parse.ts | 4++--
Mnostr/src/events/profile/parse.ts | 4++--
Mnostr/src/events/reaction/parse.ts | 4++--
Mnostr/src/index.ts | 10----------
41 files changed, 2150 insertions(+), 2771 deletions(-)

diff --git a/apps-lib-pwa/package.json b/apps-lib-pwa/package.json @@ -82,7 +82,7 @@ "@radroots/events-bindings": "workspace:*", "@radroots/client": "workspace:*", "@radroots/geo": "workspace:*", - "@radroots/tangle-db-schema-bindings": "workspace:*", + "@radroots/replica-db-schema-bindings": "workspace:*", "@radroots/themes": "workspace:*", "@radroots/utils": "workspace:*", "@sveltekit-i18n/base": "^1.3.7", diff --git a/apps-lib-pwa/src/lib/types/app.ts b/apps-lib-pwa/src/lib/types/app.ts @@ -1,4 +1,4 @@ -import type { TangleDatabaseJsonExport } from "@radroots/client/tangle"; +import type { ReplicaDatabaseJsonExport } from "@radroots/client/replica"; import type { IdbClientConfig } from "@radroots/utils"; export type AppConfigRole = `farm` | `business` | `individual` @@ -32,7 +32,7 @@ export type LabelFieldKind = `link` | `on` | `shade`; export type BackupVersions = { app: string; - tangle_db: string; + replica_db: string; backup_format: string; }; @@ -53,7 +53,7 @@ export type ExportedAppState = { }; database: { store_key: string; - backup: TangleDatabaseJsonExport; + backup: ReplicaDatabaseJsonExport; }; }; diff --git a/apps-lib-pwa/src/lib/types/views/farms.ts b/apps-lib-pwa/src/lib/types/views/farms.ts @@ -1,4 +1,4 @@ -import type { Farm } from "@radroots/tangle-db-schema-bindings"; +import type { Farm } from "@radroots/replica-db-schema-bindings"; import type { GeocoderReverseResult, GeolocationPoint, LocationBasis } from "@radroots/geo"; export type FarmExtended = { diff --git a/apps-lib-pwa/src/lib/types/views/profile.ts b/apps-lib-pwa/src/lib/types/views/profile.ts @@ -1,4 +1,4 @@ -import type { NostrProfile } from "@radroots/tangle-db-schema-bindings"; +import type { NostrProfile } from "@radroots/replica-db-schema-bindings"; export type IViewProfileData = { profile: NostrProfile; diff --git a/apps-nostr/package.json b/apps-nostr/package.json @@ -38,9 +38,9 @@ }, "dependencies": { "@radroots/nostr": "workspace:*", - "@welshman/app": "workspace:*", - "@welshman/net": "workspace:*", - "@welshman/signer": "workspace:*", - "@welshman/store": "workspace:*" + "@welshman/app": "0.8.4", + "@welshman/net": "0.8.4", + "@welshman/signer": "0.8.4", + "@welshman/store": "0.8.4" } } diff --git a/client/package.json b/client/package.json @@ -70,10 +70,10 @@ "import": "./dist/esm/sql/constants.js", "require": "./dist/cjs/sql/constants.js" }, - "./tangle": { - "types": "./dist/types/tangle/index.d.ts", - "import": "./dist/esm/tangle/index.js", - "require": "./dist/cjs/tangle/index.js" + "./replica": { + "types": "./dist/types/replica/index.d.ts", + "import": "./dist/esm/replica/index.js", + "require": "./dist/cjs/replica/index.js" } }, "scripts": { @@ -88,9 +88,9 @@ "dependencies": { "@radroots/geo": "workspace:*", "@radroots/http": "workspace:*", - "@radroots/tangle-db-schema-bindings": "workspace:*", - "@radroots/tangle-db-wasm": "workspace:*", - "@radroots/tangle-events-wasm": "workspace:*", + "@radroots/replica-db-schema-bindings": "workspace:*", + "@radroots/replica-db-wasm": "workspace:*", + "@radroots/replica-sync-wasm": "workspace:*", "@radroots/types-bindings": "workspace:*", "@radroots/utils": "workspace:*", "@radroots/nostr": "workspace:*", diff --git a/client/src/error.ts b/client/src/error.ts @@ -8,4 +8,4 @@ export * from "./keystore/error.js"; export * from "./notifications/error.js"; export * from "./radroots/error.js"; export * from "./sql/error.js"; -export * from "./tangle/error.js"; +export * from "./replica/error.js"; diff --git a/client/src/idb/config.ts b/client/src/idb/config.ts @@ -8,7 +8,7 @@ export const IDB_STORE_KEYSTORE_NOSTR = "radroots.security.keystore.nostr"; export const IDB_STORE_CRYPTO_REGISTRY = "radroots.security.crypto.registry"; export const IDB_STORE_CIPHER_AES_GCM = "radroots.security.cipher.aes-gcm"; export const IDB_STORE_CIPHER_SQL = "radroots.security.cipher.sql"; -export const IDB_STORE_TANGLE = "radroots.storage.tangle.sql"; +export const IDB_STORE_REPLICA = "radroots.storage.replica.sql"; export const IDB_STORE_CIPHER_SUFFIX = ".cipher"; export const IDB_CONFIG_DATASTORE: IdbClientConfig = { @@ -41,9 +41,9 @@ export const IDB_CONFIG_CIPHER_SQL: IdbClientConfig = { store: IDB_STORE_CIPHER_SQL }; -export const IDB_CONFIG_TANGLE: IdbClientConfig = { +export const IDB_CONFIG_REPLICA: IdbClientConfig = { database: RADROOTS_IDB_DATABASE, - store: IDB_STORE_TANGLE + store: IDB_STORE_REPLICA }; export const RADROOTS_IDB_CONFIGS: IdbClientConfig[] = [ @@ -53,7 +53,7 @@ export const RADROOTS_IDB_CONFIGS: IdbClientConfig[] = [ IDB_CONFIG_CRYPTO_REGISTRY, IDB_CONFIG_CIPHER_AES_GCM, IDB_CONFIG_CIPHER_SQL, - IDB_CONFIG_TANGLE + IDB_CONFIG_REPLICA ]; export const RADROOTS_IDB_STORES: string[] = [ diff --git a/client/src/tangle/bridge.ts b/client/src/replica/bridge.ts diff --git a/client/src/replica/error.ts b/client/src/replica/error.ts @@ -0,0 +1,10 @@ +export const cl_replica_error = { + init_failure: "error.client.replica.init_failure", + parse_failure: "error.client.replica.parse_failure", + invalid_response: "error.client.replica.invalid_response", + runtime_unavailable: "error.client.replica.runtime_unavailable", + crypto_unavailable: "error.client.replica.crypto_unavailable" +} as const; + +export type ClientReplicaError = keyof typeof cl_replica_error; +export type ClientReplicaErrorMessage = (typeof cl_replica_error)[ClientReplicaError]; diff --git a/client/src/tangle/index.ts b/client/src/replica/index.ts diff --git a/client/src/replica/types.ts b/client/src/replica/types.ts @@ -0,0 +1,263 @@ +import type { + IFarmCreate, + IFarmCreateResolve, + IFarmDelete, + IFarmDeleteResolve, + IFarmFindMany, + IFarmFindManyResolve, + IFarmFindOne, + IFarmFindOneResolve, + IFarmUpdate, + IFarmUpdateResolve, + IFarmGcsLocationCreate, + IFarmGcsLocationCreateResolve, + IFarmGcsLocationDelete, + IFarmGcsLocationDeleteResolve, + IFarmGcsLocationFindMany, + IFarmGcsLocationFindManyResolve, + IFarmGcsLocationFindOne, + IFarmGcsLocationFindOneResolve, + IFarmGcsLocationUpdate, + IFarmGcsLocationUpdateResolve, + IFarmMemberClaimCreate, + IFarmMemberClaimCreateResolve, + IFarmMemberClaimDelete, + IFarmMemberClaimDeleteResolve, + IFarmMemberClaimFindMany, + IFarmMemberClaimFindManyResolve, + IFarmMemberClaimFindOne, + IFarmMemberClaimFindOneResolve, + IFarmMemberClaimUpdate, + IFarmMemberClaimUpdateResolve, + IFarmMemberCreate, + IFarmMemberCreateResolve, + IFarmMemberDelete, + IFarmMemberDeleteResolve, + IFarmMemberFindMany, + IFarmMemberFindManyResolve, + IFarmMemberFindOne, + IFarmMemberFindOneResolve, + IFarmMemberUpdate, + IFarmMemberUpdateResolve, + IFarmTagCreate, + IFarmTagCreateResolve, + IFarmTagDelete, + IFarmTagDeleteResolve, + IFarmTagFindMany, + IFarmTagFindManyResolve, + IFarmTagFindOne, + IFarmTagFindOneResolve, + IFarmTagUpdate, + IFarmTagUpdateResolve, + IGcsLocationCreate, + IGcsLocationCreateResolve, + IGcsLocationDelete, + IGcsLocationDeleteResolve, + IGcsLocationFindMany, + IGcsLocationFindManyResolve, + IGcsLocationFindOne, + IGcsLocationFindOneResolve, + IGcsLocationUpdate, + IGcsLocationUpdateResolve, + ILogErrorCreate, + ILogErrorCreateResolve, + ILogErrorDelete, + ILogErrorDeleteResolve, + ILogErrorFindMany, + ILogErrorFindManyResolve, + ILogErrorFindOne, + ILogErrorFindOneResolve, + ILogErrorUpdate, + ILogErrorUpdateResolve, + IMediaImageCreate, + IMediaImageCreateResolve, + IMediaImageDelete, + IMediaImageDeleteResolve, + IMediaImageFindMany, + IMediaImageFindManyResolve, + IMediaImageFindOne, + IMediaImageFindOneResolve, + IMediaImageUpdate, + IMediaImageUpdateResolve, + INostrEventStateCreate, + INostrEventStateCreateResolve, + INostrEventStateDelete, + INostrEventStateDeleteResolve, + INostrEventStateFindMany, + INostrEventStateFindManyResolve, + INostrEventStateFindOne, + INostrEventStateFindOneResolve, + INostrEventStateUpdate, + INostrEventStateUpdateResolve, + INostrProfileCreate, + INostrProfileCreateResolve, + INostrProfileDelete, + INostrProfileDeleteResolve, + INostrProfileFindMany, + INostrProfileFindManyResolve, + INostrProfileFindOne, + INostrProfileFindOneResolve, + INostrProfileUpdate, + INostrProfileUpdateResolve, + INostrRelayCreate, + INostrRelayCreateResolve, + INostrRelayDelete, + INostrRelayDeleteResolve, + INostrRelayFindMany, + INostrRelayFindManyResolve, + INostrRelayFindOne, + INostrRelayFindOneResolve, + INostrRelayUpdate, + INostrRelayUpdateResolve, + IPlotCreate, + IPlotCreateResolve, + IPlotDelete, + IPlotDeleteResolve, + IPlotFindMany, + IPlotFindManyResolve, + IPlotFindOne, + IPlotFindOneResolve, + IPlotGcsLocationCreate, + IPlotGcsLocationCreateResolve, + IPlotGcsLocationDelete, + IPlotGcsLocationDeleteResolve, + IPlotGcsLocationFindMany, + IPlotGcsLocationFindManyResolve, + IPlotGcsLocationFindOne, + IPlotGcsLocationFindOneResolve, + IPlotGcsLocationUpdate, + IPlotGcsLocationUpdateResolve, + IPlotTagCreate, + IPlotTagCreateResolve, + IPlotTagDelete, + IPlotTagDeleteResolve, + IPlotTagFindMany, + IPlotTagFindManyResolve, + IPlotTagFindOne, + IPlotTagFindOneResolve, + IPlotTagUpdate, + IPlotTagUpdateResolve, + IPlotUpdate, + IPlotUpdateResolve, + ITradeProductCreate, + ITradeProductCreateResolve, + ITradeProductDelete, + ITradeProductDeleteResolve, + ITradeProductFindMany, + ITradeProductFindManyResolve, + ITradeProductFindOne, + ITradeProductFindOneResolve, + ITradeProductUpdate, + ITradeProductUpdateResolve, + INostrProfileRelayRelation, + INostrProfileRelayResolve, + ITradeProductLocationRelation, + ITradeProductLocationResolve, + ITradeProductMediaRelation, + ITradeProductMediaResolve +} from "@radroots/replica-db-schema-bindings"; +import { type SqlJsMigrationState } from "../sql/types.js"; +import type { IError } from "@radroots/types-bindings"; +import type { + ReplicaDatabaseExportOptions, + ReplicaDatabaseJsonExport, + ReplicaNostrSyncOptions, + ReplicaNostrSyncSummary +} from "./web.js"; + +export interface IClientReplicaDatabase { + init(): Promise<void>; + close(): Promise<void>; + migration_state(): Promise<SqlJsMigrationState | IError<string>>; + reset(): Promise<SqlJsMigrationState | IError<string>>; + reinit(): Promise<SqlJsMigrationState | IError<string>>; + get_store_key(): string; + export_json(): Promise<ReplicaDatabaseJsonExport | IError<string>>; + import_json(backup: ReplicaDatabaseJsonExport): Promise<void | IError<string>>; + export_database(opts: ReplicaDatabaseExportOptions): Promise<void | IError<string>>; + nostr_sync_all(opts: ReplicaNostrSyncOptions): Promise<ReplicaNostrSyncSummary | IError<string>>; + farm_create(opts: IFarmCreate): Promise<IFarmCreateResolve | IError<string>>; + farm_find_one(opts: IFarmFindOne): Promise<IFarmFindOneResolve | IError<string>>; + farm_find_many(opts?: IFarmFindMany): Promise<IFarmFindManyResolve | IError<string>>; + farm_delete(opts: IFarmDelete): Promise<IFarmDeleteResolve | IError<string>>; + farm_update(opts: IFarmUpdate): Promise<IFarmUpdateResolve | IError<string>>; + plot_create(opts: IPlotCreate): Promise<IPlotCreateResolve | IError<string>>; + plot_find_one(opts: IPlotFindOne): Promise<IPlotFindOneResolve | IError<string>>; + plot_find_many(opts?: IPlotFindMany): Promise<IPlotFindManyResolve | IError<string>>; + plot_delete(opts: IPlotDelete): Promise<IPlotDeleteResolve | IError<string>>; + plot_update(opts: IPlotUpdate): Promise<IPlotUpdateResolve | IError<string>>; + gcs_location_create(opts: IGcsLocationCreate): Promise<IGcsLocationCreateResolve | IError<string>>; + gcs_location_find_one(opts: IGcsLocationFindOne): Promise<IGcsLocationFindOneResolve | IError<string>>; + gcs_location_find_many(opts?: IGcsLocationFindMany): Promise<IGcsLocationFindManyResolve | IError<string>>; + gcs_location_delete(opts: IGcsLocationDelete): Promise<IGcsLocationDeleteResolve | IError<string>>; + gcs_location_update(opts: IGcsLocationUpdate): Promise<IGcsLocationUpdateResolve | IError<string>>; + farm_gcs_location_create(opts: IFarmGcsLocationCreate): Promise<IFarmGcsLocationCreateResolve | IError<string>>; + farm_gcs_location_find_one(opts: IFarmGcsLocationFindOne): Promise<IFarmGcsLocationFindOneResolve | IError<string>>; + farm_gcs_location_find_many(opts?: IFarmGcsLocationFindMany): Promise<IFarmGcsLocationFindManyResolve | IError<string>>; + farm_gcs_location_delete(opts: IFarmGcsLocationDelete): Promise<IFarmGcsLocationDeleteResolve | IError<string>>; + farm_gcs_location_update(opts: IFarmGcsLocationUpdate): Promise<IFarmGcsLocationUpdateResolve | IError<string>>; + plot_gcs_location_create(opts: IPlotGcsLocationCreate): Promise<IPlotGcsLocationCreateResolve | IError<string>>; + plot_gcs_location_find_one(opts: IPlotGcsLocationFindOne): Promise<IPlotGcsLocationFindOneResolve | IError<string>>; + plot_gcs_location_find_many(opts?: IPlotGcsLocationFindMany): Promise<IPlotGcsLocationFindManyResolve | IError<string>>; + plot_gcs_location_delete(opts: IPlotGcsLocationDelete): Promise<IPlotGcsLocationDeleteResolve | IError<string>>; + plot_gcs_location_update(opts: IPlotGcsLocationUpdate): Promise<IPlotGcsLocationUpdateResolve | IError<string>>; + farm_tag_create(opts: IFarmTagCreate): Promise<IFarmTagCreateResolve | IError<string>>; + farm_tag_find_one(opts: IFarmTagFindOne): Promise<IFarmTagFindOneResolve | IError<string>>; + farm_tag_find_many(opts?: IFarmTagFindMany): Promise<IFarmTagFindManyResolve | IError<string>>; + farm_tag_delete(opts: IFarmTagDelete): Promise<IFarmTagDeleteResolve | IError<string>>; + farm_tag_update(opts: IFarmTagUpdate): Promise<IFarmTagUpdateResolve | IError<string>>; + plot_tag_create(opts: IPlotTagCreate): Promise<IPlotTagCreateResolve | IError<string>>; + plot_tag_find_one(opts: IPlotTagFindOne): Promise<IPlotTagFindOneResolve | IError<string>>; + plot_tag_find_many(opts?: IPlotTagFindMany): Promise<IPlotTagFindManyResolve | IError<string>>; + plot_tag_delete(opts: IPlotTagDelete): Promise<IPlotTagDeleteResolve | IError<string>>; + plot_tag_update(opts: IPlotTagUpdate): Promise<IPlotTagUpdateResolve | IError<string>>; + farm_member_create(opts: IFarmMemberCreate): Promise<IFarmMemberCreateResolve | IError<string>>; + farm_member_find_one(opts: IFarmMemberFindOne): Promise<IFarmMemberFindOneResolve | IError<string>>; + farm_member_find_many(opts?: IFarmMemberFindMany): Promise<IFarmMemberFindManyResolve | IError<string>>; + farm_member_delete(opts: IFarmMemberDelete): Promise<IFarmMemberDeleteResolve | IError<string>>; + farm_member_update(opts: IFarmMemberUpdate): Promise<IFarmMemberUpdateResolve | IError<string>>; + farm_member_claim_create(opts: IFarmMemberClaimCreate): Promise<IFarmMemberClaimCreateResolve | IError<string>>; + farm_member_claim_find_one(opts: IFarmMemberClaimFindOne): Promise<IFarmMemberClaimFindOneResolve | IError<string>>; + farm_member_claim_find_many(opts?: IFarmMemberClaimFindMany): Promise<IFarmMemberClaimFindManyResolve | IError<string>>; + farm_member_claim_delete(opts: IFarmMemberClaimDelete): Promise<IFarmMemberClaimDeleteResolve | IError<string>>; + farm_member_claim_update(opts: IFarmMemberClaimUpdate): Promise<IFarmMemberClaimUpdateResolve | IError<string>>; + nostr_event_state_create(opts: INostrEventStateCreate): Promise<INostrEventStateCreateResolve | IError<string>>; + nostr_event_state_find_one(opts: INostrEventStateFindOne): Promise<INostrEventStateFindOneResolve | IError<string>>; + nostr_event_state_find_many(opts?: INostrEventStateFindMany): Promise<INostrEventStateFindManyResolve | IError<string>>; + nostr_event_state_delete(opts: INostrEventStateDelete): Promise<INostrEventStateDeleteResolve | IError<string>>; + nostr_event_state_update(opts: INostrEventStateUpdate): Promise<INostrEventStateUpdateResolve | IError<string>>; + log_error_create(opts: ILogErrorCreate): Promise<ILogErrorCreateResolve | IError<string>>; + log_error_find_one(opts: ILogErrorFindOne): Promise<ILogErrorFindOneResolve | IError<string>>; + log_error_find_many(opts?: ILogErrorFindMany): Promise<ILogErrorFindManyResolve | IError<string>>; + log_error_delete(opts: ILogErrorDelete): Promise<ILogErrorDeleteResolve | IError<string>>; + log_error_update(opts: ILogErrorUpdate): Promise<ILogErrorUpdateResolve | IError<string>>; + media_image_create(opts: IMediaImageCreate): Promise<IMediaImageCreateResolve | IError<string>>; + media_image_find_one(opts: IMediaImageFindOne): Promise<IMediaImageFindOneResolve | IError<string>>; + media_image_find_many(opts?: IMediaImageFindMany): Promise<IMediaImageFindManyResolve | IError<string>>; + media_image_delete(opts: IMediaImageDelete): Promise<IMediaImageDeleteResolve | IError<string>>; + media_image_update(opts: IMediaImageUpdate): Promise<IMediaImageUpdateResolve | IError<string>>; + nostr_profile_create(opts: INostrProfileCreate): Promise<INostrProfileCreateResolve | IError<string>>; + nostr_profile_find_one(opts: INostrProfileFindOne): Promise<INostrProfileFindOneResolve | IError<string>>; + nostr_profile_find_many(opts?: INostrProfileFindMany): Promise<INostrProfileFindManyResolve | IError<string>>; + nostr_profile_delete(opts: INostrProfileDelete): Promise<INostrProfileDeleteResolve | IError<string>>; + nostr_profile_update(opts: INostrProfileUpdate): Promise<INostrProfileUpdateResolve | IError<string>>; + nostr_relay_create(opts: INostrRelayCreate): Promise<INostrRelayCreateResolve | IError<string>>; + nostr_relay_find_one(opts: INostrRelayFindOne): Promise<INostrRelayFindOneResolve | IError<string>>; + nostr_relay_find_many(opts?: INostrRelayFindMany): Promise<INostrRelayFindManyResolve | IError<string>>; + nostr_relay_delete(opts: INostrRelayDelete): Promise<INostrRelayDeleteResolve | IError<string>>; + nostr_relay_update(opts: INostrRelayUpdate): Promise<INostrRelayUpdateResolve | IError<string>>; + trade_product_create(opts: ITradeProductCreate): Promise<ITradeProductCreateResolve | IError<string>>; + trade_product_find_one(opts: ITradeProductFindOne): Promise<ITradeProductFindOneResolve | IError<string>>; + trade_product_find_many(opts?: ITradeProductFindMany): Promise<ITradeProductFindManyResolve | IError<string>>; + trade_product_delete(opts: ITradeProductDelete): Promise<ITradeProductDeleteResolve | IError<string>>; + trade_product_update(opts: ITradeProductUpdate): Promise<ITradeProductUpdateResolve | IError<string>>; + nostr_profile_relay_set(opts: INostrProfileRelayRelation): Promise<INostrProfileRelayResolve | IError<string>>; + nostr_profile_relay_unset(opts: INostrProfileRelayRelation): Promise<INostrProfileRelayResolve | IError<string>>; + trade_product_location_set(opts: ITradeProductLocationRelation): Promise<ITradeProductLocationResolve | IError<string>>; + trade_product_location_unset(opts: ITradeProductLocationRelation): Promise<ITradeProductLocationResolve | IError<string>>; + trade_product_media_set(opts: ITradeProductMediaRelation): Promise<ITradeProductMediaResolve | IError<string>>; + trade_product_media_unset(opts: ITradeProductMediaRelation): Promise<ITradeProductMediaResolve | IError<string>>; +} + +export interface IWebReplicaDatabase extends IClientReplicaDatabase { +} diff --git a/client/src/replica/web.ts b/client/src/replica/web.ts @@ -0,0 +1,1769 @@ +import type { + IFarmCreate, + IFarmCreateResolve, + IFarmDelete, + IFarmDeleteResolve, + IFarmFindMany, + IFarmFindManyResolve, + IFarmFindOne, + IFarmFindOneResolve, + IFarmUpdate, + IFarmUpdateResolve, + IFarmGcsLocationCreate, + IFarmGcsLocationCreateResolve, + IFarmGcsLocationDelete, + IFarmGcsLocationDeleteResolve, + IFarmGcsLocationFindMany, + IFarmGcsLocationFindManyResolve, + IFarmGcsLocationFindOne, + IFarmGcsLocationFindOneResolve, + IFarmGcsLocationUpdate, + IFarmGcsLocationUpdateResolve, + IFarmMemberClaimCreate, + IFarmMemberClaimCreateResolve, + IFarmMemberClaimDelete, + IFarmMemberClaimDeleteResolve, + IFarmMemberClaimFindMany, + IFarmMemberClaimFindManyResolve, + IFarmMemberClaimFindOne, + IFarmMemberClaimFindOneResolve, + IFarmMemberClaimUpdate, + IFarmMemberClaimUpdateResolve, + IFarmMemberCreate, + IFarmMemberCreateResolve, + IFarmMemberDelete, + IFarmMemberDeleteResolve, + IFarmMemberFindMany, + IFarmMemberFindManyResolve, + IFarmMemberFindOne, + IFarmMemberFindOneResolve, + IFarmMemberUpdate, + IFarmMemberUpdateResolve, + IFarmTagCreate, + IFarmTagCreateResolve, + IFarmTagDelete, + IFarmTagDeleteResolve, + IFarmTagFindMany, + IFarmTagFindManyResolve, + IFarmTagFindOne, + IFarmTagFindOneResolve, + IFarmTagUpdate, + IFarmTagUpdateResolve, + IGcsLocationCreate, + IGcsLocationCreateResolve, + IGcsLocationDelete, + IGcsLocationDeleteResolve, + IGcsLocationFindMany, + IGcsLocationFindManyResolve, + IGcsLocationFindOne, + IGcsLocationFindOneResolve, + IGcsLocationUpdate, + IGcsLocationUpdateResolve, + ILogErrorCreate, + ILogErrorCreateResolve, + ILogErrorDelete, + ILogErrorDeleteResolve, + ILogErrorFindMany, + ILogErrorFindManyResolve, + ILogErrorFindOne, + ILogErrorFindOneResolve, + ILogErrorUpdate, + ILogErrorUpdateResolve, + IMediaImageCreate, + IMediaImageCreateResolve, + IMediaImageDelete, + IMediaImageDeleteResolve, + IMediaImageFindMany, + IMediaImageFindManyResolve, + IMediaImageFindOne, + IMediaImageFindOneResolve, + IMediaImageUpdate, + IMediaImageUpdateResolve, + INostrEventStateCreate, + INostrEventStateCreateResolve, + INostrEventStateDelete, + INostrEventStateDeleteResolve, + INostrEventStateFindMany, + INostrEventStateFindManyResolve, + INostrEventStateFindOne, + INostrEventStateFindOneResolve, + INostrEventStateUpdate, + INostrEventStateUpdateResolve, + INostrProfileCreate, + INostrProfileCreateResolve, + INostrProfileDelete, + INostrProfileDeleteResolve, + INostrProfileFindMany, + INostrProfileFindManyResolve, + INostrProfileFindOne, + INostrProfileFindOneResolve, + INostrProfileUpdate, + INostrProfileUpdateResolve, + INostrRelayCreate, + INostrRelayCreateResolve, + INostrRelayDelete, + INostrRelayDeleteResolve, + INostrRelayFindMany, + INostrRelayFindManyResolve, + INostrRelayFindOne, + INostrRelayFindOneResolve, + INostrRelayUpdate, + INostrRelayUpdateResolve, + IPlotCreate, + IPlotCreateResolve, + IPlotDelete, + IPlotDeleteResolve, + IPlotFindMany, + IPlotFindManyResolve, + IPlotFindOne, + IPlotFindOneResolve, + IPlotGcsLocationCreate, + IPlotGcsLocationCreateResolve, + IPlotGcsLocationDelete, + IPlotGcsLocationDeleteResolve, + IPlotGcsLocationFindMany, + IPlotGcsLocationFindManyResolve, + IPlotGcsLocationFindOne, + IPlotGcsLocationFindOneResolve, + IPlotGcsLocationUpdate, + IPlotGcsLocationUpdateResolve, + IPlotTagCreate, + IPlotTagCreateResolve, + IPlotTagDelete, + IPlotTagDeleteResolve, + IPlotTagFindMany, + IPlotTagFindManyResolve, + IPlotTagFindOne, + IPlotTagFindOneResolve, + IPlotTagUpdate, + IPlotTagUpdateResolve, + IPlotUpdate, + IPlotUpdateResolve, + ITradeProductCreate, + ITradeProductCreateResolve, + ITradeProductDelete, + ITradeProductDeleteResolve, + ITradeProductFindMany, + ITradeProductFindManyResolve, + ITradeProductFindOne, + ITradeProductFindOneResolve, + ITradeProductUpdate, + ITradeProductUpdateResolve, + INostrProfileRelayRelation, + INostrProfileRelayResolve, + ITradeProductLocationRelation, + ITradeProductLocationResolve, + ITradeProductMediaRelation, + ITradeProductMediaResolve +} from "@radroots/replica-db-schema-bindings"; +import init_wasm, { + replica_db_farm_create, + replica_db_farm_delete, + replica_db_farm_find_many, + replica_db_farm_find_one, + replica_db_farm_update, + replica_db_plot_create, + replica_db_plot_delete, + replica_db_plot_find_many, + replica_db_plot_find_one, + replica_db_plot_update, + replica_db_gcs_location_create, + replica_db_gcs_location_delete, + replica_db_gcs_location_find_many, + replica_db_gcs_location_find_one, + replica_db_gcs_location_update, + replica_db_farm_gcs_location_create, + replica_db_farm_gcs_location_delete, + replica_db_farm_gcs_location_find_many, + replica_db_farm_gcs_location_find_one, + replica_db_farm_gcs_location_update, + replica_db_plot_gcs_location_create, + replica_db_plot_gcs_location_delete, + replica_db_plot_gcs_location_find_many, + replica_db_plot_gcs_location_find_one, + replica_db_plot_gcs_location_update, + replica_db_farm_tag_create, + replica_db_farm_tag_delete, + replica_db_farm_tag_find_many, + replica_db_farm_tag_find_one, + replica_db_farm_tag_update, + replica_db_plot_tag_create, + replica_db_plot_tag_delete, + replica_db_plot_tag_find_many, + replica_db_plot_tag_find_one, + replica_db_plot_tag_update, + replica_db_farm_member_create, + replica_db_farm_member_delete, + replica_db_farm_member_find_many, + replica_db_farm_member_find_one, + replica_db_farm_member_update, + replica_db_farm_member_claim_create, + replica_db_farm_member_claim_delete, + replica_db_farm_member_claim_find_many, + replica_db_farm_member_claim_find_one, + replica_db_farm_member_claim_update, + replica_db_log_error_create, + replica_db_log_error_delete, + replica_db_log_error_find_many, + replica_db_log_error_find_one, + replica_db_log_error_update, + replica_db_media_image_create, + replica_db_media_image_delete, + replica_db_media_image_find_many, + replica_db_media_image_find_one, + replica_db_media_image_update, + replica_db_nostr_event_state_create, + replica_db_nostr_event_state_delete, + replica_db_nostr_event_state_find_many, + replica_db_nostr_event_state_find_one, + replica_db_nostr_event_state_update, + replica_db_nostr_profile_create, + replica_db_nostr_profile_delete, + replica_db_nostr_profile_find_many, + replica_db_nostr_profile_find_one, + replica_db_nostr_profile_update, + replica_db_nostr_relay_create, + replica_db_nostr_relay_delete, + replica_db_nostr_relay_find_many, + replica_db_nostr_relay_find_one, + replica_db_nostr_relay_update, + replica_db_trade_product_create, + replica_db_trade_product_delete, + replica_db_trade_product_find_many, + replica_db_trade_product_find_one, + replica_db_trade_product_update, + replica_db_nostr_profile_relay_set, + replica_db_nostr_profile_relay_unset, + replica_db_trade_product_location_set, + replica_db_trade_product_location_unset, + replica_db_trade_product_media_set, + replica_db_trade_product_media_unset, + replica_db_reset_database, + replica_db_run_migrations, + replica_db_export_begin, + replica_db_export_finish, + replica_db_export_json, + replica_db_import_json +} from "@radroots/replica-db-wasm"; +import init_replica_sync_wasm, { + replica_sync_ingest_event, + replica_sync_sync_all +} from "@radroots/replica-sync-wasm"; +import { + nostr_context_create, + nostr_event_sign, + nostr_public_key_from_secret, + nostr_publish, + nostr_relays_clear, + nostr_relays_open, + type NostrContext +} from "@radroots/nostr"; +import type { IError } from "@radroots/types-bindings"; +import { err_msg, handle_err, type IdbClientConfig } from "@radroots/utils"; +import { IDB_CONFIG_REPLICA } from "../idb/config.js"; +import type { SqlJsMigrationRow, SqlJsMigrationState, WebSqlEngineConfig } from "../sql/types.js"; +import { WebSqlEngine } from "../sql/web.js"; +import { radroots_sql_install_bridges } from "./bridge.js"; +import { cl_replica_error } from "./error.js"; +import type { IWebReplicaDatabase } from "./types.js"; + +export type ReplicaDatabaseSchemaEntry = { + object_type: string; + name: string; + table_name?: string; + sql?: string; +}; + +export type ReplicaDatabaseMigrationEntry = { + name: string; + up_sql: string; + down_sql: string; +}; + +export type ReplicaDatabaseJsonExport = { + format_version: string; + replica_db_version: string; + schema: ReplicaDatabaseSchemaEntry[]; + data: { + name: string; + rows: Record<string, unknown>[]; + }[]; + migrations: ReplicaDatabaseMigrationEntry[]; +}; + +export type ReplicaDatabaseExportManifestRs = { + export_version: string; + replica_db_version: string; + backup_format_version: string; + schema_hash: string; + schema: ReplicaDatabaseSchemaEntry[]; + migrations: ReplicaDatabaseMigrationEntry[]; + table_counts: { + name: string; + row_count: number; + }[]; +}; + +export type NostrEventEnvelope = { + id: string; + pubkey: string; + created_at: number; + kind: number; + tags: string[][]; + content: string; + sig: string; +}; + +export type ReplicaDatabaseExportManifestTs = { + app_name: string; + app_version: string; + exported_at: string; + db_sha256: string; + db_size_bytes: number; + store_key: string; + nostr_event?: NostrEventEnvelope; +}; + +export type ReplicaDatabaseExportManifest = { + rust: ReplicaDatabaseExportManifestRs; + client: ReplicaDatabaseExportManifestTs; +}; + +export type ReplicaDatabaseExportSnapshot = { + manifest_rs: ReplicaDatabaseExportManifestRs; + db_bytes: Uint8Array; +}; + +export type ReplicaDatabaseExportSignRequest = { + db_sha256: string; + manifest: ReplicaDatabaseExportManifest; +}; + +export type ReplicaDatabaseExportSigner = (opts: ReplicaDatabaseExportSignRequest) => Promise<NostrEventEnvelope | null>; + +export type ReplicaDatabaseExportOptions = { + app_name: string; + app_version: string; + store_key?: string; + signer?: ReplicaDatabaseExportSigner; +}; + +export type ReplicaNostrSyncSigner = { + secret_key: string; +}; + +export type ReplicaNostrEventDraft = { + kind: number; + author: string; + content: string; + tags: string[][]; +}; + +export type ReplicaNostrSyncBundle = { + version: number; + events: ReplicaNostrEventDraft[]; +}; + +export type ReplicaNostrSyncOptions = { + relays: string[]; + signers: ReplicaNostrSyncSigner[]; + publish_timeout_ms?: number; + context?: NostrContext; +}; + +export type ReplicaNostrSyncSummary = { + events_total: number; + events_published: number; + events_failed: number; + events_skipped: number; + missing_signers: string[]; +}; + +export type WebReplicaDatabaseConfig = { + store_key?: string; + idb_config?: IdbClientConfig; + cipher_config?: IdbClientConfig | null; + sql_wasm_path?: string; +}; + +const is_record = (value: unknown): value is Record<string, unknown> => + typeof value === "object" && value !== null && !Array.isArray(value); + +const is_sql_migration_row = (value: unknown): value is SqlJsMigrationRow => { + if (!is_record(value)) return false; + return typeof value.id === "number" + && Number.isFinite(value.id) + && typeof value.name === "string" + && typeof value.applied_at === "string"; +}; + +const is_sql_migration_row_list = (value: unknown): value is SqlJsMigrationRow[] => + Array.isArray(value) && value.every(is_sql_migration_row); + +const is_schema_entry = (value: unknown): value is ReplicaDatabaseSchemaEntry => { + if (!is_record(value)) return false; + if (typeof value.object_type !== "string") return false; + if (typeof value.name !== "string") return false; + if ("table_name" in value && typeof value.table_name !== "undefined" && typeof value.table_name !== "string") return false; + if ("sql" in value && typeof value.sql !== "undefined" && typeof value.sql !== "string") return false; + return true; +}; + +const is_json_export_data_entry = (value: unknown): value is ReplicaDatabaseJsonExport["data"][number] => { + if (!is_record(value)) return false; + if (typeof value.name !== "string") return false; + if (!Array.isArray(value.rows)) return false; + if (!value.rows.every(is_record)) return false; + return true; +}; + +const is_migration_entry = (value: unknown): value is ReplicaDatabaseMigrationEntry => { + if (!is_record(value)) return false; + return typeof value.name === "string" + && typeof value.up_sql === "string" + && typeof value.down_sql === "string"; +}; + +const is_table_count_entry = (value: unknown): value is ReplicaDatabaseExportManifestRs["table_counts"][number] => { + if (!is_record(value)) return false; + if (typeof value.name !== "string") return false; + if (typeof value.row_count !== "number" || !Number.isFinite(value.row_count)) return false; + return true; +}; + +const is_replica_database_json_export = (value: unknown): value is ReplicaDatabaseJsonExport => { + if (!is_record(value)) return false; + if (typeof value.format_version !== "string") return false; + if (typeof value.replica_db_version !== "string") return false; + if (!Array.isArray(value.schema) || !value.schema.every(is_schema_entry)) return false; + if (!Array.isArray(value.data) || !value.data.every(is_json_export_data_entry)) return false; + if (!Array.isArray(value.migrations) || !value.migrations.every(is_migration_entry)) return false; + return true; +}; + +const is_export_manifest_rs = (value: unknown): value is ReplicaDatabaseExportManifestRs => { + if (!is_record(value)) return false; + if (typeof value.export_version !== "string") return false; + if (typeof value.replica_db_version !== "string") return false; + if (typeof value.backup_format_version !== "string") return false; + if (typeof value.schema_hash !== "string") return false; + if (!Array.isArray(value.schema) || !value.schema.every(is_schema_entry)) return false; + if (!Array.isArray(value.migrations) || !value.migrations.every(is_migration_entry)) return false; + if (!Array.isArray(value.table_counts) || !value.table_counts.every(is_table_count_entry)) return false; + return true; +}; + +const is_export_snapshot = (value: unknown): value is ReplicaDatabaseExportSnapshot => { + if (!is_record(value)) return false; + if (!("manifest_rs" in value) || !is_export_manifest_rs(value.manifest_rs)) return false; + if (!("db_bytes" in value) || !(value.db_bytes instanceof Uint8Array)) return false; + return true; +}; + +const is_string_list = (value: unknown): value is string[] => + Array.isArray(value) && value.every((item) => typeof item === "string"); + +const is_tag_list = (value: unknown): value is string[][] => + Array.isArray(value) && value.every(is_string_list); + +const is_replica_nostr_event_draft = (value: unknown): value is ReplicaNostrEventDraft => { + if (!is_record(value)) return false; + if (typeof value.kind !== "number" || !Number.isFinite(value.kind)) return false; + if (typeof value.author !== "string") return false; + if (typeof value.content !== "string") return false; + if (!is_tag_list(value.tags)) return false; + return true; +}; + +const is_replica_nostr_sync_bundle = (value: unknown): value is ReplicaNostrSyncBundle => { + if (!is_record(value)) return false; + if (typeof value.version !== "number" || !Number.isFinite(value.version)) return false; + if (!Array.isArray(value.events) || !value.events.every(is_replica_nostr_event_draft)) return false; + return true; +}; + +const parse_replica_nostr_sync_bundle = (value: unknown): ReplicaNostrSyncBundle | IError<string> => { + let parsed: unknown = value; + if (typeof value === "string") { + try { + parsed = JSON.parse(value); + } catch { + return err_msg(cl_replica_error.parse_failure); + } + } + if (!is_replica_nostr_sync_bundle(parsed)) return err_msg(cl_replica_error.invalid_response); + return parsed; +}; + +const is_ingest_outcome = (value: unknown): value is "applied" | "skipped" => + value === "applied" || value === "skipped"; + +const replica_sync_event_d_tag = (tags: string[][]): string => { + const match = tags.find((tag) => tag[0] === "d"); + const value = match?.[1]; + return typeof value === "string" ? value : ""; +}; + +const replica_sync_event_key = (draft: ReplicaNostrEventDraft): string => + `${draft.kind}:${draft.author}:${replica_sync_event_d_tag(draft.tags)}`; + +const build_signer_map = (signers: ReplicaNostrSyncSigner[]): Record<string, string> => { + const map: Record<string, string> = {}; + for (const signer of signers) { + const secret_key = signer.secret_key; + if (!secret_key || typeof secret_key !== "string") continue; + const pubkey = nostr_public_key_from_secret(secret_key); + map[pubkey] = secret_key; + } + return map; +}; + +const publish_results_has_success = (results: Record<string, { status?: string }>): boolean => + Object.values(results).some((result) => result.status === "success"); + +type ZipEntry = { + name: string; + data: Uint8Array; +}; + +type ZipEntryPrepared = { + name_bytes: Uint8Array; + data: Uint8Array; + crc32: number; + size: number; +}; + +type ZipFilePickerOptions = { + suggestedName?: string; + types?: { + description?: string; + accept: Record<string, string[]>; + }[]; +}; + +type ZipFileHandle = { + createWritable(): Promise<ZipFileWritable>; +}; + +type ZipFileWritable = { + write(data: Uint8Array): Promise<void>; + close(): Promise<void>; +}; + +type ZipFilePicker = (options?: ZipFilePickerOptions) => Promise<ZipFileHandle>; + +const ZIP_CRC_TABLE = (() => { + const table = new Uint32Array(256); + for (let i = 0; i < 256; i++) { + let c = i; + for (let k = 0; k < 8; k++) { + if (c & 1) c = 0xedb88320 ^ (c >>> 1); + else c >>>= 1; + } + table[i] = c >>> 0; + } + return table; +})(); + +const crc32 = (data: Uint8Array): number => { + let crc = 0xffffffff; + for (let i = 0; i < data.length; i++) { + const idx = (crc ^ data[i]) & 0xff; + crc = ZIP_CRC_TABLE[idx] ^ (crc >>> 8); + } + return (crc ^ 0xffffffff) >>> 0; +}; + +const zip_prepare_entries = (entries: ZipEntry[]): ZipEntryPrepared[] => { + const enc = new TextEncoder(); + return entries.map((entry) => ({ + name_bytes: enc.encode(entry.name), + data: entry.data, + crc32: crc32(entry.data), + size: entry.data.length + })); +}; + +const zip_dos_time = (date: Date): { time: number; date: number } => { + const year = Math.max(1980, date.getFullYear()); + const month = date.getMonth() + 1; + const day = date.getDate(); + const hours = date.getHours(); + const minutes = date.getMinutes(); + const seconds = Math.floor(date.getSeconds() / 2); + const time = (hours << 11) | (minutes << 5) | seconds; + const date_val = ((year - 1980) << 9) | (month << 5) | day; + return { time, date: date_val }; +}; + +const zip_local_header = (entry: ZipEntryPrepared, time: number, date: number): Uint8Array => { + const name_len = entry.name_bytes.length; + const buffer = new ArrayBuffer(30 + name_len); + const view = new DataView(buffer); + view.setUint32(0, 0x04034b50, true); + view.setUint16(4, 20, true); + view.setUint16(6, 0, true); + view.setUint16(8, 0, true); + view.setUint16(10, time, true); + view.setUint16(12, date, true); + view.setUint32(14, entry.crc32, true); + view.setUint32(18, entry.size, true); + view.setUint32(22, entry.size, true); + view.setUint16(26, name_len, true); + view.setUint16(28, 0, true); + const out = new Uint8Array(buffer); + out.set(entry.name_bytes, 30); + return out; +}; + +const zip_central_header = ( + entry: ZipEntryPrepared, + time: number, + date: number, + offset: number +): Uint8Array => { + const name_len = entry.name_bytes.length; + const buffer = new ArrayBuffer(46 + name_len); + const view = new DataView(buffer); + view.setUint32(0, 0x02014b50, true); + view.setUint16(4, 20, true); + view.setUint16(6, 20, true); + view.setUint16(8, 0, true); + view.setUint16(10, 0, true); + view.setUint16(12, time, true); + view.setUint16(14, date, true); + view.setUint32(16, entry.crc32, true); + view.setUint32(20, entry.size, true); + view.setUint32(24, entry.size, true); + view.setUint16(28, name_len, true); + view.setUint16(30, 0, true); + view.setUint16(32, 0, true); + view.setUint16(34, 0, true); + view.setUint16(36, 0, true); + view.setUint32(38, 0, true); + view.setUint32(42, offset, true); + const out = new Uint8Array(buffer); + out.set(entry.name_bytes, 46); + return out; +}; + +const zip_end_record = (entry_count: number, central_size: number, central_offset: number): Uint8Array => { + const buffer = new ArrayBuffer(22); + const view = new DataView(buffer); + view.setUint32(0, 0x06054b50, true); + view.setUint16(4, 0, true); + view.setUint16(6, 0, true); + view.setUint16(8, entry_count, true); + view.setUint16(10, entry_count, true); + view.setUint32(12, central_size, true); + view.setUint32(16, central_offset, true); + view.setUint16(20, 0, true); + return new Uint8Array(buffer); +}; + +const zip_build_bytes = (entries: ZipEntry[]): Uint8Array => { + const prepared = zip_prepare_entries(entries); + const { time, date } = zip_dos_time(new Date()); + const local_parts: Uint8Array[] = []; + const central_parts: Uint8Array[] = []; + let offset = 0; + + for (const entry of prepared) { + const local = zip_local_header(entry, time, date); + local_parts.push(local, entry.data); + const central = zip_central_header(entry, time, date, offset); + central_parts.push(central); + offset += local.length + entry.data.length; + } + + let central_size = 0; + for (const part of central_parts) central_size += part.length; + const end = zip_end_record(prepared.length, central_size, offset); + const total = offset + central_size + end.length; + const out = new Uint8Array(total); + let cursor = 0; + for (const part of local_parts) { + out.set(part, cursor); + cursor += part.length; + } + for (const part of central_parts) { + out.set(part, cursor); + cursor += part.length; + } + out.set(end, cursor); + return out; +}; + +const zip_write_stream = async (stream: ZipFileWritable, entries: ZipEntry[]): Promise<void> => { + const prepared = zip_prepare_entries(entries); + const { time, date } = zip_dos_time(new Date()); + const central_parts: Uint8Array[] = []; + let offset = 0; + + for (const entry of prepared) { + const local = zip_local_header(entry, time, date); + await stream.write(local); + await stream.write(entry.data); + central_parts.push(zip_central_header(entry, time, date, offset)); + offset += local.length + entry.data.length; + } + + let central_size = 0; + for (const part of central_parts) central_size += part.length; + for (const part of central_parts) await stream.write(part); + const end = zip_end_record(prepared.length, central_size, offset); + await stream.write(end); + await stream.close(); +}; + +type TarEntry = { + name: string; + data: Uint8Array; +}; + +const TAR_BLOCK_SIZE = 512; + +const tar_pad_size = (size: number): number => { + const rem = size % TAR_BLOCK_SIZE; + return rem === 0 ? 0 : TAR_BLOCK_SIZE - rem; +}; + +const tar_write_string = (buf: Uint8Array, offset: number, length: number, value: string): void => { + const enc = new TextEncoder(); + const bytes = enc.encode(value); + const slice = bytes.length > length ? bytes.slice(0, length) : bytes; + buf.set(slice, offset); +}; + +const tar_write_octal = (buf: Uint8Array, offset: number, length: number, value: number): void => { + const str = value.toString(8).padStart(length - 1, "0"); + tar_write_string(buf, offset, length - 1, str); + buf[offset + length - 1] = 0; +}; + +const tar_header = (entry: TarEntry, mtime: number): Uint8Array => { + if (entry.name.length > 100) throw new Error("tar entry name too long"); + const header = new Uint8Array(TAR_BLOCK_SIZE); + tar_write_string(header, 0, 100, entry.name); + tar_write_octal(header, 100, 8, 0o644); + tar_write_octal(header, 108, 8, 0); + tar_write_octal(header, 116, 8, 0); + tar_write_octal(header, 124, 12, entry.data.length); + tar_write_octal(header, 136, 12, mtime); + for (let i = 148; i < 156; i++) header[i] = 32; + header[156] = 48; + tar_write_string(header, 257, 6, "ustar"); + tar_write_string(header, 263, 2, "00"); + let checksum = 0; + for (let i = 0; i < header.length; i++) checksum += header[i]; + const chk = checksum.toString(8).padStart(6, "0"); + tar_write_string(header, 148, 6, chk); + header[154] = 0; + header[155] = 32; + return header; +}; + +const tar_build_bytes = (entries: TarEntry[], mtime: number): Uint8Array => { + const parts: Uint8Array[] = []; + let total = 0; + for (const entry of entries) { + const header = tar_header(entry, mtime); + const pad = tar_pad_size(entry.data.length); + parts.push(header, entry.data); + total += header.length + entry.data.length; + if (pad) { + const padding = new Uint8Array(pad); + parts.push(padding); + total += padding.length; + } + } + const end = new Uint8Array(TAR_BLOCK_SIZE * 2); + parts.push(end); + total += end.length; + const out = new Uint8Array(total); + let offset = 0; + for (const part of parts) { + out.set(part, offset); + offset += part.length; + } + return out; +}; + +const tar_stream = (entries: TarEntry[], mtime: number): ReadableStream<Uint8Array> => { + return new ReadableStream({ + start(controller) { + for (const entry of entries) { + const header = tar_header(entry, mtime); + controller.enqueue(header); + controller.enqueue(entry.data); + const pad = tar_pad_size(entry.data.length); + if (pad) controller.enqueue(new Uint8Array(pad)); + } + controller.enqueue(new Uint8Array(TAR_BLOCK_SIZE * 2)); + controller.close(); + } + }); +}; + +const gzip_bytes = async (bytes: Uint8Array): Promise<Uint8Array> => { + if (typeof CompressionStream === "undefined") { + throw new Error("replica export requires gzip support"); + } + const stream = new CompressionStream("gzip"); + const writer = stream.writable.getWriter(); + writer.write(bytes); + await writer.close(); + const buffer = await new Response(stream.readable).arrayBuffer(); + return new Uint8Array(buffer); +}; + +const bytes_to_hex = (bytes: Uint8Array): string => { + const hex: string[] = []; + for (let i = 0; i < bytes.length; i++) { + hex.push(bytes[i].toString(16).padStart(2, "0")); + } + return hex.join(""); +}; + +const sha256_hex = async (bytes: Uint8Array): Promise<string> => { + if (!globalThis.crypto || !globalThis.crypto.subtle) throw new Error(cl_replica_error.crypto_unavailable); + const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes); + return bytes_to_hex(new Uint8Array(digest)); +}; + +const filename_slug = (value: string): string => { + const slug = value + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); + if (slug.startsWith("radroots-")) return slug; + return `radroots-${slug}`; +}; + +const export_filename = (app_name: string, app_version: string): string => { + const base = filename_slug(app_name); + return `${base}-${app_version}-backup.tar.gz`; +}; + +const get_zip_file_picker = (): ZipFilePicker | undefined => { + if (typeof window === "undefined") return undefined; + const picker = (window as Window & { showSaveFilePicker?: ZipFilePicker }).showSaveFilePicker; + return picker; +}; + +const user_activation_is_active = (): boolean => { + if (typeof navigator === "undefined") return false; + const nav = navigator as Navigator & { userActivation?: { isActive?: boolean } }; + if (!nav.userActivation) return true; + return nav.userActivation.isActive === true; +}; + +const is_permission_error = (err: unknown): boolean => { + if (!err) return false; + if (typeof err === "string") { + const msg = err.toLowerCase(); + return msg.includes("permission") || msg.includes("denied") || msg.includes("not allowed"); + } + if (!is_record(err)) return false; + const name = typeof err.name === "string" ? err.name.toLowerCase() : ""; + const message = typeof err.message === "string" ? err.message.toLowerCase() : ""; + if (name.includes("notallowed") || name.includes("abort")) return true; + return message.includes("permission") || message.includes("denied") || message.includes("not allowed"); +}; + +const can_share_file = (file: File): boolean => { + if (typeof navigator === "undefined") return false; + const nav = navigator as Navigator & { canShare?: (data: { files?: File[] }) => boolean }; + if (!nav.canShare) return false; + return nav.canShare({ files: [file] }); +}; + +const share_file = async (file: File): Promise<boolean> => { + if (typeof navigator === "undefined") return false; + const nav = navigator as Navigator & { share?: (data: { files?: File[]; title?: string }) => Promise<void> }; + if (!nav.share) return false; + await nav.share({ files: [file], title: file.name }); + return true; +}; + +const download_blob = (blob: Blob, filename: string): void => { + if (typeof document === "undefined") return; + const url = URL.createObjectURL(blob); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = filename; + anchor.click(); + URL.revokeObjectURL(url); +}; + +const export_tar_gz = async (filename: string, entries: TarEntry[], mtime: number): Promise<void> => { + const picker = get_zip_file_picker(); + if (picker && user_activation_is_active() && typeof CompressionStream !== "undefined") { + try { + const handle = await picker({ + suggestedName: filename, + types: [ + { + description: "Radroots replica export", + accept: { "application/gzip": [".tar.gz"] } + } + ] + }); + const stream = await handle.createWritable(); + const writable_stream = new WritableStream<Uint8Array>({ + write: (chunk) => stream.write(chunk), + close: () => stream.close() + }); + await tar_stream(entries, mtime) + .pipeThrough(new CompressionStream("gzip")) + .pipeTo(writable_stream); + return; + } catch (e) { + if (!is_permission_error(e)) throw e; + } + } + const tar_bytes = tar_build_bytes(entries, mtime); + const gz_bytes = await gzip_bytes(tar_bytes); + const blob = new Blob([gz_bytes], { type: "application/gzip" }); + const file = new File([blob], filename, { type: "application/gzip" }); + if (can_share_file(file) && user_activation_is_active()) { + try { + const shared = await share_file(file); + if (shared) return; + } catch (e) { + if (!is_permission_error(e)) throw e; + } + } + download_blob(blob, filename); +}; + +const DEFAULT_REPLICA_STORE_KEY = "radroots-pwa-v1-replica-db"; +const DEFAULT_REPLICA_IDB_CONFIG: IdbClientConfig = IDB_CONFIG_REPLICA; +let wasm_init_promise: Promise<void> | null = null; + +const runtime_available = (): boolean => { + return typeof window !== "undefined" || typeof self !== "undefined"; +}; + +const wasm_init_once = async (): Promise<void> => { + if (!wasm_init_promise) { + wasm_init_promise = (async () => { + await init_wasm(); + await init_replica_sync_wasm(); + })(); + } + try { + await wasm_init_promise; + } catch (e) { + wasm_init_promise = null; + throw e; + } +}; + +export class WebReplicaDatabase implements IWebReplicaDatabase { + private engine: WebSqlEngine | null = null; + private readonly store_key: string; + private readonly idb_config: IdbClientConfig; + private readonly cipher_config: IdbClientConfig | null; + private readonly sql_wasm_path: string | undefined; + private init_promise: Promise<void> | null = null; + + constructor(config?: WebReplicaDatabaseConfig) { + this.store_key = config?.store_key ?? DEFAULT_REPLICA_STORE_KEY; + this.idb_config = config?.idb_config ?? DEFAULT_REPLICA_IDB_CONFIG; + this.cipher_config = config?.cipher_config ?? null; + this.sql_wasm_path = config?.sql_wasm_path; + } + + get_store_key(): string { + return this.store_key; + } + + private serialize<T>(opts: T): string { + return JSON.stringify(opts); + } + + private deserialize<T>(data: string): T | IError<string> { + try { + return JSON.parse(data); + } catch { + return err_msg(cl_replica_error.parse_failure); + } + } + + private get_engine_config(): WebSqlEngineConfig { + return { + store_key: this.store_key, + idb_config: this.idb_config, + cipher_config: this.cipher_config, + sql_wasm_path: this.sql_wasm_path + }; + } + + private async ensure_ready(): Promise<void> { + await this.init(); + if (!this.engine) throw new Error(cl_replica_error.init_failure); + } + + async init(): Promise<void> { + if (this.engine) return; + if (!runtime_available()) throw new Error(cl_replica_error.runtime_unavailable); + if (!this.init_promise) { + this.init_promise = (async () => { + await wasm_init_once(); + this.engine = await WebSqlEngine.create(this.get_engine_config()); + radroots_sql_install_bridges(this.engine); + replica_db_run_migrations(); + })(); + } + try { + await this.init_promise; + } catch (e) { + this.engine = null; + this.init_promise = null; + throw e; + } + } + + async close(): Promise<void> { + if (this.engine) await this.engine.close(); + this.engine = null; + this.init_promise = null; + } + + async migration_state(): Promise<SqlJsMigrationState | IError<string>> { + try { + await this.ensure_ready(); + const parsed = this.engine?.query("select id, name, applied_at from __migrations order by id asc", []) ?? []; + if (!is_sql_migration_row_list(parsed)) return err_msg(cl_replica_error.invalid_response); + const names = parsed.map((row) => row.name); + return { applied_names: names, applied_count: names.length }; + } catch (e) { + return handle_err(e); + } + } + + async reset(): Promise<SqlJsMigrationState | IError<string>> { + try { + await this.ensure_ready(); + replica_db_reset_database(); + replica_db_run_migrations(); + return this.migration_state(); + } catch (e) { + return handle_err(e); + } + } + + async reinit(): Promise<SqlJsMigrationState | IError<string>> { + try { + await this.ensure_ready(); + if (this.engine) { + await this.engine.purge_storage(); + await this.engine.close(); + } + this.engine = await WebSqlEngine.create(this.get_engine_config()); + radroots_sql_install_bridges(this.engine); + replica_db_run_migrations(); + return this.migration_state(); + } catch (e) { + return handle_err(e); + } + } + + async export_json(): Promise<ReplicaDatabaseJsonExport | IError<string>> { + try { + await this.ensure_ready(); + const res = await replica_db_export_json(); + let parsed: unknown = res; + if (typeof res === "string") { + try { + parsed = JSON.parse(res); + } catch { + return err_msg(cl_replica_error.parse_failure); + } + } + if (!is_replica_database_json_export(parsed)) return err_msg(cl_replica_error.invalid_response); + return parsed; + } catch (e) { + return handle_err(e); + } + } + + async import_json(backup: ReplicaDatabaseJsonExport): Promise<void | IError<string>> { + try { + await this.ensure_ready(); + replica_db_import_json(this.serialize(backup)); + return; + } catch (e) { + return handle_err(e); + } + } + + async export_database(opts: ReplicaDatabaseExportOptions): Promise<void | IError<string>> { + try { + if (opts.store_key && opts.store_key !== this.store_key) { + const alt_db = new WebReplicaDatabase({ + store_key: opts.store_key, + idb_config: this.idb_config, + cipher_config: this.cipher_config, + sql_wasm_path: this.sql_wasm_path + }); + const res = await alt_db.export_database(opts); + await alt_db.close(); + return res; + } + await this.export_database_inner(opts); + return; + } catch (e) { + return handle_err(e); + } + } + + async nostr_sync_all(opts: ReplicaNostrSyncOptions): Promise<ReplicaNostrSyncSummary | IError<string>> { + try { + await this.ensure_ready(); + const relays = Array.from(new Set(opts.relays.map((relay) => relay.trim()).filter((relay) => relay.length))); + if (!relays.length) return err_msg(`replica sync requires relays`); + if (!opts.signers.length) return err_msg(`replica sync requires signers`); + const signer_map = build_signer_map(opts.signers); + if (!Object.keys(signer_map).length) return err_msg(`replica sync requires valid signers`); + + const farms = await this.farm_find_many(); + if ("err" in farms) return farms; + const event_map: Record<string, ReplicaNostrEventDraft> = {}; + for (const farm of farms.results) { + const bundle_raw = replica_sync_sync_all(this.serialize({ + farm: { id: farm.id }, + options: null + })); + const bundle = parse_replica_nostr_sync_bundle(bundle_raw); + if ("err" in bundle) return bundle; + for (const draft of bundle.events) { + const key = replica_sync_event_key(draft); + if (!event_map[key]) event_map[key] = draft; + } + } + + const event_keys = Object.keys(event_map); + event_keys.sort(); + if (!event_keys.length) { + return { + events_total: 0, + events_published: 0, + events_failed: 0, + events_skipped: 0, + missing_signers: [] + }; + } + + const context = opts.context ?? nostr_context_create(); + const context_owned = !opts.context; + let events_published = 0; + let events_failed = 0; + let events_skipped = 0; + const missing_signers = new Set<string>(); + + try { + nostr_relays_open(context, relays); + for (const key of event_keys) { + const draft = event_map[key]; + const secret_key = signer_map[draft.author]; + if (!secret_key) { + missing_signers.add(draft.author); + events_skipped += 1; + continue; + } + const event = nostr_event_sign({ + secret_key, + event: { + kind: draft.kind, + created_at: Math.floor(Date.now() / 1000), + tags: draft.tags, + content: draft.content + } + }); + const publish_results = await nostr_publish({ + event, + relays, + context, + timeout: opts.publish_timeout_ms + }); + if (!publish_results_has_success(publish_results)) { + events_failed += 1; + continue; + } + const ingest_result = replica_sync_ingest_event(this.serialize(event)); + if (!is_ingest_outcome(ingest_result)) { + events_failed += 1; + continue; + } + events_published += 1; + } + } finally { + if (context_owned) nostr_relays_clear(context); + } + + const summary: ReplicaNostrSyncSummary = { + events_total: event_keys.length, + events_published, + events_failed, + events_skipped, + missing_signers: Array.from(missing_signers) + }; + if (summary.missing_signers.length) return err_msg(`replica sync missing signers: ${summary.missing_signers.join(", ")}`); + if (summary.events_failed) return err_msg(`replica sync publish failed (${summary.events_failed}/${summary.events_total})`); + return summary; + } catch (e) { + return handle_err(e); + } + } + + private async export_database_inner(opts: ReplicaDatabaseExportOptions): Promise<void> { + await this.ensure_ready(); + const app_name = opts.app_name; + const app_version = opts.app_version; + const store_key = this.store_key; + let export_active = false; + + try { + const snapshot_raw = await replica_db_export_begin(); + export_active = true; + if (!is_export_snapshot(snapshot_raw)) throw new Error(cl_replica_error.invalid_response); + const manifest_rs = snapshot_raw.manifest_rs; + const db_bytes = snapshot_raw.db_bytes; + const db_sha256 = await sha256_hex(db_bytes); + const exported_at = new Date().toISOString(); + + const manifest_ts_base: ReplicaDatabaseExportManifestTs = { + app_name, + app_version, + exported_at, + db_sha256, + db_size_bytes: db_bytes.byteLength, + store_key + }; + + let manifest: ReplicaDatabaseExportManifest = { + rust: manifest_rs, + client: manifest_ts_base + }; + + if (opts.signer) { + const nostr_event = await opts.signer({ db_sha256, manifest }); + if (nostr_event) { + manifest = { + rust: manifest_rs, + client: { + ...manifest_ts_base, + nostr_event + } + }; + } + } + + const manifest_json = JSON.stringify(manifest, null, 2); + const manifest_bytes = new TextEncoder().encode(manifest_json); + const filename = export_filename(app_name, app_version); + const mtime = Math.floor(Date.parse(exported_at) / 1000); + await export_tar_gz(filename, [ + { name: "manifest.json", data: manifest_bytes }, + { name: "replica.db", data: db_bytes } + ], mtime); + } finally { + if (export_active) replica_db_export_finish(); + } + } + + async farm_create(opts: IFarmCreate): Promise<IFarmCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_create(this.serialize(opts)); + return this.deserialize<IFarmCreateResolve>(res); + } + + async farm_find_one(opts: IFarmFindOne): Promise<IFarmFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_find_one(this.serialize(opts)); + return this.deserialize<IFarmFindOneResolve>(res); + } + + async farm_find_many(opts?: IFarmFindMany): Promise<IFarmFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_find_many(this.serialize(opts ?? {})); + return this.deserialize<IFarmFindManyResolve>(res); + } + + async farm_delete(opts: IFarmDelete): Promise<IFarmDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_delete(this.serialize(opts)); + return this.deserialize<IFarmDeleteResolve>(res); + } + + async farm_update(opts: IFarmUpdate): Promise<IFarmUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_update(this.serialize(opts)); + return this.deserialize<IFarmUpdateResolve>(res); + } + + async plot_create(opts: IPlotCreate): Promise<IPlotCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_create(this.serialize(opts)); + return this.deserialize<IPlotCreateResolve>(res); + } + + async plot_find_one(opts: IPlotFindOne): Promise<IPlotFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_find_one(this.serialize(opts)); + return this.deserialize<IPlotFindOneResolve>(res); + } + + async plot_find_many(opts?: IPlotFindMany): Promise<IPlotFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_find_many(this.serialize(opts ?? {})); + return this.deserialize<IPlotFindManyResolve>(res); + } + + async plot_delete(opts: IPlotDelete): Promise<IPlotDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_delete(this.serialize(opts)); + return this.deserialize<IPlotDeleteResolve>(res); + } + + async plot_update(opts: IPlotUpdate): Promise<IPlotUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_update(this.serialize(opts)); + return this.deserialize<IPlotUpdateResolve>(res); + } + + async gcs_location_create(opts: IGcsLocationCreate): Promise<IGcsLocationCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_gcs_location_create(this.serialize(opts)); + return this.deserialize<IGcsLocationCreateResolve>(res); + } + + async gcs_location_find_one(opts: IGcsLocationFindOne): Promise<IGcsLocationFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_gcs_location_find_one(this.serialize(opts)); + return this.deserialize<IGcsLocationFindOneResolve>(res); + } + + async gcs_location_find_many(opts?: IGcsLocationFindMany): Promise<IGcsLocationFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_gcs_location_find_many(this.serialize(opts ?? {})); + return this.deserialize<IGcsLocationFindManyResolve>(res); + } + + async gcs_location_delete(opts: IGcsLocationDelete): Promise<IGcsLocationDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_gcs_location_delete(this.serialize(opts)); + return this.deserialize<IGcsLocationDeleteResolve>(res); + } + + async gcs_location_update(opts: IGcsLocationUpdate): Promise<IGcsLocationUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_gcs_location_update(this.serialize(opts)); + return this.deserialize<IGcsLocationUpdateResolve>(res); + } + + async farm_gcs_location_create(opts: IFarmGcsLocationCreate): Promise<IFarmGcsLocationCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_gcs_location_create(this.serialize(opts)); + return this.deserialize<IFarmGcsLocationCreateResolve>(res); + } + + async farm_gcs_location_find_one(opts: IFarmGcsLocationFindOne): Promise<IFarmGcsLocationFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_gcs_location_find_one(this.serialize(opts)); + return this.deserialize<IFarmGcsLocationFindOneResolve>(res); + } + + async farm_gcs_location_find_many(opts?: IFarmGcsLocationFindMany): Promise<IFarmGcsLocationFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_gcs_location_find_many(this.serialize(opts ?? {})); + return this.deserialize<IFarmGcsLocationFindManyResolve>(res); + } + + async farm_gcs_location_delete(opts: IFarmGcsLocationDelete): Promise<IFarmGcsLocationDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_gcs_location_delete(this.serialize(opts)); + return this.deserialize<IFarmGcsLocationDeleteResolve>(res); + } + + async farm_gcs_location_update(opts: IFarmGcsLocationUpdate): Promise<IFarmGcsLocationUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_gcs_location_update(this.serialize(opts)); + return this.deserialize<IFarmGcsLocationUpdateResolve>(res); + } + + async plot_gcs_location_create(opts: IPlotGcsLocationCreate): Promise<IPlotGcsLocationCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_gcs_location_create(this.serialize(opts)); + return this.deserialize<IPlotGcsLocationCreateResolve>(res); + } + + async plot_gcs_location_find_one(opts: IPlotGcsLocationFindOne): Promise<IPlotGcsLocationFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_gcs_location_find_one(this.serialize(opts)); + return this.deserialize<IPlotGcsLocationFindOneResolve>(res); + } + + async plot_gcs_location_find_many(opts?: IPlotGcsLocationFindMany): Promise<IPlotGcsLocationFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_gcs_location_find_many(this.serialize(opts ?? {})); + return this.deserialize<IPlotGcsLocationFindManyResolve>(res); + } + + async plot_gcs_location_delete(opts: IPlotGcsLocationDelete): Promise<IPlotGcsLocationDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_gcs_location_delete(this.serialize(opts)); + return this.deserialize<IPlotGcsLocationDeleteResolve>(res); + } + + async plot_gcs_location_update(opts: IPlotGcsLocationUpdate): Promise<IPlotGcsLocationUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_gcs_location_update(this.serialize(opts)); + return this.deserialize<IPlotGcsLocationUpdateResolve>(res); + } + + async farm_tag_create(opts: IFarmTagCreate): Promise<IFarmTagCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_tag_create(this.serialize(opts)); + return this.deserialize<IFarmTagCreateResolve>(res); + } + + async farm_tag_find_one(opts: IFarmTagFindOne): Promise<IFarmTagFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_tag_find_one(this.serialize(opts)); + return this.deserialize<IFarmTagFindOneResolve>(res); + } + + async farm_tag_find_many(opts?: IFarmTagFindMany): Promise<IFarmTagFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_tag_find_many(this.serialize(opts ?? {})); + return this.deserialize<IFarmTagFindManyResolve>(res); + } + + async farm_tag_delete(opts: IFarmTagDelete): Promise<IFarmTagDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_tag_delete(this.serialize(opts)); + return this.deserialize<IFarmTagDeleteResolve>(res); + } + + async farm_tag_update(opts: IFarmTagUpdate): Promise<IFarmTagUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_tag_update(this.serialize(opts)); + return this.deserialize<IFarmTagUpdateResolve>(res); + } + + async plot_tag_create(opts: IPlotTagCreate): Promise<IPlotTagCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_tag_create(this.serialize(opts)); + return this.deserialize<IPlotTagCreateResolve>(res); + } + + async plot_tag_find_one(opts: IPlotTagFindOne): Promise<IPlotTagFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_tag_find_one(this.serialize(opts)); + return this.deserialize<IPlotTagFindOneResolve>(res); + } + + async plot_tag_find_many(opts?: IPlotTagFindMany): Promise<IPlotTagFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_tag_find_many(this.serialize(opts ?? {})); + return this.deserialize<IPlotTagFindManyResolve>(res); + } + + async plot_tag_delete(opts: IPlotTagDelete): Promise<IPlotTagDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_tag_delete(this.serialize(opts)); + return this.deserialize<IPlotTagDeleteResolve>(res); + } + + async plot_tag_update(opts: IPlotTagUpdate): Promise<IPlotTagUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_plot_tag_update(this.serialize(opts)); + return this.deserialize<IPlotTagUpdateResolve>(res); + } + + async farm_member_create(opts: IFarmMemberCreate): Promise<IFarmMemberCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_member_create(this.serialize(opts)); + return this.deserialize<IFarmMemberCreateResolve>(res); + } + + async farm_member_find_one(opts: IFarmMemberFindOne): Promise<IFarmMemberFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_member_find_one(this.serialize(opts)); + return this.deserialize<IFarmMemberFindOneResolve>(res); + } + + async farm_member_find_many(opts?: IFarmMemberFindMany): Promise<IFarmMemberFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_member_find_many(this.serialize(opts ?? {})); + return this.deserialize<IFarmMemberFindManyResolve>(res); + } + + async farm_member_delete(opts: IFarmMemberDelete): Promise<IFarmMemberDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_member_delete(this.serialize(opts)); + return this.deserialize<IFarmMemberDeleteResolve>(res); + } + + async farm_member_update(opts: IFarmMemberUpdate): Promise<IFarmMemberUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_member_update(this.serialize(opts)); + return this.deserialize<IFarmMemberUpdateResolve>(res); + } + + async farm_member_claim_create(opts: IFarmMemberClaimCreate): Promise<IFarmMemberClaimCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_member_claim_create(this.serialize(opts)); + return this.deserialize<IFarmMemberClaimCreateResolve>(res); + } + + async farm_member_claim_find_one(opts: IFarmMemberClaimFindOne): Promise<IFarmMemberClaimFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_member_claim_find_one(this.serialize(opts)); + return this.deserialize<IFarmMemberClaimFindOneResolve>(res); + } + + async farm_member_claim_find_many(opts?: IFarmMemberClaimFindMany): Promise<IFarmMemberClaimFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_member_claim_find_many(this.serialize(opts ?? {})); + return this.deserialize<IFarmMemberClaimFindManyResolve>(res); + } + + async farm_member_claim_delete(opts: IFarmMemberClaimDelete): Promise<IFarmMemberClaimDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_member_claim_delete(this.serialize(opts)); + return this.deserialize<IFarmMemberClaimDeleteResolve>(res); + } + + async farm_member_claim_update(opts: IFarmMemberClaimUpdate): Promise<IFarmMemberClaimUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_farm_member_claim_update(this.serialize(opts)); + return this.deserialize<IFarmMemberClaimUpdateResolve>(res); + } + + async log_error_create(opts: ILogErrorCreate): Promise<ILogErrorCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_log_error_create(this.serialize(opts)); + return this.deserialize<ILogErrorCreateResolve>(res); + } + + async log_error_find_one(opts: ILogErrorFindOne): Promise<ILogErrorFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_log_error_find_one(this.serialize(opts)); + return this.deserialize<ILogErrorFindOneResolve>(res); + } + + async log_error_find_many(opts?: ILogErrorFindMany): Promise<ILogErrorFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_log_error_find_many(this.serialize(opts ?? {})); + return this.deserialize<ILogErrorFindManyResolve>(res); + } + + async log_error_delete(opts: ILogErrorDelete): Promise<ILogErrorDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_log_error_delete(this.serialize(opts)); + return this.deserialize<ILogErrorDeleteResolve>(res); + } + + async log_error_update(opts: ILogErrorUpdate): Promise<ILogErrorUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_log_error_update(this.serialize(opts)); + return this.deserialize<ILogErrorUpdateResolve>(res); + } + + async media_image_create(opts: IMediaImageCreate): Promise<IMediaImageCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_media_image_create(this.serialize(opts)); + return this.deserialize<IMediaImageCreateResolve>(res); + } + + async media_image_find_one(opts: IMediaImageFindOne): Promise<IMediaImageFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_media_image_find_one(this.serialize(opts)); + return this.deserialize<IMediaImageFindOneResolve>(res); + } + + async media_image_find_many(opts?: IMediaImageFindMany): Promise<IMediaImageFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_media_image_find_many(this.serialize(opts ?? {})); + return this.deserialize<IMediaImageFindManyResolve>(res); + } + + async media_image_delete(opts: IMediaImageDelete): Promise<IMediaImageDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_media_image_delete(this.serialize(opts)); + return this.deserialize<IMediaImageDeleteResolve>(res); + } + + async media_image_update(opts: IMediaImageUpdate): Promise<IMediaImageUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_media_image_update(this.serialize(opts)); + return this.deserialize<IMediaImageUpdateResolve>(res); + } + + async nostr_profile_create(opts: INostrProfileCreate): Promise<INostrProfileCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_profile_create(this.serialize(opts)); + return this.deserialize<INostrProfileCreateResolve>(res); + } + + async nostr_profile_find_one(opts: INostrProfileFindOne): Promise<INostrProfileFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_profile_find_one(this.serialize(opts)); + return this.deserialize<INostrProfileFindOneResolve>(res); + } + + async nostr_profile_find_many(opts?: INostrProfileFindMany): Promise<INostrProfileFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_profile_find_many(this.serialize(opts ?? {})); + return this.deserialize<INostrProfileFindManyResolve>(res); + } + + async nostr_profile_delete(opts: INostrProfileDelete): Promise<INostrProfileDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_profile_delete(this.serialize(opts)); + return this.deserialize<INostrProfileDeleteResolve>(res); + } + + async nostr_profile_update(opts: INostrProfileUpdate): Promise<INostrProfileUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_profile_update(this.serialize(opts)); + return this.deserialize<INostrProfileUpdateResolve>(res); + } + + async nostr_event_state_create(opts: INostrEventStateCreate): Promise<INostrEventStateCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_event_state_create(this.serialize(opts)); + return this.deserialize<INostrEventStateCreateResolve>(res); + } + + async nostr_event_state_find_one(opts: INostrEventStateFindOne): Promise<INostrEventStateFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_event_state_find_one(this.serialize(opts)); + return this.deserialize<INostrEventStateFindOneResolve>(res); + } + + async nostr_event_state_find_many(opts?: INostrEventStateFindMany): Promise<INostrEventStateFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_event_state_find_many(this.serialize(opts ?? {})); + return this.deserialize<INostrEventStateFindManyResolve>(res); + } + + async nostr_event_state_delete(opts: INostrEventStateDelete): Promise<INostrEventStateDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_event_state_delete(this.serialize(opts)); + return this.deserialize<INostrEventStateDeleteResolve>(res); + } + + async nostr_event_state_update(opts: INostrEventStateUpdate): Promise<INostrEventStateUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_event_state_update(this.serialize(opts)); + return this.deserialize<INostrEventStateUpdateResolve>(res); + } + + async nostr_relay_create(opts: INostrRelayCreate): Promise<INostrRelayCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_relay_create(this.serialize(opts)); + return this.deserialize<INostrRelayCreateResolve>(res); + } + + async nostr_relay_find_one(opts: INostrRelayFindOne): Promise<INostrRelayFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_relay_find_one(this.serialize(opts)); + return this.deserialize<INostrRelayFindOneResolve>(res); + } + + async nostr_relay_find_many(opts?: INostrRelayFindMany): Promise<INostrRelayFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_relay_find_many(this.serialize(opts ?? {})); + return this.deserialize<INostrRelayFindManyResolve>(res); + } + + async nostr_relay_delete(opts: INostrRelayDelete): Promise<INostrRelayDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_relay_delete(this.serialize(opts)); + return this.deserialize<INostrRelayDeleteResolve>(res); + } + + async nostr_relay_update(opts: INostrRelayUpdate): Promise<INostrRelayUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_relay_update(this.serialize(opts)); + return this.deserialize<INostrRelayUpdateResolve>(res); + } + + async trade_product_create(opts: ITradeProductCreate): Promise<ITradeProductCreateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_trade_product_create(this.serialize(opts)); + return this.deserialize<ITradeProductCreateResolve>(res); + } + + async trade_product_find_one(opts: ITradeProductFindOne): Promise<ITradeProductFindOneResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_trade_product_find_one(this.serialize(opts)); + return this.deserialize<ITradeProductFindOneResolve>(res); + } + + async trade_product_find_many(opts?: ITradeProductFindMany): Promise<ITradeProductFindManyResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_trade_product_find_many(this.serialize(opts ?? {})); + return this.deserialize<ITradeProductFindManyResolve>(res); + } + + async trade_product_delete(opts: ITradeProductDelete): Promise<ITradeProductDeleteResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_trade_product_delete(this.serialize(opts)); + return this.deserialize<ITradeProductDeleteResolve>(res); + } + + async trade_product_update(opts: ITradeProductUpdate): Promise<ITradeProductUpdateResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_trade_product_update(this.serialize(opts)); + return this.deserialize<ITradeProductUpdateResolve>(res); + } + + async nostr_profile_relay_set(opts: INostrProfileRelayRelation): Promise<INostrProfileRelayResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_profile_relay_set(this.serialize(opts)); + return this.deserialize<INostrProfileRelayResolve>(res); + } + + async nostr_profile_relay_unset(opts: INostrProfileRelayRelation): Promise<INostrProfileRelayResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_nostr_profile_relay_unset(this.serialize(opts)); + return this.deserialize<INostrProfileRelayResolve>(res); + } + + async trade_product_location_set(opts: ITradeProductLocationRelation): Promise<ITradeProductLocationResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_trade_product_location_set(this.serialize(opts)); + return this.deserialize<ITradeProductLocationResolve>(res); + } + + async trade_product_location_unset(opts: ITradeProductLocationRelation): Promise<ITradeProductLocationResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_trade_product_location_unset(this.serialize(opts)); + return this.deserialize<ITradeProductLocationResolve>(res); + } + + async trade_product_media_set(opts: ITradeProductMediaRelation): Promise<ITradeProductMediaResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_trade_product_media_set(this.serialize(opts)); + return this.deserialize<ITradeProductMediaResolve>(res); + } + + async trade_product_media_unset(opts: ITradeProductMediaRelation): Promise<ITradeProductMediaResolve | IError<string>> { + await this.ensure_ready(); + const res = await replica_db_trade_product_media_unset(this.serialize(opts)); + return this.deserialize<ITradeProductMediaResolve>(res); + } + +} + +export const web_replica_database_create = async (config?: WebReplicaDatabaseConfig): Promise<WebReplicaDatabase> => { + const db = new WebReplicaDatabase(config); + await db.init(); + return db; +}; diff --git a/client/src/tangle/error.ts b/client/src/tangle/error.ts @@ -1,10 +0,0 @@ -export const cl_tangle_error = { - init_failure: "error.client.tangle.init_failure", - parse_failure: "error.client.tangle.parse_failure", - invalid_response: "error.client.tangle.invalid_response", - runtime_unavailable: "error.client.tangle.runtime_unavailable", - crypto_unavailable: "error.client.tangle.crypto_unavailable" -} as const; - -export type ClientTangleError = keyof typeof cl_tangle_error; -export type ClientTangleErrorMessage = (typeof cl_tangle_error)[ClientTangleError]; diff --git a/client/src/tangle/types.ts b/client/src/tangle/types.ts @@ -1,263 +0,0 @@ -import type { - IFarmCreate, - IFarmCreateResolve, - IFarmDelete, - IFarmDeleteResolve, - IFarmFindMany, - IFarmFindManyResolve, - IFarmFindOne, - IFarmFindOneResolve, - IFarmUpdate, - IFarmUpdateResolve, - IFarmGcsLocationCreate, - IFarmGcsLocationCreateResolve, - IFarmGcsLocationDelete, - IFarmGcsLocationDeleteResolve, - IFarmGcsLocationFindMany, - IFarmGcsLocationFindManyResolve, - IFarmGcsLocationFindOne, - IFarmGcsLocationFindOneResolve, - IFarmGcsLocationUpdate, - IFarmGcsLocationUpdateResolve, - IFarmMemberClaimCreate, - IFarmMemberClaimCreateResolve, - IFarmMemberClaimDelete, - IFarmMemberClaimDeleteResolve, - IFarmMemberClaimFindMany, - IFarmMemberClaimFindManyResolve, - IFarmMemberClaimFindOne, - IFarmMemberClaimFindOneResolve, - IFarmMemberClaimUpdate, - IFarmMemberClaimUpdateResolve, - IFarmMemberCreate, - IFarmMemberCreateResolve, - IFarmMemberDelete, - IFarmMemberDeleteResolve, - IFarmMemberFindMany, - IFarmMemberFindManyResolve, - IFarmMemberFindOne, - IFarmMemberFindOneResolve, - IFarmMemberUpdate, - IFarmMemberUpdateResolve, - IFarmTagCreate, - IFarmTagCreateResolve, - IFarmTagDelete, - IFarmTagDeleteResolve, - IFarmTagFindMany, - IFarmTagFindManyResolve, - IFarmTagFindOne, - IFarmTagFindOneResolve, - IFarmTagUpdate, - IFarmTagUpdateResolve, - IGcsLocationCreate, - IGcsLocationCreateResolve, - IGcsLocationDelete, - IGcsLocationDeleteResolve, - IGcsLocationFindMany, - IGcsLocationFindManyResolve, - IGcsLocationFindOne, - IGcsLocationFindOneResolve, - IGcsLocationUpdate, - IGcsLocationUpdateResolve, - ILogErrorCreate, - ILogErrorCreateResolve, - ILogErrorDelete, - ILogErrorDeleteResolve, - ILogErrorFindMany, - ILogErrorFindManyResolve, - ILogErrorFindOne, - ILogErrorFindOneResolve, - ILogErrorUpdate, - ILogErrorUpdateResolve, - IMediaImageCreate, - IMediaImageCreateResolve, - IMediaImageDelete, - IMediaImageDeleteResolve, - IMediaImageFindMany, - IMediaImageFindManyResolve, - IMediaImageFindOne, - IMediaImageFindOneResolve, - IMediaImageUpdate, - IMediaImageUpdateResolve, - INostrEventStateCreate, - INostrEventStateCreateResolve, - INostrEventStateDelete, - INostrEventStateDeleteResolve, - INostrEventStateFindMany, - INostrEventStateFindManyResolve, - INostrEventStateFindOne, - INostrEventStateFindOneResolve, - INostrEventStateUpdate, - INostrEventStateUpdateResolve, - INostrProfileCreate, - INostrProfileCreateResolve, - INostrProfileDelete, - INostrProfileDeleteResolve, - INostrProfileFindMany, - INostrProfileFindManyResolve, - INostrProfileFindOne, - INostrProfileFindOneResolve, - INostrProfileUpdate, - INostrProfileUpdateResolve, - INostrRelayCreate, - INostrRelayCreateResolve, - INostrRelayDelete, - INostrRelayDeleteResolve, - INostrRelayFindMany, - INostrRelayFindManyResolve, - INostrRelayFindOne, - INostrRelayFindOneResolve, - INostrRelayUpdate, - INostrRelayUpdateResolve, - IPlotCreate, - IPlotCreateResolve, - IPlotDelete, - IPlotDeleteResolve, - IPlotFindMany, - IPlotFindManyResolve, - IPlotFindOne, - IPlotFindOneResolve, - IPlotGcsLocationCreate, - IPlotGcsLocationCreateResolve, - IPlotGcsLocationDelete, - IPlotGcsLocationDeleteResolve, - IPlotGcsLocationFindMany, - IPlotGcsLocationFindManyResolve, - IPlotGcsLocationFindOne, - IPlotGcsLocationFindOneResolve, - IPlotGcsLocationUpdate, - IPlotGcsLocationUpdateResolve, - IPlotTagCreate, - IPlotTagCreateResolve, - IPlotTagDelete, - IPlotTagDeleteResolve, - IPlotTagFindMany, - IPlotTagFindManyResolve, - IPlotTagFindOne, - IPlotTagFindOneResolve, - IPlotTagUpdate, - IPlotTagUpdateResolve, - IPlotUpdate, - IPlotUpdateResolve, - ITradeProductCreate, - ITradeProductCreateResolve, - ITradeProductDelete, - ITradeProductDeleteResolve, - ITradeProductFindMany, - ITradeProductFindManyResolve, - ITradeProductFindOne, - ITradeProductFindOneResolve, - ITradeProductUpdate, - ITradeProductUpdateResolve, - INostrProfileRelayRelation, - INostrProfileRelayResolve, - ITradeProductLocationRelation, - ITradeProductLocationResolve, - ITradeProductMediaRelation, - ITradeProductMediaResolve -} from "@radroots/tangle-db-schema-bindings"; -import { type SqlJsMigrationState } from "../sql/types.js"; -import type { IError } from "@radroots/types-bindings"; -import type { - TangleDatabaseExportOptions, - TangleDatabaseJsonExport, - TangleNostrSyncOptions, - TangleNostrSyncSummary -} from "./web.js"; - -export interface IClientTangleDatabase { - init(): Promise<void>; - close(): Promise<void>; - migration_state(): Promise<SqlJsMigrationState | IError<string>>; - reset(): Promise<SqlJsMigrationState | IError<string>>; - reinit(): Promise<SqlJsMigrationState | IError<string>>; - get_store_key(): string; - export_json(): Promise<TangleDatabaseJsonExport | IError<string>>; - import_json(backup: TangleDatabaseJsonExport): Promise<void | IError<string>>; - export_database(opts: TangleDatabaseExportOptions): Promise<void | IError<string>>; - nostr_sync_all(opts: TangleNostrSyncOptions): Promise<TangleNostrSyncSummary | IError<string>>; - farm_create(opts: IFarmCreate): Promise<IFarmCreateResolve | IError<string>>; - farm_find_one(opts: IFarmFindOne): Promise<IFarmFindOneResolve | IError<string>>; - farm_find_many(opts?: IFarmFindMany): Promise<IFarmFindManyResolve | IError<string>>; - farm_delete(opts: IFarmDelete): Promise<IFarmDeleteResolve | IError<string>>; - farm_update(opts: IFarmUpdate): Promise<IFarmUpdateResolve | IError<string>>; - plot_create(opts: IPlotCreate): Promise<IPlotCreateResolve | IError<string>>; - plot_find_one(opts: IPlotFindOne): Promise<IPlotFindOneResolve | IError<string>>; - plot_find_many(opts?: IPlotFindMany): Promise<IPlotFindManyResolve | IError<string>>; - plot_delete(opts: IPlotDelete): Promise<IPlotDeleteResolve | IError<string>>; - plot_update(opts: IPlotUpdate): Promise<IPlotUpdateResolve | IError<string>>; - gcs_location_create(opts: IGcsLocationCreate): Promise<IGcsLocationCreateResolve | IError<string>>; - gcs_location_find_one(opts: IGcsLocationFindOne): Promise<IGcsLocationFindOneResolve | IError<string>>; - gcs_location_find_many(opts?: IGcsLocationFindMany): Promise<IGcsLocationFindManyResolve | IError<string>>; - gcs_location_delete(opts: IGcsLocationDelete): Promise<IGcsLocationDeleteResolve | IError<string>>; - gcs_location_update(opts: IGcsLocationUpdate): Promise<IGcsLocationUpdateResolve | IError<string>>; - farm_gcs_location_create(opts: IFarmGcsLocationCreate): Promise<IFarmGcsLocationCreateResolve | IError<string>>; - farm_gcs_location_find_one(opts: IFarmGcsLocationFindOne): Promise<IFarmGcsLocationFindOneResolve | IError<string>>; - farm_gcs_location_find_many(opts?: IFarmGcsLocationFindMany): Promise<IFarmGcsLocationFindManyResolve | IError<string>>; - farm_gcs_location_delete(opts: IFarmGcsLocationDelete): Promise<IFarmGcsLocationDeleteResolve | IError<string>>; - farm_gcs_location_update(opts: IFarmGcsLocationUpdate): Promise<IFarmGcsLocationUpdateResolve | IError<string>>; - plot_gcs_location_create(opts: IPlotGcsLocationCreate): Promise<IPlotGcsLocationCreateResolve | IError<string>>; - plot_gcs_location_find_one(opts: IPlotGcsLocationFindOne): Promise<IPlotGcsLocationFindOneResolve | IError<string>>; - plot_gcs_location_find_many(opts?: IPlotGcsLocationFindMany): Promise<IPlotGcsLocationFindManyResolve | IError<string>>; - plot_gcs_location_delete(opts: IPlotGcsLocationDelete): Promise<IPlotGcsLocationDeleteResolve | IError<string>>; - plot_gcs_location_update(opts: IPlotGcsLocationUpdate): Promise<IPlotGcsLocationUpdateResolve | IError<string>>; - farm_tag_create(opts: IFarmTagCreate): Promise<IFarmTagCreateResolve | IError<string>>; - farm_tag_find_one(opts: IFarmTagFindOne): Promise<IFarmTagFindOneResolve | IError<string>>; - farm_tag_find_many(opts?: IFarmTagFindMany): Promise<IFarmTagFindManyResolve | IError<string>>; - farm_tag_delete(opts: IFarmTagDelete): Promise<IFarmTagDeleteResolve | IError<string>>; - farm_tag_update(opts: IFarmTagUpdate): Promise<IFarmTagUpdateResolve | IError<string>>; - plot_tag_create(opts: IPlotTagCreate): Promise<IPlotTagCreateResolve | IError<string>>; - plot_tag_find_one(opts: IPlotTagFindOne): Promise<IPlotTagFindOneResolve | IError<string>>; - plot_tag_find_many(opts?: IPlotTagFindMany): Promise<IPlotTagFindManyResolve | IError<string>>; - plot_tag_delete(opts: IPlotTagDelete): Promise<IPlotTagDeleteResolve | IError<string>>; - plot_tag_update(opts: IPlotTagUpdate): Promise<IPlotTagUpdateResolve | IError<string>>; - farm_member_create(opts: IFarmMemberCreate): Promise<IFarmMemberCreateResolve | IError<string>>; - farm_member_find_one(opts: IFarmMemberFindOne): Promise<IFarmMemberFindOneResolve | IError<string>>; - farm_member_find_many(opts?: IFarmMemberFindMany): Promise<IFarmMemberFindManyResolve | IError<string>>; - farm_member_delete(opts: IFarmMemberDelete): Promise<IFarmMemberDeleteResolve | IError<string>>; - farm_member_update(opts: IFarmMemberUpdate): Promise<IFarmMemberUpdateResolve | IError<string>>; - farm_member_claim_create(opts: IFarmMemberClaimCreate): Promise<IFarmMemberClaimCreateResolve | IError<string>>; - farm_member_claim_find_one(opts: IFarmMemberClaimFindOne): Promise<IFarmMemberClaimFindOneResolve | IError<string>>; - farm_member_claim_find_many(opts?: IFarmMemberClaimFindMany): Promise<IFarmMemberClaimFindManyResolve | IError<string>>; - farm_member_claim_delete(opts: IFarmMemberClaimDelete): Promise<IFarmMemberClaimDeleteResolve | IError<string>>; - farm_member_claim_update(opts: IFarmMemberClaimUpdate): Promise<IFarmMemberClaimUpdateResolve | IError<string>>; - nostr_event_state_create(opts: INostrEventStateCreate): Promise<INostrEventStateCreateResolve | IError<string>>; - nostr_event_state_find_one(opts: INostrEventStateFindOne): Promise<INostrEventStateFindOneResolve | IError<string>>; - nostr_event_state_find_many(opts?: INostrEventStateFindMany): Promise<INostrEventStateFindManyResolve | IError<string>>; - nostr_event_state_delete(opts: INostrEventStateDelete): Promise<INostrEventStateDeleteResolve | IError<string>>; - nostr_event_state_update(opts: INostrEventStateUpdate): Promise<INostrEventStateUpdateResolve | IError<string>>; - log_error_create(opts: ILogErrorCreate): Promise<ILogErrorCreateResolve | IError<string>>; - log_error_find_one(opts: ILogErrorFindOne): Promise<ILogErrorFindOneResolve | IError<string>>; - log_error_find_many(opts?: ILogErrorFindMany): Promise<ILogErrorFindManyResolve | IError<string>>; - log_error_delete(opts: ILogErrorDelete): Promise<ILogErrorDeleteResolve | IError<string>>; - log_error_update(opts: ILogErrorUpdate): Promise<ILogErrorUpdateResolve | IError<string>>; - media_image_create(opts: IMediaImageCreate): Promise<IMediaImageCreateResolve | IError<string>>; - media_image_find_one(opts: IMediaImageFindOne): Promise<IMediaImageFindOneResolve | IError<string>>; - media_image_find_many(opts?: IMediaImageFindMany): Promise<IMediaImageFindManyResolve | IError<string>>; - media_image_delete(opts: IMediaImageDelete): Promise<IMediaImageDeleteResolve | IError<string>>; - media_image_update(opts: IMediaImageUpdate): Promise<IMediaImageUpdateResolve | IError<string>>; - nostr_profile_create(opts: INostrProfileCreate): Promise<INostrProfileCreateResolve | IError<string>>; - nostr_profile_find_one(opts: INostrProfileFindOne): Promise<INostrProfileFindOneResolve | IError<string>>; - nostr_profile_find_many(opts?: INostrProfileFindMany): Promise<INostrProfileFindManyResolve | IError<string>>; - nostr_profile_delete(opts: INostrProfileDelete): Promise<INostrProfileDeleteResolve | IError<string>>; - nostr_profile_update(opts: INostrProfileUpdate): Promise<INostrProfileUpdateResolve | IError<string>>; - nostr_relay_create(opts: INostrRelayCreate): Promise<INostrRelayCreateResolve | IError<string>>; - nostr_relay_find_one(opts: INostrRelayFindOne): Promise<INostrRelayFindOneResolve | IError<string>>; - nostr_relay_find_many(opts?: INostrRelayFindMany): Promise<INostrRelayFindManyResolve | IError<string>>; - nostr_relay_delete(opts: INostrRelayDelete): Promise<INostrRelayDeleteResolve | IError<string>>; - nostr_relay_update(opts: INostrRelayUpdate): Promise<INostrRelayUpdateResolve | IError<string>>; - trade_product_create(opts: ITradeProductCreate): Promise<ITradeProductCreateResolve | IError<string>>; - trade_product_find_one(opts: ITradeProductFindOne): Promise<ITradeProductFindOneResolve | IError<string>>; - trade_product_find_many(opts?: ITradeProductFindMany): Promise<ITradeProductFindManyResolve | IError<string>>; - trade_product_delete(opts: ITradeProductDelete): Promise<ITradeProductDeleteResolve | IError<string>>; - trade_product_update(opts: ITradeProductUpdate): Promise<ITradeProductUpdateResolve | IError<string>>; - nostr_profile_relay_set(opts: INostrProfileRelayRelation): Promise<INostrProfileRelayResolve | IError<string>>; - nostr_profile_relay_unset(opts: INostrProfileRelayRelation): Promise<INostrProfileRelayResolve | IError<string>>; - trade_product_location_set(opts: ITradeProductLocationRelation): Promise<ITradeProductLocationResolve | IError<string>>; - trade_product_location_unset(opts: ITradeProductLocationRelation): Promise<ITradeProductLocationResolve | IError<string>>; - trade_product_media_set(opts: ITradeProductMediaRelation): Promise<ITradeProductMediaResolve | IError<string>>; - trade_product_media_unset(opts: ITradeProductMediaRelation): Promise<ITradeProductMediaResolve | IError<string>>; -} - -export interface IWebTangleDatabase extends IClientTangleDatabase { -} diff --git a/client/src/tangle/web.ts b/client/src/tangle/web.ts @@ -1,1778 +0,0 @@ -import type { - IFarmCreate, - IFarmCreateResolve, - IFarmDelete, - IFarmDeleteResolve, - IFarmFindMany, - IFarmFindManyResolve, - IFarmFindOne, - IFarmFindOneResolve, - IFarmUpdate, - IFarmUpdateResolve, - IFarmGcsLocationCreate, - IFarmGcsLocationCreateResolve, - IFarmGcsLocationDelete, - IFarmGcsLocationDeleteResolve, - IFarmGcsLocationFindMany, - IFarmGcsLocationFindManyResolve, - IFarmGcsLocationFindOne, - IFarmGcsLocationFindOneResolve, - IFarmGcsLocationUpdate, - IFarmGcsLocationUpdateResolve, - IFarmMemberClaimCreate, - IFarmMemberClaimCreateResolve, - IFarmMemberClaimDelete, - IFarmMemberClaimDeleteResolve, - IFarmMemberClaimFindMany, - IFarmMemberClaimFindManyResolve, - IFarmMemberClaimFindOne, - IFarmMemberClaimFindOneResolve, - IFarmMemberClaimUpdate, - IFarmMemberClaimUpdateResolve, - IFarmMemberCreate, - IFarmMemberCreateResolve, - IFarmMemberDelete, - IFarmMemberDeleteResolve, - IFarmMemberFindMany, - IFarmMemberFindManyResolve, - IFarmMemberFindOne, - IFarmMemberFindOneResolve, - IFarmMemberUpdate, - IFarmMemberUpdateResolve, - IFarmTagCreate, - IFarmTagCreateResolve, - IFarmTagDelete, - IFarmTagDeleteResolve, - IFarmTagFindMany, - IFarmTagFindManyResolve, - IFarmTagFindOne, - IFarmTagFindOneResolve, - IFarmTagUpdate, - IFarmTagUpdateResolve, - IGcsLocationCreate, - IGcsLocationCreateResolve, - IGcsLocationDelete, - IGcsLocationDeleteResolve, - IGcsLocationFindMany, - IGcsLocationFindManyResolve, - IGcsLocationFindOne, - IGcsLocationFindOneResolve, - IGcsLocationUpdate, - IGcsLocationUpdateResolve, - ILogErrorCreate, - ILogErrorCreateResolve, - ILogErrorDelete, - ILogErrorDeleteResolve, - ILogErrorFindMany, - ILogErrorFindManyResolve, - ILogErrorFindOne, - ILogErrorFindOneResolve, - ILogErrorUpdate, - ILogErrorUpdateResolve, - IMediaImageCreate, - IMediaImageCreateResolve, - IMediaImageDelete, - IMediaImageDeleteResolve, - IMediaImageFindMany, - IMediaImageFindManyResolve, - IMediaImageFindOne, - IMediaImageFindOneResolve, - IMediaImageUpdate, - IMediaImageUpdateResolve, - INostrEventStateCreate, - INostrEventStateCreateResolve, - INostrEventStateDelete, - INostrEventStateDeleteResolve, - INostrEventStateFindMany, - INostrEventStateFindManyResolve, - INostrEventStateFindOne, - INostrEventStateFindOneResolve, - INostrEventStateUpdate, - INostrEventStateUpdateResolve, - INostrProfileCreate, - INostrProfileCreateResolve, - INostrProfileDelete, - INostrProfileDeleteResolve, - INostrProfileFindMany, - INostrProfileFindManyResolve, - INostrProfileFindOne, - INostrProfileFindOneResolve, - INostrProfileUpdate, - INostrProfileUpdateResolve, - INostrRelayCreate, - INostrRelayCreateResolve, - INostrRelayDelete, - INostrRelayDeleteResolve, - INostrRelayFindMany, - INostrRelayFindManyResolve, - INostrRelayFindOne, - INostrRelayFindOneResolve, - INostrRelayUpdate, - INostrRelayUpdateResolve, - IPlotCreate, - IPlotCreateResolve, - IPlotDelete, - IPlotDeleteResolve, - IPlotFindMany, - IPlotFindManyResolve, - IPlotFindOne, - IPlotFindOneResolve, - IPlotGcsLocationCreate, - IPlotGcsLocationCreateResolve, - IPlotGcsLocationDelete, - IPlotGcsLocationDeleteResolve, - IPlotGcsLocationFindMany, - IPlotGcsLocationFindManyResolve, - IPlotGcsLocationFindOne, - IPlotGcsLocationFindOneResolve, - IPlotGcsLocationUpdate, - IPlotGcsLocationUpdateResolve, - IPlotTagCreate, - IPlotTagCreateResolve, - IPlotTagDelete, - IPlotTagDeleteResolve, - IPlotTagFindMany, - IPlotTagFindManyResolve, - IPlotTagFindOne, - IPlotTagFindOneResolve, - IPlotTagUpdate, - IPlotTagUpdateResolve, - IPlotUpdate, - IPlotUpdateResolve, - ITradeProductCreate, - ITradeProductCreateResolve, - ITradeProductDelete, - ITradeProductDeleteResolve, - ITradeProductFindMany, - ITradeProductFindManyResolve, - ITradeProductFindOne, - ITradeProductFindOneResolve, - ITradeProductUpdate, - ITradeProductUpdateResolve, - INostrProfileRelayRelation, - INostrProfileRelayResolve, - ITradeProductLocationRelation, - ITradeProductLocationResolve, - ITradeProductMediaRelation, - ITradeProductMediaResolve -} from "@radroots/tangle-db-schema-bindings"; -import init_wasm, { - query_sql, - tangle_db_farm_create, - tangle_db_farm_delete, - tangle_db_farm_find_many, - tangle_db_farm_find_one, - tangle_db_farm_update, - tangle_db_plot_create, - tangle_db_plot_delete, - tangle_db_plot_find_many, - tangle_db_plot_find_one, - tangle_db_plot_update, - tangle_db_gcs_location_create, - tangle_db_gcs_location_delete, - tangle_db_gcs_location_find_many, - tangle_db_gcs_location_find_one, - tangle_db_gcs_location_update, - tangle_db_farm_gcs_location_create, - tangle_db_farm_gcs_location_delete, - tangle_db_farm_gcs_location_find_many, - tangle_db_farm_gcs_location_find_one, - tangle_db_farm_gcs_location_update, - tangle_db_plot_gcs_location_create, - tangle_db_plot_gcs_location_delete, - tangle_db_plot_gcs_location_find_many, - tangle_db_plot_gcs_location_find_one, - tangle_db_plot_gcs_location_update, - tangle_db_farm_tag_create, - tangle_db_farm_tag_delete, - tangle_db_farm_tag_find_many, - tangle_db_farm_tag_find_one, - tangle_db_farm_tag_update, - tangle_db_plot_tag_create, - tangle_db_plot_tag_delete, - tangle_db_plot_tag_find_many, - tangle_db_plot_tag_find_one, - tangle_db_plot_tag_update, - tangle_db_farm_member_create, - tangle_db_farm_member_delete, - tangle_db_farm_member_find_many, - tangle_db_farm_member_find_one, - tangle_db_farm_member_update, - tangle_db_farm_member_claim_create, - tangle_db_farm_member_claim_delete, - tangle_db_farm_member_claim_find_many, - tangle_db_farm_member_claim_find_one, - tangle_db_farm_member_claim_update, - tangle_db_log_error_create, - tangle_db_log_error_delete, - tangle_db_log_error_find_many, - tangle_db_log_error_find_one, - tangle_db_log_error_update, - tangle_db_media_image_create, - tangle_db_media_image_delete, - tangle_db_media_image_find_many, - tangle_db_media_image_find_one, - tangle_db_media_image_update, - tangle_db_nostr_event_state_create, - tangle_db_nostr_event_state_delete, - tangle_db_nostr_event_state_find_many, - tangle_db_nostr_event_state_find_one, - tangle_db_nostr_event_state_update, - tangle_db_nostr_profile_create, - tangle_db_nostr_profile_delete, - tangle_db_nostr_profile_find_many, - tangle_db_nostr_profile_find_one, - tangle_db_nostr_profile_update, - tangle_db_nostr_relay_create, - tangle_db_nostr_relay_delete, - tangle_db_nostr_relay_find_many, - tangle_db_nostr_relay_find_one, - tangle_db_nostr_relay_update, - tangle_db_trade_product_create, - tangle_db_trade_product_delete, - tangle_db_trade_product_find_many, - tangle_db_trade_product_find_one, - tangle_db_trade_product_update, - tangle_db_nostr_profile_relay_set, - tangle_db_nostr_profile_relay_unset, - tangle_db_trade_product_location_set, - tangle_db_trade_product_location_unset, - tangle_db_trade_product_media_set, - tangle_db_trade_product_media_unset, - tangle_db_reset_database, - tangle_db_run_migrations, - tangle_db_export_begin, - tangle_db_export_finish, - tangle_db_export_json, - tangle_db_import_json -} from "@radroots/tangle-db-wasm"; -import init_tangle_events_wasm, { - tangle_events_ingest_event, - tangle_events_sync_all -} from "@radroots/tangle-events-wasm"; -import { - nostr_context_create, - nostr_event_sign, - nostr_public_key_from_secret, - nostr_publish, - nostr_relays_clear, - nostr_relays_open, - type NostrContext -} from "@radroots/nostr"; -import type { IError } from "@radroots/types-bindings"; -import { err_msg, handle_err, type IdbClientConfig } from "@radroots/utils"; -import { IDB_CONFIG_TANGLE } from "../idb/config.js"; -import type { SqlJsMigrationRow, SqlJsMigrationState, WebSqlEngineConfig } from "../sql/types.js"; -import { WebSqlEngine } from "../sql/web.js"; -import { radroots_sql_install_bridges } from "./bridge.js"; -import { cl_tangle_error } from "./error.js"; -import type { IWebTangleDatabase } from "./types.js"; - -export type TangleDatabaseSchemaEntry = { - object_type: string; - name: string; - table_name?: string; - sql?: string; -}; - -export type TangleDatabaseMigrationEntry = { - name: string; - up_sql: string; - down_sql: string; -}; - -export type TangleDatabaseJsonExport = { - format_version: string; - tangle_db_version: string; - schema: TangleDatabaseSchemaEntry[]; - data: { - name: string; - rows: Record<string, unknown>[]; - }[]; - migrations: TangleDatabaseMigrationEntry[]; -}; - -export type TangleDatabaseExportManifestRs = { - export_version: string; - tangle_db_version: string; - backup_format_version: string; - schema_hash: string; - schema: TangleDatabaseSchemaEntry[]; - migrations: TangleDatabaseMigrationEntry[]; - table_counts: { - name: string; - row_count: number; - }[]; -}; - -export type NostrEventEnvelope = { - id: string; - pubkey: string; - created_at: number; - kind: number; - tags: string[][]; - content: string; - sig: string; -}; - -export type TangleDatabaseExportManifestTs = { - app_name: string; - app_version: string; - exported_at: string; - db_sha256: string; - db_size_bytes: number; - store_key: string; - nostr_event?: NostrEventEnvelope; -}; - -export type TangleDatabaseExportManifest = { - rust: TangleDatabaseExportManifestRs; - client: TangleDatabaseExportManifestTs; -}; - -export type TangleDatabaseExportSnapshot = { - manifest_rs: TangleDatabaseExportManifestRs; - db_bytes: Uint8Array; -}; - -export type TangleDatabaseExportSignRequest = { - db_sha256: string; - manifest: TangleDatabaseExportManifest; -}; - -export type TangleDatabaseExportSigner = (opts: TangleDatabaseExportSignRequest) => Promise<NostrEventEnvelope | null>; - -export type TangleDatabaseExportOptions = { - app_name: string; - app_version: string; - store_key?: string; - signer?: TangleDatabaseExportSigner; -}; - -export type TangleNostrSyncSigner = { - secret_key: string; -}; - -export type TangleNostrEventDraft = { - kind: number; - author: string; - content: string; - tags: string[][]; -}; - -export type TangleNostrSyncBundle = { - version: number; - events: TangleNostrEventDraft[]; -}; - -export type TangleNostrSyncOptions = { - relays: string[]; - signers: TangleNostrSyncSigner[]; - publish_timeout_ms?: number; - context?: NostrContext; -}; - -export type TangleNostrSyncSummary = { - events_total: number; - events_published: number; - events_failed: number; - events_skipped: number; - missing_signers: string[]; -}; - -export type WebTangleDatabaseConfig = { - store_key?: string; - idb_config?: IdbClientConfig; - cipher_config?: IdbClientConfig | null; - sql_wasm_path?: string; -}; - -const is_record = (value: unknown): value is Record<string, unknown> => - typeof value === "object" && value !== null && !Array.isArray(value); - -const is_sql_migration_row = (value: unknown): value is SqlJsMigrationRow => { - if (!is_record(value)) return false; - return typeof value.id === "number" - && Number.isFinite(value.id) - && typeof value.name === "string" - && typeof value.applied_at === "string"; -}; - -const is_sql_migration_row_list = (value: unknown): value is SqlJsMigrationRow[] => - Array.isArray(value) && value.every(is_sql_migration_row); - -const is_schema_entry = (value: unknown): value is TangleDatabaseSchemaEntry => { - if (!is_record(value)) return false; - if (typeof value.object_type !== "string") return false; - if (typeof value.name !== "string") return false; - if ("table_name" in value && typeof value.table_name !== "undefined" && typeof value.table_name !== "string") return false; - if ("sql" in value && typeof value.sql !== "undefined" && typeof value.sql !== "string") return false; - return true; -}; - -const is_json_export_data_entry = (value: unknown): value is TangleDatabaseJsonExport["data"][number] => { - if (!is_record(value)) return false; - if (typeof value.name !== "string") return false; - if (!Array.isArray(value.rows)) return false; - if (!value.rows.every(is_record)) return false; - return true; -}; - -const is_migration_entry = (value: unknown): value is TangleDatabaseMigrationEntry => { - if (!is_record(value)) return false; - return typeof value.name === "string" - && typeof value.up_sql === "string" - && typeof value.down_sql === "string"; -}; - -const is_table_count_entry = (value: unknown): value is TangleDatabaseExportManifestRs["table_counts"][number] => { - if (!is_record(value)) return false; - if (typeof value.name !== "string") return false; - if (typeof value.row_count !== "number" || !Number.isFinite(value.row_count)) return false; - return true; -}; - -const is_tangle_database_json_export = (value: unknown): value is TangleDatabaseJsonExport => { - if (!is_record(value)) return false; - if (typeof value.format_version !== "string") return false; - if (typeof value.tangle_db_version !== "string") return false; - if (!Array.isArray(value.schema) || !value.schema.every(is_schema_entry)) return false; - if (!Array.isArray(value.data) || !value.data.every(is_json_export_data_entry)) return false; - if (!Array.isArray(value.migrations) || !value.migrations.every(is_migration_entry)) return false; - return true; -}; - -const is_export_manifest_rs = (value: unknown): value is TangleDatabaseExportManifestRs => { - if (!is_record(value)) return false; - if (typeof value.export_version !== "string") return false; - if (typeof value.tangle_db_version !== "string") return false; - if (typeof value.backup_format_version !== "string") return false; - if (typeof value.schema_hash !== "string") return false; - if (!Array.isArray(value.schema) || !value.schema.every(is_schema_entry)) return false; - if (!Array.isArray(value.migrations) || !value.migrations.every(is_migration_entry)) return false; - if (!Array.isArray(value.table_counts) || !value.table_counts.every(is_table_count_entry)) return false; - return true; -}; - -const is_export_snapshot = (value: unknown): value is TangleDatabaseExportSnapshot => { - if (!is_record(value)) return false; - if (!("manifest_rs" in value) || !is_export_manifest_rs(value.manifest_rs)) return false; - if (!("db_bytes" in value) || !(value.db_bytes instanceof Uint8Array)) return false; - return true; -}; - -const is_string_list = (value: unknown): value is string[] => - Array.isArray(value) && value.every((item) => typeof item === "string"); - -const is_tag_list = (value: unknown): value is string[][] => - Array.isArray(value) && value.every(is_string_list); - -const is_tangle_nostr_event_draft = (value: unknown): value is TangleNostrEventDraft => { - if (!is_record(value)) return false; - if (typeof value.kind !== "number" || !Number.isFinite(value.kind)) return false; - if (typeof value.author !== "string") return false; - if (typeof value.content !== "string") return false; - if (!is_tag_list(value.tags)) return false; - return true; -}; - -const is_tangle_nostr_sync_bundle = (value: unknown): value is TangleNostrSyncBundle => { - if (!is_record(value)) return false; - if (typeof value.version !== "number" || !Number.isFinite(value.version)) return false; - if (!Array.isArray(value.events) || !value.events.every(is_tangle_nostr_event_draft)) return false; - return true; -}; - -const parse_tangle_nostr_sync_bundle = (value: unknown): TangleNostrSyncBundle | IError<string> => { - let parsed: unknown = value; - if (typeof value === "string") { - try { - parsed = JSON.parse(value); - } catch { - return err_msg(cl_tangle_error.parse_failure); - } - } - if (!is_tangle_nostr_sync_bundle(parsed)) return err_msg(cl_tangle_error.invalid_response); - return parsed; -}; - -const is_ingest_outcome = (value: unknown): value is "applied" | "skipped" => - value === "applied" || value === "skipped"; - -const tangle_sync_event_d_tag = (tags: string[][]): string => { - const match = tags.find((tag) => tag[0] === "d"); - const value = match?.[1]; - return typeof value === "string" ? value : ""; -}; - -const tangle_sync_event_key = (draft: TangleNostrEventDraft): string => - `${draft.kind}:${draft.author}:${tangle_sync_event_d_tag(draft.tags)}`; - -const build_signer_map = (signers: TangleNostrSyncSigner[]): Record<string, string> => { - const map: Record<string, string> = {}; - for (const signer of signers) { - const secret_key = signer.secret_key; - if (!secret_key || typeof secret_key !== "string") continue; - const pubkey = nostr_public_key_from_secret(secret_key); - map[pubkey] = secret_key; - } - return map; -}; - -const publish_results_has_success = (results: Record<string, { status?: string }>): boolean => - Object.values(results).some((result) => result.status === "success"); - -type ZipEntry = { - name: string; - data: Uint8Array; -}; - -type ZipEntryPrepared = { - name_bytes: Uint8Array; - data: Uint8Array; - crc32: number; - size: number; -}; - -type ZipFilePickerOptions = { - suggestedName?: string; - types?: { - description?: string; - accept: Record<string, string[]>; - }[]; -}; - -type ZipFileHandle = { - createWritable(): Promise<ZipFileWritable>; -}; - -type ZipFileWritable = { - write(data: Uint8Array): Promise<void>; - close(): Promise<void>; -}; - -type ZipFilePicker = (options?: ZipFilePickerOptions) => Promise<ZipFileHandle>; - -const ZIP_CRC_TABLE = (() => { - const table = new Uint32Array(256); - for (let i = 0; i < 256; i++) { - let c = i; - for (let k = 0; k < 8; k++) { - if (c & 1) c = 0xedb88320 ^ (c >>> 1); - else c >>>= 1; - } - table[i] = c >>> 0; - } - return table; -})(); - -const crc32 = (data: Uint8Array): number => { - let crc = 0xffffffff; - for (let i = 0; i < data.length; i++) { - const idx = (crc ^ data[i]) & 0xff; - crc = ZIP_CRC_TABLE[idx] ^ (crc >>> 8); - } - return (crc ^ 0xffffffff) >>> 0; -}; - -const zip_prepare_entries = (entries: ZipEntry[]): ZipEntryPrepared[] => { - const enc = new TextEncoder(); - return entries.map((entry) => ({ - name_bytes: enc.encode(entry.name), - data: entry.data, - crc32: crc32(entry.data), - size: entry.data.length - })); -}; - -const zip_dos_time = (date: Date): { time: number; date: number } => { - const year = Math.max(1980, date.getFullYear()); - const month = date.getMonth() + 1; - const day = date.getDate(); - const hours = date.getHours(); - const minutes = date.getMinutes(); - const seconds = Math.floor(date.getSeconds() / 2); - const time = (hours << 11) | (minutes << 5) | seconds; - const date_val = ((year - 1980) << 9) | (month << 5) | day; - return { time, date: date_val }; -}; - -const zip_local_header = (entry: ZipEntryPrepared, time: number, date: number): Uint8Array => { - const name_len = entry.name_bytes.length; - const buffer = new ArrayBuffer(30 + name_len); - const view = new DataView(buffer); - view.setUint32(0, 0x04034b50, true); - view.setUint16(4, 20, true); - view.setUint16(6, 0, true); - view.setUint16(8, 0, true); - view.setUint16(10, time, true); - view.setUint16(12, date, true); - view.setUint32(14, entry.crc32, true); - view.setUint32(18, entry.size, true); - view.setUint32(22, entry.size, true); - view.setUint16(26, name_len, true); - view.setUint16(28, 0, true); - const out = new Uint8Array(buffer); - out.set(entry.name_bytes, 30); - return out; -}; - -const zip_central_header = ( - entry: ZipEntryPrepared, - time: number, - date: number, - offset: number -): Uint8Array => { - const name_len = entry.name_bytes.length; - const buffer = new ArrayBuffer(46 + name_len); - const view = new DataView(buffer); - view.setUint32(0, 0x02014b50, true); - view.setUint16(4, 20, true); - view.setUint16(6, 20, true); - view.setUint16(8, 0, true); - view.setUint16(10, 0, true); - view.setUint16(12, time, true); - view.setUint16(14, date, true); - view.setUint32(16, entry.crc32, true); - view.setUint32(20, entry.size, true); - view.setUint32(24, entry.size, true); - view.setUint16(28, name_len, true); - view.setUint16(30, 0, true); - view.setUint16(32, 0, true); - view.setUint16(34, 0, true); - view.setUint16(36, 0, true); - view.setUint32(38, 0, true); - view.setUint32(42, offset, true); - const out = new Uint8Array(buffer); - out.set(entry.name_bytes, 46); - return out; -}; - -const zip_end_record = (entry_count: number, central_size: number, central_offset: number): Uint8Array => { - const buffer = new ArrayBuffer(22); - const view = new DataView(buffer); - view.setUint32(0, 0x06054b50, true); - view.setUint16(4, 0, true); - view.setUint16(6, 0, true); - view.setUint16(8, entry_count, true); - view.setUint16(10, entry_count, true); - view.setUint32(12, central_size, true); - view.setUint32(16, central_offset, true); - view.setUint16(20, 0, true); - return new Uint8Array(buffer); -}; - -const zip_build_bytes = (entries: ZipEntry[]): Uint8Array => { - const prepared = zip_prepare_entries(entries); - const { time, date } = zip_dos_time(new Date()); - const local_parts: Uint8Array[] = []; - const central_parts: Uint8Array[] = []; - let offset = 0; - - for (const entry of prepared) { - const local = zip_local_header(entry, time, date); - local_parts.push(local, entry.data); - const central = zip_central_header(entry, time, date, offset); - central_parts.push(central); - offset += local.length + entry.data.length; - } - - let central_size = 0; - for (const part of central_parts) central_size += part.length; - const end = zip_end_record(prepared.length, central_size, offset); - const total = offset + central_size + end.length; - const out = new Uint8Array(total); - let cursor = 0; - for (const part of local_parts) { - out.set(part, cursor); - cursor += part.length; - } - for (const part of central_parts) { - out.set(part, cursor); - cursor += part.length; - } - out.set(end, cursor); - return out; -}; - -const zip_write_stream = async (stream: ZipFileWritable, entries: ZipEntry[]): Promise<void> => { - const prepared = zip_prepare_entries(entries); - const { time, date } = zip_dos_time(new Date()); - const central_parts: Uint8Array[] = []; - let offset = 0; - - for (const entry of prepared) { - const local = zip_local_header(entry, time, date); - await stream.write(local); - await stream.write(entry.data); - central_parts.push(zip_central_header(entry, time, date, offset)); - offset += local.length + entry.data.length; - } - - let central_size = 0; - for (const part of central_parts) central_size += part.length; - for (const part of central_parts) await stream.write(part); - const end = zip_end_record(prepared.length, central_size, offset); - await stream.write(end); - await stream.close(); -}; - -type TarEntry = { - name: string; - data: Uint8Array; -}; - -const TAR_BLOCK_SIZE = 512; - -const tar_pad_size = (size: number): number => { - const rem = size % TAR_BLOCK_SIZE; - return rem === 0 ? 0 : TAR_BLOCK_SIZE - rem; -}; - -const tar_write_string = (buf: Uint8Array, offset: number, length: number, value: string): void => { - const enc = new TextEncoder(); - const bytes = enc.encode(value); - const slice = bytes.length > length ? bytes.slice(0, length) : bytes; - buf.set(slice, offset); -}; - -const tar_write_octal = (buf: Uint8Array, offset: number, length: number, value: number): void => { - const str = value.toString(8).padStart(length - 1, "0"); - tar_write_string(buf, offset, length - 1, str); - buf[offset + length - 1] = 0; -}; - -const tar_header = (entry: TarEntry, mtime: number): Uint8Array => { - if (entry.name.length > 100) throw new Error("tar entry name too long"); - const header = new Uint8Array(TAR_BLOCK_SIZE); - tar_write_string(header, 0, 100, entry.name); - tar_write_octal(header, 100, 8, 0o644); - tar_write_octal(header, 108, 8, 0); - tar_write_octal(header, 116, 8, 0); - tar_write_octal(header, 124, 12, entry.data.length); - tar_write_octal(header, 136, 12, mtime); - for (let i = 148; i < 156; i++) header[i] = 32; - header[156] = 48; - tar_write_string(header, 257, 6, "ustar"); - tar_write_string(header, 263, 2, "00"); - let checksum = 0; - for (let i = 0; i < header.length; i++) checksum += header[i]; - const chk = checksum.toString(8).padStart(6, "0"); - tar_write_string(header, 148, 6, chk); - header[154] = 0; - header[155] = 32; - return header; -}; - -const tar_build_bytes = (entries: TarEntry[], mtime: number): Uint8Array => { - const parts: Uint8Array[] = []; - let total = 0; - for (const entry of entries) { - const header = tar_header(entry, mtime); - const pad = tar_pad_size(entry.data.length); - parts.push(header, entry.data); - total += header.length + entry.data.length; - if (pad) { - const padding = new Uint8Array(pad); - parts.push(padding); - total += padding.length; - } - } - const end = new Uint8Array(TAR_BLOCK_SIZE * 2); - parts.push(end); - total += end.length; - const out = new Uint8Array(total); - let offset = 0; - for (const part of parts) { - out.set(part, offset); - offset += part.length; - } - return out; -}; - -const tar_stream = (entries: TarEntry[], mtime: number): ReadableStream<Uint8Array> => { - return new ReadableStream({ - start(controller) { - for (const entry of entries) { - const header = tar_header(entry, mtime); - controller.enqueue(header); - controller.enqueue(entry.data); - const pad = tar_pad_size(entry.data.length); - if (pad) controller.enqueue(new Uint8Array(pad)); - } - controller.enqueue(new Uint8Array(TAR_BLOCK_SIZE * 2)); - controller.close(); - } - }); -}; - -const gzip_bytes = async (bytes: Uint8Array): Promise<Uint8Array> => { - if (typeof CompressionStream === "undefined") { - throw new Error("tangle export requires gzip support"); - } - const stream = new CompressionStream("gzip"); - const writer = stream.writable.getWriter(); - writer.write(bytes); - await writer.close(); - const buffer = await new Response(stream.readable).arrayBuffer(); - return new Uint8Array(buffer); -}; - -const bytes_to_hex = (bytes: Uint8Array): string => { - const hex: string[] = []; - for (let i = 0; i < bytes.length; i++) { - hex.push(bytes[i].toString(16).padStart(2, "0")); - } - return hex.join(""); -}; - -const sha256_hex = async (bytes: Uint8Array): Promise<string> => { - if (!globalThis.crypto || !globalThis.crypto.subtle) throw new Error(cl_tangle_error.crypto_unavailable); - const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes); - return bytes_to_hex(new Uint8Array(digest)); -}; - -const filename_slug = (value: string): string => { - const slug = value - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-+|-+$/g, ""); - if (slug.startsWith("radroots-")) return slug; - return `radroots-${slug}`; -}; - -const export_filename = (app_name: string, app_version: string): string => { - const base = filename_slug(app_name); - return `${base}-${app_version}-backup.tar.gz`; -}; - -const get_zip_file_picker = (): ZipFilePicker | undefined => { - if (typeof window === "undefined") return undefined; - const picker = (window as Window & { showSaveFilePicker?: ZipFilePicker }).showSaveFilePicker; - return picker; -}; - -const user_activation_is_active = (): boolean => { - if (typeof navigator === "undefined") return false; - const nav = navigator as Navigator & { userActivation?: { isActive?: boolean } }; - if (!nav.userActivation) return true; - return nav.userActivation.isActive === true; -}; - -const is_permission_error = (err: unknown): boolean => { - if (!err) return false; - if (typeof err === "string") { - const msg = err.toLowerCase(); - return msg.includes("permission") || msg.includes("denied") || msg.includes("not allowed"); - } - if (!is_record(err)) return false; - const name = typeof err.name === "string" ? err.name.toLowerCase() : ""; - const message = typeof err.message === "string" ? err.message.toLowerCase() : ""; - if (name.includes("notallowed") || name.includes("abort")) return true; - return message.includes("permission") || message.includes("denied") || message.includes("not allowed"); -}; - -const can_share_file = (file: File): boolean => { - if (typeof navigator === "undefined") return false; - const nav = navigator as Navigator & { canShare?: (data: { files?: File[] }) => boolean }; - if (!nav.canShare) return false; - return nav.canShare({ files: [file] }); -}; - -const share_file = async (file: File): Promise<boolean> => { - if (typeof navigator === "undefined") return false; - const nav = navigator as Navigator & { share?: (data: { files?: File[]; title?: string }) => Promise<void> }; - if (!nav.share) return false; - await nav.share({ files: [file], title: file.name }); - return true; -}; - -const download_blob = (blob: Blob, filename: string): void => { - if (typeof document === "undefined") return; - const url = URL.createObjectURL(blob); - const anchor = document.createElement("a"); - anchor.href = url; - anchor.download = filename; - anchor.click(); - URL.revokeObjectURL(url); -}; - -const export_tar_gz = async (filename: string, entries: TarEntry[], mtime: number): Promise<void> => { - const picker = get_zip_file_picker(); - if (picker && user_activation_is_active() && typeof CompressionStream !== "undefined") { - try { - const handle = await picker({ - suggestedName: filename, - types: [ - { - description: "Radroots tangle export", - accept: { "application/gzip": [".tar.gz"] } - } - ] - }); - const stream = await handle.createWritable(); - const writable_stream = new WritableStream<Uint8Array>({ - write: (chunk) => stream.write(chunk), - close: () => stream.close() - }); - await tar_stream(entries, mtime) - .pipeThrough(new CompressionStream("gzip")) - .pipeTo(writable_stream); - return; - } catch (e) { - if (!is_permission_error(e)) throw e; - } - } - const tar_bytes = tar_build_bytes(entries, mtime); - const gz_bytes = await gzip_bytes(tar_bytes); - const blob = new Blob([gz_bytes], { type: "application/gzip" }); - const file = new File([blob], filename, { type: "application/gzip" }); - if (can_share_file(file) && user_activation_is_active()) { - try { - const shared = await share_file(file); - if (shared) return; - } catch (e) { - if (!is_permission_error(e)) throw e; - } - } - download_blob(blob, filename); -}; - -const DEFAULT_TANGLE_STORE_KEY = "radroots-pwa-v1-tangle-db"; -const DEFAULT_TANGLE_IDB_CONFIG: IdbClientConfig = IDB_CONFIG_TANGLE; -let wasm_init_promise: Promise<void> | null = null; - -const runtime_available = (): boolean => { - return typeof window !== "undefined" || typeof self !== "undefined"; -}; - -const wasm_init_once = async (): Promise<void> => { - if (!wasm_init_promise) { - wasm_init_promise = (async () => { - await init_wasm(); - await init_tangle_events_wasm(); - })(); - } - try { - await wasm_init_promise; - } catch (e) { - wasm_init_promise = null; - throw e; - } -}; - -export class WebTangleDatabase implements IWebTangleDatabase { - private engine: WebSqlEngine | null = null; - private readonly store_key: string; - private readonly idb_config: IdbClientConfig; - private readonly cipher_config: IdbClientConfig | null; - private readonly sql_wasm_path: string | undefined; - private init_promise: Promise<void> | null = null; - - constructor(config?: WebTangleDatabaseConfig) { - this.store_key = config?.store_key ?? DEFAULT_TANGLE_STORE_KEY; - this.idb_config = config?.idb_config ?? DEFAULT_TANGLE_IDB_CONFIG; - this.cipher_config = config?.cipher_config ?? null; - this.sql_wasm_path = config?.sql_wasm_path; - } - - get_store_key(): string { - return this.store_key; - } - - private serialize<T>(opts: T): string { - return JSON.stringify(opts); - } - - private deserialize<T>(data: string): T | IError<string> { - try { - return JSON.parse(data); - } catch { - return err_msg(cl_tangle_error.parse_failure); - } - } - - private get_engine_config(): WebSqlEngineConfig { - return { - store_key: this.store_key, - idb_config: this.idb_config, - cipher_config: this.cipher_config, - sql_wasm_path: this.sql_wasm_path - }; - } - - private async ensure_ready(): Promise<void> { - await this.init(); - if (!this.engine) throw new Error(cl_tangle_error.init_failure); - } - - async init(): Promise<void> { - if (this.engine) return; - if (!runtime_available()) throw new Error(cl_tangle_error.runtime_unavailable); - if (!this.init_promise) { - this.init_promise = (async () => { - await wasm_init_once(); - this.engine = await WebSqlEngine.create(this.get_engine_config()); - radroots_sql_install_bridges(this.engine); - tangle_db_run_migrations(); - })(); - } - try { - await this.init_promise; - } catch (e) { - this.engine = null; - this.init_promise = null; - throw e; - } - } - - async close(): Promise<void> { - if (this.engine) await this.engine.close(); - this.engine = null; - this.init_promise = null; - } - - async migration_state(): Promise<SqlJsMigrationState | IError<string>> { - try { - await this.ensure_ready(); - const res = await query_sql("select id, name, applied_at from __migrations order by id asc", "[]"); - let parsed: unknown = res; - if (typeof res === "string") { - try { - parsed = JSON.parse(res); - } catch { - return err_msg(cl_tangle_error.parse_failure); - } - } - if (!is_sql_migration_row_list(parsed)) return err_msg(cl_tangle_error.invalid_response); - const names = parsed.map((row) => row.name); - return { applied_names: names, applied_count: names.length }; - } catch (e) { - return handle_err(e); - } - } - - async reset(): Promise<SqlJsMigrationState | IError<string>> { - try { - await this.ensure_ready(); - tangle_db_reset_database(); - tangle_db_run_migrations(); - return this.migration_state(); - } catch (e) { - return handle_err(e); - } - } - - async reinit(): Promise<SqlJsMigrationState | IError<string>> { - try { - await this.ensure_ready(); - if (this.engine) { - await this.engine.purge_storage(); - await this.engine.close(); - } - this.engine = await WebSqlEngine.create(this.get_engine_config()); - radroots_sql_install_bridges(this.engine); - tangle_db_run_migrations(); - return this.migration_state(); - } catch (e) { - return handle_err(e); - } - } - - async export_json(): Promise<TangleDatabaseJsonExport | IError<string>> { - try { - await this.ensure_ready(); - const res = await tangle_db_export_json(); - let parsed: unknown = res; - if (typeof res === "string") { - try { - parsed = JSON.parse(res); - } catch { - return err_msg(cl_tangle_error.parse_failure); - } - } - if (!is_tangle_database_json_export(parsed)) return err_msg(cl_tangle_error.invalid_response); - return parsed; - } catch (e) { - return handle_err(e); - } - } - - async import_json(backup: TangleDatabaseJsonExport): Promise<void | IError<string>> { - try { - await this.ensure_ready(); - tangle_db_import_json(this.serialize(backup)); - return; - } catch (e) { - return handle_err(e); - } - } - - async export_database(opts: TangleDatabaseExportOptions): Promise<void | IError<string>> { - try { - if (opts.store_key && opts.store_key !== this.store_key) { - const alt_db = new WebTangleDatabase({ - store_key: opts.store_key, - idb_config: this.idb_config, - cipher_config: this.cipher_config, - sql_wasm_path: this.sql_wasm_path - }); - const res = await alt_db.export_database(opts); - await alt_db.close(); - return res; - } - await this.export_database_inner(opts); - return; - } catch (e) { - return handle_err(e); - } - } - - async nostr_sync_all(opts: TangleNostrSyncOptions): Promise<TangleNostrSyncSummary | IError<string>> { - try { - await this.ensure_ready(); - const relays = Array.from(new Set(opts.relays.map((relay) => relay.trim()).filter((relay) => relay.length))); - if (!relays.length) return err_msg(`tangle sync requires relays`); - if (!opts.signers.length) return err_msg(`tangle sync requires signers`); - const signer_map = build_signer_map(opts.signers); - if (!Object.keys(signer_map).length) return err_msg(`tangle sync requires valid signers`); - - const farms = await this.farm_find_many(); - if ("err" in farms) return farms; - const event_map: Record<string, TangleNostrEventDraft> = {}; - for (const farm of farms.results) { - const bundle_raw = tangle_events_sync_all(this.serialize({ - farm: { id: farm.id }, - options: null - })); - const bundle = parse_tangle_nostr_sync_bundle(bundle_raw); - if ("err" in bundle) return bundle; - for (const draft of bundle.events) { - const key = tangle_sync_event_key(draft); - if (!event_map[key]) event_map[key] = draft; - } - } - - const event_keys = Object.keys(event_map); - event_keys.sort(); - if (!event_keys.length) { - return { - events_total: 0, - events_published: 0, - events_failed: 0, - events_skipped: 0, - missing_signers: [] - }; - } - - const context = opts.context ?? nostr_context_create(); - const context_owned = !opts.context; - let events_published = 0; - let events_failed = 0; - let events_skipped = 0; - const missing_signers = new Set<string>(); - - try { - nostr_relays_open(context, relays); - for (const key of event_keys) { - const draft = event_map[key]; - const secret_key = signer_map[draft.author]; - if (!secret_key) { - missing_signers.add(draft.author); - events_skipped += 1; - continue; - } - const event = nostr_event_sign({ - secret_key, - event: { - kind: draft.kind, - created_at: Math.floor(Date.now() / 1000), - tags: draft.tags, - content: draft.content - } - }); - const publish_results = await nostr_publish({ - event, - relays, - context, - timeout: opts.publish_timeout_ms - }); - if (!publish_results_has_success(publish_results)) { - events_failed += 1; - continue; - } - const ingest_result = tangle_events_ingest_event(this.serialize(event)); - if (!is_ingest_outcome(ingest_result)) { - events_failed += 1; - continue; - } - events_published += 1; - } - } finally { - if (context_owned) nostr_relays_clear(context); - } - - const summary: TangleNostrSyncSummary = { - events_total: event_keys.length, - events_published, - events_failed, - events_skipped, - missing_signers: Array.from(missing_signers) - }; - if (summary.missing_signers.length) return err_msg(`tangle sync missing signers: ${summary.missing_signers.join(", ")}`); - if (summary.events_failed) return err_msg(`tangle sync publish failed (${summary.events_failed}/${summary.events_total})`); - return summary; - } catch (e) { - return handle_err(e); - } - } - - private async export_database_inner(opts: TangleDatabaseExportOptions): Promise<void> { - await this.ensure_ready(); - const app_name = opts.app_name; - const app_version = opts.app_version; - const store_key = this.store_key; - let export_active = false; - - try { - const snapshot_raw = await tangle_db_export_begin(); - export_active = true; - if (!is_export_snapshot(snapshot_raw)) throw new Error(cl_tangle_error.invalid_response); - const manifest_rs = snapshot_raw.manifest_rs; - const db_bytes = snapshot_raw.db_bytes; - const db_sha256 = await sha256_hex(db_bytes); - const exported_at = new Date().toISOString(); - - const manifest_ts_base: TangleDatabaseExportManifestTs = { - app_name, - app_version, - exported_at, - db_sha256, - db_size_bytes: db_bytes.byteLength, - store_key - }; - - let manifest: TangleDatabaseExportManifest = { - rust: manifest_rs, - client: manifest_ts_base - }; - - if (opts.signer) { - const nostr_event = await opts.signer({ db_sha256, manifest }); - if (nostr_event) { - manifest = { - rust: manifest_rs, - client: { - ...manifest_ts_base, - nostr_event - } - }; - } - } - - const manifest_json = JSON.stringify(manifest, null, 2); - const manifest_bytes = new TextEncoder().encode(manifest_json); - const filename = export_filename(app_name, app_version); - const mtime = Math.floor(Date.parse(exported_at) / 1000); - await export_tar_gz(filename, [ - { name: "manifest.json", data: manifest_bytes }, - { name: "tangle.db", data: db_bytes } - ], mtime); - } finally { - if (export_active) tangle_db_export_finish(); - } - } - - async farm_create(opts: IFarmCreate): Promise<IFarmCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_create(this.serialize(opts)); - return this.deserialize<IFarmCreateResolve>(res); - } - - async farm_find_one(opts: IFarmFindOne): Promise<IFarmFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_find_one(this.serialize(opts)); - return this.deserialize<IFarmFindOneResolve>(res); - } - - async farm_find_many(opts?: IFarmFindMany): Promise<IFarmFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_find_many(this.serialize(opts ?? {})); - return this.deserialize<IFarmFindManyResolve>(res); - } - - async farm_delete(opts: IFarmDelete): Promise<IFarmDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_delete(this.serialize(opts)); - return this.deserialize<IFarmDeleteResolve>(res); - } - - async farm_update(opts: IFarmUpdate): Promise<IFarmUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_update(this.serialize(opts)); - return this.deserialize<IFarmUpdateResolve>(res); - } - - async plot_create(opts: IPlotCreate): Promise<IPlotCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_create(this.serialize(opts)); - return this.deserialize<IPlotCreateResolve>(res); - } - - async plot_find_one(opts: IPlotFindOne): Promise<IPlotFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_find_one(this.serialize(opts)); - return this.deserialize<IPlotFindOneResolve>(res); - } - - async plot_find_many(opts?: IPlotFindMany): Promise<IPlotFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_find_many(this.serialize(opts ?? {})); - return this.deserialize<IPlotFindManyResolve>(res); - } - - async plot_delete(opts: IPlotDelete): Promise<IPlotDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_delete(this.serialize(opts)); - return this.deserialize<IPlotDeleteResolve>(res); - } - - async plot_update(opts: IPlotUpdate): Promise<IPlotUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_update(this.serialize(opts)); - return this.deserialize<IPlotUpdateResolve>(res); - } - - async gcs_location_create(opts: IGcsLocationCreate): Promise<IGcsLocationCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_gcs_location_create(this.serialize(opts)); - return this.deserialize<IGcsLocationCreateResolve>(res); - } - - async gcs_location_find_one(opts: IGcsLocationFindOne): Promise<IGcsLocationFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_gcs_location_find_one(this.serialize(opts)); - return this.deserialize<IGcsLocationFindOneResolve>(res); - } - - async gcs_location_find_many(opts?: IGcsLocationFindMany): Promise<IGcsLocationFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_gcs_location_find_many(this.serialize(opts ?? {})); - return this.deserialize<IGcsLocationFindManyResolve>(res); - } - - async gcs_location_delete(opts: IGcsLocationDelete): Promise<IGcsLocationDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_gcs_location_delete(this.serialize(opts)); - return this.deserialize<IGcsLocationDeleteResolve>(res); - } - - async gcs_location_update(opts: IGcsLocationUpdate): Promise<IGcsLocationUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_gcs_location_update(this.serialize(opts)); - return this.deserialize<IGcsLocationUpdateResolve>(res); - } - - async farm_gcs_location_create(opts: IFarmGcsLocationCreate): Promise<IFarmGcsLocationCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_gcs_location_create(this.serialize(opts)); - return this.deserialize<IFarmGcsLocationCreateResolve>(res); - } - - async farm_gcs_location_find_one(opts: IFarmGcsLocationFindOne): Promise<IFarmGcsLocationFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_gcs_location_find_one(this.serialize(opts)); - return this.deserialize<IFarmGcsLocationFindOneResolve>(res); - } - - async farm_gcs_location_find_many(opts?: IFarmGcsLocationFindMany): Promise<IFarmGcsLocationFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_gcs_location_find_many(this.serialize(opts ?? {})); - return this.deserialize<IFarmGcsLocationFindManyResolve>(res); - } - - async farm_gcs_location_delete(opts: IFarmGcsLocationDelete): Promise<IFarmGcsLocationDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_gcs_location_delete(this.serialize(opts)); - return this.deserialize<IFarmGcsLocationDeleteResolve>(res); - } - - async farm_gcs_location_update(opts: IFarmGcsLocationUpdate): Promise<IFarmGcsLocationUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_gcs_location_update(this.serialize(opts)); - return this.deserialize<IFarmGcsLocationUpdateResolve>(res); - } - - async plot_gcs_location_create(opts: IPlotGcsLocationCreate): Promise<IPlotGcsLocationCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_gcs_location_create(this.serialize(opts)); - return this.deserialize<IPlotGcsLocationCreateResolve>(res); - } - - async plot_gcs_location_find_one(opts: IPlotGcsLocationFindOne): Promise<IPlotGcsLocationFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_gcs_location_find_one(this.serialize(opts)); - return this.deserialize<IPlotGcsLocationFindOneResolve>(res); - } - - async plot_gcs_location_find_many(opts?: IPlotGcsLocationFindMany): Promise<IPlotGcsLocationFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_gcs_location_find_many(this.serialize(opts ?? {})); - return this.deserialize<IPlotGcsLocationFindManyResolve>(res); - } - - async plot_gcs_location_delete(opts: IPlotGcsLocationDelete): Promise<IPlotGcsLocationDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_gcs_location_delete(this.serialize(opts)); - return this.deserialize<IPlotGcsLocationDeleteResolve>(res); - } - - async plot_gcs_location_update(opts: IPlotGcsLocationUpdate): Promise<IPlotGcsLocationUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_gcs_location_update(this.serialize(opts)); - return this.deserialize<IPlotGcsLocationUpdateResolve>(res); - } - - async farm_tag_create(opts: IFarmTagCreate): Promise<IFarmTagCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_tag_create(this.serialize(opts)); - return this.deserialize<IFarmTagCreateResolve>(res); - } - - async farm_tag_find_one(opts: IFarmTagFindOne): Promise<IFarmTagFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_tag_find_one(this.serialize(opts)); - return this.deserialize<IFarmTagFindOneResolve>(res); - } - - async farm_tag_find_many(opts?: IFarmTagFindMany): Promise<IFarmTagFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_tag_find_many(this.serialize(opts ?? {})); - return this.deserialize<IFarmTagFindManyResolve>(res); - } - - async farm_tag_delete(opts: IFarmTagDelete): Promise<IFarmTagDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_tag_delete(this.serialize(opts)); - return this.deserialize<IFarmTagDeleteResolve>(res); - } - - async farm_tag_update(opts: IFarmTagUpdate): Promise<IFarmTagUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_tag_update(this.serialize(opts)); - return this.deserialize<IFarmTagUpdateResolve>(res); - } - - async plot_tag_create(opts: IPlotTagCreate): Promise<IPlotTagCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_tag_create(this.serialize(opts)); - return this.deserialize<IPlotTagCreateResolve>(res); - } - - async plot_tag_find_one(opts: IPlotTagFindOne): Promise<IPlotTagFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_tag_find_one(this.serialize(opts)); - return this.deserialize<IPlotTagFindOneResolve>(res); - } - - async plot_tag_find_many(opts?: IPlotTagFindMany): Promise<IPlotTagFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_tag_find_many(this.serialize(opts ?? {})); - return this.deserialize<IPlotTagFindManyResolve>(res); - } - - async plot_tag_delete(opts: IPlotTagDelete): Promise<IPlotTagDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_tag_delete(this.serialize(opts)); - return this.deserialize<IPlotTagDeleteResolve>(res); - } - - async plot_tag_update(opts: IPlotTagUpdate): Promise<IPlotTagUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_plot_tag_update(this.serialize(opts)); - return this.deserialize<IPlotTagUpdateResolve>(res); - } - - async farm_member_create(opts: IFarmMemberCreate): Promise<IFarmMemberCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_member_create(this.serialize(opts)); - return this.deserialize<IFarmMemberCreateResolve>(res); - } - - async farm_member_find_one(opts: IFarmMemberFindOne): Promise<IFarmMemberFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_member_find_one(this.serialize(opts)); - return this.deserialize<IFarmMemberFindOneResolve>(res); - } - - async farm_member_find_many(opts?: IFarmMemberFindMany): Promise<IFarmMemberFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_member_find_many(this.serialize(opts ?? {})); - return this.deserialize<IFarmMemberFindManyResolve>(res); - } - - async farm_member_delete(opts: IFarmMemberDelete): Promise<IFarmMemberDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_member_delete(this.serialize(opts)); - return this.deserialize<IFarmMemberDeleteResolve>(res); - } - - async farm_member_update(opts: IFarmMemberUpdate): Promise<IFarmMemberUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_member_update(this.serialize(opts)); - return this.deserialize<IFarmMemberUpdateResolve>(res); - } - - async farm_member_claim_create(opts: IFarmMemberClaimCreate): Promise<IFarmMemberClaimCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_member_claim_create(this.serialize(opts)); - return this.deserialize<IFarmMemberClaimCreateResolve>(res); - } - - async farm_member_claim_find_one(opts: IFarmMemberClaimFindOne): Promise<IFarmMemberClaimFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_member_claim_find_one(this.serialize(opts)); - return this.deserialize<IFarmMemberClaimFindOneResolve>(res); - } - - async farm_member_claim_find_many(opts?: IFarmMemberClaimFindMany): Promise<IFarmMemberClaimFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_member_claim_find_many(this.serialize(opts ?? {})); - return this.deserialize<IFarmMemberClaimFindManyResolve>(res); - } - - async farm_member_claim_delete(opts: IFarmMemberClaimDelete): Promise<IFarmMemberClaimDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_member_claim_delete(this.serialize(opts)); - return this.deserialize<IFarmMemberClaimDeleteResolve>(res); - } - - async farm_member_claim_update(opts: IFarmMemberClaimUpdate): Promise<IFarmMemberClaimUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_farm_member_claim_update(this.serialize(opts)); - return this.deserialize<IFarmMemberClaimUpdateResolve>(res); - } - - async log_error_create(opts: ILogErrorCreate): Promise<ILogErrorCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_log_error_create(this.serialize(opts)); - return this.deserialize<ILogErrorCreateResolve>(res); - } - - async log_error_find_one(opts: ILogErrorFindOne): Promise<ILogErrorFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_log_error_find_one(this.serialize(opts)); - return this.deserialize<ILogErrorFindOneResolve>(res); - } - - async log_error_find_many(opts?: ILogErrorFindMany): Promise<ILogErrorFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_log_error_find_many(this.serialize(opts ?? {})); - return this.deserialize<ILogErrorFindManyResolve>(res); - } - - async log_error_delete(opts: ILogErrorDelete): Promise<ILogErrorDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_log_error_delete(this.serialize(opts)); - return this.deserialize<ILogErrorDeleteResolve>(res); - } - - async log_error_update(opts: ILogErrorUpdate): Promise<ILogErrorUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_log_error_update(this.serialize(opts)); - return this.deserialize<ILogErrorUpdateResolve>(res); - } - - async media_image_create(opts: IMediaImageCreate): Promise<IMediaImageCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_media_image_create(this.serialize(opts)); - return this.deserialize<IMediaImageCreateResolve>(res); - } - - async media_image_find_one(opts: IMediaImageFindOne): Promise<IMediaImageFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_media_image_find_one(this.serialize(opts)); - return this.deserialize<IMediaImageFindOneResolve>(res); - } - - async media_image_find_many(opts?: IMediaImageFindMany): Promise<IMediaImageFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_media_image_find_many(this.serialize(opts ?? {})); - return this.deserialize<IMediaImageFindManyResolve>(res); - } - - async media_image_delete(opts: IMediaImageDelete): Promise<IMediaImageDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_media_image_delete(this.serialize(opts)); - return this.deserialize<IMediaImageDeleteResolve>(res); - } - - async media_image_update(opts: IMediaImageUpdate): Promise<IMediaImageUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_media_image_update(this.serialize(opts)); - return this.deserialize<IMediaImageUpdateResolve>(res); - } - - async nostr_profile_create(opts: INostrProfileCreate): Promise<INostrProfileCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_profile_create(this.serialize(opts)); - return this.deserialize<INostrProfileCreateResolve>(res); - } - - async nostr_profile_find_one(opts: INostrProfileFindOne): Promise<INostrProfileFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_profile_find_one(this.serialize(opts)); - return this.deserialize<INostrProfileFindOneResolve>(res); - } - - async nostr_profile_find_many(opts?: INostrProfileFindMany): Promise<INostrProfileFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_profile_find_many(this.serialize(opts ?? {})); - return this.deserialize<INostrProfileFindManyResolve>(res); - } - - async nostr_profile_delete(opts: INostrProfileDelete): Promise<INostrProfileDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_profile_delete(this.serialize(opts)); - return this.deserialize<INostrProfileDeleteResolve>(res); - } - - async nostr_profile_update(opts: INostrProfileUpdate): Promise<INostrProfileUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_profile_update(this.serialize(opts)); - return this.deserialize<INostrProfileUpdateResolve>(res); - } - - async nostr_event_state_create(opts: INostrEventStateCreate): Promise<INostrEventStateCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_event_state_create(this.serialize(opts)); - return this.deserialize<INostrEventStateCreateResolve>(res); - } - - async nostr_event_state_find_one(opts: INostrEventStateFindOne): Promise<INostrEventStateFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_event_state_find_one(this.serialize(opts)); - return this.deserialize<INostrEventStateFindOneResolve>(res); - } - - async nostr_event_state_find_many(opts?: INostrEventStateFindMany): Promise<INostrEventStateFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_event_state_find_many(this.serialize(opts ?? {})); - return this.deserialize<INostrEventStateFindManyResolve>(res); - } - - async nostr_event_state_delete(opts: INostrEventStateDelete): Promise<INostrEventStateDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_event_state_delete(this.serialize(opts)); - return this.deserialize<INostrEventStateDeleteResolve>(res); - } - - async nostr_event_state_update(opts: INostrEventStateUpdate): Promise<INostrEventStateUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_event_state_update(this.serialize(opts)); - return this.deserialize<INostrEventStateUpdateResolve>(res); - } - - async nostr_relay_create(opts: INostrRelayCreate): Promise<INostrRelayCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_relay_create(this.serialize(opts)); - return this.deserialize<INostrRelayCreateResolve>(res); - } - - async nostr_relay_find_one(opts: INostrRelayFindOne): Promise<INostrRelayFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_relay_find_one(this.serialize(opts)); - return this.deserialize<INostrRelayFindOneResolve>(res); - } - - async nostr_relay_find_many(opts?: INostrRelayFindMany): Promise<INostrRelayFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_relay_find_many(this.serialize(opts ?? {})); - return this.deserialize<INostrRelayFindManyResolve>(res); - } - - async nostr_relay_delete(opts: INostrRelayDelete): Promise<INostrRelayDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_relay_delete(this.serialize(opts)); - return this.deserialize<INostrRelayDeleteResolve>(res); - } - - async nostr_relay_update(opts: INostrRelayUpdate): Promise<INostrRelayUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_relay_update(this.serialize(opts)); - return this.deserialize<INostrRelayUpdateResolve>(res); - } - - async trade_product_create(opts: ITradeProductCreate): Promise<ITradeProductCreateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_trade_product_create(this.serialize(opts)); - return this.deserialize<ITradeProductCreateResolve>(res); - } - - async trade_product_find_one(opts: ITradeProductFindOne): Promise<ITradeProductFindOneResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_trade_product_find_one(this.serialize(opts)); - return this.deserialize<ITradeProductFindOneResolve>(res); - } - - async trade_product_find_many(opts?: ITradeProductFindMany): Promise<ITradeProductFindManyResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_trade_product_find_many(this.serialize(opts ?? {})); - return this.deserialize<ITradeProductFindManyResolve>(res); - } - - async trade_product_delete(opts: ITradeProductDelete): Promise<ITradeProductDeleteResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_trade_product_delete(this.serialize(opts)); - return this.deserialize<ITradeProductDeleteResolve>(res); - } - - async trade_product_update(opts: ITradeProductUpdate): Promise<ITradeProductUpdateResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_trade_product_update(this.serialize(opts)); - return this.deserialize<ITradeProductUpdateResolve>(res); - } - - async nostr_profile_relay_set(opts: INostrProfileRelayRelation): Promise<INostrProfileRelayResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_profile_relay_set(this.serialize(opts)); - return this.deserialize<INostrProfileRelayResolve>(res); - } - - async nostr_profile_relay_unset(opts: INostrProfileRelayRelation): Promise<INostrProfileRelayResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_nostr_profile_relay_unset(this.serialize(opts)); - return this.deserialize<INostrProfileRelayResolve>(res); - } - - async trade_product_location_set(opts: ITradeProductLocationRelation): Promise<ITradeProductLocationResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_trade_product_location_set(this.serialize(opts)); - return this.deserialize<ITradeProductLocationResolve>(res); - } - - async trade_product_location_unset(opts: ITradeProductLocationRelation): Promise<ITradeProductLocationResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_trade_product_location_unset(this.serialize(opts)); - return this.deserialize<ITradeProductLocationResolve>(res); - } - - async trade_product_media_set(opts: ITradeProductMediaRelation): Promise<ITradeProductMediaResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_trade_product_media_set(this.serialize(opts)); - return this.deserialize<ITradeProductMediaResolve>(res); - } - - async trade_product_media_unset(opts: ITradeProductMediaRelation): Promise<ITradeProductMediaResolve | IError<string>> { - await this.ensure_ready(); - const res = await tangle_db_trade_product_media_unset(this.serialize(opts)); - return this.deserialize<ITradeProductMediaResolve>(res); - } - -} - -export const web_tangle_database_create = async (config?: WebTangleDatabaseConfig): Promise<WebTangleDatabase> => { - const db = new WebTangleDatabase(config); - await db.init(); - return db; -}; diff --git a/geo/package.json b/geo/package.json @@ -35,7 +35,7 @@ }, "dependencies": { "@radroots/utils": "workspace:*", - "@radroots/tangle-db-schema-bindings": "workspace:*", + "@radroots/replica-db-schema-bindings": "workspace:*", "geohashing": "^2.0.1", "zod": "^4.0.5" } diff --git a/geo/src/gcs.ts b/geo/src/gcs.ts @@ -1,4 +1,4 @@ -import type { GcsLocation } from "@radroots/tangle-db-schema-bindings"; +import type { GcsLocation } from "@radroots/replica-db-schema-bindings"; import type { LocationBasis } from "./types.js"; export const gcs_to_location_basis = ({ diff --git a/locales/src/messages/en/error.json b/locales/src/messages/en/error.json @@ -72,8 +72,8 @@ "missing_version_metadata": "Import file is missing version metadata.", "no_file_chosen": "Choose a backup file before continuing.", "storage_mismatch": "Import failed: {{label}} storage mismatch (app={{app_database}}/{{app_store}}, backup={{backup_database}}/{{backup_store}}).", - "tangle_db_version_mismatch": "Import file tangle-db version does not match metadata.", - "tangle_store_key_mismatch": "Import failed: tangle DB store key mismatch (app={{app_store_key}}, backup={{backup_store_key}}).", + "replica_db_version_mismatch": "Import file replica-db version does not match metadata.", + "replica_store_key_mismatch": "Import failed: replica DB store key mismatch (app={{app_store_key}}, backup={{backup_store_key}}).", "unsupported_backup_version": "Unsupported backup version ({{backup_version}}); expected {{expected_version}}." }, "profile": { diff --git a/nostr/package.json b/nostr/package.json @@ -40,9 +40,9 @@ "@radroots/events-codec-wasm": "workspace:*", "@radroots/trade-bindings": "workspace:*", "@radroots/utils": "workspace:*", - "@welshman/net": "workspace:*", - "@welshman/signer": "workspace:*", - "@welshman/util": "workspace:*", + "@welshman/net": "0.8.4", + "@welshman/signer": "0.8.4", + "@welshman/util": "0.8.4", "nostr-geotags": "^0.7.2", "nostr-tools": "^2.10.4", "zod": "^4.2.1" diff --git a/nostr/src/domain/trade/lib.ts b/nostr/src/domain/trade/lib.ts @@ -1,44 +0,0 @@ -import { - KIND_TRADE_LISTING_ACCEPT_REQ, - KIND_TRADE_LISTING_ACCEPT_RES, - KIND_TRADE_LISTING_CANCEL_REQ, - KIND_TRADE_LISTING_CANCEL_RES, - KIND_TRADE_LISTING_CONVEYANCE_REQ, - KIND_TRADE_LISTING_CONVEYANCE_RES, - KIND_TRADE_LISTING_FULFILL_REQ, - KIND_TRADE_LISTING_FULFILL_RES, - KIND_TRADE_LISTING_INVOICE_REQ, - KIND_TRADE_LISTING_INVOICE_RES, - KIND_TRADE_LISTING_ORDER_REQ, - KIND_TRADE_LISTING_ORDER_RES, - KIND_TRADE_LISTING_PAYMENT_REQ, - KIND_TRADE_LISTING_PAYMENT_RES, - KIND_TRADE_LISTING_RECEIPT_REQ, - KIND_TRADE_LISTING_RECEIPT_RES, - KIND_TRADE_LISTING_REFUND_REQ, - KIND_TRADE_LISTING_REFUND_RES, -} from "@radroots/trade-bindings"; - -export const REQUEST_KINDS: Record<string, number> = { - order: KIND_TRADE_LISTING_ORDER_REQ, - accept: KIND_TRADE_LISTING_ACCEPT_REQ, - conveyance: KIND_TRADE_LISTING_CONVEYANCE_REQ, - invoice: KIND_TRADE_LISTING_INVOICE_REQ, - payment: KIND_TRADE_LISTING_PAYMENT_REQ, - fulfillment: KIND_TRADE_LISTING_FULFILL_REQ, - receipt: KIND_TRADE_LISTING_RECEIPT_REQ, - cancel: KIND_TRADE_LISTING_CANCEL_REQ, - refund: KIND_TRADE_LISTING_REFUND_REQ, -}; - -export const RESULT_KINDS: Record<string, number> = { - order: KIND_TRADE_LISTING_ORDER_RES, - accept: KIND_TRADE_LISTING_ACCEPT_RES, - conveyance: KIND_TRADE_LISTING_CONVEYANCE_RES, - invoice: KIND_TRADE_LISTING_INVOICE_RES, - payment: KIND_TRADE_LISTING_PAYMENT_RES, - fulfillment: KIND_TRADE_LISTING_FULFILL_RES, - receipt: KIND_TRADE_LISTING_RECEIPT_RES, - cancel: KIND_TRADE_LISTING_CANCEL_RES, - refund: KIND_TRADE_LISTING_REFUND_RES, -}; diff --git a/nostr/src/domain/trade/listing/accept/lib.ts b/nostr/src/domain/trade/listing/accept/lib.ts @@ -1,63 +0,0 @@ -import { RadrootsJobInput } from "@radroots/events-bindings"; -import { - KIND_TRADE_LISTING_ACCEPT_REQ, - KIND_TRADE_LISTING_ACCEPT_RES, - MARKER_LISTING, - MARKER_ORDER_RESULT, - TradeListingAcceptRequest, - TradeListingAcceptResult, -} from "@radroots/trade-bindings"; -import { nostr_event_create } from "../../../../events/lib.js"; -import type { NostrEventFigure, NostrSignedEvent } from "../../../../types/nostr.js"; -import { - build_request_tags, - build_result_tags, - CommonRequestOpts, - CommonResultOpts, - make_event_input, -} from "../../tags.js"; -import { tags_trade_listing_chain } from "../tags.js"; - -export const nostr_event_trade_listing_accept_request = async ( - opts: NostrEventFigure<{ data: TradeListingAcceptRequest; options?: CommonRequestOpts }>, -): Promise<NostrSignedEvent | undefined> => { - const { data, options } = opts; - - const inputs: RadrootsJobInput[] = [ - make_event_input(data.order_result_event_id, MARKER_ORDER_RESULT), - make_event_input(data.listing_event_id, MARKER_LISTING), - ]; - - const tags = await build_request_tags(KIND_TRADE_LISTING_ACCEPT_REQ, inputs, options); - - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_ACCEPT_REQ, content: "", tags }, - }); -}; - -export const nostr_event_trade_listing_accept_result = async ( - opts: NostrEventFigure<{ - request_event_id: string; - content: TradeListingAcceptResult | string; - options?: CommonResultOpts & { chain?: { e_root: string; d?: string; e_prev?: string } }; - }>, -): Promise<NostrSignedEvent | undefined> => { - const { request_event_id, content, options } = opts; - - const base_tags = await build_result_tags( - KIND_TRADE_LISTING_ACCEPT_RES, - request_event_id, - options, - ); - - const tags = options?.chain - ? [...base_tags, ...tags_trade_listing_chain(options.chain)] - : base_tags; - - const content_body = typeof content === "string" ? content : JSON.stringify(content); - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_ACCEPT_RES, content: content_body, tags }, - }); -}; diff --git a/nostr/src/domain/trade/listing/conveyance/lib.ts b/nostr/src/domain/trade/listing/conveyance/lib.ts @@ -1,64 +0,0 @@ -import { RadrootsJobInput } from "@radroots/events-bindings"; -import { - KIND_TRADE_LISTING_CONVEYANCE_REQ, - KIND_TRADE_LISTING_CONVEYANCE_RES, - MARKER_ACCEPT_RESULT, - MARKER_PAYLOAD, - TradeListingConveyanceRequest, - TradeListingConveyanceResult, -} from "@radroots/trade-bindings"; -import { nostr_event_create } from "../../../../events/lib.js"; -import type { NostrEventFigure, NostrSignedEvent } from "../../../../types/nostr.js"; -import { - build_request_tags, - build_result_tags, - CommonRequestOpts, - CommonResultOpts, - make_event_input, - make_text_input, -} from "../../tags.js"; -import { tags_trade_listing_chain } from "../tags.js"; - -export const nostr_event_trade_listing_conveyance_request = async ( - opts: NostrEventFigure<{ data: TradeListingConveyanceRequest; options?: CommonRequestOpts }>, -): Promise<NostrSignedEvent | undefined> => { - const { data, options } = opts; - - const inputs: RadrootsJobInput[] = [ - make_event_input(data.accept_result_event_id, MARKER_ACCEPT_RESULT), - make_text_input({ method: data.method }, MARKER_PAYLOAD), - ]; - - const tags = await build_request_tags(KIND_TRADE_LISTING_CONVEYANCE_REQ, inputs, options); - - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_CONVEYANCE_REQ, content: "", tags }, - }); -}; - -export const nostr_event_trade_listing_conveyance_result = async ( - opts: NostrEventFigure<{ - request_event_id: string; - content: TradeListingConveyanceResult | string; - options?: CommonResultOpts & { chain?: { e_root: string; d?: string; e_prev?: string } }; - }>, -): Promise<NostrSignedEvent | undefined> => { - const { request_event_id, content, options } = opts; - - const base_tags = await build_result_tags( - KIND_TRADE_LISTING_CONVEYANCE_RES, - request_event_id, - options, - ); - - const tags = options?.chain - ? [...base_tags, ...tags_trade_listing_chain(options.chain)] - : base_tags; - - const content_body = typeof content === "string" ? content : JSON.stringify(content); - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_CONVEYANCE_RES, content: content_body, tags }, - }); -}; diff --git a/nostr/src/domain/trade/listing/fulfillment/lib.ts b/nostr/src/domain/trade/listing/fulfillment/lib.ts @@ -1,61 +0,0 @@ -import { RadrootsJobInput } from "@radroots/events-bindings"; -import { - KIND_TRADE_LISTING_FULFILL_REQ, - KIND_TRADE_LISTING_FULFILL_RES, - MARKER_PAYMENT_RESULT, - TradeListingFulfillmentRequest, - TradeListingFulfillmentResult, -} from "@radroots/trade-bindings"; -import { nostr_event_create } from "../../../../events/lib.js"; -import type { NostrEventFigure, NostrSignedEvent } from "../../../../types/nostr.js"; -import { - build_request_tags, - build_result_tags, - CommonRequestOpts, - CommonResultOpts, - make_event_input, -} from "../../tags.js"; -import { tags_trade_listing_chain } from "../tags.js"; - -export const nostr_event_trade_listing_fulfillment_request = async ( - opts: NostrEventFigure<{ data: TradeListingFulfillmentRequest; options?: CommonRequestOpts }>, -): Promise<NostrSignedEvent | undefined> => { - const { data, options } = opts; - - const inputs: RadrootsJobInput[] = [ - make_event_input(data.payment_result_event_id, MARKER_PAYMENT_RESULT), - ]; - - const tags = await build_request_tags(KIND_TRADE_LISTING_FULFILL_REQ, inputs, options); - - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_FULFILL_REQ, content: "", tags }, - }); -}; - -export const nostr_event_trade_listing_fulfillment_result = async ( - opts: NostrEventFigure<{ - request_event_id: string; - content: TradeListingFulfillmentResult | string; - options?: CommonResultOpts & { chain?: { e_root: string; d?: string; e_prev?: string } }; - }>, -): Promise<NostrSignedEvent | undefined> => { - const { request_event_id, content, options } = opts; - - const base_tags = await build_result_tags( - KIND_TRADE_LISTING_FULFILL_RES, - request_event_id, - options, - ); - - const tags = options?.chain - ? [...base_tags, ...tags_trade_listing_chain(options.chain)] - : base_tags; - - const content_body = typeof content === "string" ? content : JSON.stringify(content); - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_FULFILL_RES, content: content_body, tags }, - }); -}; diff --git a/nostr/src/domain/trade/listing/invoice/lib.ts b/nostr/src/domain/trade/listing/invoice/lib.ts @@ -1,71 +0,0 @@ -import { RadrootsJobInput } from "@radroots/events-bindings"; -import { - KIND_TRADE_LISTING_INVOICE_REQ, - KIND_TRADE_LISTING_INVOICE_RES, - MARKER_ACCEPT_RESULT, - TradeListingInvoiceRequest, - TradeListingInvoiceResult, -} from "@radroots/trade-bindings"; -import { nostr_event_create } from "../../../../events/lib.js"; -import type { NostrEventFigure, NostrSignedEvent } from "../../../../types/nostr.js"; -import { - build_request_tags, - build_result_tags, - CommonRequestOpts, - CommonResultOpts, - make_event_input, -} from "../../tags.js"; -import { tags_trade_listing_chain } from "../tags.js"; - -export const nostr_event_trade_listing_invoice_request = async ( - opts: NostrEventFigure<{ data: TradeListingInvoiceRequest; options?: CommonRequestOpts }>, -): Promise<NostrSignedEvent | undefined> => { - const { data, options } = opts; - - const inputs: RadrootsJobInput[] = [ - make_event_input(data.accept_result_event_id, MARKER_ACCEPT_RESULT), - ]; - - const tags = await build_request_tags(KIND_TRADE_LISTING_INVOICE_REQ, inputs, options); - - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_INVOICE_REQ, content: "", tags }, - }); -}; - -export const nostr_event_trade_listing_invoice_result = async ( - opts: NostrEventFigure<{ - request_event_id: string; - content: TradeListingInvoiceResult | string; - options?: Omit<CommonResultOpts, "payment_sat" | "payment_bolt11"> & { - chain?: { e_root: string; d?: string; e_prev?: string }; - }; - }>, -): Promise<NostrSignedEvent | undefined> => { - const { request_event_id, content, options } = opts; - - const parsed = typeof content === "string" ? undefined : content; - - const base_tags = await build_result_tags( - KIND_TRADE_LISTING_INVOICE_RES, - request_event_id, - options, - parsed - ? { - payment_sat: parsed.total_sat, - payment_bolt11: parsed.bolt11 ?? undefined, - } - : undefined, - ); - - const tags = options?.chain - ? [...base_tags, ...tags_trade_listing_chain(options.chain)] - : base_tags; - - const content_body = typeof content === "string" ? content : JSON.stringify(content); - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_INVOICE_RES, content: content_body, tags }, - }); -}; diff --git a/nostr/src/domain/trade/listing/order/lib.ts b/nostr/src/domain/trade/listing/order/lib.ts @@ -1,64 +0,0 @@ -import { RadrootsJobInput } from "@radroots/events-bindings"; -import { - KIND_TRADE_LISTING_ORDER_REQ, - KIND_TRADE_LISTING_ORDER_RES, - MARKER_LISTING, - MARKER_PAYLOAD, - TradeListingOrderRequest, - TradeListingOrderResult, -} from "@radroots/trade-bindings"; -import { nostr_event_create } from "../../../../events/lib.js"; -import type { NostrEventFigure, NostrSignedEvent } from "../../../../types/nostr.js"; -import { - build_request_tags, - build_result_tags, - CommonRequestOpts, - CommonResultOpts, - make_event_input, - make_text_input, -} from "../../tags.js"; -import { tags_trade_listing_chain } from "../tags.js"; - -export const nostr_event_trade_listing_order_request = async ( - opts: NostrEventFigure<{ data: TradeListingOrderRequest; options?: CommonRequestOpts }>, -): Promise<NostrSignedEvent | undefined> => { - const { data, options } = opts; - - const inputs: RadrootsJobInput[] = [ - make_event_input(data.event.id, MARKER_LISTING, data.event.relays ?? undefined), - make_text_input(data.payload, MARKER_PAYLOAD), - ]; - - const tags = await build_request_tags(KIND_TRADE_LISTING_ORDER_REQ, inputs, options); - - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_ORDER_REQ, content: "", tags }, - }); -}; - -export const nostr_event_trade_listing_order_result = async ( - opts: NostrEventFigure<{ - request_event_id: string; - content: TradeListingOrderResult | string; - options?: CommonResultOpts & { chain?: { e_root: string; d?: string; e_prev?: string } }; - }>, -): Promise<NostrSignedEvent | undefined> => { - const { request_event_id, content, options } = opts; - - const base_tags = await build_result_tags( - KIND_TRADE_LISTING_ORDER_RES, - request_event_id, - options, - ); - - const tags = options?.chain - ? [...base_tags, ...tags_trade_listing_chain(options.chain)] - : base_tags; - - const content_body = typeof content === "string" ? content : JSON.stringify(content); - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_ORDER_RES, content: content_body, tags }, - }); -}; diff --git a/nostr/src/domain/trade/listing/payment/lib.ts b/nostr/src/domain/trade/listing/payment/lib.ts @@ -1,64 +0,0 @@ -import { RadrootsJobInput } from "@radroots/events-bindings"; -import { - KIND_TRADE_LISTING_PAYMENT_REQ, - KIND_TRADE_LISTING_PAYMENT_RES, - MARKER_INVOICE_RESULT, - MARKER_PROOF, - TradeListingPaymentProofRequest, - TradeListingPaymentResult, -} from "@radroots/trade-bindings"; -import { nostr_event_create } from "../../../../events/lib.js"; -import type { NostrEventFigure, NostrSignedEvent } from "../../../../types/nostr.js"; -import { - build_request_tags, - build_result_tags, - CommonRequestOpts, - CommonResultOpts, - make_event_input, - make_text_input, -} from "../../tags.js"; -import { tags_trade_listing_chain } from "../tags.js"; - -export const nostr_event_trade_listing_payment_request = async ( - opts: NostrEventFigure<{ data: TradeListingPaymentProofRequest; options?: CommonRequestOpts }>, -): Promise<NostrSignedEvent | undefined> => { - const { data, options } = opts; - - const inputs: RadrootsJobInput[] = [ - make_event_input(data.invoice_result_event_id, MARKER_INVOICE_RESULT), - make_text_input(data.proof, MARKER_PROOF), - ]; - - const tags = await build_request_tags(KIND_TRADE_LISTING_PAYMENT_REQ, inputs, options); - - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_PAYMENT_REQ, content: "", tags }, - }); -}; - -export const nostr_event_trade_listing_payment_result = async ( - opts: NostrEventFigure<{ - request_event_id: string; - content: TradeListingPaymentResult | string; - options?: CommonResultOpts & { chain?: { e_root: string; d?: string; e_prev?: string } }; - }>, -): Promise<NostrSignedEvent | undefined> => { - const { request_event_id, content, options } = opts; - - const base_tags = await build_result_tags( - KIND_TRADE_LISTING_PAYMENT_RES, - request_event_id, - options, - ); - - const tags = options?.chain - ? [...base_tags, ...tags_trade_listing_chain(options.chain)] - : base_tags; - - const content_body = typeof content === "string" ? content : JSON.stringify(content); - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_PAYMENT_RES, content: content_body, tags }, - }); -}; diff --git a/nostr/src/domain/trade/listing/receipt/lib.ts b/nostr/src/domain/trade/listing/receipt/lib.ts @@ -1,64 +0,0 @@ -import { RadrootsJobInput } from "@radroots/events-bindings"; -import { - KIND_TRADE_LISTING_RECEIPT_REQ, - KIND_TRADE_LISTING_RECEIPT_RES, - MARKER_FULFILLMENT_RESULT, - MARKER_PAYLOAD, - TradeListingReceiptRequest, - TradeListingReceiptResult, -} from "@radroots/trade-bindings"; -import { nostr_event_create } from "../../../../events/lib.js"; -import type { NostrEventFigure, NostrSignedEvent } from "../../../../types/nostr.js"; -import { - build_request_tags, - build_result_tags, - CommonRequestOpts, - CommonResultOpts, - make_event_input, - make_text_input, -} from "../../tags.js"; -import { tags_trade_listing_chain } from "../tags.js"; - -export const nostr_event_trade_listing_receipt_request = async ( - opts: NostrEventFigure<{ data: TradeListingReceiptRequest; options?: CommonRequestOpts }>, -): Promise<NostrSignedEvent | undefined> => { - const { data, options } = opts; - - const inputs: RadrootsJobInput[] = [ - make_event_input(data.fulfillment_result_event_id, MARKER_FULFILLMENT_RESULT), - ...(data.note ? [make_text_input({ note: data.note }, MARKER_PAYLOAD)] : []), - ]; - - const tags = await build_request_tags(KIND_TRADE_LISTING_RECEIPT_REQ, inputs, options); - - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_RECEIPT_REQ, content: "", tags }, - }); -}; - -export const nostr_event_trade_listing_receipt_result = async ( - opts: NostrEventFigure<{ - request_event_id: string; - content: TradeListingReceiptResult | string; - options?: CommonResultOpts & { chain?: { e_root: string; d?: string; e_prev?: string } }; - }>, -): Promise<NostrSignedEvent | undefined> => { - const { request_event_id, content, options } = opts; - - const base_tags = await build_result_tags( - KIND_TRADE_LISTING_RECEIPT_RES, - request_event_id, - options, - ); - - const tags = options?.chain - ? [...base_tags, ...tags_trade_listing_chain(options.chain)] - : base_tags; - - const content_body = typeof content === "string" ? content : JSON.stringify(content); - return nostr_event_create({ - ...opts, - basis: { kind: KIND_TRADE_LISTING_RECEIPT_RES, content: content_body, tags }, - }); -}; diff --git a/nostr/src/domain/trade/listing/tags.ts b/nostr/src/domain/trade/listing/tags.ts @@ -1,14 +0,0 @@ -import { TAG_D, TAG_E_PREV, TAG_E_ROOT } from "@radroots/events-bindings"; -import { NostrEventTags } from "../../../types/lib.js"; - -export const tags_trade_listing_chain = (opts: { - e_root: string; - d?: string; - e_prev?: string; -}): NostrEventTags => { - const tags: NostrEventTags = []; - tags.push([TAG_E_ROOT, opts.e_root]); - if (opts.e_prev) tags.push([TAG_E_PREV, opts.e_prev]); - if (opts.d) tags.push([TAG_D, opts.d]); - return tags; -}; diff --git a/nostr/src/domain/trade/tags.ts b/nostr/src/domain/trade/tags.ts @@ -1,88 +0,0 @@ -import { RadrootsJobInput } from "@radroots/events-bindings"; -import { tags_job_request, tags_job_result } from "../../events/job/tags.js"; - -export type CommonRequestOpts = { - output?: string; - bid_sat?: number; - relays?: string[]; - providers?: string[]; - topics?: string[]; - encrypted?: boolean; - params?: Array<{ key: string; value: string }>; -}; - -export type CommonResultOpts = { - request_relay_hint?: string; - request_json?: string; - customer_pubkey?: string; - payment_sat?: number; - payment_bolt11?: string; - encrypted?: boolean; - include_inputs?: string[]; - chain?: { e_root: string; d?: string; e_prev?: string }; -}; - -export const make_event_input = ( - id: string, - marker: string, - relay?: string, -): RadrootsJobInput => ({ - data: id, - input_type: "event", - ...(relay ? { relay } : {}), - marker, -}); - -export const make_text_input = ( - payload: unknown, - marker: string, -): RadrootsJobInput => ({ - data: typeof payload === "string" ? payload : JSON.stringify(payload), - input_type: "text", - marker, -}); - -export const build_request_tags = async ( - kind: number, - inputs: RadrootsJobInput[], - opts?: CommonRequestOpts, -) => - await tags_job_request({ - kind, - inputs, - output: opts?.output, - params: opts?.params ?? [], - bid_sat: opts?.bid_sat, - relays: opts?.relays ?? [], - providers: opts?.providers ?? [], - topics: opts?.topics ?? [], - encrypted: !!opts?.encrypted, - }); - -export const build_result_tags = async ( - kind: number, - request_event_id: string, - opts?: CommonResultOpts, - extra?: { - inputs?: RadrootsJobInput[]; - payment_sat?: number; - payment_bolt11?: string; - }, -) => - await tags_job_result({ - kind, - request_event: { - id: request_event_id, - ...(opts?.request_relay_hint ? { relays: opts.request_relay_hint } : {}), - }, - request_json: opts?.request_json, - inputs: !opts?.encrypted && extra?.inputs?.length ? extra.inputs : [], - customer_pubkey: opts?.customer_pubkey, - payment: - extra?.payment_sat !== undefined - ? { amount_sat: extra.payment_sat, bolt11: extra.payment_bolt11 } - : opts?.payment_sat !== undefined - ? { amount_sat: opts.payment_sat, bolt11: opts.payment_bolt11 } - : undefined, - encrypted: !!opts?.encrypted, - }); diff --git a/nostr/src/events/comment/parse.ts b/nostr/src/events/comment/parse.ts @@ -1,4 +1,4 @@ -import { KIND_COMMENT, radroots_comment_schema, type RadrootsComment } from "@radroots/events-bindings"; +import { KIND_COMMENT, type RadrootsComment } from "@radroots/events-bindings"; import type { NostrEvent } from "../../types/nostr.js"; import { parse_nostr_event_basis } from "../lib.js"; import type { NostrEventBasis } from "../subscription.js"; @@ -12,7 +12,7 @@ export const parse_nostr_comment_event = ( if (!ev) return undefined; try { const parsed = JSON.parse(event.content); - const comment = radroots_comment_schema.parse(parsed); + const comment = parsed as RadrootsComment; return { ...ev, comment }; } catch { return undefined; diff --git a/nostr/src/events/farm/parse.ts b/nostr/src/events/farm/parse.ts @@ -1,4 +1,4 @@ -import { KIND_FARM, radroots_farm_schema, type RadrootsFarm } from "@radroots/events-bindings"; +import { KIND_FARM, type RadrootsFarm } from "@radroots/events-bindings"; import type { NostrEvent } from "../../types/nostr.js"; import { get_event_tag, parse_nostr_event_basis } from "../lib.js"; import type { NostrEventBasis } from "../subscription.js"; @@ -14,7 +14,7 @@ export const parse_nostr_farm_event = ( if (!d_tag) return undefined; try { const parsed = JSON.parse(event.content); - const farm = radroots_farm_schema.parse(parsed); + const farm = parsed as RadrootsFarm; if (farm.d_tag !== d_tag) return undefined; return { ...ev, farm }; } catch { diff --git a/nostr/src/events/follow/parse.ts b/nostr/src/events/follow/parse.ts @@ -1,4 +1,4 @@ -import { KIND_FOLLOW, radroots_follow_schema, type RadrootsFollow } from "@radroots/events-bindings"; +import { KIND_FOLLOW, type RadrootsFollow } from "@radroots/events-bindings"; import type { NostrEvent } from "../../types/nostr.js"; import { parse_nostr_event_basis } from "../lib.js"; import type { NostrEventBasis } from "../subscription.js"; @@ -12,7 +12,7 @@ export const parse_nostr_follow_event = ( if (!ev) return undefined; try { const parsed = JSON.parse(event.content); - const follow = radroots_follow_schema.parse(parsed); + const follow = parsed as RadrootsFollow; return { ...ev, follow }; } catch { return undefined; diff --git a/nostr/src/events/job/utils.ts b/nostr/src/events/job/utils.ts @@ -1,20 +1,6 @@ -import { JobInputType, KIND_JOB_FEEDBACK } from "@radroots/events-bindings"; -import { TradeListingStage } from "@radroots/trade-bindings"; -import { REQUEST_KINDS, RESULT_KINDS } from "../../domain/trade/lib.js"; +import type { JobInputType } from "@radroots/events-bindings"; import type { NostrEventTags } from "../../types/lib.js"; -const TRADE_LISTING_STAGE_KEYS: TradeListingStage["kind"][] = [ - "order", - "accept", - "conveyance", - "invoice", - "payment", - "fulfillment", - "receipt", - "cancel", - "refund", -]; - export function get_job_input_data_for_marker( tags: NostrEventTags, marker: string, @@ -28,16 +14,3 @@ export function get_job_input_data_for_marker( } return undefined; } - -export function get_trade_listing_stage_from_event_kind( - kind: number, -): TradeListingStage | undefined { - for (const key of TRADE_LISTING_STAGE_KEYS) { - if (REQUEST_KINDS[key] === kind) return { kind: key }; - } - for (const key of TRADE_LISTING_STAGE_KEYS) { - if (RESULT_KINDS[key] === kind) return { kind: key }; - } - if (kind === KIND_JOB_FEEDBACK) return { kind: "order" }; - return undefined; -} diff --git a/nostr/src/events/list/parse.ts b/nostr/src/events/list/parse.ts @@ -1,5 +1,4 @@ import type { RadrootsList } from "@radroots/events-bindings"; -import { radroots_list_schema } from "@radroots/events-bindings"; import type { NostrEventTags } from "../../types/lib.js"; import type { NostrEvent } from "../../types/nostr.js"; import type { NostrEventBasis } from "../subscription.js"; @@ -23,7 +22,7 @@ export const parse_nostr_list_event = ( content: event.content ?? "", entries: list_entries_from_tags(event.tags), }; - const list = radroots_list_schema.parse(list_raw); + const list = list_raw as RadrootsList; return { id: event.id, published_at: event.created_at, diff --git a/nostr/src/events/list_set/parse.ts b/nostr/src/events/list_set/parse.ts @@ -1,5 +1,4 @@ import type { RadrootsListSet } from "@radroots/events-bindings"; -import { radroots_list_set_schema } from "@radroots/events-bindings"; import type { NostrEventTags } from "../../types/lib.js"; import type { NostrEvent } from "../../types/nostr.js"; import type { NostrEventBasis } from "../subscription.js"; @@ -33,7 +32,7 @@ export const parse_nostr_list_set_event = ( description: get_event_tag(event.tags, "description") || undefined, image: get_event_tag(event.tags, "image") || undefined, }; - const list_set = radroots_list_set_schema.parse(list_set_raw); + const list_set = list_set_raw as RadrootsListSet; return { id: event.id, published_at: event.created_at, diff --git a/nostr/src/events/listing/parse.ts b/nostr/src/events/listing/parse.ts @@ -1,23 +1,18 @@ -import { RadrootsCoreUnit } from "@radroots/core-bindings"; import type { RadrootsCoreDiscount, + RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, } from "@radroots/core-bindings"; import { KIND_LISTING, - radroots_listing_bin_schema, - radroots_listing_discount_schema, - radroots_listing_image_schema, - radroots_listing_location_schema, - radroots_listing_product_schema, - radroots_listing_schema, type RadrootsListing, type RadrootsListingAvailability, type RadrootsListingDeliveryMethod, - type RadrootsListingFarmRef, + type RadrootsFarmRef, type RadrootsListingImage, + type RadrootsListingProduct, type RadrootsListingStatus, } from "@radroots/events-bindings"; import type { NostrEvent } from "../../types/nostr.js"; @@ -33,7 +28,7 @@ type ListingBinDraft = { bin_id: string; quantity?: RadrootsCoreQuantity; price_per_canonical_unit?: RadrootsCoreQuantityPrice; - display_amount?: number; + display_amount?: RadrootsCoreDecimal; display_unit?: CoreUnit; display_label?: string; display_price?: RadrootsCoreMoney; @@ -74,8 +69,13 @@ const clean_string = (value?: string | null) => value?.trim() || undefined; const parse_currency_code = (code?: string): CoreCurrency | undefined => { const cleaned = clean_string(code); if (!cleaned || cleaned.length < 3) return undefined; - const upper = cleaned.toUpperCase(); - return [upper.charCodeAt(0), upper.charCodeAt(1), upper.charCodeAt(2)] as CoreCurrency; + return cleaned.toUpperCase(); +}; + +const parse_decimal = (value?: string): RadrootsCoreDecimal | undefined => { + const cleaned = clean_string(value); + if (!cleaned) return undefined; + return Number.isFinite(Number(cleaned)) ? cleaned : undefined; }; const parse_unit_code = (unit?: string): CoreUnit | undefined => { @@ -83,35 +83,35 @@ const parse_unit_code = (unit?: string): CoreUnit | undefined => { case "each": case "ea": case "count": - return RadrootsCoreUnit.Each; + return "each"; case "kg": case "kilogram": case "kilograms": - return RadrootsCoreUnit.MassKg; + return "kg"; case "g": case "gram": case "grams": - return RadrootsCoreUnit.MassG; + return "g"; case "oz": case "ounce": case "ounces": - return RadrootsCoreUnit.MassOz; + return "oz"; case "lb": case "pound": case "pounds": - return RadrootsCoreUnit.MassLb; + return "lb"; case "l": case "liter": case "litre": case "liters": case "litres": - return RadrootsCoreUnit.VolumeL; + return "l"; case "ml": case "milliliter": case "millilitre": case "milliliters": case "millilitres": - return RadrootsCoreUnit.VolumeMl; + return "ml"; default: return undefined; } @@ -121,12 +121,12 @@ const parse_bin_tag = (tag: string[]): ListingBinDraft | undefined => { if (tag.length < 4) return undefined; const bin_id = clean_string(tag[1]); if (!bin_id) return undefined; - const amount = to_number(tag[2]); + const amount = parse_decimal(tag[2]); const unit = parse_unit_code(tag[3]); if (amount === undefined || !unit) return undefined; - const quantity: RadrootsCoreQuantity = { amount, unit }; + const quantity: RadrootsCoreQuantity = { amount, unit, label: null }; const draft: ListingBinDraft = { bin_id, quantity }; - const display_amount = to_number(tag[4]); + const display_amount = parse_decimal(tag[4]); const display_unit = parse_unit_code(tag[5]); if (display_amount !== undefined && display_unit) { draft.display_amount = display_amount; @@ -141,16 +141,16 @@ const parse_bin_price_tag = (tag: string[]): ListingBinDraft | undefined => { if (tag.length < 6) return undefined; const bin_id = clean_string(tag[1]); if (!bin_id) return undefined; - const amount = to_number(tag[2]); + const amount = parse_decimal(tag[2]); const currency = parse_currency_code(tag[3]); - const quantity_amount = to_number(tag[4]); + const quantity_amount = parse_decimal(tag[4]); const quantity_unit = parse_unit_code(tag[5]); if (amount === undefined || !currency || quantity_amount === undefined || !quantity_unit) return undefined; const money: RadrootsCoreMoney = { amount, currency }; - const quantity: RadrootsCoreQuantity = { amount: quantity_amount, unit: quantity_unit }; + const quantity: RadrootsCoreQuantity = { amount: quantity_amount, unit: quantity_unit, label: null }; const price_per_canonical_unit: RadrootsCoreQuantityPrice = { amount: money, quantity }; const draft: ListingBinDraft = { bin_id, price_per_canonical_unit }; - const display_price_amount = to_number(tag[6]); + const display_price_amount = parse_decimal(tag[6]); const display_price_unit = parse_unit_code(tag[7]); if (display_price_amount !== undefined && display_price_unit) { draft.display_price = { amount: display_price_amount, currency }; @@ -163,7 +163,7 @@ const parse_discount_tag = (tag: string[]): RadrootsCoreDiscount | undefined => if (tag[0] !== "radroots:discount" || !tag[1]) return undefined; try { const payload = JSON.parse(tag[1]); - return radroots_listing_discount_schema.parse(payload); + return payload as RadrootsCoreDiscount; } catch { return undefined; } @@ -176,10 +176,10 @@ const parse_image_tag = (tag: string[]): RadrootsListingImage | undefined => { const [w, h] = tag[2].split("x").map(v => Number(v)); if (Number.isFinite(w) && Number.isFinite(h)) image.size = { w, h }; } - return radroots_listing_image_schema.parse(image); + return image; }; -const parse_farm_ref = (farm_pubkey?: string, farm_address?: string): RadrootsListingFarmRef | undefined => { +const parse_farm_ref = (farm_pubkey?: string, farm_address?: string): RadrootsFarmRef | undefined => { const pubkey = clean_string(farm_pubkey); const address = clean_string(farm_address); if (!pubkey || !address) return undefined; @@ -251,7 +251,8 @@ export const parse_nostr_listing_event = ( const tags = event.tags; const d_tag = get_event_tag(tags, "d"); - if (!clean_string(d_tag)) return undefined; + const listing_d_tag = clean_string(d_tag); + if (!listing_d_tag) return undefined; const farm_pubkey = get_event_tag(tags, "p"); const farm_address = get_event_tag(tags, "a"); const farm = parse_farm_ref(farm_pubkey, farm_address); @@ -269,7 +270,21 @@ export const parse_nostr_listing_event = ( year: get_event_tag(tags, "year"), }; - const product = radroots_listing_product_schema.parse(product_raw); + const product_key = clean_string(product_raw.key); + const product_title = clean_string(product_raw.title); + const product_category = clean_string(product_raw.category); + if (!product_key || !product_title || !product_category) return undefined; + const product: RadrootsListingProduct = { + key: product_key, + title: product_title, + category: product_category, + summary: clean_string(product_raw.summary) ?? null, + process: clean_string(product_raw.process) ?? null, + lot: clean_string(product_raw.lot) ?? null, + location: clean_string(product_raw.location) ?? null, + profile: clean_string(product_raw.profile) ?? null, + year: clean_string(product_raw.year) ?? null, + }; const bin_drafts: Record<string, ListingBinDraft> = {}; const bin_order: string[] = []; @@ -283,8 +298,7 @@ export const parse_nostr_listing_event = ( } const bins = bin_order .map(bin_id => bin_drafts[bin_id]) - .filter(is_listing_bin) - .map(bin => radroots_listing_bin_schema.parse(bin)); + .filter(is_listing_bin); if (!bins.length) return undefined; const primary_bin_tag = clean_string(get_event_tag(tags, "radroots:primary_bin")); const primary_bin_id = bins.some(bin => bin.bin_id === primary_bin_tag) @@ -322,26 +336,36 @@ export const parse_nostr_listing_event = ( } const location = location_raw.primary - ? radroots_listing_location_schema.parse(location_raw) + ? { + primary: location_raw.primary, + city: location_raw.city ?? null, + region: location_raw.region ?? null, + country: location_raw.country ?? null, + lat: location_raw.lat ?? null, + lng: location_raw.lng ?? null, + geohash: location_raw.geohash ?? null, + } : undefined; - const inventory_available = to_number(get_event_tag(tags, "inventory")); + const inventory_available = parse_decimal(get_event_tag(tags, "inventory")); const availability = parse_listing_availability(tags); const delivery_method = parse_delivery_method(tags); - const listing_base = radroots_listing_schema.parse({ - d_tag, + const listing: RadrootsListing = { + d_tag: listing_d_tag, + farm, product, primary_bin_id, bins, - discounts: discounts.length ? discounts : undefined, + discounts: discounts.length ? discounts : null, inventory_available, availability, delivery_method, location, - images: images.length ? images : undefined, - }); - const listing: RadrootsListing = { ...listing_base, farm }; + images: images.length ? images : null, + resource_area: null, + plot: null, + }; return { ...ev, listing }; } catch { return undefined; diff --git a/nostr/src/events/plot/parse.ts b/nostr/src/events/plot/parse.ts @@ -1,4 +1,4 @@ -import { KIND_FARM, KIND_PLOT, radroots_plot_schema, type RadrootsPlot } from "@radroots/events-bindings"; +import { KIND_FARM, KIND_PLOT, type RadrootsPlot } from "@radroots/events-bindings"; import type { NostrEvent } from "../../types/nostr.js"; import { get_event_tag, parse_nostr_event_basis } from "../lib.js"; import type { NostrEventBasis } from "../subscription.js"; @@ -37,7 +37,7 @@ export const parse_nostr_plot_event = ( if (farm_pubkey !== farm_ref.pubkey) return undefined; try { const parsed = JSON.parse(event.content); - const plot = radroots_plot_schema.parse(parsed); + const plot = parsed as RadrootsPlot; if (plot.d_tag !== d_tag) return undefined; if (plot.farm.pubkey !== farm_ref.pubkey) return undefined; if (plot.farm.d_tag !== farm_ref.d_tag) return undefined; diff --git a/nostr/src/events/profile/parse.ts b/nostr/src/events/profile/parse.ts @@ -1,4 +1,4 @@ -import { KIND_PROFILE, radroots_profile_schema, type RadrootsProfile, type RadrootsProfileType } from "@radroots/events-bindings"; +import { KIND_PROFILE, type RadrootsProfile, type RadrootsProfileType } from "@radroots/events-bindings"; import type { NostrEvent } from "../../types/nostr.js"; import { parse_nostr_event_basis } from "../lib.js"; import type { NostrEventBasis } from "../subscription.js"; @@ -16,7 +16,7 @@ export const parse_nostr_profile_event = ( if (!ev) return undefined; try { const parsed = JSON.parse(event.content); - const profile = radroots_profile_schema.parse(parsed); + const profile = parsed as RadrootsProfile; const profile_type = parse_profile_type_tag(event.tags); return profile_type ? { ...ev, profile, profile_type } : { ...ev, profile }; } catch { diff --git a/nostr/src/events/reaction/parse.ts b/nostr/src/events/reaction/parse.ts @@ -1,4 +1,4 @@ -import { KIND_REACTION, radroots_reaction_schema, type RadrootsReaction } from "@radroots/events-bindings"; +import { KIND_REACTION, type RadrootsReaction } from "@radroots/events-bindings"; import type { NostrEvent } from "../../types/nostr.js"; import { parse_nostr_event_basis } from "../lib.js"; import type { NostrEventBasis } from "../subscription.js"; @@ -12,7 +12,7 @@ export const parse_nostr_reaction_event = ( if (!ev) return undefined; try { const parsed = JSON.parse(event.content); - const reaction = radroots_reaction_schema.parse(parsed); + const reaction = parsed as RadrootsReaction; return { ...ev, reaction }; } catch { return undefined; diff --git a/nostr/src/index.ts b/nostr/src/index.ts @@ -1,13 +1,3 @@ -export * from "./domain/trade/lib.js"; -export * from "./domain/trade/listing/accept/lib.js"; -export * from "./domain/trade/listing/conveyance/lib.js"; -export * from "./domain/trade/listing/fulfillment/lib.js"; -export * from "./domain/trade/listing/invoice/lib.js"; -export * from "./domain/trade/listing/order/lib.js"; -export * from "./domain/trade/listing/payment/lib.js"; -export * from "./domain/trade/listing/receipt/lib.js"; -export * from "./domain/trade/listing/tags.js"; -export * from "./domain/trade/tags.js"; export * from "./events/comment/lib.js"; export * from "./events/comment/parse.js"; export * from "./events/comment/tags.js";