@jondotsoy/pika 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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/lib/esm/auth/dto/connection.d.ts +43 -0
  4. package/lib/esm/auth/dto/connection.js +16 -0
  5. package/lib/esm/auth/index.d.ts +10 -0
  6. package/lib/esm/auth/index.js +64 -0
  7. package/lib/esm/cli/__samples__/parent-child-script.d.ts +1 -0
  8. package/lib/esm/cli/__samples__/parent-child-script.js +9 -0
  9. package/lib/esm/cli/cli-event-payload.d.ts +53 -0
  10. package/lib/esm/cli/cli-event-payload.js +32 -0
  11. package/lib/esm/cli/index.d.ts +2 -0
  12. package/lib/esm/cli/index.js +167 -0
  13. package/lib/esm/cli/utils/create-server.d.ts +20 -0
  14. package/lib/esm/cli/utils/create-server.js +70 -0
  15. package/lib/esm/flow/__samples__/credentials-job.d.ts +1 -0
  16. package/lib/esm/flow/__samples__/credentials-job.js +9 -0
  17. package/lib/esm/flow/__samples__/only-app.d.ts +1 -0
  18. package/lib/esm/flow/__samples__/only-app.js +4 -0
  19. package/lib/esm/flow/__samples__/parent-child-jobs.d.ts +1 -0
  20. package/lib/esm/flow/__samples__/parent-child-jobs.js +10 -0
  21. package/lib/esm/flow/__samples__/parent-with-subjobs.d.ts +1 -0
  22. package/lib/esm/flow/__samples__/parent-with-subjobs.js +9 -0
  23. package/lib/esm/flow/__samples__/wait-job.d.ts +1 -0
  24. package/lib/esm/flow/__samples__/wait-job.js +6 -0
  25. package/lib/esm/flow/__samples__/zod-invalid-job.d.ts +1 -0
  26. package/lib/esm/flow/__samples__/zod-invalid-job.js +4 -0
  27. package/lib/esm/flow/__samples__/zod-validation-job.d.ts +1 -0
  28. package/lib/esm/flow/__samples__/zod-validation-job.js +6 -0
  29. package/lib/esm/flow/configs/env-configs.d.ts +37 -0
  30. package/lib/esm/flow/configs/env-configs.js +59 -0
  31. package/lib/esm/flow/constants/run-mode.d.ts +5 -0
  32. package/lib/esm/flow/constants/run-mode.js +6 -0
  33. package/lib/esm/flow/context-reflects/cli-http-reflect.d.ts +9 -0
  34. package/lib/esm/flow/context-reflects/cli-http-reflect.js +55 -0
  35. package/lib/esm/flow/context-reflects/dsn-reflect.d.ts +29 -0
  36. package/lib/esm/flow/context-reflects/dsn-reflect.js +181 -0
  37. package/lib/esm/flow/context-reflects/file-context-reflect.d.ts +22 -0
  38. package/lib/esm/flow/context-reflects/file-context-reflect.js +47 -0
  39. package/lib/esm/flow/context.d.ts +43 -0
  40. package/lib/esm/flow/context.js +104 -0
  41. package/lib/esm/flow/current.d.ts +4 -0
  42. package/lib/esm/flow/current.js +1 -0
  43. package/lib/esm/flow/dto/cb-app.d.ts +2 -0
  44. package/lib/esm/flow/dto/cb-app.js +1 -0
  45. package/lib/esm/flow/dto/job-id.d.ts +1 -0
  46. package/lib/esm/flow/dto/job-id.js +1 -0
  47. package/lib/esm/flow/dto/job.d.ts +18 -0
  48. package/lib/esm/flow/dto/job.js +9 -0
  49. package/lib/esm/flow/dto/manifest.d.ts +5 -0
  50. package/lib/esm/flow/dto/manifest.js +1 -0
  51. package/lib/esm/flow/index.d.ts +26 -0
  52. package/lib/esm/flow/index.js +46 -0
  53. package/lib/esm/flow/loader.d.ts +1 -0
  54. package/lib/esm/flow/loader.js +227 -0
  55. package/lib/esm/flow/util/json-rpc-transporter.d.ts +80 -0
  56. package/lib/esm/flow/util/json-rpc-transporter.js +175 -0
  57. package/lib/esm/flow/utils/bytes.d.ts +29 -0
  58. package/lib/esm/flow/utils/bytes.js +32 -0
  59. package/lib/esm/flow/utils/event-manager.d.ts +11 -0
  60. package/lib/esm/flow/utils/event-manager.js +41 -0
  61. package/lib/esm/flow/utils/file.d.ts +17 -0
  62. package/lib/esm/flow/utils/file.js +52 -0
  63. package/lib/esm/flow/utils/observable-set.d.ts +9 -0
  64. package/lib/esm/flow/utils/observable-set.js +31 -0
  65. package/lib/esm/flow/utils/queue.d.ts +32 -0
  66. package/lib/esm/flow/utils/queue.js +86 -0
  67. package/lib/esm/flow/worker_ctx.d.ts +5 -0
  68. package/lib/esm/flow/worker_ctx.js +2 -0
  69. package/lib/esm/http/index.d.ts +32 -0
  70. package/lib/esm/http/index.js +54 -0
  71. package/lib/esm/telemetry/metrics/gateway.d.ts +2 -0
  72. package/lib/esm/telemetry/metrics/gateway.js +6 -0
  73. package/lib/esm/telemetry/metrics/job-gauges.d.ts +13 -0
  74. package/lib/esm/telemetry/metrics/job-gauges.js +39 -0
  75. package/lib/esm/telemetry/metrics/job-store.d.ts +3 -0
  76. package/lib/esm/telemetry/metrics/job-store.js +31 -0
  77. package/lib/esm/telemetry/metrics/push.d.ts +1 -0
  78. package/lib/esm/telemetry/metrics/push.js +4 -0
  79. package/lib/esm/telemetry/metrics/registry.d.ts +2 -0
  80. package/lib/esm/telemetry/metrics/registry.js +2 -0
  81. package/lib/esm/telemetry/metrics/telemetry-url.d.ts +1 -0
  82. package/lib/esm/telemetry/metrics/telemetry-url.js +1 -0
  83. package/package.json +55 -0
@@ -0,0 +1,26 @@
1
+ import { Job } from "./dto/job.js";
2
+ import { z } from "zod";
3
+ type Def = z.ZodTypeAny;
4
+ type InferPayload<T extends Record<string, Def>> = {
5
+ [K in keyof T]: T[K] extends z.ZodType<any> ? z.infer<T[K]> : never;
6
+ };
7
+ export type CredentialDef = {
8
+ name: string;
9
+ };
10
+ export declare function credential(name: string): CredentialDef;
11
+ type AppOptions<T extends Record<string, Def>> = {
12
+ input: T;
13
+ credentials?: CredentialDef[];
14
+ maxRetries?: number;
15
+ handler: (params: InferPayload<T>, jobCtx: Job) => Promise<any>;
16
+ };
17
+ export declare function app(appName: string, cb: (params: {}, jobCtx: Job) => Promise<any>): (jobId: string, options?: {
18
+ maxRetries?: number;
19
+ }) => Job | undefined;
20
+ export declare function app<T extends Record<string, Def>>(appName: string, input: T, cb: (params: InferPayload<T>, jobCtx: Job) => Promise<any>): (jobId: string, params: InferPayload<T>, options?: {
21
+ maxRetries?: number;
22
+ }) => Job | undefined;
23
+ export declare function app<T extends Record<string, Def>>(appName: string, options: AppOptions<T>): (jobId: string, params: InferPayload<T>, options?: {
24
+ maxRetries?: number;
25
+ }) => Job | undefined;
26
+ export {};
@@ -0,0 +1,46 @@
1
+ import { currentContext } from "./current.js";
2
+ import { processingJobContext } from "./worker_ctx.js";
3
+ import { z } from "zod";
4
+ export function credential(name) {
5
+ return { name };
6
+ }
7
+ export function app(appName, inputOrCbOrOptions, cb) {
8
+ let input;
9
+ let handler;
10
+ let credentials;
11
+ let defaultMaxRetries;
12
+ if (typeof inputOrCbOrOptions === "function") {
13
+ input = {};
14
+ handler = inputOrCbOrOptions;
15
+ }
16
+ else if ("handler" in inputOrCbOrOptions) {
17
+ const opts = inputOrCbOrOptions;
18
+ input = opts.input;
19
+ handler = opts.handler;
20
+ credentials = opts.credentials;
21
+ defaultMaxRetries = opts.maxRetries;
22
+ }
23
+ else {
24
+ input = inputOrCbOrOptions;
25
+ handler = cb;
26
+ }
27
+ currentContext.current?.apps.register(appName, input, handler, credentials);
28
+ return (jobId, params = {}, options) => {
29
+ z.object(input).parse(params);
30
+ const maxRetries = options?.maxRetries ?? defaultMaxRetries;
31
+ const job = {
32
+ jobId,
33
+ appId: appName,
34
+ credentials: credentials?.map((cred) => ({ name: cred.name })) ?? [],
35
+ parentJobId: processingJobContext.getStore()?.currentJobId ?? null,
36
+ startedAt: null,
37
+ completedAt: null,
38
+ input: params,
39
+ output: null,
40
+ workerId: null,
41
+ maxRetries,
42
+ };
43
+ currentContext.current?.jobs.load(job);
44
+ return job;
45
+ };
46
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,227 @@
1
+ import { currentContext } from "./current.js";
2
+ import { processingJobContext } from "./worker_ctx.js";
3
+ import { EnvConfigs } from "./configs/env-configs.js";
4
+ import { FileContextReflect } from "./context-reflects/file-context-reflect.js";
5
+ import { CliHttpReflect } from "./context-reflects/cli-http-reflect.js";
6
+ import { DsnReflect } from "./context-reflects/dsn-reflect.js";
7
+ import { Queue } from "./utils/queue.js";
8
+ import { ulid } from "ulid";
9
+ import { RunMode } from "./constants/run-mode.js";
10
+ import cluster from "node:cluster";
11
+ import { JSONRPCTransporter, isJSONRPCRequest, isJSONRPCResponse, isJSONRPCNotification, EVENT_NAME, } from "./util/json-rpc-transporter.js";
12
+ const configs = await EnvConfigs.configs();
13
+ const { context } = await FileContextReflect.load(configs.contextPath, {
14
+ disabled: configs.disableContextFile,
15
+ });
16
+ if (configs.disableLoadJob) {
17
+ context.configs.enableLoadJob = false;
18
+ }
19
+ currentContext.current = context;
20
+ if (configs.cliInspectorUrl !== null) {
21
+ CliHttpReflect.attach(context, configs.cliInspectorUrl);
22
+ }
23
+ if (configs.dsn !== null) {
24
+ DsnReflect.attach(context, { dsn: configs.dsn });
25
+ }
26
+ const jobQueue = new Queue();
27
+ const enqueuePendingJobs = () => {
28
+ for (const job of Object.values(context.manifest.jobs)) {
29
+ const jobIsPending = job.completedAt === null || job.completedAt === undefined;
30
+ if (jobIsPending) {
31
+ jobQueue.enqueue(job);
32
+ }
33
+ }
34
+ };
35
+ jobQueue.subscribe(async (job) => {
36
+ await processingJobContext.run({ processingId: ulid(), currentJobId: job.jobId }, async () => {
37
+ try {
38
+ context.jobs.start(job, new Date());
39
+ const app = await context.apps.get(job.appId);
40
+ if (!app) {
41
+ throw new Error(`App with id ${job.appId} not found for job ${job.jobId}`);
42
+ }
43
+ const output = await app.cb(job.input, job);
44
+ context.jobs.done(job.jobId, output, new Date());
45
+ }
46
+ catch (err) {
47
+ context.jobs.error(job.jobId, err instanceof Error ? err : new Error(String(err)));
48
+ }
49
+ });
50
+ });
51
+ const setupInProcessLoader = async () => {
52
+ context.events.addEventListener("job_loaded", (jobId) => {
53
+ const job = context.jobs.get(jobId);
54
+ if (job && !job.completedAt) {
55
+ jobQueue.enqueue(job);
56
+ }
57
+ });
58
+ enqueuePendingJobs();
59
+ };
60
+ const setupClusterPrimaryLoader = async () => {
61
+ console.log("Primary process started");
62
+ const fork = async (options) => {
63
+ const online = Promise.withResolvers();
64
+ const workerReady = Promise.withResolvers();
65
+ const worker = cluster
66
+ .fork({
67
+ ...process.env,
68
+ PIKA_RUN_MODE: RunMode.CLUSTER_WORKER,
69
+ PIKA_CONTEXT_PATH: undefined, // Workers should not read the context file directly
70
+ PIKA_DISABLE_CONTEXT_FILE: "true", // Ensure workers do not attempt to load context file
71
+ })
72
+ .on("online", () => {
73
+ online.resolve(true);
74
+ });
75
+ const workerTransporter = new JSONRPCTransporter({
76
+ connect: (events) => {
77
+ worker.on("message", (msg) => {
78
+ if (isJSONRPCRequest(msg))
79
+ events.emit(EVENT_NAME.REQUEST_TRANSPORTER_TO_CONNECTION, msg);
80
+ else if (isJSONRPCResponse(msg))
81
+ events.emit(EVENT_NAME.RESPONSE_TRANSPORTER_TO_CONNECTION, msg);
82
+ else if (isJSONRPCNotification(msg))
83
+ events.emit(EVENT_NAME.NOTIFICATION_TRANSPORTER_TO_CONNECTION, msg);
84
+ });
85
+ events.addEventListener(EVENT_NAME.REQUEST_CONNECTION_TO_TRANSPORTER, (req) => worker.send(req));
86
+ events.addEventListener(EVENT_NAME.RESPONSE_CONNECTION_TO_TRANSPORTER, (res) => worker.send(res));
87
+ events.addEventListener(EVENT_NAME.NOTIFICATION_CONNECTION_TO_TRANSPORTER, (notif) => worker.send(notif));
88
+ },
89
+ onRequest: JSONRPCTransporter.router({
90
+ "worker.ready": async () => {
91
+ workerReady.resolve(true);
92
+ return true;
93
+ },
94
+ "job.create": async (params) => {
95
+ const { job } = params;
96
+ context.manifest.jobs[job.jobId] ??= job;
97
+ context.events.emit("job_loaded", job.jobId);
98
+ context.events.emit("manifest_updated");
99
+ return true;
100
+ },
101
+ }),
102
+ onNotification: JSONRPCTransporter.notificationRouter({}),
103
+ });
104
+ await online.promise;
105
+ await workerReady.promise;
106
+ options.onStart();
107
+ const response = await workerTransporter.request({
108
+ jsonrpc: "2.0",
109
+ id: ulid(),
110
+ method: "run.job",
111
+ params: { job: options.job },
112
+ });
113
+ if (response.error) {
114
+ options.onError(new Error(response.error.message));
115
+ }
116
+ else {
117
+ const result = response.result;
118
+ if (result.ok) {
119
+ options.onDone(result.output);
120
+ }
121
+ else {
122
+ options.onError(result.error instanceof Error
123
+ ? result.error
124
+ : new Error(String(result.error)));
125
+ }
126
+ }
127
+ await workerTransporter.request({
128
+ jsonrpc: "2.0",
129
+ id: ulid(),
130
+ method: "worker.end",
131
+ params: {},
132
+ });
133
+ worker.kill();
134
+ };
135
+ context.events.addEventListener("job_loaded", async (jobId) => {
136
+ const job = context.jobs.get(jobId);
137
+ if (job && !job.completedAt) {
138
+ await fork({
139
+ job: job,
140
+ onStart: () => {
141
+ context.jobs.start(job.jobId, new Date());
142
+ },
143
+ onDone: (output) => {
144
+ context.jobs.done(job.jobId, output, new Date());
145
+ },
146
+ onError: (err) => {
147
+ context.jobs.error(job.jobId, err);
148
+ },
149
+ });
150
+ }
151
+ });
152
+ enqueuePendingJobs();
153
+ };
154
+ const setupClusterWorkerLoader = async () => {
155
+ let onProcessMessage;
156
+ const processTransporter = new JSONRPCTransporter({
157
+ connect: (events) => {
158
+ onProcessMessage = (msg) => {
159
+ if (isJSONRPCRequest(msg))
160
+ events.emit(EVENT_NAME.REQUEST_TRANSPORTER_TO_CONNECTION, msg);
161
+ else if (isJSONRPCResponse(msg))
162
+ events.emit(EVENT_NAME.RESPONSE_TRANSPORTER_TO_CONNECTION, msg);
163
+ else if (isJSONRPCNotification(msg))
164
+ events.emit(EVENT_NAME.NOTIFICATION_TRANSPORTER_TO_CONNECTION, msg);
165
+ };
166
+ process.on("message", onProcessMessage);
167
+ events.addEventListener(EVENT_NAME.REQUEST_CONNECTION_TO_TRANSPORTER, (req) => process.send?.(req));
168
+ events.addEventListener(EVENT_NAME.RESPONSE_CONNECTION_TO_TRANSPORTER, (res) => process.send?.(res));
169
+ events.addEventListener(EVENT_NAME.NOTIFICATION_CONNECTION_TO_TRANSPORTER, (notif) => process.send?.(notif));
170
+ },
171
+ onRequest: JSONRPCTransporter.router({
172
+ "run.job": async (params) => {
173
+ const { promise, resolve } = Promise.withResolvers();
174
+ context.events.addEventListener("job_done", function onJobDone(jobId) {
175
+ if (jobId === params.job.jobId) {
176
+ context.events.removeEventListener("job_done", onJobDone);
177
+ resolve({
178
+ ok: true,
179
+ output: context.jobs.get(jobId)?.output ?? {},
180
+ error: null,
181
+ });
182
+ }
183
+ });
184
+ context.events.addEventListener("job_error", function onJobError(jobId, err) {
185
+ if (jobId === params.job.jobId) {
186
+ context.events.removeEventListener("job_error", onJobError);
187
+ resolve({ ok: false, output: null, error: err });
188
+ }
189
+ });
190
+ jobQueue.enqueue(params.job);
191
+ return await promise;
192
+ },
193
+ "worker.end": async () => {
194
+ if (onProcessMessage)
195
+ process.off("message", onProcessMessage);
196
+ context.events.removeAllListeners();
197
+ },
198
+ }),
199
+ onNotification: JSONRPCTransporter.notificationRouter({}),
200
+ });
201
+ // Forward new jobs created inside callbacks to the primary
202
+ context.events.addEventListener("job_created", async (jobId) => {
203
+ const job = context.jobs.get(jobId);
204
+ if (job) {
205
+ await processTransporter.request({
206
+ jsonrpc: "2.0",
207
+ id: ulid(),
208
+ method: "job.create",
209
+ params: { job },
210
+ });
211
+ }
212
+ });
213
+ // Notify primary that this worker is ready to receive jobs
214
+ await processTransporter.request({
215
+ jsonrpc: "2.0",
216
+ id: ulid(),
217
+ method: "worker.ready",
218
+ params: { workerId: cluster.worker?.id },
219
+ });
220
+ };
221
+ const setups = {
222
+ [RunMode.IN_PROCESS]: setupInProcessLoader,
223
+ [RunMode.CLUSTER_PRIMARY]: setupClusterPrimaryLoader,
224
+ [RunMode.CLUSTER_WORKER]: setupClusterWorkerLoader,
225
+ };
226
+ const setup = setups[configs.runMode];
227
+ await setup?.();
@@ -0,0 +1,80 @@
1
+ import { EventManager } from "../utils/event-manager.js";
2
+ export type JSONRPCRequest = {
3
+ jsonrpc: "2.0";
4
+ id: string;
5
+ method: string;
6
+ params: any;
7
+ };
8
+ export type JSONRPCNotification = {
9
+ jsonrpc: "2.0";
10
+ method: string;
11
+ params: any;
12
+ };
13
+ export type JSONRPCResponse = {
14
+ jsonrpc: "2.0";
15
+ id: string;
16
+ result?: any;
17
+ error?: {
18
+ code: number;
19
+ message: string;
20
+ data?: any;
21
+ };
22
+ };
23
+ export declare const isJSONRPCResponse: (value: unknown) => value is JSONRPCResponse;
24
+ export declare const isJSONRPCRequest: (value: unknown) => value is JSONRPCRequest;
25
+ export declare const isJSONRPCNotification: (value: unknown) => value is JSONRPCNotification;
26
+ export declare const notfoundJSONRPCResponse: (id: string) => JSONRPCResponse;
27
+ export declare const internalErrorJSONRPCResponse: (id: string, error: Error) => JSONRPCResponse;
28
+ export declare const createJSONRPCResponseRouter: (routes: Record<string, (params: any) => Promise<any>>) => (request: JSONRPCRequest) => Promise<JSONRPCResponse>;
29
+ export declare const createJSONRPCNotificationRouter: (routes: Record<string, (params: any) => Promise<any>>) => (request: JSONRPCRequest) => Promise<void>;
30
+ declare class Logger {
31
+ private enabled;
32
+ enable(): this;
33
+ disable(): this;
34
+ log(message: string): void;
35
+ }
36
+ export declare enum EVENT_NAME {
37
+ REQUEST_CONNECTION_TO_TRANSPORTER = "REQUEST_CONNECTION_TO_TRANSPORTER",
38
+ REQUEST_TRANSPORTER_TO_CONNECTION = "REQUEST_TRANSPORTER_TO_CONNECTION",
39
+ RESPONSE_CONNECTION_TO_TRANSPORTER = "RESPONSE_CONNECTION_TO_TRANSPORTER",
40
+ RESPONSE_TRANSPORTER_TO_CONNECTION = "RESPONSE_TRANSPORTER_TO_CONNECTION",
41
+ NOTIFICATION_CONNECTION_TO_TRANSPORTER = "NOTIFICATION_CONNECTION_TO_TRANSPORTER",
42
+ NOTIFICATION_TRANSPORTER_TO_CONNECTION = "NOTIFICATION_TRANSPORTER_TO_CONNECTION"
43
+ }
44
+ export type JSONRPCTransporterEvents = {
45
+ [EVENT_NAME.REQUEST_CONNECTION_TO_TRANSPORTER]: (request: JSONRPCRequest) => void;
46
+ [EVENT_NAME.RESPONSE_CONNECTION_TO_TRANSPORTER]: (response: JSONRPCResponse) => void;
47
+ [EVENT_NAME.REQUEST_TRANSPORTER_TO_CONNECTION]: (request: JSONRPCRequest) => void;
48
+ [EVENT_NAME.RESPONSE_TRANSPORTER_TO_CONNECTION]: (response: JSONRPCResponse) => void;
49
+ [EVENT_NAME.NOTIFICATION_CONNECTION_TO_TRANSPORTER]: (notification: JSONRPCNotification) => void;
50
+ [EVENT_NAME.NOTIFICATION_TRANSPORTER_TO_CONNECTION]: (notification: JSONRPCNotification) => void;
51
+ };
52
+ export declare class JSONRPCTransporter {
53
+ events: EventManager<JSONRPCTransporterEvents>;
54
+ constructor(options?: {
55
+ connect?: (events: EventManager<JSONRPCTransporterEvents>) => void;
56
+ onRequest?: (request: JSONRPCRequest) => Promise<JSONRPCResponse>;
57
+ onNotification?: (notification: JSONRPCNotification) => Promise<void>;
58
+ });
59
+ notification(request: JSONRPCNotification): Promise<void>;
60
+ request(request: JSONRPCRequest): Promise<JSONRPCResponse>;
61
+ static router: (routes: Record<string, (params: any) => Promise<any>>) => (request: JSONRPCRequest) => Promise<JSONRPCResponse>;
62
+ static notificationRouter: (routes: Record<string, (params: any) => Promise<any>>) => (request: JSONRPCRequest) => Promise<void>;
63
+ }
64
+ /** @deprecated Use JSONRPCTransporter instead */
65
+ export declare class deprecated_JSONRPCTransporter {
66
+ private channel;
67
+ private requestHandler?;
68
+ private notificationHandler?;
69
+ logger: Logger;
70
+ constructor(channel: {
71
+ removeListener: (event: "message", listener: (message: unknown) => void) => void;
72
+ addListener: (event: "message", listener: (message: unknown) => void) => void;
73
+ send?: (message: unknown, cb?: (err: Error | null) => void) => boolean;
74
+ }, requestHandler?: ((request: JSONRPCRequest) => Promise<JSONRPCResponse>) | undefined, notificationHandler?: ((notification: JSONRPCNotification) => Promise<void>) | undefined);
75
+ send(message: unknown): Promise<void>;
76
+ sendNotification(request: JSONRPCNotification): Promise<void>;
77
+ sendRequest(request: JSONRPCRequest): Promise<JSONRPCResponse>;
78
+ sendResponse(response: JSONRPCResponse): void;
79
+ }
80
+ export {};
@@ -0,0 +1,175 @@
1
+ import { EventManager } from "../utils/event-manager.js";
2
+ export const isJSONRPCResponse = (value) => typeof value === "object" &&
3
+ value !== null &&
4
+ "jsonrpc" in value &&
5
+ "id" in value;
6
+ export const isJSONRPCRequest = (value) => typeof value === "object" &&
7
+ value !== null &&
8
+ "jsonrpc" in value &&
9
+ "id" in value &&
10
+ typeof value.id === "string" &&
11
+ "method" in value;
12
+ export const isJSONRPCNotification = (value) => typeof value === "object" &&
13
+ value !== null &&
14
+ "jsonrpc" in value &&
15
+ "method" in value &&
16
+ !("id" in value);
17
+ export const notfoundJSONRPCResponse = (id) => ({
18
+ jsonrpc: "2.0",
19
+ id: id,
20
+ error: { code: -32601, message: "Method not found" },
21
+ });
22
+ export const internalErrorJSONRPCResponse = (id, error) => ({
23
+ jsonrpc: "2.0",
24
+ id: id,
25
+ error: {
26
+ code: -32603,
27
+ message: "Internal error",
28
+ data: error instanceof Error
29
+ ? {
30
+ name: error.name,
31
+ message: error.message,
32
+ stack: error.stack,
33
+ }
34
+ : { value: error },
35
+ },
36
+ });
37
+ export const createJSONRPCResponseRouter = (routes) => async (request) => {
38
+ const handler = routes[request.method];
39
+ if (!handler)
40
+ return notfoundJSONRPCResponse(request.id);
41
+ try {
42
+ return {
43
+ jsonrpc: "2.0",
44
+ id: request.id,
45
+ result: await handler(request.params),
46
+ };
47
+ }
48
+ catch (error) {
49
+ return internalErrorJSONRPCResponse(request.id, error instanceof Error ? error : new Error(String(error)));
50
+ }
51
+ };
52
+ export const createJSONRPCNotificationRouter = (routes) => async (request) => {
53
+ const handler = routes[request.method];
54
+ if (!handler)
55
+ return;
56
+ try {
57
+ await handler(request.params);
58
+ }
59
+ catch (error) {
60
+ // Notifications do not expect a response, so we just log the error
61
+ console.error("Error handling JSON-RPC notification:", error instanceof Error ? error : new Error(String(error)));
62
+ }
63
+ };
64
+ class Logger {
65
+ enabled;
66
+ enable() {
67
+ this.enabled = true;
68
+ return this;
69
+ }
70
+ disable() {
71
+ this.enabled = false;
72
+ return this;
73
+ }
74
+ log(message) {
75
+ if (this.enabled) {
76
+ console.log(message);
77
+ }
78
+ }
79
+ }
80
+ export var EVENT_NAME;
81
+ (function (EVENT_NAME) {
82
+ EVENT_NAME["REQUEST_CONNECTION_TO_TRANSPORTER"] = "REQUEST_CONNECTION_TO_TRANSPORTER";
83
+ EVENT_NAME["REQUEST_TRANSPORTER_TO_CONNECTION"] = "REQUEST_TRANSPORTER_TO_CONNECTION";
84
+ EVENT_NAME["RESPONSE_CONNECTION_TO_TRANSPORTER"] = "RESPONSE_CONNECTION_TO_TRANSPORTER";
85
+ EVENT_NAME["RESPONSE_TRANSPORTER_TO_CONNECTION"] = "RESPONSE_TRANSPORTER_TO_CONNECTION";
86
+ EVENT_NAME["NOTIFICATION_CONNECTION_TO_TRANSPORTER"] = "NOTIFICATION_CONNECTION_TO_TRANSPORTER";
87
+ EVENT_NAME["NOTIFICATION_TRANSPORTER_TO_CONNECTION"] = "NOTIFICATION_TRANSPORTER_TO_CONNECTION";
88
+ })(EVENT_NAME || (EVENT_NAME = {}));
89
+ export class JSONRPCTransporter {
90
+ events = new EventManager();
91
+ constructor(options) {
92
+ if (options?.onRequest) {
93
+ this.events.addEventListener(EVENT_NAME.REQUEST_TRANSPORTER_TO_CONNECTION, async (request) => {
94
+ const response = await options.onRequest(request);
95
+ this.events.emit(EVENT_NAME.RESPONSE_CONNECTION_TO_TRANSPORTER, response);
96
+ });
97
+ }
98
+ if (options?.onNotification) {
99
+ this.events.addEventListener(EVENT_NAME.NOTIFICATION_TRANSPORTER_TO_CONNECTION, async (notification) => {
100
+ await options.onNotification(notification);
101
+ });
102
+ }
103
+ options?.connect?.(this.events);
104
+ }
105
+ async notification(request) {
106
+ this.events.emit(EVENT_NAME.NOTIFICATION_CONNECTION_TO_TRANSPORTER, request);
107
+ }
108
+ async request(request) {
109
+ const responsePromise = Promise.withResolvers();
110
+ const unsub = this.events.addEventListener(EVENT_NAME.RESPONSE_TRANSPORTER_TO_CONNECTION, (response) => {
111
+ if (response.id === request.id) {
112
+ responsePromise.resolve(response);
113
+ unsub();
114
+ }
115
+ });
116
+ this.events.emit(EVENT_NAME.REQUEST_CONNECTION_TO_TRANSPORTER, request);
117
+ return await responsePromise.promise;
118
+ }
119
+ static router = createJSONRPCResponseRouter;
120
+ static notificationRouter = createJSONRPCNotificationRouter;
121
+ }
122
+ /** @deprecated Use JSONRPCTransporter instead */
123
+ export class deprecated_JSONRPCTransporter {
124
+ channel;
125
+ requestHandler;
126
+ notificationHandler;
127
+ logger = new Logger();
128
+ constructor(channel, requestHandler, notificationHandler) {
129
+ this.channel = channel;
130
+ this.requestHandler = requestHandler;
131
+ this.notificationHandler = notificationHandler;
132
+ this.channel.addListener("message", async (message) => {
133
+ this.logger.log(`Received message on channel: ${JSON.stringify(message)}`);
134
+ if (isJSONRPCRequest(message) && this.requestHandler) {
135
+ this.logger.log(`Received request: ${JSON.stringify(message)}`);
136
+ const response = await this.requestHandler(message);
137
+ this.sendResponse(response);
138
+ }
139
+ if (isJSONRPCNotification(message) && this.notificationHandler) {
140
+ this.logger.log(`Received notification: ${JSON.stringify(message)}`);
141
+ await this.notificationHandler(message);
142
+ }
143
+ });
144
+ }
145
+ async send(message) {
146
+ this.logger.log(`Sending message: ${JSON.stringify(message)}`);
147
+ await new Promise((resolve, reject) => {
148
+ return this.channel.send?.(message, (err) => err ? reject(err) : resolve(null));
149
+ });
150
+ }
151
+ async sendNotification(request) {
152
+ this.logger.log(`Sending notification: ${JSON.stringify(request)}`);
153
+ await new Promise((resolve, reject) => {
154
+ return this.channel.send?.(request, (err) => err ? reject(err) : resolve(null));
155
+ });
156
+ }
157
+ async sendRequest(request) {
158
+ const response = Promise.withResolvers();
159
+ const listener = (message) => {
160
+ this.logger.log(`Received message on channel: ${JSON.stringify(message)}`);
161
+ if (isJSONRPCResponse(message) && message.id === request.id) {
162
+ response.resolve(message);
163
+ this.channel.removeListener("message", listener);
164
+ }
165
+ };
166
+ this.channel.addListener("message", listener);
167
+ this.logger.log(`Sending request: ${JSON.stringify(request)}`);
168
+ await new Promise((resolve, reject) => this.channel.send?.(request, (err) => err ? reject(err) : resolve(null)));
169
+ return response.promise;
170
+ }
171
+ sendResponse(response) {
172
+ this.logger.log(`Sending response: ${JSON.stringify(response)}`);
173
+ this.channel.send?.(response);
174
+ }
175
+ }
@@ -0,0 +1,29 @@
1
+ declare const UNIT_MAP: readonly [{
2
+ readonly units: readonly ["b", "bytes"];
3
+ readonly bytes: 1n;
4
+ }, {
5
+ readonly units: readonly ["kb", "kilobytes"];
6
+ readonly bytes: 1000n;
7
+ }, {
8
+ readonly units: readonly ["mb", "megabytes"];
9
+ readonly bytes: 1000000n;
10
+ }, {
11
+ readonly units: readonly ["gb", "gigabytes"];
12
+ readonly bytes: 1000000000n;
13
+ }, {
14
+ readonly units: readonly ["tb", "terabytes"];
15
+ readonly bytes: 1000000000000n;
16
+ }, {
17
+ readonly units: readonly ["pb", "petabytes"];
18
+ readonly bytes: 1000000000000000n;
19
+ }];
20
+ export type BytesUnit = (typeof UNIT_MAP)[number]["units"][number];
21
+ export type BytesInput = Partial<Record<BytesUnit, bigint | number>>;
22
+ export declare class Bytes {
23
+ private readonly _bytes;
24
+ private constructor();
25
+ static from(input: BytesInput): Bytes;
26
+ total(unit?: BytesUnit): bigint;
27
+ get bytes(): bigint;
28
+ }
29
+ export {};
@@ -0,0 +1,32 @@
1
+ const UNIT_MAP = [
2
+ { units: ["b", "bytes"], bytes: 1n },
3
+ { units: ["kb", "kilobytes"], bytes: 1000n },
4
+ { units: ["mb", "megabytes"], bytes: 1000000n },
5
+ { units: ["gb", "gigabytes"], bytes: 1000000000n },
6
+ { units: ["tb", "terabytes"], bytes: 1000000000000n },
7
+ { units: ["pb", "petabytes"], bytes: 1000000000000000n },
8
+ ];
9
+ export class Bytes {
10
+ _bytes;
11
+ constructor(_bytes) {
12
+ this._bytes = _bytes;
13
+ }
14
+ static from(input) {
15
+ let total = 0n;
16
+ for (const entry of UNIT_MAP) {
17
+ for (const unit of entry.units) {
18
+ const val = input[unit];
19
+ if (val !== undefined)
20
+ total += BigInt(val) * entry.bytes;
21
+ }
22
+ }
23
+ return new Bytes(total);
24
+ }
25
+ total(unit = "b") {
26
+ const entry = UNIT_MAP.find((e) => e.units.includes(unit));
27
+ return this._bytes / (entry?.bytes ?? 1n);
28
+ }
29
+ get bytes() {
30
+ return this._bytes;
31
+ }
32
+ }
@@ -0,0 +1,11 @@
1
+ export declare class EventManager<T extends Record<string, (...args: any[]) => void>> {
2
+ private _eventListeners;
3
+ private _weakWrapper;
4
+ constructor();
5
+ addEventListener<K extends keyof T, P extends Parameters<T[K]> = Parameters<T[K]>>(event: K, listener: (...args: P) => void, options?: {
6
+ once?: boolean;
7
+ }): any;
8
+ removeEventListener<K extends keyof T, P extends Parameters<T[K]> = Parameters<T[K]>>(event: K, listener: (...args: P) => void): any;
9
+ removeAllListeners(): void;
10
+ emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>): any;
11
+ }
@@ -0,0 +1,41 @@
1
+ export class EventManager {
2
+ _eventListeners = new Map();
3
+ _weakWrapper = new WeakMap();
4
+ constructor() {
5
+ this._eventListeners = new Map();
6
+ }
7
+ addEventListener(event, listener, options) {
8
+ const once = options?.once ?? false;
9
+ const wrapper = (...args) => {
10
+ listener(...args);
11
+ if (once) {
12
+ this.removeEventListener(event, listener);
13
+ }
14
+ };
15
+ this._weakWrapper.set(listener, wrapper);
16
+ const cbs = this._eventListeners.get(event) ?? new Set();
17
+ cbs.add(wrapper);
18
+ this._eventListeners.set(event, cbs);
19
+ return () => this.removeEventListener(event, listener);
20
+ }
21
+ removeEventListener(event, listener) {
22
+ const cbs = this._eventListeners.get(event);
23
+ const wrapper = this._weakWrapper.get(listener);
24
+ if (wrapper) {
25
+ cbs?.delete(wrapper);
26
+ this._weakWrapper.delete(listener);
27
+ }
28
+ if (cbs?.size === 0) {
29
+ this._eventListeners.delete(event);
30
+ }
31
+ }
32
+ removeAllListeners() {
33
+ this._eventListeners.clear();
34
+ }
35
+ emit(event, ...args) {
36
+ const cbs = this._eventListeners.get(event);
37
+ cbs?.forEach((listener) => {
38
+ listener(...args);
39
+ });
40
+ }
41
+ }