@tableau/taco-toolkit
Version:
Tableau Connector Toolkit
607 lines • 45.3 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFetchParams = exports.ingestDataRows = exports.loadParquetData = exports.loadData = exports.loadCsvData = exports.fetchArrayBuffer = exports.fetchJson = exports.FetchUtilsPlugin = void 0;
var csv_stringify_1 = require("csv-stringify");
var node_fs_1 = __importDefault(require("node:fs"));
var node_path_1 = __importDefault(require("node:path"));
var node_stream_1 = require("node:stream");
var node_util_1 = require("node:util");
var type_utils_1 = require("../../../../shared/type-utils");
var handler_input_1 = require("../../../../shared/types/handler-input");
var permission_check_1 = require("../../../../shared/utils/permission-check");
var eps_logger_1 = require("../../../logging/eps-logger");
var parse_utils_1 = require("../../../utils/parse-utils");
var request_utils_1 = require("../../../utils/request-utils");
var sandbox_plugin_1 = require("../sandbox-plugin");
// Convert the pipeline function to a promise so we can use async/await with it
var streamPipeline = (0, node_util_1.promisify)(node_stream_1.pipeline);
var FetchUtilsPlugin = /** @class */ (function (_super) {
__extends(FetchUtilsPlugin, _super);
function FetchUtilsPlugin(config, networkAdapter, tempDataManager) {
var _this = _super.call(this) || this;
_this.config = config;
_this.networkAdapter = networkAdapter;
_this.tempDataManager = tempDataManager;
_this.state = {};
_this.extractorId = config.extractorId;
_this.permission = config.permission;
return _this;
}
FetchUtilsPlugin.prototype.getState = function () {
return this.state;
};
FetchUtilsPlugin.prototype.registerClass = function (sandbox) {
var methods = [
{
isStatic: true,
externalName: 'fetchJson',
functionRef: this.createFetchJson(),
},
{
isStatic: true,
externalName: 'fetchArrayBuffer',
functionRef: this.createFetchArrayBuffer(),
},
];
// file ingestion functions require file name to be present
var fileName = getFileName(this.config);
if (fileName) {
methods.push({
isStatic: true,
externalName: 'loadData',
functionRef: this.createLoadData(fileName),
}, {
isStatic: true,
externalName: 'loadExcelData',
functionRef: this.createLoadExcelData(fileName),
}, {
isStatic: true,
externalName: 'loadParquetData',
functionRef: this.createLoadParquetData(fileName),
}, {
isStatic: true,
externalName: 'loadCsvData',
functionRef: this.createLoadCsvData(fileName),
}, {
isStatic: true,
externalName: 'ingestDataRows',
functionRef: this.createIngestDataRows(fileName),
});
}
(0, sandbox_plugin_1.addClassToSandbox)(sandbox, methods, 'FetchUtils');
};
FetchUtilsPlugin.prototype.createFetchJson = function () {
var _this = this;
return function (url, options) { return __awaiter(_this, void 0, void 0, function () {
var method;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
method = (_a = options === null || options === void 0 ? void 0 : options.method) !== null && _a !== void 0 ? _a : 'GET';
checkPermission(this.permission, url, method);
return [4 /*yield*/, fetchJson(this.networkAdapter, url, options)];
case 1: return [2 /*return*/, _b.sent()];
}
});
}); };
};
FetchUtilsPlugin.prototype.createFetchArrayBuffer = function () {
var _this = this;
return function (url, options) { return __awaiter(_this, void 0, void 0, function () {
var method;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
method = (_a = options === null || options === void 0 ? void 0 : options.method) !== null && _a !== void 0 ? _a : 'GET';
checkPermission(this.permission, url, method);
return [4 /*yield*/, fetchArrayBuffer(this.networkAdapter, url, options)];
case 1: return [2 /*return*/, _b.sent()];
}
});
}); };
};
FetchUtilsPlugin.prototype.createLoadData = function (fileName) {
var _this = this;
return function (url, options) { return __awaiter(_this, void 0, void 0, function () {
var method;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
method = (_a = options === null || options === void 0 ? void 0 : options.method) !== null && _a !== void 0 ? _a : 'GET';
checkPermission(this.permission, url, method);
return [4 /*yield*/, loadData(this.extractorId, fileName, this.tempDataManager, this.networkAdapter, url, options)];
case 1: return [2 /*return*/, _b.sent()];
}
});
}); };
};
FetchUtilsPlugin.prototype.createLoadExcelData = function (fileName) {
return this.createLoadData(fileName);
};
FetchUtilsPlugin.prototype.createLoadCsvData = function (fileName) {
var _this = this;
return function (url, options) { return __awaiter(_this, void 0, void 0, function () {
var method;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
method = (_a = options === null || options === void 0 ? void 0 : options.method) !== null && _a !== void 0 ? _a : 'GET';
checkPermission(this.permission, url, method);
return [4 /*yield*/, loadCsvData(this.extractorId, fileName, this.tempDataManager, this.networkAdapter, url, options)];
case 1:
_b.sent();
this.setParser('taco:csv-file-parser', fileName);
return [2 /*return*/];
}
});
}); };
};
FetchUtilsPlugin.prototype.createLoadParquetData = function (fileName) {
var _this = this;
var tableFileCounter = {};
return function (url, options) { return __awaiter(_this, void 0, void 0, function () {
var method;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
method = (_a = options === null || options === void 0 ? void 0 : options.method) !== null && _a !== void 0 ? _a : 'GET';
checkPermission(this.permission, url, method);
return [4 /*yield*/, loadParquetData(this.extractorId, fileName, this.tempDataManager, this.networkAdapter, tableFileCounter, url, options)];
case 1:
_b.sent();
this.setParser('taco:parquet-file-parser', fileName);
return [2 /*return*/];
}
});
}); };
};
FetchUtilsPlugin.prototype.createIngestDataRows = function (fileName) {
var _this = this;
return function (rows) { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, ingestDataRows(this.extractorId, fileName, this.tempDataManager, rows)];
case 1:
_a.sent();
this.setParser('taco:data-file-parser', fileName);
return [2 /*return*/];
}
});
}); };
};
FetchUtilsPlugin.prototype.setParser = function (parserName, tableName) {
var parser = this.state.parser;
if (parser === undefined) {
this.state.parser = parserName;
return;
}
if (parser !== parserName) {
throw new Error("A table can only ingest a single type of data. ".concat(parserName, " cannot be registered, because ").concat(parser, " is already set for table '").concat(tableName, "'"));
}
};
return FetchUtilsPlugin;
}(sandbox_plugin_1.SandboxPlugin));
exports.FetchUtilsPlugin = FetchUtilsPlugin;
function getFileName(config) {
if ('handlerInput' in config) {
var handlerInput = config.handlerInput;
if ((0, handler_input_1.isFileBasedHandlerInput)(handlerInput)) {
return handlerInput.name;
}
return;
}
if ('tableName' in config) {
return config.tableName;
}
// if nothing is specified for file name, returns undefined
}
function checkPermission(permission, url, method) {
if (!(0, permission_check_1.checkApiPermission)(permission.api, url, method)) {
var error = new Error("No permission to make ".concat(method, " request URL ").concat(url));
eps_logger_1.Logger.error(error);
throw error;
}
}
function fetchJson(networkAdapter, url, options) {
return __awaiter(this, void 0, void 0, function () {
var fetchParams, headers, response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
fetchParams = getFetchParams(url, options);
headers = fetchParams.options.headers;
if (headers === undefined) {
fetchParams.options.headers = headers = {};
}
headers['Accept'] = 'application/json';
return [4 /*yield*/, networkAdapter.fetch(fetchParams.urlWithQuery, fetchParams.options)];
case 1:
response = _a.sent();
if (!response.ok || response.body === null) {
throw new Error("fetchJson: unexpected response. [status]: ".concat(response.status, ", [statusText]: ").concat(response.statusText));
}
return [4 /*yield*/, response.json()];
case 2: return [2 /*return*/, _a.sent()];
}
});
});
}
exports.fetchJson = fetchJson;
function fetchArrayBuffer(networkAdapter, url, options) {
return __awaiter(this, void 0, void 0, function () {
var fetchParams, response, buffer;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
fetchParams = getFetchParams(url, options);
return [4 /*yield*/, networkAdapter.fetch(fetchParams.urlWithQuery, fetchParams.options)];
case 1:
response = _a.sent();
if (!response.ok || response.body === null) {
throw new Error("fetchArrayBuffer: unexpected response. [status]: ".concat(response.status, ", [statusText]: ").concat(response.statusText));
}
return [4 /*yield*/, response.arrayBuffer()];
case 2:
buffer = _a.sent();
return [2 /*return*/, new Uint8Array(buffer)];
}
});
});
}
exports.fetchArrayBuffer = fetchArrayBuffer;
function loadCsvData(extractorId, fileName, tempDataManager, networkAdapter, url, options) {
return __awaiter(this, void 0, void 0, function () {
function streamToFile(stream, filePath) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var trimColumnHeader, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
trimColumnHeader = (_a = options === null || options === void 0 ? void 0 : options.trimColumnHeader) !== null && _a !== void 0 ? _a : false;
_b = !trimColumnHeader || !node_fs_1.default.existsSync(filePath);
if (_b) return [3 /*break*/, 2];
return [4 /*yield*/, (0, parse_utils_1.isFileEmpty)(filePath)];
case 1:
_b = (_c.sent());
_c.label = 2;
case 2:
if (!_b) return [3 /*break*/, 4];
return [4 /*yield*/, appendStreamToFile(stream, filePath)];
case 3:
_c.sent();
return [3 /*break*/, 6];
case 4: return [4 /*yield*/, streamPipeline(stream.pipe(new FirstLineTransform()), node_fs_1.default.createWriteStream(filePath, { flags: 'a' }))];
case 5:
_c.sent();
_c.label = 6;
case 6: return [2 /*return*/];
}
});
});
}
var fetchParams, response, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
fetchParams = getFetchParams(url, options);
return [4 /*yield*/, networkAdapter.fetch(fetchParams.urlWithQuery, fetchParams.options)];
case 1:
response = _a.sent();
if (!response.ok || response.body === null) {
throw new Error("loadCsvData: unexpected response. [status]: ".concat(response.status, ", [statusText]: ").concat(response.statusText));
}
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, ingestStreamData(extractorId, tempDataManager, response.body, streamToFile, fileName)];
case 3:
_a.sent();
return [3 /*break*/, 5];
case 4:
error_1 = _a.sent();
throw new Error("ingestData: Data Injestion into epsTempData directory failed.\n" + error_1);
case 5: return [2 /*return*/];
}
});
});
}
exports.loadCsvData = loadCsvData;
function loadData(extractorId, fileName, tempDataManager, networkAdapter, url, options) {
return __awaiter(this, void 0, void 0, function () {
var fetchParams, response, error_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
fetchParams = getFetchParams(url, options);
return [4 /*yield*/, networkAdapter.fetch(fetchParams.urlWithQuery, fetchParams.options)];
case 1:
response = _a.sent();
if (!response.ok || response.body === null) {
throw new Error("loadData: unexpected response. [status]: ".concat(response.status, ", [statusText]: ").concat(response.statusText));
}
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, ingestStreamData(extractorId, tempDataManager, response.body, appendStreamToFile, fileName)];
case 3:
_a.sent();
return [3 /*break*/, 5];
case 4:
error_2 = _a.sent();
throw new Error("ingestData: Data Injestion into epsTempData directory failed.\n" + error_2);
case 5: return [2 /*return*/];
}
});
});
}
exports.loadData = loadData;
function loadParquetData(extractorId, fileName, tempDataManager, networkAdapter, tableFileCounter, url, options) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var fetchParams, response, dirPath, index, filePath, error_3;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
fetchParams = getFetchParams(url, options);
return [4 /*yield*/, networkAdapter.fetch(fetchParams.urlWithQuery, fetchParams.options)];
case 1:
response = _b.sent();
if (!response.ok || response.body === null) {
throw new Error("loadParquetData: unexpected response. [status]: ".concat(response.status, ", [statusText]: ").concat(response.statusText));
}
_b.label = 2;
case 2:
_b.trys.push([2, 4, , 5]);
tempDataManager.createTempDataDirIfNotExists(extractorId);
dirPath = tempDataManager.getTempDataDirPath(extractorId, fileName);
// create parquet data dir if it does not exist
if (!node_fs_1.default.existsSync(dirPath)) {
node_fs_1.default.mkdirSync(dirPath, { recursive: true });
eps_logger_1.Logger.info("Temporary parquet data directory created: ".concat(dirPath));
}
index = (_a = tableFileCounter[fileName]) !== null && _a !== void 0 ? _a : 0;
tableFileCounter[fileName] = index + 1;
filePath = node_path_1.default.join(dirPath, "f-".concat(index, ".parquet"));
// Duckdb read APIs can only support filepaths less than 255 characters in length.
if (filePath.length >= 255) {
throw new Error("Temporary data filepath has to be less than 255 characters in length. tempDataFilePath: ".concat(filePath));
}
return [4 /*yield*/, appendStreamToFile(response.body, filePath)];
case 3:
_b.sent();
return [3 /*break*/, 5];
case 4:
error_3 = _b.sent();
throw new Error("ingestData: Data Injestion into epsTempData directory failed.\n" + error_3);
case 5: return [2 /*return*/];
}
});
});
}
exports.loadParquetData = loadParquetData;
function ingestStreamData(extractorId, tempDataManager, stream, streamToFile, filename) {
return __awaiter(this, void 0, void 0, function () {
var filePath;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
tempDataManager.createTempDataDirIfNotExists(extractorId);
filePath = tempDataManager.getTempDataFilePath(extractorId, filename);
return [4 /*yield*/, streamToFile(stream, filePath)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
}
/**
* Using HandlerInput for now because we are getting HandlerInput via sandbox during runtime, and the type is HandlerInput.
* When we export the API from SDK we should use FileBasedHandlerInput instead.
*/
function ingestDataRows(extractorId, fileName, tempDataManager, rows) {
return __awaiter(this, void 0, void 0, function () {
var filePath;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
tempDataManager.createTempDataDirIfNotExists(extractorId);
filePath = tempDataManager.getTempDataFilePath(extractorId, fileName);
return [4 /*yield*/, writeCSVToFile(rows, filePath)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
}
exports.ingestDataRows = ingestDataRows;
/**
* Appends data to a CSV file identified by the file path.
* The function uses the first DataRow object's keys order to determine the data column ordering.
* When calling the function multiple times, the caller is responsible for maintaining the column ordering alignment.
* The function uses the name property in the DataRow object to identify a table.
* Rows will be appended to the same file if the file path is the same.
* The header is only written on the first call.
*
* @param dataRows - An array of DataRow objects containing the data to be appended.
* @param filePath - The file path of the CSV file to append data to.
* @returns A Promise that resolves when the data has been appended to the file.
*/
function writeCSVToFile(dataRows, filePath) {
return new Promise(function (resolve, reject) {
var writeHeader = true;
// Only write header once, so if the file already exists and has a header,
// then we don't need to write the header again.
if (node_fs_1.default.existsSync(filePath)) {
writeHeader = false;
}
var outputStream = node_fs_1.default.createWriteStream(filePath, { flags: 'a' });
// if dataRows is empty, close the stream with an empty file
if (dataRows.length === 0) {
outputStream.close();
resolve();
return;
}
try {
(0, csv_stringify_1.stringify)(dataRows, {
header: writeHeader,
columns: Object.keys(dataRows[0]),
// using cast as csv-stringify converts boolean values to 0 or 1
cast: {
boolean: function (value) { return value.toString(); },
},
}).pipe(outputStream);
}
catch (error) {
error.message = "ingestDataRows: ingestDataRows failed.\n".concat(error.message);
reject(error);
}
outputStream.on('error', function (err) {
eps_logger_1.Logger.error('Error during ingestDataRows.' + err);
reject(err);
});
outputStream.on('finish', function () {
eps_logger_1.Logger.info("Count of rows ingested: ".concat(dataRows.length));
resolve();
});
});
}
var FirstLineTransform = /** @class */ (function (_super) {
__extends(FirstLineTransform, _super);
function FirstLineTransform() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this._firstChunk = true;
return _this;
}
FirstLineTransform.prototype._transform = function (chunk, encoding, callback) {
if (this._firstChunk) {
var firstLineEnd = chunk.indexOf('\n');
if (firstLineEnd > -1) {
chunk = chunk.slice(firstLineEnd + 1);
this._firstChunk = false;
}
else {
chunk = null;
}
}
callback(null, chunk);
};
return FirstLineTransform;
}(node_stream_1.Transform));
/**
* Append readable stream into a file
*/
function appendStreamToFile(stream, filePath) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, streamPipeline(stream, node_fs_1.default.createWriteStream(filePath, { flags: 'a' }))];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
}
function getFetchParams(urlString, options) {
var _a;
if (options === undefined) {
return { urlWithQuery: urlString, options: { method: 'GET' } };
}
var method = (_a = options === null || options === void 0 ? void 0 : options.method) !== null && _a !== void 0 ? _a : 'GET';
if (method !== 'GET' && method !== 'POST') {
throw new Error("'method' must be 'GET' or 'POST'. Unsupported method: ".concat(method));
}
var url = new URL(urlString);
if (options.query) {
Object.entries(options.query).forEach(function (_a) {
var _b = __read(_a, 2), key = _b[0], value = _b[1];
if (value === undefined) {
return;
}
url.searchParams.append(key, (0, type_utils_1.stringifyUnknown)(value));
});
}
var headers = options.headers, body = options.body;
var fetchOption = (0, request_utils_1.composeFetchOption)(method, headers, body);
return { urlWithQuery: url.toString(), options: fetchOption };
}
exports.getFetchParams = getFetchParams;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmV0Y2gtdXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvZXBzL3NhbmRib3gvc2RrL2ZldGNoZXItdXRpbHMvZmV0Y2gtdXRpbHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLCtDQUF5QztBQUN6QyxvREFBd0I7QUFDeEIsd0RBQTRCO0FBQzVCLDJDQUFvRTtBQUNwRSx1Q0FBcUM7QUFJckMsNERBQWdFO0FBRWhFLHdFQUkrQztBQUcvQyw4RUFBOEU7QUFDOUUsMERBQW9EO0FBR3BELDBEQUF3RDtBQUN4RCw4REFBaUU7QUFFakUsb0RBQWdGO0FBRWhGLCtFQUErRTtBQUMvRSxJQUFNLGNBQWMsR0FBRyxJQUFBLHFCQUFTLEVBQUMsc0JBQVEsQ0FBQyxDQUFBO0FBc0UxQztJQUFzQyxvQ0FBYTtJQU1qRCwwQkFDVSxNQUE4QixFQUM5QixjQUE4QixFQUM5QixlQUFnQztRQUgxQyxZQUtFLGlCQUFPLFNBSVI7UUFSUyxZQUFNLEdBQU4sTUFBTSxDQUF3QjtRQUM5QixvQkFBYyxHQUFkLGNBQWMsQ0FBZ0I7UUFDOUIscUJBQWUsR0FBZixlQUFlLENBQWlCO1FBUmpDLFdBQUssR0FBMEIsRUFBRSxDQUFBO1FBWXhDLEtBQUksQ0FBQyxXQUFXLEdBQUcsTUFBTSxDQUFDLFdBQVcsQ0FBQTtRQUNyQyxLQUFJLENBQUMsVUFBVSxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUE7O0lBQ3JDLENBQUM7SUFFRCxtQ0FBUSxHQUFSO1FBQ0UsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFBO0lBQ25CLENBQUM7SUFFRCx3Q0FBYSxHQUFiLFVBQWMsT0FBa0I7UUFDOUIsSUFBTSxPQUFPLEdBQWlCO1lBQzVCO2dCQUNFLFFBQVEsRUFBRSxJQUFJO2dCQUNkLFlBQVksRUFBRSxXQUFXO2dC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