mirror of
https://github.com/sern-handler/tools
synced 2026-06-06 01:16:59 +00:00
rx: death
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
||||
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
||||
|
||||
name: Node.js Package
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
test-and-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 17
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
run_install: |
|
||||
- recursive: true
|
||||
args: [--strict-peer-dependencies]
|
||||
- run: pnpm install
|
||||
- run: pnpm test
|
||||
- run: pnpm build
|
||||
- uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
access: "public"
|
||||
13
packages/rx/.github/workflows/release-please.yml
vendored
13
packages/rx/.github/workflows/release-please.yml
vendored
@@ -1,13 +0,0 @@
|
||||
name: release-please
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
release-please:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: google-github-actions/release-please-action@v3.1.2
|
||||
with:
|
||||
release-type: node
|
||||
package-name: release-please-action
|
||||
bump-patch-for-minor-pre-major: true
|
||||
117
packages/rx/.gitignore
vendored
117
packages/rx/.gitignore
vendored
@@ -1,117 +0,0 @@
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
8
packages/rx/.idea/.gitignore
generated
vendored
8
packages/rx/.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
7
packages/rx/.idea/discord.xml
generated
7
packages/rx/.idea/discord.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
9
packages/rx/.idea/misc.xml
generated
9
packages/rx/.idea/misc.xml
generated
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
<component name="ProjectRootManager">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
packages/rx/.idea/modules.xml
generated
8
packages/rx/.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/rx.iml" filepath="$PROJECT_DIR$/rx.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
packages/rx/.idea/vcs.xml
generated
6
packages/rx/.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,12 +0,0 @@
|
||||
src
|
||||
build
|
||||
.editorconfig
|
||||
.eslintrc.json
|
||||
.gitignore
|
||||
.travis.yml
|
||||
rollup.config.dev.js
|
||||
rollup.config.js
|
||||
yarn-error.log
|
||||
vitest.config.ts
|
||||
test/**/*
|
||||
examples
|
||||
@@ -1,51 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
### [0.0.3-alpha](https://github.com/sern-handler/rx/compare/v0.0.2-alpha...v0.0.3-alpha) (2023-02-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add optional takeN index to toCollection ([8f6ac9f](https://github.com/sern-handler/rx/commit/8f6ac9fa20fff489eed1fcb2b01c74efad4bbee2))
|
||||
* parameterize source ([feb4776](https://github.com/sern-handler/rx/commit/feb4776d0be2c71025bd18165e097f786dfc17fb))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* generics ([51aba2a](https://github.com/sern-handler/rx/commit/51aba2a444067505b9b39cd6347c24084b733ef9))
|
||||
* generics ([1d1f946](https://github.com/sern-handler/rx/commit/1d1f946cc7fbd9656acdff13f98d25de03d9ebb0))
|
||||
* update package.json ([0771676](https://github.com/sern-handler/rx/commit/07716765731be34407f889ed0d58aca36c407f0a))
|
||||
|
||||
### [0.0.2-alpha](https://github.com/sern-handler/rx/compare/v0.0.1-alpha...v0.0.2-alpha) (2023-02-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add discord.js examples ([91de33d](https://github.com/sern-handler/rx/commit/91de33dda327843caf1b7c0b77cbf42e096f8cbe))
|
||||
* build for esm only ([5006f96](https://github.com/sern-handler/rx/commit/5006f96149432fa563e4db86a5fa25cf0df751d8))
|
||||
* buttons ([d2ec056](https://github.com/sern-handler/rx/commit/d2ec056f4d1e4ab950266f693b385384c9cfc789))
|
||||
* remove pipeline option ([baecfb6](https://github.com/sern-handler/rx/commit/baecfb6bb1de624aa2897492f54788d4d3bd4402))
|
||||
* testing composable-style functions ([cd1fcf7](https://github.com/sern-handler/rx/commit/cd1fcf79825968cf1b6b75816bdc1da9add96124))
|
||||
* update dependencies and tsup ([2da5fd0](https://github.com/sern-handler/rx/commit/2da5fd0b7fc360c370bae4ba4d63144977790f13))
|
||||
|
||||
### 0.0.1-alpha (2023-02-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add more methods ([7647d92](https://github.com/sern-handler/rx/commit/7647d925865098a80859408731c55f55bb3661ca))
|
||||
* add type predicates for djs bindings ([769bdf6](https://github.com/sern-handler/rx/commit/769bdf65b408acbdcb82d6851947bb05e5791c3b))
|
||||
* adding scripts and some DJS bindings ([6926098](https://github.com/sern-handler/rx/commit/69260980d71a30ba5541394e420409128d4b4e9b))
|
||||
* completeOnFirst works? ([4e967ed](https://github.com/sern-handler/rx/commit/4e967ed096882f612926a94bd54305f9cf093ed8))
|
||||
* first commit ([0fc4004](https://github.com/sern-handler/rx/commit/0fc40048815078fc896fef5a618b7c732268cc32))
|
||||
* remove redundant count operator and pause completeOnFirst ([eaeee0f](https://github.com/sern-handler/rx/commit/eaeee0fb6f0e6bde4b8a565f1cc1bfb1e5c069a7))
|
||||
* update and work ([c1047a6](https://github.com/sern-handler/rx/commit/c1047a6fefb4f59f7a275db6b0d86664d5b10cdb))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* weird thing where instanceof is not working ([eea0286](https://github.com/sern-handler/rx/commit/eea0286bcc684266cabdfc8e98cfe0e4f81afc22))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 0.0.1-alpha ([fd85195](https://github.com/sern-handler/rx/commit/fd85195fb3f15fcb46ebe2057f3f449a741f7944))
|
||||
@@ -1,7 +0,0 @@
|
||||
# @sern/rx
|
||||
A package extending rxjs for discord api libraries
|
||||
|
||||
this package offers a reactive, declarative solution using the wonderful [rxjs](https://github.com/ReactiveX/rxjs) to the current collectors
|
||||
and listeners in many discord bot libraries. It's still in alpha, but most operators and functions have been tested using vitest.
|
||||
|
||||
- View [examples](https://github.com/sern-handler/rx/tree/main/examples) to get started!
|
||||
@@ -1,18 +0,0 @@
|
||||
import {composable, useMutableState} from "../src/index";
|
||||
import {fromEvent} from "rxjs";
|
||||
import {EventEmitter} from "events";
|
||||
|
||||
|
||||
//hypothetical EventEmitter
|
||||
declare const ee : EventEmitter
|
||||
|
||||
const [str, setData, manager] = useMutableState("root")
|
||||
const messageCreate = fromEvent(ee, 'messageCreate')
|
||||
|
||||
composable<string>((close, message) => {
|
||||
|
||||
if(message === "!ping") {
|
||||
setData(message)
|
||||
}
|
||||
|
||||
},[messageCreate])
|
||||
@@ -1,39 +0,0 @@
|
||||
import {ButtonInteraction, ChatInputCommandInteraction, Collection, ComponentType} from "discord.js";
|
||||
import {filter, firstValueFrom, take} from 'rxjs'
|
||||
import {asyncTask, DJS, on, once, time} from "../../src/index.js";
|
||||
import {clientEvent, matchesCustomId} from "../../src/djs";
|
||||
|
||||
//Hypothetical interaction source
|
||||
declare const ctx : ChatInputCommandInteraction
|
||||
|
||||
// create a collector that accepts distinct interactions.
|
||||
// A timer is set to 5 seconds and it will emit one collection where its key is the interaction's id and value the interaction.
|
||||
const reactiveCollector = on<ButtonInteraction>('collect', ctx.channel.createMessageComponentCollector({ componentType: ComponentType.Button }))
|
||||
.pipe(
|
||||
DJS.distinctCustomId,
|
||||
time(5000),
|
||||
DJS.toCollection(src => [src.id, src], 4),
|
||||
)
|
||||
//Collection as promise
|
||||
const promisedCollection = firstValueFrom(reactiveCollector, { defaultValue : new Collection() })
|
||||
|
||||
//Collection subscription
|
||||
const subscriptionCollection = reactiveCollector.subscribe({
|
||||
next: (collection) => console.log(collection),
|
||||
error: (err) => console.log(err),
|
||||
complete: () => console.log("The collector has finished") //synonymous to the 'end' event for basic discord.js collectors!
|
||||
})
|
||||
|
||||
//Creates a base button handler that matches the given customId
|
||||
const buttonHandler = clientEvent(ctx.client, 'interactionCreate')
|
||||
.pipe(
|
||||
filter(DJS.isButton), matchesCustomId("i-miss-her"),
|
||||
)
|
||||
|
||||
//From the buttonHandler, defer the update and set a timeout of 60_000. Then it executes once, finally closing the subscription
|
||||
buttonHandler.pipe(
|
||||
asyncTask( b => b.deferUpdate()),
|
||||
time(60_000 ), // 1 minute
|
||||
once((b) => b.editReply(`You clicked a button once`))
|
||||
).subscribe()
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"name": "@sern/rx",
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs"
|
||||
},
|
||||
"./djs": {
|
||||
"import": "./dist/djs/index.mjs"
|
||||
}
|
||||
},
|
||||
"version": "0.0.3-alpha",
|
||||
"description": "rxjs operators and utils for discord bot projects",
|
||||
"packageManager": "pnpm@7.24.3",
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"tdd": "vitest --config ./vitest.config.ts",
|
||||
"test": "vitest run --config ./vitest.config.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "jacoobes",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.15",
|
||||
"discord.js": "^14.8.1",
|
||||
"rxjs": "7.8.1",
|
||||
"tsup": "^6.7.0",
|
||||
"typescript": "5.0.4",
|
||||
"vitest": "^0.28.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"discord.js": ">= 14.8.0"
|
||||
},
|
||||
"node": ">=18.0",
|
||||
"type": "module",
|
||||
"types": "./dist\\index.d.ts",
|
||||
"directories": {
|
||||
"example": "examples",
|
||||
"test": "test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sern-handler/rx.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/sern-handler/rx/issues"
|
||||
},
|
||||
"homepage": "https://github.com/sern-handler/rx#readme"
|
||||
}
|
||||
1754
packages/rx/pnpm-lock.yaml
generated
1754
packages/rx/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,95 +0,0 @@
|
||||
import type {
|
||||
AnySelectMenuInteraction,
|
||||
ButtonInteraction, ChannelSelectMenuInteraction, ClientEvents,
|
||||
MentionableSelectMenuInteraction,
|
||||
ModalSubmitInteraction,
|
||||
RoleSelectMenuInteraction,
|
||||
StringSelectMenuInteraction,
|
||||
UserSelectMenuInteraction,
|
||||
} from 'discord.js'
|
||||
import { Collection } from "discord.js";
|
||||
import {
|
||||
distinctUntilKeyChanged,
|
||||
filter,
|
||||
MonoTypeOperatorFunction,
|
||||
Observable,
|
||||
OperatorFunction,
|
||||
pipe,
|
||||
reduce,
|
||||
take
|
||||
} from "rxjs";
|
||||
import type {Client} from "discord.js";
|
||||
import {CustomId, on} from "../index.js";
|
||||
|
||||
/**
|
||||
* discord.js binding to ensure an event is {@link ButtonInteraction}
|
||||
* @param e
|
||||
*/
|
||||
export function isButton(e: unknown): e is ButtonInteraction {
|
||||
return (e as ButtonInteraction)?.isButton?.() ?? false
|
||||
}
|
||||
|
||||
export function matchesCustomId<T extends CustomId>(cmpt: string) : MonoTypeOperatorFunction<T> {
|
||||
return filter(i => i.customId == cmpt)
|
||||
}
|
||||
|
||||
/**
|
||||
* discord.js binding to ensure an event is {@link AnySelectMenuInteraction}
|
||||
* Reminder that AnySelectMenuInteraction will be renamed to SelectMenuInteraction in the next major
|
||||
* @param e
|
||||
*/
|
||||
export function isSelectMenu(e: unknown): e is AnySelectMenuInteraction {
|
||||
return (e as AnySelectMenuInteraction)?.isAnySelectMenu?.() ?? false
|
||||
}
|
||||
|
||||
export function isRoleSelectMenu(e: unknown): e is RoleSelectMenuInteraction {
|
||||
return (e as RoleSelectMenuInteraction)?.isRoleSelectMenu?.() ?? false
|
||||
}
|
||||
|
||||
export function isStringSelectMenu(e: unknown): e is StringSelectMenuInteraction {
|
||||
return (e as StringSelectMenuInteraction)?.isStringSelectMenu?.() ?? false
|
||||
}
|
||||
|
||||
export function isChannelSelectMenu(e: unknown): e is ChannelSelectMenuInteraction {
|
||||
return (e as ChannelSelectMenuInteraction)?.isChannelSelectMenu?.() ?? false
|
||||
}
|
||||
|
||||
export function isUserSelectMenu(e: unknown): e is UserSelectMenuInteraction {
|
||||
return (e as UserSelectMenuInteraction)?.isUserSelectMenu?.() ?? false;
|
||||
}
|
||||
|
||||
export function isMentionableSelectMenu(e: unknown) : e is MentionableSelectMenuInteraction {
|
||||
return (e as MentionableSelectMenuInteraction)?.isMentionableSelectMenu?.() ?? false;
|
||||
}
|
||||
|
||||
export function isModal(e: unknown) : e is ModalSubmitInteraction {
|
||||
return (e as ModalSubmitInteraction)?.isModalSubmit?.() ?? false;
|
||||
|
||||
}
|
||||
/**
|
||||
* emits source stream only if the custom id is unique
|
||||
*/
|
||||
export const distinctCustomId = <T extends { customId: string }> (src$: Observable<T>) => src$.pipe(distinctUntilKeyChanged('customId'));
|
||||
|
||||
//Collects all source emissions and emits them as a discord.js Collection when the source completes.
|
||||
export function toCollection<Source, Key, Value>(
|
||||
transform: (src : Source) => [Key, Value],
|
||||
takeN?: number
|
||||
): OperatorFunction<Source, Collection<Key,Value>> {
|
||||
return pipe(
|
||||
takeN ? take(takeN) : pipe(),
|
||||
reduce((coll, src) => {
|
||||
return coll.set(...transform(src));
|
||||
}, new Collection<Key, Value>())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a typed client event observable
|
||||
* @param c
|
||||
* @param key
|
||||
*/
|
||||
export function clientEvent<T extends keyof ClientEvents>(c : Client, key : T) {
|
||||
return on(key, c) as Observable<ClientEvents[T]>
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
import type {EventEmitter} from "events";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
defer,
|
||||
fromEvent,
|
||||
mergeMap,
|
||||
type MonoTypeOperatorFunction,
|
||||
Observable,
|
||||
Subject, switchMap,
|
||||
take,
|
||||
takeUntil,
|
||||
tap,
|
||||
timer,
|
||||
concatMap,
|
||||
pipe,
|
||||
catchError
|
||||
} from "rxjs";
|
||||
|
||||
export * as DJS from './djs/index.js'
|
||||
|
||||
export interface CustomId {
|
||||
customId: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an observable using fromEvent & casts it to Observable<O>
|
||||
* @param name
|
||||
* @param e
|
||||
*/
|
||||
export function on<O>(name: string, e: EventEmitter): Observable<O> {
|
||||
return fromEvent(e, name) as Observable<O>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do some task that requires asynchronous await API and reedits the source
|
||||
* @param cb
|
||||
*/
|
||||
export function asyncTask<Event>(cb: (e: Event) => Promise<unknown>) {
|
||||
return (src$: Observable<Event>) => src$.pipe(switchMap(cb), mergeMap(() => src$))
|
||||
}
|
||||
|
||||
/**
|
||||
* the operator function takeUntil with a timer. Completes the source stream after time has been reached
|
||||
* @param time a Date object or number (milliseconds)
|
||||
*/
|
||||
export function time<Event>(time: Date | number) {
|
||||
return takeUntil(timer(time)) as MonoTypeOperatorFunction<Event>
|
||||
}
|
||||
|
||||
/**
|
||||
* @param action
|
||||
* Responds to one interaction and calls { action }.
|
||||
* Unsubscribes and closes subscription afterwards
|
||||
*/
|
||||
export function once<Event>(action: (e: Event) => unknown) {
|
||||
return (source$: Observable<Event>) =>
|
||||
defer(() => {
|
||||
let isFirst = true;
|
||||
return source$.pipe(
|
||||
tap(v => {
|
||||
if (isFirst) {
|
||||
action(v);
|
||||
isFirst = false;
|
||||
}
|
||||
}),
|
||||
take(1)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @Experimental - This api may be moved to another package, deleted, or anything. consider it for playing around only
|
||||
* The scope which a composable function acts on.
|
||||
*/
|
||||
class ComposableScope<Source> {
|
||||
private symbol = Symbol("nothing");
|
||||
|
||||
constructor(
|
||||
private updater: Subject<Source>,
|
||||
private listeners: (BehaviorSubject<unknown> | Observable<unknown>)[]
|
||||
) {}
|
||||
|
||||
private executeIfUpdaterNotClosed(action: () => any) {
|
||||
if (!this.updater.closed) {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
listen(recomposable: (close: () => void, source: Source) => unknown) {
|
||||
const updaterFinalizer = () => this.updater.complete();
|
||||
this.updater
|
||||
.subscribe({
|
||||
next: data => recomposable(updaterFinalizer, data),
|
||||
complete: () => {
|
||||
this.listeners.forEach(s => {
|
||||
if (s instanceof BehaviorSubject) {
|
||||
s.unsubscribe()
|
||||
}
|
||||
})
|
||||
},
|
||||
error: () => {
|
||||
this.listeners.forEach(s => {
|
||||
if (s instanceof BehaviorSubject) {
|
||||
s.unsubscribe()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (this.listeners.length === 0) {
|
||||
this.updater.complete()
|
||||
} else {
|
||||
for (const subject of this.listeners) {
|
||||
subject.subscribe((data) => {
|
||||
if (subject instanceof Observable) {
|
||||
this.executeIfUpdaterNotClosed(() => {
|
||||
this.updater.next(data as Source)
|
||||
})
|
||||
} else {
|
||||
this.executeIfUpdaterNotClosed(() => {
|
||||
this.updater.next(this.symbol as Source)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Experimental - This api may be moved to another package, deleted, or anything. consider it for playing around only
|
||||
* @param seed - The beginning value. This works similarly to react useState or compose remember mutableStateOf
|
||||
*/
|
||||
export function useMutableState<T>(seed: T) {
|
||||
const stateManager = new BehaviorSubject(seed)
|
||||
return [
|
||||
() => stateManager.getValue(),
|
||||
(vl: T) => stateManager.next(vl),
|
||||
stateManager,
|
||||
] as const
|
||||
}
|
||||
|
||||
export type Listeners = (BehaviorSubject<unknown> | Observable<unknown>)[]
|
||||
|
||||
/**
|
||||
* @Experimental - This api may be moved to another package, deleted, or anything. consider it for playing around only
|
||||
* Creates a scope that can executed as many times as needed, provided it is listening to data.
|
||||
* @param scope - Executes callback until
|
||||
* @param listeners
|
||||
*/
|
||||
export function composable<Source>(
|
||||
scope: (close: () => void, source: Source) => unknown,
|
||||
listeners: Listeners
|
||||
) {
|
||||
const composableScope = new ComposableScope<Source>(new Subject(), listeners);
|
||||
composableScope.listen(scope)
|
||||
}
|
||||
|
||||
|
||||
export function filterMap<I, O>(cb: (i: I) => Observable<O> | Observable<never>) {
|
||||
return concatMap(cb)
|
||||
}
|
||||
type StreamResult =
|
||||
| { crash: true, error: Error }
|
||||
| { crash: false, error?: never }
|
||||
|
||||
export function failableStream<I, O(
|
||||
pipeline: typeof pipe,
|
||||
ehandler: (e: any) => StreamResult
|
||||
) {
|
||||
|
||||
return pipe(
|
||||
pipeline,
|
||||
catchError((err, caught) => {
|
||||
const { crash, error } = ehandler(err)
|
||||
if(crash) {
|
||||
throw error
|
||||
} else {
|
||||
return caught;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
}
|
||||
//
|
||||
// /**
|
||||
// *
|
||||
// * @param notifiers Should be terminal operators. Meaning, these should have some logic to close its subscription
|
||||
// */
|
||||
// export function completeOnFirst<Event>(notifiers: MonoTypeOperatorFunction<Event>[]) {
|
||||
// return (src: Observable<Event>) =>
|
||||
// src.pipe(raceWith(notifiers.map(notif => src.pipe(notif))));
|
||||
// }
|
||||
//
|
||||
//
|
||||
@@ -1,100 +0,0 @@
|
||||
import {describe, expect, it, vi, beforeEach, afterEach} from 'vitest'
|
||||
import {composable, once, time, useMutableState} from '../src/index'
|
||||
import {of, from } from 'rxjs'
|
||||
describe('sern/rx common operators', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach( () => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it("subscription completes after 2 seconds", () => {
|
||||
const completion = vi.fn(() => void 0)
|
||||
of("x").pipe(time(5_000)).subscribe({
|
||||
complete: () => {
|
||||
completion()
|
||||
}
|
||||
})
|
||||
vi.advanceTimersByTime(2000)
|
||||
expect(completion).toHaveBeenCalled()
|
||||
});
|
||||
|
||||
it("should call once and close", () => {
|
||||
const tapOnce = vi.fn((a : number) => a)
|
||||
const sub = from([1,2,3,4,5]).pipe(once(tapOnce)).subscribe()
|
||||
|
||||
expect(tapOnce).toHaveBeenCalledOnce()
|
||||
expect(sub.closed).toBe(true)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe("composable", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
it("should close without calling unsubscribe manually", () => {
|
||||
const [data, setData, manager] = useMutableState("shiddd")
|
||||
composable((close) => {
|
||||
if(data() === "shiddd") {
|
||||
setData("pooo")
|
||||
} else close()
|
||||
}, [manager]);
|
||||
expect(manager.closed).toBe(true)
|
||||
})
|
||||
|
||||
it("should invoke size of string's length", () => {
|
||||
let invoke = 0
|
||||
const theString = "shiddd"
|
||||
const [data, setData, manager] = useMutableState(theString)
|
||||
composable((close) => {
|
||||
const str = data()
|
||||
if(str.length != 0) {
|
||||
invoke++
|
||||
setData(str.slice(0, str.length-1))
|
||||
} else {
|
||||
close()
|
||||
}
|
||||
}, [manager]);
|
||||
expect(manager.closed).toBe(true)
|
||||
expect(invoke).toBe(theString.length)
|
||||
})
|
||||
|
||||
it("should act as an Observable stream", () => {
|
||||
let invoke = 0
|
||||
composable(() => {
|
||||
invoke++
|
||||
}, [from([1,2,3,4,5])])
|
||||
expect(invoke).toBe(5)
|
||||
|
||||
})
|
||||
|
||||
it("should concat a string based on event", () => {
|
||||
|
||||
const [data, setData] = useMutableState("")
|
||||
composable((_, source) => {
|
||||
setData(data() + source)
|
||||
}, [from([1,2,3,4,5])])
|
||||
expect(data()).toBe("12345")
|
||||
})
|
||||
})
|
||||
|
||||
//
|
||||
// describe("completeFirst", () => {
|
||||
//
|
||||
// it("should invoke twice", () => {
|
||||
// let invoke = 0
|
||||
// from([1,2,3,4,5]).pipe(
|
||||
// completeOnFirst([take(2)])
|
||||
// ).subscribe((s) => {
|
||||
// console.log(s)
|
||||
// invoke++
|
||||
// })
|
||||
//
|
||||
// expect(invoke).toBe(2)
|
||||
//
|
||||
// })
|
||||
// })
|
||||
@@ -1,188 +0,0 @@
|
||||
import {ButtonInteraction, ComponentType, InteractionType, ModalSubmitInteraction } from 'discord.js'
|
||||
import { filter, from, of } from 'rxjs'
|
||||
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
||||
import { DJS } from '../src/index'
|
||||
import {clientEvent, distinctCustomId, matchesCustomId, toCollection} from "../src/djs";
|
||||
import {EventEmitter} from "events";
|
||||
import {fail} from "assert";
|
||||
import exp from "constants";
|
||||
|
||||
vi.mock('discord.js', () => {
|
||||
|
||||
const Collection = Map
|
||||
const ModalSubmitInteraction = class {
|
||||
customId
|
||||
type = 5
|
||||
isModalSubmit = vi.fn()
|
||||
constructor(customId) {
|
||||
this.customId = customId
|
||||
}
|
||||
}
|
||||
const ButtonInteraction = class {
|
||||
customId
|
||||
type = 3
|
||||
componentType = 2
|
||||
isButton = vi.fn()
|
||||
constructor(customId) {
|
||||
this.customId = customId
|
||||
}
|
||||
}
|
||||
return {
|
||||
Collection,
|
||||
ComponentType: {
|
||||
Button: 2
|
||||
},
|
||||
InteractionType : {
|
||||
MessageComponent : 3,
|
||||
ModalSubmit : 5
|
||||
},
|
||||
ModalSubmitInteraction,
|
||||
ButtonInteraction
|
||||
};
|
||||
})
|
||||
|
||||
describe("test isButton ", () => {
|
||||
let btnInteraction
|
||||
beforeEach( () => {
|
||||
btnInteraction = new ButtonInteraction("hello")
|
||||
})
|
||||
afterEach(() => vi.clearAllMocks())
|
||||
it("should invoke isButton 0 times", () => {
|
||||
let invoked = 0;
|
||||
from("not a discord button interaction").pipe(
|
||||
filter(DJS.isButton)
|
||||
).subscribe(() => invoked++)
|
||||
expect(invoked).toBe(0)
|
||||
|
||||
});
|
||||
|
||||
it("should invoke isButton once", () => {
|
||||
let invoked2 = 0;
|
||||
btnInteraction.isButton.mockReturnValue(
|
||||
btnInteraction.componentType === ComponentType.Button
|
||||
&& btnInteraction.type === InteractionType.MessageComponent
|
||||
)
|
||||
of(btnInteraction).pipe(
|
||||
filter(DJS.isButton)
|
||||
).subscribe(() => invoked2++)
|
||||
expect(invoked2).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("match customid operator", () => {
|
||||
let buttonInteraction
|
||||
|
||||
beforeEach(() => {
|
||||
buttonInteraction = new ButtonInteraction("hello-world")
|
||||
buttonInteraction.isButton.mockReturnValue(true)
|
||||
})
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
it("shouldn't invoke", () => {
|
||||
let invoked = 0
|
||||
|
||||
of(buttonInteraction).pipe(
|
||||
filter(DJS.isButton),
|
||||
matchesCustomId("pooba")
|
||||
).subscribe(() => invoked++)
|
||||
expect(invoked).toBe(0)
|
||||
|
||||
})
|
||||
it("should invoke once", () => {
|
||||
let invoked = 0
|
||||
of(buttonInteraction).pipe(
|
||||
filter(DJS.isButton),
|
||||
matchesCustomId("hello-world")
|
||||
).subscribe(() => invoked++)
|
||||
expect(invoked).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("toCollection operator", () => {
|
||||
it("should fill 10 elements", () => {
|
||||
const expectedMap = new Map([
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
[3, 3],
|
||||
[4, 4],
|
||||
[5, 5],
|
||||
[6, 6],
|
||||
[7, 7],
|
||||
[8, 8],
|
||||
[9, 9],
|
||||
[10, 10]
|
||||
])
|
||||
let coll
|
||||
from([1,2,3,4,5,6,7,8,9,10]).pipe(
|
||||
toCollection(src => [src, src])
|
||||
).subscribe(collection => {
|
||||
coll = collection
|
||||
})
|
||||
expect(coll).toEqual(expectedMap)
|
||||
})
|
||||
})
|
||||
|
||||
describe("distinct custom id operator", () => {
|
||||
it("should invoke once", () => {
|
||||
let invoked = 0;
|
||||
from([new ButtonInteraction("p1"),new ButtonInteraction("p1"),new ButtonInteraction("p1") ])
|
||||
.pipe(distinctCustomId)
|
||||
.subscribe(() => invoked++)
|
||||
expect(invoked).toBe(1)
|
||||
})
|
||||
|
||||
it("should invoke twice", () => {
|
||||
let invoked = 0;
|
||||
from([new ButtonInteraction("p1"),new ButtonInteraction("p1"),new ButtonInteraction("p2") ])
|
||||
.pipe(distinctCustomId)
|
||||
.subscribe(() => invoked++)
|
||||
expect(invoked).toBe(2)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe("isModal predicate", () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
it("should invoke once", () => {
|
||||
let invoked = 0
|
||||
const m = new ModalSubmitInteraction("p1")
|
||||
m.isModalSubmit.mockReturnValue(m.type === InteractionType.ModalSubmit)
|
||||
const b = new ButtonInteraction("p2")
|
||||
b.isButton.mockReturnValue(b.type === InteractionType.MessageComponent && b.componentType === ComponentType.Button)
|
||||
from([m, b])
|
||||
.pipe(filter(DJS.isModal))
|
||||
.subscribe(() => invoked++)
|
||||
expect(invoked).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe("clientEvent observable generator", () => {
|
||||
let c
|
||||
let cEvent
|
||||
let btnClickFromClientEvent
|
||||
beforeEach(() => {
|
||||
c = new EventEmitter()
|
||||
cEvent = clientEvent(c, 'interactionCreate').subscribe((b) =>btnClickFromClientEvent = b )
|
||||
})
|
||||
afterEach( () => {
|
||||
vi.clearAllMocks()
|
||||
cEvent.unsubscribe()
|
||||
})
|
||||
|
||||
it("should yield ButtonInteraction ", () => {
|
||||
const btnClick = new ButtonInteraction("p1")
|
||||
c.emit('interactionCreate', btnClick)
|
||||
expect(btnClick).toBe(btnClickFromClientEvent)
|
||||
})
|
||||
|
||||
it("should not emit correctly", () => {
|
||||
const btnClick = new ButtonInteraction("p1")
|
||||
const modalSubmit = new ModalSubmitInteraction("p1")
|
||||
c.emit('interactionCreate', modalSubmit)
|
||||
expect(btnClickFromClientEvent).not.toBe(btnClick)
|
||||
})
|
||||
})
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictNullChecks": true,
|
||||
"importsNotUsedAsValues": "error",
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"outDir": "dist/esm",
|
||||
"target": "esnext"
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
const shared = {
|
||||
entry: ['src/index.ts', 'src/djs/index.ts'],
|
||||
external: ['discord.js', 'rxjs'],
|
||||
platform: 'node',
|
||||
clean: true,
|
||||
sourcemap: false,
|
||||
};
|
||||
export default defineConfig([
|
||||
{
|
||||
format: 'esm',
|
||||
target: 'node17',
|
||||
tsconfig: './tsconfig-esm.json',
|
||||
dts: true,
|
||||
outDir: './dist',
|
||||
external: ['discord.js'],
|
||||
treeshake: true,
|
||||
outExtension() {
|
||||
return {
|
||||
js: '.mjs',
|
||||
};
|
||||
},
|
||||
...shared,
|
||||
},
|
||||
]);
|
||||
@@ -1,9 +0,0 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
includeSource: ["src/**/*.{js,ts}"]
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user