Skip to content
This repository was archived by the owner on Nov 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions deno-runtime/AppObjectRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export type Maybe<T> = T | null | undefined;

export const AppObjectRegistry = new class {
registry: Record<string, unknown> = {};

public get(key: string): unknown {
return this.registry[key];
public get<T>(key: string): Maybe<T> {
return this.registry[key] as Maybe<T>;
}

public set(key: string, value: unknown): void {
Expand Down
5 changes: 5 additions & 0 deletions deno-runtime/deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

134 changes: 134 additions & 0 deletions deno-runtime/lib/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import * as stackTrace from 'npm:stack-trace'
import { StackFrame } from 'npm:stack-trace'

enum LogMessageSeverity {
DEBUG = 'debug',
INFORMATION = 'info',
LOG = 'log',
WARNING = 'warning',
ERROR = 'error',
SUCCESS = 'success',
}

type Entry = {
caller: string;
severity: LogMessageSeverity;
method: string;
timestamp: Date;
args: Array<unknown>;
}

interface ILoggerStorageEntry {
appId: string;
method: string;
entries: Array<Entry>;
startTime: Date;
endTime: Date;
totalTime: number;
_createdAt: Date;
}

export class Logger {
private appId: string;
private entries: Array<Entry>;
private start: Date;
private method: string;

constructor(method: string, appId: string) {
this.appId = appId;
this.method = method;
this.entries = [];
this.start = new Date();
}

public debug(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.DEBUG, this.getStack(stackTrace.get()), ...args)
}

public info(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.INFORMATION, this.getStack(stackTrace.get()), ...args)
}

public log(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.LOG, this.getStack(stackTrace.get()), ...args)
}

public warning(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.WARNING, this.getStack(stackTrace.get()), ...args)
}

public error(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.ERROR, this.getStack(stackTrace.get()), ...args)
}

public success(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.SUCCESS, this.getStack(stackTrace.get()), ...args)
}

private addEntry(severity: LogMessageSeverity, caller: string, ...items: Array<unknown>): void {
const i = items.map((args) => {
if (args instanceof Error) {
return JSON.stringify(args, Object.getOwnPropertyNames(args));
}
if (typeof args === 'object' && args !== null && 'stack' in args) {
return JSON.stringify(args, Object.getOwnPropertyNames(args));
}
if (typeof args === 'object' && args !== null && 'message' in args) {
return JSON.stringify(args, Object.getOwnPropertyNames(args));
}
const str = JSON.stringify(args, null, 2);
return str ? JSON.parse(str) : str; // force call toJSON to prevent circular references

});

this.entries.push({
caller,
severity,
method: this.method,
timestamp: new Date(),
args: i,
});
}

private getStack(stack: Array<StackFrame>): string {
let func = 'anonymous';

if (stack.length === 1) {
return func;
}

const frame = stack[1];

if (frame.getMethodName() === null) {
func = 'anonymous OR constructor';
} else {
func = frame.getMethodName();
}

if (frame.getFunctionName() !== null) {
func = `${func} -> ${frame.getFunctionName()}`;
}

return func;
}

private getTotalTime(): number {
return new Date().getTime() - this.start.getTime();
}

public hasEntries(): boolean {
return this.entries.length > 0;
}

public getLogs(): ILoggerStorageEntry {
return {
appId: this.appId,
method: this.method,
entries: this.entries,
startTime: this.start,
endTime: new Date(),
totalTime: this.getTotalTime(),
_createdAt: new Date(),
};
}
}
71 changes: 56 additions & 15 deletions deno-runtime/lib/messenger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as jsonrpc from 'jsonrpc-lite';

import { AppObjectRegistry } from "../AppObjectRegistry.ts";
import type { Logger } from './logger.ts'

export type RequestDescriptor = Pick<jsonrpc.RequestObject, 'method' | 'params'>;

export type NotificationDescriptor = Pick<jsonrpc.NotificationObject, 'method' | 'params'>;
Expand All @@ -26,6 +29,36 @@ export function isErrorResponse(message: jsonrpc.JsonRpc): message is jsonrpc.Er
const encoder = new TextEncoder();
export const RPCResponseObserver = new EventTarget();

export const Transport = new class Transporter {
private selectedTransport: Transporter["stdoutTransport"] | Transporter["noopTransport"];

constructor() {
this.selectedTransport = this.stdoutTransport.bind(this);
}

private async stdoutTransport(message: jsonrpc.JsonRpc): Promise<void> {
const encoded = encoder.encode(message.serialize());
await Deno.stdout.write(encoded);
}

private async noopTransport(_message: jsonrpc.JsonRpc): Promise<void> { }

public selectTransport(transport: 'stdout' | 'noop'): void {
switch (transport) {
case 'stdout':
this.selectedTransport = this.stdoutTransport.bind(this);
break;
case 'noop':
this.selectedTransport = this.noopTransport.bind(this);
break;
}
}

public send(message: jsonrpc.JsonRpc): Promise<void> {
return this.selectedTransport(message);
}
}

export function parseMessage(message: string) {
const parsed = jsonrpc.parse(message);

Expand All @@ -40,51 +73,59 @@ export function parseMessage(message: string) {
return parsed;
}

export async function send(message: jsonrpc.JsonRpc): Promise<void> {
const encoded = encoder.encode(message.serialize());
await Deno.stdout.write(encoded);
}

export async function sendInvalidRequestError(): Promise<void> {
const rpc = jsonrpc.error(null, jsonrpc.JsonRpcError.invalidRequest(null));

await send(rpc);
await Transport.send(rpc);
}

export async function sendInvalidParamsError(id: jsonrpc.ID): Promise<void> {
const rpc = jsonrpc.error(id, jsonrpc.JsonRpcError.invalidParams(null));

await send(rpc);
await Transport.send(rpc);
}

export async function sendParseError(): Promise<void> {
const rpc = jsonrpc.error(null, jsonrpc.JsonRpcError.parseError(null));

await send(rpc);
await Transport.send(rpc);
}

export async function sendMethodNotFound(id: jsonrpc.ID): Promise<void> {
const rpc = jsonrpc.error(id, jsonrpc.JsonRpcError.methodNotFound(null));

await send(rpc);
await Transport.send(rpc);
}

export async function errorResponse({ error: { message, code = -32000, data }, id }: ErrorResponseDescriptor): Promise<void> {
export async function errorResponse({ error: { message, code = -32000, data = {} }, id }: ErrorResponseDescriptor): Promise<void> {
const logger = AppObjectRegistry.get<Logger>('logger');

if (logger?.hasEntries()) {
data.logs = logger.getLogs();
}

const rpc = jsonrpc.error(id, new jsonrpc.JsonRpcError(message, code, data));

await send(rpc);
await Transport.send(rpc);
}

export async function successResponse({ id, result }: SuccessResponseDescriptor): Promise<void> {
const rpc = jsonrpc.success(id, result);
const payload = { value: result } as Record<string, unknown>;
const logger = AppObjectRegistry.get<Logger>('logger');

if (logger?.hasEntries()) {
payload.logs = logger.getLogs();
}

const rpc = jsonrpc.success(id, payload);

await send(rpc);
await Transport.send(rpc);
}

export async function sendRequest(requestDescriptor: RequestDescriptor): Promise<jsonrpc.SuccessObject> {
const request = jsonrpc.request(Math.random().toString(36).slice(2), requestDescriptor.method, requestDescriptor.params);

await send(request);
await Transport.send(request);

// TODO: add timeout to this
return new Promise((resolve, reject) => {
Expand All @@ -107,5 +148,5 @@ export async function sendRequest(requestDescriptor: RequestDescriptor): Promise
export function sendNotification({ method, params }: NotificationDescriptor) {
const request = jsonrpc.notification(method, params);

send(request);
Transport.send(request);
}
Loading