effectweb 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AsyncContent.d.ts +1 -1
- package/dist/AsyncContent.js +18 -35
- package/dist/cache-internals.d.ts +3 -2
- package/dist/cache.d.ts +15 -12
- package/dist/cache.js +51 -10
- package/dist/collection.d.ts +1 -1
- package/dist/collection.js +1 -3
- package/dist/component.d.ts +1 -1
- package/dist/component.js +9 -4
- package/dist/dom.d.ts +64 -31
- package/dist/dom.js +237 -121
- package/dist/effectEvent.d.ts +10 -7
- package/dist/effectEvent.js +50 -46
- 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 +10 -8
- package/dist/index.js +10 -8
- package/dist/infinite-query.d.ts +12 -8
- package/dist/infinite-query.js +21 -13
- package/dist/jsx-runtime.d.ts +6 -0
- package/dist/jsx-runtime.js +14 -1
- package/dist/jsx.d.ts +4 -1
- package/dist/keyed-tasks.d.ts +8 -6
- package/dist/keyed-tasks.js +29 -24
- package/dist/lazy.d.ts +4 -2
- package/dist/lazy.js +16 -10
- package/dist/load.d.ts +1 -1
- package/dist/load.js +1 -1
- package/dist/mount.d.ts +23 -8
- package/dist/mount.js +116 -27
- package/dist/owner.d.ts +13 -8
- package/dist/owner.js +37 -41
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +2 -1
- package/dist/program.d.ts +20 -8
- package/dist/program.js +44 -22
- package/dist/query-internals.d.ts +5 -3
- package/dist/query.d.ts +17 -6
- package/dist/query.js +6 -3
- package/dist/render.d.ts +17 -0
- package/dist/render.js +48 -0
- package/dist/resource.d.ts +4 -3
- package/dist/resource.js +12 -6
- package/dist/runtime.d.ts +12 -1
- package/dist/runtime.js +27 -5
- package/dist/session.d.ts +3 -2
- package/dist/session.js +15 -5
- package/dist/settlement.d.ts +8 -1
- package/dist/settlement.js +20 -8
- package/dist/snapshot.d.ts +1 -1
- package/dist/snapshot.js +10 -12
- package/dist/source.d.ts +38 -0
- package/dist/source.js +218 -0
- package/dist/task.d.ts +1 -1
- package/dist/task.js +3 -1
- package/dist/tasks.d.ts +9 -6
- package/dist/tasks.js +7 -4
- package/dist/testing.d.ts +5 -4
- package/dist/testing.js +4 -2
- package/package.json +10 -3
- package/README.md +0 -101
- 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/AsyncContent.d.ts
CHANGED
package/dist/AsyncContent.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as _ew_dom from "./dom.js";
|
|
2
2
|
import { commandSlot } from "./program.js";
|
|
3
|
-
import
|
|
3
|
+
import * as Cause from "effect/Cause";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
4
6
|
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
|
5
7
|
import { component } from "./component.js";
|
|
6
8
|
import { view } from "./index.js";
|
|
@@ -47,40 +49,21 @@ const implementation = component({
|
|
|
47
49
|
...model,
|
|
48
50
|
visible: true
|
|
49
51
|
} }),
|
|
50
|
-
view:
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
_ew_dom.branch(_ew_scope_1, _ew_parent_13, _ew_before_14, () => _ew_derived_4().result.waiting, (_ew_scope_1, _ew_parent_15, _ew_before_16) => {
|
|
66
|
-
_ew_dom.text(_ew_scope_1, _ew_parent_15, _ew_before_16, () => [_ew_derived_4()?.refreshing], () => _ew_derived_4().refreshing);
|
|
67
|
-
}, (_ew_scope_1, _ew_parent_15, _ew_before_16) => { });
|
|
68
|
-
_ew_dom.branch(_ew_scope_1, _ew_parent_13, _ew_before_14, () => _ew_derived_10() && _ew_derived_4().failure, (_ew_scope_1, _ew_parent_17, _ew_before_18) => {
|
|
69
|
-
_ew_dom.text(_ew_scope_1, _ew_parent_17, _ew_before_18, () => [_ew_derived_4(), _ew_derived_10()?.cause], () => _ew_derived_4().failure(_ew_derived_10().cause));
|
|
70
|
-
}, (_ew_scope_1, _ew_parent_17, _ew_before_18) => { });
|
|
71
|
-
}, (_ew_scope_1, _ew_parent_13, _ew_before_14) => {
|
|
72
|
-
_ew_dom.branch(_ew_scope_1, _ew_parent_13, _ew_before_14, () => _ew_derived_10() && _ew_derived_4().failure, (_ew_scope_1, _ew_parent_19, _ew_before_20) => {
|
|
73
|
-
_ew_dom.text(_ew_scope_1, _ew_parent_19, _ew_before_20, () => [_ew_derived_4(), _ew_derived_10()?.cause], () => _ew_derived_4().failure(_ew_derived_10().cause));
|
|
74
|
-
}, (_ew_scope_1, _ew_parent_19, _ew_before_20) => {
|
|
75
|
-
_ew_dom.branch(_ew_scope_1, _ew_parent_19, _ew_before_20, () => _ew_scope_1.value.pending, (_ew_scope_1, _ew_parent_21, _ew_before_22) => {
|
|
76
|
-
_ew_dom.branch(_ew_scope_1, _ew_parent_21, _ew_before_22, () => _ew_scope_1.value.visible, (_ew_scope_1, _ew_parent_23, _ew_before_24) => {
|
|
77
|
-
_ew_dom.text(_ew_scope_1, _ew_parent_23, _ew_before_24, () => [_ew_derived_4()?.pending], () => _ew_derived_4().pending);
|
|
78
|
-
}, (_ew_scope_1, _ew_parent_23, _ew_before_24) => { });
|
|
79
|
-
}, (_ew_scope_1, _ew_parent_21, _ew_before_22) => {
|
|
80
|
-
_ew_dom.text(_ew_scope_1, _ew_parent_21, _ew_before_22, () => [_ew_derived_4()?.empty], () => _ew_derived_4().empty);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
});
|
|
52
|
+
view: view((model, _send) => {
|
|
53
|
+
const props = model.props;
|
|
54
|
+
const data = AsyncResult.value(props.result);
|
|
55
|
+
const failure = AsyncResult.isFailure(props.result) && !props.result.waiting ? props.result : undefined;
|
|
56
|
+
if (Option.isSome(data))
|
|
57
|
+
return [
|
|
58
|
+
props.content(data.value),
|
|
59
|
+
props.result.waiting ? props.refreshing : null,
|
|
60
|
+
failure && props.failure ? props.failure(failure.cause) : null
|
|
61
|
+
];
|
|
62
|
+
if (failure && props.failure)
|
|
63
|
+
return props.failure(failure.cause);
|
|
64
|
+
if (model.pending)
|
|
65
|
+
return model.visible ? props.pending : null;
|
|
66
|
+
return props.empty;
|
|
84
67
|
})
|
|
85
68
|
});
|
|
86
69
|
/** Presentation only: resource owners choose identity, loading, caching and cancellation. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type * as Scope from 'effect/Scope';
|
|
1
2
|
import type * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
2
3
|
import type * as Atom from 'effect/unstable/reactivity/Atom';
|
|
3
4
|
import type * as AtomRegistry from 'effect/unstable/reactivity/AtomRegistry';
|
|
@@ -7,11 +8,11 @@ import type { Snapshot } from './snapshot.js';
|
|
|
7
8
|
export interface CacheInternals<R> {
|
|
8
9
|
refresh(atom: Atom.Atom<unknown>): void;
|
|
9
10
|
readonly disposed: () => boolean;
|
|
10
|
-
revision<Args, A, E>(definition: Query<Args, A, E, R>, args: Args | Snapshot<Args>): number | undefined;
|
|
11
|
+
revision<Args, A, E>(definition: Query<Args, A, E, R | Scope.Scope>, args: Args | Snapshot<Args>): number | undefined;
|
|
11
12
|
retain(atom: Atom.Atom<unknown>): () => void;
|
|
12
13
|
readonly registry: AtomRegistry.AtomRegistry;
|
|
13
14
|
readonly generation: Atom.Writable<number>;
|
|
14
|
-
query<Args, A, E>(definition: Query<Args, A, E, R>, args: Args | Snapshot<Args>): Atom.Atom<AsyncResult.AsyncResult<Snapshot<A>, E>>;
|
|
15
|
+
query<Args, A, E>(definition: Query<Args, A, E, R | Scope.Scope>, args: Args | Snapshot<Args>): Atom.Atom<AsyncResult.AsyncResult<Snapshot<A>, E>>;
|
|
15
16
|
}
|
|
16
17
|
export declare function registerCache<R>(cache: QueryCache<R>, internals: CacheInternals<R>): void;
|
|
17
18
|
/** Private implementation access; deliberately absent from package exports. */
|
package/dist/cache.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type Snapshot } from './snapshot.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
3
|
+
import * as Scope from 'effect/Scope';
|
|
3
4
|
import { type Query, type QueryGroup } from './query.js';
|
|
4
|
-
import type
|
|
5
|
+
import { type UiRuntime } from './runtime.js';
|
|
5
6
|
export { loadEffect, type UiLoad } from './load.js';
|
|
6
7
|
export { shareValue } from './share.js';
|
|
7
8
|
export interface QueryCacheOptions {
|
|
@@ -10,32 +11,34 @@ export interface QueryCacheOptions {
|
|
|
10
11
|
}
|
|
11
12
|
export declare function makeQueryCache(options?: QueryCacheOptions): QueryCache<never>;
|
|
12
13
|
export declare function makeQueryCache<R>(runtime: UiRuntime<R>, options?: QueryCacheOptions): QueryCache<R>;
|
|
14
|
+
/** Capture application services and clock, and close the cache with the current Effect scope. */
|
|
15
|
+
export declare const scopedQueryCache: <R = never>(options?: QueryCacheOptions) => Effect.Effect<QueryCache<R>, never, R | Scope.Scope>;
|
|
13
16
|
/** A cache owns one registry and the query resources published through it. */
|
|
14
17
|
export interface QueryCache<R = never> {
|
|
15
18
|
batch(work: () => void): void;
|
|
16
|
-
getQueryData<Args, A, E>(definition: Query<Args, A, E, R>, args: NoInfer<Args> | Snapshot<NoInfer<Args>>): Snapshot<A> | undefined;
|
|
17
|
-
invalidateWhere<Args, A, E>(definition: Query<Args, A, E, R>, predicate: (args: Snapshot<Args>) => boolean): void;
|
|
19
|
+
getQueryData<Args, A, E>(definition: Query<Args, A, E, R | Scope.Scope>, args: NoInfer<Args> | Snapshot<NoInfer<Args>>): Snapshot<A> | undefined;
|
|
20
|
+
invalidateWhere<Args, A, E>(definition: Query<Args, A, E, R | Scope.Scope>, predicate: (args: Snapshot<Args>) => boolean): void;
|
|
18
21
|
invalidateGroup(group: QueryGroup): void;
|
|
19
22
|
/** Cancel requests while retaining the last success. Explicit refresh restarts them. */
|
|
20
|
-
cancelQuery<Args, A, E>(definition: Query<Args, A, E, R>, ...selected: [] | [NoInfer<Args> | Snapshot<NoInfer<Args>>]): void;
|
|
23
|
+
cancelQuery<Args, A, E>(definition: Query<Args, A, E, R | Scope.Scope>, ...selected: [] | [NoInfer<Args> | Snapshot<NoInfer<Args>>]): void;
|
|
21
24
|
/** Clear cached data and cancel requests; a subsequent selection or refresh reloads. */
|
|
22
|
-
removeQuery<Args, A, E>(definition: Query<Args, A, E, R>, ...selected: [] | [NoInfer<Args> | Snapshot<NoInfer<Args>>]): void;
|
|
23
|
-
prefetch<Args, A, E>(definition: Query<Args, A, E, R>, args: NoInfer<Args> | Snapshot<NoInfer<Args>>, options?: {
|
|
25
|
+
removeQuery<Args, A, E>(definition: Query<Args, A, E, R | Scope.Scope>, ...selected: [] | [NoInfer<Args> | Snapshot<NoInfer<Args>>]): void;
|
|
26
|
+
prefetch<Args, A, E>(definition: Query<Args, A, E, R | Scope.Scope>, args: NoInfer<Args> | Snapshot<NoInfer<Args>>, options?: {
|
|
24
27
|
readonly refresh?: boolean;
|
|
25
28
|
}): Effect.Effect<Snapshot<A>, E>;
|
|
26
29
|
/** Publish a protected success, including undefined, and supersede any pending load for this key. */
|
|
27
|
-
setQueryData<Args, A, E>(definition: Query<Args, A, E, R>, args: NoInfer<Args> | Snapshot<NoInfer<Args>>, value: NoInfer<A> | Snapshot<NoInfer<A>>): Snapshot<A>;
|
|
30
|
+
setQueryData<Args, A, E>(definition: Query<Args, A, E, R | Scope.Scope>, args: NoInfer<Args> | Snapshot<NoInfer<Args>>, value: NoInfer<A> | Snapshot<NoInfer<A>>): Snapshot<A>;
|
|
28
31
|
/**
|
|
29
32
|
* Update an existing success, including one retained during refresh or failure, and supersede its load.
|
|
30
33
|
* No success or an undefined updater return skips the write. Use setQueryData to seed or store undefined.
|
|
31
34
|
* Updaters run synchronously on readonly data; a throw leaves the cached value unchanged.
|
|
32
35
|
*/
|
|
33
|
-
updateQueryData<Args, A, E>(definition: Query<Args, A, E, R>, args: NoInfer<Args> | Snapshot<NoInfer<Args>>, update: (previous: Snapshot<A>) => NoInfer<A> | Snapshot<NoInfer<A>> | undefined): Snapshot<A> | undefined;
|
|
34
|
-
invalidateQuery<Args, A, E>(definition: Query<Args, A, E, R>, ...selected: [] | [NoInfer<Args> | Snapshot<NoInfer<Args>>]): void;
|
|
36
|
+
updateQueryData<Args, A, E>(definition: Query<Args, A, E, R | Scope.Scope>, args: NoInfer<Args> | Snapshot<NoInfer<Args>>, update: (previous: Snapshot<A>) => NoInfer<A> | Snapshot<NoInfer<A>> | undefined): Snapshot<A> | undefined;
|
|
37
|
+
invalidateQuery<Args, A, E>(definition: Query<Args, A, E, R | Scope.Scope>, ...selected: [] | [NoInfer<Args> | Snapshot<NoInfer<Args>>]): void;
|
|
35
38
|
/** Observe account resets without exposing registry mutation. */
|
|
36
39
|
onReset(listener: () => void): () => void;
|
|
37
40
|
resetResources(): void;
|
|
38
|
-
/**
|
|
39
|
-
close():
|
|
41
|
+
/** Release retained acquisitions and join all load/resource finalizers, including canceled or evicted entries. */
|
|
42
|
+
close(): Effect.Effect<void>;
|
|
40
43
|
dispose(): void;
|
|
41
44
|
}
|
package/dist/cache.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { Settlement } from './settlement.js';
|
|
2
2
|
import { protectSnapshot } from './snapshot.js';
|
|
3
|
-
import
|
|
3
|
+
import * as Deferred from 'effect/Deferred';
|
|
4
|
+
import * as Effect from 'effect/Effect';
|
|
5
|
+
import * as Scope from 'effect/Scope';
|
|
6
|
+
import * as Exit from 'effect/Exit';
|
|
7
|
+
import * as Option from 'effect/Option';
|
|
4
8
|
import * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
5
9
|
import * as Atom from 'effect/unstable/reactivity/Atom';
|
|
6
10
|
import * as AtomRegistry from 'effect/unstable/reactivity/AtomRegistry';
|
|
7
11
|
import { loadEffect } from './load.js';
|
|
8
12
|
import { shareData } from './sharing.js';
|
|
9
|
-
import {
|
|
13
|
+
import { encodeQueryArguments } from './query.js';
|
|
10
14
|
import { registerCache } from './cache-internals.js';
|
|
11
15
|
import { queryDefinition } from './query-internals.js';
|
|
16
|
+
import { defaultUiRuntime, makeUiRuntime } from './runtime.js';
|
|
17
|
+
import { reportError, reportSafely } from './errors.js';
|
|
12
18
|
export { loadEffect } from './load.js';
|
|
13
19
|
export { shareValue } from './share.js';
|
|
14
20
|
export function makeQueryCache(runtimeOrOptions, options = {}) {
|
|
@@ -21,6 +27,7 @@ function createQueryCache(runtime, options = {}) {
|
|
|
21
27
|
if (!Number.isFinite(retention) || retention < 0)
|
|
22
28
|
throw new RangeError('Query retention must be finite and nonnegative.');
|
|
23
29
|
const registry = AtomRegistry.make({ defaultIdleTTL: retention });
|
|
30
|
+
const clock = (runtime ?? defaultUiRuntime).clock;
|
|
24
31
|
const cancelValue = Symbol('cancel');
|
|
25
32
|
const removeValue = Symbol('remove');
|
|
26
33
|
const generation = Atom.keepAlive(Atom.make(0));
|
|
@@ -52,8 +59,28 @@ function createQueryCache(runtime, options = {}) {
|
|
|
52
59
|
next.revision = ++nextRevision;
|
|
53
60
|
next.canceled = false;
|
|
54
61
|
const revision = next.revision;
|
|
62
|
+
const scope = Scope.makeUnsafe();
|
|
63
|
+
const completed = Deferred.makeUnsafe();
|
|
64
|
+
const finish = settlement.begin();
|
|
65
|
+
let started = false;
|
|
66
|
+
let stopping = false;
|
|
67
|
+
get.addFinalizer(() => {
|
|
68
|
+
stopping = true;
|
|
69
|
+
// The atom interrupts its load first. Join acquisition and its ensuring
|
|
70
|
+
// finalizers before closing resources, including late acquisitions.
|
|
71
|
+
Effect.runFork(Effect.uninterruptible(Effect.gen(function* () {
|
|
72
|
+
if (started)
|
|
73
|
+
yield* Deferred.await(completed);
|
|
74
|
+
yield* Scope.close(scope, Exit.void);
|
|
75
|
+
})).pipe(Effect.ensuring(Effect.sync(finish)))).addObserver((exit) => {
|
|
76
|
+
if (Exit.isFailure(exit))
|
|
77
|
+
reportSafely(reportError, exit.cause);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
55
80
|
return Effect.suspend(() => {
|
|
56
|
-
|
|
81
|
+
if (stopping)
|
|
82
|
+
return Effect.interrupt;
|
|
83
|
+
started = true;
|
|
57
84
|
return loadEffect(() => next.load()).pipe(Effect.map((value) => {
|
|
58
85
|
const shared = Option.isSome(previous)
|
|
59
86
|
? share(previous.value, value)
|
|
@@ -62,7 +89,12 @@ function createQueryCache(runtime, options = {}) {
|
|
|
62
89
|
if (!disposed && revision === next.revision)
|
|
63
90
|
remember(next, snapshot);
|
|
64
91
|
return snapshot;
|
|
65
|
-
}), Effect.ensuring(Effect.sync(
|
|
92
|
+
}), Scope.provide(scope), Effect.onExit((exit) => (Exit.isFailure(exit) ? Scope.close(scope, exit) : Effect.void).pipe(Effect.ensuring(Effect.sync(() => {
|
|
93
|
+
Deferred.doneUnsafe(completed, Effect.void);
|
|
94
|
+
// Successful loads retain their resources with the atom entry.
|
|
95
|
+
if (Exit.isFailure(exit))
|
|
96
|
+
finish();
|
|
97
|
+
})))));
|
|
66
98
|
});
|
|
67
99
|
});
|
|
68
100
|
const next = {
|
|
@@ -110,7 +142,7 @@ function createQueryCache(runtime, options = {}) {
|
|
|
110
142
|
return { entry, atom: entry.atom };
|
|
111
143
|
};
|
|
112
144
|
const remember = (entry, value) => {
|
|
113
|
-
entry.loadedAt =
|
|
145
|
+
entry.loadedAt = clock.currentTimeMillisUnsafe();
|
|
114
146
|
const node = registry.getNodes().get(entry.atom);
|
|
115
147
|
if (node)
|
|
116
148
|
values.set(node, { value });
|
|
@@ -123,13 +155,15 @@ function createQueryCache(runtime, options = {}) {
|
|
|
123
155
|
if (disposed)
|
|
124
156
|
throw new Error('Cannot write to a disposed query cache.');
|
|
125
157
|
};
|
|
126
|
-
const queryKey = (definition, args) => `query:${identity(definition)}:${
|
|
158
|
+
const queryKey = (definition, args) => `query:${identity(definition)}:${encodeQueryArguments(definition, args)}`;
|
|
127
159
|
const acquireQuery = (definition, args) => {
|
|
128
160
|
const config = queryDefinition(definition);
|
|
129
161
|
protectSnapshot(args);
|
|
130
162
|
const { entry, atom } = acquire(queryKey(definition, args), () => {
|
|
131
163
|
const effect = Effect.suspend(() => config.load(args, previousValue(resources.get(queryKey(definition, args)))?.value));
|
|
132
|
-
return runtime
|
|
164
|
+
return runtime
|
|
165
|
+
? runtime.provideScoped(effect)
|
|
166
|
+
: effect;
|
|
133
167
|
}, config.share);
|
|
134
168
|
entry.query = definition;
|
|
135
169
|
entry.args = args;
|
|
@@ -147,7 +181,7 @@ function createQueryCache(runtime, options = {}) {
|
|
|
147
181
|
refresh(entry);
|
|
148
182
|
if (registry.getNodes().has(atom) &&
|
|
149
183
|
entry.loadedAt !== undefined &&
|
|
150
|
-
|
|
184
|
+
clock.currentTimeMillisUnsafe() - entry.loadedAt >= config.staleTime) {
|
|
151
185
|
const current = registry.get(atom);
|
|
152
186
|
if (!current.waiting)
|
|
153
187
|
refresh(entry);
|
|
@@ -292,8 +326,10 @@ function createQueryCache(runtime, options = {}) {
|
|
|
292
326
|
},
|
|
293
327
|
close() {
|
|
294
328
|
if (!closing) {
|
|
295
|
-
|
|
296
|
-
|
|
329
|
+
closing = Effect.suspend(() => {
|
|
330
|
+
cache.dispose();
|
|
331
|
+
return settlement.wait();
|
|
332
|
+
});
|
|
297
333
|
}
|
|
298
334
|
return closing;
|
|
299
335
|
},
|
|
@@ -322,3 +358,8 @@ function createQueryCache(runtime, options = {}) {
|
|
|
322
358
|
});
|
|
323
359
|
return Object.freeze(cache);
|
|
324
360
|
}
|
|
361
|
+
/** Capture application services and clock, and close the cache with the current Effect scope. */
|
|
362
|
+
export const scopedQueryCache = (options = {}) => Effect.gen(function* () {
|
|
363
|
+
const runtime = yield* makeUiRuntime();
|
|
364
|
+
return yield* Effect.acquireRelease(Effect.sync(() => makeQueryCache(runtime, options)), (cache) => cache.close());
|
|
365
|
+
});
|
package/dist/collection.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface Rows<A> {
|
|
|
9
9
|
slice(start?: number, end?: number): Rows<A>;
|
|
10
10
|
}
|
|
11
11
|
/** Validate without retaining values. Positions are zero-based array indices. */
|
|
12
|
-
export declare function validateIdentities<A>(items: readonly A[], identity: (item: A, index: number) =>
|
|
12
|
+
export declare function validateIdentities<A, K>(items: readonly A[], identity: (item: A, index: number) => K): K[];
|
|
13
13
|
export interface Collection<A> {
|
|
14
14
|
from(this: void, items: readonly (A | Snapshot<A>)[]): Rows<Snapshot<A>>;
|
|
15
15
|
share<B extends A | Snapshot<A>>(this: void, previous: readonly B[], next: readonly B[]): readonly Snapshot<B>[];
|
package/dist/collection.js
CHANGED
|
@@ -2,10 +2,8 @@ import { shareData } from './sharing.js';
|
|
|
2
2
|
/** Validate without retaining values. Positions are zero-based array indices. */
|
|
3
3
|
export function validateIdentities(items, identity) {
|
|
4
4
|
const seen = new Map();
|
|
5
|
-
return
|
|
5
|
+
return Array.from(items, (item, index) => {
|
|
6
6
|
const key = identity(item, index);
|
|
7
|
-
if (typeof key !== 'string' && typeof key !== 'number')
|
|
8
|
-
throw new Error(`Invalid collection identity at index ${index}. Use a string or number domain identity, or sequence(items) for positional identity.`);
|
|
9
7
|
const previous = seen.get(key);
|
|
10
8
|
if (previous !== undefined)
|
|
11
9
|
throw new Error(`Duplicate collection identity at indices ${previous} and ${index}. Identity must be unique within the collection; use a composite domain identity when IDs are only locally unique.`);
|
package/dist/component.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare function component<Props, Model extends {
|
|
|
31
31
|
/** Mount an existing program without introducing a second state owner. */
|
|
32
32
|
export declare function programView<Props, Model, Message>(definition: {
|
|
33
33
|
identity?: (props: Snapshot<Props>) => unknown;
|
|
34
|
-
create: (props: Snapshot<Props>) => import('./program').Program<Model, Message>;
|
|
34
|
+
create: (props: Snapshot<Props>, runtime: UiRuntime<never>) => import('./program').Program<Model, Message>;
|
|
35
35
|
receive: (source: import('./program').Program<Model, Message>, props: Snapshot<Props>) => void;
|
|
36
36
|
view: View<Model, Message>;
|
|
37
37
|
}): View<Props, never>;
|
package/dist/component.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as Cause from 'effect/Cause';
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
1
3
|
import { compiled, Scope, viewRegion } from './dom.js';
|
|
2
4
|
import { mapCommand, program } from './program.js';
|
|
3
5
|
import { patchModel } from './state.js';
|
|
@@ -17,8 +19,9 @@ export function localComponent(definition) {
|
|
|
17
19
|
}
|
|
18
20
|
/** A child model receives immutable parent inputs and owns its own message/command loop. */
|
|
19
21
|
export function component(definition) {
|
|
20
|
-
const runtime = definition.runtime ?? defaultUiRuntime;
|
|
21
22
|
return identify(compiled((scope, parent, before) => {
|
|
23
|
+
const ownerRuntime = scope.settlement.runtime ?? defaultUiRuntime;
|
|
24
|
+
const runtime = definition.runtime ?? ownerRuntime;
|
|
22
25
|
const wrap = (next) => ({
|
|
23
26
|
model: next.model,
|
|
24
27
|
...(next.cancel ? { cancel: next.cancel } : {}),
|
|
@@ -31,6 +34,7 @@ export function component(definition) {
|
|
|
31
34
|
const source = program({
|
|
32
35
|
initial: definition.init(scope.value),
|
|
33
36
|
onDefect: scope.report,
|
|
37
|
+
runtime: ownerRuntime,
|
|
34
38
|
update: (model, envelope) => wrap(envelope.type === 'Input'
|
|
35
39
|
? (definition.receive?.(model, envelope.props) ?? {
|
|
36
40
|
model: Object.is(model.props, envelope.props)
|
|
@@ -60,7 +64,7 @@ export function component(definition) {
|
|
|
60
64
|
/** Mount an existing program without introducing a second state owner. */
|
|
61
65
|
export function programView(definition) {
|
|
62
66
|
return identify(compiled((scope, parent, before) => {
|
|
63
|
-
const source = definition.create(scope.value);
|
|
67
|
+
const source = definition.create(scope.value, scope.settlement.runtime ?? defaultUiRuntime);
|
|
64
68
|
if (scope.disposed) {
|
|
65
69
|
closeProgram(scope, source);
|
|
66
70
|
return;
|
|
@@ -82,9 +86,10 @@ function closeProgram(scope, source) {
|
|
|
82
86
|
const finish = scope.settlement.begin();
|
|
83
87
|
try {
|
|
84
88
|
if (source.close) {
|
|
85
|
-
source.close().
|
|
89
|
+
Effect.runFork(source.close()).addObserver((exit) => {
|
|
86
90
|
finish();
|
|
87
|
-
|
|
91
|
+
if (exit._tag === 'Failure')
|
|
92
|
+
reportSafely(scope.report, Cause.squash(exit.cause));
|
|
88
93
|
});
|
|
89
94
|
}
|
|
90
95
|
else {
|
package/dist/dom.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
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';
|
|
7
8
|
import { Settlement } from './settlement.js';
|
|
8
|
-
import type {
|
|
9
|
+
import type { Send } from './program.js';
|
|
10
|
+
import type { Source } from './source.js';
|
|
11
|
+
import type { UiRuntime } from './runtime.js';
|
|
9
12
|
type Dependencies = () => readonly unknown[];
|
|
13
|
+
type BindingLocation = BindingSource | (() => BindingSource | undefined);
|
|
10
14
|
type Cleanup = () => void;
|
|
11
15
|
type Build<M, E> = (scope: Scope<M, E>, parent: Node, before: Node | null) => void;
|
|
12
16
|
/** Compiler implementation. No implicit tracking, proxies, or per-binding subscriptions. */
|
|
@@ -18,10 +22,8 @@ export declare class Scope<M, E> {
|
|
|
18
22
|
readonly jobs: Cleanup[];
|
|
19
23
|
readonly cleanups: Cleanup[];
|
|
20
24
|
disposed: boolean;
|
|
21
|
-
private revision;
|
|
22
25
|
constructor(value: M, send: Send<E>, report?: ReportError, settlement?: Settlement);
|
|
23
|
-
|
|
24
|
-
watch(dependencies: Dependencies, apply: () => void, source?: BindingSource): void;
|
|
26
|
+
watch(dependencies: Dependencies, apply: () => void, source?: BindingLocation): void;
|
|
25
27
|
set(value: M): void;
|
|
26
28
|
dispose(): void;
|
|
27
29
|
}
|
|
@@ -29,8 +31,8 @@ export interface View<M, E> extends JSX.ComponentType {
|
|
|
29
31
|
(this: never, props: [E] extends [never] ? M | Snapshot<M> : never): JSX.Element;
|
|
30
32
|
readonly build: Build<M, E>;
|
|
31
33
|
}
|
|
32
|
-
/**
|
|
33
|
-
export declare const ViewBinding: (<M, E>(this: never,
|
|
34
|
+
/** Mount a view with an explicit model and message dispatcher. */
|
|
35
|
+
export declare const ViewBinding: (<M, E>(this: never, props: {
|
|
34
36
|
view: View<M, E>;
|
|
35
37
|
model: NoInfer<M> | Snapshot<NoInfer<M>>;
|
|
36
38
|
send: Send<NoInfer<E>>;
|
|
@@ -38,51 +40,82 @@ export declare const ViewBinding: (<M, E>(this: never, _props: {
|
|
|
38
40
|
[jsxComponent]: true;
|
|
39
41
|
};
|
|
40
42
|
/** Untyped callers get an actionable failure instead of silently losing child messages. */
|
|
41
|
-
export declare function unboundSend(_message:
|
|
43
|
+
export declare function unboundSend(_message: unknown): never;
|
|
42
44
|
declare const contentBrand: unique symbol;
|
|
43
45
|
/** Owned markup accepted wherever JSX can render content. */
|
|
44
46
|
export interface CompiledContent {
|
|
45
47
|
readonly [contentBrand]: true;
|
|
46
48
|
}
|
|
47
|
-
/**
|
|
49
|
+
/** An ordinary render callback with a typed input. Invoke it to produce content. */
|
|
48
50
|
export interface Slot<A = void> {
|
|
49
51
|
(value: A | Snapshot<A>): JSX.Element;
|
|
50
|
-
readonly [contentBrand]: [A] extends [void] ? true : false;
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
export declare function compiledSlot<M, E, A>(owner: Scope<M, E>, build: Build<A, E>): Slot<A>;
|
|
56
|
-
/** This declaration is a compiler marker, never a component setup callback. */
|
|
57
|
-
export declare function view<M, E = never>(_render: (model: Snapshot<M>, send: Send<E>) => JSX.Element): View<M, E>;
|
|
53
|
+
export declare function slot<A = void>(render: (value: Snapshot<A>) => JSX.Element): Slot<A>;
|
|
54
|
+
/** Evaluate ordinary synchronous JavaScript for each immutable model publication. */
|
|
55
|
+
export declare function view<M, E = never>(render: (model: Snapshot<M>, send: Send<E>) => JSX.Element): View<M, E>;
|
|
58
56
|
export declare function compiled<M, E>(build: Build<M, E>): View<M, E>;
|
|
57
|
+
/** Skip a view only under the caller's explicit equality contract. */
|
|
58
|
+
export declare function memoView<M, E>(definition: View<M, E>, equals: (previous: Snapshot<M>, next: Snapshot<M>) => boolean): View<M, E>;
|
|
59
|
+
type MarkupProps = Readonly<Record<string, unknown>> & {
|
|
60
|
+
readonly children?: JSX.Element;
|
|
61
|
+
};
|
|
62
|
+
/** JSX factories preserve host identity by element type; source locations are metadata only. */
|
|
63
|
+
export declare function markup(tag: string, sources?: Readonly<Record<string, BindingSource>>): (props: MarkupProps) => JSX.Element;
|
|
64
|
+
export declare function renderComponent(component: (props: never) => JSX.Element, props: Readonly<Record<string, unknown>>): JSX.Element;
|
|
65
|
+
/** Preserve collection identity (or array reference identity) while rendering rows. */
|
|
66
|
+
export declare function list<A>(rows: Rows<A> | readonly A[], render: (item: A, index: number) => JSX.Element): JSX.Element;
|
|
67
|
+
/**
|
|
68
|
+
* Define keyed row projection once. Every dependency is passed as an explicit input;
|
|
69
|
+
* only changed projected props enter the row view's renderer.
|
|
70
|
+
*/
|
|
71
|
+
export declare function listView<A, Input, Props, Message = never>(options: {
|
|
72
|
+
readonly project: (item: A, index: number, input: Input) => Props | Snapshot<Props>;
|
|
73
|
+
readonly view: View<Props, Message>;
|
|
74
|
+
readonly equals?: (previous: Snapshot<Props>, next: Snapshot<Props>) => boolean;
|
|
75
|
+
}): (rows: Rows<A> | readonly A[], input: Input, ...dispatch: [Message] extends [never] ? [send?: Send<Message>] : [send: Send<Message>]) => JSX.Element;
|
|
76
|
+
/** Render a declared source in its own subscribed region. No dependencies are inferred. */
|
|
77
|
+
export declare function observe<A>(source: Source<A>, render: (value: Snapshot<A>) => JSX.Element): JSX.Element;
|
|
59
78
|
export interface PortalProps {
|
|
60
79
|
readonly children?: JSX.Element;
|
|
61
80
|
readonly mount?: Element | undefined;
|
|
62
81
|
}
|
|
63
82
|
/** Owned content in an element target, defaulting to the document body. */
|
|
64
83
|
export declare const Portal: View<PortalProps, never>;
|
|
65
|
-
export
|
|
66
|
-
onError?: ReportError;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
export interface MountOptions {
|
|
85
|
+
readonly onError?: ReportError;
|
|
86
|
+
readonly runtime?: UiRuntime<never>;
|
|
87
|
+
}
|
|
88
|
+
export interface Mounted {
|
|
89
|
+
(): void;
|
|
90
|
+
readonly dispose: () => void;
|
|
91
|
+
readonly close: () => Effect.Effect<void>;
|
|
92
|
+
}
|
|
93
|
+
export declare function mountView<M, E>(parent: Node, definition: View<M, E>, source: Source<M> & {
|
|
94
|
+
readonly send: Send<E>;
|
|
95
|
+
}, options?: MountOptions): Mounted;
|
|
96
|
+
export declare function mountView<M>(parent: Node, definition: View<M, never>, source: Source<M>, options?: MountOptions): Mounted;
|
|
97
|
+
export declare function mountView<M, E>(parent: Node, definition: View<M, E>, source: Source<M>, options: MountOptions & {
|
|
98
|
+
readonly send: Send<E>;
|
|
99
|
+
}): Mounted;
|
|
100
|
+
/** Internal mounting boundary. Its owner retains completion accounting if construction fails. */
|
|
101
|
+
export declare function mountViewWithSettlement<M, E>(parent: Node, definition: View<M, E>, source: Source<M>, options: {
|
|
102
|
+
readonly send: Send<E>;
|
|
103
|
+
readonly onError?: ReportError;
|
|
104
|
+
}, settlement: Settlement): Mounted;
|
|
71
105
|
export declare function element(parent: Node, before: Node | null, tag: string): Element;
|
|
72
106
|
export declare function attribute(element: Element, name: string, value: unknown): void;
|
|
73
107
|
/** Compiler output for attributes whose unchanged value needs no DOM refresh. */
|
|
74
|
-
export declare function bindAttribute<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () => unknown, source?:
|
|
108
|
+
export declare function bindAttribute<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () => unknown, source?: BindingLocation): void;
|
|
75
109
|
/** Handled edits can change a control even when dispatch publishes no new model. */
|
|
76
|
-
export declare function bindControl<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () => unknown, source?:
|
|
77
|
-
export declare function text<M, E>(scope: Scope<M, E>, parent: Node, before: Node | null, dependencies: Dependencies, read: () =>
|
|
78
|
-
export declare function event<M, E>(scope: Scope<M, E>, element: Element, name: string, handler: (event: Event) =>
|
|
79
|
-
/**
|
|
80
|
-
export declare function bindEvent<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () =>
|
|
110
|
+
export declare function bindControl<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () => unknown, source?: BindingLocation): void;
|
|
111
|
+
export declare function text<M, E>(scope: Scope<M, E>, parent: Node, before: Node | null, dependencies: Dependencies, read: () => JSX.Element, source?: BindingLocation): void;
|
|
112
|
+
export declare function event<M, E>(scope: Scope<M, E>, element: Element, name: string, handler: (event: Event) => JSX.EventResult): void;
|
|
113
|
+
/** The event binding owns its requests; snapshot renders may replace its callback. */
|
|
114
|
+
export declare function bindEvent<M, E>(scope: Scope<M, E>, element: Element, name: string, dependencies: Dependencies, read: () => ((event: Event) => JSX.EventResult) | undefined): void;
|
|
81
115
|
/** Reconcile merged JSX attributes. Each host, event and control owns its cleanup. */
|
|
82
|
-
export declare function bindAttributes<M, E>(scope: Scope<M, E>, element: Element, dependencies: Dependencies, read: () => Readonly<Record<string, unknown>>): void;
|
|
116
|
+
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;
|
|
83
117
|
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;
|
|
84
118
|
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;
|
|
85
|
-
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;
|
|
86
119
|
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;
|
|
87
120
|
/** A late-bound compiled definition owns one replaceable DOM region. */
|
|
88
121
|
export declare function viewRegion<M, E>(scope: Scope<M, E>, parent: Node, before: Node | null, options?: {
|
|
@@ -94,4 +127,4 @@ export declare function portal<M, E>(scope: Scope<M, E>, build: Build<M, E>, mou
|
|
|
94
127
|
/** Build static markup once per document/namespace, then clone native nodes at each use. */
|
|
95
128
|
export declare function template(build: string | ((parent: Node, before: Node | null) => void), depth?: number): (parent: Node, before: Node | null) => Node;
|
|
96
129
|
export declare function literal(parent: Node, before: Node | null, value: string): void;
|
|
97
|
-
export {
|
|
130
|
+
export {};
|