@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.
package/src/store.ts ADDED
@@ -0,0 +1,257 @@
1
+ import { constant, Dictionary, mapValues, memoize, tap, thru } from "lodash";
2
+ import {
3
+ identity,
4
+ merge,
5
+ mergeMap,
6
+ NextObserver,
7
+ Observable,
8
+ ReplaySubject,
9
+ shareReplay,
10
+ UnaryFunction,
11
+ } from "rxjs";
12
+ import { ExtendableDictionary } from "./lib";
13
+ import { ComputedSignal, Signal } from "./observable";
14
+ import {
15
+ flatMap,
16
+ MulticastAction,
17
+ MulticastSubject,
18
+ sequence,
19
+ } from "./operators";
20
+ import { Action, Transform } from "./types";
21
+
22
+ export type StoreEffect<Result> = () => Signal<Result>;
23
+
24
+ export type StoreEffects<Signals extends Dictionary<ComputedSignal<any>>> = {
25
+ [K in keyof Signals]: ReturnType<
26
+ <
27
+ Result extends Signals[K] extends ComputedSignal<infer Result>
28
+ ? Result
29
+ : never,
30
+ >() => StoreEffect<Result>
31
+ >;
32
+ };
33
+
34
+ export const asStoreEffects = <Signals extends Dictionary<ComputedSignal<any>>>(
35
+ signals: Signals,
36
+ observer?: NextObserver<Signal<any>>,
37
+ ): StoreEffects<Signals> =>
38
+ mapValues(signals, (signal) =>
39
+ memoize(() => tap(signal.capture(), (value) => observer?.next(value))),
40
+ );
41
+
42
+ export class StoreAdapter<
43
+ Signals extends Dictionary<ComputedSignal<any>>,
44
+ Actions extends Dictionary<Action<any, any>>,
45
+ > {
46
+ constructor(
47
+ public signals: Signals,
48
+ public actions: Actions,
49
+ ) {}
50
+ }
51
+
52
+ export class ExtendableStoreAdapter<
53
+ Signals extends Dictionary<ComputedSignal<any>>,
54
+ Actions extends Dictionary<Action<any, any>>,
55
+ > {
56
+ complete(): StoreAdapter<Signals, Actions> {
57
+ return new StoreAdapter(
58
+ this.extendableSignals.complete(),
59
+ this.extendableActions.complete(),
60
+ );
61
+ }
62
+
63
+ constructor(
64
+ public context: {
65
+ transform: UnaryFunction<
66
+ Promise<string>,
67
+ {
68
+ share: UnaryFunction<
69
+ {
70
+ args: unknown;
71
+ callback: UnaryFunction<unknown, void>;
72
+ sameOrigin?: boolean;
73
+ },
74
+ void
75
+ >;
76
+ reflect: UnaryFunction<(args: any) => Promise<Transform<any>>, void>;
77
+ }
78
+ >;
79
+ },
80
+ private extendableSignals: ExtendableDictionary<
81
+ ComputedSignal<any>,
82
+ Signals
83
+ >,
84
+ private extendableActions: ExtendableDictionary<Action<any, any>, Actions>,
85
+ ) {}
86
+
87
+ provideSignals<MoreSignals extends Dictionary<ComputedSignal<any>>>(
88
+ signals: UnaryFunction<
89
+ {
90
+ signal: <T>(
91
+ constructor: UnaryFunction<Signals, ComputedSignal<T>>,
92
+ ) => ComputedSignal<T>;
93
+ },
94
+ MoreSignals
95
+ >,
96
+ ) {
97
+ return new ExtendableStoreAdapter(
98
+ this.context,
99
+ this.extendableSignals.extend(
100
+ (currentSignals) => () =>
101
+ signals({
102
+ signal: (constructor) => constructor(currentSignals),
103
+ }),
104
+ ),
105
+ this.extendableActions,
106
+ );
107
+ }
108
+
109
+ provideActions<MoreActions extends Dictionary<Action<any, any>>>(
110
+ actions: UnaryFunction<
111
+ {
112
+ action: <Args>(
113
+ constructor: UnaryFunction<
114
+ {
115
+ [K in keyof Signals]: {
116
+ update: <
117
+ T extends Signals[K] extends ComputedSignal<infer T>
118
+ ? T
119
+ : never,
120
+ >(
121
+ predicate: UnaryFunction<Args, Transform<T>>,
122
+ config?: { sameOrigin?: boolean },
123
+ ) => Action<Args, any>;
124
+ };
125
+ },
126
+ Action<Args, any>
127
+ >,
128
+ ) => Action<Args, any>;
129
+ },
130
+ MoreActions
131
+ >,
132
+ ) {
133
+ return new ExtendableStoreAdapter(
134
+ this.context,
135
+ this.extendableSignals,
136
+ this.extendableActions.extend(
137
+ () =>
138
+ ({ property }) =>
139
+ actions({
140
+ action: (constructor) =>
141
+ property((key) =>
142
+ constructor(
143
+ mapValues(
144
+ this.extendableSignals.currentValue,
145
+ (signal) => ({
146
+ update: (predicate, config = {}) =>
147
+ thru(this.context.transform(key), (transform) => {
148
+ transform.reflect((args) =>
149
+ signal.reflector.reflect(predicate(args)),
150
+ );
151
+
152
+ return (args) =>
153
+ new Promise<unknown>((callback) =>
154
+ transform.share({ args, callback, ...config }),
155
+ );
156
+ }),
157
+ }),
158
+ ),
159
+ ),
160
+ ),
161
+ }),
162
+ ),
163
+ );
164
+ }
165
+ }
166
+
167
+ export class StoreProvider<Data> extends ExtendableStoreAdapter<
168
+ { data: ComputedSignal<Data> },
169
+ {}
170
+ > {
171
+ constructor({ actions }: { actions: MulticastSubject }) {
172
+ const transformActions = new ReplaySubject<
173
+ MulticastAction<Data, "transformAction">
174
+ >();
175
+ const seedActions: Observable<MulticastAction<Data, "seedAction">> =
176
+ actions.pipe(
177
+ flatMap((event) =>
178
+ event.type === "seedAction"
179
+ ? {
180
+ ...event,
181
+ predicate: constant(event.data.seed),
182
+ }
183
+ : [],
184
+ ),
185
+ );
186
+
187
+ const callbacks = new Map<string, UnaryFunction<unknown, void>>();
188
+
189
+ super(
190
+ {
191
+ transform: (key) => ({
192
+ share: ({ args, callback, sameOrigin }) =>
193
+ actions.next(
194
+ async ({ id }) => (
195
+ callbacks.set(id, callback),
196
+ {
197
+ id,
198
+ type: "transformAction",
199
+ data: {
200
+ key: await key,
201
+ args,
202
+ },
203
+ sameOrigin,
204
+ }
205
+ ),
206
+ ),
207
+ reflect: (transform) =>
208
+ actions
209
+ .pipe(
210
+ mergeMap(async (event) => {
211
+ if (
212
+ event.type === "transformAction" &&
213
+ event.data.key === (await key)
214
+ ) {
215
+ return transform(event.data.args).then((predicate) => ({
216
+ ...event,
217
+ predicate,
218
+ callback: callbacks.get(event.id),
219
+ }));
220
+ }
221
+
222
+ return [];
223
+ }),
224
+ flatMap(identity),
225
+ )
226
+ .subscribe(transformActions),
227
+ }),
228
+ },
229
+ new ExtendableDictionary({
230
+ data: new ComputedSignal({
231
+ value: merge(seedActions, transformActions).pipe(
232
+ sequence<MulticastAction<Data>, Data>(
233
+ ([action, previousAction], [previousState]) => {
234
+ if (action.type === "seedAction") {
235
+ return action.predicate();
236
+ }
237
+
238
+ if (
239
+ action.previousId !== previousAction?.id ||
240
+ previousState == null
241
+ ) {
242
+ throw new Error();
243
+ }
244
+
245
+ return tap(action.predicate(previousState), (state) =>
246
+ action.callback?.call(null, state),
247
+ );
248
+ },
249
+ ),
250
+ shareReplay(1),
251
+ ),
252
+ }),
253
+ }),
254
+ new ExtendableDictionary({}),
255
+ );
256
+ }
257
+ }
@@ -0,0 +1,208 @@
1
+ import { Dictionary, mapValues, memoize, tap, thru } from "lodash";
2
+ import {
3
+ combineLatest,
4
+ distinct,
5
+ map,
6
+ NextObserver,
7
+ switchMap,
8
+ UnaryFunction,
9
+ } from "rxjs";
10
+ import { ApiAdapter, ApiEffect } from "./api";
11
+ import { ExtendableDictionary } from "./lib";
12
+ import { ComputedSignal, TapObservable } from "./observable";
13
+ import { concat, transform } from "./operators";
14
+ import { asStoreEffects, StoreAdapter, StoreEffects } from "./store";
15
+ import { Action, Effect, TapEffect } from "./types";
16
+
17
+ export interface TerminalEffect<Args, Result> extends TapEffect<Args, Result> {}
18
+
19
+ type TerminalEffects<Effects extends Dictionary<TapEffect<any, any>>> = {
20
+ [K in keyof Effects]: ReturnType<
21
+ <
22
+ Args extends Effects[K] extends TapEffect<infer Args, infer _>
23
+ ? Args
24
+ : never,
25
+ Result extends Effects[K] extends TapEffect<infer _, infer Result>
26
+ ? Result
27
+ : never,
28
+ >() => TerminalEffect<Args, Result>
29
+ >;
30
+ };
31
+
32
+ const asTerminalEffects = <Effects extends Dictionary<TapEffect<any, any>>>(
33
+ effects: Effects,
34
+ observer?: NextObserver<TapObservable<any>>,
35
+ ): TerminalEffects<Effects> =>
36
+ mapValues(effects, (effect) =>
37
+ memoize((args) => tap(effect(args), (value) => observer?.next(value))),
38
+ );
39
+
40
+ export class TerminalAdapter<
41
+ Effects extends Dictionary<TerminalEffect<any, any>>,
42
+ Actions extends Dictionary<Action<any, any>>,
43
+ > {
44
+ constructor(
45
+ public effects: Effects,
46
+ public actions: Actions,
47
+ ) {}
48
+ }
49
+
50
+ export class ExtendableTerminalAdapter<
51
+ StoreSignals extends Dictionary<ComputedSignal<any>>,
52
+ StoreActions extends Dictionary<Action<any, any>>,
53
+ ApiEffects extends Dictionary<ApiEffect<any, any>>,
54
+ ApiActions extends Dictionary<Action<any, any>>,
55
+ Effects extends Dictionary<TerminalEffect<any, any>>,
56
+ Actions extends Dictionary<Action<any, any>>,
57
+ > {
58
+ complete(): TerminalAdapter<Effects, Actions> {
59
+ return new TerminalAdapter(
60
+ this.extendableEffects.complete(),
61
+ this.extendableActions.complete(),
62
+ );
63
+ }
64
+
65
+ constructor(
66
+ private context: {
67
+ store: StoreAdapter<StoreSignals, StoreActions>;
68
+ api: ApiAdapter<ApiEffects, ApiActions>;
69
+ },
70
+ private extendableEffects: ExtendableDictionary<
71
+ TerminalEffect<any, any>,
72
+ Effects
73
+ >,
74
+ private extendableActions: ExtendableDictionary<Action<any, any>, Actions>,
75
+ ) {}
76
+
77
+ provideEffects<MoreEffects extends Dictionary<TerminalEffect<any, any>>>(
78
+ effects: UnaryFunction<
79
+ {
80
+ effect: <Args, Result>(
81
+ constructor: UnaryFunction<
82
+ {
83
+ store: {
84
+ effects: StoreEffects<StoreSignals>;
85
+ };
86
+ api: {
87
+ effects: TerminalEffects<ApiEffects>;
88
+ };
89
+ terminal: {
90
+ effects: TerminalEffects<Effects>;
91
+ };
92
+ },
93
+ Effect<Args, Result>
94
+ >,
95
+ ) => TerminalEffect<Args, Result>;
96
+ },
97
+ MoreEffects
98
+ >,
99
+ ) {
100
+ return new ExtendableTerminalAdapter(
101
+ this.context,
102
+ this.extendableEffects.extend(
103
+ (currentEffects) => () =>
104
+ effects({
105
+ effect: (constructor) => {
106
+ const deps = transform<TapObservable<any>, boolean>((deps) =>
107
+ deps.pipe(
108
+ distinct(),
109
+ concat(),
110
+ switchMap((dep) =>
111
+ combineLatest(dep.map((dep) => dep.loading)),
112
+ ),
113
+ map((values) => values.some(Boolean)),
114
+ ),
115
+ );
116
+
117
+ return thru(
118
+ constructor({
119
+ store: {
120
+ effects: asStoreEffects(this.context.store.signals, deps),
121
+ },
122
+ api: {
123
+ effects: asTerminalEffects(this.context.api.effects, deps),
124
+ },
125
+ terminal: {
126
+ effects: asTerminalEffects(currentEffects, deps),
127
+ },
128
+ }),
129
+ (effect) => (args) => new TapObservable(effect(args), deps),
130
+ );
131
+ },
132
+ }),
133
+ ),
134
+ this.extendableActions,
135
+ );
136
+ }
137
+
138
+ provideActions<MoreActions extends Dictionary<Action<any, any>>>(
139
+ actions: UnaryFunction<
140
+ {
141
+ action: <Args, Result>(
142
+ constructor: UnaryFunction<
143
+ {
144
+ store: {
145
+ effects: StoreEffects<StoreSignals>;
146
+ actions: StoreActions;
147
+ };
148
+ api: {
149
+ actions: ApiActions;
150
+ };
151
+ terminal: {
152
+ effects: Effects;
153
+ actions: Actions;
154
+ };
155
+ },
156
+ Action<Args, Result>
157
+ >,
158
+ ) => Action<Args, Result>;
159
+ },
160
+ MoreActions
161
+ >,
162
+ ) {
163
+ return new ExtendableTerminalAdapter(
164
+ this.context,
165
+ this.extendableEffects,
166
+ this.extendableActions.extend(
167
+ (currentActions) => () =>
168
+ actions({
169
+ action: (constructor) =>
170
+ constructor({
171
+ store: {
172
+ effects: asStoreEffects(this.context.store.signals),
173
+ actions: this.context.store.actions,
174
+ },
175
+ api: {
176
+ actions: this.context.api.actions,
177
+ },
178
+ terminal: {
179
+ effects: this.extendableEffects.currentValue,
180
+ actions: currentActions,
181
+ },
182
+ }),
183
+ }),
184
+ ),
185
+ );
186
+ }
187
+ }
188
+
189
+ export class TerminalProvider<
190
+ StoreSignals extends Dictionary<ComputedSignal<any>>,
191
+ StoreActions extends Dictionary<Action<any, any>>,
192
+ ApiEffects extends Dictionary<ApiEffect<any, any>>,
193
+ ApiActions extends Dictionary<Action<any, any>>,
194
+ > extends ExtendableTerminalAdapter<
195
+ StoreSignals,
196
+ StoreActions,
197
+ ApiEffects,
198
+ ApiActions,
199
+ {},
200
+ {}
201
+ > {
202
+ constructor(context: {
203
+ api: ApiAdapter<ApiEffects, ApiActions>;
204
+ store: StoreAdapter<StoreSignals, StoreActions>;
205
+ }) {
206
+ super(context, new ExtendableDictionary({}), new ExtendableDictionary({}));
207
+ }
208
+ }
package/src/types.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { UnaryFunction } from "rxjs";
2
+ import { Observable } from "rxjs/internal/Observable";
3
+ import { ObservableInput } from "rxjs/internal/types";
4
+ import { TapObservable } from "./observable";
5
+
6
+ export interface Effect<Args, Result> extends UnaryFunction<
7
+ Args,
8
+ Observable<Result>
9
+ > {}
10
+
11
+ export interface TapEffect<Args, Result> extends UnaryFunction<
12
+ Args,
13
+ TapObservable<Result>
14
+ > {}
15
+
16
+ export interface Action<Args, Result> extends UnaryFunction<
17
+ Args,
18
+ Promise<Result>
19
+ > {}
20
+
21
+ export type MaybeArray<T> = T | T[];
22
+
23
+ export type MaybeObservable<T> = T | Observable<T>;
24
+
25
+ export type MaybeObservableInput<T> = T | ObservableInput<T>;
26
+
27
+ export type MaybeObservableInputTuple<T> = {
28
+ [K in keyof T]: MaybeObservableInput<T[K]>;
29
+ };
30
+
31
+ export type Transform<T> = UnaryFunction<T, T>;
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowImportingTsExtensions": true,
4
+ "lib": ["ESNext", "DOM", "DOM.Iterable", "WebWorker"],
5
+ "module": "ESNext",
6
+ "moduleDetection": "force",
7
+ "moduleResolution": "bundler",
8
+ "noEmit": true,
9
+ "noFallthroughCasesInSwitch": true,
10
+ "noUncheckedIndexedAccess": true,
11
+ "noUncheckedSideEffectImports": true,
12
+ "noUnusedLocals": true,
13
+ "noUnusedParameters": true,
14
+ "skipLibCheck": true,
15
+ "strict": true,
16
+ "target": "ES2022",
17
+ "types": ["vite/client"],
18
+ "useDefineForClassFields": true
19
+ },
20
+ "include": ["src"]
21
+ }