modality-kit 0.6.5 → 0.7.0

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.
package/dist/index.js CHANGED
@@ -4267,6 +4267,174 @@ var fileEntrySchema = exports_external.object({
4267
4267
  type: exports_external.enum(["file", "directory"]).describe("Entry type"),
4268
4268
  lastModified: exports_external.number().optional().nullable().describe("Last modified timestamp in milliseconds since epoch")
4269
4269
  });
4270
+ // src/schemas/jsonrpc.ts
4271
+ var exports_jsonrpc = {};
4272
+ __export(exports_jsonrpc, {
4273
+ STANDARD_ERROR_MESSAGES: () => STANDARD_ERROR_MESSAGES,
4274
+ JSONRPC_VERSION: () => JSONRPC_VERSION,
4275
+ JSONRPCUtils: () => JSONRPCUtils,
4276
+ JSONRPCErrorCode: () => JSONRPCErrorCode
4277
+ });
4278
+ var JSONRPC_VERSION = "2.0";
4279
+ var JSONRPCErrorCode;
4280
+ ((JSONRPCErrorCode2) => {
4281
+ JSONRPCErrorCode2[JSONRPCErrorCode2["PARSE_ERROR"] = -32700] = "PARSE_ERROR";
4282
+ JSONRPCErrorCode2[JSONRPCErrorCode2["INVALID_REQUEST"] = -32600] = "INVALID_REQUEST";
4283
+ JSONRPCErrorCode2[JSONRPCErrorCode2["METHOD_NOT_FOUND"] = -32601] = "METHOD_NOT_FOUND";
4284
+ JSONRPCErrorCode2[JSONRPCErrorCode2["INVALID_PARAMS"] = -32602] = "INVALID_PARAMS";
4285
+ JSONRPCErrorCode2[JSONRPCErrorCode2["INTERNAL_ERROR"] = -32603] = "INTERNAL_ERROR";
4286
+ JSONRPCErrorCode2[JSONRPCErrorCode2["SERVER_ERROR_START"] = -32099] = "SERVER_ERROR_START";
4287
+ JSONRPCErrorCode2[JSONRPCErrorCode2["SERVER_ERROR_END"] = -32000] = "SERVER_ERROR_END";
4288
+ JSONRPCErrorCode2[JSONRPCErrorCode2["TIMEOUT_ERROR"] = -32001] = "TIMEOUT_ERROR";
4289
+ JSONRPCErrorCode2[JSONRPCErrorCode2["CONNECTION_ERROR"] = -32002] = "CONNECTION_ERROR";
4290
+ JSONRPCErrorCode2[JSONRPCErrorCode2["AUTHENTICATION_ERROR"] = -32003] = "AUTHENTICATION_ERROR";
4291
+ JSONRPCErrorCode2[JSONRPCErrorCode2["AUTHORIZATION_ERROR"] = -32004] = "AUTHORIZATION_ERROR";
4292
+ JSONRPCErrorCode2[JSONRPCErrorCode2["RATE_LIMIT_ERROR"] = -32005] = "RATE_LIMIT_ERROR";
4293
+ JSONRPCErrorCode2[JSONRPCErrorCode2["VALIDATION_ERROR"] = -32006] = "VALIDATION_ERROR";
4294
+ })(JSONRPCErrorCode ||= {});
4295
+
4296
+ class JSONRPCUtils {
4297
+ static validateMessage(message) {
4298
+ if (Array.isArray(message)) {
4299
+ if (message.length === 0) {
4300
+ return {
4301
+ valid: false,
4302
+ error: {
4303
+ code: -32600 /* INVALID_REQUEST */,
4304
+ message: "Invalid request: batch array cannot be empty"
4305
+ }
4306
+ };
4307
+ }
4308
+ for (const item of message) {
4309
+ const itemValidation = this.validateSingleMessage(item);
4310
+ if (!itemValidation.valid) {
4311
+ return itemValidation;
4312
+ }
4313
+ }
4314
+ return {
4315
+ valid: true,
4316
+ messageType: "batch"
4317
+ };
4318
+ }
4319
+ return this.validateSingleMessage(message);
4320
+ }
4321
+ static validateSingleMessage(message) {
4322
+ if (!message || typeof message !== "object") {
4323
+ return {
4324
+ valid: false,
4325
+ error: {
4326
+ code: -32600 /* INVALID_REQUEST */,
4327
+ message: "Invalid request: message must be an object"
4328
+ }
4329
+ };
4330
+ }
4331
+ if (message.jsonrpc !== JSONRPC_VERSION) {
4332
+ return {
4333
+ valid: false,
4334
+ error: {
4335
+ code: -32600 /* INVALID_REQUEST */,
4336
+ message: `Invalid request: expect ${JSONRPC_VERSION}, received ${message.jsonrpc}`
4337
+ }
4338
+ };
4339
+ }
4340
+ if (!message.method || typeof message.method !== "string") {
4341
+ if ("result" in message || "error" in message) {
4342
+ return {
4343
+ valid: true,
4344
+ messageType: "response"
4345
+ };
4346
+ }
4347
+ return {
4348
+ valid: false,
4349
+ error: {
4350
+ code: -32600 /* INVALID_REQUEST */,
4351
+ message: "Invalid request: method must be a string"
4352
+ }
4353
+ };
4354
+ }
4355
+ const hasId = "id" in message;
4356
+ const messageType = hasId ? "request" : "notification";
4357
+ return {
4358
+ valid: true,
4359
+ messageType
4360
+ };
4361
+ }
4362
+ static createRequest(method, params, id) {
4363
+ return {
4364
+ jsonrpc: JSONRPC_VERSION,
4365
+ method,
4366
+ params,
4367
+ id: id ?? this.generateId()
4368
+ };
4369
+ }
4370
+ static createNotification(method, params) {
4371
+ return {
4372
+ jsonrpc: JSONRPC_VERSION,
4373
+ method,
4374
+ params
4375
+ };
4376
+ }
4377
+ static createSuccessResponse(result, id) {
4378
+ return {
4379
+ jsonrpc: JSONRPC_VERSION,
4380
+ result,
4381
+ id
4382
+ };
4383
+ }
4384
+ static createErrorResponse(error, id) {
4385
+ return {
4386
+ jsonrpc: JSONRPC_VERSION,
4387
+ error,
4388
+ id
4389
+ };
4390
+ }
4391
+ static createError(code, message, data) {
4392
+ return { code, message, data };
4393
+ }
4394
+ static generateId() {
4395
+ return Math.random().toString(36).substring(2) + Date.now().toString(36);
4396
+ }
4397
+ static isRequest(message) {
4398
+ return message && message.jsonrpc === JSONRPC_VERSION && typeof message.method === "string" && "id" in message;
4399
+ }
4400
+ static isNotification(message) {
4401
+ return message && message.jsonrpc === JSONRPC_VERSION && typeof message.method === "string" && !("id" in message);
4402
+ }
4403
+ static isResponse(message) {
4404
+ return message && message.jsonrpc === JSONRPC_VERSION && (("result" in message) || ("error" in message)) && "id" in message;
4405
+ }
4406
+ static isSuccessResponse(response) {
4407
+ return "result" in response;
4408
+ }
4409
+ static isErrorResponse(response) {
4410
+ return "error" in response;
4411
+ }
4412
+ static serialize(message) {
4413
+ return JSON.stringify(message);
4414
+ }
4415
+ static deserialize(data) {
4416
+ try {
4417
+ return JSON.parse(data);
4418
+ } catch {
4419
+ return null;
4420
+ }
4421
+ }
4422
+ }
4423
+ var STANDARD_ERROR_MESSAGES = {
4424
+ [-32700 /* PARSE_ERROR */]: "Parse error",
4425
+ [-32600 /* INVALID_REQUEST */]: "Invalid Request",
4426
+ [-32601 /* METHOD_NOT_FOUND */]: "Method not found",
4427
+ [-32602 /* INVALID_PARAMS */]: "Invalid params",
4428
+ [-32603 /* INTERNAL_ERROR */]: "Internal error",
4429
+ [-32099 /* SERVER_ERROR_START */]: "Server error",
4430
+ [-32000 /* SERVER_ERROR_END */]: "Server error",
4431
+ [-32001 /* TIMEOUT_ERROR */]: "Request timeout",
4432
+ [-32002 /* CONNECTION_ERROR */]: "Connection error",
4433
+ [-32003 /* AUTHENTICATION_ERROR */]: "Authentication required",
4434
+ [-32004 /* AUTHORIZATION_ERROR */]: "Authorization failed",
4435
+ [-32005 /* RATE_LIMIT_ERROR */]: "Rate limit exceeded",
4436
+ [-32006 /* VALIDATION_ERROR */]: "Validation error"
4437
+ };
4270
4438
  // src/schemas/schemas_empty.ts
4271
4439
  var emptySchema = exports_external.object({}).describe("No parameters required");
4272
4440
  // src/util_version.ts
@@ -4989,6 +5157,276 @@ async function compressWithLanguageDetection(text, maxTokens = DEFAULT_CONFIG.ma
4989
5157
  compressionLevel: "moderate"
4990
5158
  });
4991
5159
  }
5160
+ // src/util_pending.ts
5161
+ var getUUID = () => crypto.randomUUID();
5162
+
5163
+ class PendingOperationsBase {
5164
+ operations = new Map;
5165
+ config;
5166
+ cleanupIntervalHandle;
5167
+ eventHandlers = {};
5168
+ constructor(config = {}, eventHandlers = {}) {
5169
+ this.config = {
5170
+ defaultTimeout: 30000,
5171
+ cleanupInterval: 1e4,
5172
+ enableAutoCleanup: true,
5173
+ generateId: getUUID,
5174
+ ...config
5175
+ };
5176
+ this.eventHandlers = eventHandlers;
5177
+ if (this.config.enableAutoCleanup) {
5178
+ this.startAutoCleanup();
5179
+ }
5180
+ }
5181
+ resolve(id, result) {
5182
+ const operation = this.operations.get(id);
5183
+ if (!operation) {
5184
+ return false;
5185
+ }
5186
+ if (operation.timeoutHandle) {
5187
+ clearTimeout(operation.timeoutHandle);
5188
+ }
5189
+ if (operation.type === "promise") {
5190
+ operation.resolve(result);
5191
+ }
5192
+ if (this.eventHandlers.onResolve) {
5193
+ this.eventHandlers.onResolve(operation, result);
5194
+ }
5195
+ this.operations.delete(id);
5196
+ return true;
5197
+ }
5198
+ reject(id, reason) {
5199
+ const operation = this.operations.get(id);
5200
+ if (!operation) {
5201
+ return false;
5202
+ }
5203
+ if (operation.timeoutHandle) {
5204
+ clearTimeout(operation.timeoutHandle);
5205
+ }
5206
+ if (operation.type === "promise") {
5207
+ operation.reject(reason);
5208
+ }
5209
+ if (this.eventHandlers.onReject) {
5210
+ this.eventHandlers.onReject(operation, reason);
5211
+ }
5212
+ this.operations.delete(id);
5213
+ return true;
5214
+ }
5215
+ get(id) {
5216
+ return this.operations.get(id);
5217
+ }
5218
+ has(id) {
5219
+ return this.operations.has(id);
5220
+ }
5221
+ remove(id) {
5222
+ const operation = this.operations.get(id);
5223
+ if (!operation) {
5224
+ return false;
5225
+ }
5226
+ if (operation.timeoutHandle) {
5227
+ clearTimeout(operation.timeoutHandle);
5228
+ }
5229
+ if (this.eventHandlers.onCleanup) {
5230
+ this.eventHandlers.onCleanup(operation);
5231
+ }
5232
+ this.operations.delete(id);
5233
+ return true;
5234
+ }
5235
+ getAll() {
5236
+ return new Map(this.operations);
5237
+ }
5238
+ getByType(type) {
5239
+ return Array.from(this.operations.values()).filter((op) => op.type === type);
5240
+ }
5241
+ clear(reason) {
5242
+ const count = this.operations.size;
5243
+ const keys = Array.from(this.operations.keys());
5244
+ for (const id of keys) {
5245
+ this.reject(id, reason || "All operations cleared");
5246
+ }
5247
+ return count;
5248
+ }
5249
+ cleanupExpired() {
5250
+ const now = Date.now();
5251
+ const expiredOperations = [];
5252
+ for (const [id, operation] of Array.from(this.operations.entries())) {
5253
+ if (operation.timeout && operation.timeout > 0) {
5254
+ const expirationTime = operation.timestamp + operation.timeout;
5255
+ if (now >= expirationTime) {
5256
+ expiredOperations.push(id);
5257
+ }
5258
+ }
5259
+ }
5260
+ for (const id of expiredOperations) {
5261
+ this.handleTimeout(this.operations.get(id));
5262
+ }
5263
+ return expiredOperations.length;
5264
+ }
5265
+ getStats() {
5266
+ const now = Date.now();
5267
+ const operations = Array.from(this.operations.values());
5268
+ const byType = {};
5269
+ let totalAge = 0;
5270
+ let expiringSoon = 0;
5271
+ let oldestTimestamp;
5272
+ for (const operation of operations) {
5273
+ byType[operation.type] = (byType[operation.type] || 0) + 1;
5274
+ const age = now - operation.timestamp;
5275
+ totalAge += age;
5276
+ if (!oldestTimestamp || operation.timestamp < oldestTimestamp) {
5277
+ oldestTimestamp = operation.timestamp;
5278
+ }
5279
+ if (operation.timeout && operation.timeout > 0) {
5280
+ const expirationTime = operation.timestamp + operation.timeout;
5281
+ const timeToExpiry = expirationTime - now;
5282
+ if (timeToExpiry <= 60000 && timeToExpiry > 0) {
5283
+ expiringSoon++;
5284
+ }
5285
+ }
5286
+ }
5287
+ return {
5288
+ total: operations.length,
5289
+ byType,
5290
+ expiringSoon,
5291
+ oldestTimestamp,
5292
+ averageAge: operations.length > 0 ? totalAge / operations.length : 0
5293
+ };
5294
+ }
5295
+ startAutoCleanup() {
5296
+ if (this.cleanupIntervalHandle) {
5297
+ return;
5298
+ }
5299
+ this.cleanupIntervalHandle = setInterval(() => {
5300
+ this.cleanupExpired();
5301
+ }, this.config.cleanupInterval);
5302
+ }
5303
+ stopAutoCleanup() {
5304
+ if (this.cleanupIntervalHandle) {
5305
+ clearInterval(this.cleanupIntervalHandle);
5306
+ this.cleanupIntervalHandle = undefined;
5307
+ }
5308
+ }
5309
+ handleTimeout({ id }) {
5310
+ const operation = this.operations.get(id);
5311
+ if (operation) {
5312
+ if (this.eventHandlers.onTimeout) {
5313
+ this.eventHandlers.onTimeout(operation);
5314
+ }
5315
+ this.reject(operation.id, new Error(`Operation timed out after ${operation.timeout}ms`));
5316
+ }
5317
+ }
5318
+ destroy(reason) {
5319
+ this.stopAutoCleanup();
5320
+ this.clear(reason || "PendingOperations destroyed");
5321
+ }
5322
+ getConfig() {
5323
+ return { ...this.config };
5324
+ }
5325
+ setEventHandlers(handlers) {
5326
+ this.eventHandlers = { ...this.eventHandlers, ...handlers };
5327
+ }
5328
+ handleAdd(options = {}) {
5329
+ const id = options.customId ?? this.config.generateId();
5330
+ const timeout = options.timeout ?? this.config.defaultTimeout;
5331
+ if (options.customId && this.operations.has(options.customId)) {
5332
+ throw new Error(`Operation with ID '${options.customId}' already exists`);
5333
+ }
5334
+ const timeoutHandle = timeout > 0 ? setTimeout(() => {
5335
+ this.handleTimeout({ id });
5336
+ }, timeout) : undefined;
5337
+ return { id, timeout, timeoutHandle };
5338
+ }
5339
+ }
5340
+
5341
+ class PromisePendingOperations extends PendingOperationsBase {
5342
+ add(data, options = {}) {
5343
+ const { id, timeout, timeoutHandle } = this.handleAdd(options);
5344
+ const promise = new Promise((resolve, reject) => {
5345
+ const operation = {
5346
+ id,
5347
+ type: "promise",
5348
+ timestamp: Date.now(),
5349
+ timeout,
5350
+ timeoutHandle,
5351
+ data,
5352
+ resolve,
5353
+ reject
5354
+ };
5355
+ this.operations.set(id, operation);
5356
+ });
5357
+ return { id, promise };
5358
+ }
5359
+ }
5360
+
5361
+ class DataPendingOperations extends PendingOperationsBase {
5362
+ add(data, options = {}) {
5363
+ const { id, timeout, timeoutHandle } = this.handleAdd(options);
5364
+ const operation = {
5365
+ id,
5366
+ type: "data",
5367
+ timestamp: Date.now(),
5368
+ timeout,
5369
+ timeoutHandle,
5370
+ data
5371
+ };
5372
+ this.operations.set(id, operation);
5373
+ return { id };
5374
+ }
5375
+ }
5376
+ function createPromisePendingOperations(config, eventHandlers) {
5377
+ return new PromisePendingOperations({
5378
+ defaultTimeout: 30000,
5379
+ cleanupInterval: 30000,
5380
+ enableAutoCleanup: true,
5381
+ ...config
5382
+ }, eventHandlers);
5383
+ }
5384
+ function createDataPendingOperations(config, eventHandlers) {
5385
+ return new DataPendingOperations({
5386
+ defaultTimeout: 30000,
5387
+ cleanupInterval: 30000,
5388
+ enableAutoCleanup: true,
5389
+ ...config
5390
+ }, eventHandlers);
5391
+ }
5392
+
5393
+ class JSONRPCCall {
5394
+ pendingRequests;
5395
+ constructor(config = {}) {
5396
+ const pendingEventHandlers = {
5397
+ onTimeout: (operation) => {
5398
+ console.warn(`JSON-RPC operation timed out:`, operation.id);
5399
+ },
5400
+ onResolve: (operation, _result) => {
5401
+ console.log(`JSON-RPC operation resolved:`, operation.id);
5402
+ },
5403
+ onReject: (operation, reason) => {
5404
+ console.error(`JSON-RPC operation rejected:`, operation.id, reason);
5405
+ }
5406
+ };
5407
+ this.pendingRequests = createPromisePendingOperations(config, pendingEventHandlers);
5408
+ }
5409
+ handleResponse(response) {
5410
+ const id = response.id;
5411
+ if (JSONRPCUtils.isSuccessResponse(response)) {
5412
+ this.pendingRequests.resolve(id, response.result);
5413
+ } else {
5414
+ this.pendingRequests.reject(id, new Error(response.error.message));
5415
+ }
5416
+ }
5417
+ async handleRequest(method, params, options = {}) {
5418
+ const { promise } = this.pendingRequests.add({ method, params }, options);
5419
+ return promise;
5420
+ }
5421
+ getStats() {
5422
+ return {
5423
+ pendingRequests: this.pendingRequests.getStats()
5424
+ };
5425
+ }
5426
+ destroy() {
5427
+ this.pendingRequests.destroy("JSONRPCManager destroyed");
5428
+ }
5429
+ }
4992
5430
  export {
4993
5431
  withErrorHandling,
4994
5432
  setupAITools,
@@ -4997,7 +5435,10 @@ export {
4997
5435
  formatSuccessResponse,
4998
5436
  formatErrorResponse,
4999
5437
  emptySchema,
5438
+ createDataPendingOperations,
5000
5439
  compressWithLanguageDetection as compressText,
5001
5440
  exports_schemas_symbol as SymbolType,
5441
+ exports_jsonrpc as JSONRPCType,
5442
+ JSONRPCCall,
5002
5443
  ErrorCode
5003
5444
  };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Unit Tests for PendingOperations Library
3
+ *
4
+ * Tests all functionality of the PendingOperations class including:
5
+ * - Promise-based operations
6
+ * - Request-based operations
7
+ * - Context-based operations
8
+ * - Timeout handling
9
+ * - Cleanup functionality
10
+ * - Statistics and monitoring
11
+ */
12
+ export {};
@@ -4,7 +4,9 @@ export { formatErrorResponse, formatSuccessResponse } from "./util_response";
4
4
  export { getLoggerInstance, type ModalityLogger } from "./util_logger";
5
5
  export { withErrorHandling, ErrorCode } from "./util_error";
6
6
  export * as SymbolType from "./schemas/schemas_symbol";
7
+ export * as JSONRPCType from "./schemas/jsonrpc";
7
8
  export type { EmptyType } from "./schemas/schemas_empty";
8
9
  export { emptySchema } from "./schemas/schemas_empty";
9
10
  export { loadVersion } from "./util_version";
10
11
  export { compressWithLanguageDetection as compressText } from "./util_text_compression";
12
+ export { JSONRPCCall, createDataPendingOperations } from "./util_pending";
@@ -0,0 +1,212 @@
1
+ /**
2
+ * JSON-RPC 2.0 Types and Interfaces
3
+ * @see https://www.jsonrpc.org/specification
4
+ *
5
+ * Comprehensive TypeScript definitions for JSON-RPC 2.0 specification
6
+ * Provides type-safe interfaces for request/response/notification patterns
7
+ * and utilities for message handling and validation.
8
+ */
9
+ /**
10
+ * Generic JSON-RPC method type for type-safe method definitions
11
+ * Usage: type MyMethod = JSONRPCMethod<MyParams, MyResult>
12
+ * Or as object: { methodName: JSONRPCMethod<MyParams, MyResult> }
13
+ */
14
+ export type JSONRPCMethod<TParams = JSONRPCParams, TResult = any> = (method: string, params?: TParams, options?: any) => Promise<TResult>;
15
+ /**
16
+ * JSON-RPC 2.0 version identifier
17
+ */
18
+ export declare const JSONRPC_VERSION: "2.0";
19
+ /**
20
+ * Standard JSON-RPC 2.0 error codes as defined in the specification
21
+ */
22
+ export declare enum JSONRPCErrorCode {
23
+ PARSE_ERROR = -32700,
24
+ INVALID_REQUEST = -32600,
25
+ METHOD_NOT_FOUND = -32601,
26
+ INVALID_PARAMS = -32602,
27
+ INTERNAL_ERROR = -32603,
28
+ SERVER_ERROR_START = -32099,
29
+ SERVER_ERROR_END = -32000,
30
+ TIMEOUT_ERROR = -32001,
31
+ CONNECTION_ERROR = -32002,
32
+ AUTHENTICATION_ERROR = -32003,
33
+ AUTHORIZATION_ERROR = -32004,
34
+ RATE_LIMIT_ERROR = -32005,
35
+ VALIDATION_ERROR = -32006
36
+ }
37
+ /**
38
+ * JSON-RPC 2.0 parameter types - can be object, array, or null
39
+ *
40
+ * IMPORTANT: DO NOT add 'undefined' to this type!
41
+ *
42
+ * Reasoning per JSON-RPC 2.0 specification (https://www.jsonrpc.org/specification):
43
+ * 1. JSON specification does not support 'undefined' - only null, objects, arrays, etc.
44
+ * 2. JSON.stringify() omits undefined values, making {params: undefined} equivalent to {}
45
+ * 3. JSON-RPC 2.0 spec defines params as: Object | Array | omitted entirely
46
+ * 4. Use cases:
47
+ * - No parameters: omit 'params' field entirely
48
+ * - Explicit null: use 'params: null'
49
+ * - Empty object: use 'params: {}'
50
+ * - Empty array: use 'params: []'
51
+ *
52
+ * This ensures strict JSON-RPC 2.0 compliance and wire protocol compatibility.
53
+ * See: https://www.jsonrpc.org/specification#parameter_structures
54
+ */
55
+ export type JSONRPCParams = object | any[] | null;
56
+ /**
57
+ * JSON-RPC 2.0 ID type - string, number, or null
58
+ */
59
+ export type JSONRPCId = string | number | null;
60
+ /**
61
+ * JSON-RPC 2.0 Error object
62
+ */
63
+ export interface JSONRPCError {
64
+ /** Error code indicating the error type */
65
+ code: JSONRPCErrorCode | number;
66
+ /** Human-readable error message */
67
+ message: string;
68
+ /** Optional additional error data */
69
+ data?: any;
70
+ }
71
+ /**
72
+ * JSON-RPC 2.0 Request object
73
+ */
74
+ export interface JSONRPCRequest {
75
+ /** JSON-RPC version identifier */
76
+ jsonrpc: typeof JSONRPC_VERSION;
77
+ /** Method name to be invoked */
78
+ method: string;
79
+ /** Optional parameters for the method */
80
+ params?: JSONRPCParams;
81
+ /** Request identifier for correlation with response */
82
+ id: JSONRPCId;
83
+ }
84
+ /**
85
+ * JSON-RPC 2.0 Notification object (request without id)
86
+ */
87
+ export interface JSONRPCNotification {
88
+ /** JSON-RPC version identifier */
89
+ jsonrpc: typeof JSONRPC_VERSION;
90
+ /** Method name to be invoked */
91
+ method: string;
92
+ /** Optional parameters for the method */
93
+ params?: JSONRPCParams;
94
+ }
95
+ /**
96
+ * JSON-RPC 2.0 Success Response object
97
+ */
98
+ export interface JSONRPCSuccessResponse {
99
+ /** JSON-RPC version identifier */
100
+ jsonrpc: typeof JSONRPC_VERSION;
101
+ /** Method result */
102
+ result: any;
103
+ /** Request identifier matching the original request */
104
+ id: JSONRPCId;
105
+ }
106
+ /**
107
+ * JSON-RPC 2.0 Error Response object
108
+ */
109
+ export interface JSONRPCErrorResponse {
110
+ /** JSON-RPC version identifier */
111
+ jsonrpc: typeof JSONRPC_VERSION;
112
+ /** Error information */
113
+ error: JSONRPCError;
114
+ /** Request identifier matching the original request */
115
+ id: JSONRPCId;
116
+ }
117
+ /**
118
+ * Union type for JSON-RPC 2.0 Response objects
119
+ */
120
+ export type JSONRPCResponse = JSONRPCSuccessResponse | JSONRPCErrorResponse;
121
+ /**
122
+ * JSON-RPC 2.0 Batch Request (array of requests and/or notifications)
123
+ */
124
+ export type JSONRPCBatchRequest = (JSONRPCRequest | JSONRPCNotification)[];
125
+ /**
126
+ * JSON-RPC 2.0 Batch Response (array of responses)
127
+ */
128
+ export type JSONRPCBatchResponse = JSONRPCResponse[];
129
+ /**
130
+ * Union type for all JSON-RPC 2.0 message types
131
+ */
132
+ export type JSONRPCMessage = JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCBatchRequest | JSONRPCBatchResponse;
133
+ /**
134
+ * JSON-RPC message validation result
135
+ */
136
+ export interface JSONRPCValidationResult {
137
+ /** Whether the message is valid */
138
+ valid: boolean;
139
+ /** Error information if validation failed */
140
+ error?: JSONRPCError;
141
+ /** Parsed message type */
142
+ messageType?: "request" | "notification" | "response" | "batch";
143
+ }
144
+ /**
145
+ * Utility functions for JSON-RPC message handling
146
+ */
147
+ export declare class JSONRPCUtils {
148
+ /**
149
+ * Validate a JSON-RPC message (supports batch requests)
150
+ */
151
+ static validateMessage(message: any): JSONRPCValidationResult;
152
+ /**
153
+ * Validate a single JSON-RPC message
154
+ */
155
+ static validateSingleMessage(message: any): JSONRPCValidationResult;
156
+ /**
157
+ * Create a JSON-RPC request
158
+ */
159
+ static createRequest(method: string, params?: JSONRPCParams, id?: JSONRPCId): JSONRPCRequest;
160
+ /**
161
+ * Create a JSON-RPC notification
162
+ */
163
+ static createNotification(method: string, params?: JSONRPCParams): JSONRPCNotification;
164
+ /**
165
+ * Create a JSON-RPC success response
166
+ */
167
+ static createSuccessResponse(result: any, id: JSONRPCId): JSONRPCSuccessResponse;
168
+ /**
169
+ * Create a JSON-RPC error response
170
+ */
171
+ static createErrorResponse(error: JSONRPCError, id: JSONRPCId): JSONRPCErrorResponse;
172
+ /**
173
+ * Create a standard JSON-RPC error
174
+ */
175
+ static createError(code: JSONRPCErrorCode | number, message: string, data?: any): JSONRPCError;
176
+ /**
177
+ * Generate a unique ID for requests
178
+ */
179
+ static generateId(): string;
180
+ /**
181
+ * Check if a message is a JSON-RPC request
182
+ */
183
+ static isRequest(message: any): message is JSONRPCRequest;
184
+ /**
185
+ * Check if a message is a JSON-RPC notification
186
+ */
187
+ static isNotification(message: any): message is JSONRPCNotification;
188
+ /**
189
+ * Check if a message is a JSON-RPC response
190
+ */
191
+ static isResponse(message: any): message is JSONRPCResponse;
192
+ /**
193
+ * Check if a response is a success response
194
+ */
195
+ static isSuccessResponse(response: JSONRPCResponse): response is JSONRPCSuccessResponse;
196
+ /**
197
+ * Check if a response is an error response
198
+ */
199
+ static isErrorResponse(response: JSONRPCResponse): response is JSONRPCErrorResponse;
200
+ /**
201
+ * Serialize a JSON-RPC message to string
202
+ */
203
+ static serialize(message: JSONRPCMessage): string;
204
+ /**
205
+ * Deserialize a JSON-RPC message from string
206
+ */
207
+ static deserialize(data: string): JSONRPCMessage | null;
208
+ }
209
+ /**
210
+ * Standard error messages for common JSON-RPC errors
211
+ */
212
+ export declare const STANDARD_ERROR_MESSAGES: Record<JSONRPCErrorCode, string>;
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Sharable Pending Operations Library
3
+ *
4
+ * A generic library for managing pending operations with timeout, cleanup,
5
+ * and lifecycle management. Can be used on both client and server sides
6
+ * for WebSocket communication or any async operation tracking.
7
+ */
8
+ import { JSONRPCResponse } from "./schemas/jsonrpc";
9
+ /**
10
+ * Configuration options for PendingOperations
11
+ *
12
+ * @interface PendingOperationsConfig
13
+ * @description Configures timeout behavior, cleanup intervals, and ID generation for pending operations
14
+ */
15
+ export interface PendingOperationsConfig {
16
+ /**
17
+ * Default timeout in milliseconds for operations (default: 30000)
18
+ * @default 30000
19
+ * @description Sets the default timeout for operations that don't specify their own timeout
20
+ */
21
+ defaultTimeout?: number;
22
+ /**
23
+ * Cleanup interval in milliseconds for expired operations (default: 10000)
24
+ * @default 10000
25
+ * @description How often the system checks for and cleans up expired operations
26
+ */
27
+ cleanupInterval?: number;
28
+ /**
29
+ * Enable automatic cleanup of expired operations (default: true)
30
+ * @default true
31
+ * @description Whether to automatically clean up expired operations in the background
32
+ */
33
+ enableAutoCleanup?: boolean;
34
+ /**
35
+ * Custom ID generator function (default: randomUUIDv7)
36
+ * @default getUUID
37
+ * @description Function to generate unique IDs for operations. Must return unique strings.
38
+ */
39
+ generateId?: () => string;
40
+ }
41
+ /**
42
+ * Base interface for all pending operation data
43
+ *
44
+ * @interface PendingOperationBase
45
+ * @description Common properties shared by all pending operation types
46
+ */
47
+ export interface PendingOperationBase {
48
+ /**
49
+ * Unique identifier for the operation
50
+ * @description Generated automatically by the ID generator function
51
+ */
52
+ id: string;
53
+ /**
54
+ * Timestamp when the operation was created
55
+ * @description Used for age calculations and timeout management
56
+ */
57
+ timestamp: number;
58
+ /**
59
+ * Timeout in milliseconds for this specific operation
60
+ * @description Overrides the default timeout if specified
61
+ */
62
+ timeout?: number;
63
+ /**
64
+ * Optional data for the operation
65
+ * @description Arbitrary data for application-specific use
66
+ */
67
+ data?: any;
68
+ }
69
+ /**
70
+ * Promise-based pending operation (used for server-to-client calls)
71
+ *
72
+ * @interface PromisePendingOperation
73
+ * @extends PendingOperationBase
74
+ * @description Represents an operation that provides a Promise interface for async resolution
75
+ * @example
76
+ * ```typescript
77
+ * const { id, promise } = pendingOps.addPromiseOperation();
78
+ * promise.then(result => console.log('Success:', result));
79
+ * // Later: pendingOps.resolve(id, { data: 'result' });
80
+ * ```
81
+ */
82
+ export interface PromisePendingOperation extends PendingOperationBase {
83
+ /** Operation type identifier */
84
+ type: "promise";
85
+ /**
86
+ * Promise resolve function
87
+ * @description Called when the operation completes successfully
88
+ */
89
+ resolve: (value: any) => void;
90
+ /**
91
+ * Promise reject function
92
+ * @description Called when the operation fails or times out
93
+ */
94
+ reject: (reason?: any) => void;
95
+ /**
96
+ * Optional timeout handle for cleanup
97
+ * @description Internal timeout handle for automatic cleanup
98
+ */
99
+ timeoutHandle?: NodeJS.Timeout;
100
+ }
101
+ /**
102
+ * Data-based pending operation (used for request tracking and context storage)
103
+ * Replaces both RequestPendingOperation and ContextPendingOperation
104
+ *
105
+ * @interface DataPendingOperation
106
+ * @extends PendingOperationBase
107
+ * @description Flexible operation type that can store any data payload. Use for client-server requests, context tracking, or any data-centric operations.
108
+ * @example
109
+ * ```typescript
110
+ * // Request-style usage
111
+ * const id = pendingOps.addDataOperation(
112
+ * { requestType: 'getUserProfile', payload: { userId: 123 } },
113
+ * { connectionId: 'client-001', timeout: 30000 }
114
+ * );
115
+ *
116
+ * // Context-style usage
117
+ * const id2 = pendingOps.addDataOperation(
118
+ * { functionName: 'processCommand', context: { command: 'export' } },
119
+ * { timeout: 60000 }
120
+ * );
121
+ * ```
122
+ */
123
+ export interface DataPendingOperation extends PendingOperationBase {
124
+ /** Operation type identifier */
125
+ type: "data";
126
+ /**
127
+ * Arbitrary data payload for the operation
128
+ * @description Can contain requestType/payload, functionName/context, connectionId, connection, or any other data structure
129
+ */
130
+ data: any;
131
+ /**
132
+ * Optional timeout handle for cleanup
133
+ * @description Internal timeout handle for automatic cleanup
134
+ */
135
+ timeoutHandle?: NodeJS.Timeout;
136
+ }
137
+ /**
138
+ * Union type for all pending operation types
139
+ */
140
+ export type PendingOperation = PromisePendingOperation | DataPendingOperation;
141
+ /**
142
+ * Event handlers for pending operations
143
+ */
144
+ export interface PendingOperationEventHandlers {
145
+ /** Called when an operation times out */
146
+ onTimeout?: (operation: PendingOperation) => void;
147
+ /** Called when an operation is resolved */
148
+ onResolve?: (operation: PendingOperation, result?: any) => void;
149
+ /** Called when an operation is rejected */
150
+ onReject?: (operation: PendingOperation, reason?: any) => void;
151
+ /** Called when an operation is removed/cleaned up */
152
+ onCleanup?: (operation: PendingOperation) => void;
153
+ }
154
+ /**
155
+ * Statistics about pending operations
156
+ */
157
+ export interface PendingOperationStats {
158
+ /** Total number of pending operations */
159
+ total: number;
160
+ /** Number of operations by type */
161
+ byType: Record<string, number>;
162
+ /** Number of operations that will expire soon (within next minute) */
163
+ expiringSoon: number;
164
+ /** Oldest operation timestamp */
165
+ oldestTimestamp?: number;
166
+ /** Average age of operations in milliseconds */
167
+ averageAge: number;
168
+ }
169
+ /**
170
+ * Abstract base class for pending operations management
171
+ * Contains all common functionality while allowing specialized subclasses
172
+ */
173
+ export declare abstract class PendingOperationsBase {
174
+ protected operations: Map<string, PendingOperation>;
175
+ protected config: Required<PendingOperationsConfig>;
176
+ protected cleanupIntervalHandle?: NodeJS.Timeout;
177
+ protected eventHandlers: PendingOperationEventHandlers;
178
+ constructor(config?: PendingOperationsConfig, eventHandlers?: PendingOperationEventHandlers);
179
+ /**
180
+ * Resolve a pending operation with a result
181
+ */
182
+ resolve(id: string, result?: any): boolean;
183
+ /**
184
+ * Reject a pending operation with a reason
185
+ */
186
+ reject(id: string, reason?: any): boolean;
187
+ /**
188
+ * Get a pending operation by ID
189
+ */
190
+ get(id: string): PendingOperation | undefined;
191
+ /**
192
+ * Check if an operation exists
193
+ */
194
+ has(id: string): boolean;
195
+ /**
196
+ * Remove an operation without resolving or rejecting
197
+ */
198
+ remove(id: string): boolean;
199
+ /**
200
+ * Get all pending operations
201
+ */
202
+ getAll(): Map<string, PendingOperation>;
203
+ /**
204
+ * Get pending operations by type
205
+ */
206
+ getByType(type: PendingOperation["type"]): PendingOperation[];
207
+ /**
208
+ * Clear all pending operations
209
+ */
210
+ clear(reason?: any): number;
211
+ /**
212
+ * Clean up expired operations
213
+ */
214
+ cleanupExpired(): number;
215
+ /**
216
+ * Get statistics about pending operations
217
+ */
218
+ getStats(): PendingOperationStats;
219
+ /**
220
+ * Start automatic cleanup of expired operations
221
+ */
222
+ private startAutoCleanup;
223
+ /**
224
+ * Stop automatic cleanup
225
+ */
226
+ stopAutoCleanup(): void;
227
+ /**
228
+ * Handle operation timeout
229
+ */
230
+ protected handleTimeout({ id }: {
231
+ id: string;
232
+ }): void;
233
+ /**
234
+ * Destroy the pending operations manager
235
+ */
236
+ destroy(reason?: any): void;
237
+ /**
238
+ * Get the current configuration
239
+ */
240
+ getConfig(): Required<PendingOperationsConfig>;
241
+ /**
242
+ * Update event handlers
243
+ */
244
+ setEventHandlers(handlers: PendingOperationEventHandlers): void;
245
+ /**
246
+ * Abstract method that subclasses must implement for their specific operation type
247
+ */
248
+ abstract add(...args: any[]): any;
249
+ handleAdd(options?: {
250
+ timeout?: number;
251
+ customId?: string;
252
+ }): {
253
+ id: string;
254
+ timeout: number;
255
+ timeoutHandle: NodeJS.Timeout | undefined;
256
+ };
257
+ }
258
+ /**
259
+ * Specialized class for promise-based operations only
260
+ * Enforces type safety by only exposing promise-related methods
261
+ */
262
+ export declare class PromisePendingOperations extends PendingOperationsBase {
263
+ /**
264
+ * Add a promise-based pending operation
265
+ */
266
+ add(data: any, options?: {
267
+ timeout?: number;
268
+ customId?: string;
269
+ }): {
270
+ id: string;
271
+ promise: Promise<any>;
272
+ };
273
+ }
274
+ /**
275
+ * Specialized class for data-based operations only
276
+ * Enforces type safety by only exposing data-related methods
277
+ */
278
+ export declare class DataPendingOperations extends PendingOperationsBase {
279
+ /**
280
+ * Add a data-based pending operation
281
+ */
282
+ add(data: any, options?: {
283
+ timeout?: number;
284
+ customId?: string;
285
+ }): {
286
+ id: string;
287
+ };
288
+ }
289
+ /**
290
+ * Create a PendingOperations instance with Promise-based operations
291
+ * Optimized for server-to-client function calls
292
+ *
293
+ * @param config Optional configuration for the pending operations
294
+ * @param eventHandlers Optional event handlers for operation lifecycle
295
+ * @returns PromisePendingOperations instance that only allows promise operations
296
+ */
297
+ export declare function createPromisePendingOperations(config?: PendingOperationsConfig, eventHandlers?: PendingOperationEventHandlers): PromisePendingOperations;
298
+ /**
299
+ * Create a PendingOperations instance with Data-based operations
300
+ * Optimized for both request processing and context handling
301
+ *
302
+ * @param config Optional configuration for the pending operations
303
+ * @param eventHandlers Optional event handlers for operation lifecycle
304
+ * @returns DataPendingOperations instance that only allows data operations
305
+ */
306
+ export declare function createDataPendingOperations(config?: PendingOperationsConfig, eventHandlers?: PendingOperationEventHandlers): DataPendingOperations;
307
+ export declare class JSONRPCCall {
308
+ private pendingRequests;
309
+ constructor(config?: PendingOperationsConfig);
310
+ /**
311
+ * Process a JSON-RPC response
312
+ */
313
+ handleResponse(response: JSONRPCResponse): void;
314
+ /**
315
+ * Send a JSON-RPC request and return a promise for the response
316
+ */
317
+ handleRequest(method: string, params?: any, options?: {
318
+ timeout?: number;
319
+ }): Promise<any>;
320
+ /**
321
+ * Get manager statistics
322
+ */
323
+ getStats(): {
324
+ pendingRequests: PendingOperationStats;
325
+ };
326
+ /**
327
+ * Clean up resources
328
+ */
329
+ destroy(): void;
330
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Timer Mock Utilities
3
+ *
4
+ * These utilities provide centralized timer mocking for tests that need to control time.
5
+ * This ensures consistent behavior across all tests that use setTimeout, setInterval, etc.
6
+ */
7
+ /**
8
+ * Centralized timer mock manager for controlling time in tests
9
+ *
10
+ * Features:
11
+ * - Mock setTimeout with virtual clock advancement
12
+ * - Mock Math.random for deterministic jitter elimination
13
+ * - Track virtual time progression
14
+ * - Immediate callback execution for fast tests
15
+ *
16
+ * Usage:
17
+ * ```typescript
18
+ * import { TimerMockManager } from "../util_tests/TimerMockManager.js";
19
+ *
20
+ * let timerMock: TimerMockManager;
21
+ *
22
+ * beforeEach(() => {
23
+ * timerMock = new TimerMockManager();
24
+ * timerMock.setup();
25
+ * });
26
+ *
27
+ * afterEach(() => {
28
+ * timerMock.restore();
29
+ * });
30
+ * ```
31
+ */
32
+ export declare class TimerMockManager {
33
+ private originalSetTimeout?;
34
+ private originalMathRandom?;
35
+ private mockSetTimeout?;
36
+ private virtualClock;
37
+ /**
38
+ * Setup timer mocks
39
+ * @param executeImmediately - Whether to execute callbacks immediately (default: true for fast tests)
40
+ * @param mockRandomValue - Fixed value for Math.random (default: 0 to eliminate jitter)
41
+ */
42
+ setup(executeImmediately?: boolean, mockRandomValue?: number): void;
43
+ /**
44
+ * Restore original timer functions
45
+ */
46
+ restore(): void;
47
+ /**
48
+ * Get the current virtual clock time
49
+ */
50
+ getVirtualClock(): number;
51
+ /**
52
+ * Reset virtual clock to zero
53
+ */
54
+ resetClock(): void;
55
+ /**
56
+ * Get the mock setTimeout function for assertions
57
+ */
58
+ getMockSetTimeout(): any;
59
+ /**
60
+ * Verify that setTimeout was called with expected parameters
61
+ */
62
+ expectSetTimeoutCalled(times: number, delay?: number): void;
63
+ }
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.5",
2
+ "version": "0.7.0",
3
3
  "name": "modality-kit",
4
4
  "repository": {
5
5
  "type": "git",