diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml
index df7ef06c..ed18d388 100644
--- a/.github/workflows/javascript.yml
+++ b/.github/workflows/javascript.yml
@@ -18,10 +18,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Use Node.js 16.x
+ - name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
- node-version: 16.x
+ node-version: 18.x
cache: yarn
- name: Install
@@ -45,10 +45,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Use Node.js 16.x
+ - name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
- node-version: 16.x
+ node-version: 18.x
cache: yarn
- name: Install
@@ -70,10 +70,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Use Node.js 16.x
+ - name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
- node-version: 16.x
+ node-version: 18.x
cache: yarn
- name: Install
@@ -101,10 +101,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Use Node.js 16.x
+ - name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
- node-version: 16.x
+ node-version: 18.x
registry-url: https://registry.npmjs.org
cache: yarn
diff --git a/.idea/aegis.iml b/.idea/aegis.iml
index 96150fc3..852852aa 100644
--- a/.idea/aegis.iml
+++ b/.idea/aegis.iml
@@ -9,8 +9,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/build.xml b/.idea/runConfigurations/build.xml
new file mode 100644
index 00000000..0a4f98f9
--- /dev/null
+++ b/.idea/runConfigurations/build.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/sonarlint.xml b/.idea/sonarlint.xml
new file mode 100644
index 00000000..e081b886
--- /dev/null
+++ b/.idea/sonarlint.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.swcrc b/.swcrc
index 387af680..829b6824 100644
--- a/.swcrc
+++ b/.swcrc
@@ -2,7 +2,11 @@
"jsc": {
"target": "esnext",
"parser": {
- "syntax": "typescript"
+ "syntax": "typescript",
+ "decorators": true
+ },
+ "transform": {
+ "decoratorVersion": "2022-03"
}
},
"module": {
diff --git a/.yarn/releases/yarn-3.6.4.cjs b/.yarn/releases/yarn-3.6.4.cjs
old mode 100755
new mode 100644
diff --git a/.yarnrc.yml b/.yarnrc.yml
index 2a88f99c..164b16b8 100644
--- a/.yarnrc.yml
+++ b/.yarnrc.yml
@@ -4,10 +4,4 @@ changesetBaseRefs:
- next
- origin/next
-packageExtensions:
- rollup-plugin-swc3@*:
- peerDependenciesMeta:
- rollup:
- optional: true
-
yarnPath: .yarn/releases/yarn-3.6.4.cjs
diff --git a/package.json b/package.json
index 2275dd5f..127b4ef4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@jujulego/aegis",
- "version": "2.0.0-alpha.6",
+ "version": "2.0.0-beta.6",
"license": "MIT",
"author": "Julien Capellari ",
"repository": {
@@ -32,24 +32,32 @@
"test:types": "vitest typecheck"
},
"dependencies": {
- "@jujulego/event-tree": "^4.0.1",
+ "@jujulego/event-tree": "^4.1.1",
"@jujulego/utils": "^2.0.0"
},
"devDependencies": {
- "@jujulego/jill": "2.3.1",
+ "@jujulego/jill": "2.3.2",
+ "@jujulego/vite-plugin-swc": "1.1.0",
"@microsoft/eslint-formatter-sarif": "3.0.0",
"@swc/cli": "0.1.62",
"@swc/core": "1.3.94",
- "@types/node": "16.18.59",
+ "@types/node": "18.18.6",
"@typescript-eslint/eslint-plugin": "6.8.0",
"@typescript-eslint/parser": "6.8.0",
"@vitest/coverage-v8": "0.34.6",
"eslint": "8.52.0",
"eslint-plugin-vitest": "0.3.6",
- "rollup-plugin-swc3": "0.10.3",
+ "jsdom": "22.1.0",
"shx": "0.3.4",
- "typescript": "5.1.6",
- "vitest": "0.34.6"
+ "typescript": "5.2.2",
+ "vite": "5.0.0-beta.11",
+ "vite-tsconfig-paths": "4.2.1",
+ "vitest": "0.34.5"
},
- "packageManager": "yarn@3.6.4"
+ "packageManager": "yarn@3.6.4",
+ "dependenciesMeta": {
+ "vitest@0.34.5": {
+ "unplugged": true
+ }
+ }
}
diff --git a/src/aegis.ts b/src/aegis.ts
deleted file mode 100644
index 72558151..00000000
--- a/src/aegis.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { source, waitFor } from '@jujulego/event-tree';
-
-import { Blade, ReadonlyRef } from './defs/index.js';
-
-/**
- *
- */
-export class Aegis implements ReadonlyRef {
- // Attributes
- private _data?: D;
- private _args?: A;
-
- private readonly _events = source();
-
- // Constructor
- constructor(readonly blade: Blade) {}
-
- // Methods
- readonly subscribe = this._events.subscribe;
- readonly unsubscribe = this._events.unsubscribe;
- readonly clear = this._events.clear;
-
- private _needRefresh(args: A): boolean {
- if (!this._args || this._args.length !== args.length) {
- return true;
- }
-
- return this._args.some((arg, idx) => args[idx] !== arg);
- }
-
- async read(): Promise {
- if (this._data === undefined) {
- return waitFor(this._events);
- }
-
- return this._data;
- }
-
- async refresh(...args: A): Promise {
- // Check args
- if (this._data && !this._needRefresh(args)) {
- return this._data;
- }
-
- // Refresh
- this._args = args;
- this._data = await this.blade(...args);
- this._events.next(this._data);
-
- return this._data;
- }
-
- // Properties
- get data(): D | undefined {
- return this._data;
- }
-
- get isEmpty(): boolean {
- return !this._data;
- }
-}
-
-/**
- *
- */
-export function aegis(blade: Blade): Aegis {
- return new Aegis(blade);
-}
diff --git a/src/bind.ts b/src/bind.ts
new file mode 100644
index 00000000..da815ec1
--- /dev/null
+++ b/src/bind.ts
@@ -0,0 +1,23 @@
+import { SyncMutableRef, SyncRef } from './defs/index.js';
+
+// Types
+export type BindDecorator = (target: ClassAccessorDecoratorTarget, ctx: ClassAccessorDecoratorContext) => ClassAccessorDecoratorResult;
+
+// Decorator
+export function BindRef(ref: SyncRef | SyncMutableRef): BindDecorator {
+ return (_, ctx) => {
+ const result: ClassAccessorDecoratorResult = {
+ get: ref.read,
+ };
+
+ if ('mutate' in ref) {
+ result.set = ref.mutate;
+ } else {
+ result.set = () => {
+ throw new Error(`Cannot set ${String(ctx.name)}, it is bound to a readonly reference.`);
+ };
+ }
+
+ return result;
+ };
+}
\ No newline at end of file
diff --git a/src/data/d-ref.ts b/src/data/d-ref.ts
deleted file mode 100644
index 8faeccc9..00000000
--- a/src/data/d-ref.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { source, waitFor } from '@jujulego/event-tree';
-
-import { Ref } from '../defs/index.js';
-
-import { DataAccessor } from './types.js';
-
-// Class
-export class DRef implements Ref {
- // Attributes
- private readonly _events = source();
-
- // Constructor
- constructor(
- private readonly accessor: DataAccessor,
- ) {}
-
- // Methods
- readonly subscribe = this._events.subscribe;
- readonly unsubscribe = this._events.unsubscribe;
- readonly clear = this._events.clear;
-
- async read(): Promise {
- const data = this.data;
-
- if (data === undefined) {
- return waitFor(this._events);
- }
-
- return data;
- }
-
- update(data: D): void {
- this.accessor.update(data);
- this._events.next(data);
- }
-
- // Properties
- get data(): D | undefined {
- return this.accessor.read();
- }
-
- get isEmpty(): boolean {
- if (this.accessor.isEmpty) {
- return this.accessor.isEmpty();
- }
-
- return this.accessor.read() === undefined;
- }
-}
diff --git a/src/data/d-var.ts b/src/data/d-var.ts
deleted file mode 100644
index 9f541194..00000000
--- a/src/data/d-var.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { DRef } from './d-ref.js';
-
-// Class
-export class DVar extends DRef {
- // Attributes
- private _data: D | undefined;
-
- // Constructor
- constructor(initial?: D) {
- super({
- read: () => this._data,
- update: (data: D) => {
- this._data = data;
- }
- });
-
- this._data = initial;
- }
-}
diff --git a/src/data/index.ts b/src/data/index.ts
deleted file mode 100644
index 263336e9..00000000
--- a/src/data/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './d-ref.js';
-export * from './d-var.js';
-export * from './types.js';
\ No newline at end of file
diff --git a/src/data/types.ts b/src/data/types.ts
deleted file mode 100644
index 4a3159f5..00000000
--- a/src/data/types.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-// Types
-export interface DataAccessor {
- isEmpty?: () => boolean;
- read(): D | undefined;
- update(data: D): void;
-}
-
-// Events
-export interface StoreUpdateEvent {
- data: D;
- old?: D;
-}
-
-export interface StoreDeleteEvent {
- old: D;
-}
-
-export type StoreEvent = StoreUpdateEvent | StoreDeleteEvent;
diff --git a/src/defs/blade.ts b/src/defs/blade.ts
deleted file mode 100644
index 4a59271d..00000000
--- a/src/defs/blade.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * Blade logic
- */
-export type Blade = (...args: A) => D | PromiseLike;
diff --git a/src/defs/index.ts b/src/defs/index.ts
index 7d93f982..202d0329 100644
--- a/src/defs/index.ts
+++ b/src/defs/index.ts
@@ -1,3 +1,5 @@
-export * from './blade.js';
-export * from './readonly-ref.js';
+export * from './mutable.js';
+export * from './mutable-ref.js';
+export * from './pipe.js';
+export * from './readable.js';
export * from './ref.js';
diff --git a/src/defs/mutable-ref.ts b/src/defs/mutable-ref.ts
new file mode 100644
index 00000000..163e977e
--- /dev/null
+++ b/src/defs/mutable-ref.ts
@@ -0,0 +1,23 @@
+import { AsyncMutable, MapMutateArg, Mutable, SyncMutable } from './mutable.js';
+import { AsyncReadable, MapReadValue, Readable, SyncReadable } from './readable.js';
+import { Ref } from './ref.js';
+
+/**
+ * Mutable reference
+ */
+export type MutableRef = Readable, M extends Mutable = Mutable> = Ref & M;
+
+/**
+ * Mutable synchronous reference
+ */
+export type SyncMutableRef = MutableRef, SyncMutable>;
+
+/**
+ * Mutable asynchronous reference
+ */
+export type AsyncMutableRef = MutableRef, AsyncMutable>;
+
+/**
+ * Build a Mutable type with the same synchronicity and the given value types
+ */
+export type MapMutableValue = MutableRef, MapMutateArg>;
diff --git a/src/defs/mutable.ts b/src/defs/mutable.ts
new file mode 100644
index 00000000..8c909a93
--- /dev/null
+++ b/src/defs/mutable.ts
@@ -0,0 +1,52 @@
+import { Awaitable } from '@jujulego/utils';
+
+/**
+ * Defines an object that can be mutated
+ */
+export interface Mutable {
+ /**
+ * Mutate current value
+ */
+ mutate(arg: A): Awaitable;
+}
+
+/**
+ * Defines an object that can be synchronously mutated
+ */
+export interface SyncMutable extends Mutable {
+ /**
+ * Mutate current value synchronously
+ */
+ mutate(arg: A): D;
+}
+
+/**
+ * Defines an object that can be asynchronously mutated
+ */
+export interface AsyncMutable extends Mutable {
+ /**
+ * Mutate current value asynchronously
+ */
+ mutate(arg: A): PromiseLike;
+}
+
+// Utils
+/**
+ * Extract mutate value type
+ */
+export type MutateArg =
+ M extends AsyncMutable
+ ? A
+ : M extends SyncMutable
+ ? A
+ : never;
+
+/**
+ * Build a Mutable type with the same synchronicity and the given value type
+ */
+export type MapMutateArg =
+ M extends AsyncMutable
+ ? AsyncMutable
+ : M extends SyncMutable
+ ? SyncMutable
+ : never;
diff --git a/src/defs/pipe.ts b/src/defs/pipe.ts
new file mode 100644
index 00000000..dce40c0f
--- /dev/null
+++ b/src/defs/pipe.ts
@@ -0,0 +1,11 @@
+import { OffGroup, Observable as Obs } from '@jujulego/event-tree';
+
+export interface PipeContext {
+ off: OffGroup;
+}
+
+export type PipeOperator = (arg: A, context: PipeContext) => B;
+
+export type PipeOff = R & {
+ off(): void;
+}
diff --git a/src/defs/readable.ts b/src/defs/readable.ts
new file mode 100644
index 00000000..db05aae6
--- /dev/null
+++ b/src/defs/readable.ts
@@ -0,0 +1,52 @@
+import { Awaitable } from '@jujulego/utils';
+
+/**
+ * Defines an object that can be read
+ */
+export interface Readable {
+ /**
+ * Return current value
+ */
+ read(): Awaitable;
+}
+
+/**
+ * Defines an object that can be synchronously read
+ */
+export interface SyncReadable extends Readable {
+ /**
+ * Return current value
+ */
+ read(): D;
+}
+
+/**
+ * Defines an object that can be asynchronously read
+ */
+export interface AsyncReadable extends Readable {
+ /**
+ * Return current value asynchronously
+ */
+ read(): PromiseLike;
+}
+
+// Utils
+/**
+ * Extract read value type
+ */
+export type ReadValue =
+ R extends AsyncReadable
+ ? D
+ : R extends SyncReadable
+ ? D
+ : never;
+
+/**
+ * Build a Readable type with the same synchronicity and the given value type
+ */
+export type MapReadValue =
+ R extends AsyncReadable
+ ? AsyncReadable
+ : R extends SyncReadable
+ ? SyncReadable
+ : never;
diff --git a/src/defs/readonly-ref.ts b/src/defs/readonly-ref.ts
deleted file mode 100644
index 10eea295..00000000
--- a/src/defs/readonly-ref.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { IObservable } from '@jujulego/event-tree';
-
-/**
- * Readonly reference
- */
-export interface ReadonlyRef extends IObservable {
- // Attributes
- readonly isEmpty: boolean;
- readonly data: D | undefined;
-
- // Methods
- read(): PromiseLike;
-}
diff --git a/src/defs/ref.ts b/src/defs/ref.ts
index 3768217e..5743ed3f 100644
--- a/src/defs/ref.ts
+++ b/src/defs/ref.ts
@@ -1,9 +1,23 @@
-import { ReadonlyRef } from './readonly-ref.js';
+import { Source } from '@jujulego/event-tree';
+
+import { AsyncReadable, MapReadValue, Readable, SyncReadable } from './readable.js';
+
+/**
+ * Readonly reference
+ */
+export type Ref = Readable> = R & Source;
+
+/**
+ * Readonly synchronous reference
+ */
+export type SyncRef = Ref>;
+
+/**
+ * Readonly asynchronous reference
+ */
+export type AsyncRef = Ref>;
/**
- * Updatable reference
+ * Build a Ref type with the same synchronicity and the given value type
*/
-export interface Ref extends ReadonlyRef {
- // Methods
- update(data: D): void;
-}
+export type MapRefValue = Ref>;
diff --git a/src/flow.ts b/src/flow.ts
new file mode 100644
index 00000000..d7ff114a
--- /dev/null
+++ b/src/flow.ts
@@ -0,0 +1,29 @@
+import { off$, Observable as Obs, Emitter, ObservedValue, OffGroup } from '@jujulego/event-tree';
+
+import { PipeOperator } from './defs/index.js';
+
+// Builder
+type PO = PipeOperator;
+type Rcv = Emitter>;
+
+export function flow$(obs: A, rcv: Rcv): OffGroup;
+export function flow$(obs: A, opA: PO, rcv: Rcv): OffGroup;
+export function flow$(obs: A, opA: PO, opB: PO, rcv: Rcv): OffGroup;
+export function flow$(obs: A, opA: PO, opB: PO, opC: PO, rcv: Rcv): OffGroup;
+export function flow$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO, rcv: Rcv): OffGroup;
+export function flow$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO, opE: PO, rcv: Rcv): OffGroup;
+export function flow$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO, opE: PO, opF: PO, rcv: Rcv): OffGroup;
+export function flow$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO, opE: PO, opF: PO, opG: PO, rcv: Rcv): OffGroup;
+export function flow$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO, opE: PO, opF: PO, opG: PO, opH: PO, rcv: Rcv): OffGroup;
+export function flow$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO, opE: PO, opF: PO, opG: PO, opH: PO, opI: PO, rcv: Rcv): OffGroup;
+
+export function flow$(obs: Obs, ...rest: [...PipeOperator[], rcv: Emitter]): OffGroup {
+ const rcv = rest.pop() as Emitter;
+ const ops = rest as PipeOperator[];
+
+ const off = off$();
+ const out = ops.reduce((step, op) => op(step, { off }), obs);
+ off.add(out.subscribe(rcv.next));
+
+ return off;
+}
diff --git a/src/index.ts b/src/index.ts
index ccd27ca7..aa7e06ae 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,9 @@
-export * from './aegis.js';
-export * from './data/index.js';
-export * from './query/index.js';
-export * from './store/index.js';
-export * from './utils/index.js';
+export * from './bind.js';
+export * from './defs/index.js';
+export * from './flow.js';
+export * from './operators/index.js';
+export * from './pipe.js';
+export * from './refs/index.js';
+export * from './registry.js';
+export * from './stores/index.js';
+export * from './watch.js';
diff --git a/src/operators/each.ts b/src/operators/each.ts
new file mode 100644
index 00000000..0a688f5b
--- /dev/null
+++ b/src/operators/each.ts
@@ -0,0 +1,89 @@
+import { Observable as Obs, Source, ObservedValue, source$ } from '@jujulego/event-tree';
+import { Awaitable } from '@jujulego/utils';
+
+import {
+ AsyncMutableRef, AsyncRef,
+ MapMutateArg,
+ MapReadValue, MapRefValue,
+ Mutable,
+ MutableRef, PipeOperator,
+ Readable,
+ Ref
+} from '../defs/index.js';
+import { awaitedCall } from '../utils/promise.js';
+
+// Types
+export type EachFn = (arg: DA) => Awaitable;
+export type SyncEachFn = (arg: DA) => DB;
+export type AsyncEachFn = (arg: DA) => PromiseLike;
+
+/** Builds an async source type, with same features than A, but a different data type DB */
+export type EachAsyncSource = A extends Ref
+ ? A extends Mutable
+ ? AsyncMutableRef
+ : AsyncRef
+ : Source;
+
+/** Builds a source type, with same features and synchronicity than A, but a different data type DB */
+export type EachSyncSource = A extends Ref
+ ? A extends Mutable
+ ? MutableRef, MapMutateArg>
+ : MapRefValue
+ : Source;
+
+/** Builds an awaitable source type, with same features than A, but a different data type DB */
+export type EachSource = A extends Ref
+ ? A extends Mutable
+ ? MutableRef
+ : Ref
+ : Source;
+
+// Operator
+/**
+ * Applies fn to each emitted value, read result and mutate result.
+ * As fn is asynchronous, read and mutate in the final reference will too be asynchronous.
+ *
+ * WARNING: Order is not guaranteed, results will be emitted as they are resolved not as input comes.
+ *
+ * @param fn
+ */
+export function each$(fn: AsyncEachFn, DB>): PipeOperator>;
+
+/**
+ * Applies fn to each emitted value, read result and mutate result.
+ * As fn is synchronous, read and mutate in the final reference will have the same synchronicity as the base ref.
+ *
+ * @param fn
+ */
+export function each$(fn: SyncEachFn, DB>): PipeOperator>;
+
+/**
+ * Applies fn to each emitted value, read result and mutate result.
+ *
+ * @param fn
+ */
+export function each$(fn: EachFn, DB>): PipeOperator>;
+
+export function each$(fn: EachFn): PipeOperator, Obs> {
+ return (obs: Obs, { off }) => {
+ const out = source$();
+
+ if ('read' in obs) {
+ Object.assign(out, {
+ read: () => awaitedCall(fn, (obs as Readable).read()),
+ });
+
+ if ('mutate' in obs) {
+ Object.assign(out, {
+ mutate: (arg: AA) => awaitedCall(fn, awaitedCall((obs as Mutable).mutate, arg))
+ });
+ }
+ }
+
+ off.add(
+ obs.subscribe((data) => awaitedCall(out.next, fn(data)))
+ );
+
+ return out;
+ };
+}
diff --git a/src/operators/filter.ts b/src/operators/filter.ts
new file mode 100644
index 00000000..379b5eac
--- /dev/null
+++ b/src/operators/filter.ts
@@ -0,0 +1,22 @@
+import { Observable as Obs, Source, source$ } from '@jujulego/event-tree';
+
+import { PipeOperator } from '../defs/index.js';
+
+// Operator
+export function filter$(fn: (arg: DA) => arg is DB): PipeOperator, Source>;
+
+export function filter$(fn: (arg: D) => boolean): PipeOperator, Source>;
+
+export function filter$(fn: (arg: D) => boolean): PipeOperator, Source> {
+ return (obs: Obs, { off }) => {
+ const out = source$();
+
+ off.add(obs.subscribe((data) => {
+ if (fn(data)) {
+ out.next(data);
+ }
+ }));
+
+ return out;
+ };
+}
diff --git a/src/operators/index.ts b/src/operators/index.ts
new file mode 100644
index 00000000..6ba3911a
--- /dev/null
+++ b/src/operators/index.ts
@@ -0,0 +1,2 @@
+export * from './each.js';
+export * from './filter.js';
diff --git a/src/pipe.ts b/src/pipe.ts
new file mode 100644
index 00000000..d7dc7cfe
--- /dev/null
+++ b/src/pipe.ts
@@ -0,0 +1,24 @@
+import { off$, Observable as Obs } from '@jujulego/event-tree';
+
+import { PipeOff, PipeOperator } from './defs/index.js';
+
+// Builder
+type PO = PipeOperator;
+
+export function pipe$(obs: A): PipeOff;
+export function pipe$(obs: A, opA: PO): PipeOff;
+export function pipe$(obs: A, opA: PO, opB: PO): PipeOff;
+export function pipe$(obs: A, opA: PO, opB: PO, opC: PO): PipeOff;
+export function pipe$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO): PipeOff;
+export function pipe$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO, opE: PO): PipeOff;
+export function pipe$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO, opE: PO, opF: PO): PipeOff;
+export function pipe$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO, opE: PO, opF: PO, opG: PO): PipeOff;
+export function pipe$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO, opE: PO, opF: PO, opG: PO, opH: PO): PipeOff;
+export function pipe$(obs: A, opA: PO, opB: PO, opC: PO, opD: PO, opE: PO, opF: PO, opG: PO, opH: PO, opI: PO): PipeOff;
+
+export function pipe$(obs: Obs, ...ops: PipeOperator[]): PipeOff {
+ const off = off$();
+ const out = ops.reduce((step, op) => op(step, { off }), obs);
+
+ return Object.assign(out, { off });
+}
diff --git a/src/query/index.ts b/src/query/index.ts
deleted file mode 100644
index 807bab77..00000000
--- a/src/query/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './manager.js';
-export * from './q-ref.js';
diff --git a/src/query/manager.ts b/src/query/manager.ts
deleted file mode 100644
index 6a319c3b..00000000
--- a/src/query/manager.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { IListenable, KeyPart, ListenEventRecord, multiplexerMap } from '@jujulego/event-tree';
-
-import { WeakStore } from '../utils/index.js';
-
-import { Fetcher, QRef, Strategy } from './q-ref.js';
-
-// Types
-export type ManagerEventMap = ListenEventRecord>;
-
-// Class
-export class Manager