@expressive/mvc 1.0.0-beta.2 → 1.0.0-beta.20

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/types/model.d.ts CHANGED
@@ -1,13 +1,40 @@
1
- import React from 'react';
1
+ import { Key } from 'react';
2
2
 
3
- import Controller from './controller';
4
3
  import Lifecycle from './lifecycle';
5
- import { Selector } from './selector';
6
- import { Class, InstanceOf, Key } from './types';
4
+ import { Select, Selector } from './selector';
5
+ import { BunchOf, Callback, Class, InstanceOf, Query, RequestCallback, UpdateCallback } from './types';
6
+
7
+ interface PropertyDescriptor<T> {
8
+ configurable?: boolean;
9
+ enumerable?: boolean;
10
+ value?: T;
11
+ writable?: boolean;
12
+ get?(): T;
13
+ set?(v: T): void;
14
+ }
15
+
16
+ type Argument<T> = T extends (arg: infer U) => any ? U : never;
7
17
 
8
- type RefFunction = (e: HTMLElement | null) => void;
18
+ type Thenable<T> = {
19
+ then(onFulfilled: (arg: T) => void): void;
20
+ }
9
21
 
10
22
  export namespace Model {
23
+ /** Exotic value, actual value is contained. */
24
+ interface Ref<T = any> {
25
+ (next: T): void;
26
+ current: T | null;
27
+ }
28
+
29
+ type GetFunction<T> = (within?: Subscriber) => T;
30
+
31
+ /**
32
+ * Property initializer, will run upon instance creation.
33
+ * Optional returned callback will run when once upon first access.
34
+ */
35
+ type Instruction<T> = (this: Controller, key: string, thisArg: Controller) =>
36
+ void | GetFunction<T> | PropertyDescriptor<T>;
37
+
11
38
  /** Shallow replacement given all entries of Model */
12
39
  type Overlay<T, R> = { [K in keyof Entries<T>]: R };
13
40
 
@@ -17,6 +44,14 @@ export namespace Model {
17
44
  /** Object containing data found in T. */
18
45
  type Entries<T, E = Model> = Pick<T, Fields<T, E>>;
19
46
 
47
+ /** Actual value stored in state. */
48
+ type Value<R> = R extends Ref<infer T> ? T : R;
49
+
50
+ /** Values from current state of given controller. */
51
+ type State<T, K extends keyof T = Fields<T, Model>> = {
52
+ [P in K]: Value<T[P]>;
53
+ }
54
+
20
55
  /** Object comperable to data which may be found in T. */
21
56
  type Data<T, E = Model> = Partial<Entries<T, E>>;
22
57
 
@@ -67,11 +102,67 @@ export namespace Model {
67
102
  type SelectFields<T> = Selector.Function<Omit<T, keyof Model>>;
68
103
 
69
104
  type SelectField<T> = (arg: Omit<T, keyof Model>) => any;
105
+
106
+ type SelectFieldKey<T> = Select.Key<Fields<T>>;
107
+
108
+ type Typeof<T, ST, X extends keyof T = Exclude<keyof T, keyof Model>> = {
109
+ [Key in X]: T[Key] extends ST ? Key : never;
110
+ }[X];
111
+
112
+ type SelectTypeof<T, ST> = Select.Key<Typeof<T, ST>>;
113
+
114
+ type HandleValue = (value: any) => boolean | void;
115
+
116
+ export class Controller {
117
+ state: BunchOf<any>;
118
+ subject: {};
119
+
120
+ /** Add property to managed state. */
121
+ manage(key: string, initial: any, effect?: HandleValue): void;
122
+
123
+ select(using?: Query): string[];
124
+
125
+ setter(key: string, effect?: HandleValue): (value: any) => boolean | void;
126
+
127
+ addListener(batch: BunchOf<RequestCallback>): Callback;
128
+
129
+ update(key: string, value?: any): void;
130
+
131
+ include(cb: RequestCallback): void;
132
+ }
133
+
134
+ export class Subscriber {
135
+ proxy: any;
136
+ source: any;
137
+ parent: Controller;
138
+ active: boolean;
139
+ follows: BunchOf<Callback>;
140
+ dependant: Set<{
141
+ commit(): void;
142
+ release(): void;
143
+ }>;
144
+
145
+ follow(key: string, cb?: Callback | undefined): void;
146
+ commit(): Callback;
147
+ release(): Callback;
148
+ onUpdate(): void;
149
+ }
70
150
  }
71
151
 
72
- export interface Model extends Controller, Lifecycle {}
152
+ declare const CONTROL: unique symbol;
153
+ declare const LOCAL: unique symbol;
154
+ declare const STATE: unique symbol;
155
+
156
+ export abstract class State {
157
+ /** Controller for this instance. */
158
+ [CONTROL]: Model.Controller;
159
+
160
+ /** Current state of this instance. */
161
+ [STATE]?: Model.State<this>;
162
+
163
+ /** Current subscriber (if present) while used in a live context (e.g. hook or effect). */
164
+ [LOCAL]?: Model.Subscriber;
73
165
 
74
- export abstract class Model {
75
166
  /**
76
167
  * Circular reference to `this` controller.
77
168
  *
@@ -131,15 +222,35 @@ export abstract class Model {
131
222
  */
132
223
  set: this;
133
224
 
134
- /**
135
- * Plug-n-Play references for properties of this controller.
136
- *
137
- * Matched ref-functions automatically bind between receiving element and live value of field.
138
- *
139
- * For `<input type="text" />` this is a two-way binding.
140
- * User input is captured and part of controller's state/event stream.
141
- */
142
- bind: Model.Overlay<this, RefFunction>;
225
+ import <O extends Model.Data<this>> (via: O, select?: string[] | Model.SelectFields<this>): void;
226
+
227
+ export(): Model.State<this>;
228
+ export <P extends Model.Fields<this>> (select: P[]): Model.State<this, P>;
229
+ export <S extends Model.SelectFields<this>> (select: S): Model.State<this, Selector.From<S>>;
230
+
231
+ update(): PromiseLike<string[] | false>;
232
+ update(strict: true): Promise<string[]>;
233
+ update(strict: false): Promise<false>;
234
+ update(strict: boolean): Promise<string[] | false>;
235
+
236
+ update(keys: Model.Fields<this>): Thenable<string[]>;
237
+ update(keys: Model.SelectFieldKey<this>): Thenable<string[]>;
238
+
239
+ update(keys: Model.Fields<this>, callMethod: boolean): PromiseLike<string[]>;
240
+ update(keys: Model.SelectFields<this>, callMethod: boolean): PromiseLike<string[]>;
241
+
242
+ update<T>(keys: Model.Fields<this>, argument: T): PromiseLike<string[]>;
243
+ update<T>(keys: Model.SelectFields<this>, argument: T): PromiseLike<string[]>;
244
+
245
+ /*
246
+ Issue with self-reference, using fallback.
247
+
248
+ update(keys: Model.Typeof<this, () => void>, callMethod: boolean): Thenable<string[]>;
249
+ update(keys: Model.SelectTypeof<this, () => void>, callMethod: boolean): Thenable<string[]>;
250
+
251
+ update<T>(keys: Model.Typeof<this, (arg: T) => void>, argument: T): Thenable<string[]>;
252
+ update<T>(keys: Model.SelectTypeof<this, (arg: T) => void>, argument: T): Thenable<string[]>;
253
+ */
143
254
 
144
255
  /**
145
256
  * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
@@ -170,23 +281,14 @@ export abstract class Model {
170
281
  tap <K extends Model.SelectField<this>> (key: K, expect?: boolean): ReturnType<K>;
171
282
  tap <K extends Model.SelectField<this>> (key: K, expect: true): Exclude<ReturnType<K>, undefined>;
172
283
 
173
- /**
174
- * **React Hook** - Attach to instance of this controller within ambient component.
175
- *
176
- * This method will fire lifecycle events on given controller.
177
- *
178
- * @param id - Argument passed to controller-lifecycle methods. Use to identify the consumer.
179
- */
180
- tag(id?: Key): this;
284
+ /** Use symbol to access controller of a model. */
285
+ static CONTROL: typeof CONTROL;
181
286
 
182
- /**
183
- * **React Hook** - Attach to instance of this controller within ambient component.
184
- *
185
- * This method will fire lifecycle events on given controller.
186
- *
187
- * @param idFactory - Will be invoked with fetched instance. Use this to register a tag as-needed.
188
- */
189
- tag(idFactory: (idFactory: this) => Key | void): this;
287
+ /** Use symbol to access current state of a model. */
288
+ static STATE: typeof STATE;
289
+
290
+ /** Use symbol to access current subscriber of a model in a live context (e.g. hook or effect). */
291
+ static LOCAL: typeof LOCAL;
190
292
 
191
293
  /**
192
294
  * Creates a new instance of this controller.
@@ -202,8 +304,11 @@ export abstract class Model {
202
304
  *
203
305
  * Differs from `use()` in lacking subscription and lifecycle events.
204
306
  * Much more efficient if you don't need hook-based features.
307
+ *
308
+ * @param args - Arguments passed to constructor of `this`
309
+ * @param callback - Run after creation of instance.
205
310
  */
206
- static new <T extends Class> (this: T, ...args: ConstructorParameters<T>): InstanceOf<T>;
311
+ static new <T extends Class> (this: T, args?: ConstructorParameters<T>, callback?: (instance: InstanceOf<T>) => void): InstanceOf<T>;
207
312
 
208
313
  /**
209
314
  * **React Hook** - Create and attach an instance of this controller a react component.
@@ -211,8 +316,9 @@ export abstract class Model {
211
316
  * Note: Model will be destroyed when ambient component unmounts!
212
317
  *
213
318
  * @param args - Arguments passed to constructor of `this`
319
+ * @param callback - Run after creation of instance.
214
320
  */
215
- static use <T extends Class> (this: T, ...args: ConstructorParameters<T>): InstanceOf<T>;
321
+ static use <T extends Class> (this: T, args?: ConstructorParameters<T>, callback?: (instance: InstanceOf<T>) => void): InstanceOf<T>;
216
322
 
217
323
  /**
218
324
  * **React Hook** - Similar to `use`, will instanciate a controller bound to ambient component.
@@ -220,7 +326,7 @@ export abstract class Model {
220
326
  *
221
327
  * @param data - Data to be applied to controller upon creation.
222
328
  */
223
- static uses <T extends Class, I extends InstanceOf<T>, D extends Partial<I>> (this: T, data: D): I;
329
+ static uses <T extends Class, I extends InstanceOf<T>, D extends Partial<I>> (this: T, data: D, only?: (keyof D)[]): I;
224
330
 
225
331
  /**
226
332
  * **React Hook** - Similar to `uses`, will instanciate a controller includive of given data.
@@ -229,7 +335,13 @@ export abstract class Model {
229
335
  *
230
336
  * @param data - Data to be observed by controller.
231
337
  */
232
- static using <T extends Class, I extends InstanceOf<T>, D extends Partial<I>> (this: T, data: D): I;
338
+ static using <T extends Class, I extends InstanceOf<T>, D extends Partial<I>> (this: T, data: D, only?: (keyof D)[]): I;
339
+
340
+ /**
341
+ * **React Hook** - Locate most relevant instance of this type in context.
342
+ */
343
+ static find <T extends Class>(this: T, strict: true): InstanceOf<T>;
344
+ static find <T extends Class>(this: T, strict?: boolean): InstanceOf<T> | undefined;
233
345
 
234
346
  /**
235
347
  * **React Hook** - Fetch most instance of this controller from context, if it exists.
@@ -275,6 +387,61 @@ export abstract class Model {
275
387
  static tap <T extends Class, I extends InstanceOf<T>, K extends Model.Fields<I>> (this: T, key: K, expect: true): Exclude<I[K], undefined>;
276
388
  static tap <T extends Class, I extends InstanceOf<T>, K extends Model.SelectField<I>> (this: T, key: K, expect: true): Exclude<ReturnType<K>, undefined>;
277
389
 
390
+ /**
391
+ * Static equivalent of `x instanceof this`.
392
+ *
393
+ * Will determine if provided class is a subtype of this one.
394
+ */
395
+ static isTypeof <T extends Class>(this: T, subject: any): subject is T;
396
+ }
397
+
398
+ export interface Model extends Lifecycle {}
399
+ export abstract class Model extends State {
400
+ // Keyed
401
+ on <S extends Model.SelectEvents<this>> (via: S, cb: Selector.Callback<S, this>, squash?: false, once?: boolean): Callback;
402
+ on <P extends Model.EventsCompat<this>> (key: P | P[], listener: UpdateCallback<this, P>, squash?: false, once?: boolean): Callback;
403
+ // Squash
404
+ on <S extends Model.SelectEvents<this>> (via: S, cb: (keys: Selector.From<S>[]) => void, squash: true, once?: boolean): Callback;
405
+ on <P extends Model.EventsCompat<this>> (key: P | P[], listener: (keys: P[]) => void, squash: true, once?: boolean): Callback;
406
+ // Unknown
407
+ on <S extends Model.SelectEvents<this>> (via: S, cb: unknown, squash: boolean, once?: boolean): Callback;
408
+ on <P extends Model.EventsCompat<this>> (key: P | P[], listener: unknown, squash: boolean, once?: boolean): Callback;
409
+
410
+ // Keyed
411
+ once <S extends Model.SelectEvents<this>> (via: S, cb: Selector.Callback<S, this>, squash?: false): Callback;
412
+ once <P extends Model.EventsCompat<this>> (key: P | P[], listener: UpdateCallback<this, P>, squash?: false): Callback;
413
+ // Squash
414
+ once <S extends Model.SelectEvents<this>> (via: S, cb: (keys: Selector.From<S>[]) => void, squash: true): Callback;
415
+ once <P extends Model.EventsCompat<this>> (key: P | P[], listener: (keys: P[]) => void, squash: true): Callback;
416
+ // Promise
417
+ once <S extends Model.SelectEvents<this>> (via: S): Promise<Selector.From<S>[]>;
418
+ once <P extends Model.EventsCompat<this>> (key: P | P[]): Promise<P[]>;
419
+ // Unknown
420
+ once <S extends Model.SelectEvents<this>> (via: S, cb: unknown, squash: boolean): Callback;
421
+ once <P extends Model.EventsCompat<this>> (key: P | P[], listener: unknown, squash: boolean): Callback;
422
+
423
+ effect(callback: (this: this, state: this) => void, select?: Model.SelectFields<this>): Callback;
424
+ effect(callback: (this: this, state: this) => void, select?: (keyof this)[]): Callback;
425
+
426
+ /**
427
+ * **React Hook** - Attach to instance of this controller within ambient component.
428
+ *
429
+ * This method will fire lifecycle events on given controller.
430
+ *
431
+ * @param id - Argument passed to controller-lifecycle methods. Use to identify the consumer.
432
+ */
433
+ tag(id?: any): this;
434
+
435
+ /**
436
+ * **React Hook** - Attach to instance of this controller within ambient component.
437
+ *
438
+ * This method will fire lifecycle events on given controller.
439
+ *
440
+ * @param idFactory - Will be invoked with fetched instance. Use this to register a tag as-needed.
441
+ */
442
+ tag(idFactory: (idFactory: this) => any): this;
443
+
444
+
278
445
  /**
279
446
  * **React Hook** - Attach to instance of this controller within ambient component.
280
447
  *
@@ -300,7 +467,7 @@ export abstract class Model {
300
467
  *
301
468
  * Documentation TBD.
302
469
  */
303
- static meta <T extends Class>(this: T): T & Controller;
470
+ static meta <T extends Class>(this: T): T;
304
471
 
305
472
  /**
306
473
  * **React Hook** - Fetch and subscribe to value defined on class itself using selectors.
@@ -308,26 +475,21 @@ export abstract class Model {
308
475
  * Documentation TBD.
309
476
  */
310
477
  static meta <T extends Class, K extends Model.SelectField<T>> (this: T, key?: K): ReturnType<K>;
311
-
312
- /**
313
- * **React Hook** - Locate most relevant instance of this type in context.
314
- */
315
- static find <T extends Class>(this: T): InstanceOf<T>;
316
-
317
- /**
318
- * Static equivalent of `x instanceof this`.
319
- *
320
- * Will determine if provided class is a subtype of this one.
321
- */
322
- static isTypeof <T extends Class>(this: T, subject: any): subject is T;
323
-
324
- /**
325
- * Retreives parent inherited by this class, unless it is base `Model`.
326
- */
327
- static inherits: typeof Model | undefined;
328
478
  }
329
479
 
330
480
  export class Singleton extends Model {
331
- /** Current instance of this controller accessable anywhere. */
332
- static current?: Singleton;
481
+ /** Get current instance of this singleton. Will throw if one does not exist. */
482
+ static is<T extends Class>(this: T): InstanceOf<T>;
483
+
484
+ /**
485
+ * Update the active instance of this class.
486
+ * Returns a thenable; resolves after successful update.
487
+ * If instance does not already exist, one will be created.
488
+ **/
489
+ static update<T extends Class>(
490
+ this: T, updates: Model.Data<InstanceOf<T>>
491
+ ): PromiseLike<string[] | false>;
492
+
493
+ /** Destroy current instance of Singleton, if it exists. */
494
+ static reset(): void;
333
495
  }
package/types/selector.ts CHANGED
@@ -1,3 +1,7 @@
1
+ export type KeysOfType<T, ST> = {
2
+ [Key in keyof T]: T[Key] extends ST ? Key : never
3
+ }[keyof T];
4
+
1
5
  export namespace Selector {
2
6
  /**
3
7
  * Shallow-recursive clone of source object.
@@ -26,4 +30,18 @@ export namespace Selector {
26
30
 
27
31
  export type Gets<F extends Function<any>> =
28
32
  F extends (spy: any) => infer R ? Values<R> : never;
33
+
34
+ export type Callback<S extends Function<any>, T> =
35
+ (this: T, value: Gets<S>, key: From<S>) => void;
36
+ }
37
+
38
+ export namespace Select {
39
+ export type One<T> =
40
+ (arg: { [K in keyof T]: K }) => string & keyof T;
41
+
42
+ export type Key<T extends string | number | symbol> =
43
+ (arg: { [K in T]: K }) => string & T;
44
+
45
+ export type From<T extends One<any>> =
46
+ T extends (from: any) => infer R ? R : never;
29
47
  }
package/types/types.d.ts CHANGED
@@ -9,4 +9,10 @@ export type IfApplicable<T extends {}, K> = K extends keyof T ? T[K] : undefined
9
9
  export type Callback = () => void;
10
10
  export type RequestCallback = (keys?: string[]) => void;
11
11
  export type EffectCallback<T, A = T> = (this: T, argument: A) => Callback | Promise<any> | void;
12
- export type UpdateCallback<T, P> = (this: T, value: IfApplicable<T, P>, changed: P) => void;
12
+ export type UpdateCallback<T, P> = (this: T, value: IfApplicable<T, P>, changed: P) => void;
13
+ export type InterceptCallback<T> = (argument: T) => ((next: T) => void) | Promise<any> | void | boolean;
14
+
15
+ export type RefFunction = (e: HTMLElement | null) => void;
16
+
17
+ export type Recursive<T> = { [K in keyof T]: Recursive<Omit<T, K>> };
18
+ export type Query = (select: Recursive<{}>) => void;
@@ -1,56 +0,0 @@
1
- import { Model } from './model';
2
- import { Selector } from './selector';
3
- import { Callback, EffectCallback, UpdateCallback } from './types';
4
-
5
- type CallbackFor<S extends Selector.Function<any>, T> =
6
- (this: T, value: Selector.Gets<S>, key: Selector.From<S>) => void;
7
-
8
- /**
9
- * Observable Instance
10
- *
11
- * Implements internal value tracking.
12
- * Able to be subscribed to, per-value to know when updated.
13
- */
14
- interface Dispatch {
15
- // Explicit all
16
- on <S extends Model.SelectEvents<this>> (via: S, cb: CallbackFor<S, this>, squash?: false, once?: boolean): Callback;
17
- on <P extends Model.EventsCompat<this>> (key: P | P[], listener: UpdateCallback<this, P>, squash?: false, once?: boolean): Callback;
18
-
19
- once <S extends Model.SelectEvents<this>> (via: S, cb: CallbackFor<S, this>, squash?: false): Callback;
20
- once <P extends Model.EventsCompat<this>> (key: P | P[], listener: UpdateCallback<this, P>, squash?: false): Callback;
21
-
22
- // Explicit squash
23
- on <S extends Model.SelectEvents<this>> (via: S, cb: (keys: Selector.From<S>[]) => void, squash: true, once?: boolean): Callback;
24
- on <P extends Model.EventsCompat<this>> (key: P | P[], listener: (keys: P[]) => void, squash: true, once?: boolean): Callback;
25
-
26
- once <S extends Model.SelectEvents<this>> (via: S, cb: (keys: Selector.From<S>[]) => void, squash: true): Callback;
27
- once <P extends Model.EventsCompat<this>> (key: P | P[], listener: (keys: P[]) => void, squash: true): Callback;
28
-
29
- // Implicit squash
30
- once <S extends Model.SelectEvents<this>> (via: S): Promise<Selector.From<S>[]>;
31
- once <P extends Model.EventsCompat<this>> (key: P | P[]): Promise<P[]>;
32
-
33
- // Unknown squash
34
- on <S extends Model.SelectEvents<this>> (via: S, cb: unknown, squash: boolean, once?: boolean): Callback;
35
- on <P extends Model.EventsCompat<this>> (key: P | P[], listener: unknown, squash: boolean, once?: boolean): Callback;
36
-
37
- once <S extends Model.SelectEvents<this>> (via: S, cb: unknown, squash: boolean): Callback;
38
- once <P extends Model.EventsCompat<this>> (key: P | P[], listener: unknown, squash: boolean): Callback;
39
-
40
- effect(callback: EffectCallback<this>, select?: Model.SelectFields<this>): Callback;
41
- effect(callback: EffectCallback<this>, select?: (keyof this)[]): Callback;
42
-
43
- import <O extends Model.Data<this>> (via: O, select?: string[] | Model.SelectFields<this>): void;
44
-
45
- export(): Model.Entries<this>;
46
- export <P extends Model.Fields<this>> (select: P[]): Pick<this, P>;
47
- export <S extends Model.SelectFields<this>> (select: S): Pick<this, Selector.From<S>>;
48
-
49
- update(keys: Model.SelectFields<this>): void;
50
- update(keys: Model.Fields<this>[]): void;
51
-
52
- requestUpdate(strict?: boolean): Promise<string[] | false>;
53
- requestUpdate(cb: (keys: string[]) => void): void;
54
- }
55
-
56
- export = Dispatch;
package/types/react.d.ts DELETED
@@ -1,46 +0,0 @@
1
- import { ReactElement } from "react";
2
- import { Model } from "./model"
3
- import { Class } from "./types";
4
-
5
- type ComponentOutput = ReactElement<any, any> | null;
6
-
7
- type ProviderForProps<E> = E extends Class
8
- ? ({ of: E, children?: React.ReactNode } & Model.Data<InstanceType<E>>)
9
- | ({ of: E, children?: (instance: InstanceType<E>) => React.ReactNode } & Model.Data<InstanceType<E>>)
10
- : ({ of: E, children?: React.ReactNode } & Model.Data<E>);
11
-
12
- type ConsumerProps<E extends Class> =
13
- | {
14
- /** Type of controller to fetch from context. */
15
- of: E;
16
- /**
17
- * Getter function. Is called on every natural render of this component.
18
- * Will throw if usable instance cannot be found in context.
19
- */
20
- has: (value: InstanceType<E>) => void;
21
- }
22
- | {
23
- /** Type of controller to fetch from context. */
24
- of: E;
25
- /** Getter function. Is called on every natural render of this component. */
26
- get: (value: InstanceType<E> | undefined) => void;
27
- }
28
- | {
29
- /** Type of controller to fetch from context. */
30
- of: E;
31
- /**
32
- * Render function, will receive instance of desired controller.
33
- *
34
- * Similar to `tap()`, updates to properties accessed in
35
- * this function will cause a refresh when they change.
36
- */
37
- children: (value: InstanceType<E>) => ComponentOutput;
38
- }
39
-
40
- // type ProvideCollection = Array<Model | typeof Model> | BunchOf<Model | typeof Model>;
41
- // declare function Provider(props: ProviderOfProps): ComponentOutput;
42
-
43
- declare function Provider<T>(props: ProviderForProps<T>): ComponentOutput;
44
- declare function Consumer<T extends Class>(props: ConsumerProps<T>): ComponentOutput;
45
-
46
- export { Consumer, Provider };