effectweb 0.3.1 → 0.4.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/README.md +35 -4
- package/dist/AsyncContent.d.ts +1 -1
- package/dist/AsyncContent.js +18 -35
- package/dist/cache.d.ts +2 -2
- package/dist/cache.js +7 -3
- package/dist/collection.d.ts +1 -1
- package/dist/collection.js +1 -3
- package/dist/component.js +5 -2
- package/dist/dom.d.ts +26 -23
- package/dist/dom.js +150 -119
- package/dist/effectEvent.d.ts +3 -3
- package/dist/effectEvent.js +27 -39
- package/dist/form.d.ts +2 -1
- package/dist/http.d.ts +2 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/infinite-query.d.ts +1 -1
- package/dist/infinite-query.js +1 -1
- package/dist/jsx-runtime.d.ts +6 -0
- package/dist/jsx-runtime.js +14 -1
- package/dist/jsx.d.ts +2 -1
- package/dist/keyed-tasks.d.ts +5 -4
- package/dist/keyed-tasks.js +27 -22
- package/dist/lazy.d.ts +2 -1
- package/dist/lazy.js +13 -8
- package/dist/load.d.ts +1 -1
- package/dist/load.js +1 -1
- package/dist/mount.d.ts +12 -3
- package/dist/mount.js +63 -20
- package/dist/owner.d.ts +8 -6
- package/dist/owner.js +22 -40
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +2 -1
- package/dist/program.d.ts +7 -5
- package/dist/program.js +33 -20
- package/dist/query-internals.d.ts +1 -1
- package/dist/query.d.ts +1 -1
- package/dist/query.js +0 -2
- package/dist/resource.js +3 -1
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.js +3 -1
- package/dist/session.js +10 -3
- package/dist/settlement.d.ts +2 -1
- package/dist/settlement.js +13 -8
- package/dist/snapshot.d.ts +1 -1
- package/dist/snapshot.js +10 -12
- package/dist/task.d.ts +1 -1
- package/dist/task.js +3 -1
- package/dist/tasks.d.ts +1 -1
- package/dist/tasks.js +2 -1
- package/dist/testing.d.ts +5 -4
- package/dist/testing.js +4 -2
- package/package.json +1 -1
- package/dist/intrinsic.d.ts +0 -2
- package/dist/intrinsic.js +0 -120
- package/dist/slotIdentity.d.ts +0 -1
- package/dist/slotIdentity.js +0 -2
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { patchModel } from './state.js';
|
|
|
6
6
|
export { defineActions, type ActionMessage } from './actions.js';
|
|
7
7
|
export { taskComponent, taskControls, type TaskModel, type TaskMessage } from './task.js';
|
|
8
8
|
export { effectEvent } from './effectEvent.js';
|
|
9
|
-
export { mountView, view, ViewBinding, type View } from './dom.js';
|
|
9
|
+
export { mountView, view, list, ViewBinding, type View } from './dom.js';
|
|
10
10
|
export { lazyView, type LazyViewOptions } from './lazy.js';
|
|
11
11
|
export type { JSX } from './jsx.js';
|
|
12
12
|
export { Portal, domBinding, domMount, type DomMount, type PortalProps } from './mount.js';
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { patchModel } from './state.js';
|
|
|
5
5
|
export { defineActions } from './actions.js';
|
|
6
6
|
export { taskComponent, taskControls } from './task.js';
|
|
7
7
|
export { effectEvent } from './effectEvent.js';
|
|
8
|
-
export { mountView, view, ViewBinding } from './dom.js';
|
|
8
|
+
export { mountView, view, list, ViewBinding } from './dom.js';
|
|
9
9
|
export { lazyView } from './lazy.js';
|
|
10
10
|
export { Portal, domBinding, domMount } from './mount.js';
|
|
11
11
|
export { pages, } from './pages.js';
|
package/dist/infinite-query.d.ts
CHANGED
package/dist/infinite-query.js
CHANGED
package/dist/jsx-runtime.d.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
1
|
export type { JSX } from './jsx.js';
|
|
2
|
+
import type { JSX } from './jsx.js';
|
|
3
|
+
export declare function Fragment(props: {
|
|
4
|
+
readonly children?: JSX.Element;
|
|
5
|
+
}): JSX.Element;
|
|
6
|
+
export declare function jsx(type: string | ((props: never) => JSX.Element), props: Readonly<Record<string, unknown>>): JSX.Element;
|
|
7
|
+
export { jsx as jsxs, jsx as jsxDEV };
|
package/dist/jsx-runtime.js
CHANGED
|
@@ -1 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { markup } from './dom.js';
|
|
2
|
+
export function Fragment(props) {
|
|
3
|
+
return props.children;
|
|
4
|
+
}
|
|
5
|
+
const tags = new Map();
|
|
6
|
+
export function jsx(type, props) {
|
|
7
|
+
if (typeof type !== 'string')
|
|
8
|
+
return type(props);
|
|
9
|
+
let render = tags.get(type);
|
|
10
|
+
if (!render)
|
|
11
|
+
tags.set(type, (render = markup(type)));
|
|
12
|
+
return render(props);
|
|
13
|
+
}
|
|
14
|
+
export { jsx as jsxs, jsx as jsxDEV };
|
package/dist/jsx.d.ts
CHANGED
|
@@ -16,10 +16,11 @@ export declare namespace JSX {
|
|
|
16
16
|
export interface ElementArray extends ReadonlyArray<Element>, SnapshotOpaque {
|
|
17
17
|
}
|
|
18
18
|
export type Element = string | number | boolean | null | undefined | CompiledContent | ElementArray;
|
|
19
|
+
export type EventResult = void | boolean | EffectEventRequest;
|
|
19
20
|
export type EventHandler<T, E extends Event> = (event: E & {
|
|
20
21
|
currentTarget: T;
|
|
21
22
|
target: EventTarget & globalThis.Element;
|
|
22
|
-
}) =>
|
|
23
|
+
}) => EventResult;
|
|
23
24
|
export type EventHandlerUnion<T, E extends Event> = EventHandler<T, E>;
|
|
24
25
|
export type CSSProperties = {
|
|
25
26
|
[property: string]: string | number | undefined | null;
|
package/dist/keyed-tasks.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Cause from 'effect/Cause';
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
2
3
|
import { type TaskPolicy } from './program.js';
|
|
3
4
|
import type { DisposableOwner } from './owner.js';
|
|
4
5
|
import { type UiRuntime } from './runtime.js';
|
|
@@ -13,7 +14,7 @@ export type TaskOutcome<A, E> = {
|
|
|
13
14
|
readonly _tag: 'Cancelled' | 'Superseded' | 'Dropped';
|
|
14
15
|
};
|
|
15
16
|
export interface TaskHandle<A, E> {
|
|
16
|
-
readonly outcome:
|
|
17
|
+
readonly outcome: Effect.Effect<TaskOutcome<A, E>>;
|
|
17
18
|
}
|
|
18
19
|
/** Per-entity serialization. Inputs are captured now; run reads services when work starts. */
|
|
19
20
|
export declare function keyedTasks<Key, Input, A, E = never, R = never>(owner: DisposableOwner, definition: {
|
|
@@ -23,7 +24,7 @@ export declare function keyedTasks<Key, Input, A, E = never, R = never>(owner: D
|
|
|
23
24
|
}, ...provided: [R] extends [never] ? [runtime?: UiRuntime<R>] : [runtime: UiRuntime<R>]): {
|
|
24
25
|
submit(key: Key, input: Input | Snapshot<Input>): TaskHandle<A, E>;
|
|
25
26
|
cancel(key: Key): void;
|
|
26
|
-
drain(...selected: [] | [Key]):
|
|
27
|
-
close: () =>
|
|
27
|
+
drain(...selected: [] | [Key]): Effect.Effect<void>;
|
|
28
|
+
close: () => Effect.Effect<void, never, never>;
|
|
28
29
|
dispose: () => void;
|
|
29
30
|
};
|
package/dist/keyed-tasks.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Cause from 'effect/Cause';
|
|
2
|
+
import * as Deferred from 'effect/Deferred';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
4
|
+
import * as Exit from 'effect/Exit';
|
|
2
5
|
import { commandSlot, program } from './program.js';
|
|
3
6
|
import { defaultUiRuntime } from './runtime.js';
|
|
4
7
|
import { reportError, reportSafely } from './errors.js';
|
|
@@ -27,39 +30,42 @@ export function keyedTasks(owner, definition, ...provided) {
|
|
|
27
30
|
for (const entry of entries.values())
|
|
28
31
|
entry.source.dispose();
|
|
29
32
|
};
|
|
30
|
-
const close =
|
|
33
|
+
const close = () => Effect.gen(function* () {
|
|
31
34
|
dispose();
|
|
32
|
-
|
|
35
|
+
yield* Effect.all([...entries.values()].map((entry) => entry.source.close()), { concurrency: 'unbounded' });
|
|
33
36
|
entries.clear();
|
|
34
|
-
};
|
|
37
|
+
});
|
|
35
38
|
owner.own({ dispose, close });
|
|
36
39
|
return {
|
|
37
40
|
submit(key, input) {
|
|
38
41
|
if (disposed || owner.disposed)
|
|
39
|
-
return { outcome:
|
|
42
|
+
return { outcome: Effect.succeed(Object.freeze({ _tag: 'Cancelled' })) };
|
|
40
43
|
const captured = protectSnapshot(input);
|
|
41
44
|
const entry = get(key);
|
|
42
45
|
entry.version++;
|
|
43
|
-
|
|
44
|
-
const outcome =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
const result = Deferred.makeUnsafe();
|
|
47
|
+
const outcome = Deferred.await(result);
|
|
48
|
+
const settle = (value) => {
|
|
49
|
+
Deferred.doneUnsafe(result, Effect.succeed(protectSnapshot(value)));
|
|
50
|
+
};
|
|
51
|
+
Effect.runFork(Effect.gen(function* () {
|
|
52
|
+
yield* outcome;
|
|
49
53
|
if (entry.retiring)
|
|
50
54
|
return;
|
|
51
55
|
entry.retiring = true;
|
|
52
56
|
let version;
|
|
53
57
|
do {
|
|
54
58
|
version = entry.version;
|
|
55
|
-
|
|
59
|
+
yield* entry.source.awaitStopped();
|
|
56
60
|
} while (version !== entry.version);
|
|
57
61
|
if (entries.get(key) === entry) {
|
|
58
62
|
entries.delete(key);
|
|
59
63
|
entry.source.dispose();
|
|
60
64
|
}
|
|
61
|
-
})
|
|
62
|
-
.
|
|
65
|
+
})).addObserver((exit) => {
|
|
66
|
+
if (Exit.isFailure(exit))
|
|
67
|
+
reportSafely(reportError, exit.cause);
|
|
68
|
+
});
|
|
63
69
|
const action = Effect.uninterruptibleMask((restore) => restore(Effect.suspend(() => definition.run(key, captured)).pipe(Effect.map((value) => protectSnapshot(value)))).pipe(Effect.exit, Effect.flatMap((exit) => Effect.sync(() => settle(Exit.isSuccess(exit)
|
|
64
70
|
? { _tag: 'Success', value: exit.value }
|
|
65
71
|
: Cause.hasInterruptsOnly(exit.cause)
|
|
@@ -82,14 +88,13 @@ export function keyedTasks(owner, definition, ...provided) {
|
|
|
82
88
|
entry.source.send({ cancel: true });
|
|
83
89
|
}
|
|
84
90
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}));
|
|
91
|
+
drain(...selected) {
|
|
92
|
+
return Effect.suspend(() => {
|
|
93
|
+
const pending = selected.length
|
|
94
|
+
? [entries.get(selected[0])].filter((entry) => !!entry)
|
|
95
|
+
: [...entries.values()];
|
|
96
|
+
return Effect.all(pending.map((entry) => Effect.andThen(entry.source.awaitIdle(), entry.source.awaitStopped())), { concurrency: 'unbounded', discard: true });
|
|
97
|
+
});
|
|
93
98
|
},
|
|
94
99
|
close,
|
|
95
100
|
dispose,
|
package/dist/lazy.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Cause from 'effect/Cause';
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
2
3
|
import { type View } from './dom.js';
|
|
3
4
|
import { type UiRuntime } from './runtime.js';
|
|
4
5
|
export interface LazyViewOptions<Model, Message, E> {
|
package/dist/lazy.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Cause from 'effect/Cause';
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
3
|
+
import * as Fiber from 'effect/Fiber';
|
|
2
4
|
import { child, compiled, viewRegion } from './dom.js';
|
|
3
5
|
import { reportSafely } from './errors.js';
|
|
4
6
|
import { defaultUiRuntime } from './runtime.js';
|
|
@@ -28,20 +30,18 @@ export function lazyView(load, ...provided) {
|
|
|
28
30
|
const finished = scope.settlement.begin();
|
|
29
31
|
try {
|
|
30
32
|
fiber = Effect.runFork(runtime.provide(Effect.suspend(load)));
|
|
31
|
-
fiber.addObserver(finished);
|
|
32
33
|
}
|
|
33
34
|
catch (error) {
|
|
34
35
|
finished();
|
|
35
36
|
throw error;
|
|
36
37
|
}
|
|
37
|
-
if (scope.disposed) {
|
|
38
|
-
interrupt();
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
38
|
fiber.addObserver((exit) => {
|
|
42
|
-
if (scope.disposed)
|
|
43
|
-
return;
|
|
44
39
|
try {
|
|
40
|
+
if (scope.disposed) {
|
|
41
|
+
if (exit._tag === 'Failure' && !Cause.hasInterruptsOnly(exit.cause))
|
|
42
|
+
reportSafely(scope.report, exit.cause);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
45
|
if (exit._tag === 'Success') {
|
|
46
46
|
loaded = exit.value;
|
|
47
47
|
render(loaded);
|
|
@@ -61,6 +61,11 @@ export function lazyView(load, ...provided) {
|
|
|
61
61
|
catch (error) {
|
|
62
62
|
reportSafely(scope.report, error);
|
|
63
63
|
}
|
|
64
|
+
finally {
|
|
65
|
+
finished();
|
|
66
|
+
}
|
|
64
67
|
});
|
|
68
|
+
if (scope.disposed)
|
|
69
|
+
interrupt();
|
|
65
70
|
});
|
|
66
71
|
}
|
package/dist/load.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
2
2
|
export type UiLoad<A, E = unknown, R = never> = Effect.Effect<A, E, R>;
|
|
3
3
|
export declare const loadEffect: <A, E, R>(load: () => UiLoad<A, E, R>) => UiLoad<A, E, R>;
|
|
4
4
|
/** Adapt Promise libraries at the integration boundary. Pass the signal to APIs that support it. */
|
package/dist/load.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
2
2
|
export const loadEffect = (load) => Effect.suspend(load);
|
|
3
3
|
/** Adapt Promise libraries at the integration boundary. Pass the signal to APIs that support it. */
|
|
4
4
|
export const fromPromise = (load) => Effect.tryPromise({ try: load, catch: (error) => error });
|
package/dist/mount.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Snapshot } from './snapshot.js';
|
|
2
2
|
import { type ReportError } from './errors.js';
|
|
3
|
-
import
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
4
4
|
import { type UiRuntime } from './runtime.js';
|
|
5
5
|
export { Portal, type PortalProps } from './dom.js';
|
|
6
6
|
type Work<R = never> = (() => void) | {
|
|
@@ -16,9 +16,18 @@ export interface DomMount<T extends Element = Element> {
|
|
|
16
16
|
export declare function domMount<T extends Element, R = never>(start: (element: T) => Work<R>, ...provided: [R] extends [never] ? [runtime?: UiRuntime<R>] : [runtime: UiRuntime<R>]): DomMount<T>;
|
|
17
17
|
/** A stable DOM lifecycle with fresh immutable inputs, for focus, measurements and native events. */
|
|
18
18
|
export declare function domBinding<T extends Element, A, R = never>(data: A | Snapshot<A>, acquire: (element: T, input: () => Snapshot<A>) => Work<R>, ...provided: [R] extends [never] ? [runtime?: UiRuntime<R>] : [runtime: UiRuntime<R>]): DomMount<T>;
|
|
19
|
+
/** Allocate the lifetime before acquisition so reentrant publications can update or close it. */
|
|
20
|
+
export declare function prepareMount<T extends Element>(element: T, mount: DomMount<T>, report?: ReportError): {
|
|
21
|
+
closed: Effect.Effect<void, never, never>;
|
|
22
|
+
start(): void;
|
|
23
|
+
close: () => Effect.Effect<void, never, never>;
|
|
24
|
+
update(next: DomMount<T>): boolean;
|
|
25
|
+
dispose: () => void;
|
|
26
|
+
};
|
|
19
27
|
export declare function startMount<T extends Element>(element: T, mount: DomMount<T>, report?: ReportError): {
|
|
20
|
-
closed:
|
|
21
|
-
|
|
28
|
+
closed: Effect.Effect<void, never, never>;
|
|
29
|
+
start(): void;
|
|
30
|
+
close: () => Effect.Effect<void, never, never>;
|
|
22
31
|
update(next: DomMount<T>): boolean;
|
|
23
32
|
dispose: () => void;
|
|
24
33
|
};
|
package/dist/mount.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { reportError, reportSafely } from './errors.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Cause from 'effect/Cause';
|
|
3
|
+
import * as Deferred from 'effect/Deferred';
|
|
4
|
+
import * as Effect from 'effect/Effect';
|
|
5
|
+
import * as Fiber from 'effect/Fiber';
|
|
3
6
|
import { defaultUiRuntime } from './runtime.js';
|
|
4
7
|
export { Portal } from './dom.js';
|
|
5
8
|
/** Element-owned work. Changed acquisition functions replace and interrupt the prior lifetime. */
|
|
@@ -26,24 +29,22 @@ export function domBinding(data, acquire, ...provided) {
|
|
|
26
29
|
},
|
|
27
30
|
};
|
|
28
31
|
}
|
|
29
|
-
|
|
32
|
+
/** Allocate the lifetime before acquisition so reentrant publications can update or close it. */
|
|
33
|
+
export function prepareMount(element, mount, report = reportError) {
|
|
30
34
|
let data = mount.data;
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
let work;
|
|
36
|
+
let fiber;
|
|
37
|
+
let started = false;
|
|
33
38
|
let disposed = false;
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
finish();
|
|
42
|
-
});
|
|
43
|
-
const dispose = () => {
|
|
44
|
-
if (disposed)
|
|
39
|
+
let released = false;
|
|
40
|
+
let changedDuringSetup = false;
|
|
41
|
+
const completed = Deferred.makeUnsafe();
|
|
42
|
+
const closed = Deferred.await(completed);
|
|
43
|
+
const finish = () => Deferred.doneUnsafe(completed, Effect.void);
|
|
44
|
+
const release = () => {
|
|
45
|
+
if (released || work === undefined || (Effect.isEffect(work) && !fiber))
|
|
45
46
|
return;
|
|
46
|
-
|
|
47
|
+
released = true;
|
|
47
48
|
if (fiber)
|
|
48
49
|
Effect.runFork(Fiber.interrupt(fiber));
|
|
49
50
|
else {
|
|
@@ -58,20 +59,62 @@ export function startMount(element, mount, report = reportError) {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
};
|
|
62
|
+
const dispose = () => {
|
|
63
|
+
if (disposed)
|
|
64
|
+
return;
|
|
65
|
+
disposed = true;
|
|
66
|
+
if (!started)
|
|
67
|
+
finish();
|
|
68
|
+
else
|
|
69
|
+
release();
|
|
70
|
+
};
|
|
61
71
|
return {
|
|
62
72
|
closed,
|
|
63
|
-
|
|
73
|
+
start() {
|
|
74
|
+
if (started || disposed)
|
|
75
|
+
return;
|
|
76
|
+
started = true;
|
|
77
|
+
try {
|
|
78
|
+
work = mount.acquire(element, () => data);
|
|
79
|
+
if (Effect.isEffect(work)) {
|
|
80
|
+
fiber = Effect.runFork(work);
|
|
81
|
+
fiber.addObserver((exit) => {
|
|
82
|
+
if (exit._tag === 'Failure' && (!disposed || !Cause.hasInterruptsOnly(exit.cause)))
|
|
83
|
+
reportSafely(report, exit.cause);
|
|
84
|
+
finish();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (disposed)
|
|
88
|
+
release();
|
|
89
|
+
else if (changedDuringSetup && !fiber && typeof work === 'object' && 'update' in work)
|
|
90
|
+
work.update?.();
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
dispose();
|
|
94
|
+
if (work === undefined)
|
|
95
|
+
finish();
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
close: () => Effect.suspend(() => {
|
|
64
100
|
dispose();
|
|
65
101
|
return closed;
|
|
66
|
-
},
|
|
102
|
+
}),
|
|
67
103
|
update(next) {
|
|
68
|
-
if (next.identity !== mount.identity)
|
|
104
|
+
if (disposed || next.identity !== mount.identity)
|
|
69
105
|
return false;
|
|
70
106
|
data = next.data;
|
|
71
|
-
if (
|
|
107
|
+
if (started && work === undefined)
|
|
108
|
+
changedDuringSetup = true;
|
|
109
|
+
else if (!fiber && typeof work === 'object' && work && 'update' in work)
|
|
72
110
|
work.update?.();
|
|
73
111
|
return true;
|
|
74
112
|
},
|
|
75
113
|
dispose,
|
|
76
114
|
};
|
|
77
115
|
}
|
|
116
|
+
export function startMount(element, mount, report = reportError) {
|
|
117
|
+
const lifetime = prepareMount(element, mount, report);
|
|
118
|
+
lifetime.start();
|
|
119
|
+
return lifetime;
|
|
120
|
+
}
|
package/dist/owner.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { type Snapshot } from './snapshot.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
3
3
|
import { type Program, type TaskPolicy, type CommandSlot } from './program.js';
|
|
4
4
|
import { type ReportError } from './errors.js';
|
|
5
5
|
import type { UiRuntime } from './runtime.js';
|
|
6
6
|
export type { TaskPolicy } from './program.js';
|
|
7
|
+
interface OwnedResource {
|
|
8
|
+
dispose(): void;
|
|
9
|
+
close?(): Effect.Effect<void, unknown>;
|
|
10
|
+
}
|
|
7
11
|
export interface DisposableOwner {
|
|
8
12
|
readonly disposed: boolean;
|
|
9
|
-
readonly own: <A extends
|
|
10
|
-
dispose(): void;
|
|
11
|
-
}>(resource: A) => A;
|
|
13
|
+
readonly own: <A extends OwnedResource>(resource: A) => A;
|
|
12
14
|
}
|
|
13
15
|
export interface ModelOwner<Model extends object, R = never> extends DisposableOwner {
|
|
14
16
|
readonly source: Program<Model, never>;
|
|
@@ -24,9 +26,9 @@ export interface ModelOwner<Model extends object, R = never> extends DisposableO
|
|
|
24
26
|
readonly run: (slot: CommandSlot, effect: Effect.Effect<unknown, unknown, R>, policy: TaskPolicy) => void;
|
|
25
27
|
readonly cancel: (slot: CommandSlot) => void;
|
|
26
28
|
readonly isRunning: (slot: CommandSlot) => boolean;
|
|
27
|
-
readonly awaitIdle: (slot?: CommandSlot) =>
|
|
29
|
+
readonly awaitIdle: (slot?: CommandSlot) => Effect.Effect<void>;
|
|
28
30
|
/** Interrupt and join work before closing owned dependencies in reverse order. */
|
|
29
|
-
readonly close: () =>
|
|
31
|
+
readonly close: () => Effect.Effect<void, AggregateError>;
|
|
30
32
|
readonly dispose: () => void;
|
|
31
33
|
}
|
|
32
34
|
type Options = {
|
package/dist/owner.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { protectSnapshot } from './snapshot.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Cause from 'effect/Cause';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
4
|
+
import * as Exit from 'effect/Exit';
|
|
3
5
|
import { program, } from './program.js';
|
|
4
6
|
import { patchModel } from './state.js';
|
|
5
7
|
import { runAll, reportError } from './errors.js';
|
|
@@ -7,7 +9,7 @@ export function modelOwner(initial, options = {}) {
|
|
|
7
9
|
let disposed = false;
|
|
8
10
|
let staged;
|
|
9
11
|
const cleanups = [];
|
|
10
|
-
let
|
|
12
|
+
let resourcesDisposed = false;
|
|
11
13
|
const source = program({
|
|
12
14
|
initial,
|
|
13
15
|
...options,
|
|
@@ -57,42 +59,27 @@ export function modelOwner(initial, options = {}) {
|
|
|
57
59
|
return;
|
|
58
60
|
disposed = true;
|
|
59
61
|
source.dispose();
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.reverse()
|
|
63
|
-
.map((resource) => () => resource.dispose()), options.onDefect ?? reportError);
|
|
62
|
+
resourcesDisposed = true;
|
|
63
|
+
runAll([...cleanups].reverse().map((resource) => () => resource.dispose()), options.onDefect ?? reportError);
|
|
64
64
|
};
|
|
65
|
-
const
|
|
66
|
-
if (closing)
|
|
67
|
-
return closing;
|
|
65
|
+
const closing = Effect.runSync(Effect.cached(Effect.uninterruptible(Effect.gen(function* () {
|
|
68
66
|
disposed = true;
|
|
69
67
|
const resources = cleanups.splice(0).reverse();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
errors.push(error);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (errors.length)
|
|
91
|
-
throw new AggregateError(errors, 'Owner cleanup failed.');
|
|
92
|
-
};
|
|
93
|
-
closeResources().then(finish, fail);
|
|
94
|
-
return closing;
|
|
95
|
-
};
|
|
68
|
+
yield* source.close();
|
|
69
|
+
const errors = [];
|
|
70
|
+
for (const resource of resources) {
|
|
71
|
+
const exit = yield* Effect.exit(Effect.suspend(() => resource.close
|
|
72
|
+
? resource.close()
|
|
73
|
+
: resourcesDisposed
|
|
74
|
+
? Effect.void
|
|
75
|
+
: Effect.sync(() => resource.dispose())));
|
|
76
|
+
if (Exit.isFailure(exit))
|
|
77
|
+
errors.push(Cause.squash(exit.cause));
|
|
78
|
+
}
|
|
79
|
+
if (errors.length)
|
|
80
|
+
return yield* Effect.fail(new AggregateError(errors, 'Owner cleanup failed.'));
|
|
81
|
+
}))));
|
|
82
|
+
const close = () => closing;
|
|
96
83
|
return {
|
|
97
84
|
source: { model: source.model, send: source.send, subscribe: source.subscribe, dispose, close },
|
|
98
85
|
read,
|
|
@@ -122,11 +109,6 @@ export function modelOwner(initial, options = {}) {
|
|
|
122
109
|
staged = batch;
|
|
123
110
|
try {
|
|
124
111
|
const result = work();
|
|
125
|
-
if (result &&
|
|
126
|
-
(typeof result === 'object' || typeof result === 'function') &&
|
|
127
|
-
'then' in result &&
|
|
128
|
-
typeof result.then === 'function')
|
|
129
|
-
throw new TypeError('Model transactions must be synchronous. Run async work as an owned Effect.');
|
|
130
112
|
staged = parent;
|
|
131
113
|
if (!disposed) {
|
|
132
114
|
if (parent) {
|
package/dist/pages.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Snapshot } from './snapshot.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Cause from 'effect/Cause';
|
|
3
3
|
import * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
4
4
|
import type { UiLoad, UiPage } from './load.js';
|
|
5
5
|
import { type Transition, type RunningProgram } from './program.js';
|
package/dist/pages.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { commandSlot } from './program.js';
|
|
2
2
|
import { shareData } from './sharing.js';
|
|
3
|
-
import
|
|
3
|
+
import * as Cause from 'effect/Cause';
|
|
4
|
+
import * as Option from 'effect/Option';
|
|
4
5
|
import * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
5
6
|
import { effectCommand, program, } from './program.js';
|
|
6
7
|
import { defaultUiRuntime } from './runtime.js';
|
package/dist/program.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type Snapshot } from './snapshot.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Cause from 'effect/Cause';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
4
|
+
import * as Stream from 'effect/Stream';
|
|
3
5
|
export type Send<Message> = (message: Message) => void;
|
|
4
6
|
/** queue retains FIFO requests; latest-queued retains only the newest pending request. */
|
|
5
7
|
export type TaskPolicy = 'replace' | 'drop' | 'parallel' | 'queue' | 'latest-queued';
|
|
@@ -51,14 +53,14 @@ export interface Program<Model, Message> {
|
|
|
51
53
|
readonly send: Send<Message>;
|
|
52
54
|
readonly subscribe: (listener: (model: Snapshot<Model>) => void) => () => void;
|
|
53
55
|
readonly dispose: () => void;
|
|
54
|
-
readonly close?: () =>
|
|
56
|
+
readonly close?: () => Effect.Effect<void, unknown>;
|
|
55
57
|
}
|
|
56
58
|
export interface RunningProgram<Model, Message> extends Program<Model, Message> {
|
|
57
59
|
/** Wait for interrupted work and its finalizers as well as admitted work. */
|
|
58
|
-
readonly awaitStopped: () =>
|
|
59
|
-
readonly close: () =>
|
|
60
|
+
readonly awaitStopped: () => Effect.Effect<void>;
|
|
61
|
+
readonly close: () => Effect.Effect<void>;
|
|
60
62
|
readonly activeSlots: () => readonly CommandSlot[];
|
|
61
|
-
readonly awaitIdle: (slot?: CommandSlot) =>
|
|
63
|
+
readonly awaitIdle: (slot?: CommandSlot) => Effect.Effect<void>;
|
|
62
64
|
}
|
|
63
65
|
export declare function program<Model, Message>(options: {
|
|
64
66
|
initial: Model | Snapshot<Model>;
|