diff --git a/.github/release.yaml b/.github/release.yaml
deleted file mode 100644
index ae38baa8..00000000
--- a/.github/release.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-changelog:
- exclude:
- labels:
- - ignore-for-release
- authors:
- - octocat
- categories:
- - title: Breaking Changes 🛠
- labels:
- - breaking
- - title: Exciting New Features 🎉
- labels:
- - feature
- - title: Fixes 🔧
- labels:
- - fix
- - title: Documentation 🔧
- labels:
- - docs
- - title: CI 👷
- labels:
- - ci
- - title: Other Changes
- labels:
- - '*'
\ No newline at end of file
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
deleted file mode 100644
index 1d844374..00000000
--- a/.github/workflows/ci.yaml
+++ /dev/null
@@ -1,150 +0,0 @@
-name: ci
-on:
- push:
- branches:
- - main
- pull_request:
- branches:
- - main
-
-jobs:
-
- test_node:
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- node: [ '15', '14', '12' ]
- os: [ windows-latest, ubuntu-latest ]
- name: Test with Node v${{ matrix.node }} on ${{ matrix.os }}
- steps:
- - name: Tell if project is using npm or yarn
- id: step1
- uses: garronej/ts-ci@v1.1.2
- with:
- action_name: tell_if_project_uses_npm_or_yarn
- - uses: actions/checkout@v2.3.4
- - uses: actions/setup-node@v2.1.3
- with:
- node-version: ${{ matrix.node }}
- - uses: bahmutov/npm-install@v1
- - if: steps.step1.outputs.npm_or_yarn == 'yarn'
- run: |
- yarn build
- yarn test:node
- - if: steps.step1.outputs.npm_or_yarn == 'npm'
- run: |
- npm run build
- npm test:node
- test_deno:
- runs-on: ubuntu-latest
- name: test with Deno
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v1
- - name: Cache
- uses: actions/cache@v1
- with:
- path: ~/.cache/deno
- key: deno-${{ runner.os }}-${{ hashFiles('deno-lock.json') }}
- restore-keys: |
- deno-${{ runner.os }}-${{ hashFiles('deno-lock.json') }}
- deno-${{ runner.os }}-
- deno-
- - uses: denolib/setup-deno@master
- with:
- deno-version: v1.x
- - run: deno --version
- - uses: bahmutov/npm-install@v1
- - run: |
- PACKAGE_MANAGER=npm
- if [ -f "./yarn.lock" ]; then
- PACKAGE_MANAGER=yarn
- fi
- $PACKAGE_MANAGER run build
- $PACKAGE_MANAGER run test:deno
-
- check_if_version_upgraded:
- name: Check if version upgrade
- # We run this only if it's a push on the default branch or if it's a PR from a
- # branch (meaning not a PR from a fork). It would be more straightforward to test if secrets.NPM_TOKEN is
- # defined but GitHub Action don't allow it yet.
- if: |
- github.event_name == 'push' ||
- github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login
- runs-on: ubuntu-latest
- needs:
- - test_node
- - test_deno
- outputs:
- from_version: ${{ steps.step1.outputs.from_version }}
- to_version: ${{ steps.step1.outputs.to_version }}
- is_upgraded_version: ${{ steps.step1.outputs.is_upgraded_version }}
- is_release_beta: ${{steps.step1.outputs.is_release_beta }}
- steps:
- - uses: garronej/ts-ci@v1.1.2
- id: step1
- with:
- action_name: is_package_json_version_upgraded
- branch: ${{ github.head_ref || github.ref }}
-
- publish:
- runs-on: ubuntu-latest
- if: needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true'
- needs:
- - check_if_version_upgraded
- steps:
- - uses: actions/checkout@v2
- with:
- fetch-depth: 0
- ref: ${{ github.ref }}
- - run: rm -r .github
- - run: |
- git branch -d latest || true
- git push origin :latest || true
- - run: |
- git branch latest
- git checkout latest
- - uses: actions/setup-node@v2.1.3
- with:
- node-version: '15'
- registry-url: https://registry.npmjs.org/
- - uses: bahmutov/npm-install@v1
- - run: |
- PACKAGE_MANAGER=npm
- if [ -f "./yarn.lock" ]; then
- PACKAGE_MANAGER=yarn
- fi
- $PACKAGE_MANAGER run build
- - run: npx -y -p denoify@0.6.5 denoify_enable_short_npm_import_path
- env:
- DRY_RUN: "0"
- - run: |
- if [ "$(npm show . version)" = "$VERSION" ]; then
- echo "This version is already published"
- exit 0
- fi
- if [ "$NODE_AUTH_TOKEN" = "" ]; then
- echo "Can't publish on NPM, You must first create a secret called NPM_TOKEN that contains your NPM auth token. https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets"
- false
- fi
- npm publish
- env:
- NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
- VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}
- - run: |
- git config --global user.name "actions"
- git config --global user.email actions@example.com
- git add -A
- git commit -am "Enabling shorter import paths"
- git push origin latest
- - uses: garronej/action-gh-release@v0.2.0
- with:
- name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
- tag_name: v${{ needs.check_if_version_upgraded.outputs.to_version }}
- target_commitish: latest
- generate_release_notes: true
- draft: false
- prerelease: ${{ needs.check_if_version_upgraded.outputs.is_release_beta == 'true' }}
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
diff --git a/.gitignore b/.gitignore
index 7371373f..13be271a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,46 +1,2 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-
-# Runtime data
-pids
-*.pid
-*.seed
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (http://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directories
-node_modules
-jspm_packages
-
-# Optional npm cache directory
-.npm
-
-# Optional REPL history
-.node_repl_history
-
-dist/test
-.vscode
-
-.DS_Store
-
-
-/dist
-/deno_dist
\ No newline at end of file
+
+/node_modules
diff --git a/deno_dist/LICENSE b/deno_dist/LICENSE
new file mode 100644
index 00000000..61f220f7
--- /dev/null
+++ b/deno_dist/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 GitHub user u/garronej
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/deno_dist/README.md b/deno_dist/README.md
new file mode 100644
index 00000000..fc48a9a2
--- /dev/null
+++ b/deno_dist/README.md
@@ -0,0 +1,179 @@
+
+
+
+
+ 💧EventEmitter's typesafe replacement 💧
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Home
+ -
+ Documentation
+ -
+ v2.0 🚀
+
+
+---
+
+`'evt'` is intended to be a replacement for `'events'`.
+It enables and encourages **functional programming** and makes heavy use of **typescript**'s type inference features to provide **type safety** while keeping things **concise and elegant** 🍸.
+
+Suitable for any JS runtime env (deno, node, old browsers, react-native ...)
+- ✅ It is both a [Deno](https://deno.land/x/evt) and an [NPM](https://www.npmjs.com/evt) module. ( Achieved with [Denoify](https://github.com/garronej/denoify) )
+- ✅ Lightweight, no dependency.
+- ✅ Can be imported with `require` (CJS) or `import` (ESM).
+- ✅ [React Hooks integration](https://stackblitz.com/edit/evt-react-hooks-todo-list?file=index.tsx)
+
+Can be imported in TypeScript projects using version >= **3.4** \(Mar 2019\) and in any plain JS projects.
+
+# TL;DR*
+
+```typescript
+import { Evt } from "evt";
+
+const evtText = new Evt();
+const evtTime = new Evt();
+
+evtText.attach(text => console.log(text));
+evtTime.attachOnce(time => console.log(time));
+
+evtText.post("hi!"); //Prints "hi!"
+evtTime.post(123); //Prints "123"
+evtTime.post(1234); //Prints nothing
+```
+OR
+```typescript
+import { Evt, to } from "evt";
+
+const evt = new Evt<
+ [ "text", string ] |
+ [ "time", number ]
+>();
+
+//Mind the '$' prefixing 'attach'
+evt.$attach(to("text"), text => console.log(text));
+evt.$attachOnce(to("time"), time => console.log(time));
+
+evt.post(["text", "hi!"]);
+evt.post(["time", 123]);
+evt.post(["time", 1234]);
+```
+in React, it let you attach event listeners without having to worry about detaching them.
+```typescript
+import { useState } from "react";
+import { Evt } from "evt";
+import { useEvt } from "evt/hooks";
+
+const evtTick = Evt.create();
+
+setInterval(()=> evtTick.post(), 1000);
+
+function App(){
+
+ const [count, setCount]= useState(0);
+
+ useEvt(ctx=> {
+
+ evtTick.attach(ctx, ()=> setCount(count+1));
+
+ },[count]);
+
+ return tick count: {count} ;
+
+
+}
+```
+[run it](https://stackblitz.com/edit/evt-hooks-101?file=index.tsx)
+
+_*Those are introductory examples, EVT can do much more than this._
+
+# Who is using it
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Install / Import
+
+## In Deno:
+```typescript
+import { Evt } from "https://deno.land/x/evt/mod.ts";
+```
+## Anywhere else:
+```bash
+$ npm install --save evt
+```
+```typescript
+import { Evt } from "evt";
+```
+
+# Try it
+
+[Run some examples](https://stackblitz.com/edit/evt-playground?embed=1&file=index.ts&hideExplorer=1)
+
+
+
+
+
+# Motivations
+
+There are a lot of things that can't easily be done with `EventEmitter`:
+
+* Enforcing **type safety**.
+* Removing a particular listener ( if the callback is an anonymous function ).
+* Adding a one-time listener for the next event that meets a condition.
+* Waiting \(via a Promise\) for one thing or another to happen.
+_Example: waiting at most one second for the next message, stop waiting if the socket disconnects._
+
+Why would someone pick EVT over RxJS:
+
+* EVT's learning curve is not as steep as RxJS's.
+* Generates code that is easier to grasp for people not familiar with reactive programming.
+
+EVT is an attempt to address all these points while trying to remain as accessible as `EventEmitter`.
+
+
+
+[Get started](https://docs.evt.land/overview#rxjs-comparison)
+
+# The sticker
+
+
+
+
+
+ Shop
+
diff --git a/deno_dist/hooks/index.ts b/deno_dist/hooks/index.ts
new file mode 100644
index 00000000..f2a4672c
--- /dev/null
+++ b/deno_dist/hooks/index.ts
@@ -0,0 +1,9 @@
+/*
+NOTE: In theory react should be a peer dependency
+instead of a dev dependency.
+But given that /hook is just a plugin for using Evt
+with react we don't want to require react to be
+installed to install Evt.
+*/
+export { useEvt } from "./useEvt.ts";
+export { useRerenderOnStateChange } from "./useRerenderOnStateChange.ts";
\ No newline at end of file
diff --git a/deno_dist/hooks/useEvt.ts b/deno_dist/hooks/useEvt.ts
new file mode 100644
index 00000000..cda41e28
--- /dev/null
+++ b/deno_dist/hooks/useEvt.ts
@@ -0,0 +1,51 @@
+import { Evt } from "../lib/Evt.ts";
+import type { Ctx } from "../lib/index.ts";
+import React from "https://dev.jspm.io/react@18.2.0";;
+const { useEffect } = React;
+
+
+/**
+ * https://docs.evt.land/api/react-hooks
+ *
+ * Provide a Ctx to attach handlers.
+ * You should list in deps all the Evt that are
+ * susceptible to change ( Evt passed as props
+ * or Evt that are react states ) that you use in the
+ * factoryOrEffect callback.
+ * As for useEffect you should also list every other
+ * value that you use.
+ * Whenever any value in deps is changed factoryOrEffect
+ * is invoked again with the new Evt and the previous handler
+ * get detached.
+ * All handler are also detached when the component unmount.
+ *
+ * factoryOrEffect can be used for attaching handler to event
+ * or to generate a new event that is a merge/pipe of other
+ * Evts.
+ *
+ * BE AWARE: Unlike useEffect factoryOrEffect is called
+ * on render ( like useMemo's callback ).
+ * Remember that you shouldn't update state in a component
+ * render tick (in the useMemo for example). If you you need to
+ * perform an effect on first render (attaching a stateful evt
+ * for example) use registerSideEffect(()=>{ ... })
+ *
+ * Demo: https://stackblitz.com/edit/evt-useevt?file=index.tsx
+ */
+export function useEvt(
+ effect: (ctx: Ctx) => void,
+ deps?: React.DependencyList
+): void {
+
+ useEffect(
+ ()=> {
+ const ctx= Evt.newCtx();
+
+ effect(ctx);
+
+ return ()=> { ctx.done(); };
+ },
+ deps
+ );
+
+}
diff --git a/deno_dist/hooks/useRerenderOnStateChange.ts b/deno_dist/hooks/useRerenderOnStateChange.ts
new file mode 100644
index 00000000..10a755b2
--- /dev/null
+++ b/deno_dist/hooks/useRerenderOnStateChange.ts
@@ -0,0 +1,27 @@
+import { useEvt } from "./useEvt.ts";
+import React from "https://dev.jspm.io/react@18.2.0";;
+const { useState } = React;
+
+type CtxLike = import("../lib/types/interfaces/CtxLike.ts").CtxLike;
+
+interface StatefulReadonlyEvtLike {
+ state: unknown;
+ attach: (ctx: CtxLike, cb: (state: unknown)=> void)=> void;
+};
+
+/**
+ * https://docs.evt.land/api/react-hooks
+ *
+ * To use StatefulEvt as react component state.
+ * */
+export function useRerenderOnStateChange(evt: StatefulReadonlyEvtLike): void {
+
+ //NOTE: We use function in case the state is a function
+ const [, setState] = useState(() => evt.state);
+
+ useEvt(
+ ctx =>
+ evt.attach(ctx, state => setState(() => state)),
+ [evt]
+ );
+}
diff --git a/deno_dist/lib/Ctx.ts b/deno_dist/lib/Ctx.ts
new file mode 100644
index 00000000..2f1e8d4e
--- /dev/null
+++ b/deno_dist/lib/Ctx.ts
@@ -0,0 +1,153 @@
+import { Polyfill as Set } from "https://raw.githubusercontent.com/garronej/minimal_polyfills/v2.2.1/deno_dist/Set.ts";
+import { Polyfill as WeakMap } from "https://raw.githubusercontent.com/garronej/minimal_polyfills/v2.2.1/deno_dist/WeakMap.ts";
+import { assert } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/assert.ts";;
+import { is } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/is.ts";
+import { LazyEvt } from "./LazyEvt.ts";
+import { importProxy } from "./importProxy.ts";
+import { overwriteReadonlyProp } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/lab/overwriteReadonlyProp.ts";
+
+import type {
+ Handler,
+ EvtLike,
+ Evt,
+ CtxLike,
+ DoneOrAborted
+} from "./types/index.ts";
+
+export type Ctx = import("./types/interfaces/index.ts").Ctx;
+
+class CtxImpl implements Ctx{
+
+
+ get evtDoneOrAborted(): Evt> {
+ return this.lazyEvtDoneOrAborted.evt;
+ }
+
+ get evtAttach(): Evt> {
+ return this.lazyEvtAttach.evt;
+ }
+
+ get evtDetach(): Evt> {
+ return this.lazyEvtDetach.evt;
+ }
+
+
+ private lazyEvtAttach = new LazyEvt>();
+ private lazyEvtDetach = new LazyEvt>();
+ private lazyEvtDoneOrAborted = new LazyEvt>();
+
+ private onDoneOrAborted(doneEvtData: DoneOrAborted): void {
+ this.lazyEvtDoneOrAborted.post(doneEvtData);
+ }
+
+ waitFor(timeout?: number): Promise {
+ return this.evtDoneOrAborted
+ .waitFor(timeout)
+ .then(
+ data => {
+ if (data.type === "ABORTED") {
+ throw data.error;
+ }
+ return data.result;
+ },
+ timeoutError => {
+ this.abort(timeoutError);
+ throw timeoutError;
+ }
+ )
+ ;
+ }
+
+
+ abort(error: Error) {
+ return this.__done(error);
+ }
+
+ done(result: Result) {
+ return this.__done(undefined, result);
+ }
+
+ /** Detach all handler bound to this context from theirs respective Evt and post getEvtDone() */
+ private __done(error: Error | undefined, result?: Result): Handler.WithEvt[] {
+
+ const handlers: Handler.WithEvt[] = [];
+
+ for (const handler of this.handlers.values()) {
+
+ const evt = this.evtByHandler.get(handler)!;
+
+ const wasStillAttached = handler.detach();
+
+ //NOTE: It should not be possible
+ if (!wasStillAttached) {
+ continue;
+ }
+
+ handlers.push({ handler, evt });
+ }
+
+ this.onDoneOrAborted({
+ ...(!!error ?
+ { type: "ABORTED", error } :
+ { type: "DONE", "result": result as NonNullable }
+ ),
+ handlers
+ });
+
+
+ return handlers;
+
+ }
+
+ private handlers = new Set<
+ Handler>
+ >();
+ private evtByHandler = new WeakMap<
+ Handler>,
+ EvtLike
+ >();
+
+ getHandlers(): Handler.WithEvt[] {
+ return Array.from(this.handlers.values())
+ .map(handler => ({ handler, "evt": this.evtByHandler.get(handler)! }))
+ ;
+ }
+
+
+ zz__addHandler(
+ handler: Handler>,
+ evt: EvtLike
+ ) {
+ assert(handler.ctx === this);
+ assert(is>>(handler));
+ this.handlers.add(handler);
+ this.evtByHandler.set(handler, evt);
+ this.lazyEvtAttach.post({ handler, evt });
+ }
+
+ zz__removeHandler(
+ handler: Handler>,
+ ) {
+ assert(handler.ctx === this);
+ assert(is>>(handler));
+
+ this.lazyEvtDetach.post({
+ handler,
+ "evt": this.evtByHandler.get(handler)!
+ });
+
+ this.handlers.delete(handler);
+ }
+
+}
+
+export const Ctx: {
+ new (): Ctx;
+ readonly prototype: Ctx;
+} = CtxImpl;
+
+try{ overwriteReadonlyProp(Ctx as any, "name", "Ctx"); }catch{}
+
+importProxy.Ctx = Ctx;
+
+
diff --git a/deno_dist/lib/Evt.asNonPostable.ts b/deno_dist/lib/Evt.asNonPostable.ts
new file mode 100644
index 00000000..928118b5
--- /dev/null
+++ b/deno_dist/lib/Evt.asNonPostable.ts
@@ -0,0 +1,7 @@
+
+import type { ToNonPostableEvt, EvtLike } from "./types/index.ts";
+
+/** https://docs.evt.land/api/evt/asnonpostable */
+export function asNonPostable>(evt: E): ToNonPostableEvt{
+ return evt as any;
+}
\ No newline at end of file
diff --git a/deno_dist/lib/Evt.asPostable.ts b/deno_dist/lib/Evt.asPostable.ts
new file mode 100644
index 00000000..7b722a57
--- /dev/null
+++ b/deno_dist/lib/Evt.asPostable.ts
@@ -0,0 +1,10 @@
+import type { EvtLike, ToPostableEvt} from "./types/index.ts";
+
+/**
+ * https://docs.evt.land/api/evt/aspostable
+ * ⚠ UNSAFE ⚠ - Please refer to documentation before using.
+ * */
+export function asPostable>(evt: E): ToPostableEvt{
+ return evt as any;
+}
+
diff --git a/deno_dist/lib/Evt.asyncPipe.ts b/deno_dist/lib/Evt.asyncPipe.ts
new file mode 100644
index 00000000..1fc0210a
--- /dev/null
+++ b/deno_dist/lib/Evt.asyncPipe.ts
@@ -0,0 +1,82 @@
+
+import type { Evt } from "./Evt.ts";
+import type { StatefulEvt, StatefulReadonlyEvt, NonPostableEvt, UnpackEvt } from "./types/index.ts";
+import type { PromiseOrNot } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/lab/PromiseOrNot.ts";
+import { importProxy } from "./importProxy.ts";
+
+type EvtLike = import("./types/helper/index.ts").EvtLike & {
+ attach(callback: (data: T) => void): void;
+};
+
+
+/**
+ * NOTE: Workaround until v2.0 where .pipe() will support async operators
+ * Usage example: https://stackblitz.com/edit/evt-async-op?file=index.ts
+ *
+ * When the argument is a StatefulEvt:
+ * If, wile asyncOp was running, the state of the source evt
+ * have changed then the result will be discarded.
+ *
+ * If the asyncOp complete synchronously (meaning it does not return
+ * a promise) then the result is synchronously transformed. (As with .pipe() )
+ *
+ * More usage example in src/test/test95.ts
+ */
+export function asyncPipe, U>(
+ evt: E,
+ asyncOp: (data: UnpackEvt) => PromiseOrNot<[U] | null>
+):
+ E extends StatefulReadonlyEvt ? StatefulEvt :
+ E extends NonPostableEvt ? Evt :
+ EvtLike
+{
+
+ const out = "state" in evt ?
+ importProxy.Evt.create | undefined>(undefined) :
+ importProxy.Evt.create>();
+
+ let currentCallCount = 0;
+
+ evt.attach(async (data: UnpackEvt) => {
+
+ currentCallCount++;
+
+ const thisCallCount = currentCallCount;
+
+ const prOpResult = asyncOp(data);
+
+ let opResult: [U] | null;
+
+ if (
+ prOpResult !== null &&
+ "then" in prOpResult
+ ) {
+
+ opResult = await prOpResult;
+
+ if (
+ "state" in evt &&
+ thisCallCount !== currentCallCount
+ ) {
+ return;
+ }
+
+ } else {
+
+ opResult = prOpResult;
+
+ }
+
+ if (!opResult) {
+
+ return;
+ }
+
+ out.post(opResult[0] as any);
+
+ });
+
+ return out as any;
+
+}
+
diff --git a/deno_dist/lib/Evt.create.ts b/deno_dist/lib/Evt.create.ts
new file mode 100644
index 00000000..b9a71ed5
--- /dev/null
+++ b/deno_dist/lib/Evt.create.ts
@@ -0,0 +1,20 @@
+
+import { importProxy } from "./importProxy.ts";
+import type { Evt, StatefulEvt } from "./types/index.ts";
+
+/**
+ * https://docs.evt.land/api/evt/create
+ * Return a new Evt instance.
+ */
+export function create(): Evt;
+/**
+ * https://docs.evt.land/api/evt/create
+ * Return a new StatefulEvt instance.
+ */
+export function create(initialState: T): StatefulEvt;
+export function create(...args: [] | [any] ): Evt | StatefulEvt {
+ return args.length === 0 ?
+ new importProxy.Evt() :
+ new importProxy.StatefulEvt(args[0])
+ ;
+}
\ No newline at end of file
diff --git a/deno_dist/lib/Evt.factorize.ts b/deno_dist/lib/Evt.factorize.ts
new file mode 100644
index 00000000..4ac54449
--- /dev/null
+++ b/deno_dist/lib/Evt.factorize.ts
@@ -0,0 +1,14 @@
+import type { FactorizeEvt, EvtLike } from "./types/helper/index.ts";
+
+/** https://docs.evt.land/api/evt/factorize */
+export function factorize>(
+ evt: E
+): FactorizeEvt {
+ return evt as any;
+}
+
+/*
+import { Evt } from "./Evt.ts";
+const x: Evt = loosenType(new Evt()); x;
+const y: Evt = loosenType(new Evt()); y;
+*/
\ No newline at end of file
diff --git a/deno_dist/lib/Evt.from.ts b/deno_dist/lib/Evt.from.ts
new file mode 100644
index 00000000..cfc7c568
--- /dev/null
+++ b/deno_dist/lib/Evt.from.ts
@@ -0,0 +1,335 @@
+import { id } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/id.ts";
+import { assert } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/assert.ts";;
+import { typeGuard } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/typeGuard.ts";
+import { mergeImpl } from "./Evt.merge.ts";
+import { importProxy } from "./importProxy.ts";
+import type { dom, Evt, EvtLike } from "./types/index.ts";
+import type { EventTargetLike } from "./types/index.ts";
+import * as nsEventTargetLike from "./types/EventTargetLike.ts";
+const { EventTargetLike: EventTargetLikeAsValue } = nsEventTargetLike;
+import type { ObserverConstructor } from "./types/Observer.ts";
+
+type OneOrMany = T | ArrayLike;
+type CtxLike = import("./types/index.ts").CtxLike & {
+ evtDoneOrAborted: EvtLike & { postCount: number; attachOnce(callback: () => void): void; };
+};
+
+function fromImplForTargetEventLike(
+ ctx: CtxLike | undefined,
+ target: OneOrMany> | PromiseLike,
+ eventName?: string,
+ options?: EventTargetLike.HasEventTargetAddRemove.Options
+): Evt {
+
+ const matchEventTargetLike =
+ (target_: typeof target): target_ is EventTargetLike =>
+ EventTargetLikeAsValue.canBe(target_);
+
+ if (!matchEventTargetLike(target)) {
+
+ if ("then" in target) {
+
+ const evt = new importProxy.Evt();
+
+ const isCtxDone = (() => {
+
+ const getEvtDonePostCount = () => ctx?.evtDoneOrAborted.postCount;
+
+ const n = getEvtDonePostCount();
+
+ return () => n !== getEvtDonePostCount();
+
+ })();
+
+ target.then(data => {
+
+ if (isCtxDone()) {
+ return;
+ }
+
+ evt.post(data);
+
+ });
+
+ return evt;
+
+ }
+
+ return mergeImpl>(
+ ctx,
+ Array.from(target).map(
+ target => fromImplForTargetEventLike(ctx, target, eventName, options)
+ )
+ );
+
+ }
+
+ type ProxyMethod = (
+ listener: (data: T) => void,
+ eventName: string,
+ options?: EventTargetLike.HasEventTargetAddRemove.Options
+ ) => void;
+
+ let proxy: {
+ on: ProxyMethod;
+ off: ProxyMethod;
+ };
+
+ if (EventTargetLikeAsValue.NodeStyleEventEmitter.match(target)) {
+ proxy = {
+ "on": (listener, eventName) => target.addListener(eventName, listener),
+ "off": (listener, eventName) => target.removeListener(eventName, listener)
+ };
+ } else if (EventTargetLikeAsValue.JQueryStyleEventEmitter.match(target)) {
+ proxy = {
+ "on": (listener, eventName) => target.on(eventName, listener),
+ "off": (listener, eventName) => target.off(eventName, listener)
+ };
+ } else if (EventTargetLikeAsValue.HasEventTargetAddRemove.match(target)) {
+ proxy = {
+ "on": (listener, eventName, options) => target.addEventListener(eventName, listener, options),
+ "off": (listener, eventName, options) => target.removeEventListener(eventName, listener, options)
+ };
+ } else if (EventTargetLikeAsValue.RxJSSubject.match(target)) {
+
+ let subscription: EventTargetLike.RxJSSubject.Subscription;
+
+ proxy = {
+ "on": listener => subscription = target.subscribe(data => listener(data)),
+ "off": () => subscription.unsubscribe()
+ };
+
+ } else {
+
+ id(target);
+ assert(false);
+
+ }
+
+ const evt = new importProxy.Evt();
+
+ const listener = (data: T) => evt.post(data);
+
+ ctx?.evtDoneOrAborted.attachOnce(
+ () => proxy.off(
+ listener,
+ eventName!,
+ options
+ )
+ );
+
+ proxy.on(listener, eventName!, options);
+
+ return evt;
+
+}
+
+function fromImplForObserver(
+ ctx: CtxLike | undefined,
+ ObserverConstructor: ObserverConstructor,
+ target: Target
+): Evt {
+
+ const evt = importProxy.Evt.create();
+
+ const listener = ([entry]: Entry[]) => evt.post(entry);
+
+ const observer = new ObserverConstructor(listener);
+
+ observer.observe(target);
+
+ ctx?.evtDoneOrAborted.attachOnce(
+ () => observer.disconnect()
+ );
+
+ return evt;
+
+}
+
+/** https://docs.evt.land/api/evt/from */
+export function from(
+ ctx: CtxLike,
+ target: EventTargetLike.HTMLElement,
+ eventName: K,
+ options?: EventTargetLike.HasEventTargetAddRemove.Options
+): Evt;
+
+export function from(
+ ctx: CtxLike,
+ target: EventTargetLike.Window,
+ eventName: K,
+ options?: EventTargetLike.HasEventTargetAddRemove.Options
+): Evt;
+
+export function from(
+ ctx: CtxLike,
+ target: EventTargetLike.Document,
+ eventName: K,
+ options?: EventTargetLike.HasEventTargetAddRemove.Options
+): Evt;
+
+export function from(
+ ctx: CtxLike,
+ target: OneOrMany<
+ EventTargetLike.NodeStyleEventEmitter |
+ EventTargetLike.JQueryStyleEventEmitter
+ >,
+ eventName: string
+): Evt;
+export function from(
+ ctx: CtxLike,
+ target: OneOrMany<
+ EventTargetLike.HasEventTargetAddRemove
+ >,
+ eventName: string,
+ options?: EventTargetLike.HasEventTargetAddRemove.Options
+): Evt;
+export function from(
+ ctx: CtxLike,
+ target: OneOrMany>
+): Evt;
+
+export function from(
+ ctx: CtxLike,
+ target: PromiseLike
+): Evt;
+
+export function from(
+ ctx: CtxLike,
+ ObserverConstructor: ObserverConstructor,
+ target: Target
+): Evt;
+
+
+export function from(
+ target: EventTargetLike.HTMLElement,
+ eventName: K,
+ options?: EventTargetLike.HasEventTargetAddRemove.Options
+): Evt;
+export function from(
+ target: EventTargetLike.Window,
+ eventName: K,
+ options?: EventTargetLike.HasEventTargetAddRemove.Options
+): Evt;
+export function from(
+ target: EventTargetLike.Document,
+ eventName: K,
+ options?: EventTargetLike.HasEventTargetAddRemove.Options
+): Evt;
+export function from(
+ target: OneOrMany<
+ EventTargetLike.NodeStyleEventEmitter |
+ EventTargetLike.JQueryStyleEventEmitter
+ >,
+ eventName: string
+): Evt;
+export function from(
+ target: OneOrMany<
+ EventTargetLike.HasEventTargetAddRemove
+ >,
+ eventName: string,
+ options?: EventTargetLike.HasEventTargetAddRemove.Options
+): Evt;
+export function from(
+ target: OneOrMany>
+): Evt;
+export function from(
+ target: PromiseLike
+): Evt;
+
+export function from(
+ ObserverConstructor: ObserverConstructor,
+ target: Target
+): Evt;
+/*
+/^[A-Z]/.test(targetOrEventNameOrObserverConstructorOrObserverTarget.name
+ */
+
+export function from(
+ ctxOrTargetOrObserverConstructor: CtxLike | OneOrMany> | PromiseLike | ObserverConstructor,
+ targetOrEventNameOrObserverConstructorOrObserverTarget?: OneOrMany> | string | PromiseLike | ObserverConstructor | ObserverTarget,
+ eventNameOrOptionsOrObserverTarget?: string | EventTargetLike.HasEventTargetAddRemove.Options | ObserverTarget,
+ options?: EventTargetLike.HasEventTargetAddRemove.Options
+): Evt {
+
+ if ("evtDoneOrAborted" in ctxOrTargetOrObserverConstructor) {
+
+ assert(
+ typeGuard> | PromiseLike | ObserverConstructor>(targetOrEventNameOrObserverConstructorOrObserverTarget, true) &&
+ typeGuard(eventNameOrOptionsOrObserverTarget, true) &&
+ typeGuard(options, true)
+ );
+
+ if (typeof targetOrEventNameOrObserverConstructorOrObserverTarget === "function") {
+
+ assert(
+ typeGuard(eventNameOrOptionsOrObserverTarget, true) &&
+ typeGuard(options, true)
+ );
+
+ return fromImplForObserver(
+ ctxOrTargetOrObserverConstructor,
+ targetOrEventNameOrObserverConstructorOrObserverTarget,
+ eventNameOrOptionsOrObserverTarget
+ );
+
+ } else {
+
+ assert(
+ typeGuard>(eventNameOrOptionsOrObserverTarget, true)
+ );
+
+ return fromImplForTargetEventLike(
+ ctxOrTargetOrObserverConstructor,
+ targetOrEventNameOrObserverConstructorOrObserverTarget,
+ eventNameOrOptionsOrObserverTarget,
+ options
+ );
+
+ }
+
+
+ } else {
+
+ assert(
+ typeGuard>>(ctxOrTargetOrObserverConstructor, true) &&
+ typeGuard(targetOrEventNameOrObserverConstructorOrObserverTarget, true) &&
+ typeGuard(eventNameOrOptionsOrObserverTarget, true)
+ );
+
+ if (typeof ctxOrTargetOrObserverConstructor === "function") {
+
+ assert(
+ typeGuard(targetOrEventNameOrObserverConstructorOrObserverTarget, true) &&
+ typeGuard(eventNameOrOptionsOrObserverTarget, true)
+ );
+
+ return fromImplForObserver(
+ undefined,
+ ctxOrTargetOrObserverConstructor,
+ targetOrEventNameOrObserverConstructorOrObserverTarget
+ );
+
+
+ } else {
+
+ assert(
+ typeGuard>(
+ targetOrEventNameOrObserverConstructorOrObserverTarget, true
+ )
+ );
+
+ return fromImplForTargetEventLike(
+ undefined,
+ ctxOrTargetOrObserverConstructor,
+ targetOrEventNameOrObserverConstructorOrObserverTarget,
+ eventNameOrOptionsOrObserverTarget
+ );
+
+ }
+
+
+
+ }
+
+}
diff --git a/deno_dist/lib/Evt.getCtx.ts b/deno_dist/lib/Evt.getCtx.ts
new file mode 100644
index 00000000..319a9a45
--- /dev/null
+++ b/deno_dist/lib/Evt.getCtx.ts
@@ -0,0 +1,34 @@
+import { Polyfill as WeakMap } from "https://raw.githubusercontent.com/garronej/minimal_polyfills/v2.2.1/deno_dist/WeakMap.ts";
+import { importProxy } from "./importProxy.ts";
+import type { Ctx } from "./types/index.ts";
+
+/**
+ * https://docs.evt.land/api/evt/getctx
+ *
+ * Evt.getCtx(obj) an instance of Ctx, always the same for a given object.
+ * No strong reference to the object is created
+ * when the object is no longer referenced it's associated Ctx will be freed from memory.
+ */
+export function getCtxFactory() {
+
+ const ctxByObj = new WeakMap();
+
+ function getCtx(obj: object): Ctx {
+
+ let ctx = ctxByObj.get(obj);
+
+ if (ctx === undefined) {
+
+ ctx = (new importProxy.Ctx());
+
+ ctxByObj.set(obj, ctx);
+
+ }
+
+ return ctx;
+
+ }
+
+ return getCtx;
+
+}
diff --git a/deno_dist/lib/Evt.loosenType.ts b/deno_dist/lib/Evt.loosenType.ts
new file mode 100644
index 00000000..7340f8c8
--- /dev/null
+++ b/deno_dist/lib/Evt.loosenType.ts
@@ -0,0 +1,20 @@
+import type { SwapEvtType, UnpackEvt, EvtLike} from "./types/index.ts";
+
+/**
+ * https://docs.evt.land/api/evt/loosenType
+ */
+export function loosenType, SupersetOfT>(
+ evt: E
+): UnpackEvt extends SupersetOfT ?
+ SwapEvtType : "NOT A SUPERSET" {
+ return evt as any;
+}
+
+/*
+import { Evt } from "./Evt.ts";
+const x: Evt = loosenType(new Evt()); x;
+const y: Evt = loosenType(new Evt()); y;
+*/
+
+
+
diff --git a/deno_dist/lib/Evt.merge.ts b/deno_dist/lib/Evt.merge.ts
new file mode 100644
index 00000000..cc7ab9fb
--- /dev/null
+++ b/deno_dist/lib/Evt.merge.ts
@@ -0,0 +1,57 @@
+
+import { importProxy } from "./importProxy.ts";
+import type { UnpackEvt, Evt, CtxLike } from "./types/index.ts";
+
+
+export type EvtLike = import("./types/helper/index.ts").EvtLike & {
+ attach(ctx: CtxLike, callback: (data: T) => void): void;
+ attach(callback: (data: T) => void): void;
+};
+
+export function mergeImpl>(
+ ctx: CtxLike | undefined,
+ evts: readonly EvtUnion[]
+): Evt> {
+
+ const evtUnion = new importProxy.Evt>();
+
+ const callback = (data: UnpackEvt) => evtUnion.post(data)
+
+ evts.forEach(
+ evt => {
+
+ if (ctx === undefined) {
+ evt.attach(callback);
+ } else {
+ evt.attach(ctx, callback);
+ }
+
+ }
+ );
+
+ return evtUnion;
+
+}
+
+
+
+/** https://docs.evt.land/api/evt/merge */
+export function merge>(
+ ctx: CtxLike,
+ evts: readonly EvtUnion[]
+): Evt>;
+export function merge>(
+ evts: readonly EvtUnion[]
+): Evt>;
+export function merge>(
+ p1: CtxLike | readonly EvtUnion[],
+ p2?: readonly EvtUnion[]
+): Evt> {
+
+ return "length" in p1 ?
+ mergeImpl(undefined, p1) :
+ mergeImpl(p1, p2!)
+ ;
+
+
+}
\ No newline at end of file
diff --git a/deno_dist/lib/Evt.newCtx.ts b/deno_dist/lib/Evt.newCtx.ts
new file mode 100644
index 00000000..85ab1a15
--- /dev/null
+++ b/deno_dist/lib/Evt.newCtx.ts
@@ -0,0 +1,11 @@
+import { importProxy } from "./importProxy.ts";
+import type { Ctx } from "./Ctx.ts";
+
+/**
+ * https://docs.evt.land/api/evt/newctx
+ *
+ * return a new Ctx instance
+ * */
+export function newCtx(): Ctx{
+ return new importProxy.Ctx();
+}
\ No newline at end of file
diff --git a/deno_dist/lib/Evt.parsePropsFromArgs.ts b/deno_dist/lib/Evt.parsePropsFromArgs.ts
new file mode 100644
index 00000000..04e4a224
--- /dev/null
+++ b/deno_dist/lib/Evt.parsePropsFromArgs.ts
@@ -0,0 +1,250 @@
+
+import type { Operator } from "./types/Operator.ts"
+import { id } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/id.ts";
+import { compose } from "./util/compose.ts";
+import { typeGuard } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/typeGuard.ts"
+import type { CtxLike, Handler } from "./types/index.ts";
+
+export function matchAll() { return true; }
+
+const canBeOperator = (p: undefined | CtxLike | Operator): boolean => {
+ return (
+ p !== undefined &&
+ typeGuard>(p, true) &&
+ (
+ typeof p === "function" ||
+ typeof p[0] === "function"
+ )
+ );
+};
+
+const defaultParams: Handler.PropsFromArgs = {
+ "op": matchAll,
+ "ctx": undefined,
+ "timeout": undefined,
+ "callback": undefined
+};
+
+export function parsePropsFromArgs(
+ inputs: readonly any[],
+ methodName: "waitFor" | "attach*" | "pipe"
+): Handler.PropsFromArgs {
+
+ type Out = Handler.PropsFromArgs;
+
+ switch (methodName) {
+ case "pipe": {
+
+ //[]
+ //[undefined] ( not valid but user would expect it to work )
+ //[ ctx, ...op[] ]
+ //[ ...op[] ]
+
+ const getOpWrap = (ops: [Operator, ...Operator[]]) =>
+ ops.length === 0 ?
+ {}
+ :
+ { "op": ops.length === 1 ? ops[0] : compose(...ops) }
+ ;
+
+ if (canBeOperator(inputs[0])) {
+
+ //[ ...op[] ]
+
+ return id({
+ ...defaultParams,
+ ...getOpWrap(inputs as any)
+ });
+
+ } else {
+
+ //[]
+ //[ ctx, ...Operator.fλ[] ]
+
+ const [ctx, ...rest] = inputs;
+
+ return id({
+ ...defaultParams,
+ ...(ctx !== undefined ? { ctx } : {}),
+ ...getOpWrap(rest as any)
+ });
+
+ }
+
+
+ } break;
+
+ case "waitFor": {
+
+ //[ op, ctx, timeout ]
+ //[ op, ctx, undefined ]
+ //[ op, ctx ]
+ //[ op, timeout ]
+ //[ op, undefined ]
+ //[ ctx, timeout ]
+ //[ ctx, undefined ]
+ //[ op ]
+ //[ ctx ]
+ //[ timeout ]
+ //[ undefined ]
+ //[ callback ]
+
+ return parsePropsFromArgs(
+ [
+ //If the last element is undefined, remove it.
+ ...inputs.filter(
+ (value, index) => !(
+ index === inputs.length - 1 &&
+ value === undefined
+ )
+ ),
+ defaultParams.callback
+ ],
+ "attach*"
+ );
+
+ } break;
+ case "attach*": {
+
+ //NOTE: when callback is undefined call has been forward from waitFor.
+
+ //[ op, ctx, timeout, callback ]
+ //[ op, ctx, timeout, undefined ]
+ //[ op, ctx, callback ]
+ //[ op, ctx, undefined ]
+ //[ op, timeout, callback ]
+ //[ op, timeout, undefined ]
+ //[ ctx, timeout, callback ]
+ //[ ctx, timeout, undefined ]
+ //[ op, callback ]
+ //[ op, undefined ]
+ //[ ctx, callback ]
+ //[ ctx, undefined ]
+ //[ timeout, callback ]
+ //[ timeout, undefined ]
+ //[ callback ]
+ //[ undefined ]
+
+ const n = inputs.length as 4 | 3 | 2 | 1 | 0;
+
+ switch (n) {
+ case 4: {
+
+ //[ op, ctx, timeout, callback ]
+ const [p1, p2, p3, p4] = inputs;
+
+ return id({
+ ...defaultParams,
+ "op": p1,
+ "ctx": p2,
+ "timeout": p3,
+ "callback": p4
+ });
+
+ }
+ case 3: {
+
+ //[ op, ctx, callback ]
+ //[ op, timeout, callback ]
+ //[ ctx, timeout, callback ]
+ const [p1, p2, p3] = inputs;
+ if (typeof p2 === "number") {
+ //[ op, timeout, callback ]
+ //[ ctx, timeout, callback ]
+
+ const timeout: Out["timeout"] = p2;
+ const callback: Out["callback"] = p3;
+
+ if (canBeOperator(p1)) {
+ //[ op, timeout, callback ]
+ return id({
+ ...defaultParams,
+ timeout,
+ callback,
+ "op": p1
+ });
+
+ } else {
+ //[ ctx, timeout, callback ]
+
+ return id({
+ ...defaultParams,
+ timeout,
+ callback,
+ "ctx": p1
+ });
+
+ }
+ } else {
+ //[ op, ctx, callback ]
+ return id({
+ ...defaultParams,
+ "op": p1,
+ "ctx": p2,
+ "callback": p3
+ });
+
+ }
+
+ }
+ case 2: {
+
+ //[ op, callback ]
+ //[ ctx, callback ]
+ //[ timeout, callback ]
+ const [p1, p2] = inputs;
+ if (typeof p1 === "number") {
+ //[ timeout, callback ]
+ return id({
+ ...defaultParams,
+ "timeout": p1,
+ "callback": p2
+ });
+ } else {
+ //[ op, callback ]
+ //[ ctx, callback ]
+ const callback: Out["callback"] = p2;
+ if (canBeOperator(p1)) {
+
+ return id({
+ ...defaultParams,
+ callback,
+ "op": p1
+ });
+
+ } else {
+
+ return id({
+ ...defaultParams,
+ callback,
+ "ctx": p1
+ });
+
+ }
+ }
+
+ }
+ case 1: {
+
+ //[ callback ]
+ const [p] = inputs;
+
+ return id({
+ ...defaultParams,
+ "callback": p
+ });
+
+ }
+ case 0: {
+ return id({ ...defaultParams });
+ }
+
+ }
+
+
+ } break;
+
+ }
+
+}
+
diff --git a/deno_dist/lib/Evt.ts b/deno_dist/lib/Evt.ts
new file mode 100644
index 00000000..33b89f11
--- /dev/null
+++ b/deno_dist/lib/Evt.ts
@@ -0,0 +1,936 @@
+import "https://raw.githubusercontent.com/garronej/minimal_polyfills/v2.2.1/deno_dist/Array.prototype.find.ts";
+import { importProxy } from "./importProxy.ts";
+import { create } from "./Evt.create.ts";
+import { getCtxFactory } from "./Evt.getCtx.ts";
+import { factorize } from "./Evt.factorize.ts";
+import { merge } from "./Evt.merge.ts";
+import { from } from "./Evt.from.ts";
+import { asPostable } from "./Evt.asPostable.ts";
+import { asyncPipe } from "./Evt.asyncPipe.ts";
+import { asNonPostable } from "./Evt.asNonPostable.ts";
+import { parsePropsFromArgs, matchAll } from "./Evt.parsePropsFromArgs.ts";
+import { newCtx } from "./Evt.newCtx.ts";
+import { LazyEvt } from "./LazyEvt.ts";
+import { Polyfill as Map, LightMap } from "https://raw.githubusercontent.com/garronej/minimal_polyfills/v2.2.1/deno_dist/Map.ts";
+import { Polyfill as WeakMap } from "https://raw.githubusercontent.com/garronej/minimal_polyfills/v2.2.1/deno_dist/WeakMap.ts";
+import * as runExclusive from "https://raw.githubusercontent.com/garronej/run_exclusive/v2.2.14/deno_dist/mod.ts";
+import { overwriteReadonlyProp } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/lab/overwriteReadonlyProp.ts";
+import { typeGuard } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/typeGuard.ts";
+import { Deferred } from "../tools/Deferred.ts";
+import { loosenType } from "./Evt.loosenType.ts";
+import { safeClearTimeout, safeSetTimeout, Timer } from "../tools/safeSetTimeout.ts";
+import { isPromiseLike } from "https://raw.githubusercontent.com/garronej/tsafe/v0.10.0/deno_dist/isPromiseLike.ts";
+import { DetachedEvtError, TimeoutEvtError } from "./types/EvtError.ts";
+import * as nsCtxLike from "./types/interfaces/CtxLike.ts";
+import type { Handler, Operator, NonPostableEvt, StatefulEvt, EvtLike, CtxLike } from "./types/index.ts";
+import { convertOperatorToStatelessFλ } from "./util/convertOperatorToStatelessFLambda.ts";
+
+const runSideEffect = (sideEffect: () => void) => sideEffect();
+
+// NOTE: For compat with --no-check
+// https://github.com/asos-craigmorten/opine/issues/97#issuecomment-751806014
+const { CtxLike: CtxLikeAsValue } = nsCtxLike;
+
+/** https://docs.evt.land/api/evt */
+export type Evt = import("./types/interfaces/Evt.ts").Evt;
+
+
+class EvtImpl implements Evt {
+
+ static readonly create = create;
+
+ static readonly newCtx = newCtx;
+
+ static readonly merge = merge;
+
+ static readonly from = from;
+
+ static readonly getCtx = getCtxFactory();
+
+ static readonly loosenType = loosenType;
+
+ static readonly factorize = factorize;
+
+ static readonly asPostable = asPostable;
+
+ static readonly asyncPipe = asyncPipe;
+
+ static readonly asNonPostable = asNonPostable;
+
+ private static __defaultMaxHandlers = 25;
+
+ static setDefaultMaxHandlers(n: number): void {
+ this.__defaultMaxHandlers = isFinite(n) ? n : 0;
+ }
+
+ toStateful(p1: any, p2?: CtxLike): StatefulEvt {
+
+ const isP1Ctx = CtxLikeAsValue.match(p1);
+
+ const initialValue: any = isP1Ctx ? undefined : p1;
+ const ctx = p2 || (isP1Ctx ? p1 : undefined);
+
+ const out = new importProxy.StatefulEvt(initialValue);
+
+ const callback = (data: T) => out.post(data);
+
+ if (!!ctx) {
+ this.attach(ctx, callback);
+ } else {
+ this.attach(callback);
+ }
+
+ return out;
+
+ }
+
+ get evtAttach(): Evt> {
+ return this.lazyEvtAttach.evt;
+ }
+
+ get evtDetach(): Evt> {
+ return this.lazyEvtDetach.evt;
+ }
+
+ private readonly lazyEvtAttach = new LazyEvt>();
+ private readonly lazyEvtDetach = new LazyEvt>();
+
+
+ private __maxHandlers: undefined | number = undefined;
+
+ setMaxHandlers(n: number): this {
+ this.__maxHandlers = isFinite(n) ? n : 0;
+ return this;
+ }
+
+ readonly postCount: number = 0;
+
+ private traceId: string | null = null;
+ private traceFormatter!: (data: T) => string;
+ private log!: Exclude["enableTrace"]>[0]["log"], false>;
+
+ enableTrace(
+ params: {
+ id: string,
+ formatter?: (data: T) => string,
+ log?: ((message?: any, ...optionalParams: any[]) => void) | false
+ }
+ //NOTE: Not typeof console.log as we don't want to expose types from node
+ ): void {
+
+ const { id, formatter, log } = params;
+
+ this.traceId = id;
+
+ this.traceFormatter = formatter || (
+ data => {
+ try {
+ return JSON.stringify(data, null, 2);
+ } catch {
+ return `${data}`;
+ }
+ }
+ );
+
+ this.log =
+ log === undefined ?
+ ((...inputs) => console.log(...inputs)) :
+ log === false ? undefined : log
+ ;
+
+ }
+
+ disableTrace(): this {
+ this.traceId = null;
+ return this;
+ }
+
+ private readonly handlers: Handler[] = [];
+
+ private readonly handlerTriggers: LightMap<
+ Handler,
+ (opResult: readonly [any]) => PromiseLike | undefined
+ > = new Map();
+
+
+ //NOTE: An async handler ( attached with waitFor ) is only eligible to handle a post if the post
+ //occurred after the handler was set. We don't want to waitFor event from the past.
+ //private readonly asyncHandlerChronologyMark = new WeakMap();
+ private get asyncHandlerChronologyMark(): WeakMap<
+ Handler.PropsFromMethodName.Async,
+ number
+ > {
+ return ((this as any)["~internal"] ??= {})["asyncHandlerChronologyMark"] ??= new WeakMap();
+ }
+
+ //NOTE: There is an exception to the above rule, we want to allow async waitFor loop
+ //do so we have to handle the case where multiple event would be posted synchronously.
+ private get asyncHandlerChronologyExceptionRange(): WeakMap<
+ Handler.PropsFromMethodName.Async,
+ { lowerMark: number; upperMark: number; }
+ > {
+ return ((this as any)["~internal"] ??= {})["asyncHandlerChronologyExceptionRange"] ??= new WeakMap();
+ }
+
+
+ private get invocableOpByOp(): WeakMap<
+ Operator,
+ Operator.fλ.Stateless
+ > {
+ return ((this as any)["~internal"] ??= {})["invocableOpByOp"] ??= new WeakMap();
+ }
+
+ getInvocableOp(op: Operator): Operator.fλ.Stateless {
+
+ const invocableOp = this.invocableOpByOp.get(op);
+
+ if (invocableOp === undefined) {
+ throw new Error([
+ "Provided operator isn't the operator of any handler",
+ "currently attached to the Evt instance"
+ ].join(" "));
+ }
+
+ return invocableOp;
+
+ }
+
+ /*
+ NOTE: Used as Date.now() would be used to compare if an event is anterior
+ or posterior to an other. We don't use Date.now() because two call within
+ less than a ms will return the same value unlike this function.
+ */
+ private __currentChronologyMark = 0;
+ private getChronologyMark() {
+ return this.__currentChronologyMark++;
+ }
+
+
+ private asyncHandlerCount: number = 0;
+
+ private detachHandler(
+ handler: Handler,
+ wTimer: [Timer | undefined],
+ rejectPr: (error: DetachedEvtError) => void
+ ) {
+
+ const index = this.handlers.indexOf(handler);
+
+ if (index < 0) {
+ return false;
+ }
+
+ if (typeGuard>>(handler, !!handler.ctx)) {
+ handler.ctx.zz__removeHandler(handler);
+ }
+
+
+ this.handlers.splice(index, 1);
+
+ if (handler.async) {
+ this.asyncHandlerCount--;
+ }
+
+ this.handlerTriggers.delete(handler);
+
+ if (wTimer[0] !== undefined) {
+
+ safeClearTimeout(wTimer[0]);
+
+ rejectPr(new DetachedEvtError());
+
+ }
+
+ this.lazyEvtDetach.post(handler);
+
+ return true;
+
+ }
+
+
+ private triggerHandler(
+ handler: Handler,
+ wTimer: [Timer | undefined],
+ resolvePr: ((transformedData: any) => void) | undefined,
+ opResult: readonly [U] //TODO: Or readonly [ any ] ??
+ ): PromiseLike | undefined {
+
+ const { callback, once } = handler;
+
+ if (wTimer[0] !== undefined) {
+ safeClearTimeout(wTimer[0]);
+ wTimer[0] = undefined;
+ }
+
+ if (once) {
+ handler.detach();
+ }
+
+ const [transformedData] = opResult;
+
+ const prOrValue = callback?.call(
+ this,
+ transformedData
+ );
+
+ resolvePr?.(transformedData);
+
+ return isPromiseLike(prOrValue) ? prOrValue : undefined;
+
+ }
+
+ private addHandler(
+ propsFromArgs: Handler.PropsFromArgs,
+ propsFromMethodName: Handler.PropsFromMethodName
+ ): Handler {
+
+ this.invocableOpByOp.set(
+ propsFromArgs.op,
+ convertOperatorToStatelessFλ(propsFromArgs.op)
+ );
+
+ const d = new Deferred();
+
+ const wTimer: [Timer | undefined] = [undefined];
+
+ const handler: Handler = {
+ ...propsFromArgs,
+ ...propsFromMethodName,
+ "detach": () => this.detachHandler(handler, wTimer, d.reject),
+ "promise": d.pr
+ };
+
+ if (typeof handler.timeout === "number") {
+
+ wTimer[0] = safeSetTimeout(() => {
+
+ wTimer[0] = undefined;
+
+ handler.detach();
+
+ d.reject(new TimeoutEvtError(handler.timeout!));
+
+ }, handler.timeout);
+
+ }
+
+ const handlerTrigger: (opResult: readonly [U]) => PromiseLike | undefined
+ = opResult => this.triggerHandler(
+ handler,
+ wTimer,
+ d.isPending ? d.resolve : undefined,
+ opResult
+ );
+
+ this.handlerTriggers.set(
+ handler,
+ handlerTrigger
+ );
+
+ if (handler.async) {
+
+ this.asyncHandlerChronologyMark.set(
+ handler,
+ this.getChronologyMark()
+ );
+
+ }
+
+ if (handler.prepend) {
+
+ let i: number;
+
+ for (i = 0; i < this.handlers.length; i++) {
+
+ if (this.handlers[i].extract) {
+ continue;
+ }
+
+ break;
+
+ }
+
+ this.handlers.splice(i, 0, handler);
+
+ } else {
+
+ this.handlers.push(handler);
+
+ }
+
+ if (handler.async) {
+ this.asyncHandlerCount++;
+ }
+
+ this.checkForPotentialMemoryLeak();
+
+ if (typeGuard>>(handler, !!handler.ctx)) {
+ handler.ctx.zz__addHandler(handler, this);
+ }
+
+ onAddHandlerByEvt.get(this)?.(handler, handlerTrigger);
+
+ //NOTE: Can happen for example if this is a StatefulEvt
+ //and the handler is "once" and the matcher match the state
+ //We don't want to post an attach if the handler is already detached.
+ if (this.handlerTriggers.has(handler)) {
+
+ this.lazyEvtAttach.post(handler);
+
+ }
+
+ return handler;
+
+ }
+
+
+
+ private checkForPotentialMemoryLeak(): void {
+
+ const maxHandlers = this.__maxHandlers !== undefined ?
+ this.__maxHandlers :
+ EvtImpl.__defaultMaxHandlers
+ ;
+
+
+ if (
+ maxHandlers === 0 ||
+ this.handlers.length % (maxHandlers + 1) !== 0) {
+ return;
+ }
+
+ let message = [
+ `MaxHandlersExceededWarning: Possible Evt memory leak detected.`,
+ `${this.handlers.length} handlers attached${this.traceId ? ` to "${this.traceId}"` : ""}.\n`,
+ `Use Evt.prototype.setMaxHandlers(n) to increase limit on a specific Evt.\n`,
+ `Use Evt.setDefaultMaxHandlers(n) to change the default limit currently set to ${EvtImpl.__defaultMaxHandlers}.\n`,
+ ].join("");
+
+ const map = new Map();
+
+ this.getHandlers()
+ .map(({ ctx, async, once, prepend, extract, op, callback }) => ({
+ "hasCtx": !!ctx,
+ once,
+ prepend,
+ extract,
+ "isWaitFor": async,
+ ...(op === matchAll ? {} : { "op": op.toString() }),
+ ...(!callback ? {} : { "callback": callback.toString() })
+ }))
+ .map(obj =>
+ "{\n" + Object.keys(obj)
+ .map(key => ` ${key}: ${(obj as any)[key]}`)
+ .join(",\n") + "\n}"
+ )
+ .forEach(str => map.set(str, (map.has(str) ? map.get(str)! : 0) + 1))
+ ;
+
+ message += "\n" + Array.from(map.keys())
+ .map(str => `${map.get(str)} handler${map.get(str) === 1 ? "" : "s"} like:\n${str}`)
+ .join("\n") + "\n";
+
+ if (this.traceId === null) {
+
+ message += "\n" + [
+ `To validate the identify of the Evt instance that is triggering this warning you can call`,
+ `Evt.prototype.enableTrace({ "id": "My evt id", "log": false }) on the Evt that you suspect.\n`
+ ].join(" ");
+
+ }
+
+ try {
+ console.warn(message);
+ } catch {
+ }
+
+ }
+
+ isHandledByOp(op: Operator, data: T): boolean {
+
+ let hasSideEffect = false;
+
+ let invocableOp: Operator.fλ.Stateless;
+
+ try {
+
+ invocableOp = this.getInvocableOp(op);
+
+ } catch {
+
+ return false;
+
+ }
+
+ const opResult = invocableOp(
+ data,
+ () => hasSideEffect = true
+ );
+
+ return opResult !== null || hasSideEffect;
+
+ }
+
+
+ private trace(data: T) {
+
+ if (this.traceId === null) {
+ return;
+ }
+
+ let message = `(${this.traceId}) `;
+
+ const isExtracted = !!this.handlers.find(
+ ({ extract, op }) => (
+ extract &&
+ this.isHandledByOp(op, data)
+ )
+ );
+
+ if (isExtracted) {
+
+ message += "extracted ";
+
+ } else {
+
+ const handlerCount = this.handlers
+ .filter(
+ ({ extract, op }) => !extract &&
+ this.isHandledByOp(op, data)
+ )
+ .length;
+
+ message += `${handlerCount} handler${(handlerCount > 1) ? "s" : ""}, `;
+
+ }
+
+ this.log?.(message + this.traceFormatter(data));
+
+ }
+
+ /** Return [ isExtracted, prAllHandlerCallbacksResolved ] */
+ private postSync(data: T): readonly [boolean, Promise] {
+
+ const prAllHandlerCallbacksResolved: PromiseLike[] = [];
+
+ const getReturnValue = (isExtracted: boolean) => [
+ isExtracted,
+ Promise.all(prAllHandlerCallbacksResolved).then(() => { })
+ ] as const;
+
+
+ for (const handler of [...this.handlers]) {
+
+ const { async, op, extract } = handler;
+
+ if (async) {
+ continue;
+ }
+
+ //NOTE: If detached while executing the operator
+ //we still want to trigger the handler.
+ const handlerTrigger = this.handlerTriggers.get(handler);
+
+ const opResult = this.getInvocableOp(op)(
+ data,
+ runSideEffect
+ );
+
+ if (opResult === null) {
+ continue;
+ }
+
+ //NOTE: Possible if detached while in the loop.
+ if (!handlerTrigger) {
+ continue;
+ }
+
+ const prOrUndefined = handlerTrigger(opResult);
+
+ if (prOrUndefined !== undefined) {
+ prAllHandlerCallbacksResolved.push(prOrUndefined);
+ }
+
+ if (extract) {
+ return getReturnValue(true);
+ }
+
+ }
+
+ return getReturnValue(false);
+
+ }
+
+ private postAsyncFactory() {
+ return runExclusive.buildMethodCb(
+ (data: T, postChronologyMark: number, releaseLock?) => {
+
+ if (this.asyncHandlerCount === 0) {
+ releaseLock();
+ return;
+ }
+
+ const promises: Promise[] = [];
+
+ let chronologyMarkStartResolveTick: number;
+
+ //NOTE: Must be before handlerTrigger call.
+ Promise.resolve().then(
+ () => chronologyMarkStartResolveTick = this.getChronologyMark()
+ );
+
+
+ for (const handler of [...this.handlers]) {
+
+ if (!handler.async) {
+ continue;
+ }
+
+ const opResult = this.getInvocableOp(handler.op)(
+ data,
+ runSideEffect
+ );
+
+ if (opResult === null) {
+ continue;
+ }
+
+ const handlerTrigger = this.handlerTriggers.get(handler);
+
+ if (!handlerTrigger) {
+ continue;
+ }
+
+ const shouldCallHandlerTrigger = (() => {
+
+ const handlerMark = this.asyncHandlerChronologyMark.get(handler)!;
+
+ if (postChronologyMark > handlerMark) {
+ return true;
+ }
+
+ const exceptionRange = this.asyncHandlerChronologyExceptionRange.get(handler);
+
+ return (
+ exceptionRange !== undefined &&
+ exceptionRange.lowerMark < postChronologyMark &&
+ postChronologyMark < exceptionRange.upperMark &&
+ handlerMark > exceptionRange.upperMark
+ );
+
+ })();
+
+ if (!shouldCallHandlerTrigger) {
+ continue;
+ }
+
+ promises.push(
+ new Promise(
+ resolve => handler.promise
+ .then(() => resolve())
+ .catch(() => resolve())
+ )
+ );
+
+ handlerTrigger(opResult);
+
+
+ }
+
+ if (promises.length === 0) {
+ releaseLock();
+ return;
+ }
+
+ const handlersDump = [...this.handlers];
+
+ Promise.all(promises).then(() => {
+
+ for (const handler of this.handlers) {
+
+ if (!handler.async) {
+ continue;
+ }
+
+ if (handlersDump.indexOf(handler) >= 0) {
+ continue;
+ }
+
+ this.asyncHandlerChronologyExceptionRange.set(
+ handler,
+ {
+ "lowerMark": postChronologyMark,
+ "upperMark": chronologyMarkStartResolveTick
+ }
+ );
+
+ }
+
+ releaseLock();
+
+ });
+
+ }
+ );
+ }
+
+ declare private postAsync: (
+ (
+ data: T,
+ postChronologyMark: number
+ ) => void
+ ) | undefined;
+
+ private static readonly propsFormMethodNames: Record<
+ "waitFor" | "attach" | "attachExtract" | "attachPrepend" | "attachOnce" |
+ "attachOncePrepend" | "attachOnceExtract"
+ ,
+ Handler.PropsFromMethodName
+ > = {
+ "waitFor": { "async": true, "extract": false, "once": true, "prepend": false },
+ "attach": { "async": false, "extract": false, "once": false, "prepend": false },
+ "attachExtract": { "async": false, "extract": true, "once": false, "prepend": true },
+ "attachPrepend": { "async": false, "extract": false, "once": false, "prepend": true },
+ "attachOnce": { "async": false, "extract": false, "once": true, "prepend": false },
+ "attachOncePrepend": { "async": false, "extract": false, "once": true, "prepend": true },
+ "attachOnceExtract": { "async": false, "extract": true, "once": true, "prepend": true }
+ };
+
+ isHandled(data: T): boolean {
+ return !!this.getHandlers()
+ .find(({ op }) => this.isHandledByOp(op, data))
+ ;
+ }
+
+ getHandlers(): Handler[] {
+ return [...this.handlers];
+ }
+
+ detach(ctx?: CtxLike): Handler[] {
+
+ const detachedHandlers: Handler[] = [];
+
+ for (const handler of this.getHandlers()) {
+
+ if (ctx !== undefined && handler.ctx !== ctx) {
+ continue;
+ }
+
+ const wasStillAttached = handler.detach();
+
+ //NOTE: It should not be possible.
+ if (!wasStillAttached) {
+ continue;
+ }
+
+ detachedHandlers.push(handler);
+
+ }
+
+ return detachedHandlers;
+
+ }
+
+ pipe(...args: any[]): Evt {
+
+ const evtDelegate = new EvtImpl