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