@geekmidas/constructs 0.0.2 → 0.0.3

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.
@@ -13,7 +13,12 @@ import {
13
13
  import type { StandardSchemaV1 } from '@standard-schema/spec';
14
14
  import { publishConstructEvents } from '../publisher';
15
15
  import type { HttpMethod } from '../types';
16
- import { Endpoint, type EndpointSchemas } from './Endpoint';
16
+ import {
17
+ Endpoint,
18
+ type EndpointSchemas,
19
+ ResponseBuilder,
20
+ type ResponseWithMetadata,
21
+ } from './Endpoint';
17
22
 
18
23
  export class TestEndpointAdaptor<
19
24
  TRoute extends string,
@@ -79,7 +84,10 @@ export class TestEndpointAdaptor<
79
84
  TEventPublisher,
80
85
  TEventPublisherServiceName
81
86
  >,
82
- ): Promise<InferStandardSchema<TOutSchema>> {
87
+ ): Promise<
88
+ | InferStandardSchema<TOutSchema>
89
+ | ResponseWithMetadata<InferStandardSchema<TOutSchema>>
90
+ > {
83
91
  const body = await this.endpoint.parseInput((ctx as any).body, 'body');
84
92
  const query = await this.endpoint.parseInput((ctx as any).query, 'query');
85
93
  const params = await this.endpoint.parseInput(
@@ -88,6 +96,7 @@ export class TestEndpointAdaptor<
88
96
  );
89
97
 
90
98
  const header = Endpoint.createHeaders(ctx.headers);
99
+ const cookie = Endpoint.createCookies(ctx.headers.cookie);
91
100
  const logger = this.endpoint.logger.child({
92
101
  route: this.endpoint.route,
93
102
  host: ctx.headers.host,
@@ -97,23 +106,47 @@ export class TestEndpointAdaptor<
97
106
  logger,
98
107
  services: ctx.services,
99
108
  header,
109
+ cookie,
100
110
  });
101
111
 
102
- const response = await this.endpoint.handler({
103
- body,
104
- query,
105
- params,
106
- session,
107
- services: ctx.services,
108
- logger,
109
- header,
110
- } as any);
112
+ const responseBuilder = new ResponseBuilder();
113
+ const response = await this.endpoint.handler(
114
+ {
115
+ body,
116
+ query,
117
+ params,
118
+ session,
119
+ services: ctx.services,
120
+ logger,
121
+ header,
122
+ cookie,
123
+ } as any,
124
+ responseBuilder,
125
+ );
126
+
127
+ // Check if response has metadata
128
+ let data = response;
129
+ let metadata = responseBuilder.getMetadata();
111
130
 
112
- const output = await this.endpoint.parseOutput(response);
131
+ if (Endpoint.hasMetadata(response)) {
132
+ data = response.data;
133
+ metadata = response.metadata;
134
+ }
135
+
136
+ const output = await this.endpoint.parseOutput(data);
113
137
  ctx.publisher && (await this.serviceDiscovery.register([ctx.publisher]));
114
138
 
115
139
  await publishConstructEvents(this.endpoint, output, this.serviceDiscovery);
116
140
 
141
+ // Return with metadata if any was set
142
+ if (
143
+ (metadata.headers && Object.keys(metadata.headers).length > 0) ||
144
+ (metadata.cookies && metadata.cookies.size > 0) ||
145
+ metadata.status
146
+ ) {
147
+ return { data: output, metadata };
148
+ }
149
+
117
150
  return output;
118
151
  }
119
152
  }
@@ -924,4 +924,244 @@ describe('AmazonApiGatewayV1Endpoint', () => {
924
924
  );
925
925
  });
926
926
  });
927
+
928
+ describe('response metadata', () => {
929
+ it('should set response cookies', async () => {
930
+ const endpoint = new Endpoint({
931
+ route: '/test',
932
+ method: 'GET',
933
+ fn: async (_, response) => {
934
+ response.cookie('session', 'abc123', {
935
+ httpOnly: true,
936
+ secure: true,
937
+ });
938
+ return { success: true };
939
+ },
940
+ input: {},
941
+ output: z.object({ success: z.boolean() }),
942
+ services: [],
943
+ logger: mockLogger,
944
+ timeout: undefined,
945
+ status: undefined,
946
+ getSession: undefined,
947
+ authorize: undefined,
948
+ description: 'Test endpoint',
949
+ });
950
+
951
+ const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
952
+ const handler = adapter.handler;
953
+
954
+ const event = createMockV1Event();
955
+ const context = createMockContext();
956
+ const response = await handler(event, context);
957
+
958
+ expect(response.multiValueHeaders?.['Set-Cookie']).toEqual([
959
+ 'session=abc123; HttpOnly; Secure',
960
+ ]);
961
+ expect(response.statusCode).toBe(200);
962
+ expect(response.body).toBe(JSON.stringify({ success: true }));
963
+ });
964
+
965
+ it('should set custom headers', async () => {
966
+ const endpoint = new Endpoint({
967
+ route: '/test',
968
+ method: 'GET',
969
+ fn: async (_, response) => {
970
+ response.header('X-Custom-Header', 'custom-value');
971
+ response.header('X-Request-Id', '12345');
972
+ return { success: true };
973
+ },
974
+ input: {},
975
+ output: z.object({ success: z.boolean() }),
976
+ services: [],
977
+ logger: mockLogger,
978
+ timeout: undefined,
979
+ status: undefined,
980
+ getSession: undefined,
981
+ authorize: undefined,
982
+ description: 'Test endpoint',
983
+ });
984
+
985
+ const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
986
+ const handler = adapter.handler;
987
+
988
+ const event = createMockV1Event();
989
+ const context = createMockContext();
990
+ const response = await handler(event, context);
991
+
992
+ expect(response.headers).toEqual({
993
+ 'X-Custom-Header': 'custom-value',
994
+ 'X-Request-Id': '12345',
995
+ });
996
+ });
997
+
998
+ it('should set custom status code', async () => {
999
+ const endpoint = new Endpoint({
1000
+ route: '/test',
1001
+ method: 'POST',
1002
+ fn: async (_, response) => {
1003
+ response.status(201);
1004
+ return { id: '123' };
1005
+ },
1006
+ input: {},
1007
+ output: z.object({ id: z.string() }),
1008
+ services: [],
1009
+ logger: mockLogger,
1010
+ timeout: undefined,
1011
+ status: undefined,
1012
+ getSession: undefined,
1013
+ authorize: undefined,
1014
+ description: 'Test endpoint',
1015
+ });
1016
+
1017
+ const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
1018
+ const handler = adapter.handler;
1019
+
1020
+ const event = createMockV1Event({ httpMethod: 'POST' });
1021
+ const context = createMockContext();
1022
+ const response = await handler(event, context);
1023
+
1024
+ expect(response.statusCode).toBe(201);
1025
+ });
1026
+
1027
+ it('should combine cookies, headers, and status', async () => {
1028
+ const endpoint = new Endpoint({
1029
+ route: '/test',
1030
+ method: 'POST',
1031
+ fn: async (_, response) => {
1032
+ response
1033
+ .status(201)
1034
+ .header('Location', '/test/123')
1035
+ .cookie('session', 'abc123', { httpOnly: true })
1036
+ .cookie('theme', 'dark');
1037
+ return { id: '123' };
1038
+ },
1039
+ input: {},
1040
+ output: z.object({ id: z.string() }),
1041
+ services: [],
1042
+ logger: mockLogger,
1043
+ timeout: undefined,
1044
+ status: undefined,
1045
+ getSession: undefined,
1046
+ authorize: undefined,
1047
+ description: 'Test endpoint',
1048
+ });
1049
+
1050
+ const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
1051
+ const handler = adapter.handler;
1052
+
1053
+ const event = createMockV1Event({ httpMethod: 'POST' });
1054
+ const context = createMockContext();
1055
+ const response = await handler(event, context);
1056
+
1057
+ expect(response.statusCode).toBe(201);
1058
+ expect(response.headers).toEqual({ Location: '/test/123' });
1059
+ expect(response.multiValueHeaders?.['Set-Cookie']).toEqual([
1060
+ 'session=abc123; HttpOnly',
1061
+ 'theme=dark',
1062
+ ]);
1063
+ });
1064
+
1065
+ it('should delete cookies', async () => {
1066
+ const endpoint = new Endpoint({
1067
+ route: '/test',
1068
+ method: 'GET',
1069
+ fn: async (_, response) => {
1070
+ response.deleteCookie('session', {
1071
+ path: '/',
1072
+ domain: '.example.com',
1073
+ });
1074
+ return { success: true };
1075
+ },
1076
+ input: {},
1077
+ output: z.object({ success: z.boolean() }),
1078
+ services: [],
1079
+ logger: mockLogger,
1080
+ timeout: undefined,
1081
+ status: undefined,
1082
+ getSession: undefined,
1083
+ authorize: undefined,
1084
+ description: 'Test endpoint',
1085
+ });
1086
+
1087
+ const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
1088
+ const handler = adapter.handler;
1089
+
1090
+ const event = createMockV1Event();
1091
+ const context = createMockContext();
1092
+ const response = await handler(event, context);
1093
+
1094
+ expect(response.multiValueHeaders?.['Set-Cookie']).toEqual([
1095
+ 'session=; Domain=.example.com; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0',
1096
+ ]);
1097
+ });
1098
+
1099
+ it('should use send() method with metadata', async () => {
1100
+ const endpoint = new Endpoint({
1101
+ route: '/test',
1102
+ method: 'GET',
1103
+ fn: async (_, response) => {
1104
+ return response
1105
+ .status(201)
1106
+ .header('X-Custom', 'value')
1107
+ .cookie('session', 'abc123')
1108
+ .send({ id: '123' });
1109
+ },
1110
+ input: {},
1111
+ output: z.object({ id: z.string() }),
1112
+ services: [],
1113
+ logger: mockLogger,
1114
+ timeout: undefined,
1115
+ status: undefined,
1116
+ getSession: undefined,
1117
+ authorize: undefined,
1118
+ description: 'Test endpoint',
1119
+ });
1120
+
1121
+ const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
1122
+ const handler = adapter.handler;
1123
+
1124
+ const event = createMockV1Event();
1125
+ const context = createMockContext();
1126
+ const response = await handler(event, context);
1127
+
1128
+ expect(response.statusCode).toBe(201);
1129
+ expect(response.headers).toEqual({ 'X-Custom': 'value' });
1130
+ expect(response.multiValueHeaders?.['Set-Cookie']).toEqual([
1131
+ 'session=abc123',
1132
+ ]);
1133
+ expect(response.body).toBe(JSON.stringify({ id: '123' }));
1134
+ });
1135
+
1136
+ it('should return simple response without metadata when not using response builder', async () => {
1137
+ const endpoint = new Endpoint({
1138
+ route: '/test',
1139
+ method: 'GET',
1140
+ fn: async () => ({ success: true }),
1141
+ input: {},
1142
+ output: z.object({ success: z.boolean() }),
1143
+ services: [],
1144
+ logger: mockLogger,
1145
+ timeout: undefined,
1146
+ status: undefined,
1147
+ getSession: undefined,
1148
+ authorize: undefined,
1149
+ description: 'Test endpoint',
1150
+ });
1151
+
1152
+ const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
1153
+ const handler = adapter.handler;
1154
+
1155
+ const event = createMockV1Event();
1156
+ const context = createMockContext();
1157
+ const response = await handler(event, context);
1158
+
1159
+ expect(response).toEqual({
1160
+ statusCode: 200,
1161
+ body: JSON.stringify({ success: true }),
1162
+ });
1163
+ expect(response.headers).toBeUndefined();
1164
+ expect(response.multiValueHeaders).toBeUndefined();
1165
+ });
1166
+ });
927
1167
  });