foldkit 0.35.2 → 0.36.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 +1 -1
- package/dist/devtools/overlay.js +1 -1
- package/dist/runtime/{errorUI.d.ts → crashUI.d.ts} +4 -2
- package/dist/runtime/crashUI.d.ts.map +1 -0
- package/dist/runtime/{errorUI.js → crashUI.js} +8 -8
- package/dist/runtime/public.d.ts +1 -1
- package/dist/runtime/public.d.ts.map +1 -1
- package/dist/runtime/runtime.d.ts +44 -10
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +261 -203
- package/package.json +1 -1
- package/dist/runtime/errorUI.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -156,7 +156,7 @@ This is what makes Foldkit unusually AI-friendly. The same property that makes t
|
|
|
156
156
|
- **[Counter](https://github.com/foldkit/foldkit/blob/main/examples/counter/src/main.ts)** — Increment/decrement with reset
|
|
157
157
|
- **[Todo](https://github.com/foldkit/foldkit/blob/main/examples/todo/src/main.ts)** — CRUD operations with localStorage persistence
|
|
158
158
|
- **[Stopwatch](https://github.com/foldkit/foldkit/blob/main/examples/stopwatch/src/main.ts)** — Timer with start/stop/reset
|
|
159
|
-
- **[
|
|
159
|
+
- **[Crash View](https://github.com/foldkit/foldkit/blob/main/examples/crash-view/src/main.ts)** — Custom crash fallback UI with crash reporting
|
|
160
160
|
- **[Form](https://github.com/foldkit/foldkit/blob/main/examples/form/src/main.ts)** — Form validation with async email checking
|
|
161
161
|
- **[Weather](https://github.com/foldkit/foldkit/blob/main/examples/weather/src/main.ts)** — HTTP requests with async state handling
|
|
162
162
|
- **[Routing](https://github.com/foldkit/foldkit/blob/main/examples/routing/src/main.ts)** — URL routing with parser combinators
|
package/dist/devtools/overlay.js
CHANGED
|
@@ -729,7 +729,7 @@ export const createOverlay = (store, position, mode, maybeBanner) => Effect.gen(
|
|
|
729
729
|
view: makeView(position, mode, maybeBanner),
|
|
730
730
|
container,
|
|
731
731
|
subscriptions: makeOverlaySubscriptions(store),
|
|
732
|
-
devtools:
|
|
732
|
+
devtools: false,
|
|
733
733
|
});
|
|
734
734
|
yield* Effect.forkDaemon(overlayRuntime());
|
|
735
735
|
});
|
|
@@ -4,5 +4,7 @@ export declare const noOpDispatch: {
|
|
|
4
4
|
dispatchAsync: (_message: unknown) => Effect.Effect<void, never, never>;
|
|
5
5
|
dispatchSync: (_message: unknown) => void;
|
|
6
6
|
};
|
|
7
|
-
export declare const
|
|
8
|
-
|
|
7
|
+
export declare const defaultCrashView: (context: Readonly<{
|
|
8
|
+
error: Error;
|
|
9
|
+
}>, viewError?: unknown) => Html;
|
|
10
|
+
//# sourceMappingURL=crashUI.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crashUI.d.ts","sourceRoot":"","sources":["../../src/runtime/crashUI.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,OAAO,EAAE,IAAI,EAAQ,MAAM,SAAS,CAAA;AAEpC,eAAO,MAAM,YAAY;8BACG,OAAO;6BACR,OAAO;CACjC,CAAA;AAmBD,eAAO,MAAM,gBAAgB,GAC3B,SAAS,QAAQ,CAAC;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC,EACnC,YAAY,OAAO,KAClB,IA4LF,CAAA"}
|
|
@@ -17,7 +17,7 @@ const colors = {
|
|
|
17
17
|
};
|
|
18
18
|
const fontStack = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
|
|
19
19
|
const monoStack = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace';
|
|
20
|
-
export const
|
|
20
|
+
export const defaultCrashView = (context, viewError) => {
|
|
21
21
|
const { div, h1, p, span, button, Style, Attribute } = html();
|
|
22
22
|
const codeBlockStyle = Style({
|
|
23
23
|
fontFamily: monoStack,
|
|
@@ -45,7 +45,7 @@ export const defaultErrorView = (error, viewError) => {
|
|
|
45
45
|
const introText = viewError
|
|
46
46
|
? [
|
|
47
47
|
'Your custom ',
|
|
48
|
-
span([inlineCodeStyle], ['
|
|
48
|
+
span([inlineCodeStyle], ['crash.view']),
|
|
49
49
|
' threw an error while rendering.',
|
|
50
50
|
]
|
|
51
51
|
: [
|
|
@@ -55,10 +55,10 @@ export const defaultErrorView = (error, viewError) => {
|
|
|
55
55
|
? [
|
|
56
56
|
div([Style({ margin: '0 0 1rem 0' })], [
|
|
57
57
|
p([labelStyle], ['Original error']),
|
|
58
|
-
p([codeBlockStyle], [error.message]),
|
|
58
|
+
p([codeBlockStyle], [context.error.message]),
|
|
59
59
|
]),
|
|
60
60
|
div([Style({ margin: '0 0 1.25rem 0' })], [
|
|
61
|
-
p([labelStyle], ['
|
|
61
|
+
p([labelStyle], ['crash.view error']),
|
|
62
62
|
p([codeBlockStyle], [viewErrorMessage]),
|
|
63
63
|
]),
|
|
64
64
|
]
|
|
@@ -74,7 +74,7 @@ export const defaultErrorView = (error, viewError) => {
|
|
|
74
74
|
padding: '0.75rem 1rem',
|
|
75
75
|
borderRadius: '0.375rem',
|
|
76
76
|
}),
|
|
77
|
-
], [error.message]),
|
|
77
|
+
], [context.error.message]),
|
|
78
78
|
];
|
|
79
79
|
const footerText = viewError
|
|
80
80
|
? []
|
|
@@ -89,8 +89,8 @@ export const defaultErrorView = (error, viewError) => {
|
|
|
89
89
|
paddingTop: '1rem',
|
|
90
90
|
}),
|
|
91
91
|
], [
|
|
92
|
-
'This is the default
|
|
93
|
-
span([inlineCodeStyle], ['
|
|
92
|
+
'This is the default crash view. You can customize it by providing a ',
|
|
93
|
+
span([inlineCodeStyle], ['crash.view']),
|
|
94
94
|
' function to ',
|
|
95
95
|
span([inlineCodeStyle], ['makeElement']),
|
|
96
96
|
' or ',
|
|
@@ -129,7 +129,7 @@ export const defaultErrorView = (error, viewError) => {
|
|
|
129
129
|
fontWeight: '600',
|
|
130
130
|
lineHeight: '1.5',
|
|
131
131
|
}),
|
|
132
|
-
], ['
|
|
132
|
+
], ['Application Crash']),
|
|
133
133
|
p([
|
|
134
134
|
Style({
|
|
135
135
|
color: colors.textPrimary,
|
package/dist/runtime/public.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { makeElement, makeApplication, run } from './runtime';
|
|
2
|
-
export type { BrowserConfig, ElementConfigWithFlags, ElementConfigWithoutFlags, ApplicationConfigWithFlags, ApplicationConfigWithoutFlags, ElementInit, ApplicationInit, MakeRuntimeReturn, } from './runtime';
|
|
2
|
+
export type { BrowserConfig, CrashConfig, CrashContext, ElementConfigWithFlags, ElementConfigWithoutFlags, ApplicationConfigWithFlags, ApplicationConfigWithoutFlags, ElementInit, ApplicationInit, MakeRuntimeReturn, Visibility, SlowViewContext, SlowViewConfig, DevtoolsConfig, } from './runtime';
|
|
3
3
|
export { UrlRequest } from './urlRequest';
|
|
4
4
|
export type { Internal, External } from './urlRequest';
|
|
5
5
|
//# sourceMappingURL=public.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/runtime/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAE7D,YAAY,EACV,aAAa,EACb,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,6BAA6B,EAC7B,WAAW,EACX,eAAe,EACf,iBAAiB,
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/runtime/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAE7D,YAAY,EACV,aAAa,EACb,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,6BAA6B,EAC7B,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Effect, Layer, Schema } from 'effect';
|
|
1
|
+
import { Context, Effect, Layer, Option, Schema } from 'effect';
|
|
2
2
|
import type { Command } from '../command';
|
|
3
3
|
import { Html } from '../html';
|
|
4
4
|
import { Url } from '../url';
|
|
@@ -7,8 +7,8 @@ import type { Subscriptions } from './subscription';
|
|
|
7
7
|
import { UrlRequest } from './urlRequest';
|
|
8
8
|
/** Position of the devtools badge and panel on screen. */
|
|
9
9
|
export type DevtoolsPosition = 'BottomRight' | 'BottomLeft' | 'TopRight' | 'TopLeft';
|
|
10
|
-
/** Controls when
|
|
11
|
-
export type
|
|
10
|
+
/** Controls when a feature is shown. */
|
|
11
|
+
export type Visibility = 'Development' | 'Always';
|
|
12
12
|
/** Controls devtools interaction mode.
|
|
13
13
|
*
|
|
14
14
|
* - `'Inspect'`: Messages stream in and clicking a row shows its state snapshot without pausing the app.
|
|
@@ -18,17 +18,40 @@ export type DevtoolsMode = 'Inspect' | 'TimeTravel';
|
|
|
18
18
|
/**
|
|
19
19
|
* Devtools configuration.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
21
|
+
* Pass `false` to disable devtools entirely.
|
|
22
|
+
*
|
|
23
|
+
* - `show`: `'Development'` (default) enables in dev mode only, `'Always'` enables in all environments including production.
|
|
22
24
|
* - `position`: Where the badge and panel appear. Defaults to `'BottomRight'`.
|
|
23
25
|
* - `mode`: `'TimeTravel'` (default) enables full time-travel debugging. `'Inspect'` allows browsing state snapshots without pausing the app.
|
|
24
26
|
* - `banner`: Optional text shown as a banner at the top of the panel.
|
|
25
27
|
*/
|
|
26
|
-
export type DevtoolsConfig = Readonly<{
|
|
27
|
-
show?:
|
|
28
|
+
export type DevtoolsConfig = false | Readonly<{
|
|
29
|
+
show?: Visibility;
|
|
28
30
|
position?: DevtoolsPosition;
|
|
29
31
|
mode?: DevtoolsMode;
|
|
30
32
|
banner?: string;
|
|
31
33
|
}>;
|
|
34
|
+
/** Context provided to the slow view callback when a view exceeds the time budget. */
|
|
35
|
+
export type SlowViewContext<Model, Message> = Readonly<{
|
|
36
|
+
model: Model;
|
|
37
|
+
message: Option.Option<Message>;
|
|
38
|
+
durationMs: number;
|
|
39
|
+
thresholdMs: number;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Slow view warning configuration.
|
|
43
|
+
*
|
|
44
|
+
* Pass `false` to disable warnings entirely.
|
|
45
|
+
*
|
|
46
|
+
* - `show`: `'Development'` (default) enables in dev mode only, `'Always'` enables in all environments.
|
|
47
|
+
* - `thresholdMs`: Duration in ms above which a view is considered slow. Defaults to 16 (one frame at 60fps).
|
|
48
|
+
* - `onSlowView`: Custom callback invoked when a slow view is detected. Defaults to `console.warn`.
|
|
49
|
+
*/
|
|
50
|
+
export type SlowViewConfig<Model, Message> = false | Readonly<{
|
|
51
|
+
show?: Visibility;
|
|
52
|
+
thresholdMs?: number;
|
|
53
|
+
onSlowView?: (context: SlowViewContext<Model, Message>) => void;
|
|
54
|
+
}>;
|
|
32
55
|
declare const Dispatch_base: Context.TagClass<Dispatch, "@foldkit/Dispatch", {
|
|
33
56
|
readonly dispatchAsync: (message: unknown) => Effect.Effect<void>;
|
|
34
57
|
readonly dispatchSync: (message: unknown) => void;
|
|
@@ -42,6 +65,17 @@ export type BrowserConfig<Message> = Readonly<{
|
|
|
42
65
|
onUrlRequest: (request: UrlRequest) => Message;
|
|
43
66
|
onUrlChange: (url: Url) => Message;
|
|
44
67
|
}>;
|
|
68
|
+
/** Context provided to crash.view and crash.report when the runtime encounters an unrecoverable error. */
|
|
69
|
+
export type CrashContext<Model, Message> = Readonly<{
|
|
70
|
+
error: Error;
|
|
71
|
+
model: Model;
|
|
72
|
+
message: Message;
|
|
73
|
+
}>;
|
|
74
|
+
/** Configuration for crash handling — custom crash UI and/or crash reporting. */
|
|
75
|
+
export type CrashConfig<Model, Message> = Readonly<{
|
|
76
|
+
view?: (context: CrashContext<Model, Message>) => Html;
|
|
77
|
+
report?: (context: CrashContext<Model, Message>) => void;
|
|
78
|
+
}>;
|
|
45
79
|
type BaseElementConfig<Model, Message, StreamDepsMap extends Schema.Struct<Schema.Struct.Fields>, Resources = never, ManagedResourceServices = never> = Readonly<{
|
|
46
80
|
Model: Schema.Schema<Model, any, never>;
|
|
47
81
|
update: (model: Model, message: Message) => [
|
|
@@ -51,8 +85,8 @@ type BaseElementConfig<Model, Message, StreamDepsMap extends Schema.Struct<Schem
|
|
|
51
85
|
view: (model: Model) => Html;
|
|
52
86
|
subscriptions?: Subscriptions<Model, Message, StreamDepsMap, Resources | ManagedResourceServices>;
|
|
53
87
|
container: HTMLElement;
|
|
54
|
-
|
|
55
|
-
|
|
88
|
+
crash?: CrashConfig<Model, Message>;
|
|
89
|
+
slowView?: SlowViewConfig<Model, Message>;
|
|
56
90
|
resources?: Layer.Layer<Resources>;
|
|
57
91
|
managedResources?: ManagedResources<Model, Message, ManagedResourceServices>;
|
|
58
92
|
devtools?: DevtoolsConfig;
|
|
@@ -83,8 +117,8 @@ type BaseApplicationConfig<Model, Message, StreamDepsMap extends Schema.Struct<S
|
|
|
83
117
|
subscriptions?: Subscriptions<Model, Message, StreamDepsMap, Resources | ManagedResourceServices>;
|
|
84
118
|
container: HTMLElement;
|
|
85
119
|
browser: BrowserConfig<Message>;
|
|
86
|
-
|
|
87
|
-
|
|
120
|
+
crash?: CrashConfig<Model, Message>;
|
|
121
|
+
slowView?: SlowViewConfig<Model, Message>;
|
|
88
122
|
resources?: Layer.Layer<Resources>;
|
|
89
123
|
managedResources?: ManagedResources<Model, Message, ManagedResourceServices>;
|
|
90
124
|
devtools?: DevtoolsConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,OAAO,EACP,MAAM,EAGN,KAAK,
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime/runtime.js
CHANGED
|
@@ -6,208 +6,243 @@ import { createDevtoolsStore } from '../devtools/store';
|
|
|
6
6
|
import { fromString as urlFromString } from '../url';
|
|
7
7
|
import { patch, toVNode } from '../vdom';
|
|
8
8
|
import { addBfcacheRestoreListener, addNavigationEventListeners, } from './browserListeners';
|
|
9
|
-
import {
|
|
10
|
-
const SLOW_VIEW_THRESHOLD_MS = 16;
|
|
9
|
+
import { defaultCrashView, noOpDispatch } from './crashUI';
|
|
11
10
|
const DEFAULT_DEVTOOLS_SHOW = 'Development';
|
|
12
11
|
const DEFAULT_DEVTOOLS_POSITION = 'BottomRight';
|
|
13
12
|
const DEFAULT_DEVTOOLS_MODE = 'TimeTravel';
|
|
13
|
+
const DEFAULT_SLOW_VIEW_SHOW = 'Development';
|
|
14
|
+
const DEFAULT_SLOW_VIEW_THRESHOLD_MS = 16;
|
|
15
|
+
const defaultSlowViewCallback = (context) => {
|
|
16
|
+
const trigger = Option.match(context.message, {
|
|
17
|
+
onNone: () => 'init',
|
|
18
|
+
onSome: message => {
|
|
19
|
+
const tag = Predicate.isRecord(message) && '_tag' in message
|
|
20
|
+
? String(message['_tag'])
|
|
21
|
+
: 'unknown';
|
|
22
|
+
return tag;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
console.warn(`[foldkit] Slow view: ${context.durationMs.toFixed(1)}ms (budget: ${context.thresholdMs}ms), triggered by ${trigger}. Consider moving computation to update or memoizing with createLazy.`, ...Option.toArray(context.message));
|
|
26
|
+
};
|
|
14
27
|
/** Effect service tag that provides message dispatching to the view layer. */
|
|
15
28
|
export class Dispatch extends Context.Tag('@foldkit/Dispatch')() {
|
|
16
29
|
}
|
|
17
|
-
const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscriptions, container, browser: browserConfig,
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscriptions, container, browser: browserConfig, crash, slowView, resources, managedResources, devtools, }) => {
|
|
31
|
+
const resolvedSlowView = pipe(slowView ?? {}, Option.liftPredicate(config => config !== false), Option.filter(config => Match.value(config.show ?? DEFAULT_SLOW_VIEW_SHOW).pipe(Match.when('Always', () => true), Match.when('Development', () => !!import.meta.hot), Match.exhaustive)), Option.map(config => ({
|
|
32
|
+
thresholdMs: config.thresholdMs ?? DEFAULT_SLOW_VIEW_THRESHOLD_MS,
|
|
33
|
+
onSlowView: config.onSlowView ?? defaultSlowViewCallback,
|
|
34
|
+
})));
|
|
35
|
+
return (hmrModel) => Effect.scoped(Effect.gen(function* () {
|
|
36
|
+
const maybeResourceLayer = resources
|
|
37
|
+
? Option.some(yield* Layer.memoize(resources))
|
|
38
|
+
: Option.none();
|
|
39
|
+
const managedResourceEntries = managedResources
|
|
40
|
+
? /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
41
|
+
Record.toEntries(managedResources)
|
|
42
|
+
: [];
|
|
43
|
+
const managedResourceRefs = yield* Effect.forEach(managedResourceEntries, ([_key, config]) => Ref.make(Option.none()).pipe(Effect.map(ref => ({ config, ref }))));
|
|
44
|
+
const mergeResourceIntoLayer = (layer, { config, ref }) => Layer.merge(layer, Layer.succeed(
|
|
32
45
|
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
onNone: () => effect,
|
|
38
|
-
onSome: resourceLayer => Effect.provide(effect, resourceLayer),
|
|
39
|
-
});
|
|
40
|
-
return Option.match(maybeManagedResourceLayer, {
|
|
41
|
-
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
42
|
-
onNone: () => withResources,
|
|
43
|
-
onSome: managedLayer =>
|
|
46
|
+
config.resource._tag, ref));
|
|
47
|
+
const maybeManagedResourceLayer = Array.match(managedResourceRefs, {
|
|
48
|
+
onEmpty: () => Option.none(),
|
|
49
|
+
onNonEmpty: refs => Option.some(Array.reduce(refs,
|
|
44
50
|
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
45
|
-
|
|
51
|
+
Layer.empty, mergeResourceIntoLayer)),
|
|
46
52
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
onSome: ({ stateRef }) => SubscriptionRef.get(stateRef).pipe(Effect.map(({ isPaused }) => isPaused)),
|
|
76
|
-
})));
|
|
77
|
-
if (!isPaused) {
|
|
78
|
-
yield* render(nextModel);
|
|
79
|
-
}
|
|
80
|
-
if (!modelEquivalence(currentModel, nextModel)) {
|
|
81
|
-
yield* SubscriptionRef.set(modelSubscriptionRef, nextModel);
|
|
82
|
-
preserveModel(nextModel);
|
|
83
|
-
}
|
|
53
|
+
const provideAllResources = (effect) => {
|
|
54
|
+
const withResources = Option.match(maybeResourceLayer, {
|
|
55
|
+
onNone: () => effect,
|
|
56
|
+
onSome: resourceLayer => Effect.provide(effect, resourceLayer),
|
|
57
|
+
});
|
|
58
|
+
return Option.match(maybeManagedResourceLayer, {
|
|
59
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
60
|
+
onNone: () => withResources,
|
|
61
|
+
onSome: managedLayer =>
|
|
62
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
63
|
+
Effect.provide(withResources, managedLayer),
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
const flags = yield* resolveFlags;
|
|
67
|
+
const modelEquivalence = Schema.equivalence(Model);
|
|
68
|
+
const messageQueue = yield* Queue.unbounded();
|
|
69
|
+
const enqueueMessage = (message) => Queue.offer(messageQueue, message);
|
|
70
|
+
const currentUrl = Option.fromNullable(browserConfig).pipe(Option.flatMap(() => urlFromString(window.location.href)));
|
|
71
|
+
const [initModel, initCommands] = Predicate.isNotUndefined(hmrModel)
|
|
72
|
+
? pipe(hmrModel, Schema.decodeUnknownEither(Model), Either.match({
|
|
73
|
+
onLeft: () => init(flags, Option.getOrUndefined(currentUrl)),
|
|
74
|
+
onRight: restoredModel => [restoredModel, []],
|
|
75
|
+
}))
|
|
76
|
+
: init(flags, Option.getOrUndefined(currentUrl));
|
|
77
|
+
const modelSubscriptionRef = yield* SubscriptionRef.make(initModel);
|
|
78
|
+
yield* Effect.forEach(initCommands, command => Effect.forkDaemon(command.pipe(provideAllResources, Effect.flatMap(enqueueMessage))));
|
|
79
|
+
if (browserConfig) {
|
|
80
|
+
addNavigationEventListeners(messageQueue, browserConfig);
|
|
84
81
|
}
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
yield* Option.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
const modelRef = yield* Ref.make(initModel);
|
|
83
|
+
const maybeCurrentVNodeRef = yield* Ref.make(Option.none());
|
|
84
|
+
const currentMessageRef = yield* Ref.make(Option.none());
|
|
85
|
+
const maybeRuntimeRef = yield* Ref.make(Option.none());
|
|
86
|
+
const maybeDevtoolsStoreRef = yield* Ref.make(Option.none());
|
|
87
|
+
const processMessage = (message) => Effect.gen(function* () {
|
|
88
|
+
const currentModel = yield* Ref.get(modelRef);
|
|
89
|
+
const [nextModel, commands] = update(currentModel, message);
|
|
90
|
+
if (currentModel !== nextModel) {
|
|
91
|
+
yield* Ref.set(modelRef, nextModel);
|
|
92
|
+
const isPaused = yield* pipe(maybeDevtoolsStoreRef, Ref.get, Effect.flatMap(Option.match({
|
|
93
|
+
onNone: () => Effect.succeed(false),
|
|
94
|
+
onSome: ({ stateRef }) => SubscriptionRef.get(stateRef).pipe(Effect.map(({ isPaused }) => isPaused)),
|
|
95
|
+
})));
|
|
96
|
+
if (!isPaused) {
|
|
97
|
+
yield* render(nextModel, Option.some(message));
|
|
98
|
+
}
|
|
99
|
+
if (!modelEquivalence(currentModel, nextModel)) {
|
|
100
|
+
yield* SubscriptionRef.set(modelSubscriptionRef, nextModel);
|
|
101
|
+
preserveModel(nextModel);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
yield* Effect.forEach(commands, command => Effect.forkDaemon(command.pipe(provideAllResources, Effect.flatMap(enqueueMessage))));
|
|
105
|
+
const maybeDevtoolsStore = yield* Ref.get(maybeDevtoolsStoreRef);
|
|
106
|
+
yield* Option.match(maybeDevtoolsStore, {
|
|
107
|
+
onNone: () => Effect.void,
|
|
108
|
+
onSome: store => store.recordMessage(
|
|
109
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
110
|
+
message, nextModel, commands.length, !modelEquivalence(currentModel, nextModel)),
|
|
111
|
+
});
|
|
92
112
|
});
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
const runProcessMessage = (message, messageEffect) => (runtime) => {
|
|
114
|
+
try {
|
|
115
|
+
Runtime.runSync(runtime, messageEffect);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
const squashed = Runtime.isFiberFailure(error)
|
|
119
|
+
? Cause.squash(error[Runtime.FiberFailureCauseId])
|
|
120
|
+
: error;
|
|
121
|
+
const appError = squashed instanceof Error
|
|
122
|
+
? squashed
|
|
123
|
+
: new Error(String(squashed));
|
|
124
|
+
const model = Ref.get(modelRef).pipe(Effect.runSync);
|
|
125
|
+
renderCrashView({ error: appError, model, message }, crash, container, maybeCurrentVNodeRef);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const dispatchSync = (message) => {
|
|
129
|
+
const maybeRuntime = Ref.get(maybeRuntimeRef).pipe(Effect.runSync);
|
|
130
|
+
Option.match(maybeRuntime, {
|
|
131
|
+
onNone: Function.constVoid,
|
|
132
|
+
onSome: runProcessMessage(
|
|
133
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
134
|
+
message,
|
|
135
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
136
|
+
processMessage(message)),
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
const dispatchAsync = (message) =>
|
|
140
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
141
|
+
enqueueMessage(message);
|
|
142
|
+
const render = (model, message) => Effect.gen(function* () {
|
|
143
|
+
const viewStart = performance.now();
|
|
144
|
+
const nextVNodeNullish = yield* view(model);
|
|
145
|
+
const viewDuration = performance.now() - viewStart;
|
|
146
|
+
Option.match(resolvedSlowView, {
|
|
147
|
+
onNone: Function.constVoid,
|
|
148
|
+
onSome: ({ thresholdMs, onSlowView }) => {
|
|
149
|
+
if (viewDuration > thresholdMs) {
|
|
150
|
+
onSlowView({
|
|
151
|
+
model,
|
|
152
|
+
message,
|
|
153
|
+
durationMs: viewDuration,
|
|
154
|
+
thresholdMs,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
const maybeCurrentVNode = yield* Ref.get(maybeCurrentVNodeRef);
|
|
160
|
+
const patchedVNode = yield* Effect.sync(() => patchVNode(maybeCurrentVNode, nextVNodeNullish, container));
|
|
161
|
+
yield* Ref.set(maybeCurrentVNodeRef, Option.some(patchedVNode));
|
|
162
|
+
}).pipe(Effect.provideService(Dispatch, {
|
|
163
|
+
dispatchAsync,
|
|
164
|
+
dispatchSync,
|
|
165
|
+
}));
|
|
166
|
+
const runtime = yield* Effect.runtime();
|
|
167
|
+
yield* Ref.set(maybeRuntimeRef, Option.some(runtime));
|
|
168
|
+
const isInIframe = window.self !== window.top;
|
|
169
|
+
const resolvedDevtools = pipe(devtools ?? {}, Option.liftPredicate(config => config !== false), Option.filter(config => Match.value(config.show ?? DEFAULT_DEVTOOLS_SHOW).pipe(Match.when('Always', () => true), Match.when('Development', () => !!import.meta.hot && !isInIframe), Match.exhaustive)), Option.map(config => ({
|
|
170
|
+
position: config.position ?? DEFAULT_DEVTOOLS_POSITION,
|
|
171
|
+
mode: config.mode ?? DEFAULT_DEVTOOLS_MODE,
|
|
172
|
+
maybeBanner: Option.fromNullable(config.banner),
|
|
173
|
+
})));
|
|
174
|
+
if (Option.isSome(resolvedDevtools)) {
|
|
175
|
+
const { position, mode, maybeBanner } = resolvedDevtools.value;
|
|
176
|
+
const devtoolsStore = yield* createDevtoolsStore({
|
|
177
|
+
replay: (model, message) =>
|
|
178
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
179
|
+
Tuple.getFirst(update(model, message)),
|
|
180
|
+
render: model =>
|
|
181
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
182
|
+
render(model, Option.none()),
|
|
183
|
+
getCurrentModel: Ref.get(modelRef),
|
|
184
|
+
});
|
|
185
|
+
yield* Ref.set(maybeDevtoolsStoreRef, Option.some(devtoolsStore));
|
|
186
|
+
yield* devtoolsStore.recordInit(initModel);
|
|
187
|
+
yield* createOverlay(devtoolsStore, position, mode, maybeBanner);
|
|
106
188
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const dispatchAsync = (message) =>
|
|
118
|
-
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
119
|
-
enqueueMessage(message);
|
|
120
|
-
const render = (model) => Effect.gen(function* () {
|
|
121
|
-
const viewStart = performance.now();
|
|
122
|
-
const nextVNodeNullish = yield* view(model);
|
|
123
|
-
const viewDuration = performance.now() - viewStart;
|
|
124
|
-
if (import.meta.hot &&
|
|
125
|
-
slowViewThresholdMs !== false &&
|
|
126
|
-
viewDuration > slowViewThresholdMs) {
|
|
127
|
-
console.warn(`[foldkit] Slow view: ${viewDuration.toFixed(1)}ms (budget: ${slowViewThresholdMs}ms). Consider moving computation to update or memoizing with createLazy.`);
|
|
189
|
+
yield* render(initModel, Option.none());
|
|
190
|
+
addBfcacheRestoreListener();
|
|
191
|
+
if (subscriptions) {
|
|
192
|
+
yield* pipe(subscriptions, Record.toEntries, Effect.forEach(([_key, { schema, modelToDependencies, depsToStream }]) => {
|
|
193
|
+
const modelStream = Stream.concat(Stream.make(initModel), modelSubscriptionRef.changes);
|
|
194
|
+
return Effect.forkDaemon(modelStream.pipe(Stream.map(modelToDependencies), Stream.changesWith(Schema.equivalence(schema)), Stream.flatMap(depsToStream, { switch: true }), Stream.runForEach(command => command.pipe(Effect.flatMap(enqueueMessage))), provideAllResources));
|
|
195
|
+
}, {
|
|
196
|
+
concurrency: 'unbounded',
|
|
197
|
+
discard: true,
|
|
198
|
+
}));
|
|
128
199
|
}
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
151
|
-
render(model),
|
|
152
|
-
getCurrentModel: Ref.get(modelRef),
|
|
153
|
-
});
|
|
154
|
-
yield* Ref.set(maybeDevtoolsStoreRef, Option.some(devtoolsStore));
|
|
155
|
-
yield* devtoolsStore.recordInit(initModel);
|
|
156
|
-
yield* createOverlay(devtoolsStore, devtoolsPosition, devtoolsMode, maybeDevtoolsBanner);
|
|
157
|
-
}
|
|
158
|
-
yield* render(initModel);
|
|
159
|
-
addBfcacheRestoreListener();
|
|
160
|
-
if (subscriptions) {
|
|
161
|
-
yield* pipe(subscriptions, Record.toEntries, Effect.forEach(([_key, { schema, modelToDependencies, depsToStream }]) => {
|
|
200
|
+
const maybeRequirementsToLifecycle = (config, resourceRef) => (maybeRequirements) => {
|
|
201
|
+
if (Option.isOption(maybeRequirements) &&
|
|
202
|
+
Option.isNone(maybeRequirements)) {
|
|
203
|
+
return Stream.empty;
|
|
204
|
+
}
|
|
205
|
+
const requirements = Option.isOption(maybeRequirements)
|
|
206
|
+
? Option.getOrThrow(maybeRequirements)
|
|
207
|
+
: maybeRequirements;
|
|
208
|
+
const acquire = Effect.gen(function* () {
|
|
209
|
+
const value = yield* config.acquire(requirements);
|
|
210
|
+
yield* Ref.set(resourceRef, Option.some(value));
|
|
211
|
+
return value;
|
|
212
|
+
});
|
|
213
|
+
const release = (value) => Effect.gen(function* () {
|
|
214
|
+
yield* config.release(value);
|
|
215
|
+
yield* Ref.set(resourceRef, Option.none());
|
|
216
|
+
yield* enqueueMessage(config.onReleased());
|
|
217
|
+
}).pipe(Effect.catchAllCause(() => Effect.void));
|
|
218
|
+
return pipe(Stream.scoped(Effect.acquireRelease(acquire, release)), Stream.flatMap(value => Stream.concat(Stream.make(config.onAcquired(value)), Stream.never)), Stream.map(Effect.succeed), Stream.catchAll(error => Stream.make(Effect.succeed(config.onAcquireError(error)))));
|
|
219
|
+
};
|
|
220
|
+
const forkManagedResourceLifecycle = ({ config, ref: resourceRef, }) => Effect.gen(function* () {
|
|
162
221
|
const modelStream = Stream.concat(Stream.make(initModel), modelSubscriptionRef.changes);
|
|
163
|
-
|
|
164
|
-
|
|
222
|
+
const equivalence = Schema.equivalence(config.schema);
|
|
223
|
+
yield* Effect.forkDaemon(modelStream.pipe(Stream.map(config.modelToMaybeRequirements), Stream.changesWith(equivalence), Stream.flatMap(maybeRequirementsToLifecycle(config, resourceRef), {
|
|
224
|
+
switch: true,
|
|
225
|
+
}), Stream.runForEach(Effect.flatMap(enqueueMessage))));
|
|
226
|
+
});
|
|
227
|
+
yield* Effect.forEach(managedResourceRefs, forkManagedResourceLifecycle, {
|
|
165
228
|
concurrency: 'unbounded',
|
|
166
229
|
discard: true,
|
|
167
|
-
}));
|
|
168
|
-
}
|
|
169
|
-
const maybeRequirementsToLifecycle = (config, resourceRef) => (maybeRequirements) => {
|
|
170
|
-
if (Option.isOption(maybeRequirements) &&
|
|
171
|
-
Option.isNone(maybeRequirements)) {
|
|
172
|
-
return Stream.empty;
|
|
173
|
-
}
|
|
174
|
-
const requirements = Option.isOption(maybeRequirements)
|
|
175
|
-
? Option.getOrThrow(maybeRequirements)
|
|
176
|
-
: maybeRequirements;
|
|
177
|
-
const acquire = Effect.gen(function* () {
|
|
178
|
-
const value = yield* config.acquire(requirements);
|
|
179
|
-
yield* Ref.set(resourceRef, Option.some(value));
|
|
180
|
-
return value;
|
|
181
230
|
});
|
|
182
|
-
|
|
183
|
-
yield*
|
|
184
|
-
yield* Ref.set(
|
|
185
|
-
yield*
|
|
186
|
-
}).
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
})
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
concurrency: 'unbounded',
|
|
198
|
-
discard: true,
|
|
199
|
-
});
|
|
200
|
-
yield* pipe(Effect.forever(Effect.gen(function* () {
|
|
201
|
-
const message = yield* Queue.take(messageQueue);
|
|
202
|
-
yield* processMessage(message);
|
|
203
|
-
})), Effect.catchAllCause(cause => Effect.sync(() => {
|
|
204
|
-
const squashed = Cause.squash(cause);
|
|
205
|
-
const appError = squashed instanceof Error
|
|
206
|
-
? squashed
|
|
207
|
-
: new Error(String(squashed));
|
|
208
|
-
renderErrorView(appError, errorView, container, maybeCurrentVNodeRef);
|
|
209
|
-
})));
|
|
210
|
-
}));
|
|
231
|
+
yield* pipe(Effect.forever(Effect.gen(function* () {
|
|
232
|
+
const message = yield* Queue.take(messageQueue);
|
|
233
|
+
yield* Ref.set(currentMessageRef, Option.some(message));
|
|
234
|
+
yield* processMessage(message);
|
|
235
|
+
})), Effect.catchAllCause(cause => Effect.sync(() => {
|
|
236
|
+
const squashed = Cause.squash(cause);
|
|
237
|
+
const appError = squashed instanceof Error
|
|
238
|
+
? squashed
|
|
239
|
+
: new Error(String(squashed));
|
|
240
|
+
const model = Ref.get(modelRef).pipe(Effect.runSync);
|
|
241
|
+
const message = Ref.get(currentMessageRef).pipe(Effect.runSync, Option.getOrThrow);
|
|
242
|
+
renderCrashView({ error: appError, model, message }, crash, container, maybeCurrentVNodeRef);
|
|
243
|
+
})));
|
|
244
|
+
}));
|
|
245
|
+
};
|
|
211
246
|
const patchVNode = (maybeCurrentVNode, nextVNodeNullish, container) => {
|
|
212
247
|
const nextVNode = Predicate.isNotNull(nextVNodeNullish)
|
|
213
248
|
? nextVNodeNullish
|
|
@@ -217,21 +252,29 @@ const patchVNode = (maybeCurrentVNode, nextVNodeNullish, container) => {
|
|
|
217
252
|
onSome: currentVNode => patch(currentVNode, nextVNode),
|
|
218
253
|
});
|
|
219
254
|
};
|
|
220
|
-
const
|
|
221
|
-
console.error('[foldkit] Application
|
|
255
|
+
const renderCrashView = (context, crash, container, maybeCurrentVNodeRef) => {
|
|
256
|
+
console.error('[foldkit] Application crash:', context.error);
|
|
257
|
+
if (crash?.report) {
|
|
258
|
+
try {
|
|
259
|
+
crash.report(context);
|
|
260
|
+
}
|
|
261
|
+
catch (reportError) {
|
|
262
|
+
console.error('[foldkit] crash.report failed:', reportError);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
222
265
|
try {
|
|
223
|
-
const
|
|
224
|
-
?
|
|
225
|
-
:
|
|
266
|
+
const crashHtml = crash?.view
|
|
267
|
+
? crash.view(context)
|
|
268
|
+
: defaultCrashView(context);
|
|
226
269
|
const maybeCurrentVNode = Ref.get(maybeCurrentVNodeRef).pipe(Effect.runSync);
|
|
227
|
-
const vnode =
|
|
270
|
+
const vnode = crashHtml.pipe(Effect.provideService(Dispatch, noOpDispatch), Effect.runSync);
|
|
228
271
|
patchVNode(maybeCurrentVNode, vnode, container);
|
|
229
272
|
}
|
|
230
273
|
catch (viewError) {
|
|
231
|
-
console.error('[foldkit]
|
|
274
|
+
console.error('[foldkit] crash.view failed:', viewError);
|
|
232
275
|
const maybeCurrentVNode = Ref.get(maybeCurrentVNodeRef).pipe(Effect.runSync);
|
|
233
276
|
const fallbackViewError = viewError instanceof Error ? viewError : new Error(String(viewError));
|
|
234
|
-
const vnode =
|
|
277
|
+
const vnode = defaultCrashView(context, fallbackViewError).pipe(Effect.provideService(Dispatch, noOpDispatch), Effect.runSync);
|
|
235
278
|
patchVNode(maybeCurrentVNode, vnode, container);
|
|
236
279
|
}
|
|
237
280
|
};
|
|
@@ -242,9 +285,9 @@ export function makeElement(config) {
|
|
|
242
285
|
view: config.view,
|
|
243
286
|
...(config.subscriptions && { subscriptions: config.subscriptions }),
|
|
244
287
|
container: config.container,
|
|
245
|
-
...(config.
|
|
246
|
-
...(Predicate.isNotUndefined(config.
|
|
247
|
-
|
|
288
|
+
...(config.crash && { crash: config.crash }),
|
|
289
|
+
...(Predicate.isNotUndefined(config.slowView) && {
|
|
290
|
+
slowView: config.slowView,
|
|
248
291
|
}),
|
|
249
292
|
...(config.resources && { resources: config.resources }),
|
|
250
293
|
...(config.managedResources && {
|
|
@@ -284,9 +327,9 @@ export function makeApplication(config) {
|
|
|
284
327
|
...(config.subscriptions && { subscriptions: config.subscriptions }),
|
|
285
328
|
container: config.container,
|
|
286
329
|
browser: config.browser,
|
|
287
|
-
...(config.
|
|
288
|
-
...(Predicate.isNotUndefined(config.
|
|
289
|
-
|
|
330
|
+
...(config.crash && { crash: config.crash }),
|
|
331
|
+
...(Predicate.isNotUndefined(config.slowView) && {
|
|
332
|
+
slowView: config.slowView,
|
|
290
333
|
}),
|
|
291
334
|
...(config.resources && { resources: config.resources }),
|
|
292
335
|
...(config.managedResources && {
|
|
@@ -322,13 +365,28 @@ const preserveModel = (model) => {
|
|
|
322
365
|
import.meta.hot.send('foldkit:preserve-model', model);
|
|
323
366
|
}
|
|
324
367
|
};
|
|
368
|
+
const PLUGIN_RESPONSE_TIMEOUT_MS = 500;
|
|
325
369
|
/** Starts a Foldkit runtime, with HMR support for development. */
|
|
326
370
|
export const run = (foldkitRuntime) => {
|
|
327
371
|
if (import.meta.hot) {
|
|
328
|
-
import.meta.hot
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
372
|
+
const hot = import.meta.hot;
|
|
373
|
+
const requestPreservedModel = pipe(Effect.async(resume => {
|
|
374
|
+
hot.on('foldkit:restore-model', model => {
|
|
375
|
+
resume(Effect.succeed(model));
|
|
376
|
+
});
|
|
377
|
+
hot.send('foldkit:request-model');
|
|
378
|
+
}), Effect.timeoutTo({
|
|
379
|
+
onTimeout: () => {
|
|
380
|
+
console.warn('[foldkit] No response from vite-plugin-foldkit. Add it to your vite.config.ts for HMR model preservation:\n\n' +
|
|
381
|
+
" import foldkit from 'vite-plugin-foldkit'\n\n" +
|
|
382
|
+
' export default defineConfig({ plugins: [foldkit()] })\n\n' +
|
|
383
|
+
'Starting without HMR support.');
|
|
384
|
+
return undefined;
|
|
385
|
+
},
|
|
386
|
+
onSuccess: Function.identity,
|
|
387
|
+
duration: PLUGIN_RESPONSE_TIMEOUT_MS,
|
|
388
|
+
}), Effect.flatMap(foldkitRuntime));
|
|
389
|
+
BrowserRuntime.runMain(requestPreservedModel);
|
|
332
390
|
}
|
|
333
391
|
else {
|
|
334
392
|
BrowserRuntime.runMain(foldkitRuntime());
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errorUI.d.ts","sourceRoot":"","sources":["../../src/runtime/errorUI.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,OAAO,EAAE,IAAI,EAAQ,MAAM,SAAS,CAAA;AAEpC,eAAO,MAAM,YAAY;8BACG,OAAO;6BACR,OAAO;CACjC,CAAA;AAmBD,eAAO,MAAM,gBAAgB,GAAI,OAAO,KAAK,EAAE,YAAY,OAAO,KAAG,IA4LpE,CAAA"}
|