@jaypie/fabric 0.2.0 → 0.2.2

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 (47) hide show
  1. package/dist/cjs/ServiceSuite.d.ts +3 -1
  2. package/dist/cjs/commander/index.cjs +42 -11
  3. package/dist/cjs/commander/index.cjs.map +1 -1
  4. package/dist/cjs/data/index.cjs +40 -11
  5. package/dist/cjs/data/index.cjs.map +1 -1
  6. package/dist/cjs/http/index.cjs +43 -13
  7. package/dist/cjs/http/index.cjs.map +1 -1
  8. package/dist/cjs/index.cjs +50 -21
  9. package/dist/cjs/index.cjs.map +1 -1
  10. package/dist/cjs/index.d.ts +1 -1
  11. package/dist/cjs/lambda/index.cjs +42 -11
  12. package/dist/cjs/lambda/index.cjs.map +1 -1
  13. package/dist/cjs/llm/index.cjs +42 -11
  14. package/dist/cjs/llm/index.cjs.map +1 -1
  15. package/dist/cjs/mcp/FabricMcpServer.d.ts +1 -1
  16. package/dist/cjs/mcp/index.cjs +43 -12
  17. package/dist/cjs/mcp/index.cjs.map +1 -1
  18. package/dist/cjs/models/base.d.ts +6 -6
  19. package/dist/cjs/resolveService.d.ts +7 -4
  20. package/dist/cjs/service.d.ts +6 -4
  21. package/dist/cjs/types.d.ts +9 -3
  22. package/dist/cjs/websocket/fabricWebSocket.d.ts +120 -0
  23. package/dist/cjs/websocket/index.d.ts +2 -0
  24. package/dist/esm/ServiceSuite.d.ts +3 -1
  25. package/dist/esm/commander/index.js +42 -11
  26. package/dist/esm/commander/index.js.map +1 -1
  27. package/dist/esm/data/index.js +40 -11
  28. package/dist/esm/data/index.js.map +1 -1
  29. package/dist/esm/http/index.js +43 -13
  30. package/dist/esm/http/index.js.map +1 -1
  31. package/dist/esm/index.d.ts +1 -1
  32. package/dist/esm/index.js +50 -21
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/lambda/index.js +42 -11
  35. package/dist/esm/lambda/index.js.map +1 -1
  36. package/dist/esm/llm/index.js +42 -11
  37. package/dist/esm/llm/index.js.map +1 -1
  38. package/dist/esm/mcp/FabricMcpServer.d.ts +1 -1
  39. package/dist/esm/mcp/index.js +43 -12
  40. package/dist/esm/mcp/index.js.map +1 -1
  41. package/dist/esm/models/base.d.ts +6 -6
  42. package/dist/esm/resolveService.d.ts +7 -4
  43. package/dist/esm/service.d.ts +6 -4
  44. package/dist/esm/types.d.ts +9 -3
  45. package/dist/esm/websocket/fabricWebSocket.d.ts +120 -0
  46. package/dist/esm/websocket/index.d.ts +2 -0
  47. package/package.json +6 -1
@@ -0,0 +1,120 @@
1
+ import type { BroadcastResult, SendResult, WebSocketContext, WebSocketEvent, WebSocketResponse } from "@jaypie/lambda";
2
+ import type { InputFieldDefinition, Message, Service, ServiceContext, ServiceFunction } from "../types.js";
3
+ /** Callback called when handler completes successfully */
4
+ export type OnCompleteCallback = (response: unknown) => void | Promise<void>;
5
+ /** Callback called for recoverable errors (via context.onError) */
6
+ export type OnErrorCallback = (error: unknown) => void | Promise<void>;
7
+ /** Callback called for fatal errors (thrown or via context.onFatal) */
8
+ export type OnFatalCallback = (error: unknown) => void | Promise<void>;
9
+ /** Callback for receiving messages from service during execution */
10
+ export type OnMessageCallback = (message: Message) => void | Promise<void>;
11
+ /** Callback for $connect event */
12
+ export type OnConnectCallback = (context: WebSocketContext) => void | Promise<void>;
13
+ /** Callback for $disconnect event */
14
+ export type OnDisconnectCallback = (context: WebSocketContext) => void | Promise<void>;
15
+ type LifecycleFunction = (...args: unknown[]) => void | Promise<void>;
16
+ type ValidatorFunction = (...args: unknown[]) => unknown | Promise<unknown>;
17
+ /**
18
+ * Extended service context for WebSocket handlers
19
+ */
20
+ export interface WebSocketServiceContext extends ServiceContext {
21
+ /** Broadcast data to multiple connections */
22
+ broadcast: (connectionIds: string[], data: unknown) => Promise<BroadcastResult>;
23
+ /** The WebSocket connection ID */
24
+ connectionId: string;
25
+ /** The WebSocket API domain name */
26
+ domainName: string;
27
+ /** Query string parameters from $connect */
28
+ queryStringParameters: Record<string, string> | null;
29
+ /** The route key (e.g., "$connect", "$disconnect", "$default", or custom) */
30
+ routeKey: string;
31
+ /** Send data to the current connection */
32
+ send: (data: unknown) => Promise<SendResult>;
33
+ /** The WebSocket API stage */
34
+ stage: string;
35
+ }
36
+ /**
37
+ * Options for fabricWebSocket
38
+ */
39
+ export interface FabricWebSocketOptions {
40
+ /** Chaos testing mode */
41
+ chaos?: string;
42
+ /** Override the service name for logging (defaults to service.alias) */
43
+ name?: string;
44
+ /** Callback for $connect events */
45
+ onConnect?: OnConnectCallback;
46
+ /** Callback called when handler completes successfully */
47
+ onComplete?: OnCompleteCallback;
48
+ /** Callback for $disconnect events */
49
+ onDisconnect?: OnDisconnectCallback;
50
+ /** Callback for recoverable errors (via context.onError) */
51
+ onError?: OnErrorCallback;
52
+ /** Callback for fatal errors (thrown or via context.onFatal) */
53
+ onFatal?: OnFatalCallback;
54
+ /** Callback for receiving messages from service during execution */
55
+ onMessage?: OnMessageCallback;
56
+ /** AWS secrets to load into process.env */
57
+ secrets?: string[];
58
+ /** Functions to run before handler */
59
+ setup?: LifecycleFunction[];
60
+ /** Functions to run after handler (always runs) */
61
+ teardown?: LifecycleFunction[];
62
+ /** Re-throw errors instead of returning error response */
63
+ throw?: boolean;
64
+ /** Return 503 Unavailable immediately */
65
+ unavailable?: boolean;
66
+ /** Validation functions to run before handler */
67
+ validate?: ValidatorFunction[];
68
+ }
69
+ /**
70
+ * Configuration for fabricWebSocket
71
+ */
72
+ export interface FabricWebSocketConfig extends FabricWebSocketOptions {
73
+ /** Service alias (used as name for logging if `name` not provided) */
74
+ alias?: string;
75
+ /** Service description */
76
+ description?: string;
77
+ /** Input field definitions */
78
+ input?: Record<string, InputFieldDefinition>;
79
+ /** The service - either a pre-instantiated Service or an inline function */
80
+ service: Service | ServiceFunction<Record<string, unknown>, unknown>;
81
+ }
82
+ /**
83
+ * The returned WebSocket Lambda handler function
84
+ */
85
+ export type FabricWebSocketResult = (event: WebSocketEvent, context?: {
86
+ awsRequestId?: string;
87
+ [key: string]: unknown;
88
+ }, ...args: unknown[]) => Promise<WebSocketResponse>;
89
+ /**
90
+ * Fabric a WebSocket Lambda handler that wraps a service.
91
+ *
92
+ * This function creates a WebSocket-compatible Lambda handler that:
93
+ * - Parses the WebSocket body as service input
94
+ * - Provides WebSocket context (connectionId, send, broadcast) to the service
95
+ * - Handles $connect and $disconnect events with optional callbacks
96
+ * - Integrates with websocketHandler for lifecycle management
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * import { fabricWebSocket } from "@jaypie/fabric/websocket";
101
+ * import { myService } from "./services";
102
+ *
103
+ * // Direct service style
104
+ * export const handler = fabricWebSocket(myService);
105
+ *
106
+ * // Config object style with lifecycle hooks
107
+ * export const handler2 = fabricWebSocket({
108
+ * service: myService,
109
+ * secrets: ["MONGODB_URI"],
110
+ * onConnect: async (context) => {
111
+ * await storeConnection(context.connectionId);
112
+ * },
113
+ * onDisconnect: async (context) => {
114
+ * await removeConnection(context.connectionId);
115
+ * },
116
+ * });
117
+ * ```
118
+ */
119
+ export declare function fabricWebSocket(serviceOrConfig: FabricWebSocketConfig | Service | ServiceFunction<Record<string, unknown>, unknown>, options?: FabricWebSocketOptions): FabricWebSocketResult;
120
+ export {};
@@ -0,0 +1,2 @@
1
+ export { fabricWebSocket } from "./fabricWebSocket.js";
2
+ export type { FabricWebSocketConfig, FabricWebSocketOptions, FabricWebSocketResult, OnCompleteCallback, OnConnectCallback, OnDisconnectCallback, OnErrorCallback, OnFatalCallback, OnMessageCallback, WebSocketServiceContext, } from "./fabricWebSocket.js";
@@ -43,7 +43,9 @@ export interface ServiceSuite {
43
43
  /** Execute a service by name */
44
44
  execute(name: string, inputs: Record<string, unknown>): Promise<unknown>;
45
45
  /** Register a fabricService into the suite */
46
- register(service: Service<any, any>, category: string): void;
46
+ register(service: Service<any, any>, options: {
47
+ category: string;
48
+ }): void;
47
49
  /** Get all registered service functions (for transport adapters like FabricMcpServer) */
48
50
  getServiceFunctions(): Service<any, any>[];
49
51
  /** Get a specific service function by name */
@@ -856,42 +856,68 @@ async function processField(fieldName, value, definition) {
856
856
  function isService(value) {
857
857
  return typeof value === "function" && "$fabric" in value;
858
858
  }
859
+ /**
860
+ * Run serializer hook if provided
861
+ * Returns transformed output or original if serializer returns undefined/null/void
862
+ */
863
+ async function runSerializer(data, serializer, context) {
864
+ if (!serializer) {
865
+ return data.output;
866
+ }
867
+ const result = await serializer(data, context);
868
+ if (result !== undefined && result !== null) {
869
+ return result;
870
+ }
871
+ return data.output;
872
+ }
859
873
  /**
860
874
  * Fabric a service function
861
875
  *
862
- * Service builds a function that initiates a "controller" step that:
876
+ * Service builds a function that:
863
877
  * - Parses the input if it is a string to object
864
878
  * - Fabrics each input field to its type
865
879
  * - Calls the validation function or regular expression or checks the array
866
- * - Calls the service function and returns the response
880
+ * - Calls the service function
881
+ * - Calls the serializer hook (can transform output)
882
+ * - Returns the response
867
883
  *
868
884
  * The returned function has config properties for introspection.
869
885
  */
870
886
  function fabricService(config) {
871
- const { input: inputDefinitions, service } = config;
887
+ const { input: inputDefinitions, serializer, service } = config;
872
888
  const handler = async (rawInput, context) => {
873
889
  // Parse input (handles string JSON)
874
890
  const parsedInput = parseInput(rawInput);
875
891
  // If no input definitions, pass through to service or return parsed input
876
892
  if (!inputDefinitions) {
893
+ let output;
877
894
  if (service) {
878
- return service(parsedInput, context);
895
+ output = await service(parsedInput, context);
879
896
  }
880
- return parsedInput;
897
+ else {
898
+ output = parsedInput;
899
+ }
900
+ // Run serializer
901
+ return (await runSerializer({ input: parsedInput, output }, serializer, context));
881
902
  }
882
903
  // Process all fields in parallel
883
904
  const entries = Object.entries(inputDefinitions);
884
905
  const processedValues = await Promise.all(entries.map(([fieldName, definition]) => processField(fieldName, parsedInput[fieldName], definition)));
885
906
  // Build processed input object
886
- const processedInput = {};
907
+ const processedInputObj = {};
887
908
  entries.forEach(([fieldName], index) => {
888
- processedInput[fieldName] = processedValues[index];
909
+ processedInputObj[fieldName] = processedValues[index];
889
910
  });
890
- // Return processed input if no service, otherwise call service
911
+ // Call service or return processed input
912
+ let output;
891
913
  if (service) {
892
- return service(processedInput, context);
914
+ output = await service(processedInputObj, context);
915
+ }
916
+ else {
917
+ output = processedInputObj;
893
918
  }
894
- return processedInput;
919
+ // Run serializer hook
920
+ return (await runSerializer({ input: processedInputObj, output }, serializer, context));
895
921
  };
896
922
  // Attach config properties directly to handler for flat access
897
923
  const typedHandler = handler;
@@ -902,6 +928,8 @@ function fabricService(config) {
902
928
  typedHandler.description = config.description;
903
929
  if (config.input !== undefined)
904
930
  typedHandler.input = config.input;
931
+ if (config.serializer !== undefined)
932
+ typedHandler.serializer = config.serializer;
905
933
  if (config.service !== undefined)
906
934
  typedHandler.service = config.service;
907
935
  return typedHandler;
@@ -920,6 +948,7 @@ function fabricService(config) {
920
948
  * - `alias` overrides service.alias
921
949
  * - `description` overrides service.description
922
950
  * - `input` overrides service.input
951
+ * - `serializer` overrides service.serializer
923
952
  *
924
953
  * The original Service is never mutated - a new Service is created when overrides
925
954
  * are applied.
@@ -943,7 +972,7 @@ function fabricService(config) {
943
972
  * ```
944
973
  */
945
974
  function resolveService(config) {
946
- const { alias, description, input, service } = config;
975
+ const { alias, description, input, serializer, service } = config;
947
976
  if (isService(service)) {
948
977
  // Service is pre-instantiated - config fields act as overrides
949
978
  // Create new Service with merged properties (config overrides service)
@@ -951,6 +980,7 @@ function resolveService(config) {
951
980
  alias: alias ?? service.alias,
952
981
  description: description ?? service.description,
953
982
  input: input ?? service.input,
983
+ serializer: serializer ?? service.serializer,
954
984
  service: service.service,
955
985
  });
956
986
  }
@@ -959,6 +989,7 @@ function resolveService(config) {
959
989
  alias,
960
990
  description,
961
991
  input,
992
+ serializer,
962
993
  service,
963
994
  });
964
995
  }