commit d037e15a3e30fd8b041153a46ea9b18186baf873
parent 9b801589beba464868597350aa08ab890581a95f
Author: triesap <137732411+triesap@users.noreply.github.com>
Date: Mon, 13 Jan 2025 16:56:48 +0000
utils: refactor utils
Diffstat:
21 files changed, 730 insertions(+), 77 deletions(-)
diff --git a/utils/src/client/database.ts b/utils/src/client/database.ts
@@ -0,0 +1,96 @@
+
+
+export type IClientDatabase<
+ TILocationGcsCreate extends object,
+ TLocationGcsCreateResolve extends object,
+ TILocationGcsRead extends object,
+ TLocationGcsReadResolve extends object,
+ TILocationGcsReadList extends object,
+ TLocationGcsReadListResolve extends object,
+ TILocationGcsDelete extends object,
+ TLocationGcsDeleteResolve extends object,
+ TILocationGcsUpdate extends object,
+ TLocationGcsUpdateResolve extends object,
+ TITradeProductCreate extends object,
+ TTradeProductCreateResolve extends object,
+ TITradeProductRead extends object,
+ TTradeProductReadResolve extends object,
+ TITradeProductReadList extends object,
+ TTradeProductReadListResolve extends object,
+ TITradeProductDelete extends object,
+ TTradeProductDeleteResolve extends object,
+ TITradeProductUpdate extends object,
+ TTradeProductUpdateResolve extends object,
+ TINostrProfileCreate extends object,
+ TNostrProfileCreateResolve extends object,
+ TINostrProfileRead extends object,
+ TNostrProfileReadResolve extends object,
+ TINostrProfileReadList extends object,
+ TNostrProfileReadListResolve extends object,
+ TINostrProfileDelete extends object,
+ TNostrProfileDeleteResolve extends object,
+ TINostrProfileUpdate extends object,
+ TNostrProfileUpdateResolve extends object,
+ TINostrRelayCreate extends object,
+ TNostrRelayCreateResolve extends object,
+ TINostrRelayRead extends object,
+ TNostrRelayReadResolve extends object,
+ TINostrRelayReadList extends object,
+ TNostrRelayReadListResolve extends object,
+ TINostrRelayDelete extends object,
+ TNostrRelayDeleteResolve extends object,
+ TINostrRelayUpdate extends object,
+ TNostrRelayUpdateResolve extends object,
+ TIMediaImageCreate extends object,
+ TMediaImageCreateResolve extends object,
+ TIMediaImageRead extends object,
+ TMediaImageReadResolve extends object,
+ TIMediaImageReadList extends object,
+ TMediaImageReadListResolve extends object,
+ TIMediaImageDelete extends object,
+ TMediaImageDeleteResolve extends object,
+ TIMediaImageUpdate extends object,
+ TMediaImageUpdateResolve extends object,
+ TILogErrorCreate extends object,
+ TLogErrorCreateResolve extends object,
+ TILogErrorRead extends object,
+ TLogErrorReadResolve extends object,
+ TILogErrorReadList extends object,
+ TLogErrorReadListResolve extends object,
+ TILogErrorDelete extends object,
+ TLogErrorDeleteResolve extends object,
+ TILogErrorUpdate extends object,
+ TLogErrorUpdateResolve extends object
+> = {
+ location_gcs_create(opts: TILocationGcsCreate): Promise<TLocationGcsCreateResolve>;
+ location_gcs_read(opts: TILocationGcsRead): Promise<TLocationGcsReadResolve>;
+ location_gcs_read_list(opts: TILocationGcsReadList): Promise<TLocationGcsReadListResolve>;
+ location_gcs_delete(opts: TILocationGcsDelete): Promise<TLocationGcsDeleteResolve>;
+ location_gcs_update(opts: TILocationGcsUpdate): Promise<TLocationGcsUpdateResolve>;
+ trade_product_create(opts: TITradeProductCreate): Promise<TTradeProductCreateResolve>;
+ trade_product_read(opts: TITradeProductRead): Promise<TTradeProductReadResolve>;
+ trade_product_read_list(opts: TITradeProductReadList): Promise<TTradeProductReadListResolve>;
+ trade_product_delete(opts: TITradeProductDelete): Promise<TTradeProductDeleteResolve>;
+ trade_product_update(opts: TITradeProductUpdate): Promise<TTradeProductUpdateResolve>;
+ nostr_profile_create(opts: TINostrProfileCreate): Promise<TNostrProfileCreateResolve>;
+ nostr_profile_read(opts: TINostrProfileRead): Promise<TNostrProfileReadResolve>;
+ nostr_profile_read_list(opts: TINostrProfileReadList): Promise<TNostrProfileReadListResolve>;
+ nostr_profile_delete(opts: TINostrProfileDelete): Promise<TNostrProfileDeleteResolve>;
+ nostr_profile_update(opts: TINostrProfileUpdate): Promise<TNostrProfileUpdateResolve>;
+ nostr_relay_create(opts: TINostrRelayCreate): Promise<TNostrRelayCreateResolve>;
+ nostr_relay_read(opts: TINostrRelayRead): Promise<TNostrRelayReadResolve>;
+ nostr_relay_read_list(opts: TINostrRelayReadList): Promise<TNostrRelayReadListResolve>;
+ nostr_relay_delete(opts: TINostrRelayDelete): Promise<TNostrRelayDeleteResolve>;
+ nostr_relay_update(opts: TINostrRelayUpdate): Promise<TNostrRelayUpdateResolve>;
+ media_image_create(opts: TIMediaImageCreate): Promise<TMediaImageCreateResolve>;
+ media_image_read(opts: TIMediaImageRead): Promise<TMediaImageReadResolve>;
+ media_image_read_list(opts: TIMediaImageReadList): Promise<TMediaImageReadListResolve>;
+ media_image_delete(opts: TIMediaImageDelete): Promise<TMediaImageDeleteResolve>;
+ media_image_update(opts: TIMediaImageUpdate): Promise<TMediaImageUpdateResolve>;
+ log_error_create(opts: TILogErrorCreate): Promise<TLogErrorCreateResolve>;
+ log_error_read(opts: TILogErrorRead): Promise<TLogErrorReadResolve>;
+ log_error_read_list(opts: TILogErrorReadList): Promise<TLogErrorReadListResolve>;
+ log_error_delete(opts: TILogErrorDelete): Promise<TLogErrorDeleteResolve>;
+ log_error_update(opts: TILogErrorUpdate): Promise<TLogErrorUpdateResolve>;
+
+};
+\ No newline at end of file
diff --git a/utils/src/client/datastore.ts b/utils/src/client/datastore.ts
@@ -0,0 +1,30 @@
+import type { ErrorMessage, ResultObj, ResultPass, ResultsList } from "..";
+
+export const ds_map = {
+ key_nostr: `nostr:publickey`,
+ role: `config:role`,
+ eula: `eula:date`
+};
+
+export const ds_map_param = {
+ radroots_profile: (public_key: string) => `radroots:profile:${public_key}`
+};
+
+export type IClientDatastoreSetResolve = ResultPass | ErrorMessage<string>;
+export type IClientDatastoreGetResolve = ResultObj<string> | ErrorMessage<string>;
+export type IClientDatastoreSetPResolve = ResultPass | ErrorMessage<string>;
+export type IClientDatastoreGetPResolve = ResultObj<string> | ErrorMessage<string>;
+export type IClientDatastoreKeysResolve = ResultsList<string> | ErrorMessage<string>;
+export type IClientDatastoreEntriesResolve = ResultsList<[string, unknown]> | ErrorMessage<string>;
+export type IClientDatastoreRemoveResolve = ResultPass | ErrorMessage<string>;
+
+export type IClientDatastore = {
+ init(): Promise<void>;
+ set(key: keyof typeof ds_map, value: string): Promise<IClientDatastoreSetResolve>;
+ get(key: keyof typeof ds_map): Promise<IClientDatastoreGetResolve>;
+ setp(key: keyof typeof ds_map_param, key_param: string, value: string): Promise<IClientDatastoreSetPResolve>;
+ getp(key: keyof typeof ds_map_param, key_param: string): Promise<IClientDatastoreGetPResolve>;
+ keys(): Promise<IClientDatastoreKeysResolve>;
+ entries(): Promise<IClientDatastoreEntriesResolve>
+ remove(key: string): Promise<IClientDatastoreRemoveResolve>;
+};
+\ No newline at end of file
diff --git a/utils/src/client/geolocation.ts b/utils/src/client/geolocation.ts
@@ -0,0 +1,15 @@
+import { type ErrorMessage } from "..";
+
+export type IGeolocationErrorMessage = `*-permissions` | `*`;
+
+export type IClientGeolocationPosition = {
+ lat: number;
+ lng: number;
+ accuracy?: number;
+ altitude?: number;
+ altitude_accuracy?: number;
+};
+
+export type IClientGeolocation = {
+ current(): Promise<IClientGeolocationPosition | ErrorMessage<IGeolocationErrorMessage>>;
+};
+\ No newline at end of file
diff --git a/utils/src/client/gui.ts b/utils/src/client/gui.ts
@@ -0,0 +1,24 @@
+import { type ResultsList } from "..";
+
+export type IClientGuiDialogKind = "info" | "warning" | "error";
+
+export type IClientGuiDialogAlertOpts = string;
+
+export type IClientGuiDialogConfirmOpts = string | { title?: string, kind?: IClientGuiDialogKind; message: string; cancel?: string; ok?: string; };
+
+export type IClientGuiDialogResolve = ResultsList<string>;
+
+export type IClientGuiNotifyPermission = "default" | "denied" | "granted";
+export type IClientGuiNotifySendOptions = {
+ id?: number;
+ channel_id?: string;
+ title: string;
+ body?: string;
+};
+
+export type IClientGui = {
+ alert(opts: IClientGuiDialogAlertOpts): Promise<boolean>;
+ confirm(opts: IClientGuiDialogConfirmOpts): Promise<boolean>;
+ notify_init(): Promise<IClientGuiNotifyPermission | undefined>;
+ notify_send(opts: string | IClientGuiNotifySendOptions): Promise<void>;
+};
+\ No newline at end of file
diff --git a/utils/src/error.ts b/utils/src/error.ts
@@ -6,6 +6,11 @@ export const handle_error = (e: unknown, append?: string): ErrorMessage<string>
return { err };
};
+export const throw_err = (param: string | ErrorMessage<string>): undefined => {
+ if (typeof param === `string`) throw new Error(param);
+ else throw new Error(param.err);
+};
+
export const err_msg = <T extends string>(err: T): ErrorMessage<T> => {
return { err };
};
@@ -16,4 +21,4 @@ export const err_res = <T extends object>(error: T): ErrorResponse<T> => {
export const err_system = (message: string): boolean => {
return message.split(` `).length > 1
-};
-\ No newline at end of file
+};
diff --git a/utils/src/geolocation.ts b/utils/src/geolocation.ts
@@ -7,6 +7,9 @@ export type GeolocationCoordinatesPoint = {
lng: number;
}
+export type GeolocationLatitudeFmtOption = 'dms' | 'd' | 'dm';
+
+
export const geohash_encode = (opts: {
lat: string | number;
lng: string | number;
@@ -33,4 +36,52 @@ export const location_geohash = (point: GeolocationCoordinatesPoint): string =>
const { lat, lng } = point;
const res = geohash_encode({ lat, lng });
return res;
+};
+
+export const geol_lat_fmt = (lat: number, fmt_opt: GeolocationLatitudeFmtOption, locale: string, precision: number = 5): string => {
+ const options: Intl.NumberFormatOptions = {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ };
+ const fmt_deg = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
+ const fmt_min = new Intl.NumberFormat(locale, options);
+ const fmt_sec = new Intl.NumberFormat(locale, options);
+ if (fmt_opt === 'dms') {
+ const deg = Math.floor(Math.abs(lat));
+ const min = Math.floor((Math.abs(lat) - deg) * 60);
+ const sec = ((Math.abs(lat) - deg - min / 60) * 3600);
+ return `${fmt_deg.format(deg)}° ${fmt_min.format(min)}' ${fmt_sec.format(sec)}" ${lat >= 0 ? 'N' : 'S'}`;
+ } else if (fmt_opt === 'dm') {
+ const deg = Math.floor(Math.abs(lat));
+ const min = (Math.abs(lat) - deg) * 60;
+ return `${fmt_deg.format(deg)}° ${fmt_min.format(min)}' ${lat >= 0 ? 'N' : 'S'}`;
+ } else {
+ return `${lat.toLocaleString(locale, { maximumFractionDigits: precision })}° ${lat >= 0 ? 'N' : 'S'}`;
+ }
+};
+
+export const geol_lng_fmt = (lng: number, fmt_opt: GeolocationLatitudeFmtOption, locale: string, precision: number = 5): string => {
+ const options: Intl.NumberFormatOptions = {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ };
+ const fmt_deg = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
+ const fmt_min = new Intl.NumberFormat(locale, options);
+ const fmt_sec = new Intl.NumberFormat(locale, options);
+ if (fmt_opt === 'dms') {
+ const degrees = Math.floor(Math.abs(lng));
+ const minutes = Math.floor((Math.abs(lng) - degrees) * 60);
+ const seconds = ((Math.abs(lng) - degrees - minutes / 60) * 3600);
+ return `${fmt_deg.format(degrees)}° ${fmt_min.format(minutes)}' ${fmt_sec.format(seconds)}" ${lng >= 0 ? 'E' : 'W'}`;
+ } else if (fmt_opt === 'dm') {
+ const degrees = Math.floor(Math.abs(lng));
+ const minutes = (Math.abs(lng) - degrees) * 60;
+ return `${fmt_deg.format(degrees)}° ${fmt_min.format(minutes)}' ${lng >= 0 ? 'E' : 'W'}`;
+ } else {
+ return `${lng.toLocaleString(locale, { maximumFractionDigits: precision })}° ${lng >= 0 ? 'E' : 'W'}`;
+ }
+};
+
+export const parse_geol_coords = (number: number): number => {
+ return Math.round(number * 1e7) / 1e7;
};
\ No newline at end of file
diff --git a/utils/src/guard.ts b/utils/src/guard.ts
@@ -0,0 +1,32 @@
+import { type NotifyMessage } from ".";
+
+export const is_err_response = (response: any): response is { err: string } => {
+ return "err" in response && typeof response.err === "string";
+}
+
+export const is_pass_response = (response: any): response is { pass: true } => {
+ return "pass" in response && response.pass === true;
+}
+
+export const is_result_response = (response: any): response is { result: string } => {
+ return "result" in response && typeof response.result === "string";
+}
+
+export const is_results_response = (response: any): response is { results: string[] } => {
+ return "results" in response && Array.isArray(response.results);
+}
+
+export const is_error_response = (response: any): response is { error: string } => {
+ return "error" in response && typeof response.result === "string";
+}
+
+export const is_message_response = (response: any): response is NotifyMessage => {
+ return (
+ typeof response === "object" &&
+ response !== null &&
+ "message" in response &&
+ typeof response.message === "string" &&
+ (response.ok === undefined || typeof response.ok === "string") &&
+ (response.cancel === undefined || typeof response.cancel === "string")
+ );
+};
+\ No newline at end of file
diff --git a/utils/src/http.ts b/utils/src/http.ts
@@ -0,0 +1,105 @@
+import { type FieldRecord, is_error_response, is_message_response, type NotifyMessage } from ".";
+
+export type IHttpImageResponse = {
+ status: number;
+ blob?: Blob;
+ headers: FieldRecord;
+ url: string;
+};
+
+export type IHttpResponse = {
+ status: number;
+ data?: any;
+ error?: string;
+ message?: NotifyMessage;
+ headers: FieldRecord;
+ url: string;
+};
+
+export type IHttpOptsData = any;
+export type IHttpOptsParams = Record<string, string | string[]>;
+
+export type IHttpOpts = {
+ url: string;
+ method?: `get` | `post` | `put`;
+ params?: IHttpOptsParams;
+ data?: IHttpOptsData;
+ data_bin?: Uint8Array;
+ authorization?: string;
+ headers?: FieldRecord;
+ connect_timeout?: number;
+};
+
+const lib_http_to_bodyinit = (data: any): BodyInit => {
+ if (typeof data === 'string') return data;
+ else if (data instanceof FormData) return data;
+ else if (data instanceof Blob) return data;
+ else if (data instanceof ArrayBuffer) return data;
+ else if (data instanceof URLSearchParams) return data;
+ return JSON.stringify(data);
+}
+
+const lib_http_parse_headers = (headers: Headers): FieldRecord => {
+ const acc: FieldRecord = {};
+ headers.forEach((value, key) => acc[key] = value);
+ return acc;
+};
+
+export const http_fetch_opts = (opts: IHttpOpts): { url: string; options: RequestInit; } => {
+ const { url } = opts;
+ const headers: FieldRecord = {
+ ...opts.headers,
+ };
+ if (opts.authorization) headers['Authorization'] = `Bearer ${encodeURIComponent(opts.authorization)}`;
+ const options: RequestInit = {
+ method: opts.method ? opts.method.toUpperCase() : `GET`,
+ headers,
+ }
+ if (opts.data) options.body = lib_http_to_bodyinit(opts.data);
+ if (opts.data_bin) options.body = opts.data_bin;
+
+ return {
+ url,
+ options
+ }
+};
+
+export const http_parse_response = async (res: Response): Promise<Promise<IHttpResponse>> => {
+ let data: any = null;
+ try {
+ const res_json = await res.json();
+ if (typeof res_json === `string`) data = JSON.parse(res_json);
+ else data = res_json;
+ } catch { }
+ if (!data) data = await res.text();
+ return {
+ status: res.status,
+ url: res.url,
+ data: res.ok && data ? data : null,
+ error: !res.ok && is_error_response(data) ? data.error : undefined,
+ message: res.ok && is_message_response(data) ? data : undefined,
+ headers: lib_http_parse_headers(res.headers)
+ };
+};
+
+export const http_fetch = async (opts: IHttpOpts): Promise<IHttpResponse> => {
+ const { url, options } = http_fetch_opts(opts);
+ const response = await fetch(url, options);
+ let response_data: any = null;
+ try {
+ const res_json = await response.json();
+ response_data = typeof res_json === `string` ? JSON.parse(res_json) : res_json;
+ } catch { }
+ if (!response_data) {
+ try {
+ const res_text = await response.text();
+ response_data = res_text;
+ } catch { }
+ }
+ return {
+ status: response.status,
+ url: response.url,
+ headers: lib_http_parse_headers(response.headers)
+ };
+};
+
diff --git a/utils/src/index.ts b/utils/src/index.ts
@@ -1,13 +1,23 @@
export * from "./ascii"
+export * from "./client/database"
+export * from "./client/datastore"
+export * from "./client/geolocation"
+export * from "./client/gui"
export * from "./currency"
export * from "./error"
export * from "./file"
export * from "./format"
export * from "./geolocation"
+export * from "./guard"
export * from "./hash"
+export * from "./http"
export * from "./list"
export * from "./math"
export * from "./model"
+export * from "./nostr/event"
+export * from "./nostr/key"
+export * from "./nostr/ndk"
+export * from "./nostr/types"
export * from "./nostr-event-util/lib"
export * from "./nostr-event-util/types"
export * from "./nostr-key-util/lib"
diff --git a/utils/src/model.ts b/utils/src/model.ts
@@ -1,7 +1,11 @@
-export type IModelsQueryValue = string | number | boolean;
-export type IModelsQueryBindValue = string | number | boolean | null;
-export type IModelsQueryBindValueTuple = [string, IModelsQueryBindValue];
+export type IModelsQueryValue = string | number | boolean | null;
+export type IModelsQueryBindValue = string | number | boolean | null;;
+export type IModelsQueryBindValueTuple = [string, IModelsQueryValue];
export type IModelsQueryBindValueOpt = (IModelsQueryBindValue | null);
+export type IModelsQueryFilterOption = `equals` | `starts-with` | `ends-with` | `contains` | `ne`;
+export type IModelsQueryFilterOptionList = `between` | `in`;
+export type IModelsQueryFilterCondition = `and` | `or` | `not`
+
export type IModelsSortCreatedAt = 'newest' | 'oldest';
export type IModelsQueryParam = { query: string; bind_values: IModelsQueryBindValue[] };
export type IModelsFormErrorTuple = [boolean, string];
@@ -18,3 +22,108 @@ export type IModelsForm = {
optional?: boolean;
default?: string | number;
};
+
+export type IModelQueryFilterMapValuesTuplesOption = [IModelsQueryValue, IModelsQueryFilterOption];
+export type IModelQueryFilterMapValuesTuplesOptionList = [IModelsQueryValue[], IModelsQueryFilterOptionList];
+export type IModelQueryFilterMapValuesTuples = ModelQueryFilterMapTuple<IModelQueryFilterMapValuesTuplesOption> | ModelQueryFilterMapTuple<IModelQueryFilterMapValuesTuplesOptionList>;
+export type IModelQueryFilterMapValues = IModelsQueryValue | IModelQueryFilterMapValuesTuples;
+
+export type ModelQueryFilterMapTupleBasis =
+ | [IModelsQueryValue, IModelsQueryFilterOption]
+ | [IModelsQueryValue, IModelsQueryFilterOption, IModelsQueryFilterCondition]
+ | [IModelsQueryValue[], IModelsQueryFilterOptionList]
+ | [IModelsQueryValue[], IModelsQueryFilterOptionList, IModelsQueryFilterCondition];
+
+export type ModelQueryFilterMapTuple<T extends ModelQueryFilterMapTupleBasis> =
+ T extends [IModelsQueryValue, IModelsQueryFilterOption]
+ ? [IModelsQueryValue, IModelsQueryFilterOption, IModelsQueryFilterCondition]
+ : T extends [IModelsQueryValue[], IModelsQueryFilterOptionList]
+ ? [IModelsQueryValue[], IModelsQueryFilterOptionList, IModelsQueryFilterCondition]
+ : T;
+
+export type IModelQueryFilterMap<ModelFilter extends object> = {
+ [K in keyof ModelFilter]: ModelFilter[K] | [ModelFilter[K], IModelsQueryFilterOption] | [ModelFilter[K], IModelsQueryFilterOption, IModelsQueryFilterCondition] | [ModelFilter[K][], IModelsQueryFilterOptionList] | [ModelFilter[K][], IModelsQueryFilterOptionList, IModelsQueryFilterCondition];
+};
+export type IModelQueryFilterMapParsed = { query_values: string[]; bind_values: IModelsQueryValue[]; };
+
+export const parse_model_query_value = (val: IModelsQueryValue): IModelsQueryBindValue => {
+ if (typeof val === `boolean`) return val ? '1' : '0';
+ else if (typeof val === `number`) return val;
+ else if (typeof val === `string` && val) return val;
+ return null;
+};
+
+export const is_model_query_filter_option = (value: string): value is IModelsQueryFilterOption => {
+ return ['equals', 'starts-with', 'ends-with', 'contains', 'ne'].includes(value);
+}
+
+export const is_model_query_filter_option_list = (value: string): value is IModelsQueryFilterOptionList => {
+ return ['between', 'in'].includes(value);
+}
+
+export const is_model_query_values = (value: unknown): value is IModelsQueryValue => {
+ return typeof value === `string` || typeof value === `number` || typeof value === `boolean`;
+}
+
+export const list_model_query_values_assert = (arr: (IModelsQueryValue | undefined)[]): (IModelsQueryValue)[] => {
+ return arr.filter((item): item is string | number | boolean => item !== undefined);
+}
+
+export const parse_model_filter_map = <T extends object>(opts: IModelQueryFilterMap<T>): IModelQueryFilterMapParsed => {
+ const bind_values: IModelsQueryValue[] = [];
+ const query_values: string[] = [];
+
+ for (const [index, entry] of Object.entries(opts).entries()) {
+ const [field, filters] = entry as [string, IModelQueryFilterMapValues];
+
+ if (is_model_query_values(filters)) {
+ query_values.push(`${field} = ?`);
+ bind_values.push(filters);
+ } else if (Array.isArray(filters)) {
+ const [filters_val, filters_opt] = filters;
+ const filter_condition = index === 0 ? `` : typeof filters[2] === `undefined` ? `AND ` : ` ${filters[2]}`;
+ if (is_model_query_values(filters_val) && is_model_query_filter_option(filters_opt)) {
+ switch (filters_opt) {
+ case `starts-with`: {
+ query_values.push(`${filter_condition}${field} LIKE ?`);
+ bind_values.push(`${filters[0]}%`);
+ } break;
+ case `ends-with`: {
+ query_values.push(`${filter_condition}${field} LIKE ?`);
+ bind_values.push(`%${filters[0]}`);
+ } break;
+ case `contains`: {
+ query_values.push(`${filter_condition}${field} LIKE ?`);
+ bind_values.push(`%${filters[0]}%`);
+ } break;
+ case `ne`: {
+ query_values.push(`${filter_condition}${field} != ?`);
+ bind_values.push(`${filters[0]}`);
+ } break;
+ case `equals`: {
+ query_values.push(`${filter_condition}${field} = ?`);
+ bind_values.push(filters[0]);
+ } break;
+ default:
+ throw new Error("util.model.parse_model_filter_map.invalid_condition");
+ };
+ } else if (is_model_query_filter_option_list(filters_opt)) {
+ switch (filters_opt) {
+ case `between`: {
+ query_values.push(`${filter_condition}${field} BETWEEN ? AND ?`);
+ bind_values.push(...filters[0].slice(0, 2));
+ } break;
+ case `in`: {
+ query_values.push(`${filter_condition}${field} IN (${`? `.repeat(filters[0].length).trim().split(" ").join(", ")})`);
+ bind_values.push(...list_model_query_values_assert(filters[0]));
+ } break;
+ default:
+ throw new Error("util.model.parse_model_filter_map.invalid_condition");
+ };
+ }
+ }
+ }
+ if (!query_values.length) throw new Error("Error: invalid filter.");
+ if (!bind_values.length) throw new Error("Error: invalid filter.");
+ return { query_values, bind_values };
+};
diff --git a/utils/src/nostr-event-util/lib.ts b/utils/src/nostr-event-util/lib.ts
@@ -1,10 +1,9 @@
-import { schnorr } from '@noble/curves/secp256k1';
-import { hexToBytes } from "@noble/hashes/utils";
import { type NDKEvent } from "@nostr-dev-kit/ndk";
import ngeotags, { type GeoTags as NostrGeotagsGeotags, type InputData as NostrGeotagsInputData } from "nostr-geotags";
-import { finalizeEvent, getEventHash, nip19, type NostrEvent as NostrToolsEvent } from "nostr-tools";
-import { uuidv4 } from "..";
-import type { INostrEventUtil, INostrEventUtilEventSign, INostrEventUtilFormatTagsBasisNip99, INostrEventUtilNeventEncode, NostrEventTagClient, NostrEventTagLocation, NostrEventTagMediaUpload, NostrEventTagPrice, NostrEventTagQuantity } from "./types";
+import { type NostrEvent as NostrToolsEvent } from "nostr-tools";
+import type { INostrEventEventSign } from "..";
+import { lib_nostr_event_sign, lib_nostr_event_sign_attest, lib_nostr_event_verify, lib_nostr_event_verify_serialized, lib_nostr_nevent_encode } from '../nostr/event';
+import type { INostrEventUtil, INostrEventUtilFormatTagsBasisNip99, INostrEventUtilNeventEncode, NostrEventTagClient, NostrEventTagLocation, NostrEventTagMediaUpload, NostrEventTagPrice, NostrEventTagQuantity } from "./types";
export class NostrEventUtil implements INostrEventUtil {
public first_tag_value = (event: NDKEvent, tag_name: string): string => {
@@ -76,38 +75,24 @@ export class NostrEventUtil implements INostrEventUtil {
return tags;
};
- public nostr_event_sign = (opts: INostrEventUtilEventSign): NostrToolsEvent => {
- return finalizeEvent(opts.event, hexToBytes(opts.secret_key))
+ public nostr_event_sign = (opts: INostrEventEventSign): NostrToolsEvent => {
+ return lib_nostr_event_sign(opts);
};
public nostr_event_sign_attest = (secret_key: string): NostrToolsEvent => {
- return this.nostr_event_sign({
- secret_key,
- event: {
- kind: 1,
- created_at: Math.floor(Date.now() / 1000),
- tags: [],
- content: uuidv4(),
- },
- });
+ return lib_nostr_event_sign_attest(secret_key);
};
public nostr_event_verify = (event: NostrToolsEvent): boolean => {
- const hash = getEventHash(event);
- if (hash !== event.id) return false
- const valid = schnorr.verify(event.sig, hash, event.pubkey);
- return valid;
+ return lib_nostr_event_verify(event);
};
public nostr_event_verify_serialized = (event_serialized: string): boolean => {
- const event = JSON.parse(event_serialized);
- const hash = getEventHash(event);
- if (hash !== event.id) return false
- const valid = schnorr.verify(event.sig, hash, event.pubkey);
- return valid;
+ const result = lib_nostr_event_verify_serialized(event_serialized);
+ return !!result;
};
public nevent_encode = (opts: INostrEventUtilNeventEncode): string => {
- return nip19.neventEncode(opts);
+ return lib_nostr_nevent_encode(opts);
};
}
\ No newline at end of file
diff --git a/utils/src/nostr-event-util/types.ts b/utils/src/nostr-event-util/types.ts
@@ -1,5 +1,6 @@
import { NDKEvent } from "@nostr-dev-kit/ndk";
-import { type NostrEvent as NostrToolsEvent, type EventTemplate as NostrToolsEventTemplate } from "nostr-tools";
+import { type NostrEvent as NostrToolsEvent } from "nostr-tools";
+import type { INostrEventEventSign } from "..";
export type INostrEventUtilFormatTagsBasisNip99 = {
d_tag: string;
@@ -18,15 +19,10 @@ export type INostrEventUtilNeventEncode = {
kind: number;
};
-export type INostrEventUtilEventSign = {
- secret_key: string;
- event: NostrToolsEventTemplate;
-}
-
export type INostrEventUtil = {
first_tag_value(event: NDKEvent, tag_name: string): string;
fmt_tags_basis_nip99: (opts: INostrEventUtilFormatTagsBasisNip99) => string[][];
- nostr_event_sign: (opts: INostrEventUtilEventSign) => NostrToolsEvent;
+ nostr_event_sign: (opts: INostrEventEventSign) => NostrToolsEvent;
nostr_event_sign_attest: (secret_key: string) => NostrToolsEvent;
nostr_event_verify_serialized: (event_serialized: string) => boolean;
nostr_event_verify: (event: NostrToolsEvent) => boolean;
diff --git a/utils/src/nostr-key-util/lib.ts b/utils/src/nostr-key-util/lib.ts
@@ -1,41 +1,25 @@
-import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
-import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
+import { getPublicKey, nip19 } from 'nostr-tools';
+import { lib_nostr_get_key_bytes, lib_nostr_key_generate, lib_nostr_nsec_decode, lib_nostr_nsec_encode } from '../nostr/key';
import type { INostrKeyUtil } from './types';
export class NostrKeyUtil implements INostrKeyUtil {
- private generate_key_bytes(): Uint8Array {
- const secret_key = generateSecretKey();
- return secret_key;
- };
-
- private get_key_hex(bytes: Uint8Array): string {
- const hex = bytesToHex(bytes);
- return hex;
- };
-
- private get_key_bytes(hex: string): Uint8Array {
- const bytes = hexToBytes(hex);
- return bytes;
- };
-
/**
*
* @returns nostr secret key hex
*/
public generate_key(): string {
- const bytes = this.generate_key_bytes();
- const hex = this.get_key_hex(bytes);
- return hex;
+ return lib_nostr_key_generate();
};
+
/**
*
- * @returns nostr public key hex
+ * @returns nostr public key hex from secret key
*/
public public_key(secret_key_hex: string | undefined): string {
try {
if (!secret_key_hex) return ``;
- const bytes = this.get_key_bytes(secret_key_hex);
+ const bytes = lib_nostr_get_key_bytes(secret_key_hex);
const hex = getPublicKey(bytes)
return hex;
} catch (e) {
@@ -50,7 +34,7 @@ export class NostrKeyUtil implements INostrKeyUtil {
public publickey_decode(secret_key_hex?: string): string | undefined {
try {
if (secret_key_hex) {
- return getPublicKey(this.get_key_bytes(secret_key_hex))
+ return getPublicKey(lib_nostr_get_key_bytes(secret_key_hex))
}
return undefined;
} catch (e) {
@@ -83,11 +67,8 @@ export class NostrKeyUtil implements INostrKeyUtil {
*
* @returns nostr secret key nsec
*/
- public nsec(secret_key_hex: string | undefined): string {
- if (!secret_key_hex) return ``;
- const bytes = this.get_key_bytes(secret_key_hex);
- const nsec = nip19.nsecEncode(bytes);
- return nsec;
+ public nsec(secret_key_hex?: string): string | undefined {
+ return lib_nostr_nsec_encode(secret_key_hex);
}
/**
@@ -95,14 +76,7 @@ export class NostrKeyUtil implements INostrKeyUtil {
* @returns nostr secret key hex from nsec
*/
public nsec_decode(nsec: string): string | undefined {
- try {
- if (!nsec) return undefined;
- const decode = nip19.decode(nsec);
- if (decode && decode.type === `nsec` && decode.data) return bytesToHex(decode.data);
- return undefined;
- } catch (e) {
- return undefined;
- }
+ return lib_nostr_nsec_decode(nsec);
}
/**
diff --git a/utils/src/nostr-key-util/types.ts b/utils/src/nostr-key-util/types.ts
@@ -3,7 +3,7 @@ export type INostrKeyUtil = {
public_key(secret_key_hex: string | undefined): string;
npub(public_key_hex: string | undefined): string;
npub_decode(npub: string): string;
- nsec(secret_key_hex: string | undefined): string;
+ nsec(secret_key_hex: string | undefined): string | undefined;
nsec_decode(nsec: string): string | undefined;
nprofile(public_key_hex: string, relays: string[]): string;
nprofile_decode(nprofile: string): [string, string[]] | undefined;
diff --git a/utils/src/nostr/event.ts b/utils/src/nostr/event.ts
@@ -0,0 +1,45 @@
+import { schnorr } from "@noble/curves/secp256k1";
+import { hexToBytes } from "@noble/hashes/utils";
+import { finalizeEvent, getEventHash, nip19, type NostrEvent as NostrToolsEvent } from "nostr-tools";
+import { type INostrEventEventSign, type INostrEventUtilNeventEncode, uuidv4 } from "..";
+
+export const lib_nostr_event_verify = (event: NostrToolsEvent): boolean => {
+ const hash = getEventHash(event);
+ if (hash !== event.id) return false
+ const valid = schnorr.verify(event.sig, hash, event.pubkey);
+ return valid;
+};
+
+export const lib_nostr_event_sign = (opts: INostrEventEventSign): NostrToolsEvent => {
+ return finalizeEvent(opts.event, hexToBytes(opts.secret_key))
+};
+
+export const lib_nostr_event_sign_attest = (secret_key: string): NostrToolsEvent => {
+ return lib_nostr_event_sign({
+ secret_key,
+ event: {
+ kind: 1,
+ created_at: Math.floor(Date.now() / 1000),
+ tags: [],
+ content: uuidv4(),
+ },
+ });
+};
+
+
+export const lib_nostr_event_verify_serialized = async (event_serialized: string): Promise<{ public_key: string } | undefined> => {
+ try {
+ const event = JSON.parse(event_serialized);
+ const hash = getEventHash(event);
+ if (hash !== event.id) return undefined;
+ const valid = schnorr.verify(event.sig, hash, event.pubkey);
+ if (valid) return { public_key: String(event.pubkey) };
+ return undefined;
+ } catch {
+ return undefined;
+ }
+};
+
+export const lib_nostr_nevent_encode = (opts: INostrEventUtilNeventEncode): string => {
+ return nip19.neventEncode(opts);
+};
+\ No newline at end of file
diff --git a/utils/src/nostr/key.ts b/utils/src/nostr/key.ts
@@ -0,0 +1,42 @@
+import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
+import { generateSecretKey, getPublicKey, nip19 } from "nostr-tools";
+
+export const lib_nostr_get_key_bytes = (hex: string): Uint8Array => {
+ return hexToBytes(hex);
+};
+
+export const lib_nostr_get_key_hex = (bytes: Uint8Array): string => {
+ return bytesToHex(bytes);
+};
+
+export const lib_nostr_key_generate = (): string => {
+ const bytes = generateSecretKey();
+ return lib_nostr_get_key_hex(bytes);
+};
+
+export const lib_nostr_nsec_encode = (secret_key_hex?: string): string | undefined => {
+ if (!secret_key_hex) return undefined;
+ const bytes = lib_nostr_get_key_bytes(secret_key_hex);
+ return nip19.nsecEncode(bytes);
+};
+
+export const lib_nostr_nsec_decode = (nsec?: string): string | undefined => {
+ if (!nsec) return undefined;
+ const decode = nip19.decode(nsec);
+ if (decode && decode.type === `nsec` && decode.data) return bytesToHex(decode.data);
+ return undefined;
+};
+
+
+export const lib_nostr_public_key = (secret_key_hex: string): string => {
+ const bytes = lib_nostr_get_key_bytes(secret_key_hex);
+ return getPublicKey(bytes);
+};
+
+export const lib_nostr_secret_key_validate = (secret_key: string): string | undefined => {
+ const is_valid_hex = lib_nostr_public_key(secret_key);
+ if (is_valid_hex) return secret_key;
+ const is_valid_nsec = lib_nostr_nsec_decode(secret_key);
+ if (is_valid_nsec) return is_valid_nsec;
+ return undefined;
+};
+\ No newline at end of file
diff --git a/utils/src/nostr/ndk.ts b/utils/src/nostr/ndk.ts
@@ -0,0 +1,75 @@
+import NDK, { NDKEvent, NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk';
+import { time_now_ms } from '..';
+import type { NostrMetadataTmp } from './types';
+
+export const ndk_init = async (opts: {
+ $ndk: NDK;
+ secret_key: string;
+}): Promise<NDKUser | undefined> => {
+ try {
+ const { $ndk: ndk, secret_key } = opts;
+ const signer = new NDKPrivateKeySigner(secret_key);
+ ndk.signer = signer;
+
+ const user = await signer.user();
+ if (user) {
+ user.ndk = ndk;
+ return user;
+ }
+ } catch (e) {
+ console.log(`(error) ndk_init `, e);
+ };
+};
+
+export const ndk_event_metadata = async (opts: {
+ $ndk: NDK;
+ $ndk_user: NDKUser;
+ metadata: NostrMetadataTmp
+}): Promise<NDKEvent | undefined> => {
+ try {
+ const { $ndk, $ndk_user } = opts;
+ const ev = await ndk_event({
+ $ndk,
+ $ndk_user,
+ basis: {
+ kind: 0,
+ content: JSON.stringify(opts.metadata),
+ },
+ });
+ return ev;
+ } catch (e) {
+ console.log(`(error) ndk_event_metadata `, e);
+ }
+};
+
+export const ndk_event = async (opts: {
+ $ndk: NDK;
+ $ndk_user: NDKUser;
+ basis: {
+ kind: number;
+ content: string;
+ tags?: string[][];
+ }
+}): Promise<NDKEvent | undefined> => {
+ try {
+ const { $ndk: ndk, $ndk_user: ndk_user, basis } = opts;
+ const time_now = time_now_ms();
+
+ const tags: string[][] = [
+ ['published_at', time_now.toString()],
+ ];
+
+ if (basis.tags && basis.tags?.length > 0) for (const tag of basis.tags) tags.push(tag);
+
+ const event: NDKEvent = new NDKEvent(ndk, {
+ kind: basis.kind,
+ pubkey: ndk_user.pubkey,
+ content: basis.content,
+ created_at: time_now,
+ tags
+ });
+ return event;
+ } catch (e) {
+ console.log(`(error) ndk_event `, e);
+ };
+};
diff --git a/utils/src/nostr/types.ts b/utils/src/nostr/types.ts
@@ -0,0 +1,19 @@
+import { type EventTemplate as NostrToolsEventTemplate } from "nostr-tools";
+
+export type NostrMetadataTmp = {
+ name?: string;
+ display_name?: string;
+ about?: string;
+ website?: string;
+ picture?: string;
+ banner?: string;
+ nip05?: string;
+ lud06?: string;
+ lud16?: string;
+ bot?: boolean;
+};
+
+export type INostrEventEventSign = {
+ secret_key: string;
+ event: NostrToolsEventTemplate;
+}
+\ No newline at end of file
diff --git a/utils/src/object.ts b/utils/src/object.ts
@@ -1,3 +1,17 @@
export const obj_en = <KeyType extends string, ValType>(object: Record<string, ValType>, parse_function: (key: string) => KeyType = (i) => i as KeyType): [KeyType, ValType][] => {
return Object.entries(object).map<[KeyType, ValType]>(([k, v]) => [parse_function(k), v])
+};
+
+export const obj_truthy_fields = (obj: Record<string, string>): boolean => {
+ return Object.values(obj).every(Boolean);
+};
+
+export const obj_result = (obj: any): string | undefined => {
+ if (`result` in obj && typeof obj.result === `string`) return obj.result;
+ return undefined;
+};
+
+export const obj_results_str = (obj: any): string[] | undefined => {
+ if (Array.isArray(obj.results)) return obj.results.map(String);
+ return undefined;
};
\ No newline at end of file
diff --git a/utils/src/types.ts b/utils/src/types.ts
@@ -3,12 +3,14 @@ import type { GeolocationCoordinatesPoint } from "./geolocation";
export type ErrorResponse<T extends object> = { error: T; };
export type ErrorMessage<T extends string> = { err: T };
+export type FieldRecord = Record<string, string>;
+
export type ResultId = { id: string; };
export type ResultPass = { pass: true; };
export type ResultsList<T> = { results: T[]; };
export type ResultObj<T> = { result: T; };
-
-export type FieldRecord = Record<string, string>;
+export type ResultPublicKey = { public_key: string; };
+export type ResultSecretKey = { secret_key: string; };
export type LocationPoint = GeolocationCoordinatesPoint & {
lat: number;
@@ -21,3 +23,10 @@ export type NumberTuple = [number, number];
export type FileBytesFormat = `kb` | `mb` | `gb`;
export type FileMimeType = string;
export type FilePath = { file_path: string; file_name: string; mime_type: FileMimeType; }
+
+
+export type NotifyMessage = {
+ message: string;
+ ok?: string;
+ cancel?: string;
+};
+\ No newline at end of file
diff --git a/utils/src/window.ts b/utils/src/window.ts
@@ -1,4 +1,13 @@
export type AppLayoutKey = 'mobile_base' | 'mobile_y';
+export type AppLayout<T1 extends string, T2 extends string> = `${T1}_${T2}`;
+export type AppHeightsResponsiveKey =
+ | `tabs`
+ | `nav`
+ | `lo_view`
+ | `view`
+ | `view_offset`
+ | `trellis_centered`;
+export type AppHeightsResponsive = AppLayout<AppHeightsResponsiveKey, AppLayoutKey>;
type ClientWindow = {
app: {