@magicdoor/magic-use-case-react 0.1.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/CHANGELOG.md +99 -0
- package/LICENSE +202 -0
- package/README.md +33 -0
- package/dist/index.d-Dyx9E1u0.d.ts +124 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +819 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +1366 -0
- package/package.json +75 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
import { useState, useMemo, useCallback, useRef, useEffect, Component } from 'react';
|
|
2
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
|
|
7
|
+
|
|
8
|
+
// ../core/dist/index.js
|
|
9
|
+
var __defProp2 = Object.defineProperty;
|
|
10
|
+
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
12
|
+
var ConcreteEventEmitter = class {
|
|
13
|
+
constructor() {
|
|
14
|
+
__publicField2(this, "events", {});
|
|
15
|
+
__publicField2(this, "stateChange", "stateChange");
|
|
16
|
+
__publicField2(this, "navigation", "navigation");
|
|
17
|
+
__publicField2(this, "error", "error");
|
|
18
|
+
__publicField2(this, "state");
|
|
19
|
+
}
|
|
20
|
+
registerForStateChange(handler) {
|
|
21
|
+
this.register(this.stateChange, handler);
|
|
22
|
+
if (this.state !== void 0) {
|
|
23
|
+
try {
|
|
24
|
+
handler(this.state);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
this.unregister(this.stateChange, handler);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
unregisterFromStateChange(handler) {
|
|
32
|
+
this.unregister(this.stateChange, handler);
|
|
33
|
+
}
|
|
34
|
+
registerForNavigation(handler) {
|
|
35
|
+
this.register(this.navigation, handler);
|
|
36
|
+
}
|
|
37
|
+
unregisterFromNavigation(handler) {
|
|
38
|
+
this.unregister(this.navigation, handler);
|
|
39
|
+
}
|
|
40
|
+
registerForErrors(handler) {
|
|
41
|
+
this.register(this.error, handler);
|
|
42
|
+
}
|
|
43
|
+
unregisterFromErrors(handler) {
|
|
44
|
+
this.unregister(this.error, handler);
|
|
45
|
+
}
|
|
46
|
+
emitStateChange(data) {
|
|
47
|
+
this.state = data;
|
|
48
|
+
this.emit(this.stateChange, data);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Drops the retained state as well as notifying. Without clearing `state`,
|
|
52
|
+
* `registerForStateChange` would replay the pre-reset value to any presenter
|
|
53
|
+
* constructed afterwards.
|
|
54
|
+
*/
|
|
55
|
+
resetState() {
|
|
56
|
+
this.state = void 0;
|
|
57
|
+
this.emit(this.stateChange, void 0);
|
|
58
|
+
}
|
|
59
|
+
emitError(error) {
|
|
60
|
+
if (!this.events[this.error]?.length) {
|
|
61
|
+
console.error("Unhandled use case error:", error);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.emit(this.error, error);
|
|
65
|
+
}
|
|
66
|
+
emitNavigation(url) {
|
|
67
|
+
this.emit(this.navigation, url);
|
|
68
|
+
}
|
|
69
|
+
register(event, handler) {
|
|
70
|
+
if (!this.events[event]) {
|
|
71
|
+
this.events[event] = [];
|
|
72
|
+
}
|
|
73
|
+
this.events[event].push(handler);
|
|
74
|
+
}
|
|
75
|
+
unregister(event, handler) {
|
|
76
|
+
if (!this.events[event]) return;
|
|
77
|
+
this.events[event] = this.events[event].filter((h2) => h2 !== handler);
|
|
78
|
+
}
|
|
79
|
+
emit(event, data) {
|
|
80
|
+
if (!this.events[event]) return;
|
|
81
|
+
this.events[event].forEach((handler) => {
|
|
82
|
+
try {
|
|
83
|
+
handler(data);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error(`Error in ${event} handler:`, error);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
function createScope(initialState) {
|
|
91
|
+
const scope = {
|
|
92
|
+
state: initialState,
|
|
93
|
+
initialStatePromise: void 0,
|
|
94
|
+
runningUseCases: /* @__PURE__ */ new Map(),
|
|
95
|
+
emitter: new ConcreteEventEmitter(),
|
|
96
|
+
sources: /* @__PURE__ */ new Map(),
|
|
97
|
+
openMutationWindows: 0
|
|
98
|
+
};
|
|
99
|
+
return scope;
|
|
100
|
+
}
|
|
101
|
+
var browserScope = createScope();
|
|
102
|
+
var resolveScope = () => browserScope;
|
|
103
|
+
function currentScope() {
|
|
104
|
+
return resolveScope();
|
|
105
|
+
}
|
|
106
|
+
function setScopeResolver(resolver) {
|
|
107
|
+
resolveScope = resolver;
|
|
108
|
+
}
|
|
109
|
+
function onError(handler) {
|
|
110
|
+
const { emitter } = currentScope();
|
|
111
|
+
const wrapper = (data) => {
|
|
112
|
+
if (data instanceof Error) handler(data);
|
|
113
|
+
};
|
|
114
|
+
emitter.registerForErrors(wrapper);
|
|
115
|
+
return () => emitter.unregisterFromErrors(wrapper);
|
|
116
|
+
}
|
|
117
|
+
function onNavigation(handler) {
|
|
118
|
+
const { emitter } = currentScope();
|
|
119
|
+
const wrapper = (data) => {
|
|
120
|
+
if (typeof data === "string") handler(data);
|
|
121
|
+
};
|
|
122
|
+
emitter.registerForNavigation(wrapper);
|
|
123
|
+
return () => emitter.unregisterFromNavigation(wrapper);
|
|
124
|
+
}
|
|
125
|
+
function openMutationWindow() {
|
|
126
|
+
currentScope().openMutationWindows += 1;
|
|
127
|
+
}
|
|
128
|
+
function closeMutationWindow() {
|
|
129
|
+
const scope = currentScope();
|
|
130
|
+
scope.openMutationWindows = Math.max(0, scope.openMutationWindows - 1);
|
|
131
|
+
}
|
|
132
|
+
function isMutationWindowOpen() {
|
|
133
|
+
return currentScope().openMutationWindows > 0;
|
|
134
|
+
}
|
|
135
|
+
async function withMutationWindow(run) {
|
|
136
|
+
openMutationWindow();
|
|
137
|
+
try {
|
|
138
|
+
return await run();
|
|
139
|
+
} finally {
|
|
140
|
+
closeMutationWindow();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function assertMutationWindowOpen(operation) {
|
|
144
|
+
if (isMutationWindowOpen()) return;
|
|
145
|
+
throw new Error(
|
|
146
|
+
`[magic-use-case] ${operation} is only allowed inside a running use case.`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
var mutatingMapMethods = /* @__PURE__ */ new Set(["set", "delete", "clear"]);
|
|
150
|
+
var mutatingSetMethods = /* @__PURE__ */ new Set(["add", "delete", "clear"]);
|
|
151
|
+
var mutatingArrayMethods = /* @__PURE__ */ new Set([
|
|
152
|
+
"push",
|
|
153
|
+
"pop",
|
|
154
|
+
"shift",
|
|
155
|
+
"unshift",
|
|
156
|
+
"splice",
|
|
157
|
+
"sort",
|
|
158
|
+
"reverse",
|
|
159
|
+
"fill",
|
|
160
|
+
"copyWithin"
|
|
161
|
+
]);
|
|
162
|
+
var iteratorKeys = /* @__PURE__ */ new Set(["values", "entries", Symbol.iterator]);
|
|
163
|
+
var READONLY = {
|
|
164
|
+
marker: /* @__PURE__ */ Symbol("readonly"),
|
|
165
|
+
cache: /* @__PURE__ */ new WeakMap(),
|
|
166
|
+
label: "readonly ",
|
|
167
|
+
allows: () => false,
|
|
168
|
+
reject(action) {
|
|
169
|
+
throw new Error(`Cannot ${action} on readonly object`);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var USE_CASE = {
|
|
173
|
+
marker: /* @__PURE__ */ Symbol("useCaseWritable"),
|
|
174
|
+
cache: /* @__PURE__ */ new WeakMap(),
|
|
175
|
+
label: "",
|
|
176
|
+
allows: isMutationWindowOpen,
|
|
177
|
+
reject(action) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`[magic-use-case] Cannot ${action} outside a use case.
|
|
180
|
+
|
|
181
|
+
Application state may only be mutated from within a running use case, so that every change emits a state-change event and reaches the UI. Mutating it elsewhere would leave presenters showing stale data.
|
|
182
|
+
|
|
183
|
+
Move this write into a use case's runLogic().`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
function isObject(value) {
|
|
188
|
+
return value !== null && typeof value === "object";
|
|
189
|
+
}
|
|
190
|
+
function mustReturnRaw(target, prop) {
|
|
191
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, prop);
|
|
192
|
+
return descriptor !== void 0 && descriptor.configurable === false && descriptor.writable === false;
|
|
193
|
+
}
|
|
194
|
+
function wrap(value, policy) {
|
|
195
|
+
return isObject(value) ? proxyFor(value, policy) : value;
|
|
196
|
+
}
|
|
197
|
+
function createMapProxy(target, policy) {
|
|
198
|
+
return new Proxy(target, {
|
|
199
|
+
get(target2, prop, receiver) {
|
|
200
|
+
if (prop === policy.marker) return true;
|
|
201
|
+
if (typeof prop === "string" && mutatingMapMethods.has(prop)) {
|
|
202
|
+
if (!policy.allows()) return () => policy.reject(`call .${prop}() on ${policy.label}Map`);
|
|
203
|
+
const method = Reflect.get(target2, prop, target2);
|
|
204
|
+
return method.bind(target2);
|
|
205
|
+
}
|
|
206
|
+
if (prop === "get") {
|
|
207
|
+
return (key) => wrap(target2.get(key), policy);
|
|
208
|
+
}
|
|
209
|
+
if (prop === "forEach") {
|
|
210
|
+
return (cb) => target2.forEach((v2, k2) => cb(wrap(v2, policy), k2, receiver));
|
|
211
|
+
}
|
|
212
|
+
if (iteratorKeys.has(prop)) {
|
|
213
|
+
return function* () {
|
|
214
|
+
if (prop === "values") {
|
|
215
|
+
for (const v2 of target2.values()) yield wrap(v2, policy);
|
|
216
|
+
} else {
|
|
217
|
+
for (const [k2, v2] of target2.entries()) yield [k2, wrap(v2, policy)];
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const value = Reflect.get(target2, prop, target2);
|
|
222
|
+
if (prop === "constructor" || typeof value !== "function") return value;
|
|
223
|
+
return value.bind(target2);
|
|
224
|
+
},
|
|
225
|
+
set(target2, prop, value) {
|
|
226
|
+
if (!policy.allows()) policy.reject(`set property '${String(prop)}'`);
|
|
227
|
+
return Reflect.set(target2, prop, value);
|
|
228
|
+
},
|
|
229
|
+
deleteProperty(target2, prop) {
|
|
230
|
+
if (!policy.allows()) policy.reject(`delete property '${String(prop)}'`);
|
|
231
|
+
return Reflect.deleteProperty(target2, prop);
|
|
232
|
+
},
|
|
233
|
+
defineProperty(target2, prop, attributes) {
|
|
234
|
+
if (!policy.allows()) policy.reject(`define property '${String(prop)}'`);
|
|
235
|
+
return Reflect.defineProperty(target2, prop, attributes);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function createSetProxy(target, policy) {
|
|
240
|
+
return new Proxy(target, {
|
|
241
|
+
get(target2, prop, receiver) {
|
|
242
|
+
if (prop === policy.marker) return true;
|
|
243
|
+
if (typeof prop === "string" && mutatingSetMethods.has(prop)) {
|
|
244
|
+
if (!policy.allows()) return () => policy.reject(`call .${prop}() on ${policy.label}Set`);
|
|
245
|
+
const method = Reflect.get(target2, prop, target2);
|
|
246
|
+
return method.bind(target2);
|
|
247
|
+
}
|
|
248
|
+
if (iteratorKeys.has(prop)) {
|
|
249
|
+
return function* () {
|
|
250
|
+
for (const v2 of target2.values()) yield wrap(v2, policy);
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (prop === "forEach") {
|
|
254
|
+
return (cb) => target2.forEach((v2) => cb(wrap(v2, policy), wrap(v2, policy), receiver));
|
|
255
|
+
}
|
|
256
|
+
const value = Reflect.get(target2, prop, target2);
|
|
257
|
+
if (prop === "constructor" || typeof value !== "function") return value;
|
|
258
|
+
return value.bind(target2);
|
|
259
|
+
},
|
|
260
|
+
set(target2, prop, value) {
|
|
261
|
+
if (!policy.allows()) policy.reject(`set property '${String(prop)}'`);
|
|
262
|
+
return Reflect.set(target2, prop, value);
|
|
263
|
+
},
|
|
264
|
+
deleteProperty(target2, prop) {
|
|
265
|
+
if (!policy.allows()) policy.reject(`delete property '${String(prop)}'`);
|
|
266
|
+
return Reflect.deleteProperty(target2, prop);
|
|
267
|
+
},
|
|
268
|
+
defineProperty(target2, prop, attributes) {
|
|
269
|
+
if (!policy.allows()) policy.reject(`define property '${String(prop)}'`);
|
|
270
|
+
return Reflect.defineProperty(target2, prop, attributes);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function createObjectProxy(target, policy) {
|
|
275
|
+
return new Proxy(target, {
|
|
276
|
+
get(target2, prop) {
|
|
277
|
+
if (prop === policy.marker) return true;
|
|
278
|
+
if (Array.isArray(target2) && typeof prop === "string" && mutatingArrayMethods.has(prop)) {
|
|
279
|
+
if (!policy.allows()) return () => policy.reject(`call .${prop}() on ${policy.label}Array`);
|
|
280
|
+
const method = Reflect.get(target2, prop, target2);
|
|
281
|
+
return method.bind(target2);
|
|
282
|
+
}
|
|
283
|
+
const value = Reflect.get(target2, prop, target2);
|
|
284
|
+
if (typeof value === "function") return prop === "constructor" ? value : value.bind(target2);
|
|
285
|
+
if (!isObject(value) || mustReturnRaw(target2, prop)) return value;
|
|
286
|
+
return proxyFor(value, policy);
|
|
287
|
+
},
|
|
288
|
+
set(target2, prop, value) {
|
|
289
|
+
if (!policy.allows()) policy.reject(`set property '${String(prop)}'`);
|
|
290
|
+
return Reflect.set(target2, prop, value);
|
|
291
|
+
},
|
|
292
|
+
deleteProperty(target2, prop) {
|
|
293
|
+
if (!policy.allows()) policy.reject(`delete property '${String(prop)}'`);
|
|
294
|
+
return Reflect.deleteProperty(target2, prop);
|
|
295
|
+
},
|
|
296
|
+
defineProperty(target2, prop, attributes) {
|
|
297
|
+
if (!policy.allows()) policy.reject(`define property '${String(prop)}'`);
|
|
298
|
+
return Reflect.defineProperty(target2, prop, attributes);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function proxyFor(obj, policy) {
|
|
303
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
304
|
+
if (obj[policy.marker]) return obj;
|
|
305
|
+
const cached = policy.cache.get(obj);
|
|
306
|
+
if (cached) return cached;
|
|
307
|
+
let proxy;
|
|
308
|
+
if (obj instanceof Map) {
|
|
309
|
+
proxy = createMapProxy(obj, policy);
|
|
310
|
+
} else if (obj instanceof Set) {
|
|
311
|
+
proxy = createSetProxy(obj, policy);
|
|
312
|
+
} else {
|
|
313
|
+
proxy = createObjectProxy(obj, policy);
|
|
314
|
+
}
|
|
315
|
+
policy.cache.set(obj, proxy);
|
|
316
|
+
return proxy;
|
|
317
|
+
}
|
|
318
|
+
function deepReadonly(obj) {
|
|
319
|
+
return proxyFor(obj, READONLY);
|
|
320
|
+
}
|
|
321
|
+
function useCaseWritable(obj) {
|
|
322
|
+
return proxyFor(obj, USE_CASE);
|
|
323
|
+
}
|
|
324
|
+
function cloneCollection(value, seen) {
|
|
325
|
+
if (value instanceof Map) {
|
|
326
|
+
const copy = /* @__PURE__ */ new Map();
|
|
327
|
+
seen.set(value, copy);
|
|
328
|
+
for (const [k2, v2] of value.entries()) {
|
|
329
|
+
copy.set(deepClone(k2, seen), deepClone(v2, seen));
|
|
330
|
+
}
|
|
331
|
+
return copy;
|
|
332
|
+
}
|
|
333
|
+
if (value instanceof Set) {
|
|
334
|
+
const copy = /* @__PURE__ */ new Set();
|
|
335
|
+
seen.set(value, copy);
|
|
336
|
+
for (const v2 of value.values()) {
|
|
337
|
+
copy.add(deepClone(v2, seen));
|
|
338
|
+
}
|
|
339
|
+
return copy;
|
|
340
|
+
}
|
|
341
|
+
if (Array.isArray(value)) {
|
|
342
|
+
const copy = [];
|
|
343
|
+
seen.set(value, copy);
|
|
344
|
+
for (const v2 of value) copy.push(deepClone(v2, seen));
|
|
345
|
+
return copy;
|
|
346
|
+
}
|
|
347
|
+
return void 0;
|
|
348
|
+
}
|
|
349
|
+
function deepClone(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
350
|
+
if (value === null || typeof value !== "object") return value;
|
|
351
|
+
const asObject = value;
|
|
352
|
+
const existing = seen.get(asObject);
|
|
353
|
+
if (existing !== void 0) return existing;
|
|
354
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
355
|
+
if (value instanceof RegExp) return new RegExp(value.source, value.flags);
|
|
356
|
+
const collection = cloneCollection(asObject, seen);
|
|
357
|
+
if (collection !== void 0) return collection;
|
|
358
|
+
const copy = Object.create(Object.getPrototypeOf(asObject));
|
|
359
|
+
seen.set(asObject, copy);
|
|
360
|
+
for (const key of Reflect.ownKeys(asObject)) {
|
|
361
|
+
const descriptor = Object.getOwnPropertyDescriptor(asObject, key);
|
|
362
|
+
if (!descriptor) continue;
|
|
363
|
+
if ("value" in descriptor) {
|
|
364
|
+
Object.defineProperty(copy, key, {
|
|
365
|
+
...descriptor,
|
|
366
|
+
value: deepClone(descriptor.value, seen)
|
|
367
|
+
});
|
|
368
|
+
} else {
|
|
369
|
+
Object.defineProperty(copy, key, descriptor);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (Object.isFrozen(asObject)) Object.freeze(copy);
|
|
373
|
+
else if (Object.isSealed(asObject)) Object.seal(copy);
|
|
374
|
+
return copy;
|
|
375
|
+
}
|
|
376
|
+
var entryPoints = /* @__PURE__ */ new WeakSet();
|
|
377
|
+
function createUseCase(UseCaseClass, onProgress) {
|
|
378
|
+
const useCase = new UseCaseClass(onProgress);
|
|
379
|
+
entryPoints.add(useCase);
|
|
380
|
+
return useCase;
|
|
381
|
+
}
|
|
382
|
+
var UseCase = class {
|
|
383
|
+
constructor(onProgress) {
|
|
384
|
+
__publicField2(this, "onProgress");
|
|
385
|
+
this.onProgress = onProgress;
|
|
386
|
+
}
|
|
387
|
+
/** Resolved per emit, so an execution always announces on the scope it is running in. */
|
|
388
|
+
get eventEmitter() {
|
|
389
|
+
return currentScope().emitter;
|
|
390
|
+
}
|
|
391
|
+
execute(params) {
|
|
392
|
+
return this.run(params, { hasCaller: !entryPoints.has(this) });
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Starts a use case that this one does not wait for. It runs on its own: the
|
|
396
|
+
* caller returns and announces its own changes immediately, and the detached
|
|
397
|
+
* run announces its own when it lands. Nobody is waiting on it, so its failure
|
|
398
|
+
* is reported rather than thrown.
|
|
399
|
+
*/
|
|
400
|
+
detach(UseCaseClass, params) {
|
|
401
|
+
void createUseCase(UseCaseClass).run(params, { hasCaller: false }).catch(() => void 0);
|
|
402
|
+
}
|
|
403
|
+
async run(params, run) {
|
|
404
|
+
const scope = currentScope();
|
|
405
|
+
if (scope.state === void 0) {
|
|
406
|
+
if (!scope.initialStatePromise) {
|
|
407
|
+
scope.initialStatePromise = this.initializeState().then((state) => {
|
|
408
|
+
scope.state = deepClone(state);
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
await scope.initialStatePromise;
|
|
413
|
+
} catch (error) {
|
|
414
|
+
this.reportUnlessCallerWill(error, run);
|
|
415
|
+
throw error;
|
|
416
|
+
} finally {
|
|
417
|
+
scope.initialStatePromise = void 0;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const classMap = scope.runningUseCases.get(this.constructor) ?? /* @__PURE__ */ new Map();
|
|
421
|
+
scope.runningUseCases.set(this.constructor, classMap);
|
|
422
|
+
const paramsKey = JSON.stringify(params);
|
|
423
|
+
if (classMap.has(paramsKey)) {
|
|
424
|
+
await classMap.get(paramsKey);
|
|
425
|
+
this.announceUnlessCallerWill(run);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const executionPromise = this.runWithUpdate(() => this.runLogic(params), run);
|
|
429
|
+
classMap.set(paramsKey, executionPromise);
|
|
430
|
+
try {
|
|
431
|
+
await executionPromise;
|
|
432
|
+
} finally {
|
|
433
|
+
classMap.delete(paramsKey);
|
|
434
|
+
if (classMap.size === 0) {
|
|
435
|
+
scope.runningUseCases.delete(this.constructor);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
getState() {
|
|
440
|
+
return useCaseWritable(currentScope().state);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Clears application state so the next execution bootstraps it again.
|
|
444
|
+
*
|
|
445
|
+
* All four pieces of module state go together: the state itself, the
|
|
446
|
+
* emitter's retained copy, the in-flight dedup map, and any bootstrap in
|
|
447
|
+
* flight. Leaving any one behind resurrects the old state.
|
|
448
|
+
*/
|
|
449
|
+
resetAppState() {
|
|
450
|
+
assertMutationWindowOpen("Resetting application state");
|
|
451
|
+
const scope = currentScope();
|
|
452
|
+
scope.state = void 0;
|
|
453
|
+
scope.runningUseCases.clear();
|
|
454
|
+
scope.initialStatePromise = void 0;
|
|
455
|
+
this.eventEmitter.resetState();
|
|
456
|
+
}
|
|
457
|
+
navigate(url) {
|
|
458
|
+
this.eventEmitter.emitNavigation(url);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Announces a failure the screen should hear about, for a use case that
|
|
462
|
+
* handles the failure rather than rethrowing it. Anything rethrown is
|
|
463
|
+
* reported on its way out and must not be reported here as well.
|
|
464
|
+
*/
|
|
465
|
+
report(error) {
|
|
466
|
+
this.eventEmitter.emitError(error);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* A run announces its work unless somebody is waiting on it. A nested run
|
|
470
|
+
* stays silent and its caller announces everything written beneath it, once.
|
|
471
|
+
*
|
|
472
|
+
* Whether a run has a caller is known when it starts, which is what keeps an
|
|
473
|
+
* unrelated run finishing at the same moment from being swallowed: two flows
|
|
474
|
+
* a screen started independently each reach it on their own.
|
|
475
|
+
*/
|
|
476
|
+
async runWithUpdate(functionToRun, run) {
|
|
477
|
+
try {
|
|
478
|
+
await withMutationWindow(functionToRun);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
this.reportUnlessCallerWill(error, run);
|
|
481
|
+
throw error;
|
|
482
|
+
} finally {
|
|
483
|
+
this.announceUnlessCallerWill(run);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
announceUnlessCallerWill(run) {
|
|
487
|
+
if (run.hasCaller) return;
|
|
488
|
+
this.eventEmitter.emitStateChange(deepReadonly(currentScope().state));
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Counting who is running cannot tell a caller from an unrelated run that
|
|
492
|
+
* happens to overlap, and guessing wrong loses the failure entirely. Whether
|
|
493
|
+
* this run has a caller is known when it starts, so that is what decides.
|
|
494
|
+
*/
|
|
495
|
+
reportUnlessCallerWill(error, run) {
|
|
496
|
+
if (run.hasCaller) return;
|
|
497
|
+
this.report(error instanceof Error ? error : new Error(String(error)));
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
function acquireSource(scope, presentation) {
|
|
501
|
+
const existing = scope.sources.get(presentation);
|
|
502
|
+
if (existing) {
|
|
503
|
+
existing.presenters += 1;
|
|
504
|
+
return existing;
|
|
505
|
+
}
|
|
506
|
+
const source = {
|
|
507
|
+
model: void 0,
|
|
508
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
509
|
+
presenters: 1,
|
|
510
|
+
handler: (state) => {
|
|
511
|
+
if (state === void 0) {
|
|
512
|
+
source.model = void 0;
|
|
513
|
+
} else {
|
|
514
|
+
try {
|
|
515
|
+
source.model = presentation(state);
|
|
516
|
+
} catch (error) {
|
|
517
|
+
console.error("[magic-use-case] A presentation threw; its model is left empty.", error);
|
|
518
|
+
source.model = void 0;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
source.listeners.forEach((listener) => listener(source.model));
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
scope.sources.set(presentation, source);
|
|
525
|
+
scope.emitter.registerForStateChange(source.handler);
|
|
526
|
+
if (source.model === void 0 && scope.state !== void 0) {
|
|
527
|
+
source.handler(deepReadonly(scope.state));
|
|
528
|
+
}
|
|
529
|
+
return source;
|
|
530
|
+
}
|
|
531
|
+
function releaseSource(scope, presentation) {
|
|
532
|
+
const source = scope.sources.get(presentation);
|
|
533
|
+
if (!source) return;
|
|
534
|
+
source.presenters -= 1;
|
|
535
|
+
if (source.presenters > 0) return;
|
|
536
|
+
scope.emitter.unregisterFromStateChange(source.handler);
|
|
537
|
+
scope.sources.delete(presentation);
|
|
538
|
+
}
|
|
539
|
+
var Presenter = class {
|
|
540
|
+
constructor(presentation) {
|
|
541
|
+
__publicField2(this, "model");
|
|
542
|
+
__publicField2(this, "subscribers", /* @__PURE__ */ new Set());
|
|
543
|
+
__publicField2(this, "presentation");
|
|
544
|
+
__publicField2(this, "source");
|
|
545
|
+
__publicField2(this, "onModel");
|
|
546
|
+
__publicField2(this, "scope");
|
|
547
|
+
this.presentation = presentation;
|
|
548
|
+
this.onModel = (model) => {
|
|
549
|
+
this.model = model;
|
|
550
|
+
this.notifySubscribers();
|
|
551
|
+
};
|
|
552
|
+
this.scope = currentScope();
|
|
553
|
+
this.source = acquireSource(this.scope, presentation);
|
|
554
|
+
this.model = this.source.model;
|
|
555
|
+
this.source.listeners.add(this.onModel);
|
|
556
|
+
}
|
|
557
|
+
subscribe(listener) {
|
|
558
|
+
this.subscribers.add(listener);
|
|
559
|
+
listener(this.model);
|
|
560
|
+
}
|
|
561
|
+
unsubscribe(listener) {
|
|
562
|
+
this.subscribers.delete(listener);
|
|
563
|
+
}
|
|
564
|
+
notifySubscribers() {
|
|
565
|
+
this.subscribers.forEach((listener) => listener(this.model));
|
|
566
|
+
}
|
|
567
|
+
destroy() {
|
|
568
|
+
if (!this.source) return;
|
|
569
|
+
this.source.listeners.delete(this.onModel);
|
|
570
|
+
this.source = void 0;
|
|
571
|
+
releaseSource(this.scope, this.presentation);
|
|
572
|
+
this.subscribers.clear();
|
|
573
|
+
this.model = void 0;
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
function currentModelFor(presentation) {
|
|
577
|
+
const { state } = currentScope();
|
|
578
|
+
if (state === void 0) {
|
|
579
|
+
return void 0;
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
return presentation(deepReadonly(state));
|
|
583
|
+
} catch (error) {
|
|
584
|
+
console.error("[magic-use-case] A presentation threw; its model is left empty.", error);
|
|
585
|
+
return void 0;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
var M = ((i) => (i[i.AggregateError = 1] = "AggregateError", i[i.ArrowFunction = 2] = "ArrowFunction", i[i.ErrorPrototypeStack = 4] = "ErrorPrototypeStack", i[i.ObjectAssign = 8] = "ObjectAssign", i[i.BigIntTypedArray = 16] = "BigIntTypedArray", i[i.RegExp = 32] = "RegExp", i))(M || {});
|
|
589
|
+
var L = "__SEROVAL_REFS__";
|
|
590
|
+
var U = /* @__PURE__ */ new Map();
|
|
591
|
+
typeof globalThis != "undefined" ? Object.defineProperty(globalThis, L, { value: U, configurable: true, writable: false, enumerable: false }) : typeof window != "undefined" ? Object.defineProperty(window, L, { value: U, configurable: true, writable: false, enumerable: false }) : typeof self != "undefined" ? Object.defineProperty(self, L, { value: U, configurable: true, writable: false, enumerable: false }) : typeof global != "undefined" && Object.defineProperty(global, L, { value: U, configurable: true, writable: false, enumerable: false });
|
|
592
|
+
var ee = () => {
|
|
593
|
+
let e = { p: 0, s: 0, f: 0 };
|
|
594
|
+
return e.p = new Promise((r, t) => {
|
|
595
|
+
e.s = r, e.f = t;
|
|
596
|
+
}), e;
|
|
597
|
+
};
|
|
598
|
+
var In = (e, r) => {
|
|
599
|
+
e.s(r), e.p.s = 1, e.p.v = r;
|
|
600
|
+
};
|
|
601
|
+
var Rn = (e, r) => {
|
|
602
|
+
e.f(r), e.p.s = 2, e.p.v = r;
|
|
603
|
+
};
|
|
604
|
+
ee.toString();
|
|
605
|
+
In.toString();
|
|
606
|
+
Rn.toString();
|
|
607
|
+
var wr = (e) => {
|
|
608
|
+
let r = atob(e), t = r.length, n = new Uint8Array(t);
|
|
609
|
+
for (let a = 0; a < t; a++) n[a] = r.charCodeAt(a);
|
|
610
|
+
return n.buffer;
|
|
611
|
+
};
|
|
612
|
+
wr.toString();
|
|
613
|
+
var oe = ((t) => (t[t.Vanilla = 1] = "Vanilla", t[t.Cross = 2] = "Cross", t))(oe || {});
|
|
614
|
+
var Ro = () => T;
|
|
615
|
+
Ro.toString();
|
|
616
|
+
var STATE_GLOBAL = "__MAGIC_USE_CASE_STATE__";
|
|
617
|
+
function transferredState() {
|
|
618
|
+
return globalThis[STATE_GLOBAL];
|
|
619
|
+
}
|
|
620
|
+
function adoptSerializedState() {
|
|
621
|
+
const state = transferredState();
|
|
622
|
+
if (state === void 0) {
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
const scope = createScope(state);
|
|
626
|
+
setScopeResolver(() => scope);
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
function useReconciledStore(initialState) {
|
|
630
|
+
const [state, setState] = useState(initialState);
|
|
631
|
+
const updateState = useCallback((newData) => {
|
|
632
|
+
setState((prevState) => {
|
|
633
|
+
const resolvedState = typeof newData === "function" ? newData(prevState) : newData;
|
|
634
|
+
if (prevState === void 0) {
|
|
635
|
+
return resolvedState;
|
|
636
|
+
}
|
|
637
|
+
if (deepEqual(prevState, resolvedState)) {
|
|
638
|
+
return prevState;
|
|
639
|
+
} else {
|
|
640
|
+
return cloneWithNewReferences(resolvedState, prevState);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
}, []);
|
|
644
|
+
return [state, updateState];
|
|
645
|
+
}
|
|
646
|
+
function deepEqual(obj1, obj2) {
|
|
647
|
+
if (obj1 == null || obj2 == null || typeof obj1 !== "object" || typeof obj2 !== "object") return obj1 === obj2;
|
|
648
|
+
if (obj1 instanceof Date && obj2 instanceof Date) return obj1.getTime() === obj2.getTime();
|
|
649
|
+
if (obj1 instanceof Set && obj2 instanceof Set) {
|
|
650
|
+
if (obj1.size !== obj2.size) return false;
|
|
651
|
+
const arr2 = [...obj2];
|
|
652
|
+
return [...obj1].every((val) => arr2.some((val2) => deepEqual(val, val2)));
|
|
653
|
+
}
|
|
654
|
+
if (obj1 instanceof Map && obj2 instanceof Map) {
|
|
655
|
+
if (obj1.size !== obj2.size) return false;
|
|
656
|
+
return [...obj1.keys()].every((key) => obj2.has(key) && deepEqual(obj1.get(key), obj2.get(key)));
|
|
657
|
+
}
|
|
658
|
+
if (Object.getPrototypeOf(obj1) !== Object.getPrototypeOf(obj2)) return false;
|
|
659
|
+
const rec1 = obj1;
|
|
660
|
+
const rec2 = obj2;
|
|
661
|
+
const keys1 = Object.keys(rec1);
|
|
662
|
+
const keys2 = Object.keys(rec2);
|
|
663
|
+
if (keys1.length !== keys2.length) return false;
|
|
664
|
+
return keys1.every((key) => key in rec2 && deepEqual(rec1[key], rec2[key]));
|
|
665
|
+
}
|
|
666
|
+
function cloneWithNewReferences(obj, referenceObj) {
|
|
667
|
+
if (Array.isArray(obj) && Array.isArray(referenceObj)) {
|
|
668
|
+
return obj.map(
|
|
669
|
+
(item, index) => deepEqual(item, referenceObj[index]) ? referenceObj[index] : cloneWithNewReferences(item, referenceObj[index])
|
|
670
|
+
);
|
|
671
|
+
} else if (obj instanceof Map && referenceObj instanceof Map) {
|
|
672
|
+
const newMap = /* @__PURE__ */ new Map();
|
|
673
|
+
let hasChanges = obj.size !== referenceObj.size;
|
|
674
|
+
for (const [key, value] of obj) {
|
|
675
|
+
const refValue = referenceObj.get(key);
|
|
676
|
+
if (deepEqual(value, refValue)) {
|
|
677
|
+
newMap.set(key, refValue);
|
|
678
|
+
} else {
|
|
679
|
+
newMap.set(key, cloneWithNewReferences(value, refValue));
|
|
680
|
+
hasChanges = true;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return hasChanges ? newMap : obj;
|
|
684
|
+
} else if (obj instanceof Set && referenceObj instanceof Set) {
|
|
685
|
+
const refArr = [...referenceObj];
|
|
686
|
+
const newArr = [...obj].map((val) => {
|
|
687
|
+
const match = refArr.find((ref) => deepEqual(val, ref));
|
|
688
|
+
return match !== void 0 ? match : val;
|
|
689
|
+
});
|
|
690
|
+
const hasChanges = obj.size !== referenceObj.size || newArr.some((v2, i) => v2 !== [...obj][i]);
|
|
691
|
+
return hasChanges ? new Set(newArr) : obj;
|
|
692
|
+
} else if (obj && typeof obj === "object" && obj.constructor === Object) {
|
|
693
|
+
const newObj = Object.create(Object.getPrototypeOf(obj));
|
|
694
|
+
let hasChanges = false;
|
|
695
|
+
for (const key in obj) {
|
|
696
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
697
|
+
const clonedValue = referenceObj && deepEqual(obj[key], referenceObj[key]) ? referenceObj[key] : cloneWithNewReferences(obj[key], referenceObj ? referenceObj[key] : void 0);
|
|
698
|
+
if (clonedValue !== obj[key]) hasChanges = true;
|
|
699
|
+
newObj[key] = clonedValue;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return hasChanges ? newObj : obj;
|
|
703
|
+
}
|
|
704
|
+
return obj;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/hooks/usePresenter.ts
|
|
708
|
+
function usePresenter(presentation) {
|
|
709
|
+
const presentationRef = useRef(presentation);
|
|
710
|
+
const [model, setModel] = useReconciledStore(() => currentModelFor(presentationRef.current));
|
|
711
|
+
const updateModel = useCallback(
|
|
712
|
+
(newModel) => {
|
|
713
|
+
setModel(newModel);
|
|
714
|
+
},
|
|
715
|
+
[setModel]
|
|
716
|
+
);
|
|
717
|
+
useEffect(() => {
|
|
718
|
+
const presenter = new Presenter(presentationRef.current);
|
|
719
|
+
presenter.subscribe(updateModel);
|
|
720
|
+
return () => {
|
|
721
|
+
presenter.unsubscribe(updateModel);
|
|
722
|
+
presenter.destroy();
|
|
723
|
+
};
|
|
724
|
+
}, [updateModel]);
|
|
725
|
+
return { model: model === void 0 ? void 0 : deepReadonly(model) };
|
|
726
|
+
}
|
|
727
|
+
function useUseCase(UseCaseClass) {
|
|
728
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
729
|
+
const [progress, setProgress] = useState(0);
|
|
730
|
+
const [didSucceed, setDidSucceed] = useState(false);
|
|
731
|
+
const useCase = useMemo(() => createUseCase(UseCaseClass, setProgress), [UseCaseClass]);
|
|
732
|
+
const execute = useCallback(
|
|
733
|
+
async (params) => {
|
|
734
|
+
setIsLoading(true);
|
|
735
|
+
setDidSucceed(false);
|
|
736
|
+
try {
|
|
737
|
+
await useCase.execute(params);
|
|
738
|
+
setDidSucceed(true);
|
|
739
|
+
} catch {
|
|
740
|
+
setDidSucceed(false);
|
|
741
|
+
} finally {
|
|
742
|
+
setIsLoading(false);
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
[useCase]
|
|
746
|
+
);
|
|
747
|
+
return { execute, isLoading, didSucceed, progress };
|
|
748
|
+
}
|
|
749
|
+
var ErrorBoundary = class extends Component {
|
|
750
|
+
constructor() {
|
|
751
|
+
super(...arguments);
|
|
752
|
+
__publicField(this, "state", {});
|
|
753
|
+
}
|
|
754
|
+
static getDerivedStateFromError(error) {
|
|
755
|
+
return { error };
|
|
756
|
+
}
|
|
757
|
+
componentDidCatch(error) {
|
|
758
|
+
this.props.onError(error);
|
|
759
|
+
}
|
|
760
|
+
render() {
|
|
761
|
+
const error = this.props.error || this.state.error;
|
|
762
|
+
if (error) {
|
|
763
|
+
return this.props.renderErrorDialog({
|
|
764
|
+
error,
|
|
765
|
+
onClose: () => {
|
|
766
|
+
this.setState({ error: void 0 });
|
|
767
|
+
this.props.onClose();
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
return this.props.children;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
var ErrorHandler = (props) => {
|
|
775
|
+
const [error, setError] = useState();
|
|
776
|
+
const propsRef = useRef(props);
|
|
777
|
+
propsRef.current = props;
|
|
778
|
+
const reportError = useCallback((error2) => {
|
|
779
|
+
const shouldReport = propsRef.current.onWillReportError(error2);
|
|
780
|
+
if (shouldReport) {
|
|
781
|
+
setError(error2);
|
|
782
|
+
propsRef.current.onDidReportError?.(error2);
|
|
783
|
+
}
|
|
784
|
+
}, []);
|
|
785
|
+
const clearError = () => {
|
|
786
|
+
setError(void 0);
|
|
787
|
+
};
|
|
788
|
+
useEffect(() => {
|
|
789
|
+
return onError(reportError);
|
|
790
|
+
}, [reportError]);
|
|
791
|
+
return /* @__PURE__ */ jsx(
|
|
792
|
+
ErrorBoundary,
|
|
793
|
+
{
|
|
794
|
+
onError: reportError,
|
|
795
|
+
renderErrorDialog: props.renderErrorDialog,
|
|
796
|
+
error,
|
|
797
|
+
onClose: clearError,
|
|
798
|
+
children: /* @__PURE__ */ jsx(Fragment, { children: props.children })
|
|
799
|
+
}
|
|
800
|
+
);
|
|
801
|
+
};
|
|
802
|
+
var Navigator = (props) => {
|
|
803
|
+
useEffect(() => {
|
|
804
|
+
return onNavigation((url) => {
|
|
805
|
+
if (url) {
|
|
806
|
+
props.onNavigate?.(url);
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
}, [props.onNavigate]);
|
|
810
|
+
return null;
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
// src/index.ts
|
|
814
|
+
adoptSerializedState();
|
|
815
|
+
function usePresenter2(presentation) {
|
|
816
|
+
return usePresenter(presentation);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
export { ErrorHandler, Navigator, Presenter, UseCase, createScope, onError, onNavigation, setScopeResolver, usePresenter2 as usePresenter, useUseCase };
|