foldkit 0.157.0 → 0.158.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/devTools/host.d.ts +2 -2
- package/dist/devTools/host.d.ts.map +1 -1
- package/dist/devTools/host.js +1 -1
- package/dist/experimental/machine/machine.d.ts +278 -84
- package/dist/experimental/machine/machine.d.ts.map +1 -1
- package/dist/experimental/machine/machine.js +362 -212
- package/dist/runtime/browserListeners.d.ts +7 -1
- package/dist/runtime/browserListeners.d.ts.map +1 -1
- package/dist/runtime/browserScheduler.d.ts +3 -0
- package/dist/runtime/browserScheduler.d.ts.map +1 -0
- package/dist/runtime/browserScheduler.js +22 -0
- package/dist/runtime/crashUI.d.ts +36 -5
- package/dist/runtime/crashUI.d.ts.map +1 -1
- package/dist/runtime/crashUI.js +55 -2
- package/dist/runtime/devToolsConfig.d.ts +85 -0
- package/dist/runtime/devToolsConfig.d.ts.map +1 -0
- package/dist/runtime/devToolsConfig.js +49 -0
- package/dist/runtime/devToolsIntegration.d.ts +56 -0
- package/dist/runtime/devToolsIntegration.d.ts.map +1 -0
- package/dist/runtime/devToolsIntegration.js +186 -0
- package/dist/runtime/dispatch.d.ts +10 -0
- package/dist/runtime/dispatch.d.ts.map +1 -0
- package/dist/runtime/dispatch.js +4 -0
- package/dist/runtime/documentMetadata.d.ts +3 -0
- package/dist/runtime/documentMetadata.d.ts.map +1 -0
- package/dist/runtime/documentMetadata.js +148 -0
- package/dist/runtime/duplicateIdScanner.d.ts +16 -0
- package/dist/runtime/duplicateIdScanner.d.ts.map +1 -0
- package/dist/runtime/duplicateIdScanner.js +70 -0
- package/dist/runtime/hmrModelBridge.d.ts +4 -0
- package/dist/runtime/hmrModelBridge.d.ts.map +1 -0
- package/dist/runtime/hmrModelBridge.js +43 -0
- package/dist/runtime/hostConnector.d.ts +61 -0
- package/dist/runtime/hostConnector.d.ts.map +1 -0
- package/dist/runtime/hostConnector.js +141 -0
- package/dist/runtime/hydrationHandoff.d.ts +44 -0
- package/dist/runtime/hydrationHandoff.d.ts.map +1 -0
- package/dist/runtime/hydrationHandoff.js +395 -0
- package/dist/runtime/index.d.ts +16 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +5 -1
- package/dist/runtime/makeApplication.d.ts +71 -0
- package/dist/runtime/makeApplication.d.ts.map +1 -0
- package/dist/runtime/makeApplication.js +98 -0
- package/dist/runtime/makeElement.d.ts +69 -0
- package/dist/runtime/makeElement.d.ts.map +1 -0
- package/dist/runtime/makeElement.js +81 -0
- package/dist/runtime/managedResourceFibers.d.ts +18 -0
- package/dist/runtime/managedResourceFibers.d.ts.map +1 -0
- package/dist/runtime/managedResourceFibers.js +47 -0
- package/dist/runtime/messageQueue.d.ts +27 -0
- package/dist/runtime/messageQueue.d.ts.map +1 -0
- package/dist/runtime/messageQueue.js +133 -0
- package/dist/runtime/public.d.ts +2 -3
- package/dist/runtime/public.d.ts.map +1 -1
- package/dist/runtime/public.js +1 -1
- package/dist/runtime/renderer.d.ts +69 -0
- package/dist/runtime/renderer.d.ts.map +1 -0
- package/dist/runtime/renderer.js +512 -0
- package/dist/runtime/resourceProvider.d.ts +33 -0
- package/dist/runtime/resourceProvider.d.ts.map +1 -0
- package/dist/runtime/resourceProvider.js +98 -0
- package/dist/runtime/runtime.d.ts +133 -412
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +118 -2184
- package/dist/runtime/runtimeStatus.d.ts +15 -0
- package/dist/runtime/runtimeStatus.d.ts.map +1 -0
- package/dist/runtime/runtimeStatus.js +6 -0
- package/dist/runtime/slowPhase.d.ts +82 -0
- package/dist/runtime/slowPhase.d.ts.map +1 -0
- package/dist/runtime/slowPhase.js +83 -0
- package/dist/runtime/start.d.ts +96 -0
- package/dist/runtime/start.d.ts.map +1 -0
- package/dist/runtime/start.js +99 -0
- package/dist/runtime/subscriptionFibers.d.ts +21 -0
- package/dist/runtime/subscriptionFibers.d.ts.map +1 -0
- package/dist/runtime/subscriptionFibers.js +47 -0
- package/dist/runtime/visibility.d.ts +8 -0
- package/dist/runtime/visibility.d.ts.map +1 -0
- package/dist/runtime/visibility.js +6 -0
- package/dist/test/scene.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three flags that gate Message processing and rendering. The boot
|
|
3
|
+
* closure creates one per runtime and shares it with the units it builds,
|
|
4
|
+
* so the unit that sets a flag and the units that read it see one value.
|
|
5
|
+
* A plain mutable object rather than a Ref, because every reader and
|
|
6
|
+
* writer runs synchronously on the main thread.
|
|
7
|
+
*/
|
|
8
|
+
export type RuntimeStatus = {
|
|
9
|
+
isRuntimeDisposed: boolean;
|
|
10
|
+
isRenderingFrame: boolean;
|
|
11
|
+
isCrashed: boolean;
|
|
12
|
+
};
|
|
13
|
+
/** A fresh status: not disposed, not crashed, and no frame on the stack. */
|
|
14
|
+
export declare const makeRuntimeStatus: () => RuntimeStatus;
|
|
15
|
+
//# sourceMappingURL=runtimeStatus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtimeStatus.d.ts","sourceRoot":"","sources":["../../src/runtime/runtimeStatus.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG;IAM1B,iBAAiB,EAAE,OAAO,CAAA;IAO1B,gBAAgB,EAAE,OAAO,CAAA;IAIzB,SAAS,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,QAAO,aAInC,CAAA"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Option, Schema } from 'effect';
|
|
2
|
+
import type { Visibility } from './visibility.js';
|
|
3
|
+
/** Context provided when view construction exceeds its configured time budget. */
|
|
4
|
+
export type SlowViewContext<Model, Message> = Readonly<{
|
|
5
|
+
_tag: 'View';
|
|
6
|
+
model: Model;
|
|
7
|
+
message: Option.Option<Message>;
|
|
8
|
+
durationMs: number;
|
|
9
|
+
thresholdMs: number;
|
|
10
|
+
}>;
|
|
11
|
+
/** Context provided when update exceeds its configured time budget. */
|
|
12
|
+
export type SlowUpdateContext<Model, Message> = Readonly<{
|
|
13
|
+
_tag: 'Update';
|
|
14
|
+
previousModel: Model;
|
|
15
|
+
nextModel: Model;
|
|
16
|
+
message: Message;
|
|
17
|
+
durationMs: number;
|
|
18
|
+
thresholdMs: number;
|
|
19
|
+
}>;
|
|
20
|
+
/** Context provided when DOM patching exceeds its configured time budget. */
|
|
21
|
+
export type SlowPatchContext<Model, Message> = Readonly<{
|
|
22
|
+
_tag: 'Patch';
|
|
23
|
+
model: Model;
|
|
24
|
+
message: Option.Option<Message>;
|
|
25
|
+
durationMs: number;
|
|
26
|
+
thresholdMs: number;
|
|
27
|
+
}>;
|
|
28
|
+
/** Context provided when subscription dependency extraction exceeds its configured time budget. */
|
|
29
|
+
export type SlowSubscriptionDependenciesContext<Model> = Readonly<{
|
|
30
|
+
_tag: 'SubscriptionDependencies';
|
|
31
|
+
subscriptionKey: string;
|
|
32
|
+
model: Model;
|
|
33
|
+
durationMs: number;
|
|
34
|
+
thresholdMs: number;
|
|
35
|
+
}>;
|
|
36
|
+
/** Tagged union of every slow-phase context passed to `slow.onSlow`. */
|
|
37
|
+
export type SlowContext<Model, Message> = SlowViewContext<Model, Message> | SlowUpdateContext<Model, Message> | SlowPatchContext<Model, Message> | SlowSubscriptionDependenciesContext<Model>;
|
|
38
|
+
/** Phase names measured by the slow warning runtime option. */
|
|
39
|
+
export declare const SlowPhase: Schema.Literals<readonly ["Update", "View", "Patch", "SubscriptionDependencies"]>;
|
|
40
|
+
export type SlowPhase = typeof SlowPhase.Type;
|
|
41
|
+
/** Budget overrides for slow warning phases. Omitted fields use Foldkit defaults. */
|
|
42
|
+
export type SlowThresholdOverrides = Readonly<{
|
|
43
|
+
Update?: number;
|
|
44
|
+
View?: number;
|
|
45
|
+
Patch?: number;
|
|
46
|
+
SubscriptionDependencies?: number;
|
|
47
|
+
}>;
|
|
48
|
+
/** One measured phase: its budget and the callback fired when it is exceeded. */
|
|
49
|
+
export type ResolvedSlowPhaseConfig<Context> = Readonly<{
|
|
50
|
+
thresholdMs: number;
|
|
51
|
+
onSlow: (context: Context) => void;
|
|
52
|
+
}>;
|
|
53
|
+
type ResolvedSlowConfig<Model, Message> = Readonly<{
|
|
54
|
+
view: Option.Option<ResolvedSlowPhaseConfig<SlowViewContext<Model, Message>>>;
|
|
55
|
+
update: Option.Option<ResolvedSlowPhaseConfig<SlowUpdateContext<Model, Message>>>;
|
|
56
|
+
patch: Option.Option<ResolvedSlowPhaseConfig<SlowPatchContext<Model, Message>>>;
|
|
57
|
+
subscriptionDependencies: Option.Option<ResolvedSlowPhaseConfig<SlowSubscriptionDependenciesContext<Model>>>;
|
|
58
|
+
}>;
|
|
59
|
+
/**
|
|
60
|
+
* Slow-phase warning configuration.
|
|
61
|
+
*
|
|
62
|
+
* By default, all phases are enabled in development with Foldkit's default
|
|
63
|
+
* thresholds. Pass `false` to disable warnings entirely. Pass an object to
|
|
64
|
+
* refine those defaults.
|
|
65
|
+
*
|
|
66
|
+
* - `show`: `'Development'` (default) enables warnings only when Vite HMR is active. `'Always'` enables them in every environment.
|
|
67
|
+
* - `measuredPhases`: Phases to measure. Defaults to every slow warning phase.
|
|
68
|
+
* - `thresholdOverrides`: Per-phase budget overrides. Omitted fields keep defaults; overrides for unmeasured phases are ignored.
|
|
69
|
+
* - `onSlow`: Callback for every measured phase that exceeds its budget. Replaces Foldkit's default `console.warn`; Foldkit will not also warn for tags your callback ignores.
|
|
70
|
+
*/
|
|
71
|
+
export type SlowConfig<Model, Message> = false | Readonly<{
|
|
72
|
+
show?: Visibility;
|
|
73
|
+
measuredPhases?: ReadonlyArray<SlowPhase>;
|
|
74
|
+
thresholdOverrides?: SlowThresholdOverrides;
|
|
75
|
+
onSlow?: (context: SlowContext<Model, Message>) => void;
|
|
76
|
+
}>;
|
|
77
|
+
export declare const __resolveSlowConfig: <Model, Message>(slow: SlowConfig<Model, Message> | undefined, isSlowVisible: (show: Visibility) => boolean) => Option.Option<ResolvedSlowConfig<Model, Message>>;
|
|
78
|
+
export declare const measureSlowPhase: <Context, Result>(maybeConfig: Option.Option<ResolvedSlowPhaseConfig<Context>>, run: () => Result) => readonly [Result, Option.Option<number>];
|
|
79
|
+
export declare const reportSlowPhase: <Context>(maybeConfig: Option.Option<ResolvedSlowPhaseConfig<Context>>, maybeDurationMs: Option.Option<number>, makeContext: (durationMs: number, thresholdMs: number) => Context) => void;
|
|
80
|
+
export declare const defaultSlowCallback: (context: SlowContext<unknown, unknown>) => void;
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=slowPhase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slowPhase.d.ts","sourceRoot":"","sources":["../../src/runtime/slowPhase.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Array, Match, Option, Predicate, Record, Schema, pipe } from 'effect';
|
|
2
|
+
/** Phase names measured by the slow warning runtime option. */
|
|
3
|
+
export const SlowPhase = Schema.Literals([
|
|
4
|
+
'Update',
|
|
5
|
+
'View',
|
|
6
|
+
'Patch',
|
|
7
|
+
'SubscriptionDependencies',
|
|
8
|
+
]);
|
|
9
|
+
const DEFAULT_SLOW_SHOW = 'Development';
|
|
10
|
+
const DEFAULT_SLOW_VIEW_THRESHOLD_MS = 16;
|
|
11
|
+
const DEFAULT_SLOW_UPDATE_THRESHOLD_MS = 4;
|
|
12
|
+
const DEFAULT_SLOW_PATCH_THRESHOLD_MS = 8;
|
|
13
|
+
const DEFAULT_SLOW_SUBSCRIPTION_DEPENDENCIES_THRESHOLD_MS = 2;
|
|
14
|
+
const ALL_SLOW_PHASES = [
|
|
15
|
+
'Update',
|
|
16
|
+
'View',
|
|
17
|
+
'Patch',
|
|
18
|
+
'SubscriptionDependencies',
|
|
19
|
+
];
|
|
20
|
+
const resolveSlowPhase = (isMeasured, thresholdMs, onSlow) => Option.liftPredicate({
|
|
21
|
+
thresholdMs,
|
|
22
|
+
onSlow,
|
|
23
|
+
}, () => isMeasured);
|
|
24
|
+
export const __resolveSlowConfig = (slow, isSlowVisible) => {
|
|
25
|
+
const maybeSlowConfig = Match.value(slow).pipe(Match.withReturnType(), Match.when(false, () => Option.none()), Match.when(Predicate.isUndefined, () => Option.some({})), Match.orElse(config => Option.some(config)));
|
|
26
|
+
return pipe(maybeSlowConfig, Option.filter(config => isSlowVisible(config.show ?? DEFAULT_SLOW_SHOW)), Option.map(config => {
|
|
27
|
+
const onSlow = config.onSlow ?? defaultSlowCallback;
|
|
28
|
+
const measuredPhases = config.measuredPhases ?? ALL_SLOW_PHASES;
|
|
29
|
+
const isPhaseMeasured = (phase) => Array.contains(measuredPhases, phase);
|
|
30
|
+
return {
|
|
31
|
+
view: resolveSlowPhase(isPhaseMeasured('View'), config.thresholdOverrides?.View ?? DEFAULT_SLOW_VIEW_THRESHOLD_MS, onSlow),
|
|
32
|
+
update: resolveSlowPhase(isPhaseMeasured('Update'), config.thresholdOverrides?.Update ?? DEFAULT_SLOW_UPDATE_THRESHOLD_MS, onSlow),
|
|
33
|
+
patch: resolveSlowPhase(isPhaseMeasured('Patch'), config.thresholdOverrides?.Patch ?? DEFAULT_SLOW_PATCH_THRESHOLD_MS, onSlow),
|
|
34
|
+
subscriptionDependencies: resolveSlowPhase(isPhaseMeasured('SubscriptionDependencies'), config.thresholdOverrides?.SubscriptionDependencies ??
|
|
35
|
+
DEFAULT_SLOW_SUBSCRIPTION_DEPENDENCIES_THRESHOLD_MS, onSlow),
|
|
36
|
+
};
|
|
37
|
+
}));
|
|
38
|
+
};
|
|
39
|
+
export const measureSlowPhase = (maybeConfig, run) => {
|
|
40
|
+
if (Option.isSome(maybeConfig)) {
|
|
41
|
+
const start = performance.now();
|
|
42
|
+
const result = run();
|
|
43
|
+
return [result, Option.some(performance.now() - start)];
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
return [run(), Option.none()];
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export const reportSlowPhase = (maybeConfig, maybeDurationMs, makeContext) => {
|
|
50
|
+
if (Option.isSome(maybeConfig)) {
|
|
51
|
+
const { thresholdMs, onSlow } = maybeConfig.value;
|
|
52
|
+
const maybeExceededDuration = Option.filter(maybeDurationMs, durationMs => durationMs > thresholdMs);
|
|
53
|
+
if (Option.isSome(maybeExceededDuration)) {
|
|
54
|
+
onSlow(makeContext(maybeExceededDuration.value, thresholdMs));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const messageTag = (rawMessage) => pipe(rawMessage, Option.liftPredicate(Predicate.isObject), Option.flatMap(Record.get('_tag')), Option.match({
|
|
59
|
+
onNone: () => 'unknown',
|
|
60
|
+
onSome: String,
|
|
61
|
+
}));
|
|
62
|
+
const optionMessageTrigger = (maybeMessage) => Option.match(maybeMessage, {
|
|
63
|
+
onNone: () => 'init',
|
|
64
|
+
onSome: messageTag,
|
|
65
|
+
});
|
|
66
|
+
const TUNING_HINT = 'Set slow.thresholdOverrides to change budgets or pass slow: false to disable warnings.';
|
|
67
|
+
export const defaultSlowCallback = (context) => {
|
|
68
|
+
const { durationMs, thresholdMs: budget } = context;
|
|
69
|
+
const duration = durationMs.toFixed(1);
|
|
70
|
+
const summary = Match.value(context).pipe(Match.tagsExhaustive({
|
|
71
|
+
View: ({ message }) => `Slow view: ${duration}ms (budget: ${budget}ms), triggered by ${optionMessageTrigger(message)}. Keep render-only work in the view path and memoize expensive subtrees with createLazy or createKeyedLazy.`,
|
|
72
|
+
Update: ({ message }) => `Slow update: ${duration}ms (budget: ${budget}ms), triggered by ${messageTag(message)}. Inspect the triggering Message branch; move render-only derivations to memoized views and keep update focused on state transitions.`,
|
|
73
|
+
Patch: ({ message }) => `Slow patch: ${duration}ms (budget: ${budget}ms), triggered by ${optionMessageTrigger(message)}. Key mapped lists by stable ids, split large views, or memoize stable subtrees with createLazy.`,
|
|
74
|
+
SubscriptionDependencies: ({ subscriptionKey }) => `Slow subscription dependencies: ${duration}ms (budget: ${budget}ms) for subscription "${subscriptionKey}". Keep modelToDependencies a cheap projection from modeled fields; avoid scans, sorting, serialization, and large dependency objects.`,
|
|
75
|
+
}));
|
|
76
|
+
const maybeRawMessage = Match.value(context).pipe(Match.withReturnType(), Match.tagsExhaustive({
|
|
77
|
+
Update: ({ message }) => Option.some(message),
|
|
78
|
+
View: ({ message }) => message,
|
|
79
|
+
Patch: ({ message }) => message,
|
|
80
|
+
SubscriptionDependencies: () => Option.none(),
|
|
81
|
+
}));
|
|
82
|
+
console.warn(`[foldkit] ${summary} ${TUNING_HINT}`, context, ...Option.toArray(maybeRawMessage));
|
|
83
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import type { Ports } from '../port/index.js';
|
|
3
|
+
import { type EmbedHandle } from './hostConnector.js';
|
|
4
|
+
import type { BootMode } from './hydrationHandoff.js';
|
|
5
|
+
import { type MakeRuntimeReturn } from './runtime.js';
|
|
6
|
+
/** Client-only startup input for an application that declares Flags. Pass it
|
|
7
|
+
* to `run` or `embed`; `hydrate` instead decodes the exact Flags value
|
|
8
|
+
* embedded by the server render.
|
|
9
|
+
*/
|
|
10
|
+
export type RunOptions<Flags, Resources = never> = Readonly<{
|
|
11
|
+
flags: Effect.Effect<Flags, never, Resources>;
|
|
12
|
+
}>;
|
|
13
|
+
type RuntimeProgram = Readonly<{
|
|
14
|
+
runtimeId: string;
|
|
15
|
+
start: (hmrModel?: unknown) => Effect.Effect<void>;
|
|
16
|
+
ports: Ports | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
/** Starts a program Effect with explicit boot inputs for runtime tests.
|
|
19
|
+
* @internal */
|
|
20
|
+
export declare const __startProgram: (program: RuntimeProgram, hmrModel: unknown, bootMode: BootMode, flags?: Effect.Effect<unknown, never, any>, buildId?: string) => Effect.Effect<void>;
|
|
21
|
+
/** Starts a Foldkit runtime that owns the page for the page's whole lifetime,
|
|
22
|
+
* with HMR support for development. The first render builds the DOM fresh in
|
|
23
|
+
* the container, replacing whatever is there. On a server-rendered page use
|
|
24
|
+
* `hydrate` instead, which adopts the existing DOM. To start a runtime under a
|
|
25
|
+
* host-controlled lifecycle, use `embed`. */
|
|
26
|
+
export declare function run<P extends Ports | undefined, Resources, Kind extends 'Application' | 'Element'>(program: MakeRuntimeReturn<P, void, Resources, Kind>): void;
|
|
27
|
+
export declare function run<P extends Ports | undefined, Flags, Resources, Kind extends 'Application' | 'Element'>(program: MakeRuntimeReturn<P, Flags, Resources, Kind>, options: RunOptions<Flags, Resources>): void;
|
|
28
|
+
/** Options for {@link hydrate}. */
|
|
29
|
+
export type HydrateOptions = Readonly<{
|
|
30
|
+
/**
|
|
31
|
+
* The deployment this client belongs to, compared against the id the server
|
|
32
|
+
* stamped on the root before the Flags payload text is accessed or decoded,
|
|
33
|
+
* so a page from a different deployment stops startup rather than handing its
|
|
34
|
+
* Flags to this build. Required, and must be non-empty: an absent id would
|
|
35
|
+
* equal the absent marker on a page served before build ids existed.
|
|
36
|
+
*
|
|
37
|
+
* Pass `import.meta.env.FOLDKIT_BUILD_ID`, which `@foldkit/vite-plugin` fills
|
|
38
|
+
* from its `buildId` option or the `FOLDKIT_BUILD_ID` environment variable,
|
|
39
|
+
* the same value the server entry passes to `renderToString`.
|
|
40
|
+
*/
|
|
41
|
+
buildId: string;
|
|
42
|
+
}>;
|
|
43
|
+
/** Starts a Foldkit runtime by adopting a server-rendered DOM in place instead
|
|
44
|
+
* of building it fresh. Use this as the client entry for a page served by
|
|
45
|
+
* `renderToString`: the first render attaches to the stamped root, keeps the
|
|
46
|
+
* existing nodes, and reconstructs the Model from the Flags the server
|
|
47
|
+
* embedded. The handoff is strict: a missing server root, an empty or
|
|
48
|
+
* duplicated root stamp, more than one stamped root, a requested root outside
|
|
49
|
+
* the document body light DOM, a missing Flags payload, an undecodable
|
|
50
|
+
* payload, or a page from another deployment terminates startup. Every one of
|
|
51
|
+
* those contains the page first:
|
|
52
|
+
* the document's body is marked `inert` and a nondismissable modal shield is
|
|
53
|
+
* opened above existing top-layer content, so pointer and physical keyboard
|
|
54
|
+
* input do not activate same-document native links, forms, or controls.
|
|
55
|
+
* Containment leaves author-owned dialogs open without calling `close` or
|
|
56
|
+
* dispatching `cancel`.
|
|
57
|
+
* Nothing is moved, so no custom element reconnects and no frame reloads.
|
|
58
|
+
* This is not a script, event, or embedded-document sandbox: existing
|
|
59
|
+
* capture-phase handlers, browser-generated top-layer events, timers, and
|
|
60
|
+
* stale scripts can still run. Controls in embedded documents can still
|
|
61
|
+
* receive input if stale code focuses them. Stale code can also open newer
|
|
62
|
+
* top-layer UI. Use `run` in a separate client-only entry when the page should
|
|
63
|
+
* boot without server output.
|
|
64
|
+
*
|
|
65
|
+
* @experimental Server rendering and hydration are experimental while their
|
|
66
|
+
* contracts settle. */
|
|
67
|
+
export declare const hydrate: <P extends Ports | undefined, Flags, Resources>(program: MakeRuntimeReturn<P, Flags, Resources, 'Application'>, options: HydrateOptions) => void;
|
|
68
|
+
/**
|
|
69
|
+
* Starts a Foldkit runtime under a host-controlled lifecycle and returns an
|
|
70
|
+
* `EmbedHandle`. This is the entry point for embedding a Foldkit app inside
|
|
71
|
+
* another application: the host pushes values in through the handle's inbound
|
|
72
|
+
* Ports, listens to outbound Ports, and calls `dispose` when it unmounts the
|
|
73
|
+
* app. The host never touches the Model or dispatches Messages directly; the
|
|
74
|
+
* Schema-typed Ports are the whole boundary.
|
|
75
|
+
*
|
|
76
|
+
* Works with programs from both `makeApplication` and `makeElement`; for a
|
|
77
|
+
* widget on a page the host owns, `makeElement` is the natural fit.
|
|
78
|
+
*
|
|
79
|
+
* A program can be embedded once at a time (it owns one container). After
|
|
80
|
+
* `dispose`, the same container can be embedded again with a fresh program.
|
|
81
|
+
*
|
|
82
|
+
* ```ts
|
|
83
|
+
* const handle = Runtime.embed(element)
|
|
84
|
+
*
|
|
85
|
+
* handle.ports.stepChanged.send(5)
|
|
86
|
+
* const unsubscribe = handle.ports.countChanged.subscribe(count => {
|
|
87
|
+
* console.log(count)
|
|
88
|
+
* })
|
|
89
|
+
*
|
|
90
|
+
* handle.dispose()
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare function embed<P extends Ports | undefined = undefined, Resources = never, Kind extends 'Application' | 'Element' = 'Application' | 'Element'>(program: MakeRuntimeReturn<P, void, Resources, Kind>): EmbedHandle<P>;
|
|
94
|
+
export declare function embed<P extends Ports | undefined, Flags, Resources, Kind extends 'Application' | 'Element'>(program: MakeRuntimeReturn<P, Flags, Resources, Kind>, options: RunOptions<Flags, Resources>): EmbedHandle<P>;
|
|
95
|
+
export {};
|
|
96
|
+
//# sourceMappingURL=start.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/runtime/start.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EAOP,MAAM,QAAQ,CAAA;AAEf,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAG7C,OAAO,EACL,KAAK,WAAW,EAGjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,cAAc,CAAA;AAEvE;;;GAGG;AACH,MAAM,MAAM,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,IAAI,QAAQ,CAAC;IAC1D,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAA;CAC9C,CAAC,CAAA;AAEF,KAAK,cAAc,GAAG,QAAQ,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAClD,KAAK,EAAE,KAAK,GAAG,SAAS,CAAA;CACzB,CAAC,CAAA;AAEF;eACe;AACf,eAAO,MAAM,cAAc,YAChB,cAAc,YACb,OAAO,YACP,QAAQ,UACV,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,YAChC,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,CAqBpB,CAAA;AA6BD;;;;8CAI8C;AAC9C,wBAAgB,GAAG,CACjB,CAAC,SAAS,KAAK,GAAG,SAAS,EAC3B,SAAS,EACT,IAAI,SAAS,aAAa,GAAG,SAAS,EACtC,OAAO,EAAE,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,IAAI,CAAA;AAC7D,wBAAgB,GAAG,CACjB,CAAC,SAAS,KAAK,GAAG,SAAS,EAC3B,KAAK,EACL,SAAS,EACT,IAAI,SAAS,aAAa,GAAG,SAAS,EAEtC,OAAO,EAAE,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,EACrD,OAAO,EAAE,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,GACpC,IAAI,CAAA;AAQP,mCAAmC;AACnC,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC;;;;;;;;;;OAUG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB,CAAC,CAAA;AAEF;;;;;;;;;;;;;;;;;;;;;;;uBAuBuB;AACvB,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,KAAK,GAAG,SAAS,EAAE,KAAK,EAAE,SAAS,WAC1D,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,aAAa,CAAC,WACrD,cAAc,KACtB,IAEF,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,KAAK,CACnB,CAAC,SAAS,KAAK,GAAG,SAAS,GAAG,SAAS,EACvC,SAAS,GAAG,KAAK,EACjB,IAAI,SAAS,aAAa,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,EAClE,OAAO,EAAE,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;AACvE,wBAAgB,KAAK,CACnB,CAAC,SAAS,KAAK,GAAG,SAAS,EAC3B,KAAK,EACL,SAAS,EACT,IAAI,SAAS,aAAa,GAAG,SAAS,EAEtC,OAAO,EAAE,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,EACrD,OAAO,EAAE,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,GACpC,WAAW,CAAC,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Effect, Fiber, Function, Option, Predicate, Runtime, pipe, } from 'effect';
|
|
2
|
+
import { provideBrowserScheduler } from './browserScheduler.js';
|
|
3
|
+
import { resolveHmrModel } from './hmrModelBridge.js';
|
|
4
|
+
import { buildPortHandles, makeHostConnector, } from './hostConnector.js';
|
|
5
|
+
import { runtimeInternals } from './runtime.js';
|
|
6
|
+
/** Starts a program Effect with explicit boot inputs for runtime tests.
|
|
7
|
+
* @internal */
|
|
8
|
+
export const __startProgram = (program, hmrModel, bootMode, flags, buildId) => {
|
|
9
|
+
const internals = runtimeInternals.get(program);
|
|
10
|
+
if (Predicate.isUndefined(internals)) {
|
|
11
|
+
return Effect.die(new Error('[foldkit] Runtime boot expects a program created by ' +
|
|
12
|
+
'makeApplication or makeElement.'));
|
|
13
|
+
}
|
|
14
|
+
if (bootMode === 'Hydrate' && internals.kind !== 'Application') {
|
|
15
|
+
return Effect.die(new Error('[foldkit] Runtime.hydrate expects a program created by ' +
|
|
16
|
+
'makeApplication.'));
|
|
17
|
+
}
|
|
18
|
+
return internals.startWith(Option.none(), hmrModel, bootMode, flags, buildId);
|
|
19
|
+
};
|
|
20
|
+
// NOTE: deliberately not `BrowserRuntime.runMain`, which interrupts the
|
|
21
|
+
// runtime on `beforeunload`. `beforeunload` is a question, not a commitment:
|
|
22
|
+
// the browser also fires it for a click on a download link, for a navigation
|
|
23
|
+
// the user cancels, and when freezing the page into the back/forward cache.
|
|
24
|
+
// The document survives all three, but the interrupt finalizer has already
|
|
25
|
+
// put the container element back empty, so the page is left alive with no app
|
|
26
|
+
// in it. A page-owning runtime gains nothing from tearing itself down while
|
|
27
|
+
// the document is on its way out, so it starts with no page-lifecycle
|
|
28
|
+
// interrupt at all and lets the document take the runtime with it. Error
|
|
29
|
+
// reporting and the keep-alive interval come from `makeRunMain` either way.
|
|
30
|
+
const runMainWithoutUnloadInterrupt = Runtime.makeRunMain(Function.constVoid);
|
|
31
|
+
const startProgram = (program, bootMode, flags, buildId) => {
|
|
32
|
+
runMainWithoutUnloadInterrupt(provideBrowserScheduler(Effect.flatMap(resolveHmrModel(program.runtimeId), hmrModel => __startProgram(program, hmrModel, bootMode, flags, buildId))));
|
|
33
|
+
};
|
|
34
|
+
export function run(program, options) {
|
|
35
|
+
startProgram(program, 'Fresh', options?.flags);
|
|
36
|
+
}
|
|
37
|
+
/** Starts a Foldkit runtime by adopting a server-rendered DOM in place instead
|
|
38
|
+
* of building it fresh. Use this as the client entry for a page served by
|
|
39
|
+
* `renderToString`: the first render attaches to the stamped root, keeps the
|
|
40
|
+
* existing nodes, and reconstructs the Model from the Flags the server
|
|
41
|
+
* embedded. The handoff is strict: a missing server root, an empty or
|
|
42
|
+
* duplicated root stamp, more than one stamped root, a requested root outside
|
|
43
|
+
* the document body light DOM, a missing Flags payload, an undecodable
|
|
44
|
+
* payload, or a page from another deployment terminates startup. Every one of
|
|
45
|
+
* those contains the page first:
|
|
46
|
+
* the document's body is marked `inert` and a nondismissable modal shield is
|
|
47
|
+
* opened above existing top-layer content, so pointer and physical keyboard
|
|
48
|
+
* input do not activate same-document native links, forms, or controls.
|
|
49
|
+
* Containment leaves author-owned dialogs open without calling `close` or
|
|
50
|
+
* dispatching `cancel`.
|
|
51
|
+
* Nothing is moved, so no custom element reconnects and no frame reloads.
|
|
52
|
+
* This is not a script, event, or embedded-document sandbox: existing
|
|
53
|
+
* capture-phase handlers, browser-generated top-layer events, timers, and
|
|
54
|
+
* stale scripts can still run. Controls in embedded documents can still
|
|
55
|
+
* receive input if stale code focuses them. Stale code can also open newer
|
|
56
|
+
* top-layer UI. Use `run` in a separate client-only entry when the page should
|
|
57
|
+
* boot without server output.
|
|
58
|
+
*
|
|
59
|
+
* @experimental Server rendering and hydration are experimental while their
|
|
60
|
+
* contracts settle. */
|
|
61
|
+
export const hydrate = (program, options) => {
|
|
62
|
+
startProgram(program, 'Hydrate', undefined, options?.buildId);
|
|
63
|
+
};
|
|
64
|
+
export function embed(program, options) {
|
|
65
|
+
const internals = runtimeInternals.get(program);
|
|
66
|
+
if (Predicate.isUndefined(internals)) {
|
|
67
|
+
throw new Error('[foldkit] embed expects a program created by makeApplication or makeElement.');
|
|
68
|
+
}
|
|
69
|
+
if (internals.isEmbedActive) {
|
|
70
|
+
throw new Error('[foldkit] This program is already embedded. Dispose the existing ' +
|
|
71
|
+
'handle first, or create a separate program: each program owns one ' +
|
|
72
|
+
'container.');
|
|
73
|
+
}
|
|
74
|
+
internals.isEmbedActive = true;
|
|
75
|
+
const connector = makeHostConnector();
|
|
76
|
+
// NOTE: a dispose immediately followed by a fresh embed (React strict mode
|
|
77
|
+
// runs effects exactly that way) must not start the new runtime while the
|
|
78
|
+
// old one is still tearing down: the teardown finalizer is what puts the
|
|
79
|
+
// container element back in the DOM. Awaiting the previous fiber's exit
|
|
80
|
+
// sequences the two.
|
|
81
|
+
const startEffect = pipe(Option.match(internals.maybeActiveFiber, {
|
|
82
|
+
onNone: () => Effect.void,
|
|
83
|
+
onSome: previousFiber => Effect.asVoid(Fiber.await(previousFiber)),
|
|
84
|
+
}), Effect.andThen(resolveHmrModel(program.runtimeId)), Effect.flatMap(hmrModel => internals.startWith(Option.some(connector), hmrModel, 'Fresh', options?.flags)));
|
|
85
|
+
const fiber = Effect.runFork(provideBrowserScheduler(startEffect));
|
|
86
|
+
internals.maybeActiveFiber = Option.some(fiber);
|
|
87
|
+
let isHandleDisposed = false;
|
|
88
|
+
const dispose = () => {
|
|
89
|
+
if (isHandleDisposed) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
isHandleDisposed = true;
|
|
93
|
+
connector.dispose();
|
|
94
|
+
internals.isEmbedActive = false;
|
|
95
|
+
Effect.runFork(Fiber.interrupt(fiber));
|
|
96
|
+
};
|
|
97
|
+
const ports = buildPortHandles(program.ports, connector);
|
|
98
|
+
return { ports, dispose };
|
|
99
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Cause, Effect, Option, PubSub, type Scope } from 'effect';
|
|
2
|
+
import type { Subscriptions } from '../subscription/subscription.js';
|
|
3
|
+
import { type ResolvedSlowPhaseConfig, type SlowSubscriptionDependenciesContext } from './slowPhase.js';
|
|
4
|
+
/**
|
|
5
|
+
* Forks one fiber per Subscription into the runtime scope. Each fiber
|
|
6
|
+
* derives the Subscription's dependencies from every Model, restarts its
|
|
7
|
+
* stream when they change under its equivalence, and feeds the Messages
|
|
8
|
+
* the stream emits into the queue. A defect anywhere in that pipeline
|
|
9
|
+
* crashes the runtime instead of dying silently in the fiber.
|
|
10
|
+
*/
|
|
11
|
+
export declare const forkSubscriptionFibers: <Model, Message, Services>({ subscriptions, initModel, modelPubSub, runtimeScope, maybeSlowSubscriptionDependencies, enqueueMessageEffect, provideAllResources, crashWith, }: Readonly<{
|
|
12
|
+
subscriptions: Subscriptions<Model, Message, Services>;
|
|
13
|
+
initModel: Model;
|
|
14
|
+
modelPubSub: PubSub.PubSub<Model>;
|
|
15
|
+
runtimeScope: Scope.Scope;
|
|
16
|
+
maybeSlowSubscriptionDependencies: Option.Option<ResolvedSlowPhaseConfig<SlowSubscriptionDependenciesContext<Model>>>;
|
|
17
|
+
enqueueMessageEffect: (message: Message) => Effect.Effect<void>;
|
|
18
|
+
provideAllResources: <A>(effect: Effect.Effect<A, never, Services>) => Effect.Effect<A>;
|
|
19
|
+
crashWith: (cause: Cause.Cause<never>, maybeMessage: Option.Option<Message>) => Effect.Effect<void>;
|
|
20
|
+
}>) => Effect.Effect<void>;
|
|
21
|
+
//# sourceMappingURL=subscriptionFibers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriptionFibers.d.ts","sourceRoot":"","sources":["../../src/runtime/subscriptionFibers.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,MAAM,EACN,MAAM,EACN,MAAM,EAIN,KAAK,KAAK,EAGX,MAAM,QAAQ,CAAA;AAEf,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AACpE,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,mCAAmC,EAGzC,MAAM,gBAAgB,CAAA;AAEvB;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,GAAI,KAAK,EAAE,OAAO,EAAE,QAAQ,qJAS5D,QAAQ,CAAC;IACV,aAAa,EAAE,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAA;IACtD,SAAS,EAAE,KAAK,CAAA;IAChB,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACjC,YAAY,EAAE,KAAK,CAAC,KAAK,CAAA;IACzB,iCAAiC,EAAE,MAAM,CAAC,MAAM,CAC9C,uBAAuB,CAAC,mCAAmC,CAAC,KAAK,CAAC,CAAC,CACpE,CAAA;IACD,oBAAoB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC/D,mBAAmB,EAAE,CAAC,CAAC,EACrB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KACtC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IACrB,SAAS,EAAE,CACT,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EACzB,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KACjC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CACzB,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CA8FpB,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Effect, Option, Record, Ref, Schema, Stream, pipe, } from 'effect';
|
|
2
|
+
import { measureSlowPhase, reportSlowPhase, } from './slowPhase.js';
|
|
3
|
+
/**
|
|
4
|
+
* Forks one fiber per Subscription into the runtime scope. Each fiber
|
|
5
|
+
* derives the Subscription's dependencies from every Model, restarts its
|
|
6
|
+
* stream when they change under its equivalence, and feeds the Messages
|
|
7
|
+
* the stream emits into the queue. A defect anywhere in that pipeline
|
|
8
|
+
* crashes the runtime instead of dying silently in the fiber.
|
|
9
|
+
*/
|
|
10
|
+
export const forkSubscriptionFibers = ({ subscriptions, initModel, modelPubSub, runtimeScope, maybeSlowSubscriptionDependencies, enqueueMessageEffect, provideAllResources, crashWith, }) => pipe(subscriptions, Record.toEntries, Effect.forEach(([key, { dependenciesSchema, modelToDependencies, keepAliveEquivalence, dependenciesToStream, },]) => Effect.gen(function* () {
|
|
11
|
+
const equivalence = keepAliveEquivalence ?? Schema.toEquivalence(dependenciesSchema);
|
|
12
|
+
const [initDependencies, maybeInitDependenciesDuration] = measureSlowPhase(maybeSlowSubscriptionDependencies, () => modelToDependencies(initModel));
|
|
13
|
+
reportSlowPhase(maybeSlowSubscriptionDependencies, maybeInitDependenciesDuration, (durationMs, thresholdMs) => ({
|
|
14
|
+
_tag: 'SubscriptionDependencies',
|
|
15
|
+
subscriptionKey: key,
|
|
16
|
+
model: initModel,
|
|
17
|
+
durationMs,
|
|
18
|
+
thresholdMs,
|
|
19
|
+
}));
|
|
20
|
+
const latestDependenciesRef = yield* Ref.make(initDependencies);
|
|
21
|
+
const modelChangesStream = Stream.fromPubSub(modelPubSub).pipe(
|
|
22
|
+
// NOTE: Ref.set runs upstream of Stream.changesWith on
|
|
23
|
+
// every model change, so readDependencies() returns
|
|
24
|
+
// current values even when the equivalence filter
|
|
25
|
+
// doesn't emit. Moving this into a tap after
|
|
26
|
+
// changesWith would silently break subscribers whose
|
|
27
|
+
// dependencies are equivalence-stable across model
|
|
28
|
+
// changes.
|
|
29
|
+
Stream.mapEffect(model => Effect.gen(function* () {
|
|
30
|
+
const [dependencies, maybeDependenciesDuration] = measureSlowPhase(maybeSlowSubscriptionDependencies, () => modelToDependencies(model));
|
|
31
|
+
reportSlowPhase(maybeSlowSubscriptionDependencies, maybeDependenciesDuration, (durationMs, thresholdMs) => ({
|
|
32
|
+
_tag: 'SubscriptionDependencies',
|
|
33
|
+
subscriptionKey: key,
|
|
34
|
+
model,
|
|
35
|
+
durationMs,
|
|
36
|
+
thresholdMs,
|
|
37
|
+
}));
|
|
38
|
+
yield* Ref.set(latestDependenciesRef, dependencies);
|
|
39
|
+
return dependencies;
|
|
40
|
+
})));
|
|
41
|
+
yield* Effect.forkIn(runtimeScope)(Stream.concat(Stream.make(initDependencies), modelChangesStream).pipe(Stream.changesWith(equivalence), Stream.switchMap(dependencies => dependenciesToStream(dependencies, () => Ref.getUnsafe(latestDependenciesRef))), Stream.runForEach(message =>
|
|
42
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
43
|
+
enqueueMessageEffect(message)), provideAllResources, Effect.catchCause(cause => crashWith(cause, Option.none()))));
|
|
44
|
+
}), {
|
|
45
|
+
concurrency: 'unbounded',
|
|
46
|
+
discard: true,
|
|
47
|
+
}));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Controls when a feature is shown. */
|
|
2
|
+
export type Visibility = 'Development' | 'Always';
|
|
3
|
+
/** Whether a feature configured with `show` is enabled. `'Always'` enables it
|
|
4
|
+
* everywhere. `'Development'` enables it only when `isDevelopment` is true.
|
|
5
|
+
* Callers pass whether Vite HMR is active, and DevTools also requires a
|
|
6
|
+
* top-level window. */
|
|
7
|
+
export declare const isVisible: (show: Visibility, isDevelopment: boolean) => boolean;
|
|
8
|
+
//# sourceMappingURL=visibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visibility.d.ts","sourceRoot":"","sources":["../../src/runtime/visibility.ts"],"names":[],"mappings":"AAEA,wCAAwC;AACxC,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,QAAQ,CAAA;AAEjD;;;wBAGwB;AACxB,eAAO,MAAM,SAAS,SAAU,UAAU,iBAAiB,OAAO,KAAG,OAKlE,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Match } from 'effect';
|
|
2
|
+
/** Whether a feature configured with `show` is enabled. `'Always'` enables it
|
|
3
|
+
* everywhere. `'Development'` enables it only when `isDevelopment` is true.
|
|
4
|
+
* Callers pass whether Vite HMR is active, and DevTools also requires a
|
|
5
|
+
* top-level window. */
|
|
6
|
+
export const isVisible = (show, isDevelopment) => Match.value(show).pipe(Match.when('Always', () => true), Match.when('Development', () => isDevelopment), Match.exhaustive);
|
package/dist/test/scene.js
CHANGED
|
@@ -4,7 +4,7 @@ import { kebabToPascal } from '../customElement/index.js';
|
|
|
4
4
|
import { serializedStylePropertyName } from '../domReflection.js';
|
|
5
5
|
import { FOLDKIT_MOUNT_KEY, FileHandlerSymbol, __clearRuntime as clearHtmlRuntime, __htmlBuilder as htmlBuilderFor, __setRuntime as setHtmlRuntime, } from '../html/index.js';
|
|
6
6
|
import { MountTracker } from '../mount/index.js';
|
|
7
|
-
import { Dispatch } from '../runtime/
|
|
7
|
+
import { Dispatch } from '../runtime/dispatch.js';
|
|
8
8
|
import { assertAllCommandsResolved, assertAllMountsResolved, assertAllUnmountsAcknowledged, assertExactCommands, assertExactMounts, assertHasCommands, assertHasMounts, assertNoUnacknowledgedUnmounts, assertNoUnresolvedCommands, assertNoUnresolvedMounts, assertResolveUnambiguous, assertZeroCommands, assertZeroMounts, formatCommand, formatMatcher, formatMountList, formatMountMatcher, mountMatches, resolveAllExactInternal, resolveAllInternal, resolveByMatcher, resolveMountByMatcher, } from './internal.js';
|
|
9
9
|
import { accessibleDescription, accessibleName, ancestorsOf, attr, isHidden, resolveTarget, selector, textContent, within, } from './query.js';
|
|
10
10
|
import { allAltText, allDisplayValue, allLabel, allPlaceholder, allRole, allSelector, allTestId, allText, allTitle, } from './query.js';
|