Skip to content

Commit 7168963

Browse files
author
Duke
committed
chore: removed createState and provideState
chore: changed pushObservable to controller feat: added get and getState to subscribable and controllerBase
1 parent 3c206c8 commit 7168963

File tree

5 files changed

+189
-265
lines changed

5 files changed

+189
-265
lines changed

src/index.ts

+4-26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { observables, pipe, pushObservable, type Operator, type PushObservable, type Subscribable, type Subscriber } from "./rx"
1+
import { observables, pipe, createObservable, type Operator, type PushObservable, type Subscribable, type Subscriber } from "./rx"
22

33
declare const ExecutorIdBrand: unique symbol
44
export type ExecutorId = string & { readonly [ExecutorIdBrand]: never }
@@ -830,40 +830,18 @@ export type PushObservableExecutor<Value> = Executor<PushObservable<Value>>
830830
* @param pValue
831831
* @returns
832832
*/
833-
export function providePushObservable<Value>(
833+
export function provideObservable<Value>(
834834
pValue?: Value | Executor<Value>
835835
): PushObservableExecutor<Value> {
836836
return pValue
837837
? map(normalize(pValue), (value) => {
838-
return pushObservable(value)
838+
return createObservable(value)
839839
})
840840
: provide(() => {
841-
return pushObservable()
841+
return createObservable()
842842
})
843843
}
844844

845-
/**
846-
* Utility to integrate state with submodule ecosystem
847-
* @param pValue - value that will be used as initial state
848-
* @param controller - api to control the state
849-
* @returns
850-
*/
851-
export function provideState<
852-
Value,
853-
Controller,
854-
ControllerFn extends (sub: Subscriber<Value>, get: () => Value) => Controller
855-
>(
856-
pValue: Value | Executor<Value>,
857-
controller: ControllerFn | Executor<ControllerFn>
858-
): Executor<readonly [Subscribable<Value>, Controller]> {
859-
return map(
860-
{ value: normalize(pValue), controller: normalize(controller) },
861-
({ value, controller }) => {
862-
return observables.createState<Value, Controller>(value, controller)
863-
}
864-
)
865-
}
866-
867845
export type OperatorLike<S, A> = Operator<S, A> | Executor<Operator<S, A>>
868846

869847
/**

src/react/index.tsx

+11-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
isExecutor,
44
map,
55
value,
6-
type PushObservableExecutor,
76
type Executor,
87
type Scope,
98
type Subscribable,
@@ -24,12 +23,7 @@ import React, {
2423
useState,
2524
useSyncExternalStore,
2625
} from "react";
27-
import type {
28-
Subscriber,
29-
OperatorFactory,
30-
PushObservable,
31-
Operator,
32-
} from "../rx";
26+
import type { OperatorFactory, PushObservable, Operator } from "../rx";
3327

3428
const scopeContext = createContext<Scope | undefined>(undefined);
3529
const resetContext = createContext<() => void>(() => {});
@@ -147,7 +141,15 @@ type CacheEntry = [unknown, Cache];
147141
* @returns The resolved value of the executor.
148142
* @throws {Promise} If the executor is not yet resolved, a promise is thrown to suspend the component.
149143
*/
150-
export function useResolve<T>(executor: Executor<T>): T {
144+
export function useResolve<T>(
145+
executor: Executor<T>,
146+
params: unknown[] = [],
147+
factory: (
148+
scope: Scope,
149+
executor: Executor<T>,
150+
params: unknown[],
151+
) => Promise<T> = (scope, executor) => scope.resolve(executor),
152+
): T {
151153
const scope = useScope();
152154
const { cache } = useCache();
153155

@@ -164,7 +166,7 @@ export function useResolve<T>(executor: Executor<T>): T {
164166
const cacheEntry: CacheEntry = [
165167
executor,
166168
{
167-
promise: scope.resolve(executor).then(
169+
promise: factory(scope, executor, params).then(
168170
(resolved) => {
169171
cacheEntry[1].result = { data: resolved };
170172
},

src/rx.ts

+54-93
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,8 @@ export type Operator<T, R> = UnaryFunction<Subscribable<T>, Subscribable<R>>
2727
*/
2828
export type Subscribable<T> = {
2929
subscribe: (observer: Partial<Observer<T>>) => Unsubscribe
30-
readonly lastValue: T | undefined
31-
pipe<A>(op1: Operator<T, A>): Subscribable<A>;
32-
pipe<A, B>(op1: Operator<T, A>, op2: Operator<A, B>): Subscribable<B>;
33-
pipe<A, B, C>(op1: Operator<T, A>, op2: Operator<A, B>, op3: Operator<B, C>): Subscribable<C>;
34-
pipe<A, B, C, D>(op1: Operator<T, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>): Subscribable<D>;
35-
pipe<A, B, C, D, E>(op1: Operator<T, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>): Subscribable<E>;
36-
pipe<A, B, C, D, E, F>(op1: Operator<T, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>, op6: Operator<E, F>): Subscribable<F>;
37-
pipe<A, B, C, D, E, F, G>(op1: Operator<T, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>, op6: Operator<E, F>, op7: Operator<F, G>): Subscribable<G>;
38-
pipe<A, B, C, D, E, F, G, H>(op1: Operator<T, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>, op6: Operator<E, F>, op7: Operator<F, G>, op8: Operator<G, H>): Subscribable<H>;
39-
pipe<A, B, C, D, E, F, G, H, I>(op1: Operator<T, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>, op6: Operator<E, F>, op7: Operator<F, G>, op8: Operator<G, H>, op9: Operator<H, I>): Subscribable<I>;
40-
pipe<A, B, C, D, E, F, G, H, I, J>(op1: Operator<T, A>, op2: Operator<A, B>, op3: Operator<B, C>, op4: Operator<C, D>, op5: Operator<D, E>, op6: Operator<E, F>, op7: Operator<F, G>, op8: Operator<G, H>, op9: Operator<H, I>, op10: Operator<I, J>): Subscribable<J>;
30+
readonly get: T | undefined
31+
readonly getState: State<T>
4132
}
4233

4334
/**
@@ -70,14 +61,19 @@ export type Subscriber<T> = {
7061
complete: () => void
7162
}
7263

64+
type ControllerBase<T> = Subscriber<T> & {
65+
readonly get: () => T | undefined
66+
readonly getState: () => State<T>
67+
}
68+
7369
/** Utility type to force tuple Subscriber and controller */
7470
export type ControllableObservable<T, V> = readonly [Subscribable<T>, V]
7571

7672
/**
7773
* Represents a push-based observable with a subscriber.
7874
* @template T The type of the value emitted by the observable.
7975
*/
80-
export type PushObservable<T> = ControllableObservable<T, Subscriber<T>>
76+
export type PushObservable<T> = ControllableObservable<T, ControllerBase<T>>
8177

8278
type SubjectInit<T> = {
8379
kind: 'init'
@@ -87,7 +83,7 @@ type SubjectInit<T> = {
8783
type SubjectActive<T> = {
8884
kind: 'active'
8985
subscribers: Set<Partial<Observer<T>>>
90-
lastValue?: T
86+
lastValue: T
9187
}
9288

9389
type SubjectError = {
@@ -100,6 +96,9 @@ type SubjectComplete = {
10096
}
10197

10298
type Subject<T> = SubjectInit<T> | SubjectActive<T> | SubjectError | SubjectComplete
99+
type State<T> =
100+
| { kind: 'active', value: T }
101+
| { kind: 'inactive', value: undefined }
103102

104103
const noops: Unsubscribe = () => { }
105104

@@ -109,7 +108,7 @@ const noops: Unsubscribe = () => { }
109108
* @param {T} [initialValue] - The initial value to emit.
110109
* @returns {PushObservable<T>} The created push-based observable.
111110
*/
112-
export function pushObservable<T>(initialValue?: T): PushObservable<T> {
111+
export function createObservable<T>(initialValue?: T): PushObservable<T> {
113112
let subject: Subject<T> = initialValue === undefined ? {
114113
kind: 'init',
115114
subscribers: new Set()
@@ -125,12 +124,11 @@ export function pushObservable<T>(initialValue?: T): PushObservable<T> {
125124
if (subject.kind === 'init') {
126125
subject = {
127126
kind: 'active',
128-
subscribers: subject.subscribers
127+
subscribers: subject.subscribers,
128+
lastValue: value
129129
};
130130
}
131131

132-
(subject as SubjectActive<T>).lastValue = value
133-
134132
for (const sub of subject.subscribers) {
135133
sub.next?.(value);
136134
}
@@ -154,10 +152,20 @@ export function pushObservable<T>(initialValue?: T): PushObservable<T> {
154152
}
155153
}
156154

157-
const subscriber: Subscriber<T> = {
155+
const controller: ControllerBase<T> = {
158156
next: handleNext,
159157
error: handleError,
160-
complete: handleComplete
158+
complete: handleComplete,
159+
get: () => {
160+
if (subject.kind === 'active') {
161+
return subject.lastValue;
162+
}
163+
},
164+
getState: () => {
165+
return subject.kind === 'active'
166+
? { kind: 'active', value: subject.lastValue }
167+
: { kind: 'inactive', value: undefined }
168+
}
161169
};
162170

163171
const observable: Subscribable<T> = {
@@ -176,9 +184,7 @@ export function pushObservable<T>(initialValue?: T): PushObservable<T> {
176184
complete: obs.complete ?? noops
177185
});
178186

179-
if (subject.lastValue !== undefined) {
180-
obs.next?.(subject.lastValue)
181-
}
187+
obs.next?.(subject.lastValue)
182188

183189
break
184190
}
@@ -198,50 +204,17 @@ export function pushObservable<T>(initialValue?: T): PushObservable<T> {
198204
}
199205
};
200206
},
201-
pipe: ((...ops: Operator<unknown, unknown>[]): Subscribable<unknown> => {
202-
return pipe(observable, ...ops);
203-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
204-
}) as any,
205-
get lastValue() {
207+
get get() {
206208
return subject.kind === 'active' ? subject.lastValue : undefined;
209+
},
210+
get getState() {
211+
return subject.kind === 'active'
212+
? { kind: 'active', value: subject.lastValue } as const
213+
: { kind: 'inactive', value: undefined } as const
207214
}
208215
}
209216

210-
return [observable, subscriber];
211-
}
212-
213-
/**
214-
* Also known as cold observable, the Input will be called and cleaned as a new subscriber subscribes
215-
* @param observer
216-
* @returns
217-
*/
218-
/**
219-
* Creates a pull-based observable.
220-
* @template T The type of the value emitted by the observable.
221-
* @param {ObservableInput<T>} observer - The observer function to call for each subscription.
222-
* @returns {Subscribable<T>} The created pull-based observable.
223-
*/
224-
export function pullObservable<T>(observer: ObservableInput<T>): Subscribable<T> {
225-
const observable = {
226-
subscribe: (subscriber) => {
227-
const unsub = observer({
228-
next: subscriber.next?.bind(subscriber),
229-
error: subscriber.error?.bind(subscriber),
230-
complete: () => {
231-
subscriber.complete?.();
232-
unsub(); // auto cleanup on complete
233-
}
234-
});
235-
return unsub;
236-
},
237-
pipe: ((...ops: Operator<unknown, unknown>[]): Subscribable<unknown> => {
238-
return pipe(observable, ...ops);
239-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
240-
}) as any,
241-
lastValue: undefined
242-
} satisfies Subscribable<T>;
243-
244-
return observable;
217+
return [observable, controller];
245218
}
246219

247220
/**
@@ -289,7 +262,7 @@ export const operators = {
289262
*/
290263
map<T, R>(fn: (value: T) => R): Operator<T, R> {
291264
return (source) => {
292-
const [observable, subscriber] = pushObservable<R>();
265+
const [observable, subscriber] = createObservable<R>();
293266
const unsub = source.subscribe({
294267
next: (val) => {
295268
try {
@@ -321,7 +294,7 @@ export const operators = {
321294
*/
322295
filter<T>(predicate: (value: T) => boolean): Operator<T, T> {
323296
return (source) => {
324-
const [observable, subscriber] = pushObservable<T>();
297+
const [observable, subscriber] = createObservable<T>();
325298
const unsub = source.subscribe({
326299
next: (val) => {
327300
try {
@@ -348,23 +321,18 @@ export const operators = {
348321
/**
349322
* Performs side effects for each value emitted by the source observable.
350323
* @template T The type of the input value.
351-
* @param {object} handlers - The side effect handlers.
352-
* @param {function(T): void} [handlers.onNext] - The side effect function to apply to each value.
353-
* @param {function(unknown): void} [handlers.onError] - The side effect function to apply on error.
354-
* @param {function(): void} [handlers.onComplete] - The side effect function to apply on completion.
324+
* @param {function(T): void} onNext - The side effect function to apply to each value.
325+
* @param {function(unknown): void} [onError] - The side effect function to apply on error.
326+
* @param {function(): void} [onComplete] - The side effect function to apply on completion.
355327
* @returns {Operator<T, T>} The operator that performs the side effects.
356328
*/
357-
tap<T>({
358-
onNext,
359-
onError,
360-
onComplete,
361-
}: {
362-
onNext?: (value: T) => void;
363-
onError?: (error: unknown) => void;
364-
onComplete?: () => void;
365-
}): Operator<T, T> {
329+
tap<T>(
330+
onNext?: (value: T) => void,
331+
onError?: (error: unknown) => void,
332+
onComplete?: () => void
333+
): Operator<T, T> {
366334
return (source) => {
367-
const [observable, subscriber] = pushObservable<T>();
335+
const [observable, subscriber] = createObservable<T>();
368336
const unsub = source.subscribe({
369337
next: (val) => {
370338
try {
@@ -416,7 +384,7 @@ export const operators = {
416384
*/
417385
latest<T>(): Operator<T, T> {
418386
return (source) => {
419-
const [observable, subscriber] = pushObservable<T>();
387+
const [observable, subscriber] = createObservable<T>();
420388
let lastValue: T | undefined;
421389
let hasValue = false;
422390
const unsub = source.subscribe({
@@ -459,7 +427,7 @@ export const operators = {
459427
const clone = options?.clone ?? structuredClone;
460428

461429
return (source) => {
462-
const [observable, subscriber] = pushObservable<T>();
430+
const [observable, subscriber] = createObservable<T>();
463431
let last: T | undefined;
464432
let hasValue = false;
465433
const unsub = source.subscribe({
@@ -501,7 +469,7 @@ export const operators = {
501469
*/
502470
reduce<T, R>(accumulator: (acc: R, value: T) => R, seed: R): Operator<T, R> {
503471
return (source) => {
504-
const [observable, subscriber] = pushObservable<R>();
472+
const [observable, subscriber] = createObservable<R>();
505473
let accumulated = seed;
506474
const unsub = source.subscribe({
507475
next: (val) => {
@@ -540,7 +508,7 @@ export const operators = {
540508
...sources: R
541509
): Operator<T, [T, ...{ [K in keyof R]: R[K] extends Subscribable<infer U> ? U : R[K] extends ControllableObservable<infer U, unknown> ? U : never }]> {
542510
return (source) => {
543-
const [observable, subscriber] = pushObservable<[T, ...{ [K in keyof R]: R[K] extends Subscribable<infer U> ? U : R[K] extends ControllableObservable<infer U, unknown> ? U : never }]>();
511+
const [observable, subscriber] = createObservable<[T, ...{ [K in keyof R]: R[K] extends Subscribable<infer U> ? U : R[K] extends ControllableObservable<infer U, unknown> ? U : never }]>();
544512
const values = new Array(sources.length) as { [K in keyof R]: R[K] extends Subscribable<infer U> ? U : R[K] extends ControllableObservable<infer U, unknown> ? U : never }[];
545513

546514
const hasValue = Array(sources.length).fill(false);
@@ -596,14 +564,7 @@ export const observables = {
596564
* @param {T} [initialValue] - The initial value to emit.
597565
* @returns {PushObservable<T>} The created push-based observable.
598566
*/
599-
pushObservable,
600-
/**
601-
* Creates a pull-based observable.
602-
* @template T The type of the value emitted by the observable.
603-
* @param {ObservableInput<T>} observer - The observer function to call for each subscription.
604-
* @returns {Subscribable<T>} The created pull-based observable.
605-
*/
606-
pullObservable,
567+
create: createObservable,
607568
/**
608569
* Pipes multiple operators together.
609570
* @template S The type of the source value.
@@ -633,9 +594,9 @@ export const observables = {
633594
initialValue: T,
634595
fn: (subscriber: Subscriber<T>, get: () => T) => C
635596
): readonly [Subscribable<T>, C] => {
636-
const [observable, subscriber] = pushObservable<T>(initialValue);
597+
const [observable, subscriber] = createObservable<T>(initialValue);
637598
// biome-ignore lint/style/noNonNullAssertion: <explanation>
638-
const get = () => observable.lastValue!;
599+
const get = () => observable.get!;
639600

640601
const controller = fn(subscriber, get);
641602
return [observable, controller] as const;
@@ -654,7 +615,7 @@ export const observables = {
654615

655616
// only emit where every up streams have emitted
656617
const keys = Object.keys(sources) as (keyof T)[];
657-
const [observable, subscriber] = pushObservable<T>();
618+
const [observable, subscriber] = createObservable<T>();
658619
const values = {} as T;
659620

660621
// value can be Subscriable or PushObservable, distinguish them

0 commit comments

Comments
 (0)