@spikard/node 0.11.0 → 0.12.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.d.mts CHANGED
@@ -117,6 +117,12 @@ interface OpenApiConfig {
117
117
  servers?: ServerInfo[];
118
118
  securitySchemes?: Record<string, SecuritySchemeInfo>;
119
119
  }
120
+ interface JsonRpcConfig {
121
+ enabled?: boolean;
122
+ endpointPath?: string;
123
+ enableBatch?: boolean;
124
+ maxBatchSize?: number;
125
+ }
120
126
  interface StaticFilesConfig {
121
127
  directory: string;
122
128
  routePrefix: string;
@@ -138,6 +144,7 @@ interface ServerConfig {
138
144
  gracefulShutdown?: boolean;
139
145
  shutdownTimeout?: number;
140
146
  openapi?: OpenApiConfig | null;
147
+ jsonrpc?: JsonRpcConfig | null;
141
148
  }
142
149
 
143
150
  interface ServerOptions {
@@ -292,6 +299,13 @@ interface WebSocketTestConnection {
292
299
  close(): Promise<void>;
293
300
  }
294
301
  type TestResponse = NativeTestResponse;
302
+ interface GraphQLSubscriptionResult {
303
+ operationId: string;
304
+ acknowledged: boolean;
305
+ event: unknown | null;
306
+ errors: unknown[];
307
+ completeReceived: boolean;
308
+ }
295
309
  interface MultipartFile {
296
310
  name: string;
297
311
  filename?: string;
@@ -330,6 +344,7 @@ declare class TestClient {
330
344
  headers: string;
331
345
  bodyText: string;
332
346
  }>;
347
+ graphqlSubscription(query: string, variables?: Record<string, unknown> | null, operationName?: string | null, path?: string): Promise<GraphQLSubscriptionResult>;
333
348
  cleanup(): Promise<void>;
334
349
  }
335
350
 
@@ -397,4 +412,4 @@ interface SpikardApp {
397
412
  dependencies?: Record<string, unknown>;
398
413
  }
399
414
 
400
- export { type ApiKeyConfig, type Base64EncodedBody, type Body, type CompressionConfig, type ContactInfo, type CorsConfig, type DependencyFactory, type DependencyOptions, type DependencyValue, type FileParam, GrpcError, type GrpcHandler, type GrpcMetadata, type GrpcRequest, type GrpcResponse, type GrpcServiceConfig, GrpcStatusCode, type HandlerFunction, type HandlerResult, type JsonPrimitive, type JsonRecord, type JsonSchema, type JsonValue, type JwtConfig, type LicenseInfo, type LifecycleHookFunction, type LifecycleHooks, type MaybePromise, type NativeHandlerFunction, type OpenApiConfig, type Path, type Query, QueryDefault, type RateLimitConfig, type Request, type RouteMetadata, type RouteOptions, type SecuritySchemeInfo, type ServerConfig, type ServerInfo, type ServerOptions, Spikard, type SpikardApp, type StaticFilesConfig, StreamingResponse, type StreamingResponseInit, type StructuredHandlerResponse, TestClient, type TestResponse, UploadFile, type WebSocketHandler, type WebSocketOptions, background, createServiceHandler, createUnaryHandler, del, get, patch, post, put, route, runServer, wrapBodyHandler, wrapHandler };
415
+ export { type ApiKeyConfig, type Base64EncodedBody, type Body, type CompressionConfig, type ContactInfo, type CorsConfig, type DependencyFactory, type DependencyOptions, type DependencyValue, type FileParam, GrpcError, type GrpcHandler, type GrpcMetadata, type GrpcRequest, type GrpcResponse, type GrpcServiceConfig, GrpcStatusCode, type HandlerFunction, type HandlerResult, type JsonPrimitive, type JsonRecord, type JsonRpcConfig, type JsonSchema, type JsonValue, type JwtConfig, type LicenseInfo, type LifecycleHookFunction, type LifecycleHooks, type MaybePromise, type NativeHandlerFunction, type OpenApiConfig, type Path, type Query, QueryDefault, type RateLimitConfig, type Request, type RouteMetadata, type RouteOptions, type SecuritySchemeInfo, type ServerConfig, type ServerInfo, type ServerOptions, Spikard, type SpikardApp, type StaticFilesConfig, StreamingResponse, type StreamingResponseInit, type StructuredHandlerResponse, TestClient, type TestResponse, UploadFile, type WebSocketHandler, type WebSocketOptions, background, createServiceHandler, createUnaryHandler, del, get, patch, post, put, route, runServer, wrapBodyHandler, wrapHandler };
package/dist/index.d.ts CHANGED
@@ -117,6 +117,12 @@ interface OpenApiConfig {
117
117
  servers?: ServerInfo[];
118
118
  securitySchemes?: Record<string, SecuritySchemeInfo>;
119
119
  }
120
+ interface JsonRpcConfig {
121
+ enabled?: boolean;
122
+ endpointPath?: string;
123
+ enableBatch?: boolean;
124
+ maxBatchSize?: number;
125
+ }
120
126
  interface StaticFilesConfig {
121
127
  directory: string;
122
128
  routePrefix: string;
@@ -138,6 +144,7 @@ interface ServerConfig {
138
144
  gracefulShutdown?: boolean;
139
145
  shutdownTimeout?: number;
140
146
  openapi?: OpenApiConfig | null;
147
+ jsonrpc?: JsonRpcConfig | null;
141
148
  }
142
149
 
143
150
  interface ServerOptions {
@@ -292,6 +299,13 @@ interface WebSocketTestConnection {
292
299
  close(): Promise<void>;
293
300
  }
294
301
  type TestResponse = NativeTestResponse;
302
+ interface GraphQLSubscriptionResult {
303
+ operationId: string;
304
+ acknowledged: boolean;
305
+ event: unknown | null;
306
+ errors: unknown[];
307
+ completeReceived: boolean;
308
+ }
295
309
  interface MultipartFile {
296
310
  name: string;
297
311
  filename?: string;
@@ -330,6 +344,7 @@ declare class TestClient {
330
344
  headers: string;
331
345
  bodyText: string;
332
346
  }>;
347
+ graphqlSubscription(query: string, variables?: Record<string, unknown> | null, operationName?: string | null, path?: string): Promise<GraphQLSubscriptionResult>;
333
348
  cleanup(): Promise<void>;
334
349
  }
335
350
 
@@ -397,4 +412,4 @@ interface SpikardApp {
397
412
  dependencies?: Record<string, unknown>;
398
413
  }
399
414
 
400
- export { type ApiKeyConfig, type Base64EncodedBody, type Body, type CompressionConfig, type ContactInfo, type CorsConfig, type DependencyFactory, type DependencyOptions, type DependencyValue, type FileParam, GrpcError, type GrpcHandler, type GrpcMetadata, type GrpcRequest, type GrpcResponse, type GrpcServiceConfig, GrpcStatusCode, type HandlerFunction, type HandlerResult, type JsonPrimitive, type JsonRecord, type JsonSchema, type JsonValue, type JwtConfig, type LicenseInfo, type LifecycleHookFunction, type LifecycleHooks, type MaybePromise, type NativeHandlerFunction, type OpenApiConfig, type Path, type Query, QueryDefault, type RateLimitConfig, type Request, type RouteMetadata, type RouteOptions, type SecuritySchemeInfo, type ServerConfig, type ServerInfo, type ServerOptions, Spikard, type SpikardApp, type StaticFilesConfig, StreamingResponse, type StreamingResponseInit, type StructuredHandlerResponse, TestClient, type TestResponse, UploadFile, type WebSocketHandler, type WebSocketOptions, background, createServiceHandler, createUnaryHandler, del, get, patch, post, put, route, runServer, wrapBodyHandler, wrapHandler };
415
+ export { type ApiKeyConfig, type Base64EncodedBody, type Body, type CompressionConfig, type ContactInfo, type CorsConfig, type DependencyFactory, type DependencyOptions, type DependencyValue, type FileParam, GrpcError, type GrpcHandler, type GrpcMetadata, type GrpcRequest, type GrpcResponse, type GrpcServiceConfig, GrpcStatusCode, type HandlerFunction, type HandlerResult, type JsonPrimitive, type JsonRecord, type JsonRpcConfig, type JsonSchema, type JsonValue, type JwtConfig, type LicenseInfo, type LifecycleHookFunction, type LifecycleHooks, type MaybePromise, type NativeHandlerFunction, type OpenApiConfig, type Path, type Query, QueryDefault, type RateLimitConfig, type Request, type RouteMetadata, type RouteOptions, type SecuritySchemeInfo, type ServerConfig, type ServerInfo, type ServerOptions, Spikard, type SpikardApp, type StaticFilesConfig, StreamingResponse, type StreamingResponseInit, type StructuredHandlerResponse, TestClient, type TestResponse, UploadFile, type WebSocketHandler, type WebSocketOptions, background, createServiceHandler, createUnaryHandler, del, get, patch, post, put, route, runServer, wrapBodyHandler, wrapHandler };
package/dist/index.js CHANGED
@@ -954,6 +954,36 @@ var import_promises = __toESM(require("fs/promises"));
954
954
  var import_node_module3 = require("module");
955
955
  var import_node_path = __toESM(require("path"));
956
956
  var import_node_zlib = require("zlib");
957
+ var GRAPHQL_WS_TIMEOUT_MS = 2e3;
958
+ var GRAPHQL_WS_MAX_CONTROL_MESSAGES = 32;
959
+ var withTimeout = async (promise, timeoutMs, context) => {
960
+ let timer;
961
+ try {
962
+ return await Promise.race([
963
+ promise,
964
+ new Promise((_resolve, reject) => {
965
+ timer = setTimeout(() => reject(new Error(`Timed out waiting for ${context}`)), timeoutMs);
966
+ })
967
+ ]);
968
+ } finally {
969
+ if (timer) {
970
+ clearTimeout(timer);
971
+ }
972
+ }
973
+ };
974
+ var decodeGraphqlWsMessage = (value) => {
975
+ if (typeof value === "string") {
976
+ const parsed = JSON.parse(value);
977
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
978
+ return parsed;
979
+ }
980
+ throw new Error("Expected GraphQL WebSocket JSON object message");
981
+ }
982
+ if (value && typeof value === "object" && !Array.isArray(value)) {
983
+ return value;
984
+ }
985
+ throw new Error("Expected GraphQL WebSocket message object");
986
+ };
957
987
  var MockWebSocketConnection = class {
958
988
  handler;
959
989
  queue = [];
@@ -1667,6 +1697,107 @@ var TestClient = class {
1667
1697
  bodyText: response.text()
1668
1698
  };
1669
1699
  }
1700
+ /**
1701
+ * Send a GraphQL subscription over WebSocket and return the first event payload.
1702
+ */
1703
+ async graphqlSubscription(query, variables, operationName, path2 = "/graphql") {
1704
+ const operationId = "spikard-subscription-1";
1705
+ const subscriptionPayload = { query };
1706
+ if (variables !== null && variables !== void 0) {
1707
+ subscriptionPayload.variables = variables;
1708
+ }
1709
+ if (operationName !== null && operationName !== void 0) {
1710
+ subscriptionPayload.operationName = operationName;
1711
+ }
1712
+ const ws = await this.websocketConnect(path2);
1713
+ try {
1714
+ await ws.sendJson({ type: "connection_init" });
1715
+ let acknowledged = false;
1716
+ for (let i = 0; i < GRAPHQL_WS_MAX_CONTROL_MESSAGES; i++) {
1717
+ const message = decodeGraphqlWsMessage(
1718
+ await withTimeout(ws.receiveJson(), GRAPHQL_WS_TIMEOUT_MS, "GraphQL connection_ack")
1719
+ );
1720
+ const messageType = typeof message.type === "string" ? message.type : "";
1721
+ if (messageType === "connection_ack") {
1722
+ acknowledged = true;
1723
+ break;
1724
+ }
1725
+ if (messageType === "ping") {
1726
+ const pong = { type: "pong" };
1727
+ if ("payload" in message) {
1728
+ pong.payload = message.payload;
1729
+ }
1730
+ await ws.sendJson(pong);
1731
+ continue;
1732
+ }
1733
+ if (messageType === "connection_error" || messageType === "error") {
1734
+ throw new Error(`GraphQL subscription rejected during init: ${JSON.stringify(message)}`);
1735
+ }
1736
+ }
1737
+ if (!acknowledged) {
1738
+ throw new Error("No GraphQL connection_ack received");
1739
+ }
1740
+ await ws.sendJson({
1741
+ id: operationId,
1742
+ type: "subscribe",
1743
+ payload: subscriptionPayload
1744
+ });
1745
+ let event = null;
1746
+ const errors = [];
1747
+ let completeReceived = false;
1748
+ for (let i = 0; i < GRAPHQL_WS_MAX_CONTROL_MESSAGES; i++) {
1749
+ const message = decodeGraphqlWsMessage(
1750
+ await withTimeout(ws.receiveJson(), GRAPHQL_WS_TIMEOUT_MS, "GraphQL subscription message")
1751
+ );
1752
+ const messageType = typeof message.type === "string" ? message.type : "";
1753
+ const messageId = typeof message.id === "string" ? message.id : void 0;
1754
+ const idMatches = messageId === void 0 || messageId === operationId;
1755
+ if (messageType === "next" && idMatches) {
1756
+ event = "payload" in message ? message.payload : null;
1757
+ await ws.sendJson({ id: operationId, type: "complete" });
1758
+ try {
1759
+ const maybeComplete = decodeGraphqlWsMessage(
1760
+ await withTimeout(ws.receiveJson(), GRAPHQL_WS_TIMEOUT_MS, "GraphQL complete message")
1761
+ );
1762
+ const completeType = typeof maybeComplete.type === "string" ? maybeComplete.type : "";
1763
+ const completeId = typeof maybeComplete.id === "string" ? maybeComplete.id : void 0;
1764
+ if (completeType === "complete" && (completeId === void 0 || completeId === operationId)) {
1765
+ completeReceived = true;
1766
+ }
1767
+ } catch {
1768
+ }
1769
+ break;
1770
+ }
1771
+ if (messageType === "error") {
1772
+ errors.push("payload" in message ? message.payload : message);
1773
+ break;
1774
+ }
1775
+ if (messageType === "complete" && idMatches) {
1776
+ completeReceived = true;
1777
+ break;
1778
+ }
1779
+ if (messageType === "ping") {
1780
+ const pong = { type: "pong" };
1781
+ if ("payload" in message) {
1782
+ pong.payload = message.payload;
1783
+ }
1784
+ await ws.sendJson(pong);
1785
+ }
1786
+ }
1787
+ if (event === null && errors.length === 0 && !completeReceived) {
1788
+ throw new Error("No GraphQL subscription event received before timeout");
1789
+ }
1790
+ return {
1791
+ operationId,
1792
+ acknowledged,
1793
+ event,
1794
+ errors,
1795
+ completeReceived
1796
+ };
1797
+ } finally {
1798
+ await ws.close();
1799
+ }
1800
+ }
1670
1801
  /**
1671
1802
  * Cleanup resources when test client is done
1672
1803
  */