@w6w/embed 0.0.0-stage → 0.1.1

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.
Files changed (102) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +166 -2
  3. package/dist/W6WEmbedProvider.d.ts +29 -0
  4. package/dist/chrome/CallFromCodeButton.d.ts +155 -0
  5. package/dist/chrome/CallableSettingsModal.d.ts +17 -0
  6. package/dist/chrome/ConfirmModal.d.ts +20 -0
  7. package/dist/chrome/DangerZone.d.ts +19 -0
  8. package/dist/chrome/ExecutionProfileHeader.d.ts +69 -0
  9. package/dist/chrome/HistoryButton.d.ts +8 -0
  10. package/dist/chrome/HistoryModal.d.ts +9 -0
  11. package/dist/chrome/HistoryView.d.ts +15 -0
  12. package/dist/chrome/ImportSpecModal.d.ts +40 -0
  13. package/dist/chrome/JsonToggle.d.ts +51 -0
  14. package/dist/chrome/Modal.d.ts +20 -0
  15. package/dist/chrome/RightRail.d.ts +24 -0
  16. package/dist/chrome/TagEditor.d.ts +23 -0
  17. package/dist/chrome/VersionHistoryModal.d.ts +38 -0
  18. package/dist/chrome/history-open-run.d.ts +7 -0
  19. package/dist/chrome/index.d.ts +36 -0
  20. package/dist/chrome/workspace.d.ts +67 -0
  21. package/dist/client-context-CX61Bgw-.js +16 -0
  22. package/dist/client-context-CX61Bgw-.js.map +1 -0
  23. package/dist/draft/autosave-flush.d.ts +63 -0
  24. package/dist/draft/autosave.d.ts +68 -0
  25. package/dist/draft/client-context.d.ts +8 -0
  26. package/dist/draft/draft-registry.d.ts +14 -0
  27. package/dist/draft/index.d.ts +15 -0
  28. package/dist/draft/use-workflow-draft.d.ts +67 -0
  29. package/dist/draft/workflow-save.d.ts +48 -0
  30. package/dist/draft-registry-YF0tqNN0.js +49 -0
  31. package/dist/draft-registry-YF0tqNN0.js.map +1 -0
  32. package/dist/draft.d.ts +2 -0
  33. package/dist/draft.mjs +18 -0
  34. package/dist/draft.mjs.map +1 -0
  35. package/dist/editor/WorkflowEditor.d.ts +12 -0
  36. package/dist/editor/WorkflowSettingsModal.d.ts +17 -0
  37. package/dist/editor/definition.d.ts +31 -0
  38. package/dist/editor/editor-apps.d.ts +7 -0
  39. package/dist/editor/types.d.ts +34 -0
  40. package/dist/editor.d.ts +9 -0
  41. package/dist/editor.mjs +2170 -0
  42. package/dist/editor.mjs.map +1 -0
  43. package/dist/index.d.ts +1 -0
  44. package/dist/index.mjs +67 -0
  45. package/dist/index.mjs.map +1 -0
  46. package/dist/lib/callable-descriptors.d.ts +77 -0
  47. package/dist/lib/callable-kind.d.ts +13 -0
  48. package/dist/lib/code-rail.d.ts +45 -0
  49. package/dist/lib/document-sources.d.ts +26 -0
  50. package/dist/lib/error-handler-self.d.ts +25 -0
  51. package/dist/lib/history-query.d.ts +135 -0
  52. package/dist/lib/import-paste.d.ts +41 -0
  53. package/dist/lib/index.d.ts +17 -0
  54. package/dist/lib/new-id.d.ts +10 -0
  55. package/dist/lib/rail-settings.d.ts +196 -0
  56. package/dist/lib/save-invalidation.d.ts +24 -0
  57. package/dist/lib/sdk-snippets.d.ts +96 -0
  58. package/dist/lib/settings-rail.d.ts +68 -0
  59. package/dist/lib/slug.d.ts +69 -0
  60. package/dist/lib/spec-import-invalidation.d.ts +48 -0
  61. package/dist/lib/tag-editor.d.ts +36 -0
  62. package/dist/lib/trigger-preview.d.ts +89 -0
  63. package/dist/lib/workflow-live-run.d.ts +165 -0
  64. package/dist/lib/workflow-poll.d.ts +8 -0
  65. package/dist/lib/workflow-route.d.ts +26 -0
  66. package/dist/lib/workflow-settings.d.ts +72 -0
  67. package/dist/lib/workflow-validity.d.ts +92 -0
  68. package/dist/lib/workflow-variables.d.ts +106 -0
  69. package/dist/lib.d.ts +2 -0
  70. package/dist/lib.mjs +62 -0
  71. package/dist/lib.mjs.map +1 -0
  72. package/dist/project-context-Dbk0Gr1K.js +16 -0
  73. package/dist/project-context-Dbk0Gr1K.js.map +1 -0
  74. package/dist/project-context.d.ts +11 -0
  75. package/dist/rail/CodeTool.d.ts +2 -0
  76. package/dist/rail/ErrorHandlingTool.d.ts +22 -0
  77. package/dist/rail/ExpressionScope.d.ts +21 -0
  78. package/dist/rail/SettingsTool.d.ts +27 -0
  79. package/dist/rail/editor-tools.d.ts +2 -0
  80. package/dist/rail/rail-env.d.ts +18 -0
  81. package/dist/rail/settings-sections.d.ts +93 -0
  82. package/dist/repos/documents.d.ts +102 -0
  83. package/dist/repos/history.d.ts +24 -0
  84. package/dist/repos/index.d.ts +22 -0
  85. package/dist/repos/spec-import.d.ts +44 -0
  86. package/dist/repos/step-tests.d.ts +19 -0
  87. package/dist/repos/vars.d.ts +25 -0
  88. package/dist/repos/vault.d.ts +24 -0
  89. package/dist/repos/versions.d.ts +112 -0
  90. package/dist/repos.d.ts +2 -0
  91. package/dist/repos.mjs +94 -0
  92. package/dist/repos.mjs.map +1 -0
  93. package/dist/slug-ByKQgxrf.js +642 -0
  94. package/dist/slug-ByKQgxrf.js.map +1 -0
  95. package/dist/spec-import-HgO7ZkLZ.js +898 -0
  96. package/dist/spec-import-HgO7ZkLZ.js.map +1 -0
  97. package/dist/spec-import-invalidation-D3retX4Q.js +18 -0
  98. package/dist/spec-import-invalidation-D3retX4Q.js.map +1 -0
  99. package/dist/use-workflow-draft-oI02dsP-.js +314 -0
  100. package/dist/use-workflow-draft-oI02dsP-.js.map +1 -0
  101. package/package.json +84 -3
  102. package/src/styles.css +829 -0
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Pure decisions behind the right rail's Settings tool (`src/ui/rail/SettingsTool.tsx`,
3
+ * `id: "aliases"`, `title: "Settings"` since T3.1.2 — the tool widened past aliases alone
4
+ * into a resource Settings panel: a metadata block and, for Functions and Endpoints, an
5
+ * Export / Edit JSON action row (T3.3.1 — the definition itself no longer renders in the
6
+ * panel, only the two buttons that open it in a modal), alongside the alias list
7
+ * `../lib/alias-rail.ts` already decides). Split out so `node --test` can gate them
8
+ * directly — same reasoning as
9
+ * `alias-rail.ts`'s own header: `node --test` throws `ERR_UNKNOWN_FILE_EXTENSION` on
10
+ * anything reaching the `@w6w/ui` / `Layout.tsx` import graph, and `src/ui/workspace.tsx`
11
+ * sits one hop from that barrel through `Layout.tsx`'s import graph. So this module
12
+ * declares its own structural mirror of the kinds it cares about instead of importing
13
+ * `WorkspaceKind`, and takes only `import type` throughout.
14
+ *
15
+ * T4.1.1 widens this file's scope past the Settings tool itself: `restoreRailState`
16
+ * (A2, `RightRail.tsx`'s persisted-state read path) and `isRowArchived`/`visibleRows`
17
+ * (A6, the three listing pages' "Show archived" toggle) are pure decisions with the
18
+ * exact same `node --test`-reachability constraint, so they land here rather than in
19
+ * a new sibling module the contract's `inputs.touch` list didn't ask for.
20
+ */
21
+ /** Structural mirror of `WorkspaceCallable["kind"]` (`src/ui/workspace.tsx`) — every kind
22
+ * the rail can have open, not only the two this tool's action-row/metadata sections
23
+ * apply to. */
24
+ export type RailSettingsKind = "workflow" | "function" | "endpoint" | "connection";
25
+ /**
26
+ * A4/T3.1.1: which metadata rows the Settings panel shows for this kind. `id` always
27
+ * (every kind the tool is available on carries one). `key` for `function`/`endpoint`
28
+ * (unchanged) AND, since T3.1.1, `workflow` too — `GET /workflows` now carries `key`
29
+ * on every row (`string | null`, D-1: nullable, no backfill), and the panel renders it
30
+ * as an editable control for `function`/`workflow` and the existing read-only `<code>`
31
+ * for `endpoint` (`SettingsTool.tsx` gates that render choice on `ctx.kind` directly,
32
+ * never on this array — an Endpoint's `key` entry here must not be read as "editable").
33
+ * `updatedAt` stays `function`/`endpoint`-only: nothing asks for a Workflow Updated row,
34
+ * and adding one would be a second, unrelated rail row. Deliberately never `createdAt`
35
+ * (note 6): neither `GET /functions/:id` nor `GET /endpoints/:id` returns it, so
36
+ * surfacing it would take a server change for one cosmetic field — this function's own
37
+ * absence of a `createdAt` arm is half of that gate, the other half is the negative
38
+ * assertion in the caller's markup.
39
+ */
40
+ export declare function metadataFieldsForKind(kind: RailSettingsKind): ReadonlyArray<"id" | "key" | "updatedAt">;
41
+ /**
42
+ * A2/A5: the Export / Edit JSON action row exists for `function`/`endpoint` only. A
43
+ * Workflow already has its own read-only Definition card
44
+ * (`pages/WorkflowDetailPage.tsx:371-388`); a Connection has no stored definition shaped
45
+ * like these two to show at all. (T3.3.1: renamed from the panel's earlier JSON-section
46
+ * name — there is no JSON section left in the panel to describe, only the two buttons
47
+ * that open one in a modal.)
48
+ */
49
+ export declare function hasDefinitionActions(kind: RailSettingsKind): boolean;
50
+ /**
51
+ * A1/T3.1.1: does this kind have a version-history browser (the read-only browse list plus
52
+ * Publish and Enable/Disable) in the Settings panel? `true` for `function`/`workflow`/`endpoint`,
53
+ * `false` for `connection` — written as a `switch` naming every one of the four `RailSettingsKind`
54
+ * arms explicitly, with no `default:`, matching {@link specCollectionForKind}'s shape and the
55
+ * reason its own docblock gives (`:69` below — "every one of the four `RailSettingsKind` arms is
56
+ * named explicitly below" so a kind can never silently fall through). Deliberately NOT derived
57
+ * from {@link hasDefinitionActions}, which is `function`/`endpoint`-only: History covers Workflow
58
+ * too, so deriving one from the other would silently drop it.
59
+ */
60
+ export declare function hasVersionHistory(kind: RailSettingsKind): boolean;
61
+ /**
62
+ * A1/T1.3.1: does this kind get a Delete (or, for `workflow`, Archive-then-Delete) affordance at
63
+ * the bottom of the Settings panel? `true` for `function`/`workflow`/`endpoint`, `false` for
64
+ * `connection` — written as a named `switch` naming every one of the four `RailSettingsKind` arms
65
+ * explicitly, with no `default:`, matching {@link hasVersionHistory}'s exact shape (`:72-81` above)
66
+ * for the same reason its own doc comment gives: a kind can never silently fall through. Deliberately
67
+ * NOT derived from {@link hasVersionHistory} even though the two currently agree on every arm — a
68
+ * future kind that gets history but not deletion (or vice versa) must not silently inherit the
69
+ * wrong answer from a sibling predicate.
70
+ */
71
+ export declare function hasDelete(kind: RailSettingsKind): boolean;
72
+ /**
73
+ * A4: which `/<collection>/:id/spec` route the Export button's `useSpecQuery`
74
+ * (`../repos/spec-export.ts`) must call for this kind — `null` for the two kinds A2's
75
+ * action row never renders on. Kept in lockstep with {@link hasDefinitionActions}
76
+ * (both are `function`/`endpoint`-only) but expressed as its own total function rather
77
+ * than derived from it, so a Function can never be exported through
78
+ * `/endpoints/:id/spec` by a wrong `if`/`else` fallthrough — every one of the four
79
+ * `RailSettingsKind` arms is named explicitly below.
80
+ */
81
+ export declare function specCollectionForKind(kind: RailSettingsKind): "functions" | "endpoints" | null;
82
+ /** What A8's `evaluateEdit` needs to know about the row already on screen — deliberately
83
+ * narrower than a whole `FunctionDef`/`EndpointDef`: the two fields a hand-edit must not
84
+ * be allowed to change out from under the row it opened on. */
85
+ export interface EditStored {
86
+ id: string;
87
+ key: string;
88
+ }
89
+ /** A8's total function's result: the parsed body (when parseable and a plain object, even
90
+ * if it goes on to fail arm 2 or 3 — the modal still has something to inspect for the
91
+ * render, just never something Save may submit) plus the one reason Save is refused, or
92
+ * `null` when the edit is allowed through. */
93
+ export interface EditEvaluation {
94
+ parsed: Record<string, unknown> | null;
95
+ blockedReason: string | null;
96
+ }
97
+ /**
98
+ * A8: is this edited text a Save the rail may submit? A total function over the modal's
99
+ * OWN text state — never `JsonEditor`'s `onValidityChange` (note 10: the only browser
100
+ * harness this repo has stubs `JsonEditor` with a callback that is wired but never
101
+ * invoked, so a Save gated on it would look gated and be inert; parsing `text` here is
102
+ * behaviourally identical and fully observable by a page guard).
103
+ *
104
+ * Three refusals, evaluated in order, each named for the near-miss it kills:
105
+ * 1. the text does not parse as JSON, or parses to something that is not a plain object
106
+ * (`null` and an array both fail here, not a dedicated fourth arm — a
107
+ * `FunctionDef`/`EndpointDef` is never legitimately either);
108
+ * 2. `parsed.id !== stored.id` (note 9 — `FunctionsRepo.upsert` selects the row by
109
+ * `definition.id`, so an edited id does not edit this row, it creates or hijacks a
110
+ * different one);
111
+ * 3. `parsed.key !== stored.key` (note 8 — `POST /endpoints` 409s on a changed key while
112
+ * `POST /functions` silently accepts one; locking it here keeps both kinds behaving
113
+ * the same from this one modal).
114
+ */
115
+ export declare function evaluateEdit(text: string, stored: EditStored): EditEvaluation;
116
+ /** A7/D-3's total function's result: may the save proceed, and if not, why —
117
+ * rendered verbatim in `WorkflowsPage.tsx`'s existing error/banner idiom. */
118
+ export interface KeyEditEvaluation {
119
+ allowed: boolean;
120
+ reason: string | null;
121
+ }
122
+ /**
123
+ * D-3: is a Workflow save carrying this `key` one `WorkflowsPage.tsx`'s raw-JSON
124
+ * editor may submit? The right rail's Key control (`SettingsTool.tsx`) is the ONLY
125
+ * path allowed to assign or change a workflow's key (see this project's Requirement);
126
+ * this total function is what keeps a hand-edit of the JSON — via the Save button OR
127
+ * auto-save, both of which must fold this in (see `WorkflowsPage.tsx`'s `canSave`) —
128
+ * from doing it silently instead.
129
+ *
130
+ * `loadedKey` is the key `WorkflowsPage.tsx` most recently loaded FROM THE SERVER
131
+ * (its own `loadedKeyRef`, captured at both of its load sites — the initial `editId`
132
+ * effect and `reloadFromServer`). `undefined` means nothing has loaded yet, which
133
+ * refuses unconditionally: there is nothing yet to compare the parsed text against,
134
+ * so allowing the save through would let an unloaded/stale comparison pass by
135
+ * accident. Once loaded, `loadedKey` is `string | null` (P5's unset state, D-1).
136
+ *
137
+ * `parsedKey` is whatever the parsed JSON text's own `key` field currently holds.
138
+ * `undefined` (the field is absent from the text) is treated exactly like an
139
+ * explicit `null` — both mean "no key" on the wire, and a definition that dropped
140
+ * the field entirely must be refused exactly like one that changed it, never waved
141
+ * through as "nothing to compare".
142
+ */
143
+ export declare function workflowKeyEditAllowed(loadedKey: string | null | undefined, parsedKey: unknown): KeyEditEvaluation;
144
+ /**
145
+ * T4.1.1/A2: the shape actually written to `localStorage` for the right
146
+ * rail's persisted state (`RightRail.tsx`'s `PersistedRail`) — `activeTool`
147
+ * ONLY. `open` is deliberately absent from this interface, not merely
148
+ * unwritten: {@link restoreRailState} below has nothing to read it FROM, so
149
+ * a pre-existing `{"open":true,…}` value written before this task cannot
150
+ * reopen the rail even by accident.
151
+ */
152
+ export interface StoredRailState {
153
+ activeTool: string | null;
154
+ }
155
+ /**
156
+ * A2: parses `localStorage`'s raw rail entry into {@link StoredRailState}.
157
+ * Missing/unparsable input, or a non-string `activeTool`, all fall back to
158
+ * `{ activeTool: null }` — the same defensive shape `RightRail.tsx`'s own
159
+ * `readStoredState` used before this task, minus the `open` field entirely.
160
+ * `RightRail.tsx` is the one caller; it combines this result with a hardcoded
161
+ * `open: false` on every read (D-I0-2) — that combination happens there, not
162
+ * here, because `open`'s absence from THIS function's return type is the
163
+ * proof a page guard can pin: there is no `open` member left to misread.
164
+ */
165
+ export declare function restoreRailState(raw: string | null): StoredRailState;
166
+ /**
167
+ * A6: a listing row either Workflow, Function or Endpoint's list carries —
168
+ * whichever archived signal the row's own kind uses. Workflow rows signal
169
+ * archived via the existing `status === "archived"` (`WorkflowSummary`);
170
+ * Function/Endpoint rows via the new `archivedAt` column T1.1.1 ships
171
+ * (`string | null`). Both fields optional so one predicate below covers all
172
+ * three wire shapes without a per-kind branch at each listing page's call
173
+ * site.
174
+ */
175
+ export interface ArchivableRow {
176
+ status?: string;
177
+ archivedAt?: string | null;
178
+ }
179
+ /**
180
+ * A6: is this row archived? `status` takes priority when the row carries one
181
+ * (Workflow rows always do) — falls back to `archivedAt` otherwise (Function/
182
+ * Endpoint rows, which have no `status` field at all).
183
+ */
184
+ export declare function isRowArchived(row: ArchivableRow): boolean;
185
+ /**
186
+ * A6: which rows a listing page's "Show archived" toggle should render.
187
+ * `showArchived` false (the default) drops every archived row; `true`
188
+ * reveals them ALONGSIDE the live ones — an include, never a toggle that
189
+ * hides the rest (the contract's mutation M6: a toggle that hides LIVE rows
190
+ * once ON must fail the "both a live and an archived row survive together"
191
+ * case below; M7: defaulting to showing archived rows must fail the
192
+ * toggle-OFF case). One pure function shared by `WorkflowsPage.tsx`,
193
+ * `FunctionsPage.tsx` and `EndpointsPage.tsx` rather than three inline
194
+ * `.filter`s.
195
+ */
196
+ export declare function visibleRows<T extends ArchivableRow>(rows: readonly T[], showArchived: boolean): T[];
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared invalidation for a whole-definition save (T3.1.1). `useSaveFunctionMutation` and
3
+ * `useSaveEndpointMutation` (`src/repos/functions.ts`, `src/repos/endpoints.ts`) both route their
4
+ * `onSuccess` through this one helper rather than inlining the two `invalidateQueries` calls at
5
+ * each site — so a save invalidates the LIST key (both list pages and the editor depend on it)
6
+ * *and* the saved resource's own single-resource key (`functionKey(id)` / `endpointKey(id)`),
7
+ * never just the list.
8
+ *
9
+ * No value imports on purpose: `node --test` throws `ERR_UNKNOWN_FILE_EXTENSION` on anything that
10
+ * reaches the `@w6w/ui` / `Layout.tsx` import graph, and `src/repos/*.ts` reaches it through
11
+ * `../api/apiClient.tsx` (JSX). This module has none of that, so it is loadable by `node --test`
12
+ * directly — see `src/lib/alias-rail.ts`'s docstring for the same reasoning.
13
+ */
14
+ export interface QueryInvalidator {
15
+ invalidateQueries(filters: {
16
+ queryKey: readonly unknown[];
17
+ }): unknown;
18
+ }
19
+ /**
20
+ * Invalidate the list key, then the saved resource's own key. List first: both keys are
21
+ * invalidated regardless of order, but the order is asserted (see the unit test) so a future edit
22
+ * that drops one call is a diff a reviewer can spot in a single line.
23
+ */
24
+ export declare function invalidateSavedCallable(qc: QueryInvalidator, listKey: readonly unknown[], callableKey: readonly unknown[]): void;
@@ -0,0 +1,96 @@
1
+ import { TriggerPreviewFieldDef } from './trigger-preview.ts';
2
+ /**
3
+ * One declared input of a Function or Endpoint, as a snippet needs it.
4
+ *
5
+ * A BARE STRING is treated as REQUIRED. That is the shape this file took before
6
+ * it knew about optionality, when every input rendered active — so a caller that
7
+ * only has keys keeps its existing output rather than silently having every
8
+ * field commented out.
9
+ */
10
+ export type SnippetInput = string | {
11
+ key: string;
12
+ required?: boolean;
13
+ };
14
+ /** What the caller is looking at. Mirrors the four runnable URN arms plus assets. */
15
+ export type SnippetCallable = {
16
+ kind: "workflow";
17
+ id: string;
18
+ displayName?: string;
19
+ key?: string;
20
+ alias?: string;
21
+ } | {
22
+ kind: "function";
23
+ id: string;
24
+ key?: string;
25
+ inputs?: readonly SnippetInput[];
26
+ alias?: string;
27
+ }
28
+ /**
29
+ * An Endpoint. `invokeUrl` / `invokeUrlByKey` are the two SERVER-rendered
30
+ * forms of its public address, passed straight through from the endpoints
31
+ * envelope; `urlForm` is which of them the page currently has selected, so a
32
+ * raw-HTTP sample follows the toggle the reader can see. All three are
33
+ * optional — an Endpoint with no URL in hand simply gets no HTTP snippet.
34
+ * `inputs` are the target's input parameter keys, resolved from the Function
35
+ * or Workflow the Endpoint dispatches to.
36
+ */
37
+ | {
38
+ kind: "endpoint";
39
+ id: string;
40
+ key?: string;
41
+ invokeUrl?: string;
42
+ invokeUrlByKey?: string;
43
+ urlForm?: "id" | "key";
44
+ alias?: string;
45
+ inputs?: readonly SnippetInput[];
46
+ }
47
+ /** One app action, invoked through a connection — the `conn_` URN arm. */
48
+ | {
49
+ kind: "connection";
50
+ id: string;
51
+ action: string;
52
+ params?: string[];
53
+ } | {
54
+ kind: "var";
55
+ name: string;
56
+ } | {
57
+ kind: "document";
58
+ key: string;
59
+ };
60
+ export interface Snippet {
61
+ /** Runnable TypeScript against `@w6w/sdk`. */
62
+ node: string;
63
+ /** A single `w6w …` invocation. */
64
+ cli: string;
65
+ /** Runnable Python against `w6w` (PyPI). */
66
+ python: string;
67
+ /**
68
+ * A raw-HTTP (`curl`) call against the resource's own public URL — OPTIONAL,
69
+ * and emitted only where such a URL exists and was handed in (today: an
70
+ * Endpoint). Optional rather than required so the other five `case` arms of
71
+ * {@link buildSnippet} are untouched, and so the tab strip can render only
72
+ * the tabs a built snippet actually carries.
73
+ */
74
+ curl?: string;
75
+ /** The contract operation this maps to, shown so the snippet is checkable. */
76
+ operation: string;
77
+ }
78
+ /**
79
+ * Can this id be passed to `client.run({urn})` at all?
80
+ *
81
+ * Ids are host-issued and carry a kind prefix (rfcs/function.md §id — "host-issued
82
+ * opaque id (`fn_…`)"), but the create routes do not *enforce* it, so a
83
+ * hand-authored id can reach the database without one. Such a resource is
84
+ * reachable through the studio and through its own `/invoke` route, but NOT
85
+ * through the URN operation the wrappers expose — and a snippet is a promise
86
+ * that the code in it runs.
87
+ */
88
+ export declare function isUrnAddressable(id: string): boolean;
89
+ /**
90
+ * Build the Node + CLI snippet for one resource, or `null` when the contract
91
+ * has no operation for it (today: a function or endpoint whose id carries no
92
+ * runnable prefix).
93
+ */
94
+ export declare function buildSnippet(resource: SnippetCallable, triggerFields?: readonly TriggerPreviewFieldDef[]): Snippet | null;
95
+ /** Why no snippet exists, phrased for the reader rather than for a log. */
96
+ export declare function unavailableReason(resource: SnippetCallable): string;
@@ -0,0 +1,68 @@
1
+ import { CallableOnError, ErrorReroute, RetryPolicy } from '@w6w/sdk/console';
2
+ /** Structural mirror of `WorkspaceCallable` (`src/ui/workspace.tsx`) — only
3
+ * the fields this module reads. `onPolicyChange` is the WORKFLOW arm's
4
+ * injected writer (see {@link isAvailable}); the two callable arms have no
5
+ * such member and never read it. */
6
+ export type SettingsCallable = {
7
+ kind: string;
8
+ id: string;
9
+ onPolicyChange?: (policy: CallablePolicy) => void;
10
+ } | null;
11
+ /** The three fields this tool owns on a Function/Endpoint definition —
12
+ * TC2's pinned shape, shared by both `Fn` and `Endpoint`. */
13
+ export interface CallablePolicy {
14
+ retry?: RetryPolicy;
15
+ onError?: CallableOnError;
16
+ reroute?: ErrorReroute;
17
+ }
18
+ /** A definition carrying (at least) the three fields above, plus whatever
19
+ * else `Fn`/`Endpoint` declares — `definitionWithPolicy` preserves every
20
+ * field this type doesn't name. */
21
+ export type CallableDefinition = CallablePolicy;
22
+ /**
23
+ * Is the Error handling tool meaningful for this context?
24
+ *
25
+ * A Function and an Endpoint: always — the panel owns their whole
26
+ * read-modify-write, because nothing else on the page is editing the same
27
+ * definition at the same time.
28
+ *
29
+ * A Workflow: only when the publishing page injects `onPolicyChange` (core
30
+ * Workflow RFC — *Amendment — 2026-08-21: the run-level error handler*). This
31
+ * is not fussiness. The workflow canvas holds the definition as JSON TEXT it
32
+ * owns, and auto-saves it (`settings.autoSave` defaults to ON), so a rail
33
+ * panel doing its own read-modify-write against the server would be racing the
34
+ * editor: whichever wrote last would silently drop the other's edit, and the
35
+ * loser would most often be the rail, since the canvas re-sends its whole
36
+ * buffer on the next keystroke. Injecting the writer instead means the rail
37
+ * edits the SAME buffer the canvas is about to save. A page that does not own
38
+ * that buffer (the run-detail page) publishes no writer, and the tool simply
39
+ * does not appear there.
40
+ *
41
+ * A Connection has no run-level policy at all. `null` (no resource published)
42
+ * fails closed, house style (mirrors `code-rail.ts`'s `isAvailable`).
43
+ */
44
+ export declare function isAvailable(ctx: SettingsCallable): boolean;
45
+ /**
46
+ * Read a definition's `{retry, onError, reroute}` into the shape the panel's
47
+ * local editing state starts from. Normalizes the same way
48
+ * `definitionWithPolicy` writes: a stored `onError: "fail"` (never written by
49
+ * this tool, but defensive against a hand-edited or older definition) reads
50
+ * back as absent, matching how `NodeConfigForm` already treats `"fail"` as
51
+ * its own default.
52
+ */
53
+ export declare function policyFromDefinition(def: CallableDefinition): CallablePolicy;
54
+ /**
55
+ * Read-modify-write: apply `policy`'s `{retry, onError, reroute}` onto `def`,
56
+ * preserving every OTHER field untouched (`impl`/`inputs`/`key` on a
57
+ * Function, `target`/`input`/`key` on an Endpoint, …) — the save this tool
58
+ * issues is never a bare `{retry, onError, reroute}` object. A field whose
59
+ * value is the default is OMITTED entirely, not written as `undefined`:
60
+ * `policy.onError === "fail"` (or absent) drops the key, matching
61
+ * `NodeConfigForm`'s own "fail" -> absent convention for the field it edits
62
+ * in place. Built by destructuring the three fields OUT of `def` and
63
+ * conditionally re-spreading them, rather than `delete`, so the key is
64
+ * genuinely absent from the returned object, not merely `undefined`-valued —
65
+ * `node:assert/strict`'s deep-equal distinguishes the two (this module's own
66
+ * test pins it).
67
+ */
68
+ export declare function definitionWithPolicy<T extends CallableDefinition>(def: T, policy: CallablePolicy): T;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Slug rules — the ONE place in `studio/src` that owns one.
3
+ *
4
+ * Two rules live here and they are deliberately NOT the same rule:
5
+ *
6
+ * · `slugify()` turns a human display name into a machine key. It is the
7
+ * fallback the Endpoint and Function editors use when the operator leaves
8
+ * `key` blank, and it is permissive on purpose: whatever it returns is a
9
+ * legal resource key, digits-first and two characters included.
10
+ * · `ACCOUNT_SLUG_RE` / `isValidAccountSlug()` decide whether a string is a
11
+ * legal ACCOUNT slug — the account segment a key-form invoke URL is
12
+ * addressed by. That rule is stricter: it must start with a letter and run to
13
+ * at least three characters, so `2024-team` and `ab` are both perfectly good
14
+ * keys and both invalid account slugs.
15
+ *
16
+ * The regex is exported rather than re-typed by each caller. A second copy is
17
+ * exactly how a form comes to accept a value the server rejects, so the literal
18
+ * appears in this file and nowhere else in `src/`.
19
+ *
20
+ * Why `src/lib` and why this module imports nothing: `src/lib/*.ts` is the only
21
+ * place studio's `node --test` suite can reach. A rule living inside a `.tsx`
22
+ * page cannot be unit-tested at all (Node's type-stripping does not parse JSX),
23
+ * and a `src/lib` module that imports `@w6w/ui` throws
24
+ * `ERR_UNKNOWN_FILE_EXTENSION` under the same runner because the barrel
25
+ * re-exports `.tsx`. Same constraint documented at
26
+ * `src/lib/workflow-validity.ts:11-20`.
27
+ */
28
+ /** Kebab-case a display name into a machine key (the fallback when key is blank). */
29
+ export declare function slugify(s: string): string;
30
+ /**
31
+ * A legal account slug: lowercase, opens with a LETTER, then two or more
32
+ * letters/digits/hyphens — three characters minimum.
33
+ *
34
+ * Note this is NOT the server's `tenants.ts` `SLUG_RE`
35
+ * (`/^[a-z0-9][a-z0-9_-]*$/`), which governs tenant ids and is a different,
36
+ * more permissive rule. Do not merge the two.
37
+ */
38
+ export declare const ACCOUNT_SLUG_RE: RegExp;
39
+ /** Does `s` satisfy {@link ACCOUNT_SLUG_RE}? */
40
+ export declare function isValidAccountSlug(s: string): boolean;
41
+ /**
42
+ * A legal Endpoint `key` — mirrors the server's rule VERBATIM, not
43
+ * {@link ACCOUNT_SLUG_RE} above: that one is `{2,}` with no 39-character cap
44
+ * and encodes neither the `--` rule nor the trailing-`-` rule, so a form that
45
+ * validated a key with it would accept a value the server rejects.
46
+ *
47
+ * Source of truth: `packages/server/packages/bll/accounts.ts:72`
48
+ * (`ACCOUNT_SLUG_RE = /^[a-z][a-z0-9-]{2,38}$/`) + `:216-218` (`isAccountSlug`),
49
+ * reached from `packages/server/packages/api/invoke-address.ts:55-57`
50
+ * (`isEndpointKey = isAccountSlug`). There is no reserved-word check on an
51
+ * Endpoint key server-side — `isAccountSlug` only, never
52
+ * `RESERVED_ACCOUNT_SLUGS` — so none is added here either.
53
+ */
54
+ export declare function isEndpointKey(s: string): boolean;
55
+ /**
56
+ * Filters a keystroke into a key input down to what {@link isEndpointKey}'s
57
+ * grammar can ever accept: lowercase letters, digits, hyphens — nothing
58
+ * else, capped at the grammar's own 39-character ceiling. A space or any
59
+ * other disallowed character is dropped outright rather than folded into a
60
+ * `-` ({@link slugify}'s job, for the one-time displayName-to-key fallback —
61
+ * this is a LIVE filter on a field the operator is typing directly into, so
62
+ * silently substituting a different character would be its own surprise).
63
+ * Positional rules the grammar also enforces (must START with a letter, no
64
+ * `--`, no trailing `-`) are left to {@link isEndpointKey} at Save time —
65
+ * blocking those mid-keystroke would refuse a caller who is one keystroke
66
+ * away from a valid value (e.g. typing `"2"` before backspacing to start
67
+ * over).
68
+ */
69
+ export declare function filterKeyInput(raw: string): string;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The extracted decision behind `useImportSpecMutation`'s `onSuccess` (T1.2.1, AC-3): given the
3
+ * KIND of document that was just imported, which single list-query key must be invalidated — and
4
+ * only that one.
5
+ *
6
+ * The bug this exists to kill: `src/repos/spec-import.ts` used to run an unconditional
7
+ * `qc.invalidateQueries({ queryKey: functionsKey })` on every import — invisible to `tsc`, only
8
+ * visible when a human imports an Endpoint or Workflow and its OWN list stays stale while
9
+ * Functions gets pointlessly invalidated.
10
+ *
11
+ * The three real list keys live in `src/repos/functions.ts` / `endpoints.ts` / `workflows.ts`, and
12
+ * this module cannot import them as VALUES: `node --test` throws `ERR_UNKNOWN_FILE_EXTENSION` on
13
+ * anything reaching the `@w6w/ui` / JSX import graph, and `src/repos/*.ts` reaches it through
14
+ * `../api/apiClient.tsx` — see `src/lib/save-invalidation.ts`'s docstring for the identical
15
+ * constraint. So the caller (`spec-import.ts`, which already imports all three) hands them in; this
16
+ * module owns only the DISPATCH — which kind picks which key — not the keys' own values. That keeps
17
+ * the two ends impossible to drift apart on the workflow arm's shape in particular: `workflowsKey`
18
+ * is `(project?: string) => ["workflows", project]`, NOT a bare constant like the other two, and
19
+ * `WorkflowsPage`'s list branch (`CallableList`, `pages/CallablePage.tsx`) reads its cache through
20
+ * `useCallableListQuery("workflow", …, projectId)` — an invalidation that drops the project id hits
21
+ * a DIFFERENT cache entry and the imported row would not appear without a manual refresh, which is
22
+ * the exact bug this module exists to prevent.
23
+ */
24
+ export type SpecImportKind = "function" | "endpoint" | "workflow";
25
+ /** Mirrors `src/lib/save-invalidation.ts`'s `QueryInvalidator` — re-declared, not imported, so this
26
+ * module's zero-value-import rule needs no exception (a structural type has no runtime cost either
27
+ * way; TanStack Query's real `QueryClient` satisfies both). */
28
+ export interface QueryInvalidator {
29
+ invalidateQueries(filters: {
30
+ queryKey: readonly unknown[];
31
+ }): unknown;
32
+ }
33
+ /** The three list-key producers, handed in by the caller — see the module docblock for why this
34
+ * module cannot hold them itself. `workflowsKey` is a function, not a value, because it is scoped
35
+ * by project. */
36
+ export interface SpecImportListKeys {
37
+ functionsKey: readonly unknown[];
38
+ endpointsKey: readonly unknown[];
39
+ workflowsKey: (projectId: string | undefined) => readonly unknown[];
40
+ }
41
+ /**
42
+ * Total: exhaustively switched over the three spec kinds (mirrors `specCollectionForKind`'s idiom
43
+ * in `src/lib/rail-settings.ts` — a plain switch with no default arm, so an unhandled kind is a
44
+ * compile error rather than a silent fallthrough).
45
+ */
46
+ export declare function specImportListKey(kind: SpecImportKind, keys: SpecImportListKeys, projectId: string | undefined): readonly unknown[];
47
+ /** Invalidate the ONE list key the imported document's own kind reads — never more than one. */
48
+ export declare function invalidateImportedCallable(qc: QueryInvalidator, kind: SpecImportKind, keys: SpecImportListKeys, projectId: string | undefined): void;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Pure decision logic behind `../components/TagEditor.tsx` — kept out of the
3
+ * component so `node --test` can exercise it directly
4
+ * (`__tests__/tag-editor.test.ts`) without touching React, mirroring
5
+ * `alias-rail.ts`/`rail-settings.ts`'s own split. Carries over the retiring
6
+ * `WorkflowsPage.tsx`'s retired tags-editing modal's data contract verbatim:
7
+ * `string[]`, trim, dedupe-on-add, drop empties.
8
+ */
9
+ /**
10
+ * Applies a chip input's freeform text to the committed tag list. Called on
11
+ * every change to the trailing, not-yet-committed input — `typed` is always
12
+ * the FULL current value of just that one input, which resets to `""` after
13
+ * a commit, so a single keystroke never carries more than the one character
14
+ * just typed.
15
+ *
16
+ * A comma anywhere in `typed` is the commit signal: every comma-delimited
17
+ * segment is trimmed and, if non-empty and not already present, appended as
18
+ * a chip — in order, regardless of whether it sits before or after the last
19
+ * comma. That is what makes both shapes work from the one rule: typing
20
+ * `"alpha,"` one character at a time commits `"alpha"` the moment the comma
21
+ * lands, and pasting `"a,b,c"` in one go (no trailing comma at all) still
22
+ * commits all three, because nothing here treats "the last segment" as
23
+ * special. The returned `typed` is always `""` once a comma triggered a
24
+ * commit. With no comma present at all, nothing commits and `typed` passes
25
+ * through unchanged, so the caller can just store it as the next draft.
26
+ */
27
+ export declare function commitTag(tags: string[], typed: string): {
28
+ tags: string[];
29
+ typed: string;
30
+ };
31
+ /**
32
+ * Removes exactly one tag by VALUE, never by position — a list carrying both
33
+ * a tag and a second tag that merely contains it as a prefix (`["ab", "b"]`)
34
+ * only ever loses the exact match.
35
+ */
36
+ export declare function removeTag(tags: string[], tag: string): string[];
@@ -0,0 +1,89 @@
1
+ /**
2
+ * "Call from code" trigger preview (T2.2.1, HITL-2/HITL-4 pinned):
3
+ *
4
+ * > one tab per MANUAL (`@w6w/trigger`) trigger step, in declared order,
5
+ * > labelled `step.displayName ?? step.id`. Webhook/scheduler steps are
6
+ * > excluded — they declare named params, never a generic `fields` array.
7
+ *
8
+ * Pure and testable by design, same shape as `workflow-validity.ts`:
9
+ * `WorkflowDetailPage.tsx`/`WorkflowsPage.tsx` are JSX, which Node 24's
10
+ * type-stripping cannot parse, so nothing in it can be loaded by
11
+ * `node --test`. This module cannot import `@w6w/ui`/`@w6w/ui/flow` either
12
+ * way (`ERR_UNKNOWN_FILE_EXTENSION` — see `workflow-validity.ts`'s docstring).
13
+ * Instead, "is this step a MANUAL trigger" and "read a trigger's declared
14
+ * `fields`" are **caller-injected** on `TriggerPreviewContext` —
15
+ * `WorkflowsPage.tsx`/`WorkflowDetailPage.tsx` import the real
16
+ * `TRIGGER_APP`/`asFieldDefs` from `@w6w/ui/flow` and pass them in. This file
17
+ * contains no `@w6w/*` app-id string literal.
18
+ *
19
+ * Deliberately narrower than `@w6w/ui/flow`'s own `isTriggerApp`, which also
20
+ * matches `@w6w/webhook`/`@w6w/scheduler` (all three are "entry nodes" for the
21
+ * canvas). Those two kinds declare named params, not a generic `fields`
22
+ * array, so they cannot be previewed the same way — the injected predicate a
23
+ * caller supplies here is expected to be the MANUAL-only check
24
+ * (`app === TRIGGER_APP`), not the broader `isTriggerApp`.
25
+ */
26
+ /** One field a trigger declares — just enough of `@w6w/ui/flow`'s
27
+ * `TriggerFieldDef` for this preview (structurally compatible, not imported). */
28
+ export interface TriggerPreviewFieldDef {
29
+ key?: string;
30
+ type?: string;
31
+ required?: boolean;
32
+ default?: unknown;
33
+ }
34
+ /** A workflow graph step, just enough of it for this preview. */
35
+ export interface WorkflowStepLike {
36
+ id: string;
37
+ /** Human-facing step name, when the author set one. Falls back to `id`. */
38
+ displayName?: string;
39
+ uses?: {
40
+ app?: string;
41
+ action?: string;
42
+ };
43
+ with?: Record<string, unknown>;
44
+ }
45
+ /** A workflow definition, just enough of it for this preview. */
46
+ export interface WorkflowLike {
47
+ steps?: WorkflowStepLike[];
48
+ }
49
+ /**
50
+ * Everything `triggerPreviews` needs that this pure module cannot compute or
51
+ * import itself — see the module docstring for why each field is
52
+ * caller-injected rather than derived in here.
53
+ */
54
+ export interface TriggerPreviewContext {
55
+ /** Is this step's `uses.app` a MANUAL trigger (`@w6w/trigger`)? Webhook and
56
+ * scheduler steps must answer `false` here even though they are also
57
+ * "entry nodes" — they have no generic `fields` array to preview. */
58
+ isManualTrigger: (app: string) => boolean;
59
+ /** `@w6w/ui/flow`'s `asFieldDefs`, injected — reads a step's `with.fields`
60
+ * param value as an array of field definitions. */
61
+ asFieldDefs: (fields: unknown) => TriggerPreviewFieldDef[];
62
+ }
63
+ /** One manual trigger's declared fields, ready for the modal's picker. */
64
+ export interface TriggerPreview {
65
+ id: string;
66
+ /** `step.displayName ?? step.id`. */
67
+ label: string;
68
+ fields: TriggerPreviewFieldDef[];
69
+ }
70
+ /**
71
+ * One `TriggerPreview` per manual trigger step in `workflow.steps`, in
72
+ * declared order. Non-manual steps (including webhook/scheduler entry nodes)
73
+ * are excluded. A field with a missing/blank `key` is dropped — mirrors
74
+ * `@w6w/ui/flow`'s `fieldsToParams`, which does the same before turning
75
+ * `TriggerFieldDef[]` into a fillable form.
76
+ */
77
+ export declare function triggerPreviews(workflow: WorkflowLike, ctx: TriggerPreviewContext): TriggerPreview[];
78
+ /**
79
+ * A start-payload skeleton for one manual trigger — every declared field at its
80
+ * declared default, so the Run panel opens with the shape the workflow actually
81
+ * expects instead of an empty object the author has to reconstruct by hand.
82
+ *
83
+ * Mirrors `@w6w/ui/flow`'s `seedValues` (which this module cannot import — see
84
+ * the header): an explicit `default` wins; otherwise the empty value for the
85
+ * declared type. Unknown types fall to `""`, matching that function.
86
+ *
87
+ * `undefined` in → `{}`, so a caller can pass a still-loading preview.
88
+ */
89
+ export declare function triggerInputSeed(preview: TriggerPreview | undefined): Record<string, unknown>;