@expressive/mvc 0.33.0 → 0.35.0-alpha.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/dist/index.mjs ADDED
@@ -0,0 +1,681 @@
1
+ // src/control.ts
2
+ var DISPATCH = /* @__PURE__ */ new Set();
3
+ var OBSERVER = /* @__PURE__ */ new WeakMap();
4
+ var LISTENER = /* @__PURE__ */ new WeakMap();
5
+ var EXPIRED = /* @__PURE__ */ new WeakSet();
6
+ function onReady() {
7
+ return null;
8
+ }
9
+ function addListener(to, fn) {
10
+ let subs = LISTENER.get(to);
11
+ if (!subs)
12
+ LISTENER.set(to, subs = /* @__PURE__ */ new Map([[onReady, null]]));
13
+ if (!subs.has(onReady))
14
+ fn(true, to);
15
+ subs.set(fn, null);
16
+ return () => subs.delete(fn);
17
+ }
18
+ function watch(from, key, value) {
19
+ const listeners = LISTENER.get(from);
20
+ const observer = OBSERVER.get(from);
21
+ if (observer) {
22
+ listeners.set(
23
+ observer,
24
+ new Set(listeners.get(observer)).add(key)
25
+ );
26
+ const nested = LISTENER.get(value);
27
+ if (nested) {
28
+ LISTENER.set(value = Object.create(value), nested);
29
+ OBSERVER.set(value, observer);
30
+ }
31
+ }
32
+ return value;
33
+ }
34
+ function event(source, key) {
35
+ LISTENER.get(source).forEach((select, callback, subs) => {
36
+ let after;
37
+ if (!select || select.has(key)) {
38
+ if (after = callback.call(source, key, source))
39
+ queue(after);
40
+ }
41
+ if (after === null || key === null)
42
+ subs.delete(callback);
43
+ });
44
+ }
45
+ function queue(event2) {
46
+ if (!DISPATCH.size)
47
+ setTimeout(() => {
48
+ DISPATCH.forEach((event3) => {
49
+ try {
50
+ event3();
51
+ } catch (err) {
52
+ console.error(err);
53
+ }
54
+ });
55
+ DISPATCH.clear();
56
+ }, 0);
57
+ DISPATCH.add(event2);
58
+ }
59
+ function effect(target, callback) {
60
+ const listeners = LISTENER.get(target);
61
+ let refresh;
62
+ let unSet;
63
+ function invoke() {
64
+ const local = Object.create(target);
65
+ const update2 = () => {
66
+ if (EXPIRED.has(update2))
67
+ return null;
68
+ EXPIRED.add(update2);
69
+ if (refresh && unSet) {
70
+ unSet();
71
+ unSet = void 0;
72
+ }
73
+ return refresh;
74
+ };
75
+ LISTENER.set(local, listeners);
76
+ OBSERVER.set(local, update2);
77
+ try {
78
+ const out = callback.call(local, local);
79
+ unSet = typeof out == "function" && out;
80
+ refresh = out === null ? out : invoke;
81
+ } catch (err) {
82
+ if (err instanceof Promise) {
83
+ refresh = void 0;
84
+ err.then(invoke).catch(console.error);
85
+ } else if (refresh)
86
+ console.error(err);
87
+ else
88
+ throw err;
89
+ }
90
+ }
91
+ addListener(target, (key) => {
92
+ if (key === true)
93
+ invoke();
94
+ else if (!refresh)
95
+ return refresh;
96
+ if (key === null && unSet)
97
+ unSet();
98
+ });
99
+ return () => {
100
+ refresh = null;
101
+ };
102
+ }
103
+
104
+ // src/model.ts
105
+ var ID = /* @__PURE__ */ new WeakMap();
106
+ var PARENT = /* @__PURE__ */ new WeakMap();
107
+ var INSTRUCT = /* @__PURE__ */ new Map();
108
+ var PENDING = /* @__PURE__ */ new WeakMap();
109
+ var STATE = /* @__PURE__ */ new WeakMap();
110
+ var Model = class _Model {
111
+ constructor(id) {
112
+ const state = {};
113
+ Object.defineProperty(this, "is", { value: this });
114
+ ID.set(this, `${this.constructor}-${id ? String(id) : uid()}`);
115
+ STATE.set(this, state);
116
+ function onNull(key) {
117
+ if (key === null)
118
+ Object.freeze(state);
119
+ }
120
+ addListener(this, () => {
121
+ for (const key in this) {
122
+ const { value } = Object.getOwnPropertyDescriptor(this, key);
123
+ const instruction = INSTRUCT.get(value);
124
+ let desc = {
125
+ enumerable: true,
126
+ set: (x) => update(this, key, x),
127
+ get() {
128
+ return watch(this, key, state[key]);
129
+ }
130
+ };
131
+ if (instruction) {
132
+ INSTRUCT.delete(value);
133
+ delete this[key];
134
+ desc = instruction(this, key, state);
135
+ } else
136
+ state[key] = value;
137
+ if (desc)
138
+ Object.defineProperty(this, key, desc);
139
+ }
140
+ addListener(this, onNull);
141
+ return null;
142
+ });
143
+ }
144
+ get(arg1, arg2) {
145
+ const self = this.is;
146
+ if (typeof arg1 == "string")
147
+ return fetch(self, arg1, arg2);
148
+ if (arg1)
149
+ return effect(self, arg1);
150
+ const cache = /* @__PURE__ */ new WeakMap();
151
+ function get2(value) {
152
+ if (value instanceof _Model) {
153
+ if (cache.has(value))
154
+ return cache.get(value);
155
+ const model = value;
156
+ cache.set(value, value = {});
157
+ for (const [key, val] of model)
158
+ value[key] = get2(val);
159
+ }
160
+ return value;
161
+ }
162
+ return get2(self);
163
+ }
164
+ set(arg1, arg2, arg3) {
165
+ const self = this.is;
166
+ if (typeof arg1 == "function")
167
+ return addListener(self, (key) => {
168
+ if (typeof key == "string")
169
+ return arg1(key);
170
+ });
171
+ if (typeof arg1 == "string" || arg1 === null)
172
+ return 1 in arguments ? update(self, arg1, arg2, arg3) : update(self, arg1);
173
+ return new Promise((resolve, reject) => {
174
+ if (typeof arg1 != "number" && !PENDING.has(self)) {
175
+ resolve(false);
176
+ return;
177
+ }
178
+ const remove = addListener(this, (key) => {
179
+ if (key === true || typeof arg2 == "function" && typeof key == "string" && arg2(key) !== true)
180
+ return;
181
+ if (timeout)
182
+ clearTimeout(timeout);
183
+ remove();
184
+ return resolve.bind(null, PENDING.get(self));
185
+ });
186
+ const timeout = typeof arg1 == "number" && setTimeout(() => {
187
+ remove();
188
+ reject(arg1);
189
+ }, arg1);
190
+ });
191
+ }
192
+ /** Iterate over managed properties in this instance of Model. */
193
+ [Symbol.iterator]() {
194
+ return Object.entries(STATE.get(this.is))[Symbol.iterator]();
195
+ }
196
+ /**
197
+ * Creates a new instance of this controller.
198
+ *
199
+ * Beyond `new this(...)`, method will activate managed-state.
200
+ *
201
+ * @param args - arguments sent to constructor
202
+ */
203
+ static new(...args) {
204
+ const instance = new this(...args);
205
+ event(instance, true);
206
+ return instance;
207
+ }
208
+ /**
209
+ * Static equivalent of `x instanceof this`.
210
+ * Determines if provided class is a subtype of this one.
211
+ * If so, language server will make available all static
212
+ * methods and properties of this class.
213
+ */
214
+ static is(maybe) {
215
+ return typeof maybe == "function" && maybe.prototype instanceof this;
216
+ }
217
+ };
218
+ Object.defineProperty(Model.prototype, "toString", {
219
+ value() {
220
+ return ID.get(this.is);
221
+ }
222
+ });
223
+ Object.defineProperty(Model, "toString", {
224
+ value() {
225
+ return this.name;
226
+ }
227
+ });
228
+ function add(instruction) {
229
+ const placeholder = Symbol("instruction");
230
+ INSTRUCT.set(placeholder, (subject, key, state) => {
231
+ const output = instruction.call(subject, key, subject, state);
232
+ if (!output)
233
+ return;
234
+ const desc = typeof output == "object" ? output : { get: output };
235
+ const { enumerable = true } = desc;
236
+ if ("value" in desc)
237
+ state[key] = desc.value;
238
+ return {
239
+ enumerable,
240
+ set(next) {
241
+ let { set: set2 } = desc;
242
+ if (set2 === false)
243
+ throw new Error(`${subject}.${key} is read-only.`);
244
+ if (typeof set2 == "function") {
245
+ const result = set2.call(subject, next, state[key]);
246
+ if (result === false)
247
+ return;
248
+ if (typeof result == "function")
249
+ next = result();
250
+ set2 = false;
251
+ }
252
+ update(subject, key, next, !!set2);
253
+ },
254
+ get() {
255
+ return watch(
256
+ this,
257
+ key,
258
+ typeof desc.get == "function" ? desc.get(this) : fetch(subject, key, desc.get)
259
+ );
260
+ }
261
+ };
262
+ });
263
+ return placeholder;
264
+ }
265
+ function fetch(subject, property, required) {
266
+ const state = STATE.get(subject);
267
+ if (property in state || required === false) {
268
+ const value = state[property];
269
+ if (value !== void 0 || !required)
270
+ return value;
271
+ }
272
+ const error = new Error(`${subject}.${property} is not yet available.`);
273
+ const promise = new Promise((resolve, reject) => {
274
+ addListener(subject, (key) => {
275
+ if (key === property) {
276
+ resolve(state[key]);
277
+ return null;
278
+ }
279
+ if (key === null)
280
+ reject(new Error(`${subject} is destroyed.`));
281
+ });
282
+ });
283
+ throw Object.assign(promise, {
284
+ toString: () => String(error),
285
+ name: "Suspense",
286
+ message: error.message,
287
+ stack: error.stack
288
+ });
289
+ }
290
+ function update(subject, key, value, silent) {
291
+ const state = STATE.get(subject);
292
+ let pending = PENDING.get(subject);
293
+ if (typeof key == "string") {
294
+ if (2 in arguments) {
295
+ const previous = state[key];
296
+ if (value === previous)
297
+ return true;
298
+ state[key] = value;
299
+ if (silent === true)
300
+ return;
301
+ }
302
+ if (!pending) {
303
+ PENDING.set(subject, pending = {});
304
+ queue(() => {
305
+ event(subject, false);
306
+ PENDING.delete(subject);
307
+ });
308
+ }
309
+ const skip = key in pending;
310
+ pending[key] = state[key];
311
+ if (skip)
312
+ return;
313
+ }
314
+ event(subject, key);
315
+ }
316
+ function uid() {
317
+ return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
318
+ }
319
+
320
+ // src/instruction/run.ts
321
+ function run(task) {
322
+ return add((key, subject) => {
323
+ let pending = false;
324
+ async function invoke(...args) {
325
+ if (pending)
326
+ return Promise.reject(
327
+ new Error(`Invoked action ${key} but one is already active.`)
328
+ );
329
+ pending = true;
330
+ subject.set(key);
331
+ try {
332
+ return await attempt(() => task.apply(subject, args));
333
+ } finally {
334
+ pending = false;
335
+ subject.set(key);
336
+ }
337
+ }
338
+ Object.defineProperty(invoke, "active", {
339
+ get: () => pending
340
+ });
341
+ return {
342
+ value: invoke,
343
+ set: false
344
+ };
345
+ });
346
+ }
347
+ function attempt(fn) {
348
+ function retry(err) {
349
+ if (err instanceof Promise)
350
+ return err.then(compute2);
351
+ else
352
+ throw err;
353
+ }
354
+ function compute2() {
355
+ try {
356
+ const output = fn();
357
+ return output instanceof Promise ? output.catch(retry) : output;
358
+ } catch (err) {
359
+ return retry(err);
360
+ }
361
+ }
362
+ return compute2();
363
+ }
364
+
365
+ // src/context.ts
366
+ var Context = class {
367
+ constructor(inputs) {
368
+ this.table = /* @__PURE__ */ new WeakMap();
369
+ this.layer = /* @__PURE__ */ new Map();
370
+ if (inputs)
371
+ this.include(inputs);
372
+ }
373
+ static resolve(from, callback) {
374
+ throw new Error(`Using context requires an adapter. If you are only testing, define \`get.context\` to simulate one.`);
375
+ }
376
+ has(T) {
377
+ let key = this.table.get(T);
378
+ if (!key) {
379
+ key = Symbol(T.name);
380
+ this.table.set(T, key);
381
+ }
382
+ return key;
383
+ }
384
+ get(Type) {
385
+ const result = this[this.has(Type)];
386
+ if (result === null)
387
+ throw new Error(`Did find ${Type} in context, but multiple were defined.`);
388
+ return result;
389
+ }
390
+ include(inputs) {
391
+ const init = /* @__PURE__ */ new Map();
392
+ for (const key in inputs) {
393
+ const input = inputs[key];
394
+ const exists = this.layer.get(key);
395
+ if (!exists) {
396
+ const instance = this.add(input);
397
+ this.layer.set(key, input);
398
+ init.set(instance, true);
399
+ } else if (exists !== input) {
400
+ this.pop();
401
+ this.key = uid();
402
+ return this.include(inputs);
403
+ }
404
+ }
405
+ for (const [model] of init)
406
+ for (const [_key, value] of model)
407
+ if (PARENT.get(value) === model) {
408
+ this.add(value, true);
409
+ init.set(value, false);
410
+ }
411
+ return init;
412
+ }
413
+ add(input, implicit) {
414
+ let writable = true;
415
+ let T;
416
+ let I;
417
+ if (typeof input == "function") {
418
+ T = input;
419
+ I = input.new();
420
+ } else {
421
+ I = input;
422
+ T = I.constructor;
423
+ writable = false;
424
+ }
425
+ do {
426
+ const key = this.has(T);
427
+ const value = this.hasOwnProperty(key) ? null : I;
428
+ if (value || I !== this[key] && !implicit)
429
+ Object.defineProperty(this, key, {
430
+ configurable: true,
431
+ value,
432
+ writable
433
+ });
434
+ T = Object.getPrototypeOf(T);
435
+ } while (T !== Model);
436
+ return I;
437
+ }
438
+ push() {
439
+ const next = Object.create(this);
440
+ next.layer = /* @__PURE__ */ new Map();
441
+ return next;
442
+ }
443
+ pop() {
444
+ const items = /* @__PURE__ */ new Set();
445
+ for (const key of Object.getOwnPropertySymbols(this)) {
446
+ const entry = Object.getOwnPropertyDescriptor(this, key);
447
+ if (entry.writable && entry.value)
448
+ items.add(entry.value);
449
+ delete this[key];
450
+ }
451
+ for (const model of items)
452
+ model.set(null);
453
+ this.layer.clear();
454
+ }
455
+ };
456
+
457
+ // src/instruction/get.ts
458
+ function get(arg0, arg1) {
459
+ return add((key, subject) => {
460
+ let from = subject;
461
+ if (typeof arg0 == "symbol")
462
+ throw new Error(`Attempted to use an instruction result (probably use or get) as computed source for ${from}.${key}. This is not allowed.`);
463
+ let source = (resolve) => resolve(from);
464
+ if (arg0 instanceof Model)
465
+ from = arg0;
466
+ else if (Model.is(arg0)) {
467
+ const hasParent = PARENT.get(from);
468
+ if (!hasParent) {
469
+ if (arg1 === true)
470
+ throw new Error(`${from} may only exist as a child of type ${arg0}.`);
471
+ source = (resolve) => {
472
+ Context.resolve(from, (context) => {
473
+ const model = context.get(arg0);
474
+ if (model)
475
+ resolve(model);
476
+ else if (arg1 !== false)
477
+ throw new Error(`Required ${arg0} not found in context for ${from}.`);
478
+ });
479
+ };
480
+ } else if (!arg0 || hasParent instanceof arg0)
481
+ from = hasParent;
482
+ else
483
+ throw new Error(`New ${from} created as child of ${hasParent}, but must be instanceof ${arg0}.`);
484
+ } else if (typeof arg0 == "function")
485
+ arg1 = arg0.call(from, key, from);
486
+ if (typeof arg1 == "function")
487
+ return compute(subject, key, source, arg1);
488
+ source((got) => subject.set(key, got));
489
+ return { get: arg1 };
490
+ });
491
+ }
492
+ var STALE = /* @__PURE__ */ new WeakSet();
493
+ function compute(subject, key, source, setter) {
494
+ let reset;
495
+ let isAsync;
496
+ let proxy;
497
+ function connect(model) {
498
+ reset = model.get((current) => {
499
+ proxy = current;
500
+ if (!reset)
501
+ compute2(true);
502
+ else if (STALE.delete(compute2))
503
+ compute2();
504
+ return () => {
505
+ STALE.add(compute2);
506
+ subject.set(key);
507
+ };
508
+ });
509
+ }
510
+ function compute2(initial) {
511
+ let next;
512
+ try {
513
+ next = setter.call(proxy, proxy);
514
+ } catch (err) {
515
+ console.warn(`An exception was thrown while ${initial ? "initializing" : "refreshing"} ${subject}.${key}.`);
516
+ if (initial)
517
+ throw err;
518
+ console.error(err);
519
+ }
520
+ subject.set(key, next, !isAsync);
521
+ }
522
+ return () => {
523
+ if (!proxy) {
524
+ source(connect);
525
+ isAsync = true;
526
+ }
527
+ if (STALE.delete(compute2))
528
+ compute2();
529
+ return fetch(subject, key, !proxy);
530
+ };
531
+ }
532
+
533
+ // src/instruction/ref.ts
534
+ var define = Object.defineProperty;
535
+ function ref(arg, arg2) {
536
+ return add((key, subject, state) => {
537
+ let value = {};
538
+ if (arg === subject)
539
+ for (const key2 in state)
540
+ if (typeof arg2 == "function")
541
+ define(value, key2, {
542
+ configurable: true,
543
+ get() {
544
+ const out = arg2(key2);
545
+ define(value, key2, { value: out });
546
+ return out;
547
+ }
548
+ });
549
+ else {
550
+ const ref2 = (value2) => subject.set(key2, value2);
551
+ define(ref2, "current", {
552
+ get: () => state[key2],
553
+ set: ref2
554
+ });
555
+ define(value, key2, { value: ref2 });
556
+ }
557
+ else if (typeof arg == "object")
558
+ throw new Error("ref instruction does not support object which is not 'this'");
559
+ else {
560
+ let unSet;
561
+ const set2 = (value2) => {
562
+ if (subject.set(key, value2) || !arg)
563
+ return;
564
+ if (unSet)
565
+ unSet = void unSet(value2);
566
+ if (value2 !== null || arg2 === false) {
567
+ const out = arg.call(subject, value2);
568
+ if (typeof out == "function")
569
+ unSet = out;
570
+ }
571
+ };
572
+ value = define(set2, "current", {
573
+ get: () => state[key],
574
+ set: set2
575
+ });
576
+ }
577
+ define(subject, key, { value });
578
+ });
579
+ }
580
+
581
+ // src/instruction/set.ts
582
+ function set(value, argument) {
583
+ return add((key, subject) => {
584
+ const output = {};
585
+ if (typeof value == "function" || value instanceof Promise) {
586
+ let init2 = function() {
587
+ if (typeof value == "function")
588
+ try {
589
+ value = attempt(value.bind(subject, key, subject));
590
+ } catch (err) {
591
+ console.warn(`Generating initial value for ${subject}.${key} failed.`);
592
+ throw err;
593
+ }
594
+ output.get = typeof argument == "boolean" ? argument : void 0;
595
+ if (value instanceof Promise) {
596
+ value.then((value2) => {
597
+ subject.set(key, value2);
598
+ return value2;
599
+ }).catch((err) => {
600
+ subject.set(key);
601
+ output.get = () => {
602
+ throw err;
603
+ };
604
+ });
605
+ } else
606
+ subject.set(key, value);
607
+ if (0 in arguments)
608
+ return subject[key];
609
+ };
610
+ var init = init2;
611
+ if (argument)
612
+ init2();
613
+ else
614
+ output.get = init2;
615
+ } else if (value !== void 0)
616
+ output.value = value;
617
+ if (typeof argument == "function") {
618
+ let unSet;
619
+ output.set = function(value2, previous) {
620
+ const out = argument.call(this, value2, previous);
621
+ if (out === false)
622
+ return out;
623
+ if (typeof unSet == "function")
624
+ unSet = void unSet(value2);
625
+ if (typeof out == "function")
626
+ unSet = out;
627
+ };
628
+ } else
629
+ output.set = (value2) => {
630
+ output.get = void 0;
631
+ subject.set(key, value2);
632
+ };
633
+ return output;
634
+ });
635
+ }
636
+
637
+ // src/instruction/use.ts
638
+ function use(value, argument) {
639
+ return add((property, subject) => {
640
+ if (typeof value === "function")
641
+ value = new value();
642
+ const output = { set: set2, value };
643
+ function set2(next) {
644
+ if (value instanceof Model && !(next instanceof value.constructor))
645
+ throw new Error(`${subject}.${property} expected Model of type ${value.constructor} but got ${next}.`);
646
+ if (next instanceof Model) {
647
+ PARENT.set(next, subject);
648
+ event(next, true);
649
+ } else if (next) {
650
+ const proxy = Object.create(next);
651
+ for (const key in proxy)
652
+ Object.defineProperty(proxy, key, {
653
+ enumerable: true,
654
+ get: () => next[key],
655
+ set(value2) {
656
+ if (value2 != next[key]) {
657
+ next[key] = value2;
658
+ subject.set(property);
659
+ }
660
+ }
661
+ });
662
+ output.get = () => proxy;
663
+ }
664
+ if (typeof argument == "function")
665
+ argument(next);
666
+ }
667
+ set2(value);
668
+ return output;
669
+ });
670
+ }
671
+ export {
672
+ Context,
673
+ Model,
674
+ add,
675
+ Model as default,
676
+ get,
677
+ ref,
678
+ run,
679
+ set,
680
+ use
681
+ };
package/package.json CHANGED
@@ -11,21 +11,23 @@
11
11
  "access": "public"
12
12
  },
13
13
  "keywords": [
14
+ "expressive",
14
15
  "mvc"
15
16
  ],
16
- "main": "lib/index.js",
17
- "module": "lib/index.esm.js",
18
- "types": "lib/index.d.ts",
17
+ "main": "dist/index.js",
18
+ "module": "dist/index.mjs",
19
+ "types": "dist/index.d.ts",
19
20
  "files": [
20
- "lib"
21
+ "dist/index.js",
22
+ "dist/index.mjs",
23
+ "dist/index.d.ts"
21
24
  ],
22
25
  "scripts": {
23
- "build": "rollup --config",
24
- "watch": "jest --watchAll",
25
- "test": "jest --collectCoverage",
26
- "prebuild": "rm -rf lib/",
26
+ "build": "tsup src/index.ts --dts --format esm,cjs",
27
+ "test": "tsc --noEmit && jest --collectCoverage",
28
+ "prebuild": "rm -rf dist",
27
29
  "preversion": "npm run test && npm run build"
28
30
  },
29
- "version": "0.33.0",
30
- "gitHead": "b6cd486f362d8188fcf11dde504978c45638ab40"
31
+ "version": "0.35.0-alpha.0",
32
+ "gitHead": "17db508f601400ac08e6f821376e7a9403525d5b"
31
33
  }