UNPKG

@the_pixelport/aws-lambda-graphql

Version:

Apollo server for AWS Lambda with WebSocket subscriptions support over API Gateway v1 + v2

184 lines 8.51 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DynamoDBConnectionManager = void 0; const assert_1 = __importDefault(require("assert")); const client_apigatewaymanagementapi_1 = require("@aws-sdk/client-apigatewaymanagementapi"); const client_dynamodb_1 = require("@aws-sdk/client-dynamodb"); const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb"); const errors_1 = require("./errors"); const helpers_1 = require("./helpers"); const isTTLExpired_1 = require("./helpers/isTTLExpired"); const DEFAULT_TTL = 7200; /** * DynamoDBConnectionManager * * Stores connections in DynamoDB table (default table name is Connections, you can override that) */ class DynamoDBConnectionManager { constructor({ apiGatewayManager, connectionsTable = 'Connections', dynamoDbClient, subscriptions, ttl = DEFAULT_TTL, debug = false, }) { this.hydrateConnection = async (connectionId, options) => { const { retryCount = 0, timeout = 50 } = options || {}; // if connection is not found, throw so we can terminate connection let connection; for (let i = 0; i <= retryCount; i++) { const result = await this.db.send(new lib_dynamodb_1.GetCommand({ TableName: this.connectionsTable, Key: { id: connectionId, }, })); if (result.Item) { // Jump out of loop connection = result.Item; break; } // wait for another round await new Promise((r) => setTimeout(r, timeout)); } if (!connection || (0, isTTLExpired_1.isTTLExpired)(connection.ttl)) { throw new errors_1.ConnectionNotFoundError(`Connection ${connectionId} not found`); } return connection; }; this.setConnectionData = async (data, { id }) => { await this.db.send(new lib_dynamodb_1.UpdateCommand({ TableName: this.connectionsTable, Key: { id, }, UpdateExpression: 'set #data = :data', ExpressionAttributeValues: { ':data': data, }, ExpressionAttributeNames: { '#data': 'data', }, })); }; this.registerConnection = async ({ connectionId, endpoint, }) => { const connection = { id: connectionId, data: { endpoint, context: {}, isInitialized: false }, }; console.log('[DEBUG] DynamoDBConnectionManager.registerConnection:', { connectionId, endpoint, connectionData: connection.data, }); if (this.debug) console.log(`Connected ${connection.id}`, connection.data); await this.db.send(new lib_dynamodb_1.PutCommand({ TableName: this.connectionsTable, Item: { createdAt: new Date().toString(), id: connection.id, data: connection.data, ...(this.ttl === false || this.ttl == null ? {} : { ttl: (0, helpers_1.computeTTL)(this.ttl), }), }, })); return connection; }; this.sendToConnection = async (connection, payload) => { var _a; console.log('[DEBUG] DynamoDBConnectionManager.sendToConnection:', { connectionId: connection.id, endpoint: connection.data.endpoint, payloadLength: payload.length, }); try { await this.createApiGatewayManager(connection.data.endpoint).send(new client_apigatewaymanagementapi_1.PostToConnectionCommand({ ConnectionId: connection.id, Data: payload, })); } catch (e) { // this is stale connection // remove it from DB if (e && ((_a = e.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode) === 410) { await this.unregisterConnection(connection); } else { throw e; } } }; this.unregisterConnection = async ({ id }) => { await Promise.all([ this.db.send(new lib_dynamodb_1.DeleteCommand({ Key: { id, }, TableName: this.connectionsTable, })), this.subscriptions.unsubscribeAllByConnectionId(id), ]); }; this.closeConnection = async ({ id, data }) => { if (this.debug) console.log('Disconnected ', id); await this.createApiGatewayManager(data.endpoint).send(new client_apigatewaymanagementapi_1.DeleteConnectionCommand({ ConnectionId: id })); }; assert_1.default.ok(typeof connectionsTable === 'string', 'Please provide connectionsTable as a string'); assert_1.default.ok(typeof subscriptions === 'object', 'Please provide subscriptions to manage subscriptions.'); assert_1.default.ok(ttl === false || (typeof ttl === 'number' && ttl > 0), 'Please provide ttl as a number greater than 0 or false to turn it off'); assert_1.default.ok(dynamoDbClient == null || typeof dynamoDbClient === 'object', 'Please provide dynamoDbClient as an instance of DynamoDBDocumentClient or DynamoDBClient'); assert_1.default.ok(apiGatewayManager == null || typeof apiGatewayManager === 'object', 'Please provide apiGatewayManager as an instance of ApiGatewayManagementApiClient'); assert_1.default.ok(typeof debug === 'boolean', 'Please provide debug as a boolean'); this.apiGatewayManager = apiGatewayManager; this.connectionsTable = connectionsTable; // Handle both DynamoDBDocumentClient and DynamoDBClient // Always ensure proper marshalling options are applied if (dynamoDbClient) { if (dynamoDbClient instanceof lib_dynamodb_1.DynamoDBDocumentClient) { // If a DynamoDBDocumentClient is passed, warn the user about potential marshalling issues console.warn('[aws-lambda-graphql] Warning: DynamoDBDocumentClient passed directly. Please ensure it was created with marshallOptions: { convertClassInstanceToMap: true, removeUndefinedValues: true } to avoid marshalling errors.'); this.db = dynamoDbClient; } else { this.db = lib_dynamodb_1.DynamoDBDocumentClient.from(dynamoDbClient, { marshallOptions: { convertClassInstanceToMap: true, removeUndefinedValues: true, }, }); } } else { this.db = lib_dynamodb_1.DynamoDBDocumentClient.from(new client_dynamodb_1.DynamoDBClient({}), { marshallOptions: { convertClassInstanceToMap: true, removeUndefinedValues: true, }, }); } this.subscriptions = subscriptions; this.ttl = ttl; this.debug = debug; } /** * Creates api gateway manager * * If custom api gateway manager is provided, uses it instead */ createApiGatewayManager(endpoint) { console.log('[DEBUG] DynamoDBConnectionManager.createApiGatewayManager:', { endpoint, isValidUrl: endpoint.startsWith('https://'), customManagerExists: !!this.apiGatewayManager, }); if (this.apiGatewayManager) { return this.apiGatewayManager; } this.apiGatewayManager = new client_apigatewaymanagementapi_1.ApiGatewayManagementApiClient({ endpoint }); return this.apiGatewayManager; } } exports.DynamoDBConnectionManager = DynamoDBConnectionManager; //# sourceMappingURL=DynamoDBConnectionManager.js.map