effectweb 0.2.3 → 0.3.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 +93 -1
- package/dist/AsyncContent.d.ts +2 -2
- package/dist/AsyncContent.js +6 -3
- package/dist/actions.d.ts +4 -3
- package/dist/actions.js +1 -0
- package/dist/cache-internals.d.ts +14 -0
- package/dist/cache-internals.js +11 -0
- package/dist/cache.d.ts +17 -14
- package/dist/cache.js +98 -26
- package/dist/collection.d.ts +10 -9
- package/dist/collection.js +32 -6
- package/dist/component.d.ts +8 -7
- package/dist/diagnostics.d.ts +25 -0
- package/dist/diagnostics.js +167 -1
- package/dist/dom-attributes.json +71 -0
- package/dist/dom.d.ts +33 -12
- package/dist/dom.js +466 -80
- package/dist/form.d.ts +61 -0
- package/dist/form.js +98 -0
- package/dist/html-attributes.d.ts +602 -0
- package/dist/html-attributes.js +1 -0
- package/dist/http.d.ts +5 -0
- package/dist/http.js +7 -0
- package/dist/index.d.ts +12 -10
- package/dist/index.js +7 -5
- package/dist/jsx.d.ts +165 -33
- package/dist/jsx.js +2 -1
- package/dist/lazy.d.ts +23 -0
- package/dist/lazy.js +58 -0
- package/dist/load.d.ts +3 -3
- package/dist/mount.d.ts +8 -11
- package/dist/mount.js +1 -3
- package/dist/owner.d.ts +11 -9
- package/dist/owner.js +27 -36
- package/dist/pages.d.ts +23 -22
- package/dist/pages.js +15 -9
- package/dist/program.d.ts +19 -9
- package/dist/program.js +145 -49
- package/dist/query-internals.d.ts +11 -0
- package/dist/query-internals.js +10 -0
- package/dist/query.d.ts +24 -19
- package/dist/query.js +60 -7
- package/dist/resource.d.ts +4 -10
- package/dist/resource.js +14 -5
- package/dist/runtime.d.ts +1 -2
- package/dist/runtime.js +3 -3
- package/dist/session.d.ts +12 -15
- package/dist/session.js +15 -12
- package/dist/share.d.ts +5 -4
- package/dist/share.js +5 -44
- package/dist/sharing.d.ts +6 -0
- package/dist/sharing.js +93 -0
- package/dist/snapshot.d.ts +16 -5
- package/dist/snapshot.js +6 -7
- package/dist/svg-attributes.d.ts +12 -0
- package/dist/svg-attributes.js +1 -0
- package/dist/task.d.ts +9 -8
- package/dist/task.js +75 -47
- package/dist/tasks.d.ts +20 -15
- package/dist/tasks.js +39 -25
- package/dist/testing.d.ts +10 -14
- package/package.json +5 -1
package/dist/tasks.d.ts
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
+
import type { Snapshot } from './snapshot.js';
|
|
1
2
|
import type { ModelOwner, TaskPolicy } from './owner.js';
|
|
2
3
|
import { Effect } from 'effect';
|
|
3
4
|
import * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
4
5
|
import type { View } from './dom.js';
|
|
5
|
-
import { type Send, type RunningProgram } from './program.js';
|
|
6
|
+
import { type CommandSlot, type Send, type RunningProgram } from './program.js';
|
|
6
7
|
import { type UiRuntime } from './runtime.js';
|
|
7
8
|
export interface TaskDefinition<Model, Input, A, E, R = never> {
|
|
8
|
-
readonly policy:
|
|
9
|
-
readonly run: (model: Model
|
|
9
|
+
readonly policy: Exclude<TaskPolicy, 'parallel'>;
|
|
10
|
+
readonly run: (model: Snapshot<Model>, input: Input) => Effect.Effect<A, E, R>;
|
|
10
11
|
/** Domain identity, compared after fields or props change. Resets this slot only. */
|
|
11
|
-
readonly identity?: (model: Model) => unknown;
|
|
12
|
+
readonly identity?: (model: Snapshot<Model>) => unknown;
|
|
12
13
|
}
|
|
13
14
|
type Definitions<Model, R> = Record<string, TaskDefinition<Model, never, unknown, unknown, R>>;
|
|
14
|
-
type AnyDefinitions =
|
|
15
|
-
|
|
15
|
+
type AnyDefinitions = Record<string, {
|
|
16
|
+
readonly run: (...args: never[]) => Effect.Effect<unknown, unknown, unknown>;
|
|
17
|
+
}>;
|
|
18
|
+
type Input<T extends AnyDefinitions[string]> = Parameters<T['run']> extends [unknown, ...infer Inputs] ? Inputs extends [] ? void : Inputs[0] : void;
|
|
19
|
+
type InputArguments<T extends AnyDefinitions[string]> = Input<T> extends void ? [input?: Input<T>] : Parameters<T['run']> extends [unknown, unknown, ...unknown[]] ? [input: Input<T>] : [input?: Input<T>];
|
|
16
20
|
export type TaskResults<T extends AnyDefinitions> = {
|
|
17
21
|
readonly [K in keyof T]: AsyncResult.AsyncResult<Effect.Success<ReturnType<T[K]['run']>>, Effect.Error<ReturnType<T[K]['run']>>>;
|
|
18
22
|
};
|
|
@@ -22,7 +26,7 @@ export type TasksModel<Props, State, T extends AnyDefinitions> = State & {
|
|
|
22
26
|
};
|
|
23
27
|
export type TasksMessage<State, T extends AnyDefinitions> = {
|
|
24
28
|
readonly type: 'Fields';
|
|
25
|
-
readonly fields: Partial<State> & {
|
|
29
|
+
readonly fields: (Partial<State> | Partial<Snapshot<State>>) & {
|
|
26
30
|
readonly props?: never;
|
|
27
31
|
readonly tasks?: never;
|
|
28
32
|
};
|
|
@@ -37,19 +41,19 @@ export type TasksMessage<State, T extends AnyDefinitions> = {
|
|
|
37
41
|
};
|
|
38
42
|
}[keyof T];
|
|
39
43
|
type Init<Props, State> = {
|
|
40
|
-
init: (props: Props) => State & {
|
|
44
|
+
init: (props: Snapshot<Props>) => (State | Snapshot<State>) & {
|
|
41
45
|
readonly props?: never;
|
|
42
46
|
readonly tasks?: never;
|
|
43
47
|
};
|
|
44
48
|
/** Changing the component entity also resets editable fields and all its tasks. */
|
|
45
|
-
identity?: (props: Props) => unknown;
|
|
49
|
+
identity?: (props: Snapshot<Props>) => unknown;
|
|
46
50
|
name?: string;
|
|
47
51
|
};
|
|
48
52
|
interface ControllerTask<R> {
|
|
49
53
|
readonly run: (...args: never[]) => Effect.Effect<unknown, unknown, R>;
|
|
50
54
|
readonly policy: TaskPolicy;
|
|
51
|
-
/** Actions sharing a slot share cancellation and concurrency rules. Defaults to
|
|
52
|
-
readonly slot?:
|
|
55
|
+
/** Actions sharing a slot share cancellation and concurrency rules. Defaults to a fresh operation identity for this definition. */
|
|
56
|
+
readonly slot?: CommandSlot;
|
|
53
57
|
}
|
|
54
58
|
export declare function defineTasks<Owner extends Pick<ModelOwner<object>, 'run'>, T extends Record<string, ControllerTask<Effect.Services<Parameters<Owner['run']>[1]>>>>(owner: Owner, definitions: T): {
|
|
55
59
|
readonly [K in keyof T]: (...args: Parameters<T[K]['run']>) => void;
|
|
@@ -62,11 +66,12 @@ declare function taskBuilder<Props, State extends object, R>(definition: Init<Pr
|
|
|
62
66
|
tasks<T extends Definitions<State & {
|
|
63
67
|
readonly props: Props;
|
|
64
68
|
}, R>>(definitions: T): {
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
slot: (name: keyof T) => CommandSlot;
|
|
70
|
+
create: (props: Props | Snapshot<Props>) => RunningProgram<TasksModel<Props, State, T>, TasksMessage<State, T>>;
|
|
71
|
+
receive: (source: RunningProgram<TasksModel<Props, State, T>, TasksMessage<State, T>>, props: Props | Snapshot<Props>) => void;
|
|
67
72
|
controls: (send: Send<TasksMessage<State, T>>) => {
|
|
68
|
-
run: <K extends keyof T>(task: K, ...input:
|
|
69
|
-
patch: (fields: Partial<State> & {
|
|
73
|
+
run: <K extends keyof T>(task: K, ...input: InputArguments<T[K]>) => void;
|
|
74
|
+
patch: (fields: (Partial<State> | Partial<Snapshot<State>>) & {
|
|
70
75
|
readonly props?: never;
|
|
71
76
|
readonly tasks?: never;
|
|
72
77
|
}) => void;
|
package/dist/tasks.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Effect, Option } from 'effect';
|
|
2
2
|
import * as AsyncResult from 'effect/unstable/reactivity/AsyncResult';
|
|
3
3
|
import { programView } from './component.js';
|
|
4
|
-
import { effectCommand, program } from './program.js';
|
|
4
|
+
import { commandSlot, effectCommand, program, } from './program.js';
|
|
5
5
|
import { defaultUiRuntime } from './runtime.js';
|
|
6
6
|
import { patchModel } from './state.js';
|
|
7
7
|
export function defineTasks(definition, definitions) {
|
|
8
8
|
if ('run' in definition) {
|
|
9
9
|
const actions = Object.create(null);
|
|
10
10
|
for (const [name, task] of Object.entries(definitions)) {
|
|
11
|
-
|
|
11
|
+
const slot = task.slot ?? commandSlot(name);
|
|
12
|
+
actions[name] = (...args) => definition.run(slot, Effect.suspend(() => task.run(...args)), task.policy);
|
|
12
13
|
}
|
|
13
14
|
return actions;
|
|
14
15
|
}
|
|
@@ -27,19 +28,17 @@ function taskBuilder(definition, runtime) {
|
|
|
27
28
|
return {
|
|
28
29
|
tasks(definitions) {
|
|
29
30
|
const names = Object.keys(definitions);
|
|
30
|
-
const
|
|
31
|
+
const slots = new Map(names.map((name) => [name, commandSlot(`task:${name}`)]));
|
|
32
|
+
const slot = (name) => slots.get(name);
|
|
31
33
|
const initialResults = () => Object.fromEntries(names.map((name) => [name, AsyncResult.initial()]));
|
|
32
|
-
const init = (props) => ({
|
|
33
|
-
...definition.init(props),
|
|
34
|
-
props,
|
|
35
|
-
tasks: initialResults(),
|
|
36
|
-
});
|
|
34
|
+
const init = (props) => ({ ...definition.init(props), props, tasks: initialResults() });
|
|
37
35
|
const withResult = (model, task, result) => ({ ...model, tasks: { ...model.tasks, [task]: result } });
|
|
38
36
|
const resetIdentities = (before, model) => {
|
|
39
37
|
const cancel = [];
|
|
40
38
|
for (const name of names) {
|
|
41
39
|
const identity = definitions[name].identity;
|
|
42
|
-
if (identity &&
|
|
40
|
+
if (identity &&
|
|
41
|
+
!Object.is(identity(before), identity(model))) {
|
|
43
42
|
model = withResult(model, name, AsyncResult.initial());
|
|
44
43
|
cancel.push(slot(name));
|
|
45
44
|
}
|
|
@@ -51,7 +50,9 @@ function taskBuilder(definition, runtime) {
|
|
|
51
50
|
const source = program({
|
|
52
51
|
initial: init(props),
|
|
53
52
|
...(definition.name ? { name: definition.name } : {}),
|
|
54
|
-
update: (
|
|
53
|
+
update: (snapshot, message) => {
|
|
54
|
+
// Internal immutable reconstruction retains the declared domain types.
|
|
55
|
+
const model = snapshot;
|
|
55
56
|
switch (message.type) {
|
|
56
57
|
case 'Input':
|
|
57
58
|
if (definition.identity &&
|
|
@@ -70,20 +71,22 @@ function taskBuilder(definition, runtime) {
|
|
|
70
71
|
return {
|
|
71
72
|
model: withResult(model, message.task, AsyncResult.waiting(previous)),
|
|
72
73
|
commands: [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
74
|
+
{
|
|
75
|
+
...effectCommand(slot(message.task), () => runtime.provide(task.run(snapshot, message.input)), {
|
|
76
|
+
policy: task.policy,
|
|
77
|
+
onSuccess: (value) => ({
|
|
78
|
+
type: 'Settled',
|
|
79
|
+
task: message.task,
|
|
80
|
+
result: AsyncResult.success(value),
|
|
81
|
+
}),
|
|
82
|
+
onFailure: (cause) => ({
|
|
83
|
+
type: 'Settled',
|
|
84
|
+
task: message.task,
|
|
85
|
+
result: AsyncResult.failure(cause),
|
|
84
86
|
}),
|
|
85
87
|
}),
|
|
86
|
-
|
|
88
|
+
policy: task.policy,
|
|
89
|
+
},
|
|
87
90
|
],
|
|
88
91
|
};
|
|
89
92
|
}
|
|
@@ -97,8 +100,18 @@ function taskBuilder(definition, runtime) {
|
|
|
97
100
|
model: withResult(model, message.task, AsyncResult.initial()),
|
|
98
101
|
cancel: [slot(message.task)],
|
|
99
102
|
};
|
|
100
|
-
case 'Settled':
|
|
101
|
-
|
|
103
|
+
case 'Settled': {
|
|
104
|
+
const result = AsyncResult.isFailure(message.result)
|
|
105
|
+
? AsyncResult.failureWithPrevious(message.result.cause, {
|
|
106
|
+
previous: Option.some(model.tasks[message.task]),
|
|
107
|
+
})
|
|
108
|
+
: message.result;
|
|
109
|
+
return {
|
|
110
|
+
model: withResult(model, message.task, source.activeSlots().includes(slot(message.task))
|
|
111
|
+
? AsyncResult.waiting(result)
|
|
112
|
+
: result),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
102
115
|
}
|
|
103
116
|
},
|
|
104
117
|
});
|
|
@@ -110,7 +123,7 @@ function taskBuilder(definition, runtime) {
|
|
|
110
123
|
const owner = owners.get(source);
|
|
111
124
|
if (!owner)
|
|
112
125
|
throw new Error('Task source belongs to a different definition');
|
|
113
|
-
owner.send({ type: 'Input', props });
|
|
126
|
+
owner.send({ type: 'Input', props: props });
|
|
114
127
|
};
|
|
115
128
|
const controls = (send) => ({
|
|
116
129
|
run: (task, ...input) => send({ type: 'Run', task, input: input[0] }),
|
|
@@ -119,6 +132,7 @@ function taskBuilder(definition, runtime) {
|
|
|
119
132
|
reset: (task) => send({ type: 'Reset', task }),
|
|
120
133
|
});
|
|
121
134
|
return {
|
|
135
|
+
slot,
|
|
122
136
|
create,
|
|
123
137
|
receive,
|
|
124
138
|
controls,
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { Effect } from 'effect';
|
|
2
|
-
import type { RunningProgram } from './program.js';
|
|
2
|
+
import type { CommandSlot, Program, RunningProgram } from './program.js';
|
|
3
3
|
import { type UiRuntime } from './runtime.js';
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
awaitIdle: () => Promise<void>;
|
|
14
|
-
run: <A, E>(effect: Effect.Effect<A, E, R>) => Promise<A>;
|
|
15
|
-
};
|
|
4
|
+
/** Program inspection and controlled Effect execution with application-owned services. */
|
|
5
|
+
export interface ProgramDriver<M, Msg, R = never> extends Program<M, Msg> {
|
|
6
|
+
readonly activeSlots: RunningProgram<M, Msg>['activeSlots'];
|
|
7
|
+
readonly awaitSlot: (slot: CommandSlot) => Promise<void>;
|
|
8
|
+
readonly awaitIdle: () => Promise<void>;
|
|
9
|
+
readonly run: <A, E>(effect: Effect.Effect<A, E, R>) => Promise<A>;
|
|
10
|
+
}
|
|
11
|
+
export declare function programDriver<M, Msg>(source: RunningProgram<M, Msg>): ProgramDriver<M, Msg>;
|
|
12
|
+
export declare function programDriver<M, Msg, R>(source: RunningProgram<M, Msg>, runtime: UiRuntime<R>): ProgramDriver<M, Msg, R>;
|
|
16
13
|
/** A reusable controlled request, with cancellation visible to tests. No renderer or private messages. */
|
|
17
14
|
export declare function controlledEffect<A, E = never>(): {
|
|
18
15
|
effect: Effect.Effect<A, E, never>;
|
|
@@ -22,4 +19,3 @@ export declare function controlledEffect<A, E = never>(): {
|
|
|
22
19
|
fail: (error: E) => void;
|
|
23
20
|
die: (defect: unknown) => void;
|
|
24
21
|
};
|
|
25
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effectweb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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": {
|
|
@@ -123,6 +123,10 @@
|
|
|
123
123
|
"./testing": {
|
|
124
124
|
"types": "./dist/testing.d.ts",
|
|
125
125
|
"import": "./dist/testing.js"
|
|
126
|
+
},
|
|
127
|
+
"./http": {
|
|
128
|
+
"types": "./dist/http.d.ts",
|
|
129
|
+
"import": "./dist/http.js"
|
|
126
130
|
}
|
|
127
131
|
},
|
|
128
132
|
"publishConfig": {
|