From 7bb6d8c54a00db44668ac805f1c6d9580a5ab2a5 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Thu, 1 Aug 2024 15:33:37 -0700 Subject: [PATCH] Add store package --- package-lock.json | 202 +++++++++++++++++++++++++++++++++- packages/content/src/index.ts | 2 +- packages/lib/src/Tools.ts | 4 + packages/store/.eslintignore | 2 + packages/store/README.md | 3 + packages/store/package.json | 36 ++++++ packages/store/src/index.ts | 199 +++++++++++++++++++++++++++++++++ packages/store/tsc-multi.json | 7 ++ packages/store/tsconfig.json | 11 ++ packages/util/src/Kinds.ts | 8 +- 10 files changed, 469 insertions(+), 5 deletions(-) create mode 100644 packages/store/.eslintignore create mode 100644 packages/store/README.md create mode 100644 packages/store/package.json create mode 100644 packages/store/src/index.ts create mode 100644 packages/store/tsc-multi.json create mode 100644 packages/store/tsconfig.json diff --git a/package-lock.json b/package-lock.json index f659d8f..47db5b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,18 @@ "node": ">=0.10.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.24.2", "dev": true, @@ -201,6 +213,49 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@noble/ciphers": { "version": "0.5.2", "license": "MIT", @@ -324,6 +379,11 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, "node_modules/@types/events": { "version": "3.0.3", "license": "MIT" @@ -557,13 +617,16 @@ "resolved": "packages/net", "link": true }, + "node_modules/@welshman/store": { + "resolved": "packages/store", + "link": true + }, "node_modules/@welshman/util": { "resolved": "packages/util", "link": true }, "node_modules/acorn": { "version": "8.11.3", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -659,6 +722,14 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/array-union": { "version": "2.1.0", "dev": true, @@ -675,6 +746,14 @@ "node": ">=0.10.0" } }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "dev": true, @@ -793,6 +872,18 @@ "node": ">=12" } }, + "node_modules/code-red": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", + "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1", + "acorn": "^8.10.0", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "dev": true, @@ -827,6 +918,18 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/debug": { "version": "4.3.4", "dev": true, @@ -879,6 +982,14 @@ "dev": true, "license": "MIT" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "dev": true, @@ -1202,6 +1313,14 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "dev": true, @@ -1727,6 +1846,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/is-stream": { "version": "2.0.1", "dev": true, @@ -1830,6 +1957,11 @@ "dev": true, "license": "MIT" }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" + }, "node_modules/locate-path": { "version": "6.0.0", "dev": true, @@ -1865,6 +1997,14 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/map-obj": { "version": "4.3.0", "dev": true, @@ -1876,6 +2016,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + }, "node_modules/meow": { "version": "9.0.0", "dev": true, @@ -2234,6 +2379,16 @@ "node": ">=8" } }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, "node_modules/picocolors": { "version": "1.0.0", "dev": true, @@ -2650,6 +2805,14 @@ "node": ">=8" } }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "dev": true, @@ -2778,6 +2941,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svelte": { + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz", + "integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/estree": "^1.0.1", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.4", + "periscopic": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/synckit": { "version": "0.8.8", "dev": true, @@ -3159,6 +3346,19 @@ "typescript": "~5.1.6" } }, + "packages/store": { + "name": "@welshman/store", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "svelte": "^4.2.18" + }, + "devDependencies": { + "gts": "^5.0.1", + "tsc-multi": "^1.1.0", + "typescript": "~5.1.6" + } + }, "packages/util": { "name": "@welshman/util", "version": "0.0.22", diff --git a/packages/content/src/index.ts b/packages/content/src/index.ts index 0062c56..4e1fcbe 100644 --- a/packages/content/src/index.ts +++ b/packages/content/src/index.ts @@ -167,7 +167,7 @@ export const parseAddress = (text: string, context: ParseContext): ParsedAddress } export const parseCashu = (text: string, context: ParseContext): ParsedCashu | void => { - const [value] = text.match(/^(cashu)[\d\w=]{50,5000}/i) || [] + const [value] = text.match(/^(cashu)[-\d\w=]{50,5000}/i) || [] if (value) { return {type: ParsedType.Cashu, value, raw: value} diff --git a/packages/lib/src/Tools.ts b/packages/lib/src/Tools.ts index f6898f9..135b108 100644 --- a/packages/lib/src/Tools.ts +++ b/packages/lib/src/Tools.ts @@ -83,6 +83,10 @@ export const mapVals = >(f: (v: any) => any, x: T) return r as T } +export const mergeLeft = >(a: T, b: T) => ({...b, ...a}) + +export const mergeRight = >(a: T, b: T) => ({...a, ...b}) + export const between = (low: number, high: number, n: number) => n > low && n < high export const randomId = (): string => Math.random().toString().slice(2) diff --git a/packages/store/.eslintignore b/packages/store/.eslintignore new file mode 100644 index 0000000..43e824a --- /dev/null +++ b/packages/store/.eslintignore @@ -0,0 +1,2 @@ +build +normalize-url diff --git a/packages/store/README.md b/packages/store/README.md new file mode 100644 index 0000000..b0db427 --- /dev/null +++ b/packages/store/README.md @@ -0,0 +1,3 @@ +# @welshman/store [![version](https://badgen.net/npm/v/@welshman/store)](https://npmjs.com/package/@welshman/store) + +Utilities for dealing with svelte stores when using welshman. diff --git a/packages/store/package.json b/packages/store/package.json new file mode 100644 index 0000000..8972802 --- /dev/null +++ b/packages/store/package.json @@ -0,0 +1,36 @@ +{ + "name": "@welshman/store", + "version": "0.0.1", + "author": "hodlbod", + "license": "MIT", + "description": "A collection of utilities based on svelte/store for use with welshman", + "publishConfig": { + "access": "public" + }, + "type": "module", + "files": [ + "build" + ], + "types": "./build/src/index.d.ts", + "exports": { + ".": { + "types": "./build/src/index.d.ts", + "import": "./build/src/index.mjs", + "require": "./build/src/index.cjs" + } + }, + "scripts": { + "pub": "npm run lint && npm run build && npm publish", + "build": "gts clean && tsc-multi", + "lint": "gts lint", + "fix": "gts fix" + }, + "devDependencies": { + "gts": "^5.0.1", + "tsc-multi": "^1.1.0", + "typescript": "~5.1.6" + }, + "dependencies": { + "svelte": "^4.2.18" + } +} diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts new file mode 100644 index 0000000..4550530 --- /dev/null +++ b/packages/store/src/index.ts @@ -0,0 +1,199 @@ +import {throttle} from "throttle-debounce" +import {derived} from "svelte/store" +import type {Readable, Writable} from "svelte/store" +import {identity, batch, partition, first} from "@welshman/lib" +import type {Repository} from "@welshman/util" +import {matchFilters, getIdAndAddress, getIdFilters} from "@welshman/util" +import type {Filter, TrustedEvent} from "@welshman/util" + +export const getter = (store: Readable) => { + let value: T + + store.subscribe((newValue: T) => { + value = newValue + }) + + return () => value +} + +type Stop = () => void +type Sub = (x: T) => void +type Start = (set: Sub) => Stop + +export const custom = (start: Start, opts: {throttle?: number} = {}) => { + const subs: Sub[] = [] + + let value: T + let stop: () => void + + return { + subscribe: (sub: Sub) => { + if (opts.throttle) { + sub = throttle(opts.throttle, sub) + } + + if (subs.length === 0) { + stop = start((newValue: T) => { + for (const sub of subs) { + sub(newValue) + } + + value = newValue + }) + } + + subs.push(sub) + sub(value) + + return () => { + subs.splice( + subs.findIndex(s => s === sub), + 1, + ) + + if (subs.length === 0) { + stop() + } + } + }, + } +} + +export function withGetter(store: Writable): Writable & {get: () => T} +export function withGetter(store: Readable): Readable & {get: () => T} +export function withGetter(store: Readable | Writable) { + return {...store, get: getter(store)} +} + +export const throttled = (delay: number, store: Readable) => + custom(set => store.subscribe(throttle(delay, set))) + +export const createEventStore = (repository: Repository) => { + let subs: Sub[] = [] + + const onUpdate = throttle(300, () => { + const $events = repository.dump() + + for (const sub of subs) { + sub($events) + } + }) + + return { + get: () => repository.dump(), + set: (events: TrustedEvent[]) => repository.load(events), + subscribe: (f: Sub) => { + f(repository.dump()) + + subs.push(f) + + if (subs.length === 1) { + repository.on("update", onUpdate) + } + + return () => { + subs = subs.filter(x => x !== f) + + if (subs.length === 0) { + repository.off("update", onUpdate) + } + } + }, + } +} + +export const deriveEventsMapped = ({ + filters, + repository, + eventToItem, + itemToEvent, + includeDeleted = false, +}: { + filters: Filter[] + repository: Repository, + eventToItem: (event: TrustedEvent) => T + itemToEvent: (item: T) => TrustedEvent + includeDeleted?: boolean +}) => + custom(setter => { + let data = repository.query(filters, {includeDeleted}).map(eventToItem).filter(identity) + + setter(data) + + const onUpdate = batch(300, (updates: {added: TrustedEvent[]; removed: Set}[]) => { + const removed = new Set() + const added = new Map() + + // Apply updates in order + for (const update of updates) { + for (const event of update.added.values()) { + added.set(event.id, event) + } + + for (const id of update.removed) { + removed.add(id) + added.delete(id) + } + } + + let dirty = false + for (const event of added.values()) { + if (matchFilters(filters, event)) { + const item = eventToItem(event) + + if (item) { + dirty = true + data.push(item) + } + } + } + + if (!includeDeleted && removed.size > 0) { + const [deleted, ok] = partition( + (item: T) => getIdAndAddress(itemToEvent(item)).some(id => removed.has(id)), + data, + ) + + if (deleted.length > 0) { + dirty = true + data = ok + } + } + + if (dirty) { + setter(data) + } + }) + + repository.on("update", onUpdate) + + return () => repository.off("update", onUpdate) + }) + +export const deriveEvents = (repository: Repository, opts: {filters: Filter[]; includeDeleted?: boolean}) => + deriveEventsMapped({ + ...opts, + repository, + eventToItem: identity, + itemToEvent: identity, + }) + +export const deriveEvent = (repository: Repository, idOrAddress: string) => + derived( + deriveEvents(repository, { + filters: getIdFilters([idOrAddress]), + includeDeleted: true, + }), + first + ) + +export const deriveIsDeletedByAddress = (repository: Repository, event: TrustedEvent) => + custom(setter => { + setter(repository.isDeletedByAddress(event)) + + const onUpdate = batch(300, () => setter(repository.isDeletedByAddress(event))) + + repository.on("update", onUpdate) + + return () => repository.off("update", onUpdate) + }) diff --git a/packages/store/tsc-multi.json b/packages/store/tsc-multi.json new file mode 100644 index 0000000..6c37019 --- /dev/null +++ b/packages/store/tsc-multi.json @@ -0,0 +1,7 @@ +{ + "targets": [ + {"extname": ".cjs", "module": "commonjs"}, + {"extname": ".mjs", "module": "esnext", "moduleResolution": "node"} + ], + "projects": ["tsconfig.json"] +} diff --git a/packages/store/tsconfig.json b/packages/store/tsconfig.json new file mode 100644 index 0000000..15d351a --- /dev/null +++ b/packages/store/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../node_modules/gts/tsconfig-google.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "build", + "esModuleInterop": true, + "skipLibCheck": true, + "lib": ["esnext", "dom", "dom.iterable"] + }, + "include": ["**/*.ts"] +} diff --git a/packages/util/src/Kinds.ts b/packages/util/src/Kinds.ts index 4122ef5..69a2bba 100644 --- a/packages/util/src/Kinds.ts +++ b/packages/util/src/Kinds.ts @@ -20,7 +20,6 @@ export const GROUP_CHAT_THREAD = 11 export const GROUP_CHAT_THREAD_REPLY = 12 export const SEAL = 13 export const DIRECT_MESSAGE = 14 -export const READ_RECEIPT = 15 export const GENERIC_REPOST = 16 export const CHANNEL_CREATE = 40 export const CHANNEL_UPDATE = 41 @@ -35,8 +34,8 @@ export const WRAP_NIP04 = 1060 export const FILE_METADATA = 1063 export const LIVE_CHAT_MESSAGE = 1311 export const GIT_PATCH = 1617 -export const GIT_ISSUE = 1617 -export const GIT_REPLY = 1617 +export const GIT_ISSUE = 1621 +export const GIT_REPLY = 1622 export const GIT_STATUS_OPEN = 1630 export const GIT_STATUS_COMPLETE = 1631 export const GIT_STATUS_CLOSED = 1632 @@ -103,6 +102,9 @@ export const TOPICS = 10015 export const EMOJIS = 10030 export const INBOX_RELAYS = 10050 export const FILE_SERVERS = 10096 +export const SEEN_GENERAL = 10115 +export const SEEN_CONTEXT = 10116 +export const SEEN_CONVERSATION = 10117 export const LIGHTNING_PUB_RPC = 21000 export const CLIENT_AUTH = 22242 export const WALLET_INFO = 13194