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/resource.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { commandSlot } from './program.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Cause from 'effect/Cause';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
4
|
+
import * as Option from 'effect/Option';
|
|
3
5
|
import * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
4
6
|
import { loadEffect } from './load.js';
|
|
5
7
|
import { component } from './component.js';
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Snapshot } from './snapshot.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Context from 'effect/Context';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
3
4
|
import { type Command, type RunningProgram, type Transition } from './program.js';
|
|
4
5
|
/** A binding to application-owned services, not a new scope or service lifetime. */
|
|
5
6
|
export interface UiRuntime<R> {
|
package/dist/runtime.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Context from 'effect/Context';
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
3
|
+
import * as Stream from 'effect/Stream';
|
|
2
4
|
import { program } from './program.js';
|
|
3
5
|
export function uiRuntime(context) {
|
|
4
6
|
const provide = (effect) => Effect.provideContext(effect, context);
|
package/dist/session.js
CHANGED
|
@@ -38,7 +38,10 @@ export function queryResource(context, definition) {
|
|
|
38
38
|
let revision = 0;
|
|
39
39
|
const initial = AsyncResult.initial();
|
|
40
40
|
const listeners = new Set();
|
|
41
|
-
const read = () => atom &&
|
|
41
|
+
const read = () => atom &&
|
|
42
|
+
!disposed &&
|
|
43
|
+
!internal.disposed() &&
|
|
44
|
+
generation === internal.registry.get(internal.generation)
|
|
42
45
|
? internal.registry.get(atom)
|
|
43
46
|
: initial;
|
|
44
47
|
let published;
|
|
@@ -102,7 +105,7 @@ export function queryResource(context, definition) {
|
|
|
102
105
|
});
|
|
103
106
|
return {
|
|
104
107
|
select(args) {
|
|
105
|
-
if (disposed)
|
|
108
|
+
if (disposed || internal.disposed())
|
|
106
109
|
return;
|
|
107
110
|
const nextGeneration = internal.registry.get(internal.generation);
|
|
108
111
|
const nextKey = args === undefined ? undefined : encodeQueryKey(args);
|
|
@@ -118,7 +121,12 @@ export function queryResource(context, definition) {
|
|
|
118
121
|
atom = nextAtom;
|
|
119
122
|
if (nextAtom) {
|
|
120
123
|
// Subscription setup itself can execute a synchronous Effect and reenter selection.
|
|
121
|
-
const
|
|
124
|
+
const releaseUse = internal.retain(nextAtom);
|
|
125
|
+
const unsubscribe = internal.registry.subscribe(nextAtom, notify);
|
|
126
|
+
const release = () => {
|
|
127
|
+
unsubscribe();
|
|
128
|
+
releaseUse();
|
|
129
|
+
};
|
|
122
130
|
if (disposed || selected !== revision) {
|
|
123
131
|
release();
|
|
124
132
|
return;
|
|
@@ -139,9 +147,10 @@ export function queryResource(context, definition) {
|
|
|
139
147
|
refresh: () => {
|
|
140
148
|
if (atom &&
|
|
141
149
|
!disposed &&
|
|
150
|
+
!internal.disposed() &&
|
|
142
151
|
generation === internal.registry.get(internal.generation) &&
|
|
143
152
|
!internal.registry.get(atom).waiting)
|
|
144
|
-
internal.
|
|
153
|
+
internal.refresh(atom);
|
|
145
154
|
},
|
|
146
155
|
dispose: () => {
|
|
147
156
|
if (disposed)
|
|
@@ -183,10 +192,17 @@ export function projectionCache() {
|
|
|
183
192
|
}
|
|
184
193
|
export function sessionGroup(sessions, changed) {
|
|
185
194
|
const scope = lifetime();
|
|
186
|
-
for (const session of sessions)
|
|
195
|
+
for (const session of sessions)
|
|
187
196
|
scope.add(() => session.dispose());
|
|
188
|
-
|
|
189
|
-
|
|
197
|
+
try {
|
|
198
|
+
for (const session of sessions) {
|
|
199
|
+
if (session.subscribe)
|
|
200
|
+
scope.add(session.subscribe(changed));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
scope.dispose();
|
|
205
|
+
throw error;
|
|
190
206
|
}
|
|
191
207
|
return {
|
|
192
208
|
refresh: () => {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Internal completion accounting, including reentrant acquisition before a fiber is available. */
|
|
2
|
+
export declare class Settlement {
|
|
3
|
+
private pending;
|
|
4
|
+
private waiters;
|
|
5
|
+
begin(): () => void;
|
|
6
|
+
wait(): Effect.Effect<void>;
|
|
7
|
+
}
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Internal completion accounting, including reentrant acquisition before a fiber is available. */
|
|
2
|
+
export class Settlement {
|
|
3
|
+
pending = 0;
|
|
4
|
+
waiters = new Set();
|
|
5
|
+
begin() {
|
|
6
|
+
this.pending++;
|
|
7
|
+
let finished = false;
|
|
8
|
+
return () => {
|
|
9
|
+
if (finished)
|
|
10
|
+
return;
|
|
11
|
+
finished = true;
|
|
12
|
+
if (--this.pending)
|
|
13
|
+
return;
|
|
14
|
+
const waiters = [...this.waiters];
|
|
15
|
+
this.waiters.clear();
|
|
16
|
+
for (const done of waiters)
|
|
17
|
+
done();
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
wait() {
|
|
21
|
+
return Effect.callback((resume) => {
|
|
22
|
+
if (!this.pending)
|
|
23
|
+
return resume(Effect.void);
|
|
24
|
+
const done = () => resume(Effect.void);
|
|
25
|
+
this.waiters.add(done);
|
|
26
|
+
return Effect.sync(() => {
|
|
27
|
+
this.waiters.delete(done);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
import * as Effect from 'effect/Effect';
|
package/dist/sharing.js
CHANGED
|
@@ -60,28 +60,31 @@ function share(previous, next, fields) {
|
|
|
60
60
|
oldKeys.some((key) => !Object.hasOwn(Object.getOwnPropertyDescriptor(previous, key), 'value')))
|
|
61
61
|
return next;
|
|
62
62
|
let equal = keys.length === oldKeys.length && (!array || value.length === old.length);
|
|
63
|
-
let
|
|
64
|
-
const shared = array ? [] : {};
|
|
65
|
-
if (array)
|
|
66
|
-
shared.length = value.length;
|
|
63
|
+
let changed;
|
|
67
64
|
for (const key of keys) {
|
|
68
65
|
const custom = fields && Object.hasOwn(fields, key) ? fields[key] : undefined;
|
|
69
66
|
const item = custom && Object.hasOwn(old, key)
|
|
70
67
|
? custom(old[key], value[key])
|
|
71
68
|
: share(Object.hasOwn(old, key) ? old[key] : undefined, value[key], undefined);
|
|
72
|
-
// Define an own data property, including __proto__, without invoking setters.
|
|
73
|
-
Object.defineProperty(shared, key, {
|
|
74
|
-
value: item,
|
|
75
|
-
writable: true,
|
|
76
|
-
enumerable: true,
|
|
77
|
-
configurable: true,
|
|
78
|
-
});
|
|
79
69
|
if (!Object.hasOwn(old, key) || !Object.is(item, old[key]))
|
|
80
70
|
equal = false;
|
|
81
71
|
if (!Object.is(item, value[key]))
|
|
82
|
-
|
|
72
|
+
(changed ??= new Map()).set(key, item);
|
|
73
|
+
}
|
|
74
|
+
let result = equal ? previous : next;
|
|
75
|
+
if (!equal && changed) {
|
|
76
|
+
const shared = array ? [] : {};
|
|
77
|
+
if (array)
|
|
78
|
+
shared.length = value.length;
|
|
79
|
+
for (const key of keys)
|
|
80
|
+
Object.defineProperty(shared, key, {
|
|
81
|
+
value: changed.has(key) ? changed.get(key) : value[key],
|
|
82
|
+
writable: true,
|
|
83
|
+
enumerable: true,
|
|
84
|
+
configurable: true,
|
|
85
|
+
});
|
|
86
|
+
result = shared;
|
|
83
87
|
}
|
|
84
|
-
const result = equal ? previous : unchangedNext ? next : shared;
|
|
85
88
|
if (fields)
|
|
86
89
|
return result;
|
|
87
90
|
if (!pairs) {
|
package/dist/snapshot.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type * as Effect from 'effect/Effect';
|
|
2
2
|
import type * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
3
3
|
/** Mark an application service as opaque to snapshot typing; this is a type-only brand. */
|
|
4
4
|
export declare const snapshotOpaque: unique symbol;
|
package/dist/snapshot.js
CHANGED
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
import { isAsyncResult } from 'effect/unstable/reactivity/AsyncResult';
|
|
2
|
-
const
|
|
2
|
+
const protectedValues = new WeakSet();
|
|
3
3
|
/** Protect published plain data in every build. Opaque objects keep their own lifecycle. */
|
|
4
4
|
export function protectSnapshot(value) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
5
|
+
const seen = new Set();
|
|
6
|
+
const plain = [];
|
|
7
|
+
const visit = (item) => {
|
|
8
|
+
if (!item || typeof item !== 'object' || protectedValues.has(item) || seen.has(item))
|
|
9
|
+
return;
|
|
10
|
+
seen.add(item);
|
|
11
|
+
// Effect wrappers remain opaque, but their successful UI data is plain data.
|
|
12
|
+
if (isAsyncResult(item)) {
|
|
13
|
+
if (item._tag === 'Success')
|
|
14
|
+
visit(item.value);
|
|
15
|
+
else if (item._tag === 'Failure' && item.previousSuccess._tag === 'Some')
|
|
16
|
+
visit(item.previousSuccess.value);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const prototype = Object.getPrototypeOf(item);
|
|
20
|
+
if (!Array.isArray(item) && prototype !== Object.prototype && prototype !== null)
|
|
21
|
+
return;
|
|
22
|
+
for (const key of Reflect.ownKeys(item)) {
|
|
23
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, key);
|
|
24
|
+
if (Object.hasOwn(descriptor, 'value'))
|
|
25
|
+
visit(descriptor.value);
|
|
26
|
+
}
|
|
27
|
+
plain.push(item);
|
|
28
|
+
};
|
|
29
|
+
// Traverse stored data without invoking application getters.
|
|
30
|
+
visit(value);
|
|
31
|
+
for (const item of plain)
|
|
32
|
+
Object.freeze(item);
|
|
33
|
+
for (const item of seen)
|
|
34
|
+
protectedValues.add(item);
|
|
29
35
|
return value;
|
|
30
36
|
}
|
package/dist/task.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Snapshot } from './snapshot.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
3
3
|
import * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
4
4
|
import type { View } from './dom.js';
|
|
5
5
|
import { type Send, type TaskPolicy } from './program.js';
|
package/dist/task.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { commandSlot } from './program.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Cause from 'effect/Cause';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
4
|
+
import * as Option from 'effect/Option';
|
|
3
5
|
import * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
4
6
|
import { programView } from './component.js';
|
|
5
7
|
import { effectCommand, } from './program.js';
|
package/dist/tasks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Snapshot } from './snapshot.js';
|
|
2
2
|
import type { ModelOwner, TaskPolicy } from './owner.js';
|
|
3
|
-
import
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
4
4
|
import * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
5
5
|
import type { View } from './dom.js';
|
|
6
6
|
import { type CommandSlot, type Send, type RunningProgram } from './program.js';
|
package/dist/tasks.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
2
|
+
import * as Option from 'effect/Option';
|
|
2
3
|
import * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
3
4
|
import { programView } from './component.js';
|
|
4
5
|
import { commandSlot, effectCommand, program, } from './program.js';
|
|
@@ -137,6 +138,7 @@ function taskBuilder(definition, runtime) {
|
|
|
137
138
|
receive,
|
|
138
139
|
controls,
|
|
139
140
|
view: (view) => programView({
|
|
141
|
+
...(definition.identity ? { identity: definition.identity } : {}),
|
|
140
142
|
create,
|
|
141
143
|
receive: (source, props) => receive(source, props),
|
|
142
144
|
view,
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
2
2
|
import type { CommandSlot, Program, RunningProgram } from './program.js';
|
|
3
3
|
import { type UiRuntime } from './runtime.js';
|
|
4
4
|
/** Program inspection and controlled Effect execution with application-owned services. */
|
|
5
5
|
export interface ProgramDriver<M, Msg, R = never> extends Program<M, Msg> {
|
|
6
|
+
readonly close: RunningProgram<M, Msg>['close'];
|
|
6
7
|
readonly activeSlots: RunningProgram<M, Msg>['activeSlots'];
|
|
7
|
-
readonly awaitSlot: (slot: CommandSlot) =>
|
|
8
|
-
readonly awaitIdle: () =>
|
|
9
|
-
readonly run: <A, E>(effect: Effect.Effect<A, E, R>) =>
|
|
8
|
+
readonly awaitSlot: (slot: CommandSlot) => Effect.Effect<void>;
|
|
9
|
+
readonly awaitIdle: () => Effect.Effect<void>;
|
|
10
|
+
readonly run: <A, E>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E>;
|
|
10
11
|
}
|
|
11
12
|
export declare function programDriver<M, Msg>(source: RunningProgram<M, Msg>): ProgramDriver<M, Msg>;
|
|
12
13
|
export declare function programDriver<M, Msg, R>(source: RunningProgram<M, Msg>, runtime: UiRuntime<R>): ProgramDriver<M, Msg, R>;
|
package/dist/testing.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Cause from 'effect/Cause';
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
2
3
|
import { defaultUiRuntime } from './runtime.js';
|
|
3
4
|
export function programDriver(source, runtime) {
|
|
4
5
|
return makeDriver(source, runtime ?? defaultUiRuntime);
|
|
@@ -9,10 +10,11 @@ function makeDriver(source, runtime) {
|
|
|
9
10
|
send: source.send,
|
|
10
11
|
subscribe: source.subscribe,
|
|
11
12
|
dispose: source.dispose,
|
|
13
|
+
close: source.close,
|
|
12
14
|
activeSlots: source.activeSlots,
|
|
13
15
|
awaitSlot: (slot) => source.awaitIdle(slot),
|
|
14
16
|
awaitIdle: () => source.awaitIdle(),
|
|
15
|
-
run:
|
|
17
|
+
run: runtime.provide,
|
|
16
18
|
};
|
|
17
19
|
}
|
|
18
20
|
/** A reusable controlled request, with cancellation visible to tests. No renderer or private messages. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effectweb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Immutable Effect models and compiled JSX with direct DOM rendering",
|
|
5
5
|
"homepage": "https://github.com/DerpyCrabs/EffectWeb",
|
|
6
6
|
"bugs": {
|
|
@@ -127,6 +127,14 @@
|
|
|
127
127
|
"./http": {
|
|
128
128
|
"types": "./dist/http.d.ts",
|
|
129
129
|
"import": "./dist/http.js"
|
|
130
|
+
},
|
|
131
|
+
"./infinite-query": {
|
|
132
|
+
"types": "./dist/infinite-query.d.ts",
|
|
133
|
+
"import": "./dist/infinite-query.js"
|
|
134
|
+
},
|
|
135
|
+
"./keyed-tasks": {
|
|
136
|
+
"types": "./dist/keyed-tasks.d.ts",
|
|
137
|
+
"import": "./dist/keyed-tasks.js"
|
|
130
138
|
}
|
|
131
139
|
},
|
|
132
140
|
"publishConfig": {
|