Added documentation; Added multiple joins

This commit is contained in:
Sam 2024-11-25 07:51:27 +01:00
parent aa0b62be09
commit 85499e837c
45 changed files with 10098 additions and 221 deletions

4
Readme.md Normal file
View File

@ -0,0 +1,4 @@
# awSQL
[Get started](awSQL.html)

3
docs/AlterTable.html Normal file

File diff suppressed because one or more lines are too long

3
docs/CreateTable.html Normal file

File diff suppressed because one or more lines are too long

3
docs/Delete.html Normal file

File diff suppressed because one or more lines are too long

3
docs/Insert.html Normal file

File diff suppressed because one or more lines are too long

3
docs/Instance.html Normal file

File diff suppressed because one or more lines are too long

3
docs/Select.html Normal file

File diff suppressed because one or more lines are too long

3
docs/Structure.html Normal file

File diff suppressed because one or more lines are too long

3
docs/Update.html Normal file

File diff suppressed because one or more lines are too long

3
docs/awSQL.html Normal file

File diff suppressed because one or more lines are too long

1
docs/data/search.json Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
docs/global.html Normal file

File diff suppressed because one or more lines are too long

3
docs/index.html Normal file

File diff suppressed because one or more lines are too long

726
docs/scripts/core.js Normal file
View File

@ -0,0 +1,726 @@
/* global document */
var accordionLocalStorageKey = 'accordion-id';
var themeLocalStorageKey = 'theme';
var fontSizeLocalStorageKey = 'font-size';
var html = document.querySelector('html');
var MAX_FONT_SIZE = 30;
var MIN_FONT_SIZE = 10;
// eslint-disable-next-line no-undef
var localStorage = window.localStorage;
function getTheme() {
var theme = localStorage.getItem(themeLocalStorageKey);
if (theme) return theme;
theme = document.body.getAttribute('data-theme');
switch (theme) {
case 'dark':
case 'light':
return theme;
case 'fallback-dark':
if (
// eslint-disable-next-line no-undef
window.matchMedia('(prefers-color-scheme)').matches &&
// eslint-disable-next-line no-undef
window.matchMedia('(prefers-color-scheme: light)').matches
) {
return 'light';
}
return 'dark';
case 'fallback-light':
if (
// eslint-disable-next-line no-undef
window.matchMedia('(prefers-color-scheme)').matches &&
// eslint-disable-next-line no-undef
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
return 'dark';
}
return 'light';
default:
return 'dark';
}
}
function localUpdateTheme(theme) {
var body = document.body;
var svgUse = document.querySelectorAll('.theme-svg-use');
var iconID = theme === 'dark' ? '#light-theme-icon' : '#dark-theme-icon';
body.setAttribute('data-theme', theme);
body.classList.remove('dark', 'light');
body.classList.add(theme);
svgUse.forEach(function (svg) {
svg.setAttribute('xlink:href', iconID);
});
}
function updateTheme(theme) {
localUpdateTheme(theme);
localStorage.setItem(themeLocalStorageKey, theme);
}
function toggleTheme() {
var body = document.body;
var theme = body.getAttribute('data-theme');
var newTheme = theme === 'dark' ? 'light' : 'dark';
updateTheme(newTheme);
}
(function () {
var theme = getTheme();
updateTheme(theme);
})();
/**
* Function to set accordion id to localStorage.
* @param {string} id Accordion id
*/
function setAccordionIdToLocalStorage(id) {
/**
* @type {object}
*/
var ids = JSON.parse(localStorage.getItem(accordionLocalStorageKey));
ids[id] = id;
localStorage.setItem(accordionLocalStorageKey, JSON.stringify(ids));
}
/**
* Function to remove accordion id from localStorage.
* @param {string} id Accordion id
*/
function removeAccordionIdFromLocalStorage(id) {
/**
* @type {object}
*/
var ids = JSON.parse(localStorage.getItem(accordionLocalStorageKey));
delete ids[id];
localStorage.setItem(accordionLocalStorageKey, JSON.stringify(ids));
}
/**
* Function to get all accordion ids from localStorage.
*
* @returns {object}
*/
function getAccordionIdsFromLocalStorage() {
/**
* @type {object}
*/
var ids = JSON.parse(localStorage.getItem(accordionLocalStorageKey));
return ids || {};
}
function toggleAccordion(element) {
var currentNode = element;
var isCollapsed = currentNode.getAttribute('data-isopen') === 'false';
if (isCollapsed) {
currentNode.setAttribute('data-isopen', 'true');
setAccordionIdToLocalStorage(currentNode.id);
} else {
currentNode.setAttribute('data-isopen', 'false');
removeAccordionIdFromLocalStorage(currentNode.id);
}
}
function initAccordion() {
if (
localStorage.getItem(accordionLocalStorageKey) === undefined ||
localStorage.getItem(accordionLocalStorageKey) === null
) {
localStorage.setItem(accordionLocalStorageKey, '{}');
}
var allAccordion = document.querySelectorAll('.sidebar-section-title');
var ids = getAccordionIdsFromLocalStorage();
allAccordion.forEach(function (item) {
item.addEventListener('click', function () {
toggleAccordion(item);
});
if (item.id in ids) {
toggleAccordion(item);
}
});
}
function isSourcePage() {
return Boolean(document.querySelector('#source-page'));
}
function bringElementIntoView(element, updateHistory = true) {
// If element is null then we are not going further
if (!element) {
return;
}
/**
* tocbotInstance is defined in layout.tmpl
* It is defined when we are initializing tocbot.
*
*/
// eslint-disable-next-line no-undef
if (tocbotInstance) {
setTimeout(
// eslint-disable-next-line no-undef
() => tocbotInstance.updateTocListActiveElement(element),
60
);
}
var navbar = document.querySelector('.navbar-container');
var body = document.querySelector('.main-content');
var elementTop = element.getBoundingClientRect().top;
var offset = 16;
if (navbar) {
offset += navbar.scrollHeight;
}
if (body) {
body.scrollBy(0, elementTop - offset);
}
if (updateHistory) {
// eslint-disable-next-line no-undef
history.pushState(null, null, '#' + element.id);
}
}
// eslint-disable-next-line no-unused-vars
function bringLinkToView(event) {
event.preventDefault();
event.stopPropagation();
var id = event.currentTarget.getAttribute('href');
if (!id) {
return;
}
var element = document.getElementById(id.slice(1));
if (element) {
bringElementIntoView(element);
}
}
function bringIdToViewOnMount() {
if (isSourcePage()) {
return;
}
// eslint-disable-next-line no-undef
var id = window.location.hash;
if (id === '') {
return;
}
var element = document.getElementById(id.slice(1));
if (!element) {
id = decodeURI(id);
element = document.getElementById(id.slice(1));
}
if (element) {
bringElementIntoView(element, false);
}
}
function createAnchorElement(id) {
var anchor = document.createElement('a');
anchor.textContent = '#';
anchor.href = '#' + id;
anchor.classList.add('link-anchor');
anchor.onclick = bringLinkToView;
return anchor;
}
function addAnchor() {
var main = document.querySelector('.main-content').querySelector('section');
var h1 = main.querySelectorAll('h1');
var h2 = main.querySelectorAll('h2');
var h3 = main.querySelectorAll('h3');
var h4 = main.querySelectorAll('h4');
var targets = [h1, h2, h3, h4];
targets.forEach(function (target) {
target.forEach(function (heading) {
var anchor = createAnchorElement(heading.id);
heading.classList.add('has-anchor');
heading.append(anchor);
});
});
}
/**
*
* @param {string} value
*/
function copy(value) {
const el = document.createElement('textarea');
el.value = value;
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
function showTooltip(id) {
var tooltip = document.getElementById(id);
tooltip.classList.add('show-tooltip');
setTimeout(function () {
tooltip.classList.remove('show-tooltip');
}, 3000);
}
/* eslint-disable-next-line */
function copyFunction(id) {
// selecting the pre element
var code = document.getElementById(id);
// selecting the ol.linenums
var element = code.querySelector('.linenums');
if (!element) {
// selecting the code block
element = code.querySelector('code');
}
// copy
copy(element.innerText.trim().replace(/(^\t)/gm, ''));
// show tooltip
showTooltip('tooltip-' + id);
}
function hideTocOnSourcePage() {
if (isSourcePage()) {
document.querySelector('.toc-container').style.display = 'none';
}
}
function getPreTopBar(id, lang = '') {
// tooltip
var tooltip = '<div class="tooltip" id="tooltip-' + id + '">Copied!</div>';
// template of copy to clipboard icon container
var copyToClipboard =
'<button aria-label="copy code" class="icon-button copy-code" onclick="copyFunction(\'' +
id +
'\')"><svg class="sm-icon" alt="click to copy"><use xlink:href="#copy-icon"></use></svg>' +
tooltip +
'</button>';
var langNameDiv =
'<div class="code-lang-name-container"><div class="code-lang-name">' +
lang.toLocaleUpperCase() +
'</div></div>';
var topBar =
'<div class="pre-top-bar-container">' +
langNameDiv +
copyToClipboard +
'</div>';
return topBar;
}
function getPreDiv() {
var divElement = document.createElement('div');
divElement.classList.add('pre-div');
return divElement;
}
function processAllPre() {
var targets = document.querySelectorAll('pre');
var footer = document.querySelector('#PeOAagUepe');
var navbar = document.querySelector('#VuAckcnZhf');
var navbarHeight = 0;
var footerHeight = 0;
if (footer) {
footerHeight = footer.getBoundingClientRect().height;
}
if (navbar) {
navbarHeight = navbar.getBoundingClientRect().height;
}
// eslint-disable-next-line no-undef
var preMaxHeight = window.innerHeight - navbarHeight - footerHeight - 250;
targets.forEach(function (pre, idx) {
var parent = pre.parentNode;
if (parent && parent.getAttribute('data-skip-pre-process') === 'true') {
return;
}
var div = getPreDiv();
var id = 'ScDloZOMdL' + idx;
var lang = pre.getAttribute('data-lang') || 'code';
var topBar = getPreTopBar(id, lang);
div.innerHTML = topBar;
pre.style.maxHeight = preMaxHeight + 'px';
pre.id = id;
pre.classList.add('prettyprint');
pre.parentNode.insertBefore(div, pre);
div.appendChild(pre);
});
}
function highlightAndBringLineIntoView() {
// eslint-disable-next-line no-undef
var lineNumber = window.location.hash.replace('#line', '');
try {
var selector = '[data-line-number="' + lineNumber + '"';
var element = document.querySelector(selector);
element.scrollIntoView();
element.parentNode.classList.add('selected');
} catch (error) {
console.error(error);
}
}
function getFontSize() {
var currentFontSize = 16;
try {
currentFontSize = Number.parseInt(
html.style.fontSize.split('px')[0],
10
);
} catch (error) {
console.log(error);
}
return currentFontSize;
}
function localUpdateFontSize(fontSize) {
html.style.fontSize = fontSize + 'px';
var fontSizeText = document.querySelector(
'#b77a68a492f343baabea06fad81f651e'
);
if (fontSizeText) {
fontSizeText.innerHTML = fontSize;
}
}
function updateFontSize(fontSize) {
localUpdateFontSize(fontSize);
localStorage.setItem(fontSizeLocalStorageKey, fontSize);
}
(function () {
var fontSize = getFontSize();
var fontSizeInLocalStorage = localStorage.getItem(fontSizeLocalStorageKey);
if (fontSizeInLocalStorage) {
var n = Number.parseInt(fontSizeInLocalStorage, 10);
if (n === fontSize) {
return;
}
updateFontSize(n);
} else {
updateFontSize(fontSize);
}
})();
// eslint-disable-next-line no-unused-vars
function incrementFont(event) {
var n = getFontSize();
if (n < MAX_FONT_SIZE) {
updateFontSize(n + 1);
}
}
// eslint-disable-next-line no-unused-vars
function decrementFont(event) {
var n = getFontSize();
if (n > MIN_FONT_SIZE) {
updateFontSize(n - 1);
}
}
function fontSizeTooltip() {
var fontSize = getFontSize();
return `
<div class="font-size-tooltip">
<button aria-label="decrease-font-size" class="icon-button ${
fontSize >= MAX_FONT_SIZE ? 'disabled' : ''
}" onclick="decrementFont(event)">
<svg>
<use xlink:href="#minus-icon"></use>
</svg>
</button>
<div class="font-size-text" id="b77a68a492f343baabea06fad81f651e">
${fontSize}
</div>
<button aria-label="increase-font-size" class="icon-button ${
fontSize <= MIN_FONT_SIZE ? 'disabled' : ''
}" onclick="incrementFont(event)">
<svg>
<use xlink:href="#add-icon"></use>
</svg>
</button>
<button class="icon-button" onclick="updateFontSize(16)">
<svg>
<use xlink:href="#reset-icon"></use>
</svg>
</button>
</div>
`;
}
function initTooltip() {
// add tooltip to navbar item
// eslint-disable-next-line no-undef
tippy('.theme-toggle', {
content: 'Toggle Theme',
delay: 500,
});
// eslint-disable-next-line no-undef
tippy('.search-button', {
content: 'Search',
delay: 500,
});
// eslint-disable-next-line no-undef
tippy('.font-size', {
content: 'Change font size',
delay: 500,
});
// eslint-disable-next-line no-undef
tippy('.codepen-button', {
content: 'Open code in CodePen',
placement: 'left',
});
// eslint-disable-next-line no-undef
tippy('.copy-code', {
content: 'Copy this code',
placement: 'left',
});
// eslint-disable-next-line no-undef
tippy('.font-size', {
content: fontSizeTooltip(),
trigger: 'click',
interactive: true,
allowHTML: true,
placement: 'left',
});
}
function fixTable() {
const tables = document.querySelectorAll('table');
for (const table of tables) {
if (table.classList.contains('hljs-ln')) {
// don't want to wrap code blocks.
return;
}
var div = document.createElement('div');
div.classList.add('table-div');
table.parentNode.insertBefore(div, table);
div.appendChild(table);
}
}
function hideMobileMenu() {
var mobileMenuContainer = document.querySelector('#mobile-sidebar');
var target = document.querySelector('#mobile-menu');
var svgUse = target.querySelector('use');
if (mobileMenuContainer) {
mobileMenuContainer.classList.remove('show');
}
if (target) {
target.setAttribute('data-isopen', 'false');
}
if (svgUse) {
svgUse.setAttribute('xlink:href', '#menu-icon');
}
}
function showMobileMenu() {
var mobileMenuContainer = document.querySelector('#mobile-sidebar');
var target = document.querySelector('#mobile-menu');
var svgUse = target.querySelector('use');
if (mobileMenuContainer) {
mobileMenuContainer.classList.add('show');
}
if (target) {
target.setAttribute('data-isopen', 'true');
}
if (svgUse) {
svgUse.setAttribute('xlink:href', '#close-icon');
}
}
function onMobileMenuClick() {
var target = document.querySelector('#mobile-menu');
var isOpen = target.getAttribute('data-isopen') === 'true';
if (isOpen) {
hideMobileMenu();
} else {
showMobileMenu();
}
}
function initMobileMenu() {
var menu = document.querySelector('#mobile-menu');
if (menu) {
menu.addEventListener('click', onMobileMenuClick);
}
}
function addHrefToSidebarTitle() {
var titles = document.querySelectorAll('.sidebar-title-anchor');
titles.forEach(function (title) {
// eslint-disable-next-line no-undef
title.setAttribute('href', baseURL);
});
}
function highlightActiveLinkInSidebar() {
const list = document.location.href.split('/');
const targetURL = decodeURI(list[list.length - 1]);
let element = document.querySelector(`.sidebar a[href*='${targetURL}']`);
if (!element) {
try {
element = document.querySelector(
`.sidebar a[href*='${targetURL.split('#')[0]}']`
);
} catch (e) {
console.error(e);
return;
}
}
if (!element) return;
element.parentElement.classList.add('active');
element.scrollIntoView();
}
function onDomContentLoaded() {
var themeButton = document.querySelectorAll('.theme-toggle');
initMobileMenu();
if (themeButton) {
themeButton.forEach(function (button) {
button.addEventListener('click', toggleTheme);
});
}
// Highlighting code
// eslint-disable-next-line no-undef
hljs.addPlugin({
'after:highlightElement': function (obj) {
// Replace 'code' with result.language when
// we are able to cross-check the correctness of
// result.
obj.el.parentNode.setAttribute('data-lang', 'code');
},
});
// eslint-disable-next-line no-undef
hljs.highlightAll();
// eslint-disable-next-line no-undef
hljs.initLineNumbersOnLoad({
singleLine: true,
});
// Highlight complete
initAccordion();
addAnchor();
processAllPre();
hideTocOnSourcePage();
setTimeout(function () {
bringIdToViewOnMount();
if (isSourcePage()) {
highlightAndBringLineIntoView();
}
}, 1000);
initTooltip();
fixTable();
addHrefToSidebarTitle();
highlightActiveLinkInSidebar();
}
// eslint-disable-next-line no-undef
window.addEventListener('DOMContentLoaded', onDomContentLoaded);
// eslint-disable-next-line no-undef
window.addEventListener('hashchange', (event) => {
const url = new URL(event.newURL);
if (url.hash !== '') {
bringIdToViewOnMount(url.hash);
}
});
// eslint-disable-next-line no-undef
window.addEventListener('storage', (event) => {
if (event.newValue === 'undefined') return;
initTooltip();
if (event.key === themeLocalStorageKey) localUpdateTheme(event.newValue);
if (event.key === fontSizeLocalStorageKey)
localUpdateFontSize(event.newValue);
});

23
docs/scripts/core.min.js vendored Normal file

File diff suppressed because one or more lines are too long

90
docs/scripts/resize.js Normal file
View File

@ -0,0 +1,90 @@
/* global document */
// This file is @deprecated
var NAVBAR_OPTIONS = {};
(function() {
var NAVBAR_RESIZE_LOCAL_STORAGE_KEY = 'NAVBAR_RESIZE_LOCAL_STORAGE_KEY';
var navbar = document.querySelector('#navbar');
var footer = document.querySelector('#footer');
var mainSection = document.querySelector('#main');
var localStorageResizeObject = JSON.parse(
// eslint-disable-next-line no-undef
localStorage.getItem(NAVBAR_RESIZE_LOCAL_STORAGE_KEY)
);
/**
* Check whether we have any resize value in local storage or not.
* If we have resize value then resize the navbar.
**/
if (localStorageResizeObject) {
navbar.style.width = localStorageResizeObject.width;
mainSection.style.marginLeft = localStorageResizeObject.width;
footer.style.marginLeft = localStorageResizeObject.width;
}
var navbarSlider = document.querySelector('#navbar-resize');
function resizeNavbar(event) {
var pageX = event.pageX,
pageXPlusPx = event.pageX + 'px',
min = Number.parseInt(NAVBAR_OPTIONS.min, 10) || 300,
max = Number.parseInt(NAVBAR_OPTIONS.max, 10) || 600;
/**
* Just to add some checks. If min is smaller than 10 then
* user may accidentally end up reducing the size of navbar
* less than 10. In that case user will not able to resize navbar
* because navbar slider will be hidden.
*/
if (min < 10) {
min = 10;
}
/**
* Only resize if pageX in range between min and max
* allowed value.
*/
if (min < pageX && pageX < max) {
navbar.style.width = pageXPlusPx;
mainSection.style.marginLeft = pageXPlusPx;
footer.style.marginLeft = pageXPlusPx;
}
}
function setupEventListeners() {
// eslint-disable-next-line no-undef
window.addEventListener('mousemove', resizeNavbar);
// eslint-disable-next-line no-undef
window.addEventListener('touchmove', resizeNavbar);
}
function afterRemovingEventListeners() {
// eslint-disable-next-line no-undef
localStorage.setItem(
NAVBAR_RESIZE_LOCAL_STORAGE_KEY,
JSON.stringify({
width: navbar.style.width
})
);
}
function removeEventListeners() {
// eslint-disable-next-line no-undef
window.removeEventListener('mousemove', resizeNavbar);
// eslint-disable-next-line no-undef
window.removeEventListener('touchend', resizeNavbar);
afterRemovingEventListeners();
}
navbarSlider.addEventListener('mousedown', setupEventListeners);
navbarSlider.addEventListener('touchstart', setupEventListeners);
// eslint-disable-next-line no-undef
window.addEventListener('mouseup', removeEventListeners);
})();
// eslint-disable-next-line no-unused-vars
function setupResizeOptions(options) {
NAVBAR_OPTIONS = options;
}

265
docs/scripts/search.js Normal file
View File

@ -0,0 +1,265 @@
/* global document */
const searchId = 'LiBfqbJVcV';
const searchHash = '#' + searchId;
const searchContainer = document.querySelector('#PkfLWpAbet');
const searchWrapper = document.querySelector('#iCxFxjkHbP');
const searchCloseButton = document.querySelector('#VjLlGakifb');
const searchInput = document.querySelector('#vpcKVYIppa');
const resultBox = document.querySelector('#fWwVHRuDuN');
function showResultText(text) {
resultBox.innerHTML = `<span class="search-result-c-text">${text}</span>`;
}
function hideSearch() {
// eslint-disable-next-line no-undef
if (window.location.hash === searchHash) {
// eslint-disable-next-line no-undef
history.go(-1);
}
// eslint-disable-next-line no-undef
window.onhashchange = null;
if (searchContainer) {
searchContainer.style.display = 'none';
}
}
function listenCloseKey(event) {
if (event.key === 'Escape') {
hideSearch();
// eslint-disable-next-line no-undef
window.removeEventListener('keyup', listenCloseKey);
}
}
function showSearch() {
try {
// Closing mobile menu before opening
// search box.
// It is defined in core.js
// eslint-disable-next-line no-undef
hideMobileMenu();
} catch (error) {
console.error(error);
}
// eslint-disable-next-line no-undef
window.onhashchange = hideSearch;
// eslint-disable-next-line no-undef
if (window.location.hash !== searchHash) {
// eslint-disable-next-line no-undef
history.pushState(null, null, searchHash);
}
if (searchContainer) {
searchContainer.style.display = 'flex';
// eslint-disable-next-line no-undef
window.addEventListener('keyup', listenCloseKey);
}
if (searchInput) {
searchInput.focus();
}
}
async function fetchAllData() {
// eslint-disable-next-line no-undef
const { hostname, protocol, port } = location;
// eslint-disable-next-line no-undef
const base = protocol + '//' + hostname + (port !== '' ? ':' + port : '') + baseURL;
// eslint-disable-next-line no-undef
const url = new URL('data/search.json', base);
const result = await fetch(url);
const { list } = await result.json();
return list;
}
// eslint-disable-next-line no-unused-vars
function onClickSearchItem(event) {
const target = event.currentTarget;
if (target) {
const href = target.getAttribute('href') || '';
let elementId = href.split('#')[1] || '';
let element = document.getElementById(elementId);
if (!element) {
elementId = decodeURI(elementId);
element = document.getElementById(elementId);
}
if (element) {
setTimeout(function() {
// eslint-disable-next-line no-undef
bringElementIntoView(element); // defined in core.js
}, 100);
}
}
}
function buildSearchResult(result) {
let output = '';
const removeHTMLTagsRegExp = /(<([^>]+)>)/ig;
for (const res of result) {
const { title = '', description = '' } = res.item;
const _link = res.item.link.replace('<a href="', '').replace(/">.*/, '');
const _title = title.replace(removeHTMLTagsRegExp, "");
const _description = description.replace(removeHTMLTagsRegExp, "");
output += `
<a onclick="onClickSearchItem(event)" href="${_link}" class="search-result-item">
<div class="search-result-item-title">${_title}</div>
<div class="search-result-item-p">${_description || 'No description available.'}</div>
</a>
`;
}
return output;
}
function getSearchResult(list, keys, searchKey) {
const defaultOptions = {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: keys
};
const options = { ...defaultOptions };
// eslint-disable-next-line no-undef
const searchIndex = Fuse.createIndex(options.keys, list);
// eslint-disable-next-line no-undef
const fuse = new Fuse(list, options, searchIndex);
const result = fuse.search(searchKey);
if (result.length > 20) {
return result.slice(0, 20);
}
return result;
}
function debounce(func, wait, immediate) {
let timeout;
return function() {
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => {
timeout = null;
if (!immediate) {
// eslint-disable-next-line consistent-this, no-invalid-this
func.apply(this, args);
}
}, wait);
if (immediate && !timeout) {
// eslint-disable-next-line consistent-this, no-invalid-this
func.apply(this, args);
}
};
}
let searchData;
async function search(event) {
const value = event.target.value;
const keys = ['title', 'description'];
if (!resultBox) {
console.error('Search result container not found');
return;
}
if (!value) {
showResultText('Type anything to view search result');
return;
}
if (!searchData) {
showResultText('Loading...');
try {
// eslint-disable-next-line require-atomic-updates
searchData = await fetchAllData();
} catch (e) {
console.log(e);
showResultText('Failed to load result.');
return;
}
}
const result = getSearchResult(searchData, keys, value);
if (!result.length) {
showResultText('No result found! Try some different combination.');
return;
}
// eslint-disable-next-line require-atomic-updates
resultBox.innerHTML = buildSearchResult(result);
}
function onDomContentLoaded() {
const searchButton = document.querySelectorAll('.search-button');
const debouncedSearch = debounce(search, 300);
if (searchCloseButton) {
searchCloseButton.addEventListener('click', hideSearch);
}
if (searchButton) {
searchButton.forEach(function(item) {
item.addEventListener('click', showSearch);
});
}
if (searchContainer) {
searchContainer.addEventListener('click', hideSearch);
}
if (searchWrapper) {
searchWrapper.addEventListener('click', function(event) {
event.stopPropagation();
});
}
if (searchInput) {
searchInput.addEventListener('keyup', debouncedSearch);
}
// eslint-disable-next-line no-undef
if (window.location.hash === searchHash) {
showSearch();
}
}
// eslint-disable-next-line no-undef
window.addEventListener('DOMContentLoaded', onDomContentLoaded);
// eslint-disable-next-line no-undef
window.addEventListener('hashchange', function() {
// eslint-disable-next-line no-undef
if (window.location.hash === searchHash) {
showSearch();
}
});

6
docs/scripts/search.min.js vendored Normal file
View File

@ -0,0 +1,6 @@
const searchId="LiBfqbJVcV",searchHash="#"+searchId,searchContainer=document.querySelector("#PkfLWpAbet"),searchWrapper=document.querySelector("#iCxFxjkHbP"),searchCloseButton=document.querySelector("#VjLlGakifb"),searchInput=document.querySelector("#vpcKVYIppa"),resultBox=document.querySelector("#fWwVHRuDuN");function showResultText(e){resultBox.innerHTML=`<span class="search-result-c-text">${e}</span>`}function hideSearch(){window.location.hash===searchHash&&history.go(-1),window.onhashchange=null,searchContainer&&(searchContainer.style.display="none")}function listenCloseKey(e){"Escape"===e.key&&(hideSearch(),window.removeEventListener("keyup",listenCloseKey))}function showSearch(){try{hideMobileMenu()}catch(e){console.error(e)}window.onhashchange=hideSearch,window.location.hash!==searchHash&&history.pushState(null,null,searchHash),searchContainer&&(searchContainer.style.display="flex",window.addEventListener("keyup",listenCloseKey)),searchInput&&searchInput.focus()}async function fetchAllData(){var{hostname:e,protocol:t,port:n}=location,t=t+"//"+e+(""!==n?":"+n:"")+baseURL,e=new URL("data/search.json",t);const a=await fetch(e);n=(await a.json()).list;return n}function onClickSearchItem(t){const n=t.currentTarget;if(n){const a=n.getAttribute("href")||"";t=a.split("#")[1]||"";let e=document.getElementById(t);e||(t=decodeURI(t),e=document.getElementById(t)),e&&setTimeout(function(){bringElementIntoView(e)},100)}}function buildSearchResult(e){let t="";var n=/(<([^>]+)>)/gi;for(const s of e){const{title:c="",description:i=""}=s.item;var a=s.item.link.replace('<a href="',"").replace(/">.*/,""),o=c.replace(n,""),r=i.replace(n,"");t+=`
<a onclick="onClickSearchItem(event)" href="${a}" class="search-result-item">
<div class="search-result-item-title">${o}</div>
<div class="search-result-item-p">${r||"No description available."}</div>
</a>
`}return t}function getSearchResult(e,t,n){var t={...{shouldSort:!0,threshold:.4,location:0,distance:100,maxPatternLength:32,minMatchCharLength:1,keys:t}},a=Fuse.createIndex(t.keys,e);const o=new Fuse(e,t,a),r=o.search(n);return 20<r.length?r.slice(0,20):r}function debounce(t,n,a){let o;return function(){const e=arguments;clearTimeout(o),o=setTimeout(()=>{o=null,a||t.apply(this,e)},n),a&&!o&&t.apply(this,e)}}let searchData;async function search(e){e=e.target.value;if(resultBox)if(e){if(!searchData){showResultText("Loading...");try{searchData=await fetchAllData()}catch(e){return console.log(e),void showResultText("Failed to load result.")}}e=getSearchResult(searchData,["title","description"],e);e.length?resultBox.innerHTML=buildSearchResult(e):showResultText("No result found! Try some different combination.")}else showResultText("Type anything to view search result");else console.error("Search result container not found")}function onDomContentLoaded(){const e=document.querySelectorAll(".search-button");var t=debounce(search,300);searchCloseButton&&searchCloseButton.addEventListener("click",hideSearch),e&&e.forEach(function(e){e.addEventListener("click",showSearch)}),searchContainer&&searchContainer.addEventListener("click",hideSearch),searchWrapper&&searchWrapper.addEventListener("click",function(e){e.stopPropagation()}),searchInput&&searchInput.addEventListener("keyup",t),window.location.hash===searchHash&&showSearch()}window.addEventListener("DOMContentLoaded",onDomContentLoaded),window.addEventListener("hashchange",function(){window.location.hash===searchHash&&showSearch()});

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

9
docs/scripts/third-party/fuse.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,369 @@
// jshint multistr:true
(function (w, d) {
'use strict';
var TABLE_NAME = 'hljs-ln',
LINE_NAME = 'hljs-ln-line',
CODE_BLOCK_NAME = 'hljs-ln-code',
NUMBERS_BLOCK_NAME = 'hljs-ln-numbers',
NUMBER_LINE_NAME = 'hljs-ln-n',
DATA_ATTR_NAME = 'data-line-number',
BREAK_LINE_REGEXP = /\r\n|\r|\n/g;
if (w.hljs) {
w.hljs.initLineNumbersOnLoad = initLineNumbersOnLoad;
w.hljs.lineNumbersBlock = lineNumbersBlock;
w.hljs.lineNumbersValue = lineNumbersValue;
addStyles();
} else {
w.console.error('highlight.js not detected!');
}
function isHljsLnCodeDescendant(domElt) {
var curElt = domElt;
while (curElt) {
if (curElt.className && curElt.className.indexOf('hljs-ln-code') !== -1) {
return true;
}
curElt = curElt.parentNode;
}
return false;
}
function getHljsLnTable(hljsLnDomElt) {
var curElt = hljsLnDomElt;
while (curElt.nodeName !== 'TABLE') {
curElt = curElt.parentNode;
}
return curElt;
}
// Function to workaround a copy issue with Microsoft Edge.
// Due to hljs-ln wrapping the lines of code inside a <table> element,
// itself wrapped inside a <pre> element, window.getSelection().toString()
// does not contain any line breaks. So we need to get them back using the
// rendered code in the DOM as reference.
function edgeGetSelectedCodeLines(selection) {
// current selected text without line breaks
var selectionText = selection.toString();
// get the <td> element wrapping the first line of selected code
var tdAnchor = selection.anchorNode;
while (tdAnchor.nodeName !== 'TD') {
tdAnchor = tdAnchor.parentNode;
}
// get the <td> element wrapping the last line of selected code
var tdFocus = selection.focusNode;
while (tdFocus.nodeName !== 'TD') {
tdFocus = tdFocus.parentNode;
}
// extract line numbers
var firstLineNumber = parseInt(tdAnchor.dataset.lineNumber);
var lastLineNumber = parseInt(tdFocus.dataset.lineNumber);
// multi-lines copied case
if (firstLineNumber != lastLineNumber) {
var firstLineText = tdAnchor.textContent;
var lastLineText = tdFocus.textContent;
// if the selection was made backward, swap values
if (firstLineNumber > lastLineNumber) {
var tmp = firstLineNumber;
firstLineNumber = lastLineNumber;
lastLineNumber = tmp;
tmp = firstLineText;
firstLineText = lastLineText;
lastLineText = tmp;
}
// discard not copied characters in first line
while (selectionText.indexOf(firstLineText) !== 0) {
firstLineText = firstLineText.slice(1);
}
// discard not copied characters in last line
while (selectionText.lastIndexOf(lastLineText) === -1) {
lastLineText = lastLineText.slice(0, -1);
}
// reconstruct and return the real copied text
var selectedText = firstLineText;
var hljsLnTable = getHljsLnTable(tdAnchor);
for (var i = firstLineNumber + 1 ; i < lastLineNumber ; ++i) {
var codeLineSel = format('.{0}[{1}="{2}"]', [CODE_BLOCK_NAME, DATA_ATTR_NAME, i]);
var codeLineElt = hljsLnTable.querySelector(codeLineSel);
selectedText += '\n' + codeLineElt.textContent;
}
selectedText += '\n' + lastLineText;
return selectedText;
// single copied line case
} else {
return selectionText;
}
}
// ensure consistent code copy/paste behavior across all browsers
// (see https://github.com/wcoder/highlightjs-line-numbers.js/issues/51)
document.addEventListener('copy', function(e) {
// get current selection
var selection = window.getSelection();
// override behavior when one wants to copy line of codes
if (isHljsLnCodeDescendant(selection.anchorNode)) {
var selectionText;
// workaround an issue with Microsoft Edge as copied line breaks
// are removed otherwise from the selection string
if (window.navigator.userAgent.indexOf('Edge') !== -1) {
selectionText = edgeGetSelectedCodeLines(selection);
} else {
// other browsers can directly use the selection string
selectionText = selection.toString();
}
e.clipboardData.setData(
'text/plain',
selectionText
.replace(/(^\t)/gm, '')
);
e.preventDefault();
}
});
function addStyles () {
var css = d.createElement('style');
css.type = 'text/css';
css.innerHTML = format(
'.{0}{border-collapse:collapse}' +
'.{0} td{padding:0}' +
'.{1}:before{content:attr({2})}',
[
TABLE_NAME,
NUMBER_LINE_NAME,
DATA_ATTR_NAME
]);
d.getElementsByTagName('head')[0].appendChild(css);
}
function initLineNumbersOnLoad (options) {
if (d.readyState === 'interactive' || d.readyState === 'complete') {
documentReady(options);
} else {
w.addEventListener('DOMContentLoaded', function () {
documentReady(options);
});
}
}
function documentReady (options) {
try {
var blocks = d.querySelectorAll('code.hljs,code.nohighlight');
for (var i in blocks) {
if (blocks.hasOwnProperty(i)) {
if (!isPluginDisabledForBlock(blocks[i])) {
lineNumbersBlock(blocks[i], options);
}
}
}
} catch (e) {
w.console.error('LineNumbers error: ', e);
}
}
function isPluginDisabledForBlock(element) {
return element.classList.contains('nohljsln');
}
function lineNumbersBlock (element, options) {
if (typeof element !== 'object') return;
async(function () {
element.innerHTML = lineNumbersInternal(element, options);
});
}
function lineNumbersValue (value, options) {
if (typeof value !== 'string') return;
var element = document.createElement('code')
element.innerHTML = value
return lineNumbersInternal(element, options);
}
function lineNumbersInternal (element, options) {
var internalOptions = mapOptions(element, options);
duplicateMultilineNodes(element);
return addLineNumbersBlockFor(element.innerHTML, internalOptions);
}
function addLineNumbersBlockFor (inputHtml, options) {
var lines = getLines(inputHtml);
// if last line contains only carriage return remove it
if (lines[lines.length-1].trim() === '') {
lines.pop();
}
if (lines.length > 1 || options.singleLine) {
var html = '';
for (var i = 0, l = lines.length; i < l; i++) {
html += format(
'<tr>' +
'<td class="{0} {1}" {3}="{5}">' +
'</td>' +
'<td class="{0} {4}" {3}="{5}">' +
'{6}' +
'</td>' +
'</tr>',
[
LINE_NAME,
NUMBERS_BLOCK_NAME,
NUMBER_LINE_NAME,
DATA_ATTR_NAME,
CODE_BLOCK_NAME,
i + options.startFrom,
lines[i].length > 0 ? lines[i] : ' '
]);
}
return format('<table class="{0}">{1}</table>', [ TABLE_NAME, html ]);
}
return inputHtml;
}
/**
* @param {HTMLElement} element Code block.
* @param {Object} options External API options.
* @returns {Object} Internal API options.
*/
function mapOptions (element, options) {
options = options || {};
return {
singleLine: getSingleLineOption(options),
startFrom: getStartFromOption(element, options)
};
}
function getSingleLineOption (options) {
var defaultValue = false;
if (!!options.singleLine) {
return options.singleLine;
}
return defaultValue;
}
function getStartFromOption (element, options) {
var defaultValue = 1;
var startFrom = defaultValue;
if (isFinite(options.startFrom)) {
startFrom = options.startFrom;
}
// can be overridden because local option is priority
var value = getAttribute(element, 'data-ln-start-from');
if (value !== null) {
startFrom = toNumber(value, defaultValue);
}
return startFrom;
}
/**
* Recursive method for fix multi-line elements implementation in highlight.js
* Doing deep passage on child nodes.
* @param {HTMLElement} element
*/
function duplicateMultilineNodes (element) {
var nodes = element.childNodes;
for (var node in nodes) {
if (nodes.hasOwnProperty(node)) {
var child = nodes[node];
if (getLinesCount(child.textContent) > 0) {
if (child.childNodes.length > 0) {
duplicateMultilineNodes(child);
} else {
duplicateMultilineNode(child.parentNode);
}
}
}
}
}
/**
* Method for fix multi-line elements implementation in highlight.js
* @param {HTMLElement} element
*/
function duplicateMultilineNode (element) {
var className = element.className;
if ( ! /hljs-/.test(className)) return;
var lines = getLines(element.innerHTML);
for (var i = 0, result = ''; i < lines.length; i++) {
var lineText = lines[i].length > 0 ? lines[i] : ' ';
result += format('<span class="{0}">{1}</span>\n', [ className, lineText ]);
}
element.innerHTML = result.trim();
}
function getLines (text) {
if (text.length === 0) return [];
return text.split(BREAK_LINE_REGEXP);
}
function getLinesCount (text) {
return (text.trim().match(BREAK_LINE_REGEXP) || []).length;
}
///
/// HELPERS
///
function async (func) {
w.setTimeout(func, 0);
}
/**
* {@link https://wcoder.github.io/notes/string-format-for-string-formating-in-javascript}
* @param {string} format
* @param {array} args
*/
function format (format, args) {
return format.replace(/\{(\d+)\}/g, function(m, n){
return args[n] !== undefined ? args[n] : m;
});
}
/**
* @param {HTMLElement} element Code block.
* @param {String} attrName Attribute name.
* @returns {String} Attribute value or empty.
*/
function getAttribute (element, attrName) {
return element.hasAttribute(attrName) ? element.getAttribute(attrName) : null;
}
/**
* @param {String} str Source string.
* @param {Number} fallback Fallback value.
* @returns Parsed number or fallback value.
*/
function toNumber (str, fallback) {
if (!str) return fallback;
var number = Number(str);
return isFinite(number) ? number : fallback;
}
}(window, document));

View File

@ -0,0 +1 @@
!function(r,o){"use strict";var e,l="hljs-ln",s="hljs-ln-line",f="hljs-ln-code",c="hljs-ln-numbers",u="hljs-ln-n",h="data-line-number",n=/\r\n|\r|\n/g;function t(e){for(var n=e.toString(),t=e.anchorNode;"TD"!==t.nodeName;)t=t.parentNode;for(var r=e.focusNode;"TD"!==r.nodeName;)r=r.parentNode;var e=parseInt(t.dataset.lineNumber),o=parseInt(r.dataset.lineNumber);if(e==o)return n;var a,i=t.textContent,l=r.textContent;for(o<e&&(a=e,e=o,o=a,a=i,i=l,l=a);0!==n.indexOf(i);)i=i.slice(1);for(;-1===n.lastIndexOf(l);)l=l.slice(0,-1);for(var s=i,c=function(e){for(var n=e;"TABLE"!==n.nodeName;)n=n.parentNode;return n}(t),u=e+1;u<o;++u){var d=v('.{0}[{1}="{2}"]',[f,h,u]);s+="\n"+c.querySelector(d).textContent}return s+="\n"+l}function a(e){try{var n,t=o.querySelectorAll("code.hljs,code.nohighlight");for(n in t)!t.hasOwnProperty(n)||t[n].classList.contains("nohljsln")||i(t[n],e)}catch(e){r.console.error("LineNumbers error: ",e)}}function i(e,n){"object"==typeof e&&r.setTimeout(function(){e.innerHTML=d(e,n)},0)}function d(e,n){var n={singleLine:function(e){return e.singleLine||!1}(n=(n=n)||{}),startFrom:function(e,n){var t=1;isFinite(n.startFrom)&&(t=n.startFrom);n=function(e,n){return e.hasAttribute(n)?e.getAttribute(n):null}(e,"data-ln-start-from");null!==n&&(t=function(e,n){if(!e)return n;e=Number(e);return isFinite(e)?e:n}(n,1));return t}(e,n)},e=(!function e(n){var t=n.childNodes;for(var r in t)!t.hasOwnProperty(r)||0<p((r=t[r]).textContent)&&(0<r.childNodes.length?e(r):m(r.parentNode))}(e),e.innerHTML),t=n,r=g(e);if(""===r[r.length-1].trim()&&r.pop(),1<r.length||t.singleLine){for(var o="",a=0,i=r.length;a<i;a++)o+=v('<tr><td class="{0} {1}" {3}="{5}"></td><td class="{0} {4}" {3}="{5}">{6}</td></tr>',[s,c,u,h,f,a+t.startFrom,0<r[a].length?r[a]:" "]);return v('<table class="{0}">{1}</table>',[l,o])}return e}function m(e){var n=e.className;if(/hljs-/.test(n)){for(var t=g(e.innerHTML),r=0,o="";r<t.length;r++)o+=v('<span class="{0}">{1}</span>\n',[n,0<t[r].length?t[r]:" "]);e.innerHTML=o.trim()}}function g(e){return 0===e.length?[]:e.split(n)}function p(e){return(e.trim().match(n)||[]).length}function v(e,t){return e.replace(/\{(\d+)\}/g,function(e,n){return void 0!==t[n]?t[n]:e})}r.hljs?(r.hljs.initLineNumbersOnLoad=function(e){"interactive"===o.readyState||"complete"===o.readyState?a(e):r.addEventListener("DOMContentLoaded",function(){a(e)})},r.hljs.lineNumbersBlock=i,r.hljs.lineNumbersValue=function(e,n){var t;if("string"==typeof e)return(t=document.createElement("code")).innerHTML=e,d(t,n)},(e=o.createElement("style")).type="text/css",e.innerHTML=v(".{0}{border-collapse:collapse}.{0} td{padding:0}.{1}:before{content:attr({2})}",[l,u,h]),o.getElementsByTagName("head")[0].appendChild(e)):r.console.error("highlight.js not detected!"),document.addEventListener("copy",function(e){var n=window.getSelection();!function(e){for(var n=e;n;){if(n.className&&-1!==n.className.indexOf("hljs-ln-code"))return 1;n=n.parentNode}}(n.anchorNode)||(n=-1!==window.navigator.userAgent.indexOf("Edge")?t(n):n.toString(),e.clipboardData.setData("text/plain",n.replace(/(^\t)/gm,"")),e.preventDefault())})}(window,document);

5171
docs/scripts/third-party/hljs-original.js vendored Normal file

File diff suppressed because it is too large Load Diff

1
docs/scripts/third-party/hljs.js vendored Normal file

File diff suppressed because one or more lines are too long

5
docs/scripts/third-party/popper.js vendored Normal file

File diff suppressed because one or more lines are too long

1
docs/scripts/third-party/tippy.js vendored Normal file

File diff suppressed because one or more lines are too long

672
docs/scripts/third-party/tocbot.js vendored Normal file
View File

@ -0,0 +1,672 @@
/* eslint no-var: off */
var defaultOptions = {
ignoreSelector: '.js-toc-ignore',
linkClass: 'toc-link',
extraLinkClasses: '',
activeLinkClass: 'is-active-link',
listClass: 'toc-list',
extraListClasses: '',
isCollapsedClass: 'is-collapsed',
collapsibleClass: 'is-collapsible',
listItemClass: 'toc-list-item',
activeListItemClass: 'is-active-li',
collapseDepth: 0,
scrollSmooth: true,
scrollSmoothDuration: 420,
scrollSmoothOffset: 0,
scrollEndCallback: function (e) { },
throttleTimeout: 50,
positionFixedSelector: null,
positionFixedClass: 'is-position-fixed',
fixedSidebarOffset: 'auto',
includeHtml: false,
includeTitleTags: false,
orderedList: true,
scrollContainer: null,
skipRendering: false,
headingLabelCallback: false,
ignoreHiddenElements: false,
headingObjectCallback: null,
basePath: '',
disableTocScrollSync: false
}
function ParseContent(options) {
var reduce = [].reduce
/**
* Get the last item in an array and return a reference to it.
* @param {Array} array
* @return {Object}
*/
function getLastItem(array) {
return array[array.length - 1]
}
/**
* Get heading level for a heading dom node.
* @param {HTMLElement} heading
* @return {Number}
*/
function getHeadingLevel(heading) {
return +heading.nodeName.toUpperCase().replace('H', '')
}
/**
* Get important properties from a heading element and store in a plain object.
* @param {HTMLElement} heading
* @return {Object}
*/
function getHeadingObject(heading) {
// each node is processed twice by this method because nestHeadingsArray() and addNode() calls it
// first time heading is real DOM node element, second time it is obj
// that is causing problem so I am processing only original DOM node
if (!(heading instanceof window.HTMLElement)) return heading
if (options.ignoreHiddenElements && (!heading.offsetHeight || !heading.offsetParent)) {
return null
}
const headingLabel = heading.getAttribute('data-heading-label') ||
(options.headingLabelCallback ? String(options.headingLabelCallback(heading.textContent)) : heading.textContent.trim())
var obj = {
id: heading.id,
children: [],
nodeName: heading.nodeName,
headingLevel: getHeadingLevel(heading),
textContent: headingLabel
}
if (options.includeHtml) {
obj.childNodes = heading.childNodes
}
if (options.headingObjectCallback) {
return options.headingObjectCallback(obj, heading)
}
return obj
}
/**
* Add a node to the nested array.
* @param {Object} node
* @param {Array} nest
* @return {Array}
*/
function addNode(node, nest) {
var obj = getHeadingObject(node)
var level = obj.headingLevel
var array = nest
var lastItem = getLastItem(array)
var lastItemLevel = lastItem
? lastItem.headingLevel
: 0
var counter = level - lastItemLevel
while (counter > 0) {
lastItem = getLastItem(array)
// Handle case where there are multiple h5+ in a row.
if (lastItem && level === lastItem.headingLevel) {
break
} else if (lastItem && lastItem.children !== undefined) {
array = lastItem.children
}
counter--
}
if (level >= options.collapseDepth) {
obj.isCollapsed = true
}
array.push(obj)
return array
}
/**
* Select headings in content area, exclude any selector in options.ignoreSelector
* @param {HTMLElement} contentElement
* @param {Array} headingSelector
* @return {Array}
*/
function selectHeadings(contentElement, headingSelector) {
var selectors = headingSelector
if (options.ignoreSelector) {
selectors = headingSelector.split(',')
.map(function mapSelectors(selector) {
return selector.trim() + ':not(' + options.ignoreSelector + ')'
})
}
try {
return contentElement.querySelectorAll(selectors)
} catch (e) {
console.warn('Headers not found with selector: ' + selectors); // eslint-disable-line
return null
}
}
/**
* Nest headings array into nested arrays with 'children' property.
* @param {Array} headingsArray
* @return {Object}
*/
function nestHeadingsArray(headingsArray) {
return reduce.call(headingsArray, function reducer(prev, curr) {
var currentHeading = getHeadingObject(curr)
if (currentHeading) {
addNode(currentHeading, prev.nest)
}
return prev
}, {
nest: []
})
}
return {
nestHeadingsArray: nestHeadingsArray,
selectHeadings: selectHeadings
}
}
function BuildHtml(options) {
var forEach = [].forEach
var some = [].some
var body = document.body
var tocElement
var mainContainer = document.querySelector(options.contentSelector)
var currentlyHighlighting = true
var SPACE_CHAR = ' '
/**
* Create link and list elements.
* @param {Object} d
* @param {HTMLElement} container
* @return {HTMLElement}
*/
function createEl(d, container) {
var link = container.appendChild(createLink(d))
if (d.children.length) {
var list = createList(d.isCollapsed)
d.children.forEach(function (child) {
createEl(child, list)
})
link.appendChild(list)
}
}
/**
* Render nested heading array data into a given element.
* @param {HTMLElement} parent Optional. If provided updates the {@see tocElement} to match.
* @param {Array} data
* @return {HTMLElement}
*/
function render(parent, data) {
var collapsed = false
var container = createList(collapsed)
data.forEach(function (d) {
createEl(d, container)
})
// Return if no TOC element is provided or known.
tocElement = parent || tocElement
if (tocElement === null) {
return
}
// Remove existing child if it exists.
if (tocElement.firstChild) {
tocElement.removeChild(tocElement.firstChild)
}
// Just return the parent and don't append the list if no links are found.
if (data.length === 0) {
return tocElement
}
// Append the Elements that have been created
return tocElement.appendChild(container)
}
/**
* Create link element.
* @param {Object} data
* @return {HTMLElement}
*/
function createLink(data) {
var item = document.createElement('li')
var a = document.createElement('a')
if (options.listItemClass) {
item.setAttribute('class', options.listItemClass)
}
if (options.onClick) {
a.onclick = options.onClick
}
if (options.includeTitleTags) {
a.setAttribute('title', data.textContent)
}
if (options.includeHtml && data.childNodes.length) {
forEach.call(data.childNodes, function (node) {
a.appendChild(node.cloneNode(true))
})
} else {
// Default behavior.
a.textContent = data.textContent
}
a.setAttribute('href', options.basePath + '#' + data.id)
a.setAttribute('class', options.linkClass +
SPACE_CHAR + 'node-name--' + data.nodeName +
SPACE_CHAR + options.extraLinkClasses)
item.appendChild(a)
return item
}
/**
* Create list element.
* @param {Boolean} isCollapsed
* @return {HTMLElement}
*/
function createList(isCollapsed) {
var listElement = (options.orderedList) ? 'ol' : 'ul'
var list = document.createElement(listElement)
var classes = options.listClass +
SPACE_CHAR + options.extraListClasses
if (isCollapsed) {
classes += SPACE_CHAR + options.collapsibleClass
classes += SPACE_CHAR + options.isCollapsedClass
}
list.setAttribute('class', classes)
return list
}
/**
* Update fixed sidebar class.
* @return {HTMLElement}
*/
function updateFixedSidebarClass() {
if (options.scrollContainer && document.querySelector(options.scrollContainer)) {
var top
top = document.querySelector(options.scrollContainer).scrollTop
} else {
top = document.documentElement.scrollTop || body.scrollTop
}
var posFixedEl = document.querySelector(options.positionFixedSelector)
if (options.fixedSidebarOffset === 'auto') {
options.fixedSidebarOffset = tocElement.offsetTop
}
if (top > options.fixedSidebarOffset) {
if (posFixedEl.className.indexOf(options.positionFixedClass) === -1) {
posFixedEl.className += SPACE_CHAR + options.positionFixedClass
}
} else {
posFixedEl.className = posFixedEl.className.split(SPACE_CHAR + options.positionFixedClass).join('')
}
}
/**
* Get top position of heading
* @param {HTMLElement} obj
* @return {int} position
*/
function getHeadingTopPos(obj) {
var position = 0
if (obj !== null) {
position = obj.offsetTop
if (options.hasInnerContainers) { position += getHeadingTopPos(obj.offsetParent) }
}
return position
}
function updateListActiveElement(topHeader) {
var forEach = [].forEach
var tocLinks = tocElement
.querySelectorAll('.' + options.linkClass)
forEach.call(tocLinks, function (tocLink) {
tocLink.className = tocLink.className.split(SPACE_CHAR + options.activeLinkClass).join('')
})
var tocLis = tocElement
.querySelectorAll('.' + options.listItemClass)
forEach.call(tocLis, function (tocLi) {
tocLi.className = tocLi.className.split(SPACE_CHAR + options.activeListItemClass).join('')
})
// Add the active class to the active tocLink.
var activeTocLink = tocElement
.querySelector('.' + options.linkClass +
'.node-name--' + topHeader.nodeName +
'[href="' + options.basePath + '#' + topHeader.id.replace(/([ #;&,.+*~':"!^$[\]()=>|/@])/g, '\\$1') + '"]')
if (activeTocLink && activeTocLink.className.indexOf(options.activeLinkClass) === -1) {
activeTocLink.className += SPACE_CHAR + options.activeLinkClass
}
var li = activeTocLink && activeTocLink.parentNode
if (li && li.className.indexOf(options.activeListItemClass) === -1) {
li.className += SPACE_CHAR + options.activeListItemClass
}
var tocLists = tocElement
.querySelectorAll('.' + options.listClass + '.' + options.collapsibleClass)
// Collapse the other collapsible lists.
forEach.call(tocLists, function (list) {
if (list.className.indexOf(options.isCollapsedClass) === -1) {
list.className += SPACE_CHAR + options.isCollapsedClass
}
})
// Expand the active link's collapsible list and its sibling if applicable.
if (activeTocLink && activeTocLink.nextSibling && activeTocLink.nextSibling.className.indexOf(options.isCollapsedClass) !== -1) {
activeTocLink.nextSibling.className = activeTocLink.nextSibling.className.split(SPACE_CHAR + options.isCollapsedClass).join('')
}
removeCollapsedFromParents(activeTocLink && activeTocLink.parentNode.parentNode)
}
/**
* Update TOC highlighting and collpased groupings.
*/
function updateToc(headingsArray) {
// If a fixed content container was set
if (options.scrollContainer && document.querySelector(options.scrollContainer)) {
var top
top = document.querySelector(options.scrollContainer).scrollTop
} else {
top = document.documentElement.scrollTop || body.scrollTop
}
// Add fixed class at offset
if (options.positionFixedSelector) {
updateFixedSidebarClass()
}
// Get the top most heading currently visible on the page so we know what to highlight.
var headings = headingsArray
var topHeader
// Using some instead of each so that we can escape early.
if (currentlyHighlighting &&
tocElement !== null &&
headings.length > 0) {
some.call(headings, function (heading, i) {
var modifiedTopOffset = top + 10
if (mainContainer) {
modifiedTopOffset += mainContainer.clientHeight * (mainContainer.scrollTop) / (mainContainer.scrollHeight - mainContainer.clientHeight)
}
if (getHeadingTopPos(heading) > modifiedTopOffset) {
// Don't allow negative index value.
var index = (i === 0) ? i : i - 1
topHeader = headings[index]
return true
} else if (i === headings.length - 1) {
// This allows scrolling for the last heading on the page.
topHeader = headings[headings.length - 1]
return true
}
})
// Remove the active class from the other tocLinks.
updateListActiveElement(topHeader)
}
}
/**
* Remove collpased class from parent elements.
* @param {HTMLElement} element
* @return {HTMLElement}
*/
function removeCollapsedFromParents(element) {
if (element && element.className.indexOf(options.collapsibleClass) !== -1 && element.className.indexOf(options.isCollapsedClass) !== -1) {
element.className = element.className.split(SPACE_CHAR + options.isCollapsedClass).join('')
return removeCollapsedFromParents(element.parentNode.parentNode)
}
return element
}
/**
* Disable TOC Animation when a link is clicked.
* @param {Event} event
*/
function disableTocAnimation(event) {
var target = event.target || event.srcElement
if (typeof target.className !== 'string' || target.className.indexOf(options.linkClass) === -1) {
return
}
// Bind to tocLink clicks to temporarily disable highlighting
// while smoothScroll is animating.
currentlyHighlighting = false
}
/**
* Enable TOC Animation.
*/
function enableTocAnimation() {
currentlyHighlighting = true
}
return {
enableTocAnimation: enableTocAnimation,
disableTocAnimation: disableTocAnimation,
render: render,
updateToc: updateToc,
updateListActiveElement: updateListActiveElement
}
}
function updateTocScroll(options) {
var toc = options.tocElement || document.querySelector(options.tocSelector)
if (toc && toc.scrollHeight > toc.clientHeight) {
var activeItem = toc.querySelector('.' + options.activeListItemClass)
if (activeItem) {
var topOffset = toc.getBoundingClientRect().top
toc.scrollTop = activeItem.offsetTop - topOffset
}
}
}
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory(root))
} else if (typeof exports === 'object') {
module.exports = factory(root)
} else {
root.tocbot = factory(root)
}
})(typeof global !== 'undefined' ? global : this.window || this.global, function (root) {
'use strict'
var options = {}
var tocbot = {}
var buildHtml
var parseContent
// Just return if its not a browser.
var supports = !!root && !!root.document && !!root.document.querySelector && !!root.addEventListener // Feature test
if (typeof window === 'undefined' && !supports) {
return
}
var headingsArray
// From: https://github.com/Raynos/xtend
var hasOwnProperty = Object.prototype.hasOwnProperty
function extend() {
var target = {}
for (var i = 0; i < arguments.length; i++) {
var source = arguments[i]
for (var key in source) {
if (hasOwnProperty.call(source, key)) {
target[key] = source[key]
}
}
}
return target
}
// From: https://remysharp.com/2010/07/21/throttling-function-calls
function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 250)
var last
var deferTimer
return function () {
var context = scope || this
var now = +new Date()
var args = arguments
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer)
deferTimer = setTimeout(function () {
last = now
fn.apply(context, args)
}, threshhold)
} else {
last = now
fn.apply(context, args)
}
}
}
function getContentElement(options) {
try {
return options.contentElement || document.querySelector(options.contentSelector)
} catch (e) {
console.warn('Contents element not found: ' + options.contentSelector) // eslint-disable-line
return null
}
}
function getTocElement(options) {
try {
return options.tocElement || document.querySelector(options.tocSelector)
} catch (e) {
console.warn('TOC element not found: ' + options.tocSelector) // eslint-disable-line
return null
}
}
/**
* Destroy tocbot.
*/
tocbot.destroy = function () {
var tocElement = getTocElement(options)
if (tocElement === null) {
return
}
if (!options.skipRendering) {
// Clear HTML.
if (tocElement) {
tocElement.innerHTML = ''
}
}
// Remove event listeners.
if (options.scrollContainer && document.querySelector(options.scrollContainer)) {
document.querySelector(options.scrollContainer).removeEventListener('scroll', this._scrollListener, false)
document.querySelector(options.scrollContainer).removeEventListener('resize', this._scrollListener, false)
} else {
document.removeEventListener('scroll', this._scrollListener, false)
document.removeEventListener('resize', this._scrollListener, false)
}
}
/**
* Initialize tocbot.
* @param {object} customOptions
*/
tocbot.init = function (customOptions) {
// feature test
if (!supports) {
return
}
// Merge defaults with user options.
// Set to options variable at the top.
options = extend(defaultOptions, customOptions || {})
this.options = options
this.state = {}
// Init smooth scroll if enabled (default).
if (options.scrollSmooth) {
options.duration = options.scrollSmoothDuration
options.offset = options.scrollSmoothOffset
}
// Pass options to these modules.
buildHtml = BuildHtml(options)
parseContent = ParseContent(options)
// For testing purposes.
this._buildHtml = buildHtml
this._parseContent = parseContent
this._headingsArray = headingsArray
this.updateTocListActiveElement = buildHtml.updateListActiveElement
// Destroy it if it exists first.
tocbot.destroy()
var contentElement = getContentElement(options)
if (contentElement === null) {
return
}
var tocElement = getTocElement(options)
if (tocElement === null) {
return
}
// Get headings array.
headingsArray = parseContent.selectHeadings(contentElement, options.headingSelector)
// Return if no headings are found.
if (headingsArray === null) {
return
}
// Build nested headings array.
var nestedHeadingsObj = parseContent.nestHeadingsArray(headingsArray)
var nestedHeadings = nestedHeadingsObj.nest
// Render.
if (!options.skipRendering) {
buildHtml.render(tocElement, nestedHeadings)
}
// Update Sidebar and bind listeners.
this._scrollListener = throttle(function (e) {
buildHtml.updateToc(headingsArray)
!options.disableTocScrollSync && updateTocScroll(options)
var isTop = e && e.target && e.target.scrollingElement && e.target.scrollingElement.scrollTop === 0
if ((e && (e.eventPhase === 0 || e.currentTarget === null)) || isTop) {
buildHtml.updateToc(headingsArray)
if (options.scrollEndCallback) {
options.scrollEndCallback(e)
}
}
}, options.throttleTimeout)
this._scrollListener()
if (options.scrollContainer && document.querySelector(options.scrollContainer)) {
document.querySelector(options.scrollContainer).addEventListener('scroll', this._scrollListener, false)
document.querySelector(options.scrollContainer).addEventListener('resize', this._scrollListener, false)
} else {
document.addEventListener('scroll', this._scrollListener, false)
document.addEventListener('resize', this._scrollListener, false)
}
return this
}
/**
* Refresh tocbot.
*/
tocbot.refresh = function (customOptions) {
tocbot.destroy()
tocbot.init(customOptions || this.options)
}
// Make tocbot available globally.
root.tocbot = tocbot
return tocbot
})

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,412 @@
::selection {
background: #ffce76;
color: #222;
}
body {
background-color: #1a1a1a;
color: #fff;
}
a,
a:active {
color: #0bf;
}
hr {
color: #222;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #fff;
}
.sidebar {
background-color: #222;
color: #999;
}
.sidebar-title {
color: #999;
}
.sidebar-section-title {
color: #999;
}
.sidebar-section-title:hover {
background: #252525;
}
.with-arrow {
fill: #999;
}
.sidebar-section-children-container {
background: #292929;
}
.sidebar-section-children.active {
background: #444;
}
.sidebar-section-children a:hover {
background: #2c2c2c;
}
.sidebar-section-children a {
color: #fff;
}
.navbar-container {
background: #1a1a1a;
}
.icon-button svg,
.navbar-item a {
color: #999;
fill: #999;
}
.font-size-tooltip .icon-button svg {
fill: #fff;
}
.font-size-tooltip .icon-button.disabled {
background: #999;
}
.icon-button:hover {
background: #333;
}
.icon-button:active {
background: #444;
}
.navbar-item a:active {
background-color: #222;
color: #aaa;
}
.navbar-item:hover {
background: #202020;
}
.footer {
background: #222;
color: #999;
}
.footer a {
color: #999;
}
.toc-link {
color: #777;
font-size: 0.875rem;
transition: color 0.3s;
}
.toc-link.is-active-link {
color: #fff;
}
.has-anchor .link-anchor {
color: #555;
}
.has-anchor .link-anchor:hover {
color: #888;
}
tt,
code,
kbd,
samp {
background: #333;
}
.signature-attributes {
color: #aaa;
}
.ancestors {
color: #999;
}
.ancestors a {
color: #999 !important;
}
.important {
color: #c51313;
}
.type-signature {
color: #00918e;
}
.name,
.name a {
color: #f7f7f7;
}
.details {
background: #222;
color: #fff;
}
.prettyprint {
background: #222;
}
.member-item-container strong,
.method-member-container strong {
color: #fff;
}
.pre-top-bar-container {
background: #292929;
}
.prettyprint.source,
.prettyprint code {
background-color: #222;
color: #c9d1d9;
}
.pre-div {
background-color: #222;
}
.hljs .hljs-ln-numbers {
color: #777;
}
.hljs .selected {
background: #444;
}
.hljs .selected .hljs-ln-numbers {
color: #eee;
}
table .name,
.params .name,
.props .name,
.name code {
color: #fff;
}
table td,
.params td {
background-color: #292929;
}
table thead th,
.params thead th,
.props thead th {
background-color: #222;
color: #fff;
}
/* stylelint-disable */
table .params thead tr,
.params .params thead tr,
.props .props thead tr {
background-color: #222;
color: #fff;
}
.disabled {
color: #aaaaaa;
}
.code-lang-name {
color: #ff8a00;
}
.tooltip {
background: #ffce76;
color: #222;
}
/* code */
.hljs-comment {
color: #8b949e;
}
.hljs-doctag,
.hljs-keyword,
.hljs-template-tag,
.hljs-variable.language_ {
color: #ff7b72;
}
.hljs-template-variable,
.hljs-type {
color: #30ac7c;
}
.hljs-meta,
.hljs-string,
.hljs-regexp {
color: #a5d6ff;
}
.hljs-title.class_,
.hljs-title {
color: #ffa657;
}
.hljs-title.class_.inherited__,
.hljs-title.function_ {
color: #d2a8ff;
}
.hljs-attr,
.hljs-attribute,
.hljs-literal,
.hljs-meta,
.hljs-number,
.hljs-operator,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-id,
.hljs-variable {
color: #79c0ff;
}
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #a5d6ff;
}
.hljs-built_in,
.hljs-symbol {
color: #ffa657;
}
.hljs-code,
.hljs-comment,
.hljs-formula {
color: #8b949e;
}
.hljs-name,
.hljs-quote,
.hljs-selector-pseudo,
.hljs-selector-tag {
color: #7ee787;
}
.hljs-subst {
color: #c9d1d9;
}
.hljs-section {
color: #1f6feb;
font-weight: 700;
}
.hljs-bullet {
color: #f2cc60;
}
.hljs-emphasis {
color: #c9d1d9;
font-style: italic;
}
.hljs-strong {
color: #c9d1d9;
font-weight: 700;
}
/* code end*/
blockquote {
background: #222;
color: #fff;
}
.search-container {
background: rgba(255, 255, 255, 0.1);
}
.icon-button.search-close-button svg {
fill: #a00;
}
.search-container .wrapper {
background: #222;
}
.search-result-c {
color: #666;
}
.search-box-c {
fill: #333;
}
.search-input {
background: #333;
color: #fff;
}
.search-box-c svg {
fill: #fff;
}
.search-result-item {
background: #333;
}
.search-result-item:hover {
background: #444;
}
.search-result-item:active {
background: #555;
}
.search-result-item-title {
color: #fff;
}
.search-result-item-p {
color: #aaa;
}
.mobile-menu-icon-container .icon-button {
background: #333;
}
.mobile-sidebar-container {
background: #1a1a1a;
}
.mobile-sidebar-wrapper {
background: #222;
}
.child-tutorial {
border-color: #555;
color: #f3f3f3;
}
.child-tutorial:hover {
background: #222;
}

View File

@ -0,0 +1,482 @@
.light ::selection {
background: #ffce76;
color: #1d1919;
}
/* stylelint-disable-next-line selector-no-qualifying-type,rule-empty-line-before */
body.light {
background-color: #fff;
color: #111;
}
.light a,
.light a:active {
color: #007bff;
}
.light hr {
color: #f7f7f7;
}
.light h1,
.light h2,
.light h3,
.light h4,
.light h5,
.light h6 {
color: #111;
}
.light .sidebar {
background-color: #f7f7f7;
color: #222;
}
.light .sidebar-title {
color: #222;
}
.light .sidebar-section-title {
color: #222;
}
.light .sidebar-section-title:hover,
.light .sidebar-section-title.active {
background: #eee;
}
.light .with-arrow {
fill: #111;
}
.light .sidebar-section-children-container {
background: #eee;
}
.light .sidebar-section-children.active {
background: #ccc;
}
.light .sidebar-section-children a:hover {
background: #e0e0e0;
}
.light .sidebar-section-children a {
color: #111;
}
.light .navbar-container {
background: #fff;
}
.light .icon-button svg,
.light .navbar-item a {
color: #222;
fill: #222;
}
.light .tippy-box {
background: #eee;
color: #111;
}
.light .tippy-arrow {
color: #f1f1f1;
}
/* stylelint-disable-next-line selector-max-compound-selectors,rule-empty-line-before */
.light .font-size-tooltip .icon-button svg {
fill: #111;
}
/* stylelint-disable-next-line selector-max-compound-selectors, rule-empty-line-before */
.light .font-size-tooltip .icon-button.disabled svg {
fill: #999;
}
.light .icon-button:hover {
background: #ddd;
}
.light .icon-button:active {
background: #ccc;
}
.light .navbar-item a:active {
background-color: #eee;
color: #333;
}
.light .navbar-item:hover {
background: #f7f7f7;
}
.light .footer {
background: #f7f7f7;
color: #111;
}
.light .footer a {
color: #111;
}
.light .toc-link {
color: #999;
font-size: 0.875rem;
transition: color 0.3s;
}
.light .toc-link.is-active-link {
color: #111;
}
.light .has-anchor .link-anchor {
color: #ddd;
}
.light .has-anchor .link-anchor:hover {
color: #ccc;
}
.light .signature-attributes {
color: #aaa;
}
.light .ancestors {
color: #999;
}
.light .ancestors a {
color: #999 !important;
}
.light .important {
color: #ee1313;
}
.light .type-signature {
color: #00918e;
}
.light .name,
.light .name a {
color: #293a80;
}
.light .details {
background: #f9f9f9;
color: #101010;
}
.light .member-item-container strong,
.light .method-member-container strong {
color: #000;
}
.light .prettyprint {
background: #f7f7f7;
}
.light .pre-div {
background: #f7f7f7;
}
.light .hljs .hljs-ln-numbers {
color: #aaa;
}
.light .hljs .selected {
background: #ccc;
}
/* stylelint-disable-next-line selector-no-qualifying-type,rule-empty-line-before */
.light table.hljs-ln td {
background: none;
}
/* stylelint-disable-next-line selector-max-compound-selectors,rule-empty-line-before */
.light .hljs .selected .hljs-ln-numbers {
color: #444;
}
.light .pre-top-bar-container {
background-color: #eee;
}
.light .prettyprint code {
background-color: #f7f7f7;
}
.light table .name,
.light .params .name,
.light .props .name,
.light .name code {
color: #4d4e53;
}
.light table td,
.light .params td {
background: #f7f7f7;
}
/* stylelint-disable-next-line selector-max-compound-selectors,rule-empty-line-before */
.light table thead th,
.light .params thead th,
.light .props thead th {
background-color: #eee;
color: #111;
}
/* stylelint-disable */
.light table .params thead tr,
.light .params .params thead tr,
.light .props .props thead tr {
background-color: #eee;
color: #111;
}
.light .disabled {
color: #454545;
}
.light .code-lang-name {
color: #ff0000;
}
.light .tooltip {
background: #ffce76;
color: #000;
}
/* Code */
.light .hljs-comment,
.light .hljs-quote {
color: #a0a1a7;
}
.light .hljs-doctag,
.light .hljs-keyword,
.light .hljs-formula {
color: #a626a4;
}
.light .hljs-section,
.light .hljs-name,
.light .hljs-selector-tag,
.light .hljs-deletion,
.light .hljs-subst {
color: #e45649;
}
.light .hljs-literal {
color: #0184bb;
}
.light .hljs-string,
.light .hljs-regexp,
.light .hljs-addition,
.light .hljs-attribute,
.light .hljs-meta .hljs-string {
color: #50a14f;
}
.light .hljs-attr,
.light .hljs-variable,
.light .hljs-template-variable,
.light .hljs-type,
.light .hljs-selector-class,
.light .hljs-selector-attr,
.light .hljs-selector-pseudo,
.light .hljs-number {
color: #986801;
}
.light .hljs-symbol,
.light .hljs-bullet,
.light .hljs-link,
.light .hljs-meta,
.light .hljs-selector-id,
.light .hljs-title {
color: #4078f2;
}
.light .hljs-built_in,
.light .hljs-title.class_,
.light .hljs-class .hljs-title {
color: #c18401;
}
.light .hljs-emphasis {
font-style: italic;
}
.light .hljs-strong {
font-weight: bold;
}
.light .hljs-link {
text-decoration: underline;
}
/* Code Ends */
.light blockquote {
background: #eee;
color: #111;
}
.light code {
background: #ddd;
color: #000;
}
.light .search-container {
background: rgba(0, 0, 0, 0.1);
}
.light .search-close-button svg {
fill: #f00;
}
.light .search-container .wrapper {
background: #eee;
}
.light .search-result-c {
color: #aaa;
}
.light .search-box-c svg {
fill: #333;
}
.light .search-input {
background: #f7f7f7;
color: #111;
}
.light .search-result-item {
background: #f7f7f7;
}
.light .search-result-item:hover {
background: #e9e9e9;
}
.light .search-result-item:active {
background: #f7f7f7;
}
.light .search-result-item-title {
color: #111;
}
.light .search-result-item-p {
color: #aaa;
}
.light .mobile-menu-icon-container .icon-button {
background: #e5e5e5;
}
.light .mobile-sidebar-container {
background: #fff;
}
.light .mobile-sidebar-wrapper {
background: #f7f7f7;
}
.light .child-tutorial {
border-color: #aaa;
color: #222;
}
.light .child-tutorial:hover {
background: #ccc;
}

View File

@ -0,0 +1,30 @@
::-webkit-scrollbar {
height: 0.3125rem;
width: 0.3125rem;
}
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-track {
border-radius: 1rem;
}
::-webkit-scrollbar-track {
background: #333;
}
::-webkit-scrollbar-thumb {
background: #555;
outline: 0.06125rem solid #555;
}
.light ::-webkit-scrollbar-track {
background: #ddd;
}
.light ::-webkit-scrollbar-thumb {
background: #aaa;
outline: 0.06125rem solid #aaa;
}

File diff suppressed because one or more lines are too long

1
docs/styles/clean-jsdoc-theme.min.css vendored Normal file

File diff suppressed because one or more lines are too long

183
index.js
View File

@ -5,59 +5,92 @@ const Delete = require("./lib/Delete");
const Update = require("./lib/Update"); const Update = require("./lib/Update");
const { CreateTable, Structure, AlterTable } = require("./lib/Tables"); const { CreateTable, Structure, AlterTable } = require("./lib/Tables");
/**
* @typedef {Object} InstanceOptions
* @property {String} [charset] - Charset to use
* @property {String} [defaultDatabase] - The default database
* @property {Boolean} [multipleStatements] - Whether multiple statements should be allowed in a single query
* @property {Boolean} [insecureAuth] - Whether insecure authentication methods should be allowed
* @property {String} [customIdentifier] - Sets a custom identifier for this instance
* @property {Boolean} [isDefault] - Whether this instance is returned by default via 'getInstance'
*/
/**
* Exported as instance
*/
class awSQL { class awSQL {
#instances = {}; #instances = {}; // Holds instances. Key is identifier/name.
#default; #default; // Holds the identifier of the default instance (the one used if identifier is omitted)
/** /**
* * Creates a new instance (database connection)
* @param {*} hostname * @param {String} hostname - Hostname where the database is located
* @param {*} username * @param {String} username - Username to login with
* @param {*} password * @param {String} password
* @param {*} options * @param {InstanceOptions} [options]
* @returns {Instance} * @returns {Instance}
*/ */
createInstance = (hostname="localhost", username, password, options = { createInstance(hostname="localhost", username, password, options = {
charset:"utf8mb4", charset:"utf8mb4",
defaultDatabase: false, defaultDatabase: false,
multipleStatements: false, multipleStatements: false,
insecureAuth: false, insecureAuth: false,
customIdentifier: false, customIdentifier: false,
isDefault: false, isDefault: false,
})=>{ }){
const identifier = options.customIdentifier||`${username}@${hostname}`; const identifier = options.customIdentifier||`${username}@${hostname}`; // Set identifier to given identifier or [username]@[hostname]
// If an instance with that identifier exists, throw error
if (this.#instances[identifier]) throw new Error(`Can't create new instance with identifier "${identifier}": An instance with the same name already exists`); if (this.#instances[identifier]) throw new Error(`Can't create new instance with identifier "${identifier}": An instance with the same name already exists`);
const instance = new Instance(hostname, username, password, options.charset, options.defaultDatabase, options.multipleStatements, options.insecureAuth); const instance = new Instance(hostname, username, password, options.charset, options.defaultDatabase, options.multipleStatements, options.insecureAuth);
this.#instances[identifier] = instance; this.#instances[identifier] = instance; // Store instance
if (options.createAsDefault) this.#default = identifier; if (options.createAsDefault) this.#default = identifier; // If this instance was created with default option set it as the default instance
return instance; return instance;
} }
/** /**
* Returns an instance matching the given identifier
* *
* @param {String} identifier * Returns default (or first) instance if no identifier is given
* @param {String} [identifier] - Identifier of the instance to get
* @returns {Instance} * @returns {Instance}
*/ */
getInstance = (identifier) => { getInstance(identifier) {
if (Object.keys(this.#instances).length===0) return undefined; if (Object.keys(this.#instances).length===0) return undefined; // If no instance is found at all return -> Safety return
if (!identifier) return this.#default?this.#instances[this.#default]:this.#instances[Object.keys(this.#instances)[0]]; // If no identifier is set return default or first instance // If no identifier is set return default or first instance
return this.#instances[identifier]; if (!identifier) return
this.#default?
this.#instances[this.#default] // If default exists get that
:
this.#instances[Object.keys(this.#instances)[0]]; // Otherwise get instance with that identifier
return this.#instances[identifier]; // If identifier given return that instance
} }
listInstances = () => { /**
* Returns a list of the identifiers of all instances
* @returns {Array<String>}
*/
listInstances(){
return Object.keys(this.#instances); return Object.keys(this.#instances);
} }
deleteInstance = (identifier) => { /**
* Deletes an instance (and closes any open connection)
* @param {String} identifier - Identifier of the instance to delete
* @returns {true}
*/
deleteInstance(identifier){
if (!identifier) throw new Error("Can't delete Instance: No identifier set"); if (!identifier) throw new Error("Can't delete Instance: No identifier set");
if (!this.#instances[identifier]) throw new Error(`Can't delete Instance '${identifier}': No Instance`); if (!this.#instances[identifier]) throw new Error(`Can't delete Instance '${identifier}': No Instance`);
this.#instances[identifier].destroy(); this.#instances[identifier].destroy(); // Memory: Close connection
if (this.#default === identifier) this.#default = undefined; if (this.#default === identifier) this.#default = undefined; // If this instance was default, clear it from default
delete this.#instances[identifier]; delete this.#instances[identifier];
return true; return true;
} }
} }
/**
* An awSQL-Instance
*/
class Instance { class Instance {
#user; #user;
#password; #password;
@ -68,7 +101,6 @@ class Instance {
#connection; #connection;
#selectedDatabase; #selectedDatabase;
constructor(hostname="localhost", username, password, charset="utf8mb4", defaultDatabase=false, multipleStatements=false, insecureAuth=false){ constructor(hostname="localhost", username, password, charset="utf8mb4", defaultDatabase=false, multipleStatements=false, insecureAuth=false){
this.#host = hostname; this.#host = hostname;
this.#user = username; this.#user = username;
@ -81,11 +113,11 @@ class Instance {
/** /**
* Connects the instance * Connects the instance
* * @returns {undefined}
*/ */
connect = () => { connect(){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const connection = mysql.createConnection({ const connection = mysql.createConnection({ // Create a new mysql-connection
user: this.#user, user: this.#user,
password: this.#password, password: this.#password,
host: this.#host, host: this.#host,
@ -93,18 +125,26 @@ class Instance {
multipleStatements: this.#multipleStatements, multipleStatements: this.#multipleStatements,
charset: this.#charset charset: this.#charset
}); });
this.#connection = connection; this.#connection = connection; // Store the connection
connection.connect((err) =>{ connection.connect((err) =>{
if (err) throw err; if (err) throw err;
resolve(`Connected to ${this.#host} with user ${this.#user}`); resolve(`Connected to ${this.#host} with user ${this.#user}`);
}); });
connection.on("error", (err) => {
if (!err.fatal){
return;
}
this.destroy(); // Memory: Destroy if connection errored
this.connect(); // And try to reconnect
})
}) })
} }
/** /**
* Destroys the instance * Destroys the instance
* @returns {true}
*/ */
destroy = () => { destroy(){
if (this.#connection) this.#connection.end(); if (this.#connection) this.#connection.end();
this.#connection = undefined; this.#connection = undefined;
return true; return true;
@ -113,10 +153,10 @@ class Instance {
/** /**
* Performs a raw query * Performs a raw query
* @param {String} queryString The sql query string to perform. * @param {String} queryString The sql query string to perform.
* @param {Array} values - An array holding all replacable ?-values from left to right. * @param {Array<Any>} values - An array holding all replacable ?-values from left to right.
* @returns {Any} - The individual result of your query * @returns {Any} - The individual result of your query
*/ */
queryRaw = (queryString, values) => { queryRaw(queryString, values){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.#connection) throw new Error("Querying failed: No connection"); if (!this.#connection) throw new Error("Querying failed: No connection");
this.#connection.query(queryString, values, (err, result) => { this.#connection.query(queryString, values, (err, result) => {
@ -129,9 +169,9 @@ class Instance {
/** /**
* Returns a list of database names the user has access to * Returns a list of database names the user has access to
* @param {Boolean} excludeSchema - Whether to exclude the default database 'information_schema' * @param {Boolean} excludeSchema - Whether to exclude the default database 'information_schema'
* @returns {Array} * @returns {Array<String>}
*/ */
getDatabases = async (excludeSchema=false) => { async getDatabases (excludeSchema=false){
let dbs = await this.queryRaw("SHOW DATABASES;"); let dbs = await this.queryRaw("SHOW DATABASES;");
if (excludeSchema) dbs = dbs.filter((db)=>db.Database!=="information_schema") if (excludeSchema) dbs = dbs.filter((db)=>db.Database!=="information_schema")
return dbs.map(db => db.Database); return dbs.map(db => db.Database);
@ -142,7 +182,7 @@ class Instance {
* @param {String} name - Name of the database * @param {String} name - Name of the database
* @returns {this} * @returns {this}
*/ */
selectDatabase = (name) => { selectDatabase(name){
this.#selectedDatabase = name; this.#selectedDatabase = name;
return this; return this;
} }
@ -153,7 +193,7 @@ class Instance {
* @param {String} [database] - Database to select. Can be empty as long as a default database was set with 'selectDatabase' * @param {String} [database] - Database to select. Can be empty as long as a default database was set with 'selectDatabase'
* @returns {Array} * @returns {Array}
*/ */
getTables = async (database) => { async getTables(database){
if (!this.#multipleStatements) throw new Error("getTables: multipleStatements must be set to 'true' in instance options"); if (!this.#multipleStatements) throw new Error("getTables: multipleStatements must be set to 'true' in instance options");
if (!this.#selectedDatabase && !database) throw new Error("getTables: No database selected"); if (!this.#selectedDatabase && !database) throw new Error("getTables: No database selected");
const tables = (await this.queryRaw(`USE ${database||this.#selectedDatabase}; SHOW TABLES;`))[1]; const tables = (await this.queryRaw(`USE ${database||this.#selectedDatabase}; SHOW TABLES;`))[1];
@ -166,7 +206,7 @@ class Instance {
* @param {...String} [columns] - Name of columns to select. Leave empty to select all * @param {...String} [columns] - Name of columns to select. Leave empty to select all
* @returns {Select} * @returns {Select}
*/ */
select = (from, ...columns) => { select(from, ...columns){
return new Select(this, this.#selectedDatabase, from, columns); return new Select(this, this.#selectedDatabase, from, columns);
} }
@ -176,7 +216,7 @@ class Instance {
* @param {String} into - Name of the table to insert into * @param {String} into - Name of the table to insert into
* @returns {Insert} * @returns {Insert}
*/ */
insert = (into) => { insert(into){
return new Insert(this, this.#selectedDatabase, into); return new Insert(this, this.#selectedDatabase, into);
} }
@ -185,7 +225,7 @@ class Instance {
* @param {String} from - Name of the table to delete from * @param {String} from - Name of the table to delete from
* @returns {Delete} * @returns {Delete}
*/ */
delete = (from) => { delete(from){
return new Delete(this, this.#selectedDatabase, from); return new Delete(this, this.#selectedDatabase, from);
} }
@ -194,7 +234,7 @@ class Instance {
* @param {String} table - Name of the table to update data in * @param {String} table - Name of the table to update data in
* @returns {Update} * @returns {Update}
*/ */
update = (table) => { update(table){
return new Update(this, this.#selectedDatabase, table); return new Update(this, this.#selectedDatabase, table);
} }
@ -202,17 +242,18 @@ class Instance {
* Drops a whole database * Drops a whole database
* - Requires admin privileges * - Requires admin privileges
* @param {String} database - Name of the database to drop * @param {String} database - Name of the database to drop
* @returns * @returns {Any}
*/ */
dropDatabase = async (database) => { async dropDatabase (database){
return await this.queryRaw(`DROP DATABASE ${database};`); return await this.queryRaw(`DROP DATABASE ${database};`);
} }
/** /**
* Drops a whole table * Drops a whole table
* @param {String} table - Name of the table to drop * @param {String} table - Name of the table to drop
* @returns {Any}
*/ */
dropTable = async (table) => { async dropTable(table){
if (!this.#selectedDatabase) throw new Error(`Can't drop table '${table}': Database not set`); if (!this.#selectedDatabase) throw new Error(`Can't drop table '${table}': Database not set`);
return await this.queryRaw(`DROP TABLE ${this.#selectedDatabase}.${table}`); return await this.queryRaw(`DROP TABLE ${this.#selectedDatabase}.${table}`);
} }
@ -221,9 +262,9 @@ class Instance {
* Creates a new database * Creates a new database
* - Requires admin privileges * - Requires admin privileges
* @param {String} name - Name of the database to create * @param {String} name - Name of the database to create
* @returns * @returns {Any}
*/ */
createDatabase = async (name) => { async createDatabase(name){
return await this.queryRaw(`CREATE DATABASE ${name};`); return await this.queryRaw(`CREATE DATABASE ${name};`);
} }
@ -232,16 +273,17 @@ class Instance {
* @param {String} name - Name of the table to create * @param {String} name - Name of the table to create
* @returns {CreateTable} * @returns {CreateTable}
*/ */
createTable = (name) => { createTable(name){
return new CreateTable(this, this.#selectedDatabase, name); return new CreateTable(this, this.#selectedDatabase, name);
} }
/** /**
* Alters a table and updates to the new given structure. * Alters a table and updates to the new given structure.
* *
* @param {*} name * @param {String} name
* @returns {AlterTable}
*/ */
alterTable = (name) => { alterTable(name){
return new AlterTable(this, this.#selectedDatabase, name); return new AlterTable(this, this.#selectedDatabase, name);
} }
@ -249,7 +291,7 @@ class Instance {
* Prepares to create a new table-structure * Prepares to create a new table-structure
* @returns {Structure} * @returns {Structure}
*/ */
createStructure = () => { createStructure(){
return new Structure(); return new Structure();
} }
@ -259,52 +301,73 @@ class Instance {
* @param {String} [database] - Name of the underlying database * @param {String} [database] - Name of the underlying database
* @returns {Structure} * @returns {Structure}
*/ */
getStructure = async (table, database) => { async getStructure(table, database){
if (!this.#selectedDatabase && !database) throw new Error(`Can't get structure of table ${table}: Database not selected`); if (!this.#selectedDatabase && !database) throw new Error(`Can't get structure of table ${table}: Database not selected`);
return new Structure(await this.queryRaw(`DESCRIBE ${database||this.#selectedDatabase}.${table};`)); return new Structure(await this.queryRaw(`DESCRIBE ${database||this.#selectedDatabase}.${table};`));
} }
checkStructure = async (table, desiredStructure, database) => { /**
* Checks the structure of a table
* @param {String} table - Name of the table
* @param {Structure} desiredStructure - Structure to check against
* @param {String} [database] - Name of the database. If omitted, uses default database
* @returns {CheckResult}
*/
async checkStructure(table, desiredStructure, database){
if (!this.#selectedDatabase && !database) throw new Error(`Can't get structure of table ${table}: Database not selected`); if (!this.#selectedDatabase && !database) throw new Error(`Can't get structure of table ${table}: Database not selected`);
const dbStruc = (await this.getStructure(table, database||this.#selectedDatabase)).get(); const dbStruc = (await this.getStructure(table, database||this.#selectedDatabase)).get(); // Get current structure -> Array<Objects>
const result = { const result = {
errors: [], errors: [],
passed: [] passed: []
} }
for (let col of dbStruc){ for (let col of desiredStructure){
const desiredCol = desiredStructure.find((desCol) => desCol.Field === col.Field); const dbCol = dbStruc.find((dbCol) => col.Field === dbCol.Field); // Check if the current table has the field
if (!desiredCol) { if (!dbCol) {
result.errors.push(`${col.Field}: Missing completely`); result.errors.push(`${col.Field}: Missing completely`);
continue; continue;
} }
let breakOut = false; let breakOut = false;
for (let key in col){ for (let key in col){
if (col[key] !== desiredCol[key]){ if (col[key] !== dbCol[key]){ // If the current key has a different value
result.errors.push(`${col.Field}.${key}: Required ${desiredCol[key]}, got ${col[key]}`); result.errors.push(`${dbCol.Field}.${key}: Required ${col[key]}, got ${dbCol[key]}`);
breakOut=true; breakOut=true;
} }
} }
if (!breakOut){ if (!breakOut){ // If no errors happened
result.passed.push(`${col.Field}`); result.passed.push(`${col.Field}`);
} }
} }
return result; return result;
} }
/** /**
* Returns total amount of rows of a table * Returns total amount of rows of a table
* @param {String} table - Table name * @param {String} table - Table name
* @returns * @returns {Any}
*/ */
total = async (table) => { async total(table){
return await new Select(this, this.#selectedDatabase, table).count(true).execute(); return await new Select(this, this.#selectedDatabase, table).count(true).execute();
} }
isConnected = () => { /**
* Returns if connection is established
* @returns {Boolean}
*/
isConnected(){
if (this.#connection) return true; if (this.#connection) return true;
return true; return true;
} }
} }
module.exports = {awSQL: new awSQL(), Structure}; /**
* @typedef {Object} CheckResult
* @property {Array<String>} errors - String representation of errors found
* @property {Array<String>} passed - String representation of passed checks
*/
const awSQLInstance = new awSQL();
module.exports = {awSQLInstance, Structure};
/**
* @exports awSQLInstance
*/

17
jsdoc.json Normal file
View File

@ -0,0 +1,17 @@
{
"source": {
"include": ["./index.js", "./lib"],
"includePattern": ".+\\.js$"
},
"opts": {
"destination": "./docs",
"recurse": true,
"template": "node_modules/clean-jsdoc-theme",
"readme": "./README.md"
},
"templates": {
"default": {
"outputSourceFiles": false
}
}
}

View File

@ -1,3 +1,6 @@
/**
* Prepares a new Deletion
*/
class Delete { class Delete {
#instance; #instance;
#database; #database;
@ -19,7 +22,7 @@ class Delete {
* @param {String} database - Name of the database * @param {String} database - Name of the database
* @returns {this} * @returns {this}
*/ */
selectDatabase = (database) => { selectDatabase(database){
this.#database = database; this.#database = database;
return this; return this;
} }
@ -28,10 +31,10 @@ class Delete {
* Adds a where-clause to the query * Adds a where-clause to the query
* - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection * - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection
* @param {String} string - The where-clause as a string with ? representing each values. * @param {String} string - The where-clause as a string with ? representing each values.
* @param {Array} values - Array containing values replacing the ? in the string (from left to right) * @param {Array<Any>} values - Array containing values replacing the ? in the string (from left to right)
* @returns {this} * @returns {this}
*/ */
where = (string, values=[]) => { where(string, values=[]){
this.#where = string; this.#where = string;
this.#whereValues = values; this.#whereValues = values;
return this; return this;
@ -40,12 +43,16 @@ class Delete {
/** /**
* Enables deletion of all rows * Enables deletion of all rows
*/ */
force = () => { force(){
this.#force = true; this.#force = true;
return this; return this;
} }
execute = async () => { /**
* Executes the prepared querry
* @returns {Any}
*/
async execute(){
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`); if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
if (!this.#database) throw new Error(`Can't execute query: Database not selected`); if (!this.#database) throw new Error(`Can't execute query: Database not selected`);

View File

@ -1,3 +1,6 @@
/**
* Prepares a new insertion
*/
class Insert { class Insert {
#instance; #instance;
#database; #database;
@ -16,22 +19,26 @@ class Insert {
* @param {String} database - Name of the database * @param {String} database - Name of the database
* @returns {this} * @returns {this}
*/ */
selectDatabase = (database) => { selectDatabase(database){
this.#database = database; this.#database = database;
return this; return this;
} }
/** /**
* The data (rows) to insert * The data (rows) to insert
* @param {Array} objects - Array containing objects to insert, where the key represents the column-name. All objects must have the same structure! * @param {Array<Object>} objects - Array containing objects to insert, where the key represents the column-name. All objects must have the same structure!
* @returns * @returns {this}
*/ */
data = (objects) => { data(objects){
this.#data = objects; this.#data = objects;
return this; return this;
} }
execute = async () => { /**
* Executes the prepared querry
* @returns {Any}
*/
async execute(){
if (!this.#data) throw new Error("Insert: tried to insert without data"); if (!this.#data) throw new Error("Insert: tried to insert without data");
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`); if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
if (!this.#database) throw new Error(`Can't execute query: Database not selected`); if (!this.#database) throw new Error(`Can't execute query: Database not selected`);

View File

@ -1,4 +1,7 @@
/**
* Prepares a new Selection
*/
class Select { class Select {
#instance; #instance;
#database; #database;
@ -16,7 +19,7 @@ class Select {
#group; #group;
#aggregator; #aggregator;
#join; #joins = [];
#having; #having;
#havingValues; #havingValues;
#limit; #limit;
@ -33,7 +36,7 @@ class Select {
* @param {String} database - Name of the database * @param {String} database - Name of the database
* @returns {this} * @returns {this}
*/ */
selectDatabase = (database) => { selectDatabase(database){
this.#database = database; this.#database = database;
return this; return this;
} }
@ -44,7 +47,7 @@ class Select {
* - With 'distinct' only unique values are returned * - With 'distinct' only unique values are returned
* @returns {this} * @returns {this}
*/ */
distinct = () => { distinct(){
this.#distinct = true; this.#distinct = true;
return this; return this;
} }
@ -54,10 +57,10 @@ class Select {
* - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection * - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection
* - If you are using joins, specify the table and column together: table.column * - If you are using joins, specify the table and column together: table.column
* @param {String} string - The where-clause as a string with ? representing each values. * @param {String} string - The where-clause as a string with ? representing each values.
* @param {Array} values - Array containing values replacing the ? in the string (from left to right) * @param {Array<Any>} values - Array containing values replacing the ? in the string (from left to right)
* @returns {this} * @returns {this}
*/ */
where = (string, values=[]) => { where(string, values=[]){
this.#where = string; this.#where = string;
this.#whereValues = values; this.#whereValues = values;
return this; return this;
@ -68,10 +71,10 @@ class Select {
* - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection * - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection
* - If you are using joins, specify the table and column together: table.column * - If you are using joins, specify the table and column together: table.column
* @param {String} string - The having-clause with possible aggregation and ? representing each values * @param {String} string - The having-clause with possible aggregation and ? representing each values
* @param {Array} values - Array containing values replacing the ? in the string (from left to right) * @param {Array<Any>} values - Array containing values replacing the ? in the string (from left to right)
* @returns {this} * @returns {this}
*/ */
having = (string, values = []) => { having(string, values = []){
this.#having = string; this.#having = string;
this.#havingValues = values; this.#havingValues = values;
return this; return this;
@ -83,9 +86,9 @@ class Select {
* @param {String} column - Column to order by * @param {String} column - Column to order by
* @param {Boolean} desc - Sorty descending * @param {Boolean} desc - Sorty descending
* @param {"MIN"|"MAX"|"COUNT"|"SUM"|"AVG"} aggregation - The aggregation type to use * @param {"MIN"|"MAX"|"COUNT"|"SUM"|"AVG"} aggregation - The aggregation type to use
* @returns * @returns {this}
*/ */
order = (column, desc=false, aggregation) => { order(column, desc=false, aggregation){
if (["MIN", "MAX", "COUNT", "SUM", "AVG"].includes(aggregation)){ if (["MIN", "MAX", "COUNT", "SUM", "AVG"].includes(aggregation)){
switch(aggregation){ switch(aggregation){
case "MIN": case "MIN":
@ -116,8 +119,9 @@ class Select {
/** /**
* Counts number of entries of the first selected column * Counts number of entries of the first selected column
* @param {Boolean} doParse - Return only an integer, not the full query result * @param {Boolean} doParse - Return only an integer, not the full query result
* @returns {this}
*/ */
count = (doParse=false) => { count(doParse=false){
this.#aggregator = "COUNT"; this.#aggregator = "COUNT";
this.#aggregatorParse = doParse; this.#aggregatorParse = doParse;
return this; return this;
@ -126,9 +130,9 @@ class Select {
/** /**
* Sums numerical rows of the first selected column * Sums numerical rows of the first selected column
* @param {Boolean} doParse - Return only an integer, not the full query result * @param {Boolean} doParse - Return only an integer, not the full query result
* @returns * @returns {this}
*/ */
sum = (doParse=false) => { sum(doParse=false){
this.#aggregator ="SUM"; this.#aggregator ="SUM";
this.#aggregatorParse = doParse; this.#aggregatorParse = doParse;
return this; return this;
@ -137,9 +141,9 @@ class Select {
/** /**
* Averages numerical rows of the first selected column * Averages numerical rows of the first selected column
* @param {Boolean} doParse - Return only an integer, not the full query result * @param {Boolean} doParse - Return only an integer, not the full query result
* @returns * @returns {this}
*/ */
avg = (doParse=false) => { avg(doParse=false){
this.#aggregator = "AVG"; this.#aggregator = "AVG";
this.#aggregatorParse = doParse; this.#aggregatorParse = doParse;
return this; return this;
@ -148,37 +152,39 @@ class Select {
/** /**
* Groups rows that have the same values into summary rows * Groups rows that have the same values into summary rows
* @param {...String} columns - The columns to group by * @param {...String} columns - The columns to group by
* @returns {this}
*/ */
group = (...columns) => { group(...columns){
this.#group = columns; this.#group = columns;
return this; return this;
} }
/** /**
* * Adds a new join to the querry
* @param {"LEFT"|"INNER"|"RIGHT"|"FULL OUTER"} type * @param {"LEFT"|"INNER"|"RIGHT"|"FULL OUTER"} type - Join-type
* @param {*} table * @param {String} table - Table to join on
* @param {*} onOriginalColumn * @param {String} onOriginalColumn - Column name on the original table to check against
* @param {*} onJoinedColumn * @param {String} onJoinedColumn - Column name of the join table to check against
* @param {...any} columns * @param {...any} columns - The columns to join. OG-Columns must be set!
* @returns {this}
*/ */
join = (type, table, onOriginalColumn, onJoinedColumn, ...columns) => { join(type, table, onOriginalColumn, onJoinedColumn, ...columns){
this.#join = { this.#joins.push({
type, type,
on: `%%FROM%%.${onOriginalColumn}=${table}.${onJoinedColumn}`, on: `%%FROM%%.${onOriginalColumn}=${table}.${onJoinedColumn}`,
columns, columns,
table table
} })
return this; return this;
} }
/** /**
* Limits the query and specifies an offset * Limits the query and specifies an offset
* @param {Number} number - Limits the query by specified rows * @param {Number} number - Limits the query by specified rows
* @param {*} offset - Offset to start at * @param {Number} offset - Offset to start at
* @returns * @returns {this}
*/ */
limit = (number, offset) => { limit(number, offset){
this.#limit = { this.#limit = {
number, number,
offset offset
@ -190,9 +196,9 @@ class Select {
* Paginates the query * Paginates the query
* @param {Number} page - The page to get (Minimum 1) * @param {Number} page - The page to get (Minimum 1)
* @param {Number} itemsPerPage - How many items a page should have * @param {Number} itemsPerPage - How many items a page should have
* @returns * @returns {this}
*/ */
pagination = (page, itemsPerPage) => { pagination(page, itemsPerPage){
if (page<1) page=1; if (page<1) page=1;
this.#limit = { this.#limit = {
number: itemsPerPage, number: itemsPerPage,
@ -202,17 +208,20 @@ class Select {
} }
/** /**
* Executes the prepared query * Executes the prepared querry
* @returns * @returns {Any}
*/ */
execute = async () => { async execute(){
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`); if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
if (!this.#database) throw new Error(`Can't execute query: Database not selected`); if (!this.#database) throw new Error(`Can't execute query: Database not selected`);
const values = []; const values = [];
let columnString; let columnString;
if (this.#join && this.#columns.length>0){ if (this.#joins.length>0 && this.#columns.length>0){
columnString = `${this.#columns.toString()},${this.#join.columns.toString()}` columnString = `${this.#columns.toString()}`;
for (let join of this.#joins){
columnString+=`,${join.columns.toString()}`;
}
}else{ }else{
columnString = this.#columns.length>0?this.#columns.toString():"*" columnString = this.#columns.length>0?this.#columns.toString():"*"
} }
@ -228,7 +237,10 @@ class Select {
const groupString = this.#group?` GROUP BY ${this.#group.toString()}`:""; const groupString = this.#group?` GROUP BY ${this.#group.toString()}`:"";
const joinString = this.#join?` ${this.#join.type} JOIN ${this.#database}.${this.#join.table} ON ${this.#join.on.replace("%%FROM%%", this.#from)}`:""; let joinString = "";
for (let join of this.#joins){
joinString+=` ${join.type} JOIN ${this.#database}.${join.table} ON ${join.on.replace("%%FROM%%", this.#from)}`;
}
const limitString = this.#limit?` LIMIT ${this.#limit.number}${this.#limit.offset?` OFFSET ${this.#limit.offset}`:""}`:""; const limitString = this.#limit?` LIMIT ${this.#limit.number}${this.#limit.offset?` OFFSET ${this.#limit.offset}`:""}`:"";

View File

@ -1,4 +1,8 @@
/**
* Prepares a new alteration of a table
*/
class AlterTable { class AlterTable {
#instance; #instance;
#database; #database;
@ -15,7 +19,7 @@ class AlterTable {
* @param {String} database - Name of the database * @param {String} database - Name of the database
* @returns {this} * @returns {this}
*/ */
selectDatabase = (database) => { selectDatabase(database){
this.#database = database; this.#database = database;
return this; return this;
} }
@ -26,14 +30,18 @@ class AlterTable {
* - Adds columns that are missing in the current table * - Adds columns that are missing in the current table
* - Modifies all other columns where at least one datatype is not matching * - Modifies all other columns where at least one datatype is not matching
* @param {Structure} struc - New structure for the table * @param {Structure} struc - New structure for the table
* @returns * @returns {this}
*/ */
structure = (struc) => { structure(struc){
this.#structure = struc; this.#structure = struc;
return this; return this;
} }
execute = async () => { /**
* Executes the prepared querry
* @returns {Any}
*/
async execute(){
if (!this.#database) throw new Error(`Can't alter table ${this.#name}: Database not selected`); if (!this.#database) throw new Error(`Can't alter table ${this.#name}: Database not selected`);
if (!this.#name) throw new Error(`Can't alter table on ${this.#database}: No name given`); if (!this.#name) throw new Error(`Can't alter table on ${this.#database}: No name given`);
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`); if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
@ -70,12 +78,13 @@ class AlterTable {
} }
for (let key in col){ for (let key in col){
if (oldStrucCol[key] !== col[key]){ if (oldStrucCol[key] !== col[key]){
console.log(oldStrucCol[key], col[key]);
// UPDATE COLUMN // UPDATE COLUMN
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} MODIFY COLUMN \`${col.Field}\` ${col.Type}${col.Extra?` ${col.Extra}`:""}${col.Null==="YES"?" NULL":" NOT NULL"}${col.Default?` DEFAULT '${col.Default}'`:""}`); await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} MODIFY COLUMN \`${col.Field}\` ${col.Type}${col.Extra?` ${col.Extra}`:""}${col.Null==="YES"?" NULL":" NOT NULL"}${col.Default?` DEFAULT '${col.Default}'`:""}`);
if (oldStrucCol.Key){ if (oldStrucCol.Key){
switch(oldStrucCol.Key){ switch(oldStrucCol.Key){
case "PRI": case "PRI":
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} DROP PRIMARY KEY (${col.Field});`); await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} DROP PRIMARY KEY;`);
break; break;
case "MUL": case "MUL":
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} DROP INDEX ${col.Field};`); await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} DROP INDEX ${col.Field};`);
@ -106,6 +115,9 @@ class AlterTable {
} }
} }
/**
* Prepares creation of a new table
*/
class CreateTable { class CreateTable {
#instance; #instance;
#database; #database;
@ -122,7 +134,7 @@ class CreateTable {
* @param {String} database - Name of the database * @param {String} database - Name of the database
* @returns {this} * @returns {this}
*/ */
selectDatabase = (database) => { selectDatabase(database){
this.#database = database; this.#database = database;
return this; return this;
} }
@ -130,26 +142,28 @@ class CreateTable {
/** /**
* Sets a new name * Sets a new name
* @param {String} name * @param {String} name
* @returns {this}
*/ */
name = (name) => { name(name){
this.#name = name; this.#name = name;
return this; return this;
} }
/** /**
* Defines the structure for the table * Defines the structure for the table
* @param {Structure} struc - Instance of Structure * @param {Structure} struc - Instance of Structure
* @returns {this}
*/ */
structure = (struc) => { structure(struc){
this.#structure = struc; this.#structure = struc;
return this; return this;
} }
/** /**
* Executes the prepared query * Executes the prepared querry
* @returns * @returns {Any}
*/ */
execute = async () => { async execute(){
if (!this.#database) throw new Error(`Can't create table ${this.#name}: Database not selected`); if (!this.#database) throw new Error(`Can't create table ${this.#name}: Database not selected`);
if (!this.#name) throw new Error(`Can't create table on ${this.#database}: No name given`); if (!this.#name) throw new Error(`Can't create table on ${this.#database}: No name given`);
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`); if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
@ -177,6 +191,9 @@ class CreateTable {
* @property {Boolean} [unsigned] - Only on numerical: Whether this numerical field should be unsigned * @property {Boolean} [unsigned] - Only on numerical: Whether this numerical field should be unsigned
*/ */
/**
* Creates a new table-structure
*/
class Structure { class Structure {
#columns = []; #columns = [];
constructor(tableDescription) { constructor(tableDescription) {
@ -187,8 +204,9 @@ class Structure {
/** /**
* Drops (Removes) a column name * Drops (Removes) a column name
* @param {String} name - The name of the column * @param {String} name - The name of the column
* @returns {this}
*/ */
drop = (name) => { drop(name){
let index; let index;
for (let i = 0; i < this.#columns.length; i++){ for (let i = 0; i < this.#columns.length; i++){
if (this.#columns[i].Field === name) { if (this.#columns[i].Field === name) {
@ -200,6 +218,10 @@ class Structure {
return this; return this;
} }
/**
* Returns the columns
* @returns {Array<ColumnStructure>}
*/
get(){ get(){
return this.#columns; return this.#columns;
} }
@ -207,10 +229,11 @@ class Structure {
/** /**
* Adds a new column of data type 'char' to this structure * Adds a new column of data type 'char' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size - Length of characters. Min 0, Max 255. Default 1 * @param {Number} [size] - Length of characters. Min 0, Max 255. Default 1
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns {this}
*/ */
char = (name, size=1, options) => { char(name, size=1, options){
if (size < 0 || size > 255){ if (size < 0 || size > 255){
throw new Error(`Column datatype 'char' size must be a number between 0 and 255. Received: ${size}`); throw new Error(`Column datatype 'char' size must be a number between 0 and 255. Received: ${size}`);
} }
@ -221,10 +244,11 @@ class Structure {
/** /**
* Adds a new column of data type 'varchar' to this structure * Adds a new column of data type 'varchar' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size - Length of characters. Min 0, Max 255. Default 1 * @param {Number} [size] - Length of characters. Min 0, Max 255. Default 1
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns {this}
*/ */
varchar = (name, size=8, options) => { varchar(name, size=8, options){
if (size < 0 || size > 255){ if (size < 0 || size > 255){
throw new Error(`Column datatype 'varchar' size must be a number between 0 and 65535. Received: ${size}`); throw new Error(`Column datatype 'varchar' size must be a number between 0 and 65535. Received: ${size}`);
} }
@ -235,11 +259,11 @@ class Structure {
/** /**
* Adds a new column of data type 'binary' to this structure * Adds a new column of data type 'binary' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size - Length of data. Min 1 * @param {Number} [size] - Length of data. Min 1
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
binary = (name, size=1, options) => { binary(name, size=1, options){
if (size < 1){ if (size < 1){
throw new Error(`Column datatype 'binary' size must be a number above 0. Received: ${size}`); throw new Error(`Column datatype 'binary' size must be a number above 0. Received: ${size}`);
} }
@ -250,11 +274,11 @@ class Structure {
/** /**
* Adds a new column of data type 'varbinary' to this structure * Adds a new column of data type 'varbinary' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size - Length of data. Min 1 * @param {Number} [size] - Length of data. Min 1
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
varbinary = (name, size=1, options) => { varbinary(name, size=1, options){
if (size < 1){ if (size < 1){
throw new Error(`Column datatype 'varbinary' size must be a number above 0. Received: ${size}`); throw new Error(`Column datatype 'varbinary' size must be a number above 0. Received: ${size}`);
} }
@ -266,10 +290,10 @@ class Structure {
/** /**
* Adds a new column of data type 'tinyblob' to this structure * Adds a new column of data type 'tinyblob' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
tinyblob = (name, options) => { tinyblob(name, options){
this.#columns.push(parseColumnData(name, `tinyblob`, options)); this.#columns.push(parseColumnData(name, `tinyblob`, options));
return this; return this;
} }
@ -277,10 +301,10 @@ class Structure {
/** /**
* Adds a new column of data type 'tinytext' to this structure * Adds a new column of data type 'tinytext' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
tinytext = (name, options) => { tinytext(name, options){
this.#columns.push(parseColumnData(name, `tinytext`, options)); this.#columns.push(parseColumnData(name, `tinytext`, options));
return this; return this;
} }
@ -288,10 +312,10 @@ class Structure {
/** /**
* Adds a new column of data type 'text' to this structure * Adds a new column of data type 'text' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
text = (name, options) => { text(name, options){
this.#columns.push(parseColumnData(name, `text`, options)); this.#columns.push(parseColumnData(name, `text`, options));
return this; return this;
} }
@ -299,11 +323,11 @@ class Structure {
/** /**
* Adds a new column of data type 'blob' to this structure * Adds a new column of data type 'blob' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size - Size in bytes. Min 1, Max 65535. Defaults to 65535 * @param {Number} [size] - Size in bytes. Min 1, Max 65535. Defaults to 65535
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
blob = (name, size=65535, options) => { blob(name, size=65535, options){
if (size < 1 || size > 65535) throw new Error(`Column datatype 'blob' size must be a number between 1 and 65535. Received: ${size}`); if (size < 1 || size > 65535) throw new Error(`Column datatype 'blob' size must be a number between 1 and 65535. Received: ${size}`);
this.#columns.push(parseColumnData(name, `blob(${size})`, options)); this.#columns.push(parseColumnData(name, `blob(${size})`, options));
return this; return this;
@ -312,10 +336,10 @@ class Structure {
/** /**
* Adds a new column of data type 'mediumtext' to this structure * Adds a new column of data type 'mediumtext' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
mediumtext = (name, options) => { mediumtext(name, options){
this.#columns.push(parseColumnData(name, `mediumtext`, options)); this.#columns.push(parseColumnData(name, `mediumtext`, options));
return this; return this;
} }
@ -323,10 +347,10 @@ class Structure {
/** /**
* Adds a new column of data type 'longtext' to this structure * Adds a new column of data type 'longtext' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
longtext = (name, options) => { longtext(name, options){
this.#columns.push(parseColumnData(name, `longtext`, options)); this.#columns.push(parseColumnData(name, `longtext`, options));
return this; return this;
} }
@ -334,10 +358,10 @@ class Structure {
/** /**
* Adds a new column of data type 'longblob' to this structure * Adds a new column of data type 'longblob' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
longblob = (name, options) => { longblob(name, options){
this.#columns.push(parseColumnData(name, `longblob`, options)); this.#columns.push(parseColumnData(name, `longblob`, options));
return this; return this;
} }
@ -345,39 +369,39 @@ class Structure {
/** /**
* Adds a new column of data type 'enum' to this structure * Adds a new column of data type 'enum' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Array} vals - Array of possible values * @param {Array<String>} vals - Array of possible values
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
enum = (name, vals=[], options) => { enum(name, vals=[], options){
if (!Array.isArray(vals)) throw new Error(`Column datatype 'enum': 'vals' must be of type Array`); if (!Array.isArray(vals)) throw new Error(`Column datatype 'enum': 'vals' must be of type Array`);
if (vals.length<=0) throw new Error(`Column datatype 'enum' must contain a list of possible values. Received undefined`); if (vals.length<=0) throw new Error(`Column datatype 'enum' must contain a list of possible values. Received undefined`);
this.#columns.push(parseColumnData(name, `enum(${vals.map(val=>`"${val}"`)})`, options)); this.#columns.push(parseColumnData(name, `enum(${vals.map(val=>`'${val}'`)})`, options));
return this; return this;
} }
/** /**
* Adds a new column of data type 'enum' to this structure * Adds a new column of data type 'enum' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Array} vals - Array of possible values * @param {Array<String>} vals - Array of possible values
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
set = (name, vals=[], options) => { set(name, vals=[], options){
if (!Array.isArray(vals)) throw new Error(`Column datatype 'set': 'vals' must be of type Array`); if (!Array.isArray(vals)) throw new Error(`Column datatype 'set': 'vals' must be of type Array`);
if (vals.length<=0) throw new Error(`Column datatype 'set' must contain a list of possible values. Received undefined`); if (vals.length<=0) throw new Error(`Column datatype 'set' must contain a list of possible values. Received undefined`);
this.#columns.push(parseColumnData(name, `set(${vals.map(val=>`"${val}"`)})`, options)); this.#columns.push(parseColumnData(name, `set(${vals.map(val=>`'${val}'`)})`, options));
return this; return this;
} }
/** /**
* Adds a new column of data type 'bit' to this structure * Adds a new column of data type 'bit' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size - Min 1, Max 64. Default to 1 * @param {Number} [size] - Min 1, Max 64. Default to 1
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
bit = (name, size=1, options) => { bit(name, size=1, options){
if (size < 1 || size > 64) throw new Error(`Column datatype 'bit' size must be a number between 1 and 64. Received: ${size}`); if (size < 1 || size > 64) throw new Error(`Column datatype 'bit' size must be a number between 1 and 64. Received: ${size}`);
this.#columns.push(parseColumnData(name, `bit(${size})`, options)); this.#columns.push(parseColumnData(name, `bit(${size})`, options));
return this; return this;
@ -386,11 +410,11 @@ class Structure {
/** /**
* Adds a new column of data type 'tinyint' to this structure * Adds a new column of data type 'tinyint' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size - Min 1, Max 255. Defaults to 255 * @param {Number} [size] - Min 1, Max 255. Defaults to 255
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
tinyint = (name, size=255, options) => { tinyint(name, size=255, options){
if (size < 1 || size > 255) throw new Error(`Column datatype 'tinyint' size must be a number between 1 and 255. Received: ${size}`); if (size < 1 || size > 255) throw new Error(`Column datatype 'tinyint' size must be a number between 1 and 255. Received: ${size}`);
this.#columns.push(parseColumnData(name, `tinyint(${size})`, options)); this.#columns.push(parseColumnData(name, `tinyint(${size})`, options));
@ -400,10 +424,10 @@ class Structure {
/** /**
* Adds a new column of data type 'bool' to this structure * Adds a new column of data type 'bool' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
bool = (name, options) => { bool(name, options){
this.#columns.push(parseColumnData(name, `bool`, options)); this.#columns.push(parseColumnData(name, `bool`, options));
return this; return this;
} }
@ -411,11 +435,11 @@ class Structure {
/** /**
* Adds a new column of data type 'smallint' to this structure * Adds a new column of data type 'smallint' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size Min 1, Max 255. Defaults to 255 * @param {Number} [size] Min 1, Max 255. Defaults to 255
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
smallint = (name, size=255, options) => { smallint(name, size=255, options){
if (size < 1 || size > 255) throw new Error(`Column datatype 'smallint' size must be a number between 1 and 255. Received: ${size}`); if (size < 1 || size > 255) throw new Error(`Column datatype 'smallint' size must be a number between 1 and 255. Received: ${size}`);
this.#columns.push(parseColumnData(name, `smallint(${size})`, options)); this.#columns.push(parseColumnData(name, `smallint(${size})`, options));
return this; return this;
@ -424,11 +448,11 @@ class Structure {
/** /**
* Adds a new column of data type 'mediumint' to this structure * Adds a new column of data type 'mediumint' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size Min 1, Max 255. Defaults to 255 * @param {Number} [size] Min 1, Max 255. Defaults to 255
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
mediumint = (name, size=255, options) => { mediumint(name, size=255, options){
if (size < 1 || size > 255) throw new Error(`Column datatype 'mediumint' size must be a number between 1 and 255. Received: ${size}`); if (size < 1 || size > 255) throw new Error(`Column datatype 'mediumint' size must be a number between 1 and 255. Received: ${size}`);
this.#columns.push(parseColumnData(name, `mediumint(${size})`, options)); this.#columns.push(parseColumnData(name, `mediumint(${size})`, options));
return this; return this;
@ -437,11 +461,11 @@ class Structure {
/** /**
* Adds a new column of data type 'int' to this structure * Adds a new column of data type 'int' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size Min 1, Max 255. Defaults to 255 * @param {Number} [size] Min 1, Max 255. Defaults to 255
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
int = (name, size=255, options) => { int(name, size=255, options){
if (size < 1 || size > 255) throw new Error(`Column datatype 'int' size must be a number between 1 and 255. Received: ${size}`); if (size < 1 || size > 255) throw new Error(`Column datatype 'int' size must be a number between 1 and 255. Received: ${size}`);
this.#columns.push(parseColumnData(name, `int(${size})`, options)); this.#columns.push(parseColumnData(name, `int(${size})`, options));
return this; return this;
@ -450,11 +474,11 @@ class Structure {
/** /**
* Adds a new column of data type 'bigint' to this structure * Adds a new column of data type 'bigint' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size Min 1, Max 255. Defaults to 255 * @param {Number} [size] Min 1, Max 255. Defaults to 255
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
bigint = (name, size=255, options) => { bigint(name, size=255, options){
if (size < 1 || size > 255) throw new Error(`Column datatype 'bigint' size must be a number between 1 and 255. Received: ${size}`); if (size < 1 || size > 255) throw new Error(`Column datatype 'bigint' size must be a number between 1 and 255. Received: ${size}`);
this.#columns.push(parseColumnData(name, `bigint(${size})`, options)); this.#columns.push(parseColumnData(name, `bigint(${size})`, options));
return this; return this;
@ -463,11 +487,11 @@ class Structure {
/** /**
* Adds a new column of data type 'float' to this structure * Adds a new column of data type 'float' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} p - Precision. Min 1, Max 53. Defaults to 25 * @param {Number} [p] - Precision. Min 1, Max 53. Defaults to 25
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
float = (name, p=25, options) => { float(name, p=25, options){
if (p < 1 || p > 53) throw new Error(`Column datatype 'float' size must be a number between 1 and 53. Received: ${p}`); if (p < 1 || p > 53) throw new Error(`Column datatype 'float' size must be a number between 1 and 53. Received: ${p}`);
this.#columns.push(parseColumnData(name, `float(${p})`, options)); this.#columns.push(parseColumnData(name, `float(${p})`, options));
@ -477,12 +501,12 @@ class Structure {
/** /**
* Adds a new column of data type 'double' to this structure * Adds a new column of data type 'double' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size - Min 1. Defaults to 16 * @param {Number} [size] - Min 1. Defaults to 16
* @param {Number} d - Double precision. Min 1. Defaults to 8 * @param {Number} [d] - Double precision. Min 1. Defaults to 8
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
double = (name, size=16, d=8, options) => { double(name, size=16, d=8, options){
if (size < 1) throw new Error(`Column datatype 'double' size must be greater than 0. Received: ${p}`); if (size < 1) throw new Error(`Column datatype 'double' size must be greater than 0. Received: ${p}`);
if (d < 1) throw new Error(`Column datatype 'double' d must be greater than 0. Received: ${p}`); if (d < 1) throw new Error(`Column datatype 'double' d must be greater than 0. Received: ${p}`);
this.#columns.push(parseColumnData(name, `double(${size},${d})`, options)); this.#columns.push(parseColumnData(name, `double(${size},${d})`, options));
@ -492,32 +516,97 @@ class Structure {
/** /**
* Adds a new column of data type 'decimal' to this structure * Adds a new column of data type 'decimal' to this structure
* @param {String} name - Name of the column * @param {String} name - Name of the column
* @param {Number} size - Min 1, Max 65. Defaults to 10 * @param {Number} [size] - Min 1, Max 65. Defaults to 10
* @param {Number} d - Double precision. Min 0. Defaults to 0. * @param {Number} [d] - Double precision. Min 0. Defaults to 0.
* @param {ConstraintOptions} options - Extra constraint options * @param {ConstraintOptions} [options] - Extra constraint options
* @returns * @returns {this}
*/ */
decimal = (name, size=10, d=0, options) => { decimal(name, size=10, d=0, options){
if (size < 1 || size > 65) throw new Error(`Column datatype 'decimal' size must be a number between 1 and 65. Received: ${size}`); if (size < 1 || size > 65) throw new Error(`Column datatype 'decimal' size must be a number between 1 and 65. Received: ${size}`);
if (d < 0) throw new Error(`Column datatype 'decimal' d must be positive. Received: ${d}`); if (d < 0) throw new Error(`Column datatype 'decimal' d must be positive. Received: ${d}`);
this.#columns.push(parseColumnData(name, `decimal(${size},${d})`, options)); this.#columns.push(parseColumnData(name, `decimal(${size},${d})`, options));
return this; return this;
} }
/**
* Adds a new column of data type 'date' to this structure
* @param {String} name - Name of the column
* @param {ConstraintOptions} [options] - Extra constraint options
* @returns {this}
*/
date(name, options){
this.#columns.push(parseColumnData(name, `date`, options));
return this;
}
/**
* Adds a new column of data type 'datetime' to this structure
* @param {String} name - Name of the column
* @param {Number} [fsp] - Fractional second precision. Min 0, Max 6. Defaults to 0
* @param {ConstraintOptions} [options] - Extra constraint options
* @returns {this}
*/
datetime(name, fsp=0, options){
if (fsp < 0 || fsp > 6) throw new Error(`Column datatype 'fsp' size must be a number between 0 and 6. Received: ${size}`);
this.#columns.push(parseColumnData(name, `datetime(${fsp})`, options));
return this;
}
/**
* Adds a new column of data type 'timestamp' to this structure
* @param {String} name - Name of the column
* @param {Number} [fsp] - Fractional second precision. Min 0, Max 6. Defaults to 0
* @param {ConstraintOptions} [options] - Extra constraint options
* @returns {this}
*/
timestamp(name, fsp=0, options){
if (fsp < 0 || fsp > 6) throw new Error(`Column datatype 'fsp' size must be a number between 0 and 6. Received: ${size}`);
this.#columns.push(parseColumnData(name, `timestamp(${fsp})`, options));
return this;
}
/**
* Adds a new column of data type 'time' to this structure
* @param {String} name - Name of the column
* @param {Number} [fsp] - Fractional second precision. Min 0, Max 6. Defaults to 0
* @param {ConstraintOptions} [options] - Extra constraint options
* @returns {this}
*/
time(name, fsp=0, options){
if (fsp < 0 || fsp > 6) throw new Error(`Column datatype 'fsp' size must be a number between 0 and 6. Received: ${size}`);
this.#columns.push(parseColumnData(name, `time(${fsp})`, options));
return this;
}
/**
* Adds a new column of data type 'year' to this structure
* @param {String} name - Name of the column
* @param {ConstraintOptions} [options] - Extra constraint options
* @returns {this}
*/
year(name, options){
this.#columns.push(parseColumnData(name, `time`, options));
return this;
}
} }
/** /**
* * Parses the given column data
* @param {String} name * @private
* @param {String} type * @param {String} name - Column name
* @param {String} type - Column type
* @param {ConstraintOptions} options * @param {ConstraintOptions} options
* @returns * @returns {ColumnStructure}
*/ */
function parseColumnData(name, type, options={}){ function parseColumnData(name, type, options={}){
if (options.primary + options.unique + options.index > 1) throw new Error(`ConstrainError: Only one of 'primary', 'unique' or 'index' allowed`); if (options.primary + options.unique + options.index > 1) throw new Error(`ConstrainError: Only one of 'primary', 'unique' or 'index' allowed`);
return { return {
Field: name, Field: name,
Type: `${type}${options.unsigned?" UNSIGNED":""}`, Type: `${type}${options.unsigned?" unsigned":""}`,
Null: options.null?"YES":"NO", Null: options.null?"YES":"NO",
Key: options.primary?"PRI":options.index?"MUL":options.unique?"UNI":"", Key: options.primary?"PRI":options.index?"MUL":options.unique?"UNI":"",
Default: options.default?options.default:null, Default: options.default?options.default:null,
@ -525,5 +614,15 @@ function parseColumnData(name, type, options={}){
} }
} }
/**
* @typedef {Object} ColumnStructure
* @property {String} Field - Column name
* @property {String} Type - Column type
* @property {"YES"|"NO"} Null - Whether the column allows null-values
* @property {""|"PRI"|"MUL"|"UNI"} Key - PRI = Primary, MUL = Indexed, UNI = Unique
* @property {String|null} Default - Default values for this row
* @property {""|"auto_increment"} Extra - Any extra
*/
module.exports = { CreateTable, Structure, AlterTable } module.exports = { CreateTable, Structure, AlterTable }

View File

@ -1,3 +1,7 @@
/**
* Prepares a new Update
*/
class Update{ class Update{
#instance; #instance;
#database; #database;
@ -16,8 +20,9 @@ class Update{
/** /**
* Updates all matching rows with the given object * Updates all matching rows with the given object
* @param {Object} object - The object with the data to update. Keys represent column names * @param {Object} object - The object with the data to update. Keys represent column names
* @returns {this}
*/ */
data = (object) => { data(object){
this.#data = object; this.#data = object;
return this; return this;
} }
@ -27,15 +32,16 @@ class Update{
* @param {String} database - Name of the database * @param {String} database - Name of the database
* @returns {this} * @returns {this}
*/ */
selectDatabase = (database) => { selectDatabase(database){
this.#database = database; this.#database = database;
return this; return this;
} }
/** /**
* Enables update of all rows * Enables update of all rows
* @returns {this}
*/ */
force = () => { force(){
this.#force = true; this.#force = true;
return this; return this;
} }
@ -44,19 +50,20 @@ class Update{
* Adds a where-clause to the query * Adds a where-clause to the query
* - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection * - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection
* @param {String} string - The where-clause as a string with ? representing each values. * @param {String} string - The where-clause as a string with ? representing each values.
* @param {Array} values - Array containing values replacing the ? in the string (from left to right) * @param {Array<Any>} values - Array containing values replacing the ? in the string (from left to right)
* @returns {this} * @returns {this}
*/ */
where = (string, values=[]) => { where(string, values=[]){
this.#where = string; this.#where = string;
this.#whereValues = values; this.#whereValues = values;
return this; return this;
} }
/** /**
* Executes the prepared query * Executes the prepared querry
* @returns {Any}
*/ */
execute = async () => { async execute(){
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`); if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
if (!this.#database) throw new Error(`Can't execute query: Database not selected`); if (!this.#database) throw new Error(`Can't execute query: Database not selected`);
if (!this.#where && !this.#force) throw new Error("Update: Tried to update all rows. If this was intentional, use .force() on Update"); if (!this.#where && !this.#force) throw new Error("Update: Tried to update all rows. If this was intentional, use .force() on Update");

View File

@ -3,16 +3,16 @@
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1",
"generate-docs": "jsdoc --configure jsdoc.json --verbose"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"mysql": "^2.18.1" "mysql": "^2.18.1"
}, },
"devDependencies": {}, "devDependencies": {
"directories": { "clean-jsdoc-theme": "^4.3.0"
"lib": "lib"
}, },
"description": "" "description": ""
} }