@cascateer/core 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,192 @@
1
+ import { bind, camelCase, isFunction, isObject } from "lodash";
2
+ import React, { CSSProperties } from "react";
3
+ import {
4
+ combineLatest,
5
+ fromEvent,
6
+ map,
7
+ ObservableInputTuple,
8
+ Observer,
9
+ UnaryFunction,
10
+ } from "rxjs";
11
+ import { ObservableFragment, Primitive } from "./fragment";
12
+ import { asArray, asObservable, keys } from "./lib";
13
+ import { sequence } from "./operators";
14
+ import {
15
+ MaybeArray,
16
+ MaybeObservable,
17
+ MaybeObservableInputTuple,
18
+ } from "./types";
19
+
20
+ type DocumentEventListener<EventName extends keyof DocumentEventMap> =
21
+ | Partial<Observer<DocumentEventMap[EventName]>>
22
+ | UnaryFunction<DocumentEventMap[EventName], void>;
23
+
24
+ declare global {
25
+ namespace JSX {
26
+ type Element = MaybeObservable<Node | Primitive>;
27
+
28
+ export type CSSCustomPropertyDefinition = Omit<PropertyDefinition, "name">;
29
+
30
+ export type CSSCustomIntegerPropertyDefinition =
31
+ CSSCustomPropertyDefinition & { syntax: "<integer>" };
32
+
33
+ export interface CSSCustomPropertyDefinitions extends Record<
34
+ keyof {},
35
+ CSSCustomPropertyDefinition
36
+ > {}
37
+
38
+ type CSSCustomProperties = Record<
39
+ keyof CSSCustomPropertyDefinitions & `--${string}`,
40
+ string | number
41
+ >;
42
+
43
+ type Children = MaybeObservable<MaybeArray<Element>>;
44
+
45
+ interface IntrinsicAttributes {
46
+ children?: Children;
47
+ }
48
+
49
+ type IntrinsicElements = {
50
+ [TagName in keyof HTMLElementTagNameMap]: IntrinsicAttributes & {
51
+ [AttributeName in keyof Omit<
52
+ React.JSX.IntrinsicElements[TagName],
53
+ "children" | "dangerouslySetInnerHTML" | "ref"
54
+ >]: AttributeName extends `on${infer ReactEventName}`
55
+ ? Lowercase<ReactEventName> extends keyof DocumentEventMap
56
+ ? DocumentEventListener<Lowercase<ReactEventName>>
57
+ : never
58
+ : MaybeObservable<
59
+ AttributeName extends "className"
60
+ ? MaybeArray<string>
61
+ : AttributeName extends "style"
62
+ ? MaybeObservableInputTuple<
63
+ CSSProperties | CSSCustomProperties
64
+ >
65
+ : React.JSX.IntrinsicElements[TagName][AttributeName]
66
+ >;
67
+ };
68
+ };
69
+
70
+ type Props = Record<string, unknown>;
71
+
72
+ interface Component<P extends Props = Props> {
73
+ (props: IntrinsicAttributes & P): Element;
74
+ }
75
+ }
76
+ }
77
+
78
+ export const createElement = (
79
+ component: JSX.Component | string,
80
+ attributes: JSX.IntrinsicAttributes | null,
81
+ ...tagContent: JSX.Children[]
82
+ ): JSX.Element => {
83
+ const { children: rawChildren = tagContent, ...propsWithoutChildren } =
84
+ attributes ?? {},
85
+ children = asArray(rawChildren).map((children) =>
86
+ asObservable(children).pipe(
87
+ map((children) => createFragment({ children })),
88
+ ),
89
+ );
90
+
91
+ switch (typeof component) {
92
+ case "function":
93
+ return component({ ...propsWithoutChildren, children });
94
+ case "string": {
95
+ const element = document.createElement(component);
96
+
97
+ for (const [name, value] of Object.entries(propsWithoutChildren)) {
98
+ const eventName = name.match(/^on(.*)$/)?.[1]?.toLowerCase();
99
+
100
+ if (eventName != null) {
101
+ fromEvent(element, eventName).subscribe(
102
+ ...(isFunction(value)
103
+ ? [value]
104
+ : isObject(value)
105
+ ? [
106
+ [...keys(value)].reduce<Partial<Observer<unknown>>>(
107
+ (observer, key) => {
108
+ const observerKey = [
109
+ "next" as const,
110
+ "error" as const,
111
+ "complete" as const,
112
+ ].find((observerKey) => observerKey === key);
113
+
114
+ const observerValue = value[key];
115
+
116
+ if (observerKey != null && isFunction(observerValue)) {
117
+ observer[observerKey] = bind(observerValue, value);
118
+ }
119
+
120
+ return observer;
121
+ },
122
+ {},
123
+ ),
124
+ ]
125
+ : []),
126
+ );
127
+ } else {
128
+ asObservable(value).subscribe({
129
+ next: (propertyValue) => {
130
+ if (name === "className") {
131
+ element.setAttribute(
132
+ "class",
133
+ String(propertyValue).replaceAll(",", " "),
134
+ );
135
+ } else if (name === "style") {
136
+ if (isObject(propertyValue)) {
137
+ combineLatest(
138
+ Object.entries(propertyValue).reduce<
139
+ ObservableInputTuple<Record<string, unknown>>
140
+ >(
141
+ (style, [key, value]) => (
142
+ (style[key] = asObservable(value)),
143
+ style
144
+ ),
145
+ {},
146
+ ),
147
+ )
148
+ .pipe(
149
+ sequence(([style], [previousStyle]) => {
150
+ if (previousStyle != null) {
151
+ for (const name in previousStyle) {
152
+ element.style.removeProperty(name);
153
+ }
154
+ }
155
+
156
+ for (const [name, value] of Object.entries(style)) {
157
+ element.style.setProperty(name, String(value));
158
+ }
159
+
160
+ return style;
161
+ }),
162
+ )
163
+ .subscribe();
164
+ }
165
+ } else if (name.startsWith("data-")) {
166
+ const camelCaseName = camelCase(name.replace(/^data/, ""));
167
+
168
+ if (propertyValue == null) {
169
+ delete element.dataset[camelCaseName];
170
+ } else {
171
+ element.dataset[camelCase(name.replace(/^data/, ""))] =
172
+ String(propertyValue);
173
+ }
174
+ } else if (name === "disabled" && !propertyValue) {
175
+ element.removeAttribute(name);
176
+ } else {
177
+ element.setAttribute(name, String(propertyValue));
178
+ }
179
+ },
180
+ });
181
+ }
182
+ }
183
+
184
+ element.append(createFragment({ children }));
185
+
186
+ return element;
187
+ }
188
+ }
189
+ };
190
+
191
+ export const createFragment = ({ children }: JSX.IntrinsicAttributes) =>
192
+ new ObservableFragment(children);
@@ -0,0 +1,23 @@
1
+ export class Enumerable<T> extends Array<
2
+ T extends readonly (infer Index)[] ? Index : never
3
+ > {
4
+ constructor(value: T) {
5
+ super();
6
+
7
+ this.push(...(Array.isArray(value) ? value : []));
8
+ }
9
+ }
10
+
11
+ export type EnumerableItem<
12
+ T,
13
+ Index extends number = number,
14
+ > = Enumerable<T>[Index];
15
+
16
+ export interface Enumerator<T> {
17
+ <Index extends number>(
18
+ item: EnumerableItem<T, Index>,
19
+ index: Index,
20
+ ): PropertyKey;
21
+ }
22
+
23
+ export const asEnumerable = <T>(value: T) => new Enumerable<T>(value);
@@ -0,0 +1,54 @@
1
+ import { Dictionary } from "lodash";
2
+ import { UnaryFunction } from "rxjs";
3
+ import { Future } from "../observable";
4
+ import { keys } from "./keys";
5
+
6
+ export type Extend<T, U> = Omit<T, keyof U> & U;
7
+
8
+ export class ExtendableDictionary<T, U extends Dictionary<T>> {
9
+ constructor(
10
+ public currentValue: U,
11
+ private value = new Future<Dictionary<T>>(),
12
+ ) {}
13
+
14
+ complete(): U {
15
+ return this.value.completeWith(this.currentValue);
16
+ }
17
+
18
+ extend<V extends Dictionary<T>>(
19
+ value: (
20
+ currentValue: U,
21
+ ) => ({
22
+ property,
23
+ }: {
24
+ property: (constructor: UnaryFunction<Promise<string>, T>) => T;
25
+ }) => V,
26
+ ) {
27
+ return new ExtendableDictionary<T, Extend<U, V>>(
28
+ {
29
+ ...this.currentValue,
30
+ ...value(this.currentValue)({
31
+ property: (constructor) => {
32
+ const property = constructor(
33
+ this.value.once(
34
+ (value) =>
35
+ new Promise<string>((resolve, reject) => {
36
+ for (const key of keys(value)) {
37
+ if (value[key] === property) {
38
+ return resolve(key);
39
+ }
40
+ }
41
+
42
+ reject();
43
+ }),
44
+ ),
45
+ );
46
+
47
+ return property;
48
+ },
49
+ }),
50
+ },
51
+ this.value,
52
+ );
53
+ }
54
+ }
@@ -0,0 +1,26 @@
1
+ import { Comparator, uniqWith } from "lodash";
2
+
3
+ export const chunkWith = <T>(
4
+ actions: T[],
5
+ comparator?: Comparator<T>,
6
+ ): T[][] => [
7
+ ...{
8
+ *[Symbol.iterator]() {
9
+ const prevActions = new Array<T>();
10
+
11
+ if (actions.length == 0) {
12
+ return;
13
+ }
14
+
15
+ for (const action of actions) {
16
+ if (uniqWith([...prevActions, action], comparator).length === 1) {
17
+ prevActions.push(action);
18
+ } else {
19
+ yield prevActions.splice(0, prevActions.length, action);
20
+ }
21
+ }
22
+
23
+ yield prevActions;
24
+ },
25
+ },
26
+ ];
@@ -0,0 +1,29 @@
1
+ import { Observable, of } from "rxjs";
2
+ import { isObservable } from "rxjs/internal/util/isObservable";
3
+ import { MaybeArray, MaybeObservable } from "../types";
4
+
5
+ export { chunkWith } from "./chunkWith";
6
+ export {
7
+ asEnumerable,
8
+ type Enumerable,
9
+ type EnumerableItem,
10
+ type Enumerator,
11
+ } from "./Enumerable";
12
+ export { ExtendableDictionary } from "./ExtendableDictionary";
13
+ export { keys } from "./keys";
14
+ export { nthArg } from "./nthArg";
15
+ export { property } from "./property";
16
+
17
+ export const asArray = <T>(array: MaybeArray<T>): Array<T> =>
18
+ Array.isArray(array) ? array : [array];
19
+
20
+ export const asObservable = <T>(value: MaybeObservable<T>): Observable<T> =>
21
+ isObservable(value) ? value : of(value);
22
+
23
+ export const nonNullable = <T>(value: T): NonNullable<T> => {
24
+ if (value == null) {
25
+ throw new Error(`${value} is nil`);
26
+ }
27
+
28
+ return value;
29
+ };
@@ -0,0 +1,7 @@
1
+ export const keys = <T>(value: T) => [
2
+ ...{
3
+ *[Symbol.iterator]() {
4
+ for (const key in value) yield key;
5
+ },
6
+ },
7
+ ];
@@ -0,0 +1,6 @@
1
+ export function nthArg<T>(n: 0): (arg0: T) => T;
2
+ export function nthArg<T>(n: 1): (arg0: any, arg1: T) => T;
3
+ export function nthArg<T>(n: 2): (arg0: any, arg1: any, arg2: T) => T;
4
+ export function nthArg<T>(n: number): (...args: any[]) => T {
5
+ return (...args) => args[n];
6
+ }
@@ -0,0 +1,4 @@
1
+ export const property =
2
+ <T, K extends keyof T = keyof T>(key: K) =>
3
+ (value: T): T[K] =>
4
+ value[key];
@@ -0,0 +1,79 @@
1
+ import { memoize, thru } from "lodash";
2
+ import { mergeAll, Observable, startWith, tap } from "rxjs";
3
+ import { v4 } from "uuid";
4
+ import { nonNullable, property } from "./lib";
5
+ import { Future } from "./observable";
6
+ import {
7
+ exchangeWith,
8
+ flatMap,
9
+ MulticastActionMessage,
10
+ MulticastClientMessage,
11
+ MulticastConnectMessageData,
12
+ sequence,
13
+ transform,
14
+ } from "./operators";
15
+
16
+ declare var self: ServiceWorkerGlobalScope;
17
+
18
+ declare global {
19
+ interface ServiceWorkerGlobalScopeEventMap {
20
+ connect: MessageEvent;
21
+ }
22
+ }
23
+
24
+ const sliceActionsSequence = memoize(
25
+ <Seed>({ seed }: MulticastConnectMessageData<Seed>) =>
26
+ transform<MulticastActionMessage<any>>((actions) =>
27
+ actions.pipe(
28
+ startWith({
29
+ id: v4(),
30
+ type: "seedAction" as const,
31
+ data: {
32
+ seed,
33
+ },
34
+ }),
35
+ ),
36
+ ),
37
+ property("key"),
38
+ );
39
+
40
+ self.addEventListener("connect", ({ ports }) => {
41
+ for (const port of ports) {
42
+ thru(
43
+ new Future<Observable<MulticastActionMessage<any>>>(),
44
+ (sliceActions) =>
45
+ transform<MulticastActionMessage<any>, MulticastClientMessage>(
46
+ (actions) =>
47
+ sliceActions.pipe(
48
+ mergeAll(),
49
+ flatMap(({ origin, ...message }) =>
50
+ !message.sameOrigin || origin === port ? message : [],
51
+ ),
52
+ sequence(([action, previousAction]) =>
53
+ action.type === "seedAction"
54
+ ? action
55
+ : {
56
+ ...action,
57
+ previousId: nonNullable(previousAction).id,
58
+ },
59
+ ),
60
+ exchangeWith<MulticastClientMessage, MulticastActionMessage<any>>(
61
+ port,
62
+ ),
63
+ flatMap((event) => {
64
+ if (event.type === "connect") {
65
+ actions.subscribe(
66
+ sliceActions.completeWith(sliceActionsSequence(event.data)),
67
+ );
68
+
69
+ return [];
70
+ }
71
+
72
+ return { ...event, origin: port };
73
+ }),
74
+ tap(actions),
75
+ ),
76
+ ),
77
+ ).subscribe();
78
+ }
79
+ });
@@ -0,0 +1,17 @@
1
+ import { once } from "lodash";
2
+ import { AsyncSubject, lastValueFrom, UnaryFunction } from "rxjs";
3
+
4
+ export class Future<T> extends AsyncSubject<T> {
5
+ completeWith = once(<U extends T>(value: U): U => {
6
+ this.next(value);
7
+ this.complete();
8
+
9
+ return value;
10
+ });
11
+
12
+ once<U>(predicate: UnaryFunction<T, U | PromiseLike<U>>): Promise<U> {
13
+ return lastValueFrom(this).then(predicate);
14
+ }
15
+
16
+ then = this.once;
17
+ }
@@ -0,0 +1,181 @@
1
+ import { clone, isEqual, memoize } from "lodash";
2
+ import { distinctUntilChanged, map, Observable, of, UnaryFunction } from "rxjs";
3
+ import { TapObservable } from ".";
4
+ import {
5
+ asEnumerable,
6
+ EnumerableItem,
7
+ Enumerator,
8
+ nonNullable,
9
+ nthArg,
10
+ property,
11
+ } from "../lib";
12
+ import { Transform } from "../types";
13
+
14
+ class SignalEnumerator<T> {
15
+ constructor(private predicate: Enumerator<T> = nthArg(1)) {}
16
+
17
+ findIndex = (key: PropertyKey) => (value: T) =>
18
+ asEnumerable(value).map(this.predicate).indexOf(key);
19
+
20
+ enumerate = (value: T) => asEnumerable(value).map(this.predicate);
21
+ }
22
+
23
+ class SignalReflector<T> {
24
+ constructor(
25
+ private predicate: (
26
+ transform: Transform<T>,
27
+ callback: UnaryFunction<Transform<any>, void>,
28
+ ) => void = (transform, callback) => callback(transform),
29
+ ) {}
30
+
31
+ reflect = (transform: Transform<T>) =>
32
+ new Promise<Transform<unknown>>((callback) =>
33
+ this.predicate(transform, callback),
34
+ );
35
+
36
+ lift = <U>(
37
+ lift: UnaryFunction<Transform<U>, Transform<T>>,
38
+ ): SignalReflector<U> =>
39
+ new SignalReflector((transform, callback) =>
40
+ this.predicate(lift(transform), callback),
41
+ );
42
+ }
43
+
44
+ export class Signal<T> extends Observable<T> implements TapObservable<T> {
45
+ loading = of(false);
46
+
47
+ capture(): Signal<T> {
48
+ return this;
49
+ }
50
+
51
+ get value(): Observable<T> {
52
+ return this;
53
+ }
54
+
55
+ enumerator: SignalEnumerator<T>;
56
+ reflector: SignalReflector<T>;
57
+
58
+ constructor({
59
+ value,
60
+ enumerator = new SignalEnumerator(),
61
+ reflector = new SignalReflector(),
62
+ }: {
63
+ value: Observable<T>;
64
+ enumerator?: SignalEnumerator<T>;
65
+ reflector?: SignalReflector<T>;
66
+ }) {
67
+ super((subscriber) => value.subscribe(subscriber));
68
+
69
+ this.enumerator = enumerator;
70
+ this.reflector = reflector;
71
+ }
72
+
73
+ private project<U>(
74
+ project: UnaryFunction<T, U>,
75
+ lift: UnaryFunction<Transform<U>, Transform<T>>,
76
+ enumerate?: Enumerator<U>,
77
+ ): Signal<U> {
78
+ return new Signal({
79
+ value: this.pipe(map(project), distinctUntilChanged()),
80
+ enumerator: new SignalEnumerator(enumerate),
81
+ reflector: this.reflector.lift(lift),
82
+ });
83
+ }
84
+
85
+ protected property<K extends keyof T>(
86
+ key: K,
87
+ enumerate?: Enumerator<T[K]>,
88
+ ): Signal<T[K]> {
89
+ const findProperty: UnaryFunction<T, T[K]> = property(key);
90
+
91
+ return this.project(
92
+ findProperty,
93
+ (transform) => (value) => {
94
+ value = clone(value);
95
+
96
+ value[key] = transform(findProperty(value));
97
+
98
+ return value;
99
+ },
100
+ enumerate,
101
+ );
102
+ }
103
+
104
+ protected item(
105
+ key: PropertyKey,
106
+ enumerate?: Enumerator<EnumerableItem<T>>,
107
+ ): Signal<EnumerableItem<T>> {
108
+ const findIndex = this.enumerator.findIndex(key);
109
+ const findItem: UnaryFunction<T, EnumerableItem<T>> = (value) =>
110
+ nonNullable(asEnumerable(value)[findIndex(value)]);
111
+
112
+ return this.project(
113
+ findItem,
114
+ (transform) => (value) => {
115
+ if (Array.isArray((value = clone(value)))) {
116
+ value[findIndex(value)] = transform(findItem(value));
117
+ }
118
+
119
+ return value;
120
+ },
121
+ enumerate,
122
+ );
123
+ }
124
+
125
+ protected collection<K extends keyof EnumerableItem<T>>(
126
+ key: K,
127
+ ): Signal<EnumerableItem<T>[K][]> {
128
+ return this.project(
129
+ (value) => asEnumerable(value).map(property(key)),
130
+ (transform) => (value) => {
131
+ if (Array.isArray((value = clone(value)))) {
132
+ value.reduce(
133
+ (property, item, index) => (
134
+ (item[key] = property[index]),
135
+ property
136
+ ),
137
+ transform(value.map(property(key))),
138
+ );
139
+ }
140
+
141
+ return value;
142
+ },
143
+ );
144
+ }
145
+
146
+ list<U>(
147
+ iteratee: UnaryFunction<Signal<EnumerableItem<T>>, U>,
148
+ ): Observable<U[]> {
149
+ const memoizedIteratee = memoize<UnaryFunction<PropertyKey, U>>((key) =>
150
+ iteratee(this.item(key)),
151
+ );
152
+
153
+ return this.pipe(
154
+ map(this.enumerator.enumerate),
155
+ distinctUntilChanged((previous, current) => isEqual(previous, current)),
156
+ map((keys) => keys.map(memoizedIteratee)),
157
+ );
158
+ }
159
+ }
160
+
161
+ export class ComputedSignal<T> extends Signal<T> {
162
+ property<K extends keyof T>(
163
+ key: K,
164
+ enumerate?: Enumerator<T[K]>,
165
+ ): ComputedSignal<T[K]> {
166
+ return new ComputedSignal(super.property(key, enumerate));
167
+ }
168
+
169
+ item(
170
+ key: PropertyKey,
171
+ enumerate?: Enumerator<EnumerableItem<T>>,
172
+ ): ComputedSignal<EnumerableItem<T>> {
173
+ return new ComputedSignal(super.item(key, enumerate));
174
+ }
175
+
176
+ collection<K extends keyof EnumerableItem<T>>(
177
+ key: K,
178
+ ): ComputedSignal<EnumerableItem<T>[K][]> {
179
+ return new ComputedSignal(super.collection(key));
180
+ }
181
+ }
@@ -0,0 +1,30 @@
1
+ import { once } from "lodash";
2
+ import {
3
+ BehaviorSubject,
4
+ combineLatest,
5
+ map,
6
+ MonoTypeOperatorFunction,
7
+ Observable,
8
+ share,
9
+ } from "rxjs";
10
+ import { tapSubscription } from "../operators";
11
+
12
+ export interface TapObservable<T> extends Observable<T> {
13
+ loading: Observable<boolean>;
14
+ }
15
+
16
+ export class TapObservable<T> extends Observable<T> {
17
+ constructor(source: Observable<T>, loading: Observable<boolean>) {
18
+ const subscribed = new BehaviorSubject(false);
19
+
20
+ const intercept: MonoTypeOperatorFunction<T> = once((source) =>
21
+ source.pipe(tapSubscription(subscribed), share()),
22
+ );
23
+
24
+ super((subscriber) => source.pipe(intercept).subscribe(subscriber));
25
+
26
+ this.loading = combineLatest([loading, subscribed]).pipe(
27
+ map((values) => values.every(Boolean)),
28
+ );
29
+ }
30
+ }
@@ -0,0 +1,39 @@
1
+ import { once } from "lodash";
2
+ import {
3
+ Observable,
4
+ Observer,
5
+ ReplaySubject,
6
+ Subject,
7
+ UnaryFunction,
8
+ Unsubscribable,
9
+ } from "rxjs";
10
+
11
+ export class TransformSubject<T, U = T>
12
+ extends Observable<U>
13
+ implements Observer<T>, Unsubscribable
14
+ {
15
+ next(value: T): void {
16
+ this.target.next(value);
17
+ }
18
+
19
+ error(err: any): void {
20
+ this.target.error(err);
21
+ }
22
+
23
+ complete(): void {
24
+ this.target.complete();
25
+ }
26
+
27
+ unsubscribe(): void {
28
+ this.target.unsubscribe();
29
+ }
30
+
31
+ constructor(
32
+ project: UnaryFunction<Subject<T>, Observable<U>>,
33
+ private target: Subject<T> = new ReplaySubject(),
34
+ ) {
35
+ project = once(project);
36
+
37
+ super((subscriber) => project(target).subscribe(subscriber));
38
+ }
39
+ }