effectweb 0.3.0 → 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/boundary.d.ts +11 -0
- package/dist/boundary.js +66 -0
- package/dist/cache-internals.d.ts +4 -0
- package/dist/cache.d.ts +21 -5
- package/dist/cache.js +178 -25
- package/dist/collection.d.ts +1 -1
- package/dist/collection.js +1 -3
- package/dist/component.d.ts +3 -0
- package/dist/component.js +73 -10
- package/dist/dom.d.ts +35 -24
- package/dist/dom.js +177 -128
- package/dist/effectEvent.d.ts +5 -4
- package/dist/effectEvent.js +29 -38
- 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 +7 -4
- package/dist/index.js +6 -3
- package/dist/infinite-query.d.ts +42 -0
- package/dist/infinite-query.js +150 -0
- 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 +30 -0
- package/dist/keyed-tasks.js +102 -0
- package/dist/lazy.d.ts +2 -1
- package/dist/lazy.js +20 -7
- package/dist/load.d.ts +1 -1
- package/dist/load.js +1 -1
- package/dist/mount.d.ts +13 -2
- package/dist/mount.js +82 -21
- package/dist/owner.d.ts +9 -5
- package/dist/owner.js +27 -9
- package/dist/pages.d.ts +16 -15
- package/dist/pages.js +8 -5
- package/dist/program.d.ts +14 -2
- package/dist/program.js +96 -36
- package/dist/query-internals.d.ts +5 -3
- package/dist/query.d.ts +7 -1
- package/dist/query.js +7 -3
- package/dist/resource.js +3 -1
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.js +3 -1
- package/dist/session.js +23 -7
- package/dist/settlement.d.ts +8 -0
- package/dist/settlement.js +32 -0
- package/dist/sharing.js +16 -13
- package/dist/snapshot.d.ts +1 -1
- package/dist/snapshot.js +31 -25
- package/dist/task.d.ts +1 -1
- package/dist/task.js +3 -1
- package/dist/tasks.d.ts +1 -1
- package/dist/tasks.js +3 -1
- package/dist/testing.d.ts +5 -4
- package/dist/testing.js +4 -2
- package/package.json +9 -1
package/dist/component.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Cause from 'effect/Cause';
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
3
|
+
import { compiled, Scope, viewRegion } from './dom.js';
|
|
2
4
|
import { mapCommand, program } from './program.js';
|
|
3
5
|
import { patchModel } from './state.js';
|
|
6
|
+
import { reportSafely } from './errors.js';
|
|
4
7
|
import { defaultUiRuntime } from './runtime.js';
|
|
5
8
|
/** Local fields with no command lifecycle. Sends are shallow patches, never updater callbacks. */
|
|
6
9
|
export function localComponent(definition) {
|
|
7
10
|
return component({
|
|
11
|
+
...(definition.identity ? { identity: definition.identity } : {}),
|
|
8
12
|
init: (props) => ({ ...definition.init(props), props }),
|
|
9
13
|
update: (model, patch) => {
|
|
10
14
|
const next = patchModel(model, patch);
|
|
@@ -16,7 +20,7 @@ export function localComponent(definition) {
|
|
|
16
20
|
/** A child model receives immutable parent inputs and owns its own message/command loop. */
|
|
17
21
|
export function component(definition) {
|
|
18
22
|
const runtime = definition.runtime ?? defaultUiRuntime;
|
|
19
|
-
return compiled((scope, parent, before) => {
|
|
23
|
+
return identify(compiled((scope, parent, before) => {
|
|
20
24
|
const wrap = (next) => ({
|
|
21
25
|
model: next.model,
|
|
22
26
|
...(next.cancel ? { cancel: next.cancel } : {}),
|
|
@@ -37,22 +41,81 @@ export function component(definition) {
|
|
|
37
41
|
})
|
|
38
42
|
: definition.update(model, envelope.message)),
|
|
39
43
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
if (scope.disposed) {
|
|
45
|
+
closeProgram(scope, source);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const child = new Scope(source.model(), (message) => source.send({ type: 'Message', message }), scope.report, scope.settlement);
|
|
49
|
+
let unsubscribe = () => { };
|
|
50
|
+
scope.cleanups.push(() => closeProgram(scope, source), () => child.dispose(), () => unsubscribe());
|
|
51
|
+
const release = source.subscribe((model) => child.set(model));
|
|
52
|
+
if (scope.disposed) {
|
|
53
|
+
release();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
unsubscribe = release;
|
|
43
57
|
definition.view.build(child, parent, before);
|
|
44
58
|
scope.jobs.push(() => source.send({ type: 'Input', props: scope.value }));
|
|
45
59
|
source.send({ type: 'Input', props: scope.value });
|
|
46
|
-
});
|
|
60
|
+
}), definition.identity);
|
|
47
61
|
}
|
|
48
62
|
/** Mount an existing program without introducing a second state owner. */
|
|
49
63
|
export function programView(definition) {
|
|
50
|
-
return compiled((scope, parent, before) => {
|
|
64
|
+
return identify(compiled((scope, parent, before) => {
|
|
51
65
|
const source = definition.create(scope.value);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
if (scope.disposed) {
|
|
67
|
+
closeProgram(scope, source);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const child = new Scope(source.model(), source.send, scope.report, scope.settlement);
|
|
71
|
+
let unsubscribe = () => { };
|
|
72
|
+
scope.cleanups.push(() => closeProgram(scope, source), () => child.dispose(), () => unsubscribe());
|
|
73
|
+
const release = source.subscribe((model) => child.set(model));
|
|
74
|
+
if (scope.disposed) {
|
|
75
|
+
release();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
unsubscribe = release;
|
|
55
79
|
definition.view.build(child, parent, before);
|
|
56
80
|
scope.jobs.push(() => definition.receive(source, scope.value));
|
|
81
|
+
}), definition.identity);
|
|
82
|
+
}
|
|
83
|
+
function closeProgram(scope, source) {
|
|
84
|
+
const finish = scope.settlement.begin();
|
|
85
|
+
try {
|
|
86
|
+
if (source.close) {
|
|
87
|
+
Effect.runFork(source.close()).addObserver((exit) => {
|
|
88
|
+
finish();
|
|
89
|
+
if (exit._tag === 'Failure')
|
|
90
|
+
reportSafely(scope.report, Cause.squash(exit.cause));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
source.dispose();
|
|
95
|
+
finish();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
finish();
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function identify(definition, identity) {
|
|
104
|
+
if (!identity)
|
|
105
|
+
return definition;
|
|
106
|
+
return compiled((scope, parent, before) => {
|
|
107
|
+
const render = viewRegion(scope, parent, before, { manual: true });
|
|
108
|
+
let current;
|
|
109
|
+
let active;
|
|
110
|
+
const update = () => {
|
|
111
|
+
const next = identity(scope.value);
|
|
112
|
+
if (!active || !Object.is(current, next)) {
|
|
113
|
+
current = next;
|
|
114
|
+
active = compiled(definition.build);
|
|
115
|
+
}
|
|
116
|
+
render(active);
|
|
117
|
+
};
|
|
118
|
+
scope.jobs.push(update);
|
|
119
|
+
update();
|
|
57
120
|
});
|
|
58
121
|
}
|
package/dist/dom.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
1
2
|
import type { Snapshot } from './snapshot.js';
|
|
2
3
|
import { type ReportError } from './errors.js';
|
|
3
4
|
import { type BindingSource } from './diagnostics.js';
|
|
4
5
|
import type { Rows } from './index.js';
|
|
5
6
|
import { jsxComponent, type JSX } from './jsx.js';
|
|
6
7
|
import type { DomMount } from './mount.js';
|
|
8
|
+
import { Settlement } from './settlement.js';
|
|
7
9
|
import type { Program, Send } from './program.js';
|
|
8
10
|
type Dependencies = () => readonly unknown[];
|
|
11
|
+
type BindingLocation = BindingSource | (() => BindingSource | undefined);
|
|
9
12
|
type Cleanup = () => void;
|
|
10
13
|
type Build<M, E> = (scope: Scope<M, E>, parent: Node, before: Node | null) => void;
|
|
11
14
|
/** Compiler implementation. No implicit tracking, proxies, or per-binding subscriptions. */
|
|
@@ -13,13 +16,12 @@ export declare class Scope<M, E> {
|
|
|
13
16
|
value: M;
|
|
14
17
|
readonly send: Send<E>;
|
|
15
18
|
readonly report: ReportError;
|
|
19
|
+
readonly settlement: Settlement;
|
|
16
20
|
readonly jobs: Cleanup[];
|
|
17
21
|
readonly cleanups: Cleanup[];
|
|
18
22
|
disposed: boolean;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
derive<A>(dependencies: Dependencies, compute: () => A, source?: BindingSource): () => A;
|
|
22
|
-
watch(dependencies: Dependencies, apply: () => void, source?: BindingSource): void;
|
|
23
|
+
constructor(value: M, send: Send<E>, report?: ReportError, settlement?: Settlement);
|
|
24
|
+
watch(dependencies: Dependencies, apply: () => void, source?: BindingLocation): void;
|
|
23
25
|
set(value: M): void;
|
|
24
26
|
dispose(): void;
|
|
25
27
|
}
|
|
@@ -27,8 +29,8 @@ export interface View<M, E> extends JSX.ComponentType {
|
|
|
27
29
|
(this: never, props: [E] extends [never] ? M | Snapshot<M> : never): JSX.Element;
|
|
28
30
|
readonly build: Build<M, E>;
|
|
29
31
|
}
|
|
30
|
-
/**
|
|
31
|
-
export declare const ViewBinding: (<M, E>(this: never,
|
|
32
|
+
/** Mount a view with an explicit model and message dispatcher. */
|
|
33
|
+
export declare const ViewBinding: (<M, E>(this: never, props: {
|
|
32
34
|
view: View<M, E>;
|
|
33
35
|
model: NoInfer<M> | Snapshot<NoInfer<M>>;
|
|
34
36
|
send: Send<NoInfer<E>>;
|
|
@@ -42,18 +44,22 @@ declare const contentBrand: unique symbol;
|
|
|
42
44
|
export interface CompiledContent {
|
|
43
45
|
readonly [contentBrand]: true;
|
|
44
46
|
}
|
|
45
|
-
/**
|
|
47
|
+
/** An ordinary render callback with a typed input. Invoke it to produce content. */
|
|
46
48
|
export interface Slot<A = void> {
|
|
47
49
|
(value: A | Snapshot<A>): JSX.Element;
|
|
48
|
-
readonly [contentBrand]: [A] extends [void] ? true : false;
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
export declare function compiledSlot<M, E, A>(owner: Scope<M, E>, build: Build<A, E>): Slot<A>;
|
|
54
|
-
/** This declaration is a compiler marker, never a component setup callback. */
|
|
55
|
-
export declare function view<M, E = never>(_render: (model: Snapshot<M>, send: Send<E>) => JSX.Element): View<M, E>;
|
|
51
|
+
export declare function slot<A = void>(render: (value: Snapshot<A>) => JSX.Element): Slot<A>;
|
|
52
|
+
/** Evaluate ordinary synchronous JavaScript for each immutable model publication. */
|
|
53
|
+
export declare function view<M, E = never>(render: (model: Snapshot<M>, send: Send<E>) => JSX.Element): View<M, E>;
|
|
56
54
|
export declare function compiled<M, E>(build: Build<M, E>): View<M, E>;
|
|
55
|
+
type MarkupProps = Readonly<Record<string, unknown>> & {
|
|
56
|
+
readonly children?: JSX.Element;
|
|
57
|
+
};
|
|
58
|
+
/** JSX factories preserve host identity by element type; source locations are metadata only. */
|
|
59
|
+
export declare function markup(tag: string, sources?: Readonly<Record<string, BindingSource>>): (props: MarkupProps) => JSX.Element;
|
|
60
|
+
export declare function renderComponent(component: (props: never) => JSX.Element, props: Readonly<Record<string, unknown>>): JSX.Element;
|
|
61
|
+
/** Preserve collection identity (or array reference identity) while rendering rows. */
|
|
62
|
+
export declare function list<A>(rows: Rows<A> | readonly A[], render: (item: A, index: number) => JSX.Element): JSX.Element;
|
|
57
63
|
export interface PortalProps {
|
|
58
64
|
readonly children?: JSX.Element;
|
|
59
65
|
readonly mount?: Element | undefined;
|
|
@@ -62,25 +68,30 @@ export interface PortalProps {
|
|
|
62
68
|
export declare const Portal: View<PortalProps, never>;
|
|
63
69
|
export declare function mountView<M, E>(parent: Node, definition: View<M, E>, source: Program<M, E>, options?: {
|
|
64
70
|
onError?: ReportError;
|
|
65
|
-
}): () => void
|
|
71
|
+
}): (() => void) & {
|
|
72
|
+
dispose: () => void;
|
|
73
|
+
close: () => Effect.Effect<void, never, never>;
|
|
74
|
+
};
|
|
66
75
|
export declare function element(parent: Node, before: Node | null, tag: string): Element;
|
|
67
76
|
export declare function attribute(element: Element, name: string, value: unknown): void;
|
|
68
77
|
/** Compiler output for attributes whose unchanged value needs no DOM refresh. */
|
|
69
|
-
export declare function bindAttribute<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () => unknown, source?:
|
|
78
|
+
export declare function bindAttribute<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () => unknown, source?: BindingLocation): void;
|
|
70
79
|
/** Handled edits can change a control even when dispatch publishes no new model. */
|
|
71
|
-
export declare function bindControl<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () => unknown, source?:
|
|
72
|
-
export declare function text<M, E>(scope: Scope<M, E>, parent: Node, before: Node | null, dependencies: Dependencies, read: () =>
|
|
73
|
-
export declare function event<M, E>(scope: Scope<M, E>, element: Element, name: string, handler: (event: Event) =>
|
|
74
|
-
/**
|
|
75
|
-
export declare function bindEvent<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () =>
|
|
80
|
+
export declare function bindControl<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () => unknown, source?: BindingLocation): void;
|
|
81
|
+
export declare function text<M, E>(scope: Scope<M, E>, parent: Node, before: Node | null, dependencies: Dependencies, read: () => JSX.Element, source?: BindingLocation): void;
|
|
82
|
+
export declare function event<M, E>(scope: Scope<M, E>, element: Element, name: string, handler: (event: Event) => JSX.EventResult): void;
|
|
83
|
+
/** The event binding owns its requests; snapshot renders may replace its callback. */
|
|
84
|
+
export declare function bindEvent<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () => ((event: Event) => JSX.EventResult) | undefined): void;
|
|
76
85
|
/** Reconcile merged JSX attributes. Each host, event and control owns its cleanup. */
|
|
77
|
-
export declare function bindAttributes<M, E>(scope: Scope<M, E>, element: Element, dependencies: Dependencies, read: () => Readonly<Record<string, unknown>>): void;
|
|
86
|
+
export declare function bindAttributes<M, E>(scope: Scope<M, E>, element: Element, dependencies: Dependencies, read: () => Readonly<Record<string, unknown>>, sources?: () => Readonly<Record<string, BindingSource>> | undefined): void;
|
|
78
87
|
export declare function branch<M, E>(scope: Scope<M, E>, parent: Node, before: Node | null, choose: () => boolean, yes: Build<M, E>, no: Build<M, E>): void;
|
|
79
88
|
export declare function each<M, E, A>(scope: Scope<M, E>, parent: Node, before: Node | null, read: () => Rows<A> | readonly A[], outer: Dependencies, indexUsed: boolean, build: Build<readonly [A, number], E>): void;
|
|
80
|
-
export declare function invoke<M, E>(scope: Scope<M, E>, parent: Node, before: Node | null, dependencies: Dependencies, read: () => readonly unknown[], build: Build<readonly unknown[], E>): void;
|
|
81
89
|
export declare function child<M, E, C, F>(scope: Scope<M, E>, parent: Node, before: Node | null, definition: View<C, F>, dependencies: Dependencies, model: () => C, send: Send<F>): void;
|
|
82
90
|
/** A late-bound compiled definition owns one replaceable DOM region. */
|
|
83
|
-
export declare function viewRegion<M, E>(scope: Scope<M, E>, parent: Node, before: Node | null
|
|
91
|
+
export declare function viewRegion<M, E>(scope: Scope<M, E>, parent: Node, before: Node | null, options?: {
|
|
92
|
+
manual?: boolean;
|
|
93
|
+
report?: ReportError;
|
|
94
|
+
}): (next: View<M, E> | undefined) => void;
|
|
84
95
|
export declare function attach<M, E, T extends Element>(scope: Scope<M, E>, element: T, dependencies: Dependencies, read: () => DomMount<NoInfer<T>> | undefined): void;
|
|
85
96
|
export declare function portal<M, E>(scope: Scope<M, E>, build: Build<M, E>, mount?: () => Element | undefined): void;
|
|
86
97
|
/** Build static markup once per document/namespace, then clone native nodes at each use. */
|