71 lines
2.0 KiB
TypeScript
71 lines
2.0 KiB
TypeScript
|
|
import { z } from 'zod';
|
||
|
|
import {
|
||
|
|
RpcCancelSchema,
|
||
|
|
RpcErrorSchema,
|
||
|
|
RpcRequestSchema,
|
||
|
|
RpcResponseSchema,
|
||
|
|
type RpcCancel,
|
||
|
|
type RpcEnvelope,
|
||
|
|
type RpcError,
|
||
|
|
type RpcRequest,
|
||
|
|
type RpcResponse,
|
||
|
|
} from '../schemas/envelope.js';
|
||
|
|
import { KIND_PREFIX } from './version.js';
|
||
|
|
|
||
|
|
/** Tagged classification of any incoming envelope. */
|
||
|
|
export type ClassifiedEnvelope =
|
||
|
|
| { kind: 'request'; envelope: RpcRequest }
|
||
|
|
| { kind: 'response'; envelope: RpcResponse }
|
||
|
|
| { kind: 'error'; envelope: RpcError }
|
||
|
|
| { kind: 'cancel'; envelope: RpcCancel };
|
||
|
|
|
||
|
|
const RpcAnySchema = z.union([
|
||
|
|
RpcRequestSchema,
|
||
|
|
RpcResponseSchema,
|
||
|
|
RpcErrorSchema,
|
||
|
|
RpcCancelSchema,
|
||
|
|
]);
|
||
|
|
|
||
|
|
/** Encode an envelope to JSON plaintext for `Shade.send`. */
|
||
|
|
export function encodeEnvelope(env: RpcEnvelope): string {
|
||
|
|
return JSON.stringify(env);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Quick-detection: does this plaintext look like an `@shade/files` envelope?
|
||
|
|
* Used by `ShadeFileRpcChannel` to skip non-files messages cheaply.
|
||
|
|
*/
|
||
|
|
export function looksLikeFileEnvelope(plaintext: string): boolean {
|
||
|
|
return plaintext.includes(KIND_PREFIX);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Parse and classify an incoming plaintext. Returns null on any malformed
|
||
|
|
* input — the channel ignores those silently (could be a different
|
||
|
|
* protocol on the same Shade.send pipe).
|
||
|
|
*/
|
||
|
|
export function tryParseEnvelope(plaintext: string): ClassifiedEnvelope | null {
|
||
|
|
let raw: unknown;
|
||
|
|
try {
|
||
|
|
raw = JSON.parse(plaintext);
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
const result = RpcAnySchema.safeParse(raw);
|
||
|
|
if (!result.success) return null;
|
||
|
|
return classify(result.data);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function classify(env: RpcEnvelope): ClassifiedEnvelope {
|
||
|
|
if (env.kind === 'shade.fs.cancel/v1') {
|
||
|
|
return { kind: 'cancel', envelope: env as RpcCancel };
|
||
|
|
}
|
||
|
|
if (env.kind === 'shade.fs.error/v1') {
|
||
|
|
return { kind: 'error', envelope: env as RpcError };
|
||
|
|
}
|
||
|
|
if (env.kind.endsWith('.response')) {
|
||
|
|
return { kind: 'response', envelope: env as RpcResponse };
|
||
|
|
}
|
||
|
|
return { kind: 'request', envelope: env as RpcRequest };
|
||
|
|
}
|