mirror of
https://github.com/wavetermdev/waveterm
synced 2026-04-21 14:37:16 +00:00
Generate WaveEvent as a typed discriminated union with explicit null payloads for no-data events (#2899)
This updates WaveEvent typing to be event-aware instead of `data?: any`,
while keeping safe fallback behavior for unmapped events. It also
codifies known no-payload events as `null` payloads and documents event
payload expectations alongside the Go event constants.
- **Event registry + payload documentation (Go)**
- Added `AllEvents` in `pkg/wps/wpstypes.go` as the canonical list of
Wave event names.
- Added/updated inline payload annotations on `Event_*` constants.
- Marked confirmed no-payload events with `// type: none` (e.g.
`route:up`, `route:down`, `workspace:update`, `waveapp:appgoupdated`).
- **Dedicated WaveEvent TS generation path**
- Added `pkg/tsgen/tsgenevent.go` with `event -> reflect.Type` metadata
(`WaveEventDataTypes`).
- Supports three cases:
- mapped concrete type → strong TS payload type
- mapped `nil` → `data?: null` (explicit no-data contract)
- unmapped event → `data?: any` (non-breaking fallback)
- **Custom WaveEvent output and default suppression**
- Suppressed default struct-based `WaveEvent` emission in
`gotypes.d.ts`.
- Added generated `frontend/types/waveevent.d.ts` containing:
- `WaveEventName` string-literal union from `AllEvents`
- discriminated `WaveEvent` union keyed by `event`.
- **Generator wiring + focused coverage**
- Hooked custom event generation into
`cmd/generatets/main-generatets.go`.
- Added `pkg/tsgen/tsgenevent_test.go` assertions for:
- typed mapped events
- explicit `null` for known no-data events
- `any` fallback for unmapped events.
```ts
type WaveEvent = {
event: WaveEventName;
scopes?: string[];
sender?: string;
persist?: number;
data?: any;
} & (
{ event: "block:jobstatus"; data?: BlockJobStatusData } |
{ event: "route:up"; data?: null } |
{ event: "workspace:update"; data?: null } |
{ event: "some:future:event"; data?: any } // fallback if unmapped
);
```
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
Co-authored-by: sawka <mike@commandline.dev>
This commit is contained in:
parent
f3b1c16882
commit
195277de45
27 changed files with 417 additions and 162 deletions
23
.github/copilot-instructions.md
vendored
Normal file
23
.github/copilot-instructions.md
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Wave Terminal — Copilot Instructions
|
||||
|
||||
## Project Rules
|
||||
|
||||
Read and follow all guidelines in [`.roo/rules/rules.md`](./.roo/rules/rules.md).
|
||||
|
||||
---
|
||||
|
||||
## Skill Guides
|
||||
|
||||
This project uses a set of "skill" guides — focused how-to documents for common implementation tasks. When your task matches one of the descriptions below, **read the linked SKILL.md file before proceeding** and follow its instructions precisely.
|
||||
|
||||
| Skill | Description |
|
||||
|-------|-------------|
|
||||
| [add-config](./.kilocode/skills/add-config/SKILL.md) | Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings. |
|
||||
| [add-rpc](./.kilocode/skills/add-rpc/SKILL.md) | Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality. |
|
||||
| [add-wshcmd](./.kilocode/skills/add-wshcmd/SKILL.md) | Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface. |
|
||||
| [context-menu](./.kilocode/skills/context-menu/SKILL.md) | Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators. |
|
||||
| [create-view](./.kilocode/skills/create-view/SKILL.md) | Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks. |
|
||||
| [electron-api](./.kilocode/skills/electron-api/SKILL.md) | Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC. |
|
||||
| [wps-events](./.kilocode/skills/wps-events/SKILL.md) | Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components. |
|
||||
|
||||
> **How skills work:** Each skill is a self-contained guide covering the exact files to edit, patterns to follow, and steps to take for a specific type of task in this codebase. If your task matches a skill's description, open that SKILL.md and treat it as your primary reference for the implementation.
|
||||
|
|
@ -40,7 +40,7 @@ const (
|
|||
Event_BlockClose = "blockclose"
|
||||
Event_ConnChange = "connchange"
|
||||
// ... other events ...
|
||||
Event_YourNewEvent = "your:newevent" // Use colon notation for namespacing
|
||||
Event_YourNewEvent = "your:newevent" // type: YourEventData (or "none" if no data)
|
||||
)
|
||||
```
|
||||
|
||||
|
|
@ -49,8 +49,37 @@ const (
|
|||
- Use descriptive PascalCase for the constant name with `Event_` prefix
|
||||
- Use lowercase with colons for the string value (e.g., "namespace:eventname")
|
||||
- Group related events with the same namespace prefix
|
||||
- Always add a `// type: <TypeName>` comment; use `// type: none` if no data is sent
|
||||
|
||||
### Step 2: Define Event Data Structure (Optional)
|
||||
### Step 2: Add to AllEvents
|
||||
|
||||
Add your new constant to the `AllEvents` slice in `pkg/wps/wpstypes.go`:
|
||||
|
||||
```go
|
||||
var AllEvents []string = []string{
|
||||
// ... existing events ...
|
||||
Event_YourNewEvent,
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Register in WaveEventDataTypes (REQUIRED)
|
||||
|
||||
You **must** add an entry to `WaveEventDataTypes` in `pkg/tsgen/tsgenevent.go`. This drives TypeScript type generation for the event's `data` field:
|
||||
|
||||
```go
|
||||
var WaveEventDataTypes = map[string]reflect.Type{
|
||||
// ... existing entries ...
|
||||
wps.Event_YourNewEvent: reflect.TypeOf(YourEventData{}), // value type
|
||||
// wps.Event_YourNewEvent: reflect.TypeOf((*YourEventData)(nil)), // pointer type
|
||||
// wps.Event_YourNewEvent: nil, // no data (type: none)
|
||||
}
|
||||
```
|
||||
|
||||
- Use `reflect.TypeOf(YourType{})` for value types
|
||||
- Use `reflect.TypeOf((*YourType)(nil))` for pointer types
|
||||
- Use `nil` if no data is sent for the event
|
||||
|
||||
### Step 4: Define Event Data Structure (Optional)
|
||||
|
||||
If your event carries structured data, define a type for it:
|
||||
|
||||
|
|
@ -61,7 +90,7 @@ type YourEventData struct {
|
|||
}
|
||||
```
|
||||
|
||||
### Step 3: Expose Type to Frontend (If Needed)
|
||||
### Step 5: Expose Type to Frontend (If Needed)
|
||||
|
||||
If your event data type isn't already exposed via an RPC call, you need to add it to `pkg/tsgen/tsgen.go` so TypeScript types are generated:
|
||||
|
||||
|
|
@ -299,9 +328,11 @@ To debug event flow:
|
|||
|
||||
When adding a new event:
|
||||
|
||||
- [ ] Add event constant to `pkg/wps/wpstypes.go`
|
||||
- [ ] Add event constant to [`pkg/wps/wpstypes.go`](pkg/wps/wpstypes.go) with a `// type: <TypeName>` comment (use `none` if no data)
|
||||
- [ ] Add the constant to `AllEvents` in [`pkg/wps/wpstypes.go`](pkg/wps/wpstypes.go)
|
||||
- [ ] **REQUIRED**: Add an entry to `WaveEventDataTypes` in [`pkg/tsgen/tsgenevent.go`](pkg/tsgen/tsgenevent.go) — use `nil` for events with no data
|
||||
- [ ] Define event data structure (if needed)
|
||||
- [ ] Add data type to `pkg/tsgen/tsgen.go` for frontend use
|
||||
- [ ] Add data type to `pkg/tsgen/tsgen.go` for frontend use (if not already exposed via RPC)
|
||||
- [ ] Run `task generate` to update TypeScript types
|
||||
- [ ] Publish events using `wps.Broker.Publish()`
|
||||
- [ ] Use goroutines for non-blocking publish when appropriate
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ The full API is defined in custom.d.ts as type ElectronApi.
|
|||
- **Match response length to question complexity** - For simple, direct questions in Ask mode (especially those that can be answered in 1-2 sentences), provide equally brief answers. Save detailed explanations for complex topics or when explicitly requested.
|
||||
- **CRITICAL** - useAtomValue and useAtom are React HOOKS. They cannot be used inline in JSX code, they must appear at the top of a component in the hooks area of the react code.
|
||||
- for simple functions, we prefer `if (!cond) { return }; functionality;` pattern overn `if (cond) { functionality }` because it produces less indentation and is easier to follow.
|
||||
- It is now 2026, so if you write new files use 2026 for the copyright year
|
||||
- It is now 2026, so if you write new files, or update files use 2026 for the copyright year
|
||||
|
||||
### Strict Comment Rules
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ func generateTypesFile(tsTypesMap map[reflect.Type]string) error {
|
|||
fileName := "frontend/types/gotypes.d.ts"
|
||||
fmt.Fprintf(os.Stderr, "generating types file to %s\n", fileName)
|
||||
tsgen.GenerateWaveObjTypes(tsTypesMap)
|
||||
tsgen.GenerateWaveEventTypes(tsTypesMap)
|
||||
err := tsgen.GenerateServiceTypes(tsTypesMap)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error generating service types: %v\n", err)
|
||||
|
|
@ -31,7 +32,7 @@ func generateTypesFile(tsTypesMap map[reflect.Type]string) error {
|
|||
return fmt.Errorf("error generating wsh server types: %w", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "// Copyright 2025, Command Line Inc.\n")
|
||||
fmt.Fprintf(&buf, "// Copyright 2026, Command Line Inc.\n")
|
||||
fmt.Fprintf(&buf, "// SPDX-License-Identifier: Apache-2.0\n\n")
|
||||
fmt.Fprintf(&buf, "// generated by cmd/generate/main-generatets.go\n\n")
|
||||
fmt.Fprintf(&buf, "declare global {\n\n")
|
||||
|
|
@ -62,11 +63,29 @@ func generateTypesFile(tsTypesMap map[reflect.Type]string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func generateWaveEventFile(tsTypesMap map[reflect.Type]string) error {
|
||||
fileName := "frontend/types/waveevent.d.ts"
|
||||
fmt.Fprintf(os.Stderr, "generating waveevent file to %s\n", fileName)
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "// Copyright 2026, Command Line Inc.\n")
|
||||
fmt.Fprintf(&buf, "// SPDX-License-Identifier: Apache-2.0\n\n")
|
||||
fmt.Fprintf(&buf, "// generated by cmd/generate/main-generatets.go\n\n")
|
||||
fmt.Fprintf(&buf, "declare global {\n\n")
|
||||
fmt.Fprint(&buf, utilfn.IndentString(" ", tsgen.GenerateWaveEventTypes(tsTypesMap)))
|
||||
fmt.Fprintf(&buf, "}\n\n")
|
||||
fmt.Fprintf(&buf, "export {}\n")
|
||||
written, err := utilfn.WriteFileIfDifferent(fileName, buf.Bytes())
|
||||
if !written {
|
||||
fmt.Fprintf(os.Stderr, "no changes to %s\n", fileName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func generateServicesFile(tsTypesMap map[reflect.Type]string) error {
|
||||
fileName := "frontend/app/store/services.ts"
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(os.Stderr, "generating services file to %s\n", fileName)
|
||||
fmt.Fprintf(&buf, "// Copyright 2025, Command Line Inc.\n")
|
||||
fmt.Fprintf(&buf, "// Copyright 2026, Command Line Inc.\n")
|
||||
fmt.Fprintf(&buf, "// SPDX-License-Identifier: Apache-2.0\n\n")
|
||||
fmt.Fprintf(&buf, "// generated by cmd/generate/main-generatets.go\n\n")
|
||||
fmt.Fprintf(&buf, "import * as WOS from \"./wos\";\n\n")
|
||||
|
|
@ -89,7 +108,7 @@ func generateWshClientApiFile(tsTypeMap map[reflect.Type]string) error {
|
|||
var buf bytes.Buffer
|
||||
declMap := wshrpc.GenerateWshCommandDeclMap()
|
||||
fmt.Fprintf(os.Stderr, "generating wshclientapi file to %s\n", fileName)
|
||||
fmt.Fprintf(&buf, "// Copyright 2025, Command Line Inc.\n")
|
||||
fmt.Fprintf(&buf, "// Copyright 2026, Command Line Inc.\n")
|
||||
fmt.Fprintf(&buf, "// SPDX-License-Identifier: Apache-2.0\n\n")
|
||||
fmt.Fprintf(&buf, "// generated by cmd/generate/main-generatets.go\n\n")
|
||||
fmt.Fprintf(&buf, "import { WshClient } from \"./wshclient\";\n\n")
|
||||
|
|
@ -128,6 +147,11 @@ func main() {
|
|||
fmt.Fprintf(os.Stderr, "Error generating services file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = generateWaveEventFile(tsTypesMap)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error generating wave event file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = generateWshClientApiFile(tsTypesMap)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error generating wshserver file: %v\n", err)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { waveEventSubscribe } from "@/app/store/wps";
|
||||
import { waveEventSubscribeSingle } from "@/app/store/wps";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import * as electron from "electron";
|
||||
import { fireAndForget } from "../frontend/util/util";
|
||||
|
|
@ -385,7 +385,7 @@ export function makeAndSetAppMenu() {
|
|||
}
|
||||
|
||||
function initMenuEventSubscriptions() {
|
||||
waveEventSubscribe({
|
||||
waveEventSubscribeSingle({
|
||||
eventType: "workspace:update",
|
||||
handler: makeAndSetAppMenu,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import { globalStore } from "./jotaiStore";
|
|||
import { modalsModel } from "./modalmodel";
|
||||
import { ClientService, ObjectService } from "./services";
|
||||
import * as WOS from "./wos";
|
||||
import { getFileSubject, waveEventSubscribe } from "./wps";
|
||||
import { getFileSubject, waveEventSubscribeSingle } from "./wps";
|
||||
|
||||
let atoms: GlobalAtomsType;
|
||||
let globalEnvironment: "electron" | "renderer";
|
||||
|
|
@ -198,65 +198,56 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
|
|||
}
|
||||
|
||||
function initGlobalWaveEventSubs(initOpts: WaveInitOpts) {
|
||||
waveEventSubscribe(
|
||||
{
|
||||
eventType: "waveobj:update",
|
||||
handler: (event) => {
|
||||
// console.log("waveobj:update wave event handler", event);
|
||||
const update: WaveObjUpdate = event.data;
|
||||
WOS.updateWaveObject(update);
|
||||
},
|
||||
waveEventSubscribeSingle({
|
||||
eventType: "waveobj:update",
|
||||
handler: (event) => {
|
||||
// console.log("waveobj:update wave event handler", event);
|
||||
WOS.updateWaveObject(event.data);
|
||||
},
|
||||
{
|
||||
eventType: "config",
|
||||
handler: (event) => {
|
||||
// console.log("config wave event handler", event);
|
||||
const fullConfig = (event.data as WatcherUpdate).fullconfig;
|
||||
globalStore.set(atoms.fullConfigAtom, fullConfig);
|
||||
},
|
||||
});
|
||||
waveEventSubscribeSingle({
|
||||
eventType: "config",
|
||||
handler: (event) => {
|
||||
// console.log("config wave event handler", event);
|
||||
globalStore.set(atoms.fullConfigAtom, event.data.fullconfig);
|
||||
},
|
||||
{
|
||||
eventType: "waveai:modeconfig",
|
||||
handler: (event) => {
|
||||
const modeConfigs = (event.data as AIModeConfigUpdate).configs;
|
||||
globalStore.set(atoms.waveaiModeConfigAtom, modeConfigs);
|
||||
},
|
||||
});
|
||||
waveEventSubscribeSingle({
|
||||
eventType: "waveai:modeconfig",
|
||||
handler: (event) => {
|
||||
globalStore.set(atoms.waveaiModeConfigAtom, event.data.configs);
|
||||
},
|
||||
{
|
||||
eventType: "userinput",
|
||||
handler: (event) => {
|
||||
// console.log("userinput event handler", event);
|
||||
const data: UserInputRequest = event.data;
|
||||
modalsModel.pushModal("UserInputModal", { ...data });
|
||||
},
|
||||
scope: initOpts.windowId,
|
||||
});
|
||||
waveEventSubscribeSingle({
|
||||
eventType: "userinput",
|
||||
handler: (event) => {
|
||||
// console.log("userinput event handler", event);
|
||||
modalsModel.pushModal("UserInputModal", { ...event.data });
|
||||
},
|
||||
{
|
||||
eventType: "blockfile",
|
||||
handler: (event) => {
|
||||
// console.log("blockfile event update", event);
|
||||
const fileData: WSFileEventData = event.data;
|
||||
const fileSubject = getFileSubject(fileData.zoneid, fileData.filename);
|
||||
if (fileSubject != null) {
|
||||
fileSubject.next(fileData);
|
||||
}
|
||||
},
|
||||
scope: initOpts.windowId,
|
||||
});
|
||||
waveEventSubscribeSingle({
|
||||
eventType: "blockfile",
|
||||
handler: (event) => {
|
||||
// console.log("blockfile event update", event);
|
||||
const fileSubject = getFileSubject(event.data.zoneid, event.data.filename);
|
||||
if (fileSubject != null) {
|
||||
fileSubject.next(event.data);
|
||||
}
|
||||
},
|
||||
{
|
||||
eventType: "waveai:ratelimit",
|
||||
handler: (event) => {
|
||||
const rateLimitInfo: RateLimitInfo = event.data;
|
||||
globalStore.set(atoms.waveAIRateLimitInfoAtom, rateLimitInfo);
|
||||
},
|
||||
});
|
||||
waveEventSubscribeSingle({
|
||||
eventType: "waveai:ratelimit",
|
||||
handler: (event) => {
|
||||
globalStore.set(atoms.waveAIRateLimitInfoAtom, event.data);
|
||||
},
|
||||
{
|
||||
eventType: "tab:indicator",
|
||||
handler: (event) => {
|
||||
const data: TabIndicatorEventData = event.data;
|
||||
setTabIndicatorInternal(data.tabid, data.indicator);
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
waveEventSubscribeSingle({
|
||||
eventType: "tab:indicator",
|
||||
handler: (event) => {
|
||||
setTabIndicatorInternal(event.data.tabid, event.data.indicator);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const blockCache = new Map<string, Map<string, any>>();
|
||||
|
|
@ -762,11 +753,11 @@ async function loadTabIndicators() {
|
|||
}
|
||||
|
||||
function subscribeToConnEvents() {
|
||||
waveEventSubscribe({
|
||||
waveEventSubscribeSingle({
|
||||
eventType: "connchange",
|
||||
handler: (event: WaveEvent) => {
|
||||
handler: (event) => {
|
||||
try {
|
||||
const connStatus = event.data as ConnStatus;
|
||||
const connStatus = event.data;
|
||||
if (connStatus == null || isBlank(connStatus.connection)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -852,7 +843,7 @@ function setTabIndicator(tabId: string, indicator: TabIndicator) {
|
|||
data: {
|
||||
tabid: tabId,
|
||||
indicator: indicator,
|
||||
} as TabIndicatorEventData,
|
||||
},
|
||||
};
|
||||
fireAndForget(() => RpcApi.EventPublishCommand(TabRpcClient, eventData));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// generated by cmd/generate/main-generatets.go
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
// WaveObjectStore
|
||||
|
||||
import { waveEventSubscribe } from "@/app/store/wps";
|
||||
import { waveEventSubscribeSingle } from "@/app/store/wps";
|
||||
import { getWebServerEndpoint } from "@/util/endpoints";
|
||||
import { fetch } from "@/util/fetchutil";
|
||||
import { fireAndForget } from "@/util/util";
|
||||
|
|
@ -79,7 +79,7 @@ function debugLogBackendCall(methodName: string, durationStr: string, args: any[
|
|||
}
|
||||
|
||||
function wpsSubscribeToObject(oref: string): () => void {
|
||||
return waveEventSubscribe({
|
||||
return waveEventSubscribeSingle({
|
||||
eventType: "waveobj:update",
|
||||
scope: oref,
|
||||
handler: (event) => {
|
||||
|
|
|
|||
|
|
@ -12,17 +12,19 @@ function setWpsRpcClient(client: WshClient) {
|
|||
WpsRpcClient = client;
|
||||
}
|
||||
|
||||
type WaveEventSubject = {
|
||||
handler: (event: WaveEvent) => void;
|
||||
type WaveEventSubject<T extends WaveEventName = WaveEventName> = {
|
||||
handler: (event: Extract<WaveEvent, { event: T }>) => void;
|
||||
scope?: string;
|
||||
};
|
||||
|
||||
type WaveEventSubjectContainer = WaveEventSubject & {
|
||||
type WaveEventSubjectContainer = {
|
||||
handler: (event: WaveEvent) => void;
|
||||
scope?: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
type WaveEventSubscription = WaveEventSubject & {
|
||||
eventType: string;
|
||||
type WaveEventSubscription<T extends WaveEventName = WaveEventName> = WaveEventSubject<T> & {
|
||||
eventType: T;
|
||||
};
|
||||
|
||||
type WaveEventUnsubscribe = {
|
||||
|
|
@ -58,29 +60,25 @@ function updateWaveEventSub(eventType: string) {
|
|||
RpcApi.EventSubCommand(WpsRpcClient, subreq, { noresponse: true });
|
||||
}
|
||||
|
||||
function waveEventSubscribe(...subscriptions: WaveEventSubscription[]): () => void {
|
||||
const unsubs: WaveEventUnsubscribe[] = [];
|
||||
const eventTypeSet = new Set<string>();
|
||||
for (const subscription of subscriptions) {
|
||||
// console.log("waveEventSubscribe", subscription);
|
||||
if (subscription.handler == null) {
|
||||
return;
|
||||
}
|
||||
const id: string = crypto.randomUUID();
|
||||
let subjects = waveEventSubjects.get(subscription.eventType);
|
||||
if (subjects == null) {
|
||||
subjects = [];
|
||||
waveEventSubjects.set(subscription.eventType, subjects);
|
||||
}
|
||||
const subcont: WaveEventSubjectContainer = { id, handler: subscription.handler, scope: subscription.scope };
|
||||
subjects.push(subcont);
|
||||
unsubs.push({ id, eventType: subscription.eventType });
|
||||
eventTypeSet.add(subscription.eventType);
|
||||
function waveEventSubscribeSingle<T extends WaveEventName>(subscription: WaveEventSubscription<T>): () => void {
|
||||
// console.log("waveEventSubscribeSingle", subscription);
|
||||
if (subscription.handler == null) {
|
||||
return () => {};
|
||||
}
|
||||
for (const eventType of eventTypeSet) {
|
||||
updateWaveEventSub(eventType);
|
||||
const id: string = crypto.randomUUID();
|
||||
let subjects = waveEventSubjects.get(subscription.eventType);
|
||||
if (subjects == null) {
|
||||
subjects = [];
|
||||
waveEventSubjects.set(subscription.eventType, subjects);
|
||||
}
|
||||
return () => waveEventUnsubscribe(...unsubs);
|
||||
const subcont: WaveEventSubjectContainer = {
|
||||
id,
|
||||
handler: subscription.handler as (event: WaveEvent) => void,
|
||||
scope: subscription.scope,
|
||||
};
|
||||
subjects.push(subcont);
|
||||
updateWaveEventSub(subscription.eventType);
|
||||
return () => waveEventUnsubscribe({ id, eventType: subscription.eventType });
|
||||
}
|
||||
|
||||
function waveEventUnsubscribe(...unsubscribes: WaveEventUnsubscribe[]) {
|
||||
|
|
@ -149,7 +147,7 @@ export {
|
|||
getFileSubject,
|
||||
handleWaveEvent,
|
||||
setWpsRpcClient,
|
||||
waveEventSubscribe,
|
||||
waveEventSubscribeSingle,
|
||||
waveEventUnsubscribe,
|
||||
wpsReconnectHandler,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// generated by cmd/generate/main-generatets.go
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { IconButton } from "../element/iconbutton";
|
|||
import { atoms, getApi } from "../store/global";
|
||||
import { WorkspaceService } from "../store/services";
|
||||
import { getObjectValue, makeORef } from "../store/wos";
|
||||
import { waveEventSubscribe } from "../store/wps";
|
||||
import { waveEventSubscribeSingle } from "../store/wps";
|
||||
import { WorkspaceEditor } from "./workspaceeditor";
|
||||
import "./workspaceswitcher.scss";
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ const WorkspaceSwitcher = forwardRef<HTMLDivElement>((_, ref) => {
|
|||
|
||||
useEffect(
|
||||
() =>
|
||||
waveEventSubscribe({
|
||||
waveEventSubscribeSingle({
|
||||
eventType: "workspace:update",
|
||||
handler: () => fireAndForget(updateWorkspaceList),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import * as jotai from "jotai";
|
|||
import * as React from "react";
|
||||
|
||||
import { useDimensionsWithExistingRef } from "@/app/hook/useDimensions";
|
||||
import { waveEventSubscribe } from "@/app/store/wps";
|
||||
import { waveEventSubscribeSingle } from "@/app/store/wps";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { TabRpcClient } from "@/app/store/wshrpcutil";
|
||||
import { atoms } from "@/store/global";
|
||||
|
|
@ -80,8 +80,8 @@ for (let i = 0; i < 32; i++) {
|
|||
DefaultPlotMeta[`cpu:${i}`] = defaultCpuMeta(`Core ${i}`);
|
||||
}
|
||||
|
||||
function convertWaveEventToDataItem(event: WaveEvent): DataItem {
|
||||
const eventData: TimeSeriesData = event.data;
|
||||
function convertWaveEventToDataItem(event: Extract<WaveEvent, { event: "sysinfo" }>): DataItem {
|
||||
const eventData = event.data;
|
||||
if (eventData == null || eventData.ts == null || eventData.values == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -360,7 +360,7 @@ function SysinfoView({ model, blockId }: SysinfoViewProps) {
|
|||
}
|
||||
}, [connStatus.status, connName]);
|
||||
React.useEffect(() => {
|
||||
const unsubFn = waveEventSubscribe({
|
||||
const unsubFn = waveEventSubscribeSingle({
|
||||
eventType: "sysinfo",
|
||||
scope: connName,
|
||||
handler: (event) => {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { BlockNodeModel } from "@/app/block/blocktypes";
|
|||
import { appHandleKeyDown } from "@/app/store/keymodel";
|
||||
import { modalsModel } from "@/app/store/modalmodel";
|
||||
import type { TabModel } from "@/app/store/tab-model";
|
||||
import { waveEventSubscribe } from "@/app/store/wps";
|
||||
import { waveEventSubscribeSingle } from "@/app/store/wps";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { makeFeBlockRouteId } from "@/app/store/wshrouter";
|
||||
import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil";
|
||||
|
|
@ -330,12 +330,11 @@ export class TermViewModel implements ViewModel {
|
|||
initialShellProcStatus.then((rts) => {
|
||||
this.updateShellProcStatus(rts);
|
||||
});
|
||||
this.shellProcStatusUnsubFn = waveEventSubscribe({
|
||||
this.shellProcStatusUnsubFn = waveEventSubscribeSingle({
|
||||
eventType: "controllerstatus",
|
||||
scope: WOS.makeORef("block", blockId),
|
||||
handler: (event) => {
|
||||
let bcRTS: BlockControllerRuntimeStatus = event.data;
|
||||
this.updateShellProcStatus(bcRTS);
|
||||
this.updateShellProcStatus(event.data);
|
||||
},
|
||||
});
|
||||
this.shellProcStatus = jotai.atom((get) => {
|
||||
|
|
@ -364,7 +363,7 @@ export class TermViewModel implements ViewModel {
|
|||
.catch((error) => {
|
||||
console.log("error getting initial block job status", error);
|
||||
});
|
||||
this.blockJobStatusUnsubFn = waveEventSubscribe({
|
||||
this.blockJobStatusUnsubFn = waveEventSubscribeSingle({
|
||||
eventType: "block:jobstatus",
|
||||
scope: `block:${blockId}`,
|
||||
handler: (event) => {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type { BlockNodeModel } from "@/app/block/blocktypes";
|
|||
import { Search, useSearch } from "@/app/element/search";
|
||||
import { ContextMenuModel } from "@/app/store/contextmenu";
|
||||
import { useTabModel } from "@/app/store/tab-model";
|
||||
import { waveEventSubscribe } from "@/app/store/wps";
|
||||
import { waveEventSubscribeSingle } from "@/app/store/wps";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { TabRpcClient } from "@/app/store/wshrpcutil";
|
||||
import type { TermViewModel } from "@/app/view/term/term-model";
|
||||
|
|
@ -55,7 +55,7 @@ const TermResyncHandler = React.memo(({ blockId, model }: TerminalViewProps) =>
|
|||
|
||||
const TermVDomToolbarNode = ({ vdomBlockId, blockId, model }: TerminalViewProps & { vdomBlockId: string }) => {
|
||||
React.useEffect(() => {
|
||||
const unsub = waveEventSubscribe({
|
||||
const unsub = waveEventSubscribeSingle({
|
||||
eventType: "blockclose",
|
||||
scope: WOS.makeORef("block", vdomBlockId),
|
||||
handler: (event) => {
|
||||
|
|
@ -98,7 +98,7 @@ const TermVDomToolbarNode = ({ vdomBlockId, blockId, model }: TerminalViewProps
|
|||
|
||||
const TermVDomNodeSingleId = ({ vdomBlockId, blockId, model }: TerminalViewProps & { vdomBlockId: string }) => {
|
||||
React.useEffect(() => {
|
||||
const unsub = waveEventSubscribe({
|
||||
const unsub = waveEventSubscribeSingle({
|
||||
eventType: "blockclose",
|
||||
scope: WOS.makeORef("block", vdomBlockId),
|
||||
handler: (event) => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { BlockNodeModel } from "@/app/block/blocktypes";
|
||||
import { getApi, globalStore, WOS } from "@/app/store/global";
|
||||
import type { TabModel } from "@/app/store/tab-model";
|
||||
import { waveEventSubscribe } from "@/app/store/wps";
|
||||
import { waveEventSubscribeSingle } from "@/app/store/wps";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { TabRpcClient } from "@/app/store/wshrpcutil";
|
||||
import { WebView, WebViewModel } from "@/app/view/webview/webview";
|
||||
|
|
@ -37,12 +37,11 @@ class TsunamiViewModel extends WebViewModel {
|
|||
initialShellProcStatus.then((rts) => {
|
||||
this.updateShellProcStatus(rts);
|
||||
});
|
||||
this.shellProcStatusUnsubFn = waveEventSubscribe({
|
||||
this.shellProcStatusUnsubFn = waveEventSubscribeSingle({
|
||||
eventType: "controllerstatus",
|
||||
scope: WOS.makeORef("block", blockId),
|
||||
handler: (event) => {
|
||||
let bcRTS: BlockControllerRuntimeStatus = event.data;
|
||||
this.updateShellProcStatus(bcRTS);
|
||||
this.updateShellProcStatus(event.data);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -69,12 +68,11 @@ class TsunamiViewModel extends WebViewModel {
|
|||
globalStore.set(this.appMeta, rtInfo["tsunami:appmeta"]);
|
||||
}
|
||||
});
|
||||
this.appMetaUnsubFn = waveEventSubscribe({
|
||||
this.appMetaUnsubFn = waveEventSubscribeSingle({
|
||||
eventType: "tsunami:updatemeta",
|
||||
scope: WOS.makeORef("block", blockId),
|
||||
handler: (event) => {
|
||||
const meta: AppMeta = event.data;
|
||||
globalStore.set(this.appMeta, meta);
|
||||
globalStore.set(this.appMeta, event.data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { BlockNodeModel } from "@/app/block/blocktypes";
|
|||
import type { TabModel } from "@/app/store/tab-model";
|
||||
import { getBlockMetaKeyAtom, globalStore, WOS } from "@/app/store/global";
|
||||
import { makeORef } from "@/app/store/wos";
|
||||
import { waveEventSubscribe } from "@/app/store/wps";
|
||||
import { waveEventSubscribeSingle } from "@/app/store/wps";
|
||||
import { RpcResponseHelper, WshClient } from "@/app/store/wshclient";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { makeFeBlockRouteId } from "@/app/store/wshrouter";
|
||||
|
|
@ -161,10 +161,10 @@ export class VDomModel {
|
|||
if (curBackendRoute) {
|
||||
this.queueUpdate(true);
|
||||
}
|
||||
this.routeGoneUnsub = waveEventSubscribe({
|
||||
this.routeGoneUnsub = waveEventSubscribeSingle({
|
||||
eventType: "route:down",
|
||||
scope: curBackendRoute,
|
||||
handler: (event: WaveEvent) => {
|
||||
handler: (_event) => {
|
||||
this.disposed = true;
|
||||
const shouldPersist = globalStore.get(this.persist);
|
||||
if (!shouldPersist) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { globalStore } from "@/app/store/jotaiStore";
|
||||
import { waveEventSubscribe } from "@/app/store/wps";
|
||||
import { waveEventSubscribeSingle } from "@/app/store/wps";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { TabRpcClient } from "@/app/store/wshrpcutil";
|
||||
import { atoms, getApi, WOS } from "@/store/global";
|
||||
|
|
@ -79,11 +79,11 @@ export class BuilderAppPanelModel {
|
|||
this.statusUnsubFn();
|
||||
}
|
||||
|
||||
this.statusUnsubFn = waveEventSubscribe({
|
||||
this.statusUnsubFn = waveEventSubscribeSingle({
|
||||
eventType: "builderstatus",
|
||||
scope: WOS.makeORef("builder", builderId),
|
||||
handler: (event) => {
|
||||
const status: BuilderStatusData = event.data;
|
||||
const status = event.data;
|
||||
const currentStatus = globalStore.get(this.builderStatusAtom);
|
||||
if (!currentStatus || !currentStatus.version || status.version > currentStatus.version) {
|
||||
globalStore.set(this.builderStatusAtom, status);
|
||||
|
|
@ -105,7 +105,7 @@ export class BuilderAppPanelModel {
|
|||
await this.loadAppFile(appId);
|
||||
await this.loadEnvVars(builderId);
|
||||
|
||||
this.appGoUpdateUnsubFn = waveEventSubscribe({
|
||||
this.appGoUpdateUnsubFn = waveEventSubscribeSingle({
|
||||
eventType: "waveapp:appgoupdated",
|
||||
scope: appId,
|
||||
handler: () => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { globalStore } from "@/app/store/jotaiStore";
|
||||
import { waveEventSubscribe } from "@/app/store/wps";
|
||||
import { waveEventSubscribeSingle } from "@/app/store/wps";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { TabRpcClient } from "@/app/store/wshrpcutil";
|
||||
import { atoms, WOS } from "@/store/global";
|
||||
|
|
@ -36,7 +36,7 @@ export class BuilderBuildPanelModel {
|
|||
this.outputUnsubFn();
|
||||
}
|
||||
|
||||
this.outputUnsubFn = waveEventSubscribe({
|
||||
this.outputUnsubFn = waveEventSubscribeSingle({
|
||||
eventType: "builderoutput",
|
||||
scope: WOS.makeORef("builder", builderId),
|
||||
handler: (event) => {
|
||||
|
|
|
|||
10
frontend/types/gotypes.d.ts
vendored
10
frontend/types/gotypes.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// generated by cmd/generate/main-generatets.go
|
||||
|
|
@ -1931,14 +1931,6 @@ declare global {
|
|||
total_tokens?: number;
|
||||
};
|
||||
|
||||
// wps.WaveEvent
|
||||
type WaveEvent = {
|
||||
event: string;
|
||||
scopes?: string[];
|
||||
sender?: string;
|
||||
persist?: number;
|
||||
data?: any;
|
||||
};
|
||||
|
||||
// filestore.WaveFile
|
||||
type WaveFile = {
|
||||
|
|
|
|||
41
frontend/types/waveevent.d.ts
vendored
Normal file
41
frontend/types/waveevent.d.ts
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// generated by cmd/generate/main-generatets.go
|
||||
|
||||
declare global {
|
||||
|
||||
// wps.WaveEvent
|
||||
type WaveEventName = "blockclose" | "connchange" | "sysinfo" | "controllerstatus" | "builderstatus" | "builderoutput" | "waveobj:update" | "blockfile" | "config" | "userinput" | "route:down" | "route:up" | "workspace:update" | "waveai:ratelimit" | "waveapp:appgoupdated" | "tsunami:updatemeta" | "waveai:modeconfig" | "tab:indicator" | "block:jobstatus";
|
||||
|
||||
type WaveEvent = {
|
||||
event: WaveEventName;
|
||||
scopes?: string[];
|
||||
sender?: string;
|
||||
persist?: number;
|
||||
data?: unknown;
|
||||
} & (
|
||||
{ event: "blockclose"; data?: string; } |
|
||||
{ event: "connchange"; data?: ConnStatus; } |
|
||||
{ event: "sysinfo"; data?: TimeSeriesData; } |
|
||||
{ event: "controllerstatus"; data?: BlockControllerRuntimeStatus; } |
|
||||
{ event: "builderstatus"; data?: BuilderStatusData; } |
|
||||
{ event: "builderoutput"; data?: {[key: string]: any}; } |
|
||||
{ event: "waveobj:update"; data?: WaveObjUpdate; } |
|
||||
{ event: "blockfile"; data?: WSFileEventData; } |
|
||||
{ event: "config"; data?: WatcherUpdate; } |
|
||||
{ event: "userinput"; data?: UserInputRequest; } |
|
||||
{ event: "route:down"; data?: null; } |
|
||||
{ event: "route:up"; data?: null; } |
|
||||
{ event: "workspace:update"; data?: null; } |
|
||||
{ event: "waveai:ratelimit"; data?: RateLimitInfo; } |
|
||||
{ event: "waveapp:appgoupdated"; data?: null; } |
|
||||
{ event: "tsunami:updatemeta"; data?: AppMeta; } |
|
||||
{ event: "waveai:modeconfig"; data?: AIModeConfigUpdate; } |
|
||||
{ event: "tab:indicator"; data?: TabIndicatorEventData; } |
|
||||
{ event: "block:jobstatus"; data?: BlockJobStatusData; }
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export {}
|
||||
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
func GenerateBoilerplate(buf *strings.Builder, pkgName string, imports []string) {
|
||||
buf.WriteString("// Copyright 2025, Command Line Inc.\n")
|
||||
buf.WriteString("// Copyright 2026, Command Line Inc.\n")
|
||||
buf.WriteString("// SPDX-License-Identifier: Apache-2.0\n")
|
||||
buf.WriteString("\n// Generated Code. DO NOT EDIT.\n\n")
|
||||
buf.WriteString(fmt.Sprintf("package %s\n\n", pkgName))
|
||||
|
|
|
|||
92
pkg/tsgen/tsgenevent.go
Normal file
92
pkg/tsgen/tsgenevent.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package tsgen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
|
||||
"github.com/wavetermdev/waveterm/pkg/blockcontroller"
|
||||
"github.com/wavetermdev/waveterm/pkg/userinput"
|
||||
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
||||
"github.com/wavetermdev/waveterm/pkg/wps"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||
)
|
||||
|
||||
var waveEventRType = reflect.TypeOf(wps.WaveEvent{})
|
||||
|
||||
var WaveEventDataTypes = map[string]reflect.Type{
|
||||
wps.Event_BlockClose: reflect.TypeOf(""),
|
||||
wps.Event_ConnChange: reflect.TypeOf(wshrpc.ConnStatus{}),
|
||||
wps.Event_SysInfo: reflect.TypeOf(wshrpc.TimeSeriesData{}),
|
||||
wps.Event_ControllerStatus: reflect.TypeOf((*blockcontroller.BlockControllerRuntimeStatus)(nil)),
|
||||
wps.Event_BuilderStatus: reflect.TypeOf(wshrpc.BuilderStatusData{}),
|
||||
wps.Event_BuilderOutput: reflect.TypeOf(map[string]any{}),
|
||||
wps.Event_WaveObjUpdate: reflect.TypeOf(waveobj.WaveObjUpdate{}),
|
||||
wps.Event_BlockFile: reflect.TypeOf((*wps.WSFileEventData)(nil)),
|
||||
wps.Event_Config: reflect.TypeOf(wconfig.WatcherUpdate{}),
|
||||
wps.Event_UserInput: reflect.TypeOf((*userinput.UserInputRequest)(nil)),
|
||||
wps.Event_RouteDown: nil,
|
||||
wps.Event_RouteUp: nil,
|
||||
wps.Event_WorkspaceUpdate: nil,
|
||||
wps.Event_WaveAIRateLimit: reflect.TypeOf((*uctypes.RateLimitInfo)(nil)),
|
||||
wps.Event_WaveAppAppGoUpdated: nil,
|
||||
wps.Event_TsunamiUpdateMeta: reflect.TypeOf(wshrpc.AppMeta{}),
|
||||
wps.Event_AIModeConfig: reflect.TypeOf(wconfig.AIModeConfigUpdate{}),
|
||||
wps.Event_TabIndicator: reflect.TypeOf(wshrpc.TabIndicatorEventData{}),
|
||||
wps.Event_BlockJobStatus: reflect.TypeOf(wshrpc.BlockJobStatusData{}),
|
||||
}
|
||||
|
||||
func getWaveEventDataTSType(eventName string, tsTypesMap map[reflect.Type]string) string {
|
||||
rtype, found := WaveEventDataTypes[eventName]
|
||||
if !found {
|
||||
return "any"
|
||||
}
|
||||
if rtype == nil {
|
||||
return "null"
|
||||
}
|
||||
tsType, _ := TypeToTSType(rtype, tsTypesMap)
|
||||
if tsType == "" {
|
||||
return "any"
|
||||
}
|
||||
return tsType
|
||||
}
|
||||
|
||||
func GenerateWaveEventTypes(tsTypesMap map[reflect.Type]string) string {
|
||||
for _, rtype := range WaveEventDataTypes {
|
||||
GenerateTSType(rtype, tsTypesMap)
|
||||
}
|
||||
// suppress default struct generation, this type is custom generated
|
||||
tsTypesMap[waveEventRType] = ""
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("// wps.WaveEvent\n")
|
||||
buf.WriteString("type WaveEventName = ")
|
||||
for idx, eventName := range wps.AllEvents {
|
||||
if idx > 0 {
|
||||
buf.WriteString(" | ")
|
||||
}
|
||||
buf.WriteString(strconv.Quote(eventName))
|
||||
}
|
||||
buf.WriteString(";\n\n")
|
||||
buf.WriteString("type WaveEvent = {\n")
|
||||
buf.WriteString(" event: WaveEventName;\n")
|
||||
buf.WriteString(" scopes?: string[];\n")
|
||||
buf.WriteString(" sender?: string;\n")
|
||||
buf.WriteString(" persist?: number;\n")
|
||||
buf.WriteString(" data?: unknown;\n")
|
||||
buf.WriteString("} & (\n")
|
||||
for idx, eventName := range wps.AllEvents {
|
||||
if idx > 0 {
|
||||
buf.WriteString(" | \n")
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf(" { event: %s; data?: %s; }", strconv.Quote(eventName), getWaveEventDataTSType(eventName, tsTypesMap)))
|
||||
}
|
||||
buf.WriteString("\n);\n")
|
||||
return buf.String()
|
||||
}
|
||||
37
pkg/tsgen/tsgenevent_test.go
Normal file
37
pkg/tsgen/tsgenevent_test.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package tsgen
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/wavetermdev/waveterm/pkg/wps"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||
)
|
||||
|
||||
func TestGenerateWaveEventTypes(t *testing.T) {
|
||||
tsTypesMap := make(map[reflect.Type]string)
|
||||
waveEventTypeDecl := GenerateWaveEventTypes(tsTypesMap)
|
||||
|
||||
if !strings.Contains(waveEventTypeDecl, `type WaveEventName = "blockclose"`) {
|
||||
t.Fatalf("expected WaveEventName declaration, got:\n%s", waveEventTypeDecl)
|
||||
}
|
||||
if !strings.Contains(waveEventTypeDecl, `{ event: "block:jobstatus"; data?: BlockJobStatusData; }`) {
|
||||
t.Fatalf("expected typed block:jobstatus event, got:\n%s", waveEventTypeDecl)
|
||||
}
|
||||
if !strings.Contains(waveEventTypeDecl, `{ event: "route:up"; data?: null; }`) {
|
||||
t.Fatalf("expected null for known no-data event, got:\n%s", waveEventTypeDecl)
|
||||
}
|
||||
if got := getWaveEventDataTSType("unmapped:event", tsTypesMap); got != "any" {
|
||||
t.Fatalf("expected any for unmapped event fallback, got: %q", got)
|
||||
}
|
||||
if _, found := tsTypesMap[reflect.TypeOf(wps.WaveEvent{})]; !found {
|
||||
t.Fatalf("expected WaveEvent type to be seeded in tsTypesMap")
|
||||
}
|
||||
if _, found := tsTypesMap[reflect.TypeOf(wshrpc.BlockJobStatusData{})]; !found {
|
||||
t.Fatalf("expected mapped data types to be generated into tsTypesMap")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Generated Code. DO NOT EDIT.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Generated Code. DO NOT EDIT.
|
||||
|
|
|
|||
|
|
@ -7,28 +7,57 @@ import (
|
|||
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
|
||||
)
|
||||
|
||||
// IMPORTANT: When adding a new event constant, you MUST also:
|
||||
// 1. Add a "// type: <TypeName>" comment (use "none" if no data is sent)
|
||||
// 2. Add the constant to AllEvents below
|
||||
// 3. Add an entry to WaveEventDataTypes in pkg/tsgen/tsgenevent.go
|
||||
// - Use reflect.TypeOf(YourType{}) for value types
|
||||
// - Use reflect.TypeOf((*YourType)(nil)) for pointer types
|
||||
// - Use nil if no data is sent for the event
|
||||
const (
|
||||
Event_BlockClose = "blockclose"
|
||||
Event_ConnChange = "connchange"
|
||||
Event_SysInfo = "sysinfo"
|
||||
Event_ControllerStatus = "controllerstatus"
|
||||
Event_BuilderStatus = "builderstatus"
|
||||
Event_BuilderOutput = "builderoutput"
|
||||
Event_WaveObjUpdate = "waveobj:update"
|
||||
Event_BlockFile = "blockfile"
|
||||
Event_Config = "config"
|
||||
Event_UserInput = "userinput"
|
||||
Event_RouteDown = "route:down"
|
||||
Event_RouteUp = "route:up"
|
||||
Event_WorkspaceUpdate = "workspace:update"
|
||||
Event_WaveAIRateLimit = "waveai:ratelimit"
|
||||
Event_WaveAppAppGoUpdated = "waveapp:appgoupdated"
|
||||
Event_TsunamiUpdateMeta = "tsunami:updatemeta"
|
||||
Event_AIModeConfig = "waveai:modeconfig"
|
||||
Event_TabIndicator = "tab:indicator"
|
||||
Event_BlockJobStatus = "block:jobstatus" // type: BlockJobStatusData
|
||||
Event_BlockClose = "blockclose" // type: string
|
||||
Event_ConnChange = "connchange" // type: wshrpc.ConnStatus
|
||||
Event_SysInfo = "sysinfo" // type: wshrpc.TimeSeriesData
|
||||
Event_ControllerStatus = "controllerstatus" // type: *blockcontroller.BlockControllerRuntimeStatus
|
||||
Event_BuilderStatus = "builderstatus" // type: wshrpc.BuilderStatusData
|
||||
Event_BuilderOutput = "builderoutput" // type: map[string]any
|
||||
Event_WaveObjUpdate = "waveobj:update" // type: waveobj.WaveObjUpdate
|
||||
Event_BlockFile = "blockfile" // type: *WSFileEventData
|
||||
Event_Config = "config" // type: wconfig.WatcherUpdate
|
||||
Event_UserInput = "userinput" // type: *userinput.UserInputRequest
|
||||
Event_RouteDown = "route:down" // type: none
|
||||
Event_RouteUp = "route:up" // type: none
|
||||
Event_WorkspaceUpdate = "workspace:update" // type: none
|
||||
Event_WaveAIRateLimit = "waveai:ratelimit" // type: *uctypes.RateLimitInfo
|
||||
Event_WaveAppAppGoUpdated = "waveapp:appgoupdated" // type: none
|
||||
Event_TsunamiUpdateMeta = "tsunami:updatemeta" // type: wshrpc.AppMeta
|
||||
Event_AIModeConfig = "waveai:modeconfig" // type: wconfig.AIModeConfigUpdate
|
||||
Event_TabIndicator = "tab:indicator" // type: wshrpc.TabIndicatorEventData
|
||||
Event_BlockJobStatus = "block:jobstatus" // type: wshrpc.BlockJobStatusData
|
||||
)
|
||||
|
||||
var AllEvents []string = []string{
|
||||
Event_BlockClose,
|
||||
Event_ConnChange,
|
||||
Event_SysInfo,
|
||||
Event_ControllerStatus,
|
||||
Event_BuilderStatus,
|
||||
Event_BuilderOutput,
|
||||
Event_WaveObjUpdate,
|
||||
Event_BlockFile,
|
||||
Event_Config,
|
||||
Event_UserInput,
|
||||
Event_RouteDown,
|
||||
Event_RouteUp,
|
||||
Event_WorkspaceUpdate,
|
||||
Event_WaveAIRateLimit,
|
||||
Event_WaveAppAppGoUpdated,
|
||||
Event_TsunamiUpdateMeta,
|
||||
Event_AIModeConfig,
|
||||
Event_TabIndicator,
|
||||
Event_BlockJobStatus,
|
||||
}
|
||||
|
||||
type WaveEvent struct {
|
||||
Event string `json:"event"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Generated Code. DO NOT EDIT.
|
||||
|
|
|
|||
Loading…
Reference in a new issue