@yopdev/dev-server 3.0.2-RC → 3.0.2-RC2

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 (100) hide show
  1. package/.github/workflows/npm-publish.yml +34 -0
  2. package/__tests__/bootstrap.test.ts +89 -0
  3. package/__tests__/deferred.test.ts +86 -0
  4. package/__tests__/event-proxy.test.ts +42 -0
  5. package/__tests__/lambda-http-proxy.test.ts +179 -0
  6. package/dist/__tests__/bootstrap.test.d.ts +1 -0
  7. package/dist/__tests__/bootstrap.test.js +90 -0
  8. package/dist/__tests__/deferred.test.d.ts +1 -0
  9. package/dist/__tests__/deferred.test.js +76 -0
  10. package/dist/__tests__/event-proxy.test.d.ts +1 -0
  11. package/dist/__tests__/event-proxy.test.js +37 -0
  12. package/dist/__tests__/lambda-http-proxy.test.d.ts +1 -0
  13. package/dist/__tests__/lambda-http-proxy.test.js +135 -0
  14. package/dist/src/assert.d.ts +1 -0
  15. package/dist/src/assert.js +9 -0
  16. package/dist/src/cloudformation-dynamodb-table.d.ts +13 -0
  17. package/dist/src/cloudformation-dynamodb-table.js +45 -0
  18. package/dist/src/cloudformation-event-proxy.d.ts +13 -0
  19. package/dist/src/cloudformation-event-proxy.js +25 -0
  20. package/dist/src/cloudformation-lambda-http-proxy.d.ts +14 -0
  21. package/dist/src/cloudformation-lambda-http-proxy.js +62 -0
  22. package/dist/src/cloudformation.d.ts +28 -0
  23. package/dist/src/cloudformation.js +50 -0
  24. package/dist/src/config.d.ts +31 -0
  25. package/dist/src/config.js +2 -0
  26. package/dist/src/container.d.ts +18 -0
  27. package/dist/src/container.js +33 -0
  28. package/dist/src/deferred.d.ts +4 -0
  29. package/dist/src/deferred.js +45 -0
  30. package/dist/src/dev-server.d.ts +19 -0
  31. package/dist/src/dev-server.js +63 -0
  32. package/dist/src/dynamodb.d.ts +16 -0
  33. package/dist/src/dynamodb.js +48 -0
  34. package/dist/src/event-proxy.d.ts +13 -0
  35. package/dist/src/event-proxy.js +68 -0
  36. package/dist/src/factories.d.ts +3 -0
  37. package/dist/src/factories.js +16 -0
  38. package/dist/src/http-server.d.ts +25 -0
  39. package/dist/src/http-server.js +37 -0
  40. package/dist/src/index.d.ts +24 -0
  41. package/dist/src/index.js +46 -0
  42. package/dist/src/internal-queue.d.ts +11 -0
  43. package/dist/src/internal-queue.js +53 -0
  44. package/dist/src/lambda-http-proxy.d.ts +28 -0
  45. package/dist/src/lambda-http-proxy.js +50 -0
  46. package/dist/src/localstack.d.ts +11 -0
  47. package/dist/src/localstack.js +62 -0
  48. package/dist/src/mappers.d.ts +25 -0
  49. package/dist/src/mappers.js +176 -0
  50. package/dist/src/pre-traffic-hooks.d.ts +2 -0
  51. package/dist/src/pre-traffic-hooks.js +19 -0
  52. package/dist/src/responses.d.ts +5 -0
  53. package/dist/src/responses.js +25 -0
  54. package/dist/src/s3.d.ts +7 -0
  55. package/dist/src/s3.js +20 -0
  56. package/dist/src/scheduled-tasks.d.ts +6 -0
  57. package/dist/src/scheduled-tasks.js +20 -0
  58. package/dist/src/services.d.ts +22 -0
  59. package/dist/src/services.js +26 -0
  60. package/dist/src/sns-http-proxy.d.ts +28 -0
  61. package/dist/src/sns-http-proxy.js +66 -0
  62. package/dist/src/sns.d.ts +15 -0
  63. package/dist/src/sns.js +35 -0
  64. package/dist/src/sqs.d.ts +13 -0
  65. package/dist/src/sqs.js +33 -0
  66. package/dist/src/stoppable.d.ts +2 -0
  67. package/dist/src/stoppable.js +15 -0
  68. package/dist/src/tunnel.d.ts +10 -0
  69. package/dist/src/tunnel.js +52 -0
  70. package/jest.config.js +7 -0
  71. package/package.json +2 -5
  72. package/src/assert.ts +4 -0
  73. package/src/cloudformation-dynamodb-table.ts +97 -0
  74. package/src/cloudformation-event-proxy.ts +61 -0
  75. package/src/cloudformation-lambda-http-proxy.ts +125 -0
  76. package/src/cloudformation.ts +95 -0
  77. package/src/config.ts +34 -0
  78. package/src/container.ts +82 -0
  79. package/src/deferred.ts +60 -0
  80. package/src/dev-server.ts +78 -0
  81. package/src/dynamodb.ts +62 -0
  82. package/src/event-proxy.ts +101 -0
  83. package/src/factories.ts +19 -0
  84. package/src/http-server.ts +59 -0
  85. package/src/index.ts +32 -0
  86. package/src/internal-queue.ts +89 -0
  87. package/src/lambda-http-proxy.ts +111 -0
  88. package/src/localstack.ts +74 -0
  89. package/src/mappers.ts +231 -0
  90. package/src/pre-traffic-hooks.ts +24 -0
  91. package/src/responses.ts +28 -0
  92. package/src/s3.ts +24 -0
  93. package/src/scheduled-tasks.ts +31 -0
  94. package/src/services.ts +46 -0
  95. package/src/sns-http-proxy.ts +109 -0
  96. package/src/sns.ts +49 -0
  97. package/src/sqs.ts +46 -0
  98. package/src/stoppable.ts +10 -0
  99. package/src/tunnel.ts +32 -0
  100. package/tsconfig.json +9 -0
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.v2 = exports.v1 = exports.mapToLambdaSqsRecord = void 0;
4
+ const url_1 = require("url");
5
+ const lambda_http_proxy_1 = require("./lambda-http-proxy");
6
+ const mapToLambdaSqsRecord = (message) => {
7
+ if (!message.Body)
8
+ throw new Error('message Body must be present');
9
+ return {
10
+ messageId: 'N/A',
11
+ receiptHandle: 'N/A',
12
+ body: message.Body,
13
+ attributes: {
14
+ ApproximateReceiveCount: 'N/A',
15
+ SentTimestamp: 'N/A',
16
+ SenderId: 'N/A',
17
+ ApproximateFirstReceiveTimestamp: 'N/A',
18
+ },
19
+ messageAttributes: {},
20
+ md5OfBody: 'N/A',
21
+ eventSource: 'N/A',
22
+ eventSourceARN: 'N/A',
23
+ awsRegion: 'N/A',
24
+ };
25
+ };
26
+ exports.mapToLambdaSqsRecord = mapToLambdaSqsRecord;
27
+ const payloadV1JwtAuthorizerLambdaMapper = () => ({
28
+ newInstance: (request, body) => Promise.resolve(new DefaultLambdaMapper(request, body))
29
+ });
30
+ const payloadV2LambdaAuthorizerLambdaMapper = () => ({
31
+ newInstance: (request, body) => Promise.resolve(new DefaultLambdaMapper(request, body))
32
+ });
33
+ const payloadV1PathParameterResolver = {
34
+ locate: (event) => event.path,
35
+ store: (event, params) => event.pathParameters = params
36
+ };
37
+ const payloadV2PathParameterResolver = {
38
+ locate: (event) => event.rawPath,
39
+ store: (event, params) => event.pathParameters = params
40
+ };
41
+ const v1 = () => ({
42
+ mapper: payloadV1JwtAuthorizerLambdaMapper(),
43
+ resolver: payloadV1PathParameterResolver,
44
+ });
45
+ exports.v1 = v1;
46
+ const v2 = () => ({
47
+ mapper: payloadV2LambdaAuthorizerLambdaMapper(),
48
+ resolver: payloadV2PathParameterResolver,
49
+ });
50
+ exports.v2 = v2;
51
+ class DefaultLambdaMapper {
52
+ constructor(request, body) {
53
+ this.body = body;
54
+ this.toResponse = (response) => response.isBase64Encoded ?
55
+ new Base64EncodedResponse(response.statusCode, response.headers?.['content-type']?.toString(), response.headers?.['location']?.toString(), response.body, this.readSetCookies(response)) :
56
+ new StringResponse(response.statusCode, response.headers?.['content-type']?.toString(), response.headers?.['location']?.toString(), response.body, this.readSetCookies(response));
57
+ this.readSetCookies = (response) => {
58
+ // V2: structured response with cookies array
59
+ if ('cookies' in response && Array.isArray(response.cookies)) {
60
+ return response.cookies;
61
+ }
62
+ // V1: check multiValueHeaders first
63
+ if ('multiValueHeaders' in response && response.multiValueHeaders?.['Set-Cookie']) {
64
+ return response.multiValueHeaders['Set-Cookie'].map(c => c.toString());
65
+ }
66
+ // V1: fallback to single header
67
+ if ('headers' in response && response.headers?.['Set-Cookie']) {
68
+ const cookieHeader = response.headers['Set-Cookie'];
69
+ return Array.isArray(cookieHeader) ? cookieHeader.map(c => c.toString()) : [cookieHeader];
70
+ }
71
+ return [];
72
+ };
73
+ this.event = (context) => ({
74
+ version: '2.0',
75
+ rawPath: this.url.pathname,
76
+ rawQueryString: this.url.search,
77
+ routeKey: '',
78
+ httpMethod: this.method,
79
+ body: this.body,
80
+ headers: this.headers,
81
+ path: this.url.pathname,
82
+ pathParameters: null,
83
+ queryStringParameters: this.queryStringParameters,
84
+ resource: '',
85
+ multiValueHeaders: {},
86
+ isBase64Encoded: false,
87
+ multiValueQueryStringParameters: null,
88
+ stageVariables: null,
89
+ requestContext: {
90
+ domainName: this.headers.host,
91
+ domainPrefix: '',
92
+ http: {
93
+ method: this.method,
94
+ path: this.url.pathname,
95
+ protocol: 'http',
96
+ sourceIp: '127.0.0.1',
97
+ userAgent: this.headers['user-agent'],
98
+ },
99
+ routeKey: '',
100
+ time: new Date(this.time).toString(),
101
+ timeEpoch: this.time,
102
+ accountId: '',
103
+ apiId: '',
104
+ authorizer: context,
105
+ httpMethod: '',
106
+ identity: {
107
+ accessKey: '',
108
+ accountId: '',
109
+ apiKey: '',
110
+ apiKeyId: '',
111
+ caller: '',
112
+ clientCert: {
113
+ clientCertPem: '',
114
+ issuerDN: '',
115
+ serialNumber: '',
116
+ subjectDN: '',
117
+ validity: { notAfter: '', notBefore: '' },
118
+ },
119
+ cognitoAuthenticationProvider: '',
120
+ cognitoAuthenticationType: '',
121
+ cognitoIdentityId: '',
122
+ cognitoIdentityPoolId: '',
123
+ principalOrgId: '',
124
+ sourceIp: '',
125
+ user: '',
126
+ userAgent: '',
127
+ userArn: '',
128
+ },
129
+ path: '',
130
+ protocol: '',
131
+ requestId: '',
132
+ requestTimeEpoch: this.time,
133
+ resourceId: '',
134
+ resourcePath: '',
135
+ stage: '',
136
+ }
137
+ });
138
+ this.authorization = () => this.authorizationHeaderValue;
139
+ if (!request.url || !request.method)
140
+ throw new Error('url and method are required');
141
+ const url = new url_1.URL(request.url, `http://${request.headers.host}`);
142
+ const qsp = {};
143
+ url.searchParams.forEach((v, k) => (qsp[k] = decodeURIComponent(v)));
144
+ this.queryStringParameters = qsp;
145
+ const headers = {};
146
+ if (request.headers['content-type'])
147
+ headers['content-type'] = request.headers['content-type'];
148
+ if (request.headers['accept'])
149
+ headers['accept'] = request.headers['accept'];
150
+ if (request.headers['origin'])
151
+ headers['origin'] = request.headers['origin'];
152
+ if (request.headers['cookie'])
153
+ headers['cookie'] = request.headers['cookie'];
154
+ const authorizationHeaderValue = request.headers['authorization'];
155
+ if (authorizationHeaderValue) {
156
+ headers['authorization'] = authorizationHeaderValue;
157
+ this.authorizationHeaderValue = authorizationHeaderValue;
158
+ }
159
+ this.headers = headers;
160
+ this.url = url;
161
+ this.time = 1428582896000;
162
+ this.method = request.method;
163
+ }
164
+ }
165
+ class StringResponse extends lambda_http_proxy_1.Response {
166
+ constructor(statusCode, contentType, location, body, cookies) {
167
+ super(statusCode, contentType, location, cookies);
168
+ this.body = () => body;
169
+ }
170
+ }
171
+ class Base64EncodedResponse extends lambda_http_proxy_1.Response {
172
+ constructor(statusCode, contentType, location, body, cookies) {
173
+ super(statusCode, contentType, location, cookies);
174
+ this.body = () => Buffer.from(body, 'base64');
175
+ }
176
+ }
@@ -0,0 +1,2 @@
1
+ import { Service } from './services';
2
+ export declare const newPreTrafficHooks: (name: string, hooks: () => Promise<void>[]) => Service<any>;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.newPreTrafficHooks = void 0;
4
+ const logging_1 = require("@yopdev/logging");
5
+ const services_1 = require("./services");
6
+ const newPreTrafficHooks = (name, hooks) => new services_1.Service(new PreTrafficHooks(name, hooks));
7
+ exports.newPreTrafficHooks = newPreTrafficHooks;
8
+ class PreTrafficHooks {
9
+ constructor(name, hooks) {
10
+ this.name = name;
11
+ this.hooks = hooks;
12
+ this.start = async () => Promise
13
+ .all(this.hooks())
14
+ .then(() => undefined);
15
+ this.stop = async () => Promise.resolve();
16
+ this.LOGGER = logging_1.LoggerFactory.create(`PRETRAFFIC[${name}]`);
17
+ this.LOGGER.info('%i hooks registered', hooks.length);
18
+ }
19
+ }
@@ -0,0 +1,5 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { ServerResponse } from 'http';
4
+ export declare function internalServerError(res: ServerResponse, body: any): void;
5
+ export declare function writeResponse(res: ServerResponse, statusCode: number, body: string | Buffer, contentType?: string, location?: string, cookies?: string[]): void;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeResponse = exports.internalServerError = void 0;
4
+ const logging_1 = require("@yopdev/logging");
5
+ const LOG = logging_1.LoggerFactory.create('RESPONSES');
6
+ function internalServerError(res, body) {
7
+ LOG.error(body, 'internal server error handled');
8
+ return writeResponse(res, 500, 'internal server error. check system logs');
9
+ }
10
+ exports.internalServerError = internalServerError;
11
+ function writeResponse(res, statusCode, body, contentType, location, cookies) {
12
+ res.setHeader('Access-Control-Allow-Origin', '*');
13
+ res.setHeader('Access-Control-Allow-Methods', '*');
14
+ res.setHeader('Access-Control-Allow-Headers', '*');
15
+ if (cookies) {
16
+ res.setHeader('Set-Cookie', cookies);
17
+ }
18
+ if (contentType)
19
+ res.setHeader('Content-Type', contentType);
20
+ if (location)
21
+ res.setHeader('Location', location);
22
+ res.statusCode = statusCode;
23
+ res.end(body);
24
+ }
25
+ exports.writeResponse = writeResponse;
@@ -0,0 +1,7 @@
1
+ import { CORSConfiguration, S3Client } from '@aws-sdk/client-s3';
2
+ import { AwsConfig } from './config';
3
+ export declare class S3 {
4
+ readonly client: S3Client;
5
+ constructor(aws: AwsConfig);
6
+ createBucket: (name: string, cors?: CORSConfiguration) => Promise<import("@aws-sdk/client-s3").PutBucketCorsCommandOutput>;
7
+ }
package/dist/src/s3.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.S3 = void 0;
4
+ const client_s3_1 = require("@aws-sdk/client-s3");
5
+ const logging_1 = require("@yopdev/logging");
6
+ const LOGGER = logging_1.LoggerFactory.create('S3');
7
+ class S3 {
8
+ constructor(aws) {
9
+ this.createBucket = async (name, cors) => this.client.send(new client_s3_1.CreateBucketCommand({
10
+ Bucket: name,
11
+ }))
12
+ .then(() => cors && this.client.send(new client_s3_1.PutBucketCorsCommand({
13
+ Bucket: name,
14
+ CORSConfiguration: cors
15
+ })))
16
+ .tap(() => LOGGER.debug('bucket %s created', name));
17
+ this.client = new client_s3_1.S3Client({ forcePathStyle: true, ...aws });
18
+ }
19
+ }
20
+ exports.S3 = S3;
@@ -0,0 +1,6 @@
1
+ import { Service } from './services';
2
+ export declare const newScheduledTasks: (name: string, schedules: Rate[]) => Service<any>;
3
+ export type Rate = {
4
+ frequency: number;
5
+ task: () => Promise<unknown>;
6
+ };
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.newScheduledTasks = void 0;
4
+ const logging_1 = require("@yopdev/logging");
5
+ const timers_1 = require("timers");
6
+ const services_1 = require("./services");
7
+ const newScheduledTasks = (name, schedules) => new services_1.Service(new ScheduledTasks(name, schedules));
8
+ exports.newScheduledTasks = newScheduledTasks;
9
+ class ScheduledTasks {
10
+ constructor(name, schedules) {
11
+ this.name = name;
12
+ this.schedules = schedules;
13
+ this.intervals = [];
14
+ this.start = async () => Promise.resolve(this.schedules.forEach((schedule) => this.intervals.push(setInterval(schedule.task, schedule.frequency * 1000))))
15
+ .then(() => undefined);
16
+ this.stop = async () => Promise.resolve(this.intervals.forEach((interval) => (0, timers_1.clearInterval)(interval)));
17
+ this.LOGGER = logging_1.LoggerFactory.create(`SCHEDULER[${name}]`);
18
+ this.LOGGER.info('registered %i scheduled tasks', schedules.length);
19
+ }
20
+ }
@@ -0,0 +1,22 @@
1
+ import { DevServerConfig } from "./config";
2
+ export interface Lifecycle<I> extends Startable<I>, Stoppable {
3
+ name: string;
4
+ }
5
+ export interface Startable<T> {
6
+ start(config: DevServerConfig): Promise<T>;
7
+ }
8
+ export interface Stoppable {
9
+ stop(): Promise<void>;
10
+ }
11
+ export type Callback<I> = (instance: I) => Promise<void>;
12
+ export declare class Service<I> implements Lifecycle<I> {
13
+ private readonly callback?;
14
+ private readonly DEFAULT_CALLBACK;
15
+ readonly name: string;
16
+ readonly start: (config: DevServerConfig) => Promise<I>;
17
+ readonly stop: () => Promise<void>;
18
+ private doStop;
19
+ private readonly LOGGER;
20
+ constructor(service: Lifecycle<I>, callback?: Callback<I>);
21
+ private callbackOrDefault;
22
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Service = void 0;
4
+ const logging_1 = require("@yopdev/logging");
5
+ class Service {
6
+ constructor(service, callback) {
7
+ this.callback = callback;
8
+ this.DEFAULT_CALLBACK = () => Promise.resolve(this.LOGGER.debug('no callback'));
9
+ this.callbackOrDefault = async (instance) => (this.callback ?? this.DEFAULT_CALLBACK)(instance);
10
+ this.name = service.name;
11
+ this.LOGGER = logging_1.LoggerFactory.create(`SERVICE[${this.name}]`);
12
+ const stop = async () => service.stop();
13
+ this.start = (config) => service.start(config)
14
+ .catch((error) => stop()
15
+ .then(() => this.doStop = () => Promise.resolve(this.LOGGER.error(error, 'failed')))
16
+ .then(() => Promise.reject(error)))
17
+ .then((started) => this
18
+ .callbackOrDefault(started)
19
+ .then(() => this.LOGGER.info('started'))
20
+ .then(() => started));
21
+ this.doStop = () => stop()
22
+ .then(() => this.LOGGER.info('stopped'));
23
+ this.stop = () => this.doStop();
24
+ }
25
+ }
26
+ exports.Service = Service;
@@ -0,0 +1,28 @@
1
+ import { HttpSettings } from "./http-server";
2
+ import { IncomingMessage, ServerResponse } from "http";
3
+ import { Lifecycle, Service, Callback } from "./services";
4
+ import { DevServerConfig } from "./config";
5
+ export declare const newSnsHttpProxy: (name: string, config: {
6
+ settings: HttpSettings;
7
+ method: string;
8
+ subject?: string;
9
+ topic?: string;
10
+ }, callback?: Callback<string>) => Service<string>;
11
+ export declare class SnsHttpProxy implements Lifecycle<string> {
12
+ readonly name: string;
13
+ private readonly settings;
14
+ private readonly method;
15
+ private readonly subject?;
16
+ private readonly topic?;
17
+ private LOGGER;
18
+ private server;
19
+ private config;
20
+ constructor(name: string, settings: HttpSettings, method: string, subject?: string, topic?: string);
21
+ start: (config: DevServerConfig) => Promise<string>;
22
+ stop: () => Promise<void>;
23
+ handler: (method: string, subject?: string) => (request: IncomingMessage, body: string, response: ServerResponse) => Promise<void>;
24
+ extractMessage: (method: string, request: IncomingMessage, body: string) => string;
25
+ private onlyWhenStarted;
26
+ private extractQueryString;
27
+ private extractMessageAttributes;
28
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SnsHttpProxy = exports.newSnsHttpProxy = void 0;
4
+ const logging_1 = require("@yopdev/logging");
5
+ const http_server_1 = require("./http-server");
6
+ const responses_1 = require("./responses");
7
+ const services_1 = require("./services");
8
+ const assert_1 = require("./assert");
9
+ const newSnsHttpProxy = (name, config, callback) => new services_1.Service(new SnsHttpProxy(name, config.settings, config.method, config.subject, config.topic), callback);
10
+ exports.newSnsHttpProxy = newSnsHttpProxy;
11
+ class SnsHttpProxy {
12
+ constructor(name, settings, method, subject, topic) {
13
+ this.name = name;
14
+ this.settings = settings;
15
+ this.method = method;
16
+ this.subject = subject;
17
+ this.topic = topic;
18
+ this.start = (config) => Promise.resolve(this.server)
19
+ .then(server => server.start())
20
+ .tap(() => {
21
+ this.config = {
22
+ topic: this.topic ?? config.eventsProxy.topic.arn,
23
+ sns: config.sns
24
+ };
25
+ })
26
+ .then((endpoint) => endpoint);
27
+ this.stop = () => this.onlyWhenStarted(this.server).stop()
28
+ .then(() => this.LOGGER.debug('stopped'));
29
+ this.handler = (method, subject) => (request, body, response) => Promise.resolve(((0, assert_1.assertNotUndefined)(this.config)))
30
+ .then((config) => config.sns
31
+ .publish({ arn: config.topic }, this.extractMessage(method, request, body), subject, this.extractMessageAttributes(request))
32
+ .then(() => (0, responses_1.writeResponse)(response, 200, ''))
33
+ .catch((e) => {
34
+ this.LOGGER.error(e, 'request failed to execute');
35
+ (0, responses_1.internalServerError)(response, e.body);
36
+ }));
37
+ this.extractMessage = (method, request, body) => {
38
+ switch (method) {
39
+ case 'GET':
40
+ return this.extractQueryString(request) || '';
41
+ case 'POST':
42
+ return body;
43
+ default:
44
+ throw new Error();
45
+ }
46
+ };
47
+ this.onlyWhenStarted = (server) => (0, assert_1.assertNotUndefined)(server, 'not started');
48
+ this.extractQueryString = (request) => request.url?.substring(request.url?.indexOf('?') + 1);
49
+ this.extractMessageAttributes = (request) => {
50
+ const initialValue = {};
51
+ return Object.keys(request.headers).reduce((obj, item) => {
52
+ return {
53
+ ...obj,
54
+ [item]: {
55
+ DataType: 'String',
56
+ StringValue: request.headers[item],
57
+ },
58
+ };
59
+ }, initialValue);
60
+ };
61
+ this.server = new http_server_1.HttpServer(this.name, this.settings, this.handler(this.method, this.subject));
62
+ this.LOGGER = logging_1.LoggerFactory.create(`HTTP->SNS[${name}]`);
63
+ this.LOGGER.info('forwarding %s %s events to %s', subject ? `events with ${subject} subject` : 'all', method, topic ?? 'the event bus');
64
+ }
65
+ }
66
+ exports.SnsHttpProxy = SnsHttpProxy;
@@ -0,0 +1,15 @@
1
+ import { MessageAttributeValue, SNSClient } from "@aws-sdk/client-sns";
2
+ import { AwsConfig } from "./config";
3
+ export declare class Sns {
4
+ readonly client: SNSClient;
5
+ constructor(aws: AwsConfig);
6
+ createTopic: (topic: string) => Promise<string>;
7
+ createSubscription: (topic: {
8
+ arn: string;
9
+ }, queue: {
10
+ arn: string;
11
+ }) => Promise<void>;
12
+ publish: (topic: {
13
+ arn: string;
14
+ }, body: string, subject?: string, attributes?: Record<string, MessageAttributeValue>) => Promise<void>;
15
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Sns = void 0;
4
+ const client_sns_1 = require("@aws-sdk/client-sns");
5
+ const assert_1 = require("./assert");
6
+ const logging_1 = require("@yopdev/logging");
7
+ const LOGGER = logging_1.LoggerFactory.create('SNS');
8
+ class Sns {
9
+ constructor(aws) {
10
+ this.createTopic = async (topic) => this.client
11
+ .send(new client_sns_1.CreateTopicCommand({
12
+ Name: topic,
13
+ }))
14
+ .then((o) => (0, assert_1.assertNotUndefined)(o.TopicArn, 'TopicArn is not present'))
15
+ .tap((arn) => LOGGER.debug('topic %o created', arn));
16
+ this.createSubscription = async (topic, queue) => this.client
17
+ .send(new client_sns_1.SubscribeCommand({
18
+ TopicArn: topic.arn,
19
+ Protocol: 'sqs',
20
+ Endpoint: queue.arn,
21
+ }))
22
+ .then(() => LOGGER.debug('subscription %s->%s created', topic.arn, queue.arn));
23
+ this.publish = async (topic, body, subject, attributes) => this.client
24
+ .send(new client_sns_1.PublishCommand({
25
+ TopicArn: topic.arn,
26
+ Message: body,
27
+ MessageAttributes: attributes,
28
+ Subject: subject,
29
+ }))
30
+ .then(() => LOGGER.trace('published %s[%s]', topic.arn, subject))
31
+ .then();
32
+ this.client = new client_sns_1.SNSClient(aws);
33
+ }
34
+ }
35
+ exports.Sns = Sns;
@@ -0,0 +1,13 @@
1
+ import { SQSClient } from "@aws-sdk/client-sqs";
2
+ import { AwsConfig } from "./config";
3
+ export declare class Sqs {
4
+ readonly client: SQSClient;
5
+ constructor(config: AwsConfig);
6
+ createStandardQueue: (name: string) => Promise<Queue>;
7
+ createFifoQueue: (name: string) => Promise<Queue>;
8
+ private createNamedQueue;
9
+ }
10
+ export type Queue = {
11
+ arn: string;
12
+ url: string;
13
+ };
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Sqs = void 0;
4
+ const client_sqs_1 = require("@aws-sdk/client-sqs");
5
+ const assert_1 = require("./assert");
6
+ const logging_1 = require("@yopdev/logging");
7
+ const QUEUE_NAME_ATTRIBUTE_NAME = 'QueueArn';
8
+ const LOGGER = logging_1.LoggerFactory.create('SQS');
9
+ class Sqs {
10
+ constructor(config) {
11
+ this.createStandardQueue = async (name) => this.createNamedQueue(name, false);
12
+ this.createFifoQueue = async (name) => this.createNamedQueue(name, true);
13
+ this.createNamedQueue = async (name, fifo) => this.client
14
+ .send(new client_sqs_1.CreateQueueCommand({
15
+ QueueName: fifo ? `${name}.fifo` : name,
16
+ Attributes: {
17
+ FifoQueue: fifo ? 'true' : undefined,
18
+ }
19
+ }))
20
+ .then((create) => this.client
21
+ .send(new client_sqs_1.GetQueueAttributesCommand({
22
+ QueueUrl: create.QueueUrl,
23
+ AttributeNames: [QUEUE_NAME_ATTRIBUTE_NAME],
24
+ }))
25
+ .then((attributes) => ({
26
+ url: (0, assert_1.assertNotUndefined)(create.QueueUrl),
27
+ arn: (0, assert_1.assertNotUndefined)(attributes.Attributes)[QUEUE_NAME_ATTRIBUTE_NAME],
28
+ })))
29
+ .tap((queue) => LOGGER.debug('created %s', queue.arn));
30
+ this.client = new client_sqs_1.SQSClient(config);
31
+ }
32
+ }
33
+ exports.Sqs = Sqs;
@@ -0,0 +1,2 @@
1
+ import { Consumer } from "sqs-consumer";
2
+ export declare const stopConsumer: (queueWaitTimeSecs: number, target?: Consumer) => Promise<void>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stopConsumer = void 0;
4
+ const stopConsumer = async (queueWaitTimeSecs, target) => {
5
+ if (target) {
6
+ target.stop();
7
+ return new Promise((resolve) => {
8
+ setTimeout(resolve, queueWaitTimeSecs * 1000 + 1);
9
+ });
10
+ }
11
+ else {
12
+ return Promise.resolve();
13
+ }
14
+ };
15
+ exports.stopConsumer = stopConsumer;
@@ -0,0 +1,10 @@
1
+ import { Service } from './services';
2
+ export declare const terminate: () => Promise<void>;
3
+ export declare const newTunnel: (config: {
4
+ name: string;
5
+ port: number;
6
+ fixed?: {
7
+ name: string;
8
+ token: string;
9
+ };
10
+ }) => Service<string>;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.newTunnel = exports.terminate = void 0;
27
+ const services_1 = require("./services");
28
+ const logging_1 = require("@yopdev/logging");
29
+ const ngrok = __importStar(require("@ngrok/ngrok"));
30
+ const assert_1 = require("./assert");
31
+ const terminate = async () => ngrok.kill();
32
+ exports.terminate = terminate;
33
+ const newTunnel = (config) => new services_1.Service(new Tunnel(config.name, config.port, config.fixed));
34
+ exports.newTunnel = newTunnel;
35
+ class Tunnel {
36
+ constructor(name, port, domain) {
37
+ this.name = name;
38
+ this.port = port;
39
+ this.domain = domain;
40
+ this.start = async () => ngrok
41
+ .forward({
42
+ proto: 'http',
43
+ domain: this.domain?.name,
44
+ addr: this.port,
45
+ authtoken: this.domain?.token,
46
+ })
47
+ .then((listener) => (0, assert_1.assertNotUndefined)(listener.url()))
48
+ .tap((url) => this.logger.debug(url));
49
+ this.stop = async () => ngrok.disconnect();
50
+ this.logger = logging_1.LoggerFactory.create(`TUNNEL[${name}]`);
51
+ }
52
+ }
package/jest.config.js ADDED
@@ -0,0 +1,7 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ module.exports = {
3
+ preset: "ts-jest",
4
+ testPathIgnorePatterns: ["/dist/"],
5
+ testRegex: "/__tests__/.*$",
6
+ testTimeout: 60000,
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yopdev/dev-server",
3
- "version": "3.0.2-RC",
3
+ "version": "3.0.2-RC2",
4
4
  "scripts": {
5
5
  "compile": "tsc",
6
6
  "pretest": "npm run compile",
@@ -35,8 +35,5 @@
35
35
  },
36
36
  "main": "dist/src/index.js",
37
37
  "exports": "./dist/src/index.js",
38
- "types": "dist/src/index.d.ts",
39
- "files": [
40
- "dist/src"
41
- ]
38
+ "types": "dist/src/index.d.ts"
42
39
  }
package/src/assert.ts ADDED
@@ -0,0 +1,4 @@
1
+ export function assertNotUndefined<T>(value: T | undefined, message = 'value is undefined'): T {
2
+ if (value === undefined) throw new Error(message);
3
+ return value;
4
+ }