phase 5: wasm core, GalaxyClient skeleton, Connect-Web stubs

Compile `ui/core` to WebAssembly via TinyGo (903 KB) and expose four
canonical-bytes / signature-verification functions on
`globalThis.galaxyCore` from `ui/wasm/main.go`. The TypeScript-side
`Core` interface plus a `WasmCore` adapter (browser + JSDOM loader)
bridge those into a typed shape, and a `GalaxyClient` skeleton wires
`Core.signRequest` → injected `Signer` → typed Connect client →
`Core.verifyPayloadHash` / `verifyResponse`.

Wire `ui/buf.gen.yaml` against the local
`@bufbuild/protoc-gen-es` v2 binary (devDependency) so the codegen
step does not depend on the buf.build BSR. Vitest covers the bridge
end-to-end: per-method WasmCore tests under JSDOM, byte-for-byte
canon parity against the gateway fixtures committed in Phase 3, and
a `GalaxyClient` orchestration test using
`createRouterTransport`. The committed `core.wasm` snapshot tracks
TinyGo output so contributors run `make wasm` only when `ui/core/`
changes; CI consumes the snapshot directly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-07 12:58:37 +02:00
parent cd61868881
commit fbc0260720
25 changed files with 7284 additions and 36 deletions
+20
View File
@@ -0,0 +1,20 @@
// `createEdgeGatewayClient` builds a typed Connect-Web client for the
// gateway's authenticated edge surface. It speaks the Connect protocol
// over HTTP/1.1 (or HTTP/2 if the host upgrades the connection) — the
// gateway listener built in Phase 4 natively serves Connect, gRPC, and
// gRPC-Web on the same h2c port.
//
// The factory is intentionally thin: callers provide the gateway base
// URL (e.g. https://api.galaxy.test), and receive a typed
// `EdgeGatewayClient`. Authentication, signing, and response
// verification live one layer up, in `GalaxyClient`.
import { createClient, type Client } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web";
import { EdgeGateway } from "../proto/galaxy/gateway/v1/edge_gateway_pb";
export type EdgeGatewayClient = Client<typeof EdgeGateway>;
export function createEdgeGatewayClient(baseUrl: string): EdgeGatewayClient {
return createClient(EdgeGateway, createConnectTransport({ baseUrl }));
}
+149
View File
@@ -0,0 +1,149 @@
// `GalaxyClient` orchestrates one authenticated unary round-trip:
// build canonical request bytes via `Core.signRequest`, sign them
// through an injected `Signer`, post the envelope through the typed
// Connect client, then verify the response signature and payload hash
// before returning the decoded payload.
//
// Phase 5 ships the orchestration only — Phase 6 plugs the WebCrypto
// `Signer` and `KeyStore`, and Phase 7 wires the auth flow that
// actually creates the device session. Tests pass an exportable
// fixture key wrapped as a `Signer` so the flow can be exercised
// end-to-end without a live gateway.
import { create } from "@bufbuild/protobuf";
import type { Core } from "../platform/core/index";
import {
ExecuteCommandRequestSchema,
type ExecuteCommandResponse,
} from "../proto/galaxy/gateway/v1/edge_gateway_pb";
import type { EdgeGatewayClient } from "./connect";
/**
* Signer produces a raw 64-byte Ed25519 signature over canonicalBytes.
* Phase 6 backs this with WebCrypto + a non-exportable IDB key handle.
* Phase 5 tests pass a function that closes over `crypto.sign(...)`
* with an exportable fixture key.
*/
export type Signer = (canonicalBytes: Uint8Array) => Promise<Uint8Array>;
/**
* Sha256 computes the raw 32-byte SHA-256 digest of payload. The
* GalaxyClient receives this as a dependency so JSDOM tests can pass
* Node's `crypto` and the browser path can use `crypto.subtle`.
*/
export type Sha256 = (payload: Uint8Array) => Promise<Uint8Array>;
export interface GalaxyClientOptions {
core: Core;
edge: EdgeGatewayClient;
signer: Signer;
sha256: Sha256;
deviceSessionId: string;
gatewayResponsePublicKey: Uint8Array;
clock?: () => bigint;
requestIdFactory?: () => string;
}
const PROTOCOL_VERSION = "v1";
export class GalaxyClient {
private readonly core: Core;
private readonly edge: EdgeGatewayClient;
private readonly signer: Signer;
private readonly sha256: Sha256;
private readonly deviceSessionId: string;
private readonly gatewayResponsePublicKey: Uint8Array;
private readonly clock: () => bigint;
private readonly requestIdFactory: () => string;
constructor(options: GalaxyClientOptions) {
this.core = options.core;
this.edge = options.edge;
this.signer = options.signer;
this.sha256 = options.sha256;
this.deviceSessionId = options.deviceSessionId;
this.gatewayResponsePublicKey = options.gatewayResponsePublicKey;
this.clock = options.clock ?? defaultClock;
this.requestIdFactory =
options.requestIdFactory ?? defaultRequestIdFactory;
}
async executeCommand(
messageType: string,
payload: Uint8Array,
traceId = "",
): Promise<Uint8Array> {
const requestId = this.requestIdFactory();
const timestampMs = this.clock();
const payloadHash = await this.sha256(payload);
const canonicalBytes = this.core.signRequest({
protocolVersion: PROTOCOL_VERSION,
deviceSessionId: this.deviceSessionId,
messageType,
timestampMs,
requestId,
payloadHash,
});
const signature = await this.signer(canonicalBytes);
const response = await this.edge.executeCommand(
create(ExecuteCommandRequestSchema, {
protocolVersion: PROTOCOL_VERSION,
deviceSessionId: this.deviceSessionId,
messageType,
timestampMs,
requestId,
payloadBytes: payload,
payloadHash,
signature,
traceId,
}),
);
this.verifyResponse(response, requestId);
return response.payloadBytes;
}
private verifyResponse(
response: ExecuteCommandResponse,
requestId: string,
): void {
if (response.requestId !== requestId) {
throw new Error(
`galaxy-client: response request_id ${response.requestId} does not match ${requestId}`,
);
}
if (
!this.core.verifyPayloadHash(
response.payloadBytes,
response.payloadHash,
)
) {
throw new Error("galaxy-client: response payload_hash mismatch");
}
const ok = this.core.verifyResponse(
this.gatewayResponsePublicKey,
response.signature,
{
protocolVersion: response.protocolVersion,
requestId: response.requestId,
timestampMs: response.timestampMs,
resultCode: response.resultCode,
payloadHash: response.payloadHash,
},
);
if (!ok) {
throw new Error("galaxy-client: invalid response signature");
}
}
}
function defaultClock(): bigint {
return BigInt(Date.now());
}
function defaultRequestIdFactory(): string {
return crypto.randomUUID();
}
+85
View File
@@ -0,0 +1,85 @@
// `Core` is the typed TypeScript view of the Galaxy compute boundary
// (`ui/core` Go module compiled to WASM/native). It only carries
// canonical-bytes serialization and signature verification — no
// network I/O, no key storage, no signing private keys. Real Ed25519
// signing happens in a platform-specific `Signer` (Phase 6 introduces
// WebCrypto for the web target).
//
// All byte fields are `Uint8Array`. Timestamps are `bigint` to keep
// nanosecond-resolution-friendly headroom and to mirror the v2
// Protobuf-ES `int64 → bigint` mapping used by the generated stubs.
export interface RequestSigningFields {
protocolVersion: string;
deviceSessionId: string;
messageType: string;
timestampMs: bigint;
requestId: string;
payloadHash: Uint8Array;
}
export interface ResponseSigningFields {
protocolVersion: string;
requestId: string;
timestampMs: bigint;
resultCode: string;
payloadHash: Uint8Array;
}
export interface EventSigningFields {
eventType: string;
eventId: string;
timestampMs: bigint;
requestId: string;
traceId: string;
payloadHash: Uint8Array;
}
export interface Core {
/**
* signRequest returns the canonical signing input bytes for a v1
* request envelope. Callers feed the result into a platform `Signer`
* (e.g. WebCrypto) to produce the actual Ed25519 signature.
*/
signRequest(fields: RequestSigningFields): Uint8Array;
/**
* verifyResponse authenticates a server response signature against
* the canonical v1 response signing input.
*/
verifyResponse(
publicKey: Uint8Array,
signature: Uint8Array,
fields: ResponseSigningFields,
): boolean;
/**
* verifyEvent authenticates a push-event signature against the
* canonical v1 event signing input.
*/
verifyEvent(
publicKey: Uint8Array,
signature: Uint8Array,
fields: EventSigningFields,
): boolean;
/**
* verifyPayloadHash returns true when payloadHash is the raw 32-byte
* SHA-256 digest of payloadBytes.
*/
verifyPayloadHash(
payloadBytes: Uint8Array,
payloadHash: Uint8Array,
): boolean;
}
export type CoreLoader = () => Promise<Core>;
import { loadWasmCore } from "./wasm";
/**
* loadCore resolves the Core implementation appropriate for the current
* build target. Phase 5 ships only the WASM adapter; later phases plug
* `WailsCore` and `CapacitorCore` here behind a build-time selector.
*/
export const loadCore: CoreLoader = loadWasmCore;
+196
View File
@@ -0,0 +1,196 @@
// `WasmCore` is the browser-side `Core` implementation. It loads the
// TinyGo-built `core.wasm` module + the matching `wasm_exec.js` runtime
// shim and bridges the four exported functions on `globalThis.galaxyCore`
// to the typed `Core` interface.
//
// The same module also runs under Node/JSDOM (via Vitest) by reading
// the bundle from disk; the test loader lives in
// `tests/setup-wasm.ts`. The browser path is the production target
// served from `static/core.wasm`.
import type {
Core,
EventSigningFields,
RequestSigningFields,
ResponseSigningFields,
} from "./index";
/**
* GalaxyCoreBridge is the shape Go installs on `globalThis.galaxyCore`.
* It is purely an internal contract between `ui/wasm/main.go` and this
* adapter; consumers see only the typed `Core` interface.
*/
interface GalaxyCoreBridge {
signRequest(fields: BridgeRequestFields): Uint8Array;
verifyResponse(
publicKey: Uint8Array,
signature: Uint8Array,
fields: BridgeResponseFields,
): boolean;
verifyEvent(
publicKey: Uint8Array,
signature: Uint8Array,
fields: BridgeEventFields,
): boolean;
verifyPayloadHash(
payloadBytes: Uint8Array,
payloadHash: Uint8Array,
): boolean;
}
interface BridgeRequestFields {
protocolVersion: string;
deviceSessionId: string;
messageType: string;
timestampMs: number;
requestId: string;
payloadHash: Uint8Array;
}
interface BridgeResponseFields {
protocolVersion: string;
requestId: string;
timestampMs: number;
resultCode: string;
payloadHash: Uint8Array;
}
interface BridgeEventFields {
eventType: string;
eventId: string;
timestampMs: number;
requestId: string;
traceId: string;
payloadHash: Uint8Array;
}
declare global {
// eslint-disable-next-line no-var
var galaxyCore: GalaxyCoreBridge | undefined;
// eslint-disable-next-line no-var
var Go: { new (): GoRuntime } | undefined;
}
interface GoRuntime {
importObject: WebAssembly.Imports;
run(instance: WebAssembly.Instance): Promise<void>;
}
/**
* loadWasmCore boots the TinyGo `core.wasm` module in the browser and
* returns a `Core` whose methods bridge into the Go-exported functions.
* The module is started exactly once per page; subsequent calls return
* the cached instance. The browser path expects `wasm_exec.js` to be
* served alongside `core.wasm` under SvelteKit's `static/` root.
*/
let cached: Promise<Core> | undefined;
export function loadWasmCore(): Promise<Core> {
if (!cached) {
cached = bootBrowserWasm();
}
return cached;
}
async function bootBrowserWasm(): Promise<Core> {
if (typeof window === "undefined") {
throw new Error(
"loadWasmCore: no `window` global; load via tests/setup-wasm.ts under JSDOM",
);
}
await ensureGoRuntimeLoaded();
const Go = globalThis.Go;
if (!Go) {
throw new Error("loadWasmCore: Go runtime missing after wasm_exec.js load");
}
const go = new Go();
const response = await fetch("/core.wasm");
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, go.importObject);
void go.run(instance);
return adaptBridge(requireBridge());
}
async function ensureGoRuntimeLoaded(): Promise<void> {
if (typeof globalThis.Go !== "undefined") {
return;
}
await new Promise<void>((resolve, reject) => {
const script = document.createElement("script");
script.src = "/wasm_exec.js";
script.onload = () => resolve();
script.onerror = () => reject(new Error("failed to load /wasm_exec.js"));
document.head.appendChild(script);
});
}
/**
* adaptBridge wraps the raw Go-installed bridge with the `Core` shape,
* normalising `bigint` ↔ JS Number conversion at the timestamp boundary.
* The bridge is also exported for the JSDOM test loader, which boots
* the WASM module differently (see `tests/setup-wasm.ts`).
*/
export function adaptBridge(bridge: GalaxyCoreBridge): Core {
return {
signRequest(fields: RequestSigningFields): Uint8Array {
return bridge.signRequest({
protocolVersion: fields.protocolVersion,
deviceSessionId: fields.deviceSessionId,
messageType: fields.messageType,
timestampMs: bigintToNumber(fields.timestampMs),
requestId: fields.requestId,
payloadHash: fields.payloadHash,
});
},
verifyResponse(
publicKey: Uint8Array,
signature: Uint8Array,
fields: ResponseSigningFields,
): boolean {
return bridge.verifyResponse(publicKey, signature, {
protocolVersion: fields.protocolVersion,
requestId: fields.requestId,
timestampMs: bigintToNumber(fields.timestampMs),
resultCode: fields.resultCode,
payloadHash: fields.payloadHash,
});
},
verifyEvent(
publicKey: Uint8Array,
signature: Uint8Array,
fields: EventSigningFields,
): boolean {
return bridge.verifyEvent(publicKey, signature, {
eventType: fields.eventType,
eventId: fields.eventId,
timestampMs: bigintToNumber(fields.timestampMs),
requestId: fields.requestId,
traceId: fields.traceId,
payloadHash: fields.payloadHash,
});
},
verifyPayloadHash(
payloadBytes: Uint8Array,
payloadHash: Uint8Array,
): boolean {
return bridge.verifyPayloadHash(payloadBytes, payloadHash);
},
};
}
export function requireBridge(): GalaxyCoreBridge {
const bridge = globalThis.galaxyCore;
if (!bridge) {
throw new Error(
"loadWasmCore: `globalThis.galaxyCore` not installed — was the WASM module instantiated?",
);
}
return bridge;
}
// Unix-millisecond timestamps fit in 53 bits well past the year 2200, so
// the bigint → number narrowing is safe for every realistic value the
// envelope contract carries.
function bigintToNumber(value: bigint): number {
return Number(value);
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,262 @@
// @generated by protoc-gen-es v2.12.0 with parameter "target=ts"
// @generated from file galaxy/gateway/v1/edge_gateway.proto (package galaxy.gateway.v1, syntax proto3)
/* eslint-disable */
import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
import { file_buf_validate_validate } from "../../../buf/validate/validate_pb";
import type { Message } from "@bufbuild/protobuf";
/**
* Describes the file galaxy/gateway/v1/edge_gateway.proto.
*/
export const file_galaxy_gateway_v1_edge_gateway: GenFile = /*@__PURE__*/
fileDesc("CiRnYWxheHkvZ2F0ZXdheS92MS9lZGdlX2dhdGV3YXkucHJvdG8SEWdhbGF4eS5nYXRld2F5LnYxIqYCChVFeGVjdXRlQ29tbWFuZFJlcXVlc3QSIQoQcHJvdG9jb2xfdmVyc2lvbhgBIAEoCUIHukgEcgIQARIiChFkZXZpY2Vfc2Vzc2lvbl9pZBgCIAEoCUIHukgEcgIQARIdCgxtZXNzYWdlX3R5cGUYAyABKAlCB7pIBHICEAESHQoMdGltZXN0YW1wX21zGAQgASgDQge6SAQiAiAAEhsKCnJlcXVlc3RfaWQYBSABKAlCB7pIBHICEAESHgoNcGF5bG9hZF9ieXRlcxgGIAEoDEIHukgEegIQARIdCgxwYXlsb2FkX2hhc2gYByABKAxCB7pIBHoCEAESGgoJc2lnbmF0dXJlGAggASgMQge6SAR6AhABEhAKCHRyYWNlX2lkGAkgASgJIrEBChZFeGVjdXRlQ29tbWFuZFJlc3BvbnNlEhgKEHByb3RvY29sX3ZlcnNpb24YASABKAkSEgoKcmVxdWVzdF9pZBgCIAEoCRIUCgx0aW1lc3RhbXBfbXMYAyABKAMSEwoLcmVzdWx0X2NvZGUYBCABKAkSFQoNcGF5bG9hZF9ieXRlcxgFIAEoDBIUCgxwYXlsb2FkX2hhc2gYBiABKAwSEQoJc2lnbmF0dXJlGAcgASgMIp4CChZTdWJzY3JpYmVFdmVudHNSZXF1ZXN0EiEKEHByb3RvY29sX3ZlcnNpb24YASABKAlCB7pIBHICEAESIgoRZGV2aWNlX3Nlc3Npb25faWQYAiABKAlCB7pIBHICEAESHQoMbWVzc2FnZV90eXBlGAMgASgJQge6SARyAhABEh0KDHRpbWVzdGFtcF9tcxgEIAEoA0IHukgEIgIgABIbCgpyZXF1ZXN0X2lkGAUgASgJQge6SARyAhABEh0KDHBheWxvYWRfaGFzaBgGIAEoDEIHukgEegIQARIaCglzaWduYXR1cmUYByABKAxCB7pIBHoCEAESFQoNcGF5bG9hZF9ieXRlcxgIIAEoDBIQCgh0cmFjZV9pZBgJIAEoCSKwAQoMR2F0ZXdheUV2ZW50EhIKCmV2ZW50X3R5cGUYASABKAkSEAoIZXZlbnRfaWQYAiABKAkSFAoMdGltZXN0YW1wX21zGAMgASgDEhUKDXBheWxvYWRfYnl0ZXMYBCABKAwSFAoMcGF5bG9hZF9oYXNoGAUgASgMEhEKCXNpZ25hdHVyZRgGIAEoDBISCgpyZXF1ZXN0X2lkGAcgASgJEhAKCHRyYWNlX2lkGAggASgJMtUBCgtFZGdlR2F0ZXdheRJlCg5FeGVjdXRlQ29tbWFuZBIoLmdhbGF4eS5nYXRld2F5LnYxLkV4ZWN1dGVDb21tYW5kUmVxdWVzdBopLmdhbGF4eS5nYXRld2F5LnYxLkV4ZWN1dGVDb21tYW5kUmVzcG9uc2USXwoPU3Vic2NyaWJlRXZlbnRzEikuZ2FsYXh5LmdhdGV3YXkudjEuU3Vic2NyaWJlRXZlbnRzUmVxdWVzdBofLmdhbGF4eS5nYXRld2F5LnYxLkdhdGV3YXlFdmVudDABQjJaMGdhbGF4eS9nYXRld2F5L3Byb3RvL2dhbGF4eS9nYXRld2F5L3YxO2dhdGV3YXl2MWIGcHJvdG8z", [file_buf_validate_validate]);
/**
* @generated from message galaxy.gateway.v1.ExecuteCommandRequest
*/
export type ExecuteCommandRequest = Message<"galaxy.gateway.v1.ExecuteCommandRequest"> & {
/**
* protocol_version identifies the request envelope version. The gateway
* accepts only the literal "v1" after required-field validation succeeds.
*
* @generated from field: string protocol_version = 1;
*/
protocolVersion: string;
/**
* @generated from field: string device_session_id = 2;
*/
deviceSessionId: string;
/**
* @generated from field: string message_type = 3;
*/
messageType: string;
/**
* @generated from field: int64 timestamp_ms = 4;
*/
timestampMs: bigint;
/**
* @generated from field: string request_id = 5;
*/
requestId: string;
/**
* @generated from field: bytes payload_bytes = 6;
*/
payloadBytes: Uint8Array;
/**
* payload_hash is the raw 32-byte SHA-256 digest of payload_bytes.
*
* @generated from field: bytes payload_hash = 7;
*/
payloadHash: Uint8Array;
/**
* @generated from field: bytes signature = 8;
*/
signature: Uint8Array;
/**
* @generated from field: string trace_id = 9;
*/
traceId: string;
};
/**
* Describes the message galaxy.gateway.v1.ExecuteCommandRequest.
* Use `create(ExecuteCommandRequestSchema)` to create a new message.
*/
export const ExecuteCommandRequestSchema: GenMessage<ExecuteCommandRequest> = /*@__PURE__*/
messageDesc(file_galaxy_gateway_v1_edge_gateway, 0);
/**
* @generated from message galaxy.gateway.v1.ExecuteCommandResponse
*/
export type ExecuteCommandResponse = Message<"galaxy.gateway.v1.ExecuteCommandResponse"> & {
/**
* @generated from field: string protocol_version = 1;
*/
protocolVersion: string;
/**
* @generated from field: string request_id = 2;
*/
requestId: string;
/**
* @generated from field: int64 timestamp_ms = 3;
*/
timestampMs: bigint;
/**
* @generated from field: string result_code = 4;
*/
resultCode: string;
/**
* @generated from field: bytes payload_bytes = 5;
*/
payloadBytes: Uint8Array;
/**
* @generated from field: bytes payload_hash = 6;
*/
payloadHash: Uint8Array;
/**
* @generated from field: bytes signature = 7;
*/
signature: Uint8Array;
};
/**
* Describes the message galaxy.gateway.v1.ExecuteCommandResponse.
* Use `create(ExecuteCommandResponseSchema)` to create a new message.
*/
export const ExecuteCommandResponseSchema: GenMessage<ExecuteCommandResponse> = /*@__PURE__*/
messageDesc(file_galaxy_gateway_v1_edge_gateway, 1);
/**
* @generated from message galaxy.gateway.v1.SubscribeEventsRequest
*/
export type SubscribeEventsRequest = Message<"galaxy.gateway.v1.SubscribeEventsRequest"> & {
/**
* protocol_version identifies the request envelope version. The gateway
* accepts only the literal "v1" after required-field validation succeeds.
*
* @generated from field: string protocol_version = 1;
*/
protocolVersion: string;
/**
* @generated from field: string device_session_id = 2;
*/
deviceSessionId: string;
/**
* @generated from field: string message_type = 3;
*/
messageType: string;
/**
* @generated from field: int64 timestamp_ms = 4;
*/
timestampMs: bigint;
/**
* @generated from field: string request_id = 5;
*/
requestId: string;
/**
* payload_hash is the raw 32-byte SHA-256 digest of payload_bytes. Empty
* payloads must use the SHA-256 digest of the empty byte slice.
*
* @generated from field: bytes payload_hash = 6;
*/
payloadHash: Uint8Array;
/**
* @generated from field: bytes signature = 7;
*/
signature: Uint8Array;
/**
* @generated from field: bytes payload_bytes = 8;
*/
payloadBytes: Uint8Array;
/**
* @generated from field: string trace_id = 9;
*/
traceId: string;
};
/**
* Describes the message galaxy.gateway.v1.SubscribeEventsRequest.
* Use `create(SubscribeEventsRequestSchema)` to create a new message.
*/
export const SubscribeEventsRequestSchema: GenMessage<SubscribeEventsRequest> = /*@__PURE__*/
messageDesc(file_galaxy_gateway_v1_edge_gateway, 2);
/**
* @generated from message galaxy.gateway.v1.GatewayEvent
*/
export type GatewayEvent = Message<"galaxy.gateway.v1.GatewayEvent"> & {
/**
* @generated from field: string event_type = 1;
*/
eventType: string;
/**
* @generated from field: string event_id = 2;
*/
eventId: string;
/**
* @generated from field: int64 timestamp_ms = 3;
*/
timestampMs: bigint;
/**
* @generated from field: bytes payload_bytes = 4;
*/
payloadBytes: Uint8Array;
/**
* @generated from field: bytes payload_hash = 5;
*/
payloadHash: Uint8Array;
/**
* @generated from field: bytes signature = 6;
*/
signature: Uint8Array;
/**
* @generated from field: string request_id = 7;
*/
requestId: string;
/**
* @generated from field: string trace_id = 8;
*/
traceId: string;
};
/**
* Describes the message galaxy.gateway.v1.GatewayEvent.
* Use `create(GatewayEventSchema)` to create a new message.
*/
export const GatewayEventSchema: GenMessage<GatewayEvent> = /*@__PURE__*/
messageDesc(file_galaxy_gateway_v1_edge_gateway, 3);
/**
* @generated from service galaxy.gateway.v1.EdgeGateway
*/
export const EdgeGateway: GenService<{
/**
* @generated from rpc galaxy.gateway.v1.EdgeGateway.ExecuteCommand
*/
executeCommand: {
methodKind: "unary";
input: typeof ExecuteCommandRequestSchema;
output: typeof ExecuteCommandResponseSchema;
},
/**
* @generated from rpc galaxy.gateway.v1.EdgeGateway.SubscribeEvents
*/
subscribeEvents: {
methodKind: "server_streaming";
input: typeof SubscribeEventsRequestSchema;
output: typeof GatewayEventSchema;
},
}> = /*@__PURE__*/
serviceDesc(file_galaxy_gateway_v1_edge_gateway, 0);