@expressive/mvc 0.53.6 → 0.54.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.
@@ -1,182 +0,0 @@
1
- /** Placeholder event determines if model is initialized or not. */
2
- const onReady = () => null;
3
- const LISTENERS = new WeakMap();
4
- /** Events pending for a given object. */
5
- const PENDING = new WeakMap();
6
- /** Update register. */
7
- const PENDING_KEYS = new WeakMap();
8
- /** Central event dispatch. Bunches all updates to occur at same time. */
9
- const DISPATCH = new Set();
10
- const OBSERVER = new WeakMap();
11
- function addListener(subject, callback, select) {
12
- let listeners = LISTENERS.get(subject);
13
- if (!listeners)
14
- LISTENERS.set(subject, listeners = new Map([[onReady, undefined]]));
15
- const filter = select === undefined ? undefined : new Set([select]);
16
- if (!listeners.has(onReady) && !filter)
17
- callback.call(subject, true, subject);
18
- listeners.set(callback, filter);
19
- return () => listeners.delete(callback);
20
- }
21
- function emit(model, key) {
22
- const listeners = LISTENERS.get(model);
23
- const notReady = listeners.has(onReady);
24
- if (key === true && !notReady)
25
- return;
26
- let pending = PENDING.get(model);
27
- if (pending) {
28
- pending.add(key);
29
- return;
30
- }
31
- PENDING.set(model, pending = new Set(notReady ? [true, key] : [key]));
32
- for (const key of pending)
33
- for (const [callback, filter] of listeners)
34
- if (!filter || filter.has(key)) {
35
- const after = callback.call(model, key, model);
36
- if (after)
37
- enqueue(after);
38
- else if (after === null)
39
- listeners.delete(callback);
40
- }
41
- if (key === null)
42
- listeners.clear();
43
- PENDING.delete(model);
44
- }
45
- function event(subject, key, silent) {
46
- if (key === null)
47
- return emit(subject, key);
48
- if (!key)
49
- return emit(subject, true);
50
- let pending = PENDING_KEYS.get(subject);
51
- if (!pending) {
52
- PENDING_KEYS.set(subject, pending = new Set());
53
- if (!silent)
54
- enqueue(() => {
55
- emit(subject, false);
56
- PENDING_KEYS.delete(subject);
57
- });
58
- }
59
- pending.add(key);
60
- if (!silent)
61
- emit(subject, key);
62
- }
63
- function pending(subject) {
64
- if (PENDING_KEYS.has(subject))
65
- return {
66
- then: (res) => new Promise(res => {
67
- const remove = addListener(subject, key => {
68
- if (key !== true) {
69
- remove();
70
- return res.bind(null, Array.from(PENDING_KEYS.get(subject)));
71
- }
72
- });
73
- }).then(res)
74
- };
75
- }
76
- function enqueue(eventHandler) {
77
- if (!DISPATCH.size)
78
- setTimeout(() => {
79
- DISPATCH.forEach(event => {
80
- try {
81
- event();
82
- }
83
- catch (err) {
84
- console.error(err);
85
- }
86
- });
87
- DISPATCH.clear();
88
- }, 0);
89
- DISPATCH.add(eventHandler);
90
- }
91
- function createProxy(from, observer) {
92
- const proxy = Object.create(from);
93
- OBSERVER.set(proxy, observer);
94
- return proxy;
95
- }
96
- function watch(from, key, value) {
97
- const access = OBSERVER.get(from);
98
- return access ? access(from, key, value) : value;
99
- }
100
- function createEffect(target, callback, argument) {
101
- const listeners = LISTENERS.get(target);
102
- let unset;
103
- let reset;
104
- function invoke() {
105
- let stale;
106
- const subscriber = createProxy(target, access);
107
- LISTENERS.set(subscriber, listeners);
108
- function onUpdate() {
109
- if (stale)
110
- return null;
111
- stale = true;
112
- if (reset && unset) {
113
- unset(true);
114
- unset = undefined;
115
- }
116
- return reset;
117
- }
118
- function access(from, key, value) {
119
- if (value === undefined && argument)
120
- throw new Error(`${from}.${key} is required in this context.`);
121
- const listeners = LISTENERS.get(from);
122
- let listener = listeners.get(onUpdate);
123
- if (!listener)
124
- listeners.set(onUpdate, listener = new Set);
125
- listener.add(key);
126
- const nested = LISTENERS.get(value);
127
- if (nested)
128
- LISTENERS.set(value = createProxy(value, access), nested);
129
- return value;
130
- }
131
- try {
132
- const ctx = context(argument === false);
133
- const ret = callback.call(subscriber, subscriber);
134
- const flush = ctx();
135
- reset = ret === null ? null : invoke;
136
- unset = key => {
137
- if (typeof ret == "function")
138
- ret(key);
139
- flush();
140
- };
141
- }
142
- catch (err) {
143
- if (err instanceof Promise) {
144
- reset = undefined;
145
- err.then(invoke);
146
- }
147
- else
148
- throw err;
149
- }
150
- }
151
- function cleanup() {
152
- if (unset)
153
- unset(false);
154
- reset = null;
155
- }
156
- ;
157
- if (EffectContext && argument !== false)
158
- EffectContext.add(cleanup);
159
- addListener(target, key => {
160
- if (key === true)
161
- invoke();
162
- else if (!reset)
163
- return reset;
164
- if (key === null && unset)
165
- unset(null);
166
- });
167
- return cleanup;
168
- }
169
- let EffectContext;
170
- export function context(ignore) {
171
- if (ignore)
172
- return () => () => { };
173
- const current = EffectContext;
174
- const ctx = EffectContext = new Set;
175
- return () => {
176
- EffectContext = current;
177
- return () => {
178
- ctx.forEach(fn => fn());
179
- };
180
- };
181
- }
182
- export { addListener, createEffect, createProxy, event, PENDING_KEYS, pending, watch };
@@ -1,183 +0,0 @@
1
- import { createEffect, createProxy } from './control';
2
- import { set } from './instruction/set';
3
- import { use } from './instruction/use';
4
- import { mockError } from './mocks';
5
- import { Model } from './model';
6
- describe("effect", () => {
7
- it("will run after properties", () => {
8
- const mock = jest.fn();
9
- class Test extends Model {
10
- constructor() {
11
- super(...arguments);
12
- this.property = use((_key, _model, state) => {
13
- this.get(() => mock(state));
14
- });
15
- this.foo = 1;
16
- this.bar = 2;
17
- }
18
- }
19
- Test.new();
20
- expect(mock).toBeCalledWith({ foo: 1, bar: 2 });
21
- });
22
- it("will enforce values if required", () => {
23
- class Test extends Model {
24
- constructor() {
25
- super(...arguments);
26
- this.property = undefined;
27
- }
28
- }
29
- const test = Test.new("ID");
30
- const attempt = () => {
31
- createEffect(test, $ => {
32
- expect($.property);
33
- }, true);
34
- };
35
- expect(attempt).toThrowError(`ID.property is required in this context.`);
36
- });
37
- it("will still get events after silent ones", async () => {
38
- class Test extends Model {
39
- constructor() {
40
- super(...arguments);
41
- this.value1 = 1;
42
- this.value2 = 2;
43
- }
44
- }
45
- const test = Test.new();
46
- const didGetValue = jest.fn();
47
- test.get($ => {
48
- didGetValue($.value1, $.value2);
49
- });
50
- expect(didGetValue).toBeCalledWith(1, 2);
51
- test.set({ value1: 10 }, true);
52
- test.set({ value2: 20 });
53
- await expect(test).toHaveUpdated();
54
- expect(didGetValue).toBeCalledWith(10, 20);
55
- expect(didGetValue).toBeCalledTimes(2);
56
- });
57
- it("will cleanup nested effects", async () => {
58
- class Test extends Model {
59
- constructor() {
60
- super(...arguments);
61
- this.foo = 1;
62
- this.bar = 2;
63
- }
64
- }
65
- const test = Test.new();
66
- const didInvoke = jest.fn();
67
- const done = createEffect(test, ({ foo }) => {
68
- createEffect(test, ({ bar }) => {
69
- didInvoke({ foo, bar });
70
- });
71
- });
72
- expect(didInvoke).toBeCalledTimes(1);
73
- expect(didInvoke).toBeCalledWith({ foo: 1, bar: 2 });
74
- await test.set({ bar: 3 });
75
- expect(didInvoke).toBeCalledWith({ foo: 1, bar: 3 });
76
- await test.set({ foo: 2, bar: 4 });
77
- expect(didInvoke).toBeCalledWith({ foo: 2, bar: 4 });
78
- expect(didInvoke).not.toBeCalledWith({ foo: 1, bar: 4 });
79
- await test.set({ bar: 2 });
80
- expect(didInvoke).toBeCalledWith({ foo: 2, bar: 2 });
81
- done();
82
- await test.set({ bar: 1 });
83
- expect(didInvoke).not.toBeCalledWith({ foo: 2, bar: 1 });
84
- expect(didInvoke).toBeCalledTimes(4);
85
- });
86
- });
87
- describe("suspense", () => {
88
- it("will seem to throw error outside react", () => {
89
- class Test extends Model {
90
- constructor() {
91
- super(...arguments);
92
- this.value = set();
93
- }
94
- }
95
- const instance = Test.new("ID");
96
- let didThrow;
97
- try {
98
- void instance.value;
99
- }
100
- catch (err) {
101
- didThrow = err;
102
- }
103
- expect(String(didThrow)).toMatchInlineSnapshot(`"Error: ID.value is not yet available."`);
104
- });
105
- it("will reject if model destroyed before resolved", async () => {
106
- class Test extends Model {
107
- constructor() {
108
- super(...arguments);
109
- this.value = set();
110
- }
111
- }
112
- const instance = Test.new("ID");
113
- let didThrow;
114
- try {
115
- void instance.value;
116
- }
117
- catch (err) {
118
- didThrow = err;
119
- }
120
- instance.set(null);
121
- await expect(didThrow).rejects.toThrowError(`ID is destroyed.`);
122
- });
123
- });
124
- describe("errors", () => {
125
- const error = mockError();
126
- it("will throw sync error to the console", async () => {
127
- class Test extends Model {
128
- constructor() {
129
- super(...arguments);
130
- this.value = 1;
131
- }
132
- }
133
- ;
134
- const test = Test.new();
135
- test.set(() => {
136
- throw new Error("sync error");
137
- });
138
- const attempt = () => test.value = 2;
139
- expect(attempt).toThrowError(`sync error`);
140
- });
141
- it("will log async error to the console", async () => {
142
- class Test extends Model {
143
- constructor() {
144
- super(...arguments);
145
- this.value = 1;
146
- }
147
- }
148
- ;
149
- const expected = new Error("async error");
150
- const test = Test.new();
151
- test.get($ => {
152
- if ($.value == 2)
153
- throw expected;
154
- });
155
- test.value = 2;
156
- await expect(test).toHaveUpdated();
157
- expect(error).toBeCalledWith(expected);
158
- });
159
- });
160
- describe("proxy", () => {
161
- it("will create a proxy object", () => {
162
- class Test extends Model {
163
- constructor() {
164
- super(...arguments);
165
- this.foo = 1;
166
- this.bar = 2;
167
- this.baz = 3;
168
- }
169
- }
170
- const test = Test.new("Test");
171
- const getter = jest.fn((self, key, value) => {
172
- return `${self}.${key}=${value}`;
173
- });
174
- const proxy = createProxy(test, getter);
175
- expect(proxy.foo).toBe(`Test.foo=1`);
176
- expect(getter).toBeCalledWith(test, "foo", 1);
177
- expect(proxy.bar).toBe(`Test.bar=2`);
178
- expect(getter).toBeCalledWith(test, "bar", 2);
179
- expect(proxy.baz).toBe(`Test.baz=3`);
180
- expect(getter).toBeCalledWith(test, "baz", 3);
181
- expect(getter).toBeCalledTimes(3);
182
- });
183
- });
package/dist/esm/index.js DELETED
@@ -1,8 +0,0 @@
1
- export { get } from './instruction/get';
2
- export { ref } from './instruction/ref';
3
- export { set } from './instruction/set';
4
- export { use } from './instruction/use';
5
- export { has } from './instruction/has';
6
- export { Model, Model as default } from './model';
7
- export { createEffect, createProxy, addListener } from './control';
8
- export { Context } from './context';
@@ -1,87 +0,0 @@
1
- import { Context } from '../context';
2
- import { createEffect } from '../control';
3
- import { fetch, METHOD, Model, PARENT, event, update } from '../model';
4
- import { use } from './use';
5
- const STALE = new WeakSet();
6
- function get(arg0, arg1) {
7
- return use((key, subject, state) => {
8
- if (typeof arg0 == "symbol")
9
- throw new Error(`Attempted to use an instruction result (probably use or get) as computed source for ${subject}.${key}. This is not allowed.`);
10
- if (Model.is(arg0)) {
11
- const hasParent = PARENT.get(subject);
12
- // TODO: notify on discovered context
13
- function assign(value) {
14
- if (typeof arg1 == "function")
15
- value.get(x => arg1(x, key));
16
- update(subject, key, value);
17
- }
18
- if (!hasParent) {
19
- if (arg1 === true)
20
- throw new Error(`${subject} may only exist as a child of type ${arg0}.`);
21
- Context.get(subject, (context) => {
22
- const model = context.get(arg0);
23
- if (model)
24
- assign(model);
25
- else if (arg1 !== false)
26
- throw new Error(`Required ${arg0} not found in context for ${subject}.`);
27
- });
28
- }
29
- else if (!arg0 || hasParent instanceof arg0)
30
- assign(hasParent);
31
- else
32
- throw new Error(`New ${subject} created as child of ${hasParent}, but must be instanceof ${arg0}.`);
33
- return {
34
- get: arg1 !== false
35
- };
36
- }
37
- let from = subject;
38
- if (arg0 instanceof Model) {
39
- from = arg0;
40
- }
41
- else {
42
- const fn = METHOD.get(arg0) || arg0;
43
- arg1 = ((p, k) => fn.call(p, k, p));
44
- }
45
- const source = resolve => resolve(from);
46
- const getter = arg1;
47
- let reset;
48
- let isAsync;
49
- let proxy;
50
- function connect(model) {
51
- reset = createEffect(model, current => {
52
- proxy = current;
53
- if (!(key in state) || STALE.delete(compute))
54
- compute(!reset);
55
- return (didUpdate) => {
56
- if (didUpdate) {
57
- STALE.add(compute);
58
- event(subject, key, true);
59
- }
60
- };
61
- }, false);
62
- }
63
- function compute(initial) {
64
- let next;
65
- try {
66
- next = getter.call(proxy, proxy, key);
67
- }
68
- catch (err) {
69
- console.warn(`An exception was thrown while ${initial ? "initializing" : "refreshing"} ${subject}.${key}.`);
70
- if (initial)
71
- throw err;
72
- console.error(err);
73
- }
74
- update(subject, key, next, !isAsync);
75
- }
76
- return () => {
77
- if (!proxy) {
78
- source(connect);
79
- isAsync = true;
80
- }
81
- if (STALE.delete(compute))
82
- compute();
83
- return fetch(subject, key, !proxy);
84
- };
85
- });
86
- }
87
- export { get };