@w6w/embed 0.0.0-stage → 0.1.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/LICENSE +105 -0
- package/README.md +166 -2
- package/dist/W6WEmbedProvider.d.ts +29 -0
- package/dist/chrome/CallFromCodeButton.d.ts +155 -0
- package/dist/chrome/CallableSettingsModal.d.ts +17 -0
- package/dist/chrome/ConfirmModal.d.ts +20 -0
- package/dist/chrome/DangerZone.d.ts +19 -0
- package/dist/chrome/ExecutionProfileHeader.d.ts +69 -0
- package/dist/chrome/HistoryButton.d.ts +8 -0
- package/dist/chrome/HistoryModal.d.ts +9 -0
- package/dist/chrome/HistoryView.d.ts +15 -0
- package/dist/chrome/ImportSpecModal.d.ts +40 -0
- package/dist/chrome/JsonToggle.d.ts +51 -0
- package/dist/chrome/Modal.d.ts +20 -0
- package/dist/chrome/RightRail.d.ts +24 -0
- package/dist/chrome/TagEditor.d.ts +23 -0
- package/dist/chrome/VersionHistoryModal.d.ts +38 -0
- package/dist/chrome/history-open-run.d.ts +7 -0
- package/dist/chrome/index.d.ts +36 -0
- package/dist/chrome/workspace.d.ts +67 -0
- package/dist/client-context-CX61Bgw-.js +16 -0
- package/dist/client-context-CX61Bgw-.js.map +1 -0
- package/dist/draft/autosave-flush.d.ts +63 -0
- package/dist/draft/autosave.d.ts +68 -0
- package/dist/draft/client-context.d.ts +8 -0
- package/dist/draft/draft-registry.d.ts +14 -0
- package/dist/draft/index.d.ts +15 -0
- package/dist/draft/use-workflow-draft.d.ts +67 -0
- package/dist/draft/workflow-save.d.ts +48 -0
- package/dist/draft-registry-YF0tqNN0.js +49 -0
- package/dist/draft-registry-YF0tqNN0.js.map +1 -0
- package/dist/draft.d.ts +2 -0
- package/dist/draft.mjs +18 -0
- package/dist/draft.mjs.map +1 -0
- package/dist/editor/WorkflowEditor.d.ts +12 -0
- package/dist/editor/WorkflowSettingsModal.d.ts +17 -0
- package/dist/editor/definition.d.ts +31 -0
- package/dist/editor/editor-apps.d.ts +7 -0
- package/dist/editor/types.d.ts +34 -0
- package/dist/editor.d.ts +9 -0
- package/dist/editor.mjs +2170 -0
- package/dist/editor.mjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +67 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/callable-descriptors.d.ts +77 -0
- package/dist/lib/callable-kind.d.ts +13 -0
- package/dist/lib/code-rail.d.ts +45 -0
- package/dist/lib/document-sources.d.ts +26 -0
- package/dist/lib/error-handler-self.d.ts +25 -0
- package/dist/lib/history-query.d.ts +135 -0
- package/dist/lib/import-paste.d.ts +41 -0
- package/dist/lib/index.d.ts +17 -0
- package/dist/lib/new-id.d.ts +10 -0
- package/dist/lib/rail-settings.d.ts +196 -0
- package/dist/lib/save-invalidation.d.ts +24 -0
- package/dist/lib/sdk-snippets.d.ts +96 -0
- package/dist/lib/settings-rail.d.ts +68 -0
- package/dist/lib/slug.d.ts +69 -0
- package/dist/lib/spec-import-invalidation.d.ts +48 -0
- package/dist/lib/tag-editor.d.ts +36 -0
- package/dist/lib/trigger-preview.d.ts +89 -0
- package/dist/lib/workflow-live-run.d.ts +165 -0
- package/dist/lib/workflow-poll.d.ts +8 -0
- package/dist/lib/workflow-route.d.ts +26 -0
- package/dist/lib/workflow-settings.d.ts +72 -0
- package/dist/lib/workflow-validity.d.ts +92 -0
- package/dist/lib/workflow-variables.d.ts +106 -0
- package/dist/lib.d.ts +2 -0
- package/dist/lib.mjs +62 -0
- package/dist/lib.mjs.map +1 -0
- package/dist/project-context-Dbk0Gr1K.js +16 -0
- package/dist/project-context-Dbk0Gr1K.js.map +1 -0
- package/dist/project-context.d.ts +11 -0
- package/dist/rail/CodeTool.d.ts +2 -0
- package/dist/rail/ErrorHandlingTool.d.ts +22 -0
- package/dist/rail/ExpressionScope.d.ts +21 -0
- package/dist/rail/SettingsTool.d.ts +27 -0
- package/dist/rail/editor-tools.d.ts +2 -0
- package/dist/rail/rail-env.d.ts +18 -0
- package/dist/rail/settings-sections.d.ts +93 -0
- package/dist/repos/documents.d.ts +102 -0
- package/dist/repos/history.d.ts +24 -0
- package/dist/repos/index.d.ts +22 -0
- package/dist/repos/spec-import.d.ts +44 -0
- package/dist/repos/step-tests.d.ts +19 -0
- package/dist/repos/vars.d.ts +25 -0
- package/dist/repos/vault.d.ts +24 -0
- package/dist/repos/versions.d.ts +112 -0
- package/dist/repos.d.ts +2 -0
- package/dist/repos.mjs +94 -0
- package/dist/repos.mjs.map +1 -0
- package/dist/slug-ByKQgxrf.js +642 -0
- package/dist/slug-ByKQgxrf.js.map +1 -0
- package/dist/spec-import-HgO7ZkLZ.js +898 -0
- package/dist/spec-import-HgO7ZkLZ.js.map +1 -0
- package/dist/spec-import-invalidation-D3retX4Q.js +18 -0
- package/dist/spec-import-invalidation-D3retX4Q.js.map +1 -0
- package/dist/use-workflow-draft-oI02dsP-.js +314 -0
- package/dist/use-workflow-draft-oI02dsP-.js.map +1 -0
- package/package.json +71 -3
- package/src/styles.css +829 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type JsonToggleMode = "synced" | "disconnected";
|
|
2
|
+
/** The header icon button: `IconButton` with the set's `json` glyph — the
|
|
3
|
+
* curly-brace icon, distinct from the `code` `</>` "Call from code" icon
|
|
4
|
+
* beside it, so the two stop reading as the same button. The local
|
|
5
|
+
* `JsonIcon` SVG this file used to carry is now `icon="json"` in the shared
|
|
6
|
+
* set. */
|
|
7
|
+
export declare function JsonToggleButton(props: {
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
export interface JsonToggleProps {
|
|
11
|
+
/** Parent-owned; render nothing when false. */
|
|
12
|
+
open: boolean;
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
/** Modal title. */
|
|
15
|
+
title: string;
|
|
16
|
+
mode: JsonToggleMode;
|
|
17
|
+
/** Parent-owned text; the component keeps NO copy of it. */
|
|
18
|
+
value: string;
|
|
19
|
+
/** Every JsonEditor change, both modes. */
|
|
20
|
+
onChange: (text: string) => void;
|
|
21
|
+
/** JsonEditor's, error ?? null. */
|
|
22
|
+
onValidityChange?: (v: {
|
|
23
|
+
valid: boolean;
|
|
24
|
+
error: string | null;
|
|
25
|
+
}) => void;
|
|
26
|
+
/** When non-null: "Invalid JSON — {invalidReason}" (Workflow's existing line). */
|
|
27
|
+
invalidReason?: string | null;
|
|
28
|
+
/** Evaluated on `value` every render; non-null → shown in
|
|
29
|
+
* <p className="muted small" data-testid="json-toggle-reason">. */
|
|
30
|
+
guard?: (text: string) => string | null;
|
|
31
|
+
/** disconnected only. */
|
|
32
|
+
onSave?: () => void;
|
|
33
|
+
/** disconnected only. */
|
|
34
|
+
saving?: boolean;
|
|
35
|
+
/** disconnected only; rendered as <div className="error"> inside the dialog. */
|
|
36
|
+
error?: string | null;
|
|
37
|
+
/** JsonEditor aria-label. */
|
|
38
|
+
editorLabel: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The JSON⇄editor toggle's chrome — the header icon button's dialog, extracted
|
|
42
|
+
* verbatim from `WorkflowsPage.tsx`'s raw-JSON modal (T2.1.1 D-2) so a second
|
|
43
|
+
* host can reuse it in `mode="disconnected"` (T2.1.2). Wraps `@w6w/ui`'s
|
|
44
|
+
* `JsonEditor` and studio's own `Modal` (not `@w6w/ui`'s) — both existing
|
|
45
|
+
* JSON-toggle-shaped precedents in this repo use studio's `Modal`.
|
|
46
|
+
*
|
|
47
|
+
* The component holds NO copy of `value`: every render shows exactly what the
|
|
48
|
+
* parent hands it, which is what keeps a reopened dialog free of a stale
|
|
49
|
+
* snapshot from the previous time it was open.
|
|
50
|
+
*/
|
|
51
|
+
export declare function JsonToggle(props: JsonToggleProps): JSX.Element | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface ModalProps {
|
|
2
|
+
title: string;
|
|
3
|
+
onClose: () => void;
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
/** Widen the dialog (e.g. for a JSON editor that needs room). */
|
|
6
|
+
wide?: boolean;
|
|
7
|
+
/** Widen further still (e.g. for the plan comparison grid). */
|
|
8
|
+
xl?: boolean;
|
|
9
|
+
/** Trailing control on the title row (e.g. a close `IconButton`). Given,
|
|
10
|
+
* the title and this node share one `.modal-head` row; absent, the title
|
|
11
|
+
* renders as a direct child exactly as before. */
|
|
12
|
+
headerRight?: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Small dialog modal. Built on the native <dialog> element so it gets focus
|
|
16
|
+
* trapping, Esc-dismiss, and accessibility semantics for free. A button
|
|
17
|
+
* positioned over the backdrop catches outside-clicks to close.
|
|
18
|
+
*/
|
|
19
|
+
export declare function Modal({ title, onClose, children, wide, xl, headerRight, }: ModalProps): import("react").JSX.Element;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
import { WorkspaceCallable } from './workspace.tsx';
|
|
3
|
+
export interface RailTool {
|
|
4
|
+
/** Stable; used as the persisted `activeTool`. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Strip tooltip + panel heading. */
|
|
7
|
+
title: string;
|
|
8
|
+
/** The strip glyph, as a component — built-in tools render a 16×16 `<Icon name="…" />`
|
|
9
|
+
* from `@w6w/ui`'s shared set; a tool may bring its own glyph instead. A component,
|
|
10
|
+
* not an `IconName`, so this seam never narrows to the shared set. */
|
|
11
|
+
Icon: ComponentType;
|
|
12
|
+
isAvailable(ctx: WorkspaceCallable): boolean;
|
|
13
|
+
/** Panel renders its own heading when it includes section actions. */
|
|
14
|
+
rendersHeading?: boolean;
|
|
15
|
+
Panel: ComponentType<{
|
|
16
|
+
ctx: WorkspaceCallable;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
/** `storageKey === null`: no key to persist under yet (pin 12) — the strip renders closed and
|
|
20
|
+
* nothing is written to localStorage. No available tool ⇒ no rail element at all (C/D-C3). */
|
|
21
|
+
export declare function RightRail({ tools, storageKey, }: {
|
|
22
|
+
tools: readonly RailTool[];
|
|
23
|
+
storageKey: string | null;
|
|
24
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A chip input for a workflow's free-form tags: type text, `,` commits the
|
|
3
|
+
* typed value as a chip (or paste a whole comma-separated list to commit
|
|
4
|
+
* several at once — `../lib/tag-editor.ts`'s `commitTag`), each chip carries
|
|
5
|
+
* an `×` to remove it, and a still-typable tail follows the chips — one
|
|
6
|
+
* control, not a stacked input-above-list.
|
|
7
|
+
*
|
|
8
|
+
* Built fresh (T2.2.1) rather than extended from the retiring
|
|
9
|
+
* `WorkflowsPage.tsx` tags-editing modal: that component was a submit-button
|
|
10
|
+
* "Add" form with its own inline add/remove branching, a different
|
|
11
|
+
* interaction shape from a comma-commits chip editor. The shared data
|
|
12
|
+
* contract (trim, dedupe-on-add, drop empties) now lives in
|
|
13
|
+
* `../lib/tag-editor.ts`'s
|
|
14
|
+
* `commitTag`/`removeTag`, so this component holds no branching of its own —
|
|
15
|
+
* it only calls them and renders the result. Controlled, mirroring
|
|
16
|
+
* `InputListEditor.tsx`'s shape: the caller owns `tags`, this component owns
|
|
17
|
+
* only the not-yet-committed typed tail.
|
|
18
|
+
*/
|
|
19
|
+
export interface TagEditorProps {
|
|
20
|
+
tags: string[];
|
|
21
|
+
onChange: (next: string[]) => void;
|
|
22
|
+
}
|
|
23
|
+
export declare function TagEditor({ tags, onChange }: TagEditorProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { VersionsKind } from '@w6w/embed/repos';
|
|
2
|
+
/**
|
|
3
|
+
* Version viewer for Function/Workflow/Endpoint (T3.1.1, extended by T5.3.1): one
|
|
4
|
+
* kind-parametrized component, mirroring `ImportSpecModal.tsx:12-46`'s established shape (a
|
|
5
|
+
* `kind` prop plus per-kind `TESTIDS`, one component mounted from more than one place rather than
|
|
6
|
+
* three near-copies). This project mounts it from `SettingsTool.tsx` only — the component
|
|
7
|
+
* boundary exists so a second mount point later costs one line.
|
|
8
|
+
*
|
|
9
|
+
* Renders the version's own read-only definition through the same two primitives
|
|
10
|
+
* `SettingsTool.tsx`'s existing Export modal already uses — the studio-local `Modal` and
|
|
11
|
+
* `@w6w/ui`'s `CodeBlock` — plus, since T5.3.1, two live actions:
|
|
12
|
+
*
|
|
13
|
+
* - **"Revert to this version"** — confirmed via the studio-local `ConfirmModal` (P2-D4:
|
|
14
|
+
* `./ConfirmModal.tsx`, never `@w6w/ui`'s), rendered as a SIBLING of `<Modal>` in
|
|
15
|
+
* the returned fragment, never nested inside it. Overwrites the LIVE resource's `definition`
|
|
16
|
+
* with this version's content, preserving its `key`/address (HITL-4's pinned default).
|
|
17
|
+
* - **"Invoke this version"** — one click, NO confirm (ruling #12: an invoke is a call, not a
|
|
18
|
+
* write to the resource, and the LIVE invoke action it mirrors carries no confirm either). Runs
|
|
19
|
+
* this version's own archived definition, never the live one, and renders the response body
|
|
20
|
+
* inline via `CodeBlock`, or a failure inline via the `ApiError` idiom (never a thrown error, a
|
|
21
|
+
* blank modal, or a browser dialog).
|
|
22
|
+
*
|
|
23
|
+
* Both actions read `kind`/`id`/`versionNumber` from props, and both mutations are fresh
|
|
24
|
+
* `useMutation` instances scoped to this component instance — since `SettingsTool.tsx`'s
|
|
25
|
+
* `openVersion` gate fully unmounts/remounts this component per version rather than swapping
|
|
26
|
+
* props on a surviving instance, any prior invoke result or error is gone the moment a different
|
|
27
|
+
* version is opened.
|
|
28
|
+
*
|
|
29
|
+
* Amendments #5/#6 (project.md, Phase 2) supersede the earlier pin that made both of the above
|
|
30
|
+
* out of scope (HITL-2's "invocation always runs the live definition"; Amendment #5's "restore is
|
|
31
|
+
* out of scope, not even a disabled button").
|
|
32
|
+
*/
|
|
33
|
+
export declare function VersionHistoryModal(props: {
|
|
34
|
+
kind: VersionsKind;
|
|
35
|
+
id: string;
|
|
36
|
+
versionNumber: number;
|
|
37
|
+
onClose: () => void;
|
|
38
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CallableKind, ExecutionSummary } from '../repos/index.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The open-in-editor decision for a History view: only a workflow run has an editor to open, and
|
|
4
|
+
* only when the host supplied `onOpenRun`. The handler closes the modal BEFORE handing the run id
|
|
5
|
+
* to the host (the host's navigation then lands on an unobscured page).
|
|
6
|
+
*/
|
|
7
|
+
export declare function historyOpenRun(kind: CallableKind, onOpenRun: ((runId: string) => void) | undefined, onClose: () => void): ((execution: ExecutionSummary) => void) | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export { CallableSettingsModal } from './CallableSettingsModal.tsx';
|
|
2
|
+
export { CallFromCodeBody, CallFromCodeButton, SNIPPET_TABS, snippetTabsFor, } from './CallFromCodeButton.tsx';
|
|
3
|
+
export type { SnippetTabId } from './CallFromCodeButton.tsx';
|
|
4
|
+
export { ConfirmModal } from './ConfirmModal.tsx';
|
|
5
|
+
export { DangerZone } from './DangerZone.tsx';
|
|
6
|
+
export type { DangerZoneProps } from './DangerZone.tsx';
|
|
7
|
+
export { ExecutionProfileHeader } from './ExecutionProfileHeader.tsx';
|
|
8
|
+
export type { EditableProfileTitle, ExecutionProfileHeaderProps, ExecutionProfileTitle, ReadOnlyProfileTitle, } from './ExecutionProfileHeader.tsx';
|
|
9
|
+
export { JsonToggle, JsonToggleButton } from './JsonToggle.tsx';
|
|
10
|
+
export type { JsonToggleMode, JsonToggleProps } from './JsonToggle.tsx';
|
|
11
|
+
export { Modal } from './Modal.tsx';
|
|
12
|
+
export { TagEditor } from './TagEditor.tsx';
|
|
13
|
+
export type { TagEditorProps } from './TagEditor.tsx';
|
|
14
|
+
export { HistoryButton } from './HistoryButton.tsx';
|
|
15
|
+
export type { HistoryButtonProps } from './HistoryButton.tsx';
|
|
16
|
+
export { HistoryModal } from './HistoryModal.tsx';
|
|
17
|
+
export type { HistoryModalProps } from './HistoryModal.tsx';
|
|
18
|
+
export { HistoryView } from './HistoryView.tsx';
|
|
19
|
+
export { historyOpenRun } from './history-open-run.ts';
|
|
20
|
+
export { ImportSpecModal } from './ImportSpecModal.tsx';
|
|
21
|
+
export type { ImportSpecModalProps } from './ImportSpecModal.tsx';
|
|
22
|
+
export { VersionHistoryModal } from './VersionHistoryModal.tsx';
|
|
23
|
+
export { RightRail } from './RightRail.tsx';
|
|
24
|
+
export type { RailTool } from './RightRail.tsx';
|
|
25
|
+
export { WorkspaceProvider, useWorkspace, useWorkspaceCallable } from './workspace.tsx';
|
|
26
|
+
export type { WorkspaceCallable, WorkspaceKind } from './workspace.tsx';
|
|
27
|
+
export { RailEnvProvider, useRailEnv } from '../rail/rail-env.tsx';
|
|
28
|
+
export type { RailEnv, RailNavigation } from '../rail/rail-env.tsx';
|
|
29
|
+
export { type SettingsOpenState, WorkflowSettingsPanel, settingsTool, } from '../rail/SettingsTool.tsx';
|
|
30
|
+
export { MetadataSection, SettingsDangerZone, SettingsHeading, SpecActionsTrigger, SpecChooserModal, VersionListModal, VersionSection, } from '../rail/settings-sections.tsx';
|
|
31
|
+
export type { SettingsKind } from '../rail/settings-sections.tsx';
|
|
32
|
+
export { codeTool } from '../rail/CodeTool.tsx';
|
|
33
|
+
export { ErrorHandlerSection, errorHandlingTool } from '../rail/ErrorHandlingTool.tsx';
|
|
34
|
+
export type { ErrorHandlerKind, ErrorHandlerSectionProps } from '../rail/ErrorHandlingTool.tsx';
|
|
35
|
+
export { ProjectExpressionScope, useProjectExpressionOptions } from '../rail/ExpressionScope.tsx';
|
|
36
|
+
export { editorRailTools } from '../rail/editor-tools.ts';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { CallablePolicy, SnippetInput, TriggerPreview } from '@w6w/embed/lib';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* T2.1.2: widened to a discriminated union, one arm per kind — every new
|
|
5
|
+
* field OPTIONAL, no new required member, so every existing producer keeps
|
|
6
|
+
* compiling and behaving identically (`{kind, id}` alone still satisfies any
|
|
7
|
+
* arm). The extra fields are what `CodeTool`'s snippet panel needs per kind
|
|
8
|
+
* (mirrors `lib/sdk-snippets.ts`'s `SnippetCallable`, field-for-field where
|
|
9
|
+
* the names match — see `lib/code-rail.ts`'s `toSnippetCallable` for the one
|
|
10
|
+
* field that doesn't, `label` -> `displayName`).
|
|
11
|
+
*/
|
|
12
|
+
export type WorkspaceCallable = {
|
|
13
|
+
kind: "function";
|
|
14
|
+
id: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
key?: string;
|
|
17
|
+
inputs?: readonly SnippetInput[];
|
|
18
|
+
alias?: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: "endpoint";
|
|
21
|
+
id: string;
|
|
22
|
+
label?: string;
|
|
23
|
+
key?: string;
|
|
24
|
+
invokeUrl?: string;
|
|
25
|
+
invokeUrlByKey?: string;
|
|
26
|
+
urlForm?: "id" | "key";
|
|
27
|
+
alias?: string;
|
|
28
|
+
inputs?: readonly SnippetInput[];
|
|
29
|
+
} | {
|
|
30
|
+
kind: "workflow";
|
|
31
|
+
id: string;
|
|
32
|
+
label?: string;
|
|
33
|
+
key?: string;
|
|
34
|
+
triggers?: TriggerPreview[];
|
|
35
|
+
/**
|
|
36
|
+
* The workflow's run-level error policy, and the writer that applies it
|
|
37
|
+
* (core Workflow RFC — *Amendment — 2026-08-21: the run-level error
|
|
38
|
+
* handler*). Published ONLY by the page that owns the definition buffer
|
|
39
|
+
* — the canvas editor — so the rail's Error handling tool edits the same
|
|
40
|
+
* text the editor is about to save instead of racing it over the wire.
|
|
41
|
+
* `ErrorHandlingTool` is the only reader; see `lib/settings-rail.ts`'s
|
|
42
|
+
* `isAvailable` for why the tool hides itself when they are absent.
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ `onPolicyChange` MUST be referentially stable across renders (a
|
|
45
|
+
* `useCallback` reading through a ref). `useWorkspace` keys its publish
|
|
46
|
+
* effect on `JSON.stringify(resource)`, which drops functions — so the
|
|
47
|
+
* stored callback is whichever one was current when some OTHER field
|
|
48
|
+
* last changed, and a closure that captured stale editor state would
|
|
49
|
+
* write stale bytes.
|
|
50
|
+
*/
|
|
51
|
+
policy?: CallablePolicy;
|
|
52
|
+
onPolicyChange?: (policy: CallablePolicy) => void;
|
|
53
|
+
} | {
|
|
54
|
+
kind: "connection";
|
|
55
|
+
id: string;
|
|
56
|
+
label?: string;
|
|
57
|
+
} | null;
|
|
58
|
+
/** Derived from {@link WorkspaceCallable}, never redeclared — the arm a
|
|
59
|
+
* `RailTool.isAvailable`/route wrapper narrows on. */
|
|
60
|
+
export type WorkspaceKind = Exclude<WorkspaceCallable, null>["kind"];
|
|
61
|
+
export declare function WorkspaceProvider({ children }: {
|
|
62
|
+
children: ReactNode;
|
|
63
|
+
}): import("react").JSX.Element;
|
|
64
|
+
/** Publisher. Compute during render, effect-write, clear on unmount. */
|
|
65
|
+
export declare function useWorkspace(resource: WorkspaceCallable): void;
|
|
66
|
+
/** Reader — used by the rail and by every tool `Panel`. Throws outside the provider. */
|
|
67
|
+
export declare function useWorkspaceCallable(): WorkspaceCallable;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createElement as n, useContext as r, createContext as i } from "react";
|
|
2
|
+
const t = i(null);
|
|
3
|
+
function l(e) {
|
|
4
|
+
return n(t.Provider, { value: e.client }, e.children);
|
|
5
|
+
}
|
|
6
|
+
function u() {
|
|
7
|
+
const e = r(t);
|
|
8
|
+
if (e === null)
|
|
9
|
+
throw new Error("useEmbedClient must be used inside an <EmbedClientProvider>.");
|
|
10
|
+
return e;
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
l as E,
|
|
14
|
+
u
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=client-context-CX61Bgw-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-context-CX61Bgw-.js","sources":["../src/draft/client-context.ts"],"sourcesContent":["/**\n * The embed-owned client context for the headless draft hook.\n *\n * The hook talks to the server through the SDK `W6WClient` (`workflows.get` / `workflows.update`),\n * never through `@w6w/ui`'s `W6WApi` — that interface has no save member and is not extended here.\n * `W6WEmbedProvider` supplies the client from `useW6WClient()`; Studio supplies its own from\n * `useApiClient()`. A plain `.ts` module (no JSX) so Node's type stripping can load it.\n */\nimport type { W6WClient } from \"@w6w/sdk\";\nimport { type ReactNode, createContext, createElement, useContext } from \"react\";\n\nconst EmbedClientContext = createContext<W6WClient | null>(null);\n\nexport function EmbedClientProvider(p: { client: W6WClient; children?: ReactNode }) {\n return createElement(EmbedClientContext.Provider, { value: p.client }, p.children);\n}\n\n/** The SDK client for the nearest {@link EmbedClientProvider}. Throws outside one. */\nexport function useEmbedClient(): W6WClient {\n const client = useContext(EmbedClientContext);\n if (client === null) {\n throw new Error(\"useEmbedClient must be used inside an <EmbedClientProvider>.\");\n }\n return client;\n}\n"],"names":["EmbedClientContext","createContext","EmbedClientProvider","p","createElement","useEmbedClient","client","useContext"],"mappings":";AAWA,MAAMA,IAAqBC,EAAgC,IAAI;AAExD,SAASC,EAAoBC,GAAgD;AAClF,SAAOC,EAAcJ,EAAmB,UAAU,EAAE,OAAOG,EAAE,OAAA,GAAUA,EAAE,QAAQ;AACnF;AAGO,SAASE,IAA4B;AAC1C,QAAMC,IAASC,EAAWP,CAAkB;AAC5C,MAAIM,MAAW;AACb,UAAM,IAAI,MAAM,8DAA8D;AAEhF,SAAOA;AACT;"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flush a pending debounced write before the page actually goes away.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists at all
|
|
5
|
+
*
|
|
6
|
+
* `autosave.ts`'s `createAutoSaver` coalesces edits behind an `AUTOSAVE_DEBOUNCE_MS`
|
|
7
|
+
* (1200ms) window timer (`autosave.ts:45`) — a `push()` inside that window never writes,
|
|
8
|
+
* it only re-arms the timer. Nothing in `autosave.ts` itself ever
|
|
9
|
+
* flushes on navigation, so a canvas pan (or any other edit) that lands less than the
|
|
10
|
+
* window's width before a refresh is lost: the tab is gone before the timer fires (I-3).
|
|
11
|
+
* A zoom gesture "works" only by accident — it takes longer than the window to finish.
|
|
12
|
+
*
|
|
13
|
+
* This module does not touch the debounce, the `AutoSaver` interface, or its save
|
|
14
|
+
* decision (`fire()`'s `enabled`/`canSave`/dedup checks in `autosave.ts:98-107` already
|
|
15
|
+
* apply to a flush exactly as they do to a natural fire) — it only decides WHEN to call
|
|
16
|
+
* the `flush()` the caller already owns.
|
|
17
|
+
*
|
|
18
|
+
* ## The rule
|
|
19
|
+
*
|
|
20
|
+
* Two signals, both wired here, neither replacing the other:
|
|
21
|
+
*
|
|
22
|
+
* - `visibilitychange` with `document.visibilityState === "hidden"` is the PRIMARY path.
|
|
23
|
+
* It fires on tab close, tab switch, and navigation alike, and — unlike `beforeunload`
|
|
24
|
+
* — a listener registered on it is not itself a signal to the browser that the page is
|
|
25
|
+
* unusual, so it carries no risk of suppressing the back/forward cache.
|
|
26
|
+
* - `beforeunload` is the BELT, wired unconditionally (it does not check
|
|
27
|
+
* `visibilityState`): some navigations do not reliably fire `visibilitychange` first in
|
|
28
|
+
* every browser, so this is a second chance at the same flush.
|
|
29
|
+
*
|
|
30
|
+
* **Known, accepted limit — do not claim this race is provably closed.** A `fetch`
|
|
31
|
+
* kicked off from either handler is not guaranteed to complete before the browser
|
|
32
|
+
* actually tears the page down (`beforeunload` in particular has no such guarantee in
|
|
33
|
+
* every browser). `visibilitychange`+`hidden` fires earlier in the common
|
|
34
|
+
* switch-tab/navigate-away cases, which is why it is the primary path and `beforeunload`
|
|
35
|
+
* only the belt — this module narrows the loss window, it does not eliminate it.
|
|
36
|
+
*
|
|
37
|
+
* ## Why the parameters are typed the way they are
|
|
38
|
+
*
|
|
39
|
+
* `WindowLike`/`DocumentLike` below name only the handful of members this module
|
|
40
|
+
* actually calls. That is what lets `node --test` — which cannot load `.tsx`, the same
|
|
41
|
+
* reason `autosave.ts` itself lives in a plain `.ts` module (`autosave.ts:37-41`) — pass
|
|
42
|
+
* a plain fake object in tests, while `tsc -b --noEmit` still proves the real
|
|
43
|
+
* `window`/`document` satisfy the same shape at the call site in `WorkflowsPage.tsx`.
|
|
44
|
+
*/
|
|
45
|
+
/** The subset of `Window` this module needs: register/unregister `beforeunload`. */
|
|
46
|
+
export interface WindowLike {
|
|
47
|
+
addEventListener(type: "beforeunload", listener: () => void): void;
|
|
48
|
+
removeEventListener(type: "beforeunload", listener: () => void): void;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The subset of `Document` this module needs: register/unregister `visibilitychange`,
|
|
52
|
+
* and read the visibility state the moment the event fires.
|
|
53
|
+
*/
|
|
54
|
+
export interface DocumentLike {
|
|
55
|
+
readonly visibilityState: string;
|
|
56
|
+
addEventListener(type: "visibilitychange", listener: () => void): void;
|
|
57
|
+
removeEventListener(type: "visibilitychange", listener: () => void): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Register both listeners; call `flush` when the page is going away. Returns a cleanup
|
|
61
|
+
* that removes both — callers wire this straight into a `useEffect`'s return.
|
|
62
|
+
*/
|
|
63
|
+
export declare function registerFlushOnHide(windowTarget: WindowLike, documentTarget: DocumentLike, flush: () => void): () => void;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The debouncing, coalescing, payload-deduplicated saver behind auto-save.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists at all
|
|
5
|
+
*
|
|
6
|
+
* Every edit on the canvas replaces the whole definition: a single keystroke in a
|
|
7
|
+
* step's param form travels `ParamsForm onChange → commit → StepEditModal onChange
|
|
8
|
+
* → updateStep → emitChange → onChange(flowToWorkflow(…))` and lands in
|
|
9
|
+
* `WorkflowsPage` as `setJson(JSON.stringify(next, null, 2))`. So a naive
|
|
10
|
+
* `useEffect(() => save(), [json])` is **one `POST /workflows` per character
|
|
11
|
+
* typed**. This module is the thing that stands between those two facts.
|
|
12
|
+
*
|
|
13
|
+
* ## The rule
|
|
14
|
+
*
|
|
15
|
+
* **Trailing debounce, single pending entry, decided at fire time.**
|
|
16
|
+
*
|
|
17
|
+
* - `push` never writes. It replaces the one pending entry and restarts a
|
|
18
|
+
* `delayMs` window. N pushes inside one window therefore produce exactly ONE
|
|
19
|
+
* `save`, carrying the LAST payload.
|
|
20
|
+
* - The decision to save is taken when the timer fires, not when the payload was
|
|
21
|
+
* pushed. That is what makes a transiently-invalid edit harmless: while the JSON
|
|
22
|
+
* modal round-trips text through `JsonEditor`, `canSave` flickers false
|
|
23
|
+
* mid-typing, and a push-time decision would discard the save the author is
|
|
24
|
+
* waiting for. An invalid push still re-arms the window; the next valid one wins.
|
|
25
|
+
* - A payload equal to the last saved one is dropped without a request. The
|
|
26
|
+
* comparison is on the serialised JSON **text**, the same structural-equality
|
|
27
|
+
* dirty signal the house already uses (`packages/ui/src/ActionTestForm.tsx`,
|
|
28
|
+
* *"Compared by structural JSON so a load→edit→revert cycle clears it"*), which
|
|
29
|
+
* is why an in-place mutation of some nested object can never fool it: nothing
|
|
30
|
+
* here holds a reference to a live object across ticks.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately absent: retry/backoff. On a failed save the host stops and shows
|
|
33
|
+
* the existing error surface; a background retry against a server that just
|
|
34
|
+
* refused is worse than a visible failure (and, with a stale-write conflict on
|
|
35
|
+
* screen, it would retry into the dialog the author is reading).
|
|
36
|
+
*
|
|
37
|
+
* It lives here rather than in `@w6w/ui` because `@w6w/ui` owns no workflow write
|
|
38
|
+
* at all — the canvas emits `onChange`, the host decides when to persist — and in
|
|
39
|
+
* a plain `.ts` module rather than in `WorkflowsPage.tsx` so it is executable:
|
|
40
|
+
* that file is JSX, which Node 24's type-stripping cannot parse, so nothing in it
|
|
41
|
+
* can be loaded by `node --test`. Same shape as `workflow-save.ts`.
|
|
42
|
+
*/
|
|
43
|
+
/** The debounce window. One quiet beat after the last keystroke, not per keystroke. */
|
|
44
|
+
export declare const AUTOSAVE_DEBOUNCE_MS = 1200;
|
|
45
|
+
export interface AutoSaverOpts {
|
|
46
|
+
/** May return a promise (awaited by `flush`); any other return value is ignored. */
|
|
47
|
+
save: (payload: string, ctx?: unknown) => unknown;
|
|
48
|
+
delayMs?: number;
|
|
49
|
+
/** Injectable for tests; defaults to the globals. */
|
|
50
|
+
setTimer?: (fn: () => void, ms: number) => number;
|
|
51
|
+
clearTimer?: (handle: number) => void;
|
|
52
|
+
}
|
|
53
|
+
export interface AutoSaver {
|
|
54
|
+
/** Consider a payload. (Re)starts the debounce window. */
|
|
55
|
+
push(payload: string, enabled: boolean, canSave: boolean, ctx?: unknown): void;
|
|
56
|
+
/**
|
|
57
|
+
* Fire the pending save now (the Save button). Resolves after the fired save settles
|
|
58
|
+
* (`Promise.resolve()` when nothing fired); rejects if the fired save rejects.
|
|
59
|
+
*/
|
|
60
|
+
flush(): Promise<void>;
|
|
61
|
+
/** Drop any pending save without firing (unmount / workflow switch / stale conflict). */
|
|
62
|
+
cancel(): void;
|
|
63
|
+
/** This payload is now on the server. */
|
|
64
|
+
markSaved(payload: string): void;
|
|
65
|
+
/** Does `payload` diverge from the last-saved one? */
|
|
66
|
+
isDirty(payload: string): boolean;
|
|
67
|
+
}
|
|
68
|
+
export declare function createAutoSaver(opts: AutoSaverOpts): AutoSaver;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { W6WClient } from '@w6w/sdk';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
export declare function EmbedClientProvider(p: {
|
|
4
|
+
client: W6WClient;
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
}): import('react').FunctionComponentElement<import('react').ProviderProps<W6WClient | null>>;
|
|
7
|
+
/** The SDK client for the nearest {@link EmbedClientProvider}. Throws outside one. */
|
|
8
|
+
export declare function useEmbedClient(): W6WClient;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export interface DraftRegistry {
|
|
3
|
+
/** Register a draft's flush. Returns the unregister function. */
|
|
4
|
+
register(flush: () => Promise<unknown>): () => void;
|
|
5
|
+
/** Await every registered flush, at most `timeoutMs` (default 5000). Never rejects. */
|
|
6
|
+
flushAll(timeoutMs?: number): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare function createDraftRegistry(): DraftRegistry;
|
|
9
|
+
export declare function DraftRegistryProvider(p: {
|
|
10
|
+
registry: DraftRegistry;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
}): import('react').FunctionComponentElement<import('react').ProviderProps<DraftRegistry | null>>;
|
|
13
|
+
/** The nearest registry, or `null` when there is no provider (the hook then skips registering). */
|
|
14
|
+
export declare function useDraftRegistry(): DraftRegistry | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@w6w/embed/draft` — the headless draft lifecycle (debounced autosave, flush-on-hide, the
|
|
3
|
+
* workflow save precondition). No UI and no `@w6w/ui`: it imports only `react`,
|
|
4
|
+
* `@tanstack/react-query` and type-only `@w6w/sdk`, so a host can use it without the editor.
|
|
5
|
+
*/
|
|
6
|
+
export { AUTOSAVE_DEBOUNCE_MS, createAutoSaver } from './autosave.ts';
|
|
7
|
+
export type { AutoSaver, AutoSaverOpts } from './autosave.ts';
|
|
8
|
+
export { registerFlushOnHide } from './autosave-flush.ts';
|
|
9
|
+
export type { DocumentLike, WindowLike } from './autosave-flush.ts';
|
|
10
|
+
export { isStaleWrite, nextSaveToken } from './workflow-save.ts';
|
|
11
|
+
export { EmbedClientProvider, useEmbedClient } from './client-context.ts';
|
|
12
|
+
export { DraftRegistryProvider, createDraftRegistry, useDraftRegistry, } from './draft-registry.ts';
|
|
13
|
+
export type { DraftRegistry } from './draft-registry.ts';
|
|
14
|
+
export { useWorkflowDraft } from './use-workflow-draft.ts';
|
|
15
|
+
export type { FlushOutcome, UseWorkflowDraftOptions, WorkflowDraft, } from './use-workflow-draft.ts';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type FlushOutcome = {
|
|
2
|
+
status: "clean";
|
|
3
|
+
} | {
|
|
4
|
+
status: "saved";
|
|
5
|
+
} | {
|
|
6
|
+
status: "blocked";
|
|
7
|
+
reason: string;
|
|
8
|
+
} | {
|
|
9
|
+
status: "failed";
|
|
10
|
+
error: unknown;
|
|
11
|
+
};
|
|
12
|
+
export interface UseWorkflowDraftOptions {
|
|
13
|
+
/** `null` ⇒ idle (the hook stays mounted above an early return). */
|
|
14
|
+
workflowId: string | null;
|
|
15
|
+
/** REQUIRED to save; empty/absent ⇒ the save is refused and no request is sent. */
|
|
16
|
+
project: string | null | undefined;
|
|
17
|
+
/** Default true; a debounced save follows the last edit. */
|
|
18
|
+
autoSave?: boolean;
|
|
19
|
+
/** Injected busy probe (Studio: expression modal open). Gates AUTOMATIC saves only. */
|
|
20
|
+
canAutoSave?: () => boolean;
|
|
21
|
+
/** Injected key-edit guard: a reason blocks saving. */
|
|
22
|
+
saveGuard?: (parsed: unknown) => string | null;
|
|
23
|
+
/** Injected (B/D-3): reported as `publishBlockedReason`, not enforced by save. */
|
|
24
|
+
publishBlockedReason?: (parsed: unknown) => string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Called exactly once when a handover (client/project change) or an identity switch
|
|
27
|
+
* (`flushAll`) drops a dirty buffer it could not save. `blocked`: the save was refused before a
|
|
28
|
+
* request (invalid text, no project, a guard, busy); `failed`: the request was sent or the row
|
|
29
|
+
* was stale and the save did not succeed. No UI here: the host decides what to show.
|
|
30
|
+
*/
|
|
31
|
+
onDiscard?: (info: {
|
|
32
|
+
workflowId: string;
|
|
33
|
+
text: string;
|
|
34
|
+
reason: "blocked" | "failed";
|
|
35
|
+
}) => void;
|
|
36
|
+
}
|
|
37
|
+
export interface WorkflowDraft {
|
|
38
|
+
json: string;
|
|
39
|
+
setJson(next: string): void;
|
|
40
|
+
parsed: unknown | null;
|
|
41
|
+
syntaxError: string | null;
|
|
42
|
+
loading: boolean;
|
|
43
|
+
loadError: unknown | null;
|
|
44
|
+
dirty: boolean;
|
|
45
|
+
saving: boolean;
|
|
46
|
+
canSave: boolean;
|
|
47
|
+
saveBlockedReason: string | null;
|
|
48
|
+
stale: boolean;
|
|
49
|
+
error: unknown | null;
|
|
50
|
+
publishBlockedReason: string | null;
|
|
51
|
+
/** Push + fire now (Ctrl/Cmd+S, the Save button). */
|
|
52
|
+
saveNow(): void;
|
|
53
|
+
flush(): Promise<FlushOutcome>;
|
|
54
|
+
/** Flush, then run `action` only when the buffer is clean or just saved. */
|
|
55
|
+
flushThen<T>(action: () => Promise<T>): Promise<{
|
|
56
|
+
ok: true;
|
|
57
|
+
value: T;
|
|
58
|
+
} | {
|
|
59
|
+
ok: false;
|
|
60
|
+
outcome: FlushOutcome;
|
|
61
|
+
}>;
|
|
62
|
+
/** `setJson(next)` + an immediate save (the saveMeta / onPolicyChange paths). */
|
|
63
|
+
commit(next: string): Promise<FlushOutcome>;
|
|
64
|
+
/** 409 recovery: GET, replace the buffer and token, clear `stale`. */
|
|
65
|
+
reload(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
export declare function useWorkflowDraft(o: UseWorkflowDraftOptions): WorkflowDraft;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two decisions behind the workflow save precondition (optimistic
|
|
3
|
+
* concurrency; server half in `packages/server` `packages/api/workflows.ts`).
|
|
4
|
+
*
|
|
5
|
+
* `POST /workflows` accepts an optional `x-w6w-if-unmodified-since` header
|
|
6
|
+
* carrying the exact ISO-8601 `updatedAt` the client last saw. If the stored row
|
|
7
|
+
* has moved on, the save is refused with **409 `workflow_stale`** instead of
|
|
8
|
+
* silently overwriting whoever saved in between.
|
|
9
|
+
*
|
|
10
|
+
* Both functions here are pure functions of their inputs, and they live in a
|
|
11
|
+
* `.ts` module rather than in `WorkflowsPage.tsx` so they are executable:
|
|
12
|
+
* `WorkflowsPage.tsx` is JSX, which Node 24's type-stripping cannot parse, so
|
|
13
|
+
* nothing in it can be loaded by `node --test`. Same shape as
|
|
14
|
+
* `src/lib/sdk-snippets.ts` + its `.test.ts`.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Is this the one save failure an author can fix by reloading?
|
|
18
|
+
*
|
|
19
|
+
* Keyed on the error **code**, never on the HTTP status. The server answers 409
|
|
20
|
+
* for two completely different situations and only one of them is recoverable:
|
|
21
|
+
*
|
|
22
|
+
* - `workflow_stale` — your copy is out of date. Reload and re-save. ← this one
|
|
23
|
+
* - `workflow_conflict` — another `(tenant, subject)` owns that workflow id.
|
|
24
|
+
* Reloading changes nothing; offering a reload here would be a dead end.
|
|
25
|
+
*
|
|
26
|
+
* Anything that carries no `code` (a thrown `TypeError`, a string, `null`) is
|
|
27
|
+
* not a server verdict at all, so it is not a stale write either.
|
|
28
|
+
*
|
|
29
|
+
* Duck-typed on the `code` property rather than `instanceof ApiError`: this module is
|
|
30
|
+
* headless and must not value-import the SDK, and two SDK copies would defeat `instanceof`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isStaleWrite(e: unknown): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The precondition token to send with the NEXT save, given the token we sent
|
|
35
|
+
* with this one and the response that came back.
|
|
36
|
+
*
|
|
37
|
+
* The server rides `updatedAt` on both the `GET /workflows/:id` body and the
|
|
38
|
+
* `POST /workflows` 201, precisely so a client can advance without a re-GET.
|
|
39
|
+
*
|
|
40
|
+
* When the response carries no usable `updatedAt`, the answer is **`null`** (no
|
|
41
|
+
* precondition on the next save), never `prev`: `prev` is by then a value the
|
|
42
|
+
* server may already have moved past, so resending it would 409 forever with no
|
|
43
|
+
* way out. Dropping the guard for one save is the weaker failure — it is exactly
|
|
44
|
+
* the pre-existing last-write-wins behaviour, which the header is optional for.
|
|
45
|
+
*/
|
|
46
|
+
export declare function nextSaveToken(prev: string | null, res: {
|
|
47
|
+
updatedAt?: unknown;
|
|
48
|
+
}): string | null;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createElement as c, useContext as u, createContext as l } from "react";
|
|
2
|
+
const m = 5e3;
|
|
3
|
+
function y() {
|
|
4
|
+
const e = /* @__PURE__ */ new Set();
|
|
5
|
+
return {
|
|
6
|
+
register(r) {
|
|
7
|
+
const t = () => r();
|
|
8
|
+
return e.add(t), () => {
|
|
9
|
+
e.delete(t);
|
|
10
|
+
};
|
|
11
|
+
},
|
|
12
|
+
async flushAll(r = m) {
|
|
13
|
+
const t = [...e].map(
|
|
14
|
+
(n) => new Promise((s) => {
|
|
15
|
+
try {
|
|
16
|
+
Promise.resolve(n()).then(
|
|
17
|
+
() => s(),
|
|
18
|
+
() => s()
|
|
19
|
+
);
|
|
20
|
+
} catch {
|
|
21
|
+
s();
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
let i;
|
|
26
|
+
const a = new Promise((n) => {
|
|
27
|
+
i = setTimeout(n, r);
|
|
28
|
+
});
|
|
29
|
+
try {
|
|
30
|
+
await Promise.race([Promise.all(t), a]);
|
|
31
|
+
} finally {
|
|
32
|
+
clearTimeout(i);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const o = l(null);
|
|
38
|
+
function d(e) {
|
|
39
|
+
return c(o.Provider, { value: e.registry }, e.children);
|
|
40
|
+
}
|
|
41
|
+
function P() {
|
|
42
|
+
return u(o);
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
d as D,
|
|
46
|
+
y as c,
|
|
47
|
+
P as u
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=draft-registry-YF0tqNN0.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft-registry-YF0tqNN0.js","sources":["../src/draft/draft-registry.ts"],"sourcesContent":["/**\n * The draft registry: every mounted `useWorkflowDraft` registers its `flush` here so a host can\n * settle all of them before an identity swap (logout, account switch) — see `flushAll`.\n *\n * `flushAll` NEVER rejects and is bounded: a flush that throws synchronously, rejects, or never\n * settles must not block or fail the swap. Each call is wrapped individually so one bad draft\n * cannot stop the others being awaited (`createAutoSaver().flush()` rejects when its save rejects\n * and lets a synchronous throw from `save` escape synchronously — both are absorbed here).\n */\nimport { type ReactNode, createContext, createElement, useContext } from \"react\";\n\nexport interface DraftRegistry {\n /** Register a draft's flush. Returns the unregister function. */\n register(flush: () => Promise<unknown>): () => void;\n /** Await every registered flush, at most `timeoutMs` (default 5000). Never rejects. */\n flushAll(timeoutMs?: number): Promise<void>;\n}\n\nconst DEFAULT_FLUSH_ALL_TIMEOUT_MS = 5000;\n\nexport function createDraftRegistry(): DraftRegistry {\n const flushes = new Set<() => Promise<unknown>>();\n\n return {\n register(flush) {\n // Wrapped so registering the same function twice yields two independent entries.\n const entry = () => flush();\n flushes.add(entry);\n return () => {\n flushes.delete(entry);\n };\n },\n async flushAll(timeoutMs = DEFAULT_FLUSH_ALL_TIMEOUT_MS) {\n const settled = [...flushes].map(\n (flush) =>\n new Promise<void>((resolve) => {\n try {\n Promise.resolve(flush()).then(\n () => resolve(),\n () => resolve(),\n );\n } catch {\n resolve();\n }\n }),\n );\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, timeoutMs);\n });\n try {\n await Promise.race([Promise.all(settled), timeout]);\n } finally {\n clearTimeout(timer);\n }\n },\n };\n}\n\nconst DraftRegistryContext = createContext<DraftRegistry | null>(null);\n\nexport function DraftRegistryProvider(p: { registry: DraftRegistry; children?: ReactNode }) {\n return createElement(DraftRegistryContext.Provider, { value: p.registry }, p.children);\n}\n\n/** The nearest registry, or `null` when there is no provider (the hook then skips registering). */\nexport function useDraftRegistry(): DraftRegistry | null {\n return useContext(DraftRegistryContext);\n}\n"],"names":["DEFAULT_FLUSH_ALL_TIMEOUT_MS","createDraftRegistry","flushes","flush","entry","timeoutMs","settled","resolve","timer","timeout","DraftRegistryContext","createContext","DraftRegistryProvider","p","createElement","useDraftRegistry","useContext"],"mappings":";AAkBA,MAAMA,IAA+B;AAE9B,SAASC,IAAqC;AACnD,QAAMC,wBAAc,IAAA;AAEpB,SAAO;AAAA,IACL,SAASC,GAAO;AAEd,YAAMC,IAAQ,MAAMD,EAAA;AACpB,aAAAD,EAAQ,IAAIE,CAAK,GACV,MAAM;AACX,QAAAF,EAAQ,OAAOE,CAAK;AAAA,MACtB;AAAA,IACF;AAAA,IACA,MAAM,SAASC,IAAYL,GAA8B;AACvD,YAAMM,IAAU,CAAC,GAAGJ,CAAO,EAAE;AAAA,QAC3B,CAACC,MACC,IAAI,QAAc,CAACI,MAAY;AAC7B,cAAI;AACF,oBAAQ,QAAQJ,EAAA,CAAO,EAAE;AAAA,cACvB,MAAMI,EAAA;AAAA,cACN,MAAMA,EAAA;AAAA,YAAQ;AAAA,UAElB,QAAQ;AACN,YAAAA,EAAA;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MAAA;AAEL,UAAIC;AACJ,YAAMC,IAAU,IAAI,QAAc,CAACF,MAAY;AAC7C,QAAAC,IAAQ,WAAWD,GAASF,CAAS;AAAA,MACvC,CAAC;AACD,UAAI;AACF,cAAM,QAAQ,KAAK,CAAC,QAAQ,IAAIC,CAAO,GAAGG,CAAO,CAAC;AAAA,MACpD,UAAA;AACE,qBAAaD,CAAK;AAAA,MACpB;AAAA,IACF;AAAA,EAAA;AAEJ;AAEA,MAAME,IAAuBC,EAAoC,IAAI;AAE9D,SAASC,EAAsBC,GAAsD;AAC1F,SAAOC,EAAcJ,EAAqB,UAAU,EAAE,OAAOG,EAAE,SAAA,GAAYA,EAAE,QAAQ;AACvF;AAGO,SAASE,IAAyC;AACvD,SAAOC,EAAWN,CAAoB;AACxC;"}
|