web_lib

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

commit 0eb6c962718b5b56dbc6e5173b90d736360af105
parent 027e986a4fe2ab526c992e9cee2026c702ff12ba
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Fri,  8 Aug 2025 22:37:06 +0000

utils-nostr: refactor adding `metadata` and `listing` utils, `ndk` utils, event tags utils, types. update tsconfig

Diffstat:
Mutils-nostr/package.json | 9+++++----
Mutils-nostr/src/events/lib.ts | 36+++++++++++++++++++++++++++++++++++-
Autils-nostr/src/events/listing/lib.ts | 21+++++++++++++++++++++
Autils-nostr/src/events/listing/parse.ts | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autils-nostr/src/events/metadata/lib.ts | 17+++++++++++++++++
Autils-nostr/src/events/metadata/parse.ts | 15+++++++++++++++
Dutils-nostr/src/events/parse.ts | 88-------------------------------------------------------------------------------
Mutils-nostr/src/events/subscription.ts | 3++-
Mutils-nostr/src/index.ts | 7++++++-
Mutils-nostr/src/types/lib.ts | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autils-nostr/src/types/ndk.ts | 8++++++++
Autils-nostr/src/utils/lib.ts | 1+
Autils-nostr/src/utils/ndk.ts | 14++++++++++++++
Autils-nostr/src/utils/tags.ts | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dutils-nostr/tsconfig.base.json | 11-----------
Mutils-nostr/tsconfig.cjs.json | 11+++++------
Dutils-nostr/tsconfig.esm.json | 12------------
Autils-nostr/tsconfig.json | 9+++++++++
18 files changed, 365 insertions(+), 124 deletions(-)

diff --git a/utils-nostr/package.json b/utils-nostr/package.json @@ -15,7 +15,7 @@ } }, "scripts": { - "build:esm": "tsc -p tsconfig.esm.json", + "build:esm": "tsc -p tsconfig.json", "build:cjs": "tsc -p tsconfig.cjs.json", "build": "npm run clean && npm run build:esm && npm run build:cjs", "prebuild": "npm run clean", @@ -23,13 +23,14 @@ "dev": "npm run watch", "watch": "tsc -w" }, + "publishConfig": { + "access": "public" + }, "devDependencies": { + "@radroots/tsconfig": "*", "rimraf": "^6.0.1", "typescript": "5.8.3" }, - "publishConfig": { - "access": "public" - }, "dependencies": { "@noble/curves": "^1.6.0", "@noble/hashes": "^1.4.0", diff --git a/utils-nostr/src/events/lib.ts b/utils-nostr/src/events/lib.ts @@ -1,4 +1,37 @@ -import { NDKTag } from "@nostr-dev-kit/ndk"; +import { NDKEvent, NDKTag } from "@nostr-dev-kit/ndk"; +import { NostrEventTags } from "../types/lib.js"; +import { NDKEventFigure } from "../types/ndk.js"; +import { time_now_ms } from "../utils/lib.js"; + export const get_event_tag = (tags: NDKTag[], key: string): string => tags.find(t => t[0] === key)?.[1] ?? ''; export const get_event_tags = (tags: NDKTag[], key: string): NDKTag[] => tags.filter(t => t[0] === key); + +export const ndk_event = async (opts: NDKEventFigure<{ + basis: { + kind: number; + content: string; + tags?: NostrEventTags; + } +}>): Promise<NDKEvent | undefined> => { + try { + const { ndk: ndk, ndk_user: ndk_user, basis } = opts; + const time_now = time_now_ms(); + const published_at = opts.date_published ? Math.floor(opts.date_published.getTime() / 1000).toString() + : time_now.toString() + const tags: NostrEventTags = [ + ['published_at', published_at], + ]; + if (basis.tags?.length) tags.push(...basis.tags); + const ev = new NDKEvent(ndk, { + kind: basis.kind, + pubkey: ndk_user.pubkey, + content: basis.content, + created_at: time_now, + tags + }); + return ev; + } catch (e) { + console.log(`(error) ndk_event `, e); + }; +}; +\ No newline at end of file diff --git a/utils-nostr/src/events/listing/lib.ts b/utils-nostr/src/events/listing/lib.ts @@ -0,0 +1,20 @@ +import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk"; +import { NostrEventListing } from "../../types/lib.js"; +import { NDKEventFigure } from "../../types/ndk"; +import { tags_classified } from "../../utils/tags.js"; +import { ndk_event } from "../lib.js"; + +export const ndk_event_classified = async (opts: NDKEventFigure<{ + data: NostrEventListing; +}>): Promise<NDKEvent | undefined> => { + const { ndk, ndk_user, data } = opts; + return await ndk_event({ + ndk, + ndk_user, + basis: { + kind: NDKKind.Classified, + content: ``, + tags: tags_classified(data), + }, + }); +}; +\ No newline at end of file diff --git a/utils-nostr/src/events/listing/parse.ts b/utils-nostr/src/events/listing/parse.ts @@ -0,0 +1,76 @@ +import { NDKEvent } from "@nostr-dev-kit/ndk"; +import { nostr_event_listing_schema, nostr_tag_listing_schema, nostr_tag_location_schema, nostr_tag_price_schema, nostr_tag_quantity_schema } from "../../schemas/lib.js"; +import { NostrEventListing } from "../../types/lib.js"; +import { get_event_tag, get_event_tags } from "../lib.js"; + +export const parse_nostr_listing_event = (event: NDKEvent): NostrEventListing | undefined => { + if (!event || event.kind !== 30402 || !Array.isArray(event.tags)) return; + + try { + const tags = event.tags; + + const d_tag = get_event_tag(tags, 'd'); + + const listing_raw = { + key: get_event_tag(tags, 'key'), + title: get_event_tag(tags, 'title'), + category: get_event_tag(tags, 'category'), + summary: get_event_tag(tags, 'summary'), + process: get_event_tag(tags, 'process'), + lot: get_event_tag(tags, 'lot'), + location: get_event_tag(tags, 'location'), + profile: get_event_tag(tags, 'profile'), + year: get_event_tag(tags, 'year') + }; + + const listing = nostr_tag_listing_schema.parse(listing_raw); + + const quantities = get_event_tags(tags, 'quantity') + .map(q => { + if (q.length < 3) return undefined; + return nostr_tag_quantity_schema.parse({ + amt: q[1], + unit: q[2], + label: q[3] + }); + }) + .filter(Boolean); + + const prices = get_event_tags(tags, 'price') + .map(p => { + if (p.length < 6) return undefined; + return nostr_tag_price_schema.parse({ + amt: p[1], + currency: p[2], + qty_amt: p[3], + qty_unit: p[4], + qty_key: p[5] + }); + }) + .filter(Boolean); + + const location_parts = get_event_tags(tags, 'location')[0]?.slice(1) ?? []; + + const location_raw: any = {}; + if (location_parts[0]) location_raw.primary = location_parts[0]; + if (location_parts[1]) location_raw.city = location_parts[1]; + if (location_parts[2]) location_raw.region = location_parts[2]; + if (location_parts[3]) location_raw.country = location_parts[3]; + + const location = Object.keys(location_raw).length + ? nostr_tag_location_schema.parse(location_raw) + : undefined; + + const parsed = nostr_event_listing_schema.parse({ + d_tag, + listing, + quantities, + prices, + location + }); + + return parsed; + } catch { + return undefined; + } +}; diff --git a/utils-nostr/src/events/metadata/lib.ts b/utils-nostr/src/events/metadata/lib.ts @@ -0,0 +1,16 @@ +import { NDKEvent } from "@nostr-dev-kit/ndk"; +import { NostrEventMetadata } from "../../types/lib.js"; +import { NDKEventFigure } from "../../types/ndk.js"; +import { ndk_event } from "../lib.js"; + +export const ndk_event_metadata = async (opts: NDKEventFigure<{ data: NostrEventMetadata }>): Promise<NDKEvent | undefined> => { + const { ndk, ndk_user, data } = opts; + return await ndk_event({ + ndk, + ndk_user, + basis: { + kind: 0, + content: JSON.stringify(data), + }, + }); +}; +\ No newline at end of file diff --git a/utils-nostr/src/events/metadata/parse.ts b/utils-nostr/src/events/metadata/parse.ts @@ -0,0 +1,15 @@ +import { NDKEvent } from "@nostr-dev-kit/ndk"; +import { nostr_event_metadata_schema } from "../../schemas/lib.js"; +import { type NostrEventMetadata } from "../../types/lib.js"; + +export const parse_nostr_metadata_event = (event: NDKEvent): NostrEventMetadata | undefined => { + if (!event || typeof event.content !== 'string' || event.kind !== 0) return undefined; + + try { + const parsed = JSON.parse(event.content); + const result = nostr_event_metadata_schema.parse(parsed); + return result; + } catch { + return undefined; + } +}; diff --git a/utils-nostr/src/events/parse.ts b/utils-nostr/src/events/parse.ts @@ -1,88 +0,0 @@ -import { NDKEvent } from "@nostr-dev-kit/ndk"; -import { nostr_event_listing_schema, nostr_event_metadata_schema, nostr_tag_listing_schema, nostr_tag_location_schema, nostr_tag_price_schema, nostr_tag_quantity_schema } from "../schemas/lib.js"; -import { NostrEventListing, type NostrEventMetadata } from "../types/lib.js"; -import { get_event_tag, get_event_tags } from "./lib.js"; - -export const parse_nostr_metadata_event = (event: NDKEvent): NostrEventMetadata | undefined => { - if (!event || typeof event.content !== 'string' || event.kind !== 0) return undefined; - - try { - const parsed = JSON.parse(event.content); - const result = nostr_event_metadata_schema.parse(parsed); - return result; - } catch { - return undefined; - } -}; - -export const parse_nostr_listing_event = (event: NDKEvent): NostrEventListing | undefined => { - if (!event || event.kind !== 30402 || !Array.isArray(event.tags)) return; - - try { - const tags = event.tags; - - const d_tag = get_event_tag(tags, 'd'); - - const listing_raw = { - key: get_event_tag(tags, 'key'), - title: get_event_tag(tags, 'title'), - category: get_event_tag(tags, 'category'), - summary: get_event_tag(tags, 'summary'), - process: get_event_tag(tags, 'process'), - lot: get_event_tag(tags, 'lot'), - location: get_event_tag(tags, 'location'), - profile: get_event_tag(tags, 'profile'), - year: get_event_tag(tags, 'year') - }; - - const listing = nostr_tag_listing_schema.parse(listing_raw); - - const quantities = get_event_tags(tags, 'quantity') - .map(q => { - if (q.length < 3) return undefined; - return nostr_tag_quantity_schema.parse({ - amt: q[1], - unit: q[2], - label: q[3] - }); - }) - .filter(Boolean); - - const prices = get_event_tags(tags, 'price') - .map(p => { - if (p.length < 6) return undefined; - return nostr_tag_price_schema.parse({ - amt: p[1], - currency: p[2], - qty_amt: p[3], - qty_unit: p[4], - qty_key: p[5] - }); - }) - .filter(Boolean); - - const location_parts = get_event_tags(tags, 'location')[0]?.slice(1) ?? []; - - const location_raw: any = {}; - if (location_parts[0]) location_raw.primary = location_parts[0]; - if (location_parts[1]) location_raw.city = location_parts[1]; - if (location_parts[2]) location_raw.region = location_parts[2]; - if (location_parts[3]) location_raw.country = location_parts[3]; - - const location = Object.keys(location_raw).length - ? nostr_tag_location_schema.parse(location_raw) - : undefined; - - const parsed = nostr_event_listing_schema.parse({ - d_tag, - listing, - quantities, - prices, - location - }); - - return parsed; - } catch { - return undefined; - } -}; diff --git a/utils-nostr/src/events/subscription.ts b/utils-nostr/src/events/subscription.ts @@ -1,6 +1,7 @@ import { NDKEvent } from "@nostr-dev-kit/ndk"; import { NostrEventListing, type NostrEventMetadata } from "../types/lib.js"; -import { parse_nostr_listing_event, parse_nostr_metadata_event } from "./parse.js"; +import { parse_nostr_listing_event } from "./listing/parse.js"; +import { parse_nostr_metadata_event } from "./metadata/parse.js"; export type NdkEventPayload = | { kind: 0; metadata: NostrEventMetadata; } diff --git a/utils-nostr/src/index.ts b/utils-nostr/src/index.ts @@ -1,6 +1,11 @@ export * from "./events/lib.js" -export * from "./events/parse.js" +export * from "./events/listing/lib.js" +export * from "./events/listing/parse.js" +export * from "./events/metadata/parse.js" export * from "./events/subscription.js" export * from "./keys/lib.js" export * from "./schemas/lib.js" export * from "./types/lib.js" +export * from "./types/ndk.js" +export * from "./utils/lib.js" +export * from "./utils/ndk.js" diff --git a/utils-nostr/src/types/lib.ts b/utils-nostr/src/types/lib.ts @@ -10,3 +10,78 @@ export type NostrTagPriceDiscount = z.infer<typeof nostr_tag_discount_schema> export type NostrTagPrice = z.infer<typeof nostr_tag_price_schema> export type NostrTagQuantity = z.infer<typeof nostr_tag_quantity_schema> export type NostrTagListing = z.infer<typeof nostr_tag_listing_schema> + +export type NostrEventTag = string[]; +export type NostrEventTags = NostrEventTag[]; + +export type NostrEventTagClient = { + name: string; + pubkey: string; + relay: string; +}; + +export type NostrEventTagQuantity = { + amt: string; + unit: string; + label?: string; +}; + +export type NostrEventTagPrice = { + amt: string; + currency: string; + qty_amt: string; + qty_unit: string; + qty_key: string; +}; + +export type NostrEventTagPriceDiscount = ( + { + quantity: { + ref_quantity: string; + threshold: string; + value: string; + currency: string; + } + } | + { + mass: { + unit: string; + threshold: string; + threshold_unit: string; + value: string; + currency: string; + } + } | + { + subtotal: { + threshold: string; + currency: string; + value: string; + measure: string; + } + } | + { + total: { + total_min: string; + value: string; + measure: string; + } + } +); + +export type NostrEventTagLocation = { + primary: string; + city?: string; + region?: string; + country?: string; + lat?: number; + lng?: number; +}; + +export type NostrEventTagImage = { + url: string; + size?: { + w: number; + h: number; + }; +}; +\ No newline at end of file diff --git a/utils-nostr/src/types/ndk.ts b/utils-nostr/src/types/ndk.ts @@ -0,0 +1,8 @@ +import NDK, { NDKUser } from "@nostr-dev-kit/ndk"; + +export type NDKEventFigure<T extends object> = { + ndk: NDK; + ndk_user: NDKUser; + date_published?: Date; +} & T; + diff --git a/utils-nostr/src/utils/lib.ts b/utils-nostr/src/utils/lib.ts @@ -0,0 +1 @@ +export const time_now_ms = (): number => Math.floor(new Date().getTime() / 1000); diff --git a/utils-nostr/src/utils/ndk.ts b/utils-nostr/src/utils/ndk.ts @@ -0,0 +1,13 @@ +import NDK, { NDKCacheAdapter, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; + +export const create_ndk = (explicitRelayUrls: string[], cacheAdapter?: NDKCacheAdapter): NDK => { + return new NDK({ + explicitRelayUrls, + enableOutboxModel: false, + cacheAdapter + }); +}; + +export const create_ndk_signer = (secret_key: string): NDKPrivateKeySigner => { + return new NDKPrivateKeySigner(secret_key); +}; +\ No newline at end of file diff --git a/utils-nostr/src/utils/tags.ts b/utils-nostr/src/utils/tags.ts @@ -0,0 +1,75 @@ +import ngeotags, { type InputData as NostrGeotagsInputData } from "nostr-geotags"; +import { NostrEventListing, NostrEventTag, NostrEventTagClient, NostrEventTagImage, NostrEventTagLocation, NostrEventTagPrice, NostrEventTagPriceDiscount, NostrEventTagQuantity, NostrEventTags } from "../types/lib.js"; + +export const tag_client = (opts: NostrEventTagClient, d_tag?: string): NostrEventTag => { + const tag = [`client`, opts.name]; + if (d_tag) tag.push(`31990:${opts.pubkey}:${d_tag}`); + tag.push(opts.relay); + return tag; +}; + +export const tag_classified_quantity = (opts: NostrEventTagQuantity): NostrEventTag => { + const tag = [`quantity`, opts.amt, opts.unit]; + if (opts.label) tag.push(opts.label); + return tag.map(i => i.toLowerCase()); +}; + +export const tag_classified_price = (price: NostrEventTagPrice): NostrEventTag => { + const tag = [`price`, price.amt, price.currency, price.qty_amt, price.qty_unit, price.qty_key]; + return tag.map(i => i.toLowerCase()); +}; + +export const tag_classified_price_discount = (discount: NostrEventTagPriceDiscount): NostrEventTag => { + const tag = [`price-discount-${Object.keys(discount)[0]}`]; + if (`mass` in discount) tag.push(...Object.values(discount.mass)); + else if (`quantity` in discount) tag.push(...Object.values(discount.quantity)); + else if (`subtotal` in discount) tag.push(...Object.values(discount.subtotal)); + else if (`total` in discount) tag.push(...Object.values(discount.total)); + return tag.map(i => i.toLowerCase()); +}; + +export const tag_classified_location = (opts: NostrEventTagLocation): NostrEventTag => { + if (!opts.primary) return []; + const tag = [`location`, opts.primary]; + if (opts.city) tag.push(opts.city); + if (opts.region) tag.push(opts.region); + if (opts.country) tag.push(opts.country); + return tag; +}; + +export const tags_classified_location_geotags = (opts: NostrEventTagLocation): NostrEventTags => { + const { lat, lng: lon, city, region: regionName, country } = opts; + const country_raw = country || ``; + const countryCode = country_raw && country_raw?.length <= 3 ? country_raw : undefined; + const countryName = country_raw && country_raw?.length > 3 ? country_raw : undefined; + return ngeotags({ lat, lon, city, regionName, countryCode, countryName } satisfies NostrGeotagsInputData, { geohash: true, gps: true, city: true, iso31662: true }); +}; + + +export const tag_classified_image = (opts: NostrEventTagImage): NostrEventTag => { + const tag = [`image`, opts.url]; + if (opts.size) tag.push(`${opts.size.w}x${opts.size.h}`) + return tag; +}; + +export const tags_classified = (opts: NostrEventListing): NostrEventTags => { + const { d_tag, listing, quantities, prices } = opts; + const tags: NostrEventTags = [[`d`, d_tag]]; + if (opts.client) tags.push(tag_client(opts.client, opts.d_tag)); + for (const [k, v] of Object.entries(listing)) if (v) tags.push([k, v]); + for (const quantity of quantities) { + tags.push(tag_classified_quantity(quantity)); + } + for (const price of prices) { + tags.push(tag_classified_price(price)); + } + for (const discount of opts.discounts || []) { + tags.push(tag_classified_price_discount(discount)); + } + if (opts.location) { + tags.push(tag_classified_location(opts.location)); + tags.push(...tags_classified_location_geotags(opts.location)); + } + if (opts.images) for (const image_tags of opts.images) tags.push(tag_classified_image(image_tags)); + return tags; +}; diff --git a/utils-nostr/tsconfig.base.json b/utils-nostr/tsconfig.base.json @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2019", - "strict": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "sourceMap": false, - "rootDir": "src" - } -} diff --git a/utils-nostr/tsconfig.cjs.json b/utils-nostr/tsconfig.cjs.json @@ -1,10 +1,9 @@ { - "extends": "./tsconfig.base.json", + "extends": "@radroots/internal-tsconfig/tsconfig.cjs.json", "compilerOptions": { - "outDir": "./dist/cjs", - "module": "CommonJS", - "declaration": false, - "moduleResolution": "Node" + "rootDir": "./src", + "outDir": "dist/cjs" }, - "include": ["src"] + "include": ["src"], + "exclude": ["node_modules", "dist"] } diff --git a/utils-nostr/tsconfig.esm.json b/utils-nostr/tsconfig.esm.json @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "outDir": "./dist/esm", - "module": "NodeNext", - "declaration": true, - "declarationDir": "./dist/types", - "moduleResolution": "NodeNext", - "emitDeclarationOnly": false - }, - "include": ["src"] -} diff --git a/utils-nostr/tsconfig.json b/utils-nostr/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@radroots/internal-tsconfig/tsconfig.esm.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "dist/esm" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +}