UNPKG

@promptbook/utils

Version:

It's time for a paradigm shift. The future of software in plain English, French or Latin

1,393 lines (1,303 loc) โ€ข 98.8 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('spacetrim'), require('path'), require('crypto')) : typeof define === 'function' && define.amd ? define(['exports', 'spacetrim', 'path', 'crypto'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-utils"] = {}, global.spaceTrim$1, global.path, global.crypto)); })(this, (function (exports, spaceTrim$1, path, crypto) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(spaceTrim$1); // โš ๏ธ WARNING: This code has been generated so that any manual changes will be overwritten /** * The version of the Book language * * @generated * @see https://github.com/webgptorg/book */ const BOOK_LANGUAGE_VERSION = '1.0.0'; /** * The version of the Promptbook engine * * @generated * @see https://github.com/webgptorg/promptbook */ const PROMPTBOOK_ENGINE_VERSION = '0.89.0-16'; /** * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name */ /** * Name for the Promptbook * * TODO: [๐Ÿ—ฝ] Unite branding and make single place for it * * @public exported from `@promptbook/core` */ const NAME = `Promptbook`; /** * Email of the responsible person * * @public exported from `@promptbook/core` */ const ADMIN_EMAIL = 'pavol@ptbk.io'; /** * Name of the responsible person for the Promptbook on GitHub * * @public exported from `@promptbook/core` */ const ADMIN_GITHUB_NAME = 'hejny'; // <- TODO: [๐Ÿง ] Better system for generator warnings - not always "code" and "by `@promptbook/cli`" /** * The maximum number of iterations for a loops * * @private within the repository - too low-level in comparison with other `MAX_...` */ const LOOP_LIMIT = 1000; /** * Strings to represent various values in the context of parameter values * * @public exported from `@promptbook/utils` */ const VALUE_STRINGS = { empty: '(nothing; empty string)', null: '(no value; null)', undefined: '(unknown value; undefined)', nan: '(not a number; NaN)', infinity: '(infinity; โˆž)', negativeInfinity: '(negative infinity; -โˆž)', unserializable: '(unserializable value)', circular: '(circular JSON)', }; /** * Small number limit * * @public exported from `@promptbook/utils` */ const SMALL_NUMBER = 0.001; // <- TODO: [๐Ÿงœโ€โ™‚๏ธ] /** * @@@ * * @public exported from `@promptbook/core` */ Object.freeze({ delimiter: ',', quoteChar: '"', newline: '\n', skipEmptyLines: true, }); /** * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name * TODO: [๐Ÿง ][๐Ÿงœโ€โ™‚๏ธ] Maybe join remoteServerUrl and path into single value */ /** * Make error report URL for the given error * * @private private within the repository */ function getErrorReportUrl(error) { const report = { title: `๐Ÿœ Error report from ${NAME}`, body: spaceTrim__default["default"]((block) => ` \`${error.name || 'Error'}\` has occurred in the [${NAME}], please look into it @${ADMIN_GITHUB_NAME}. \`\`\` ${block(error.message || '(no error message)')} \`\`\` ## More info: - **Promptbook engine version:** ${PROMPTBOOK_ENGINE_VERSION} - **Book language version:** ${BOOK_LANGUAGE_VERSION} - **Time:** ${new Date().toISOString()} <details> <summary>Stack trace:</summary> ## Stack trace: \`\`\`stacktrace ${block(error.stack || '(empty)')} \`\`\` </details> `), }; const reportUrl = new URL(`https://github.com/webgptorg/promptbook/issues/new`); reportUrl.searchParams.set('labels', 'bug'); reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME); reportUrl.searchParams.set('title', report.title); reportUrl.searchParams.set('body', report.body); return reportUrl; } /** * This error type indicates that the error should not happen and its last check before crashing with some other error * * @public exported from `@promptbook/core` */ class UnexpectedError extends Error { constructor(message) { super(spaceTrim$1.spaceTrim((block) => ` ${block(message)} Note: This error should not happen. It's probbably a bug in the pipeline collection Please report issue: ${block(getErrorReportUrl(new Error(message)).href)} Or contact us on ${ADMIN_EMAIL} `)); this.name = 'UnexpectedError'; Object.setPrototypeOf(this, UnexpectedError.prototype); } } /** * @@@ * * @param text @@@ * @param _isFirstLetterCapital @@@ * @returns @@@ * @example 'helloWorld' * @example 'iLovePromptbook' * @public exported from `@promptbook/utils` */ function normalizeTo_camelCase(text, _isFirstLetterCapital = false) { let charType; let lastCharType = null; let normalizedName = ''; for (const char of text) { let normalizedChar; if (/^[a-z]$/.test(char)) { charType = 'LOWERCASE'; normalizedChar = char; } else if (/^[A-Z]$/.test(char)) { charType = 'UPPERCASE'; normalizedChar = char.toLowerCase(); } else if (/^[0-9]$/.test(char)) { charType = 'NUMBER'; normalizedChar = char; } else { charType = 'OTHER'; normalizedChar = ''; } if (!lastCharType) { if (_isFirstLetterCapital) { normalizedChar = normalizedChar.toUpperCase(); //TODO: DRY } } else if (charType !== lastCharType && !(charType === 'LOWERCASE' && lastCharType === 'UPPERCASE') && !(lastCharType === 'NUMBER') && !(charType === 'NUMBER')) { normalizedChar = normalizedChar.toUpperCase(); //TODO: [๐ŸŒบ] DRY } normalizedName += normalizedChar; lastCharType = charType; } return normalizedName; } /** * TODO: [๐ŸŒบ] Use some intermediate util splitWords */ /** * Removes emojis from a string and fix whitespaces * * @param text with emojis * @returns text without emojis * @public exported from `@promptbook/utils` */ function removeEmojis(text) { // Replace emojis (and also ZWJ sequence) with hyphens text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1'); text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1'); text = text.replace(/(\p{Extended_Pictographic})(\u{200D}\p{Extended_Pictographic})*/gu, '$1'); text = text.replace(/\p{Extended_Pictographic}/gu, ''); return text; } /** * Tests if given string is valid URL. * * Note: This does not check if the file exists only if the path is valid * @public exported from `@promptbook/utils` */ function isValidFilePath(filename) { if (typeof filename !== 'string') { return false; } if (filename.split('\n').length > 1) { return false; } if (filename.split(' ').length > 5 /* <- TODO: [๐Ÿง ][๐Ÿˆท] Make some better non-arbitrary way how to distinct filenames from informational texts */) { return false; } const filenameSlashes = filename.split('\\').join('/'); // Absolute Unix path: /hello.txt if (/^(\/)/i.test(filenameSlashes)) { // console.log(filename, 'Absolute Unix path: /hello.txt'); return true; } // Absolute Windows path: /hello.txt if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) { // console.log(filename, 'Absolute Windows path: /hello.txt'); return true; } // Relative path: ./hello.txt if (/^(\.\.?\/)+/i.test(filenameSlashes)) { // console.log(filename, 'Relative path: ./hello.txt'); return true; } // Allow paths like foo/hello if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) { // console.log(filename, 'Allow paths like foo/hello'); return true; } // Allow paths like hello.book if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) { // console.log(filename, 'Allow paths like hello.book'); return true; } return false; } /** * TODO: [๐Ÿ] Implement for MacOs */ /** * Tests if given string is valid URL. * * Note: Dataurl are considered perfectly valid. * Note: There are two simmilar functions: * - `isValidUrl` which tests any URL * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL * * @public exported from `@promptbook/utils` */ function isValidUrl(url) { if (typeof url !== 'string') { return false; } try { if (url.startsWith('blob:')) { url = url.replace(/^blob:/, ''); } const urlObject = new URL(url /* because fail is handled */); if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) { return false; } return true; } catch (error) { return false; } } const defaultDiacriticsRemovalMap = [ { base: 'A', letters: '\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F', }, { base: 'AA', letters: '\uA732' }, { base: 'AE', letters: '\u00C6\u01FC\u01E2' }, { base: 'AO', letters: '\uA734' }, { base: 'AU', letters: '\uA736' }, { base: 'AV', letters: '\uA738\uA73A' }, { base: 'AY', letters: '\uA73C' }, { base: 'B', letters: '\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181', }, { base: 'C', letters: '\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E', }, { base: 'D', letters: '\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0', }, { base: 'DZ', letters: '\u01F1\u01C4' }, { base: 'Dz', letters: '\u01F2\u01C5' }, { base: 'E', letters: '\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E', }, { base: 'F', letters: '\u0046\u24BB\uFF26\u1E1E\u0191\uA77B' }, { base: 'G', letters: '\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E', }, { base: 'H', letters: '\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D', }, { base: 'I', letters: '\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197', }, { base: 'J', letters: '\u004A\u24BF\uFF2A\u0134\u0248' }, { base: 'K', letters: '\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2', }, { base: 'L', letters: '\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780', }, { base: 'LJ', letters: '\u01C7' }, { base: 'Lj', letters: '\u01C8' }, { base: 'M', letters: '\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C' }, { base: 'N', letters: '\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4', }, { base: 'NJ', letters: '\u01CA' }, { base: 'Nj', letters: '\u01CB' }, { base: 'O', letters: '\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C', }, { base: 'OI', letters: '\u01A2' }, { base: 'OO', letters: '\uA74E' }, { base: 'OU', letters: '\u0222' }, { base: 'OE', letters: '\u008C\u0152' }, { base: 'oe', letters: '\u009C\u0153' }, { base: 'P', letters: '\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754', }, { base: 'Q', letters: '\u0051\u24C6\uFF31\uA756\uA758\u024A' }, { base: 'R', letters: '\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782', }, { base: 'S', letters: '\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784', }, { base: 'T', letters: '\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786', }, { base: 'TZ', letters: '\uA728' }, { base: 'U', letters: '\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244', }, { base: 'V', letters: '\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245' }, { base: 'VY', letters: '\uA760' }, { base: 'W', letters: '\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72', }, { base: 'X', letters: '\u0058\u24CD\uFF38\u1E8A\u1E8C' }, { base: 'Y', letters: '\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE', }, { base: 'Z', letters: '\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762', }, { base: 'a', letters: '\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250', }, { base: 'aa', letters: '\uA733' }, { base: 'ae', letters: '\u00E6\u01FD\u01E3' }, { base: 'ao', letters: '\uA735' }, { base: 'au', letters: '\uA737' }, { base: 'av', letters: '\uA739\uA73B' }, { base: 'ay', letters: '\uA73D' }, { base: 'b', letters: '\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253', }, { base: 'c', letters: '\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184', }, { base: 'd', letters: '\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A', }, { base: 'dz', letters: '\u01F3\u01C6' }, { base: 'e', letters: '\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD', }, { base: 'f', letters: '\u0066\u24D5\uFF46\u1E1F\u0192\uA77C' }, { base: 'g', letters: '\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F', }, { base: 'h', letters: '\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265', }, { base: 'hv', letters: '\u0195' }, { base: 'i', letters: '\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131', }, { base: 'j', letters: '\u006A\u24D9\uFF4A\u0135\u01F0\u0249' }, { base: 'k', letters: '\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3', }, { base: 'l', letters: '\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747', }, { base: 'lj', letters: '\u01C9' }, { base: 'm', letters: '\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F' }, { base: 'n', letters: '\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5', }, { base: 'nj', letters: '\u01CC' }, { base: 'o', letters: '\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275', }, { base: 'oi', letters: '\u01A3' }, { base: 'ou', letters: '\u0223' }, { base: 'oo', letters: '\uA74F' }, { base: 'p', letters: '\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755', }, { base: 'q', letters: '\u0071\u24E0\uFF51\u024B\uA757\uA759' }, { base: 'r', letters: '\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783', }, { base: 's', letters: '\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B', }, { base: 't', letters: '\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787', }, { base: 'tz', letters: '\uA729' }, { base: 'u', letters: '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289', }, { base: 'v', letters: '\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C' }, { base: 'vy', letters: '\uA761' }, { base: 'w', letters: '\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73', }, { base: 'x', letters: '\u0078\u24E7\uFF58\u1E8B\u1E8D' }, { base: 'y', letters: '\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF', }, { base: 'z', letters: '\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763', }, ]; /** * Map of letters from diacritic variant to diacritless variant * Contains lowercase and uppercase separatelly * * > "รก" => "a" * > "ฤ›" => "e" * > "ฤ‚" => "A" * > ... * * @public exported from `@promptbook/utils` */ const DIACRITIC_VARIANTS_LETTERS = {}; // tslint:disable-next-line: prefer-for-of for (let i = 0; i < defaultDiacriticsRemovalMap.length; i++) { const letters = defaultDiacriticsRemovalMap[i].letters; // tslint:disable-next-line: prefer-for-of for (let j = 0; j < letters.length; j++) { DIACRITIC_VARIANTS_LETTERS[letters[j]] = defaultDiacriticsRemovalMap[i].base; } } // <- TODO: [๐Ÿ“] Put to maker function to save execution time if not needed /* @see https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript 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. */ /** * @@@ * * @param input @@@ * @returns @@@ * @public exported from `@promptbook/utils` */ function removeDiacritics(input) { /*eslint no-control-regex: "off"*/ return input.replace(/[^\u0000-\u007E]/g, (a) => { return DIACRITIC_VARIANTS_LETTERS[a] || a; }); } /** * TODO: [ะ–] Variant for cyrillic (and in general non-latin) letters */ /** * @@@ * * @param text @@@ * @returns @@@ * @example 'hello-world' * @example 'i-love-promptbook' * @public exported from `@promptbook/utils` */ function normalizeToKebabCase(text) { text = removeDiacritics(text); let charType; let lastCharType = 'OTHER'; let normalizedName = ''; for (const char of text) { let normalizedChar; if (/^[a-z]$/.test(char)) { charType = 'LOWERCASE'; normalizedChar = char; } else if (/^[A-Z]$/.test(char)) { charType = 'UPPERCASE'; normalizedChar = char.toLowerCase(); } else if (/^[0-9]$/.test(char)) { charType = 'NUMBER'; normalizedChar = char; } else { charType = 'OTHER'; normalizedChar = '-'; } if (charType !== lastCharType && !(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') && !(lastCharType === 'NUMBER') && !(charType === 'NUMBER')) { normalizedName += '-'; } normalizedName += normalizedChar; lastCharType = charType; } normalizedName = normalizedName.split(/-+/g).join('-'); normalizedName = normalizedName.split(/-?\/-?/g).join('/'); normalizedName = normalizedName.replace(/^-/, ''); normalizedName = normalizedName.replace(/-$/, ''); return normalizedName; } /** * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name */ /** * @@@ * * @param value @@@ * @returns @@@ * @example @@@ * @public exported from `@promptbook/utils` */ function titleToName(value) { if (isValidUrl(value)) { value = value.replace(/^https?:\/\//, ''); value = value.replace(/\.html$/, ''); } else if (isValidFilePath(value)) { value = path.basename(value); // Note: Keeping extension in the name } value = value.split('/').join('-'); value = removeEmojis(value); value = normalizeToKebabCase(value); // TODO: [๐Ÿง ] Maybe warn or add some padding to short name which are not good identifiers return value; } /** * Creates a Mermaid graph based on the promptbook * * Note: The result is not wrapped in a Markdown code block * * @public exported from `@promptbook/utils` */ function renderPromptbookMermaid(pipelineJson, options) { const { linkTask = () => null } = options || {}; const parameterNameToTaskName = (parameterName) => { const parameter = pipelineJson.parameters.find((parameter) => parameter.name === parameterName); if (!parameter) { throw new UnexpectedError(`Could not find {${parameterName}}`); // <- TODO: !!6 This causes problems when {knowledge} and other reserved parameters are used } if (parameter.isInput) { return 'input'; } const task = pipelineJson.tasks.find((task) => task.resultingParameterName === parameterName); if (!task) { throw new Error(`Could not find task for {${parameterName}}`); } return task.name || normalizeTo_camelCase('task-' + titleToName(task.title)); }; const promptbookMermaid = spaceTrim$1.spaceTrim((block) => ` %% ๐Ÿ”ฎ Tip: Open this on GitHub or in the VSCode website to see the Mermaid graph visually flowchart LR subgraph "${pipelineJson.title}" direction TB input((Input)):::input ${block(pipelineJson.tasks .flatMap(({ title, dependentParameterNames, resultingParameterName }) => [ `${parameterNameToTaskName(resultingParameterName)}("${title}")`, ...dependentParameterNames.map((dependentParameterName) => `${parameterNameToTaskName(dependentParameterName)}--"{${dependentParameterName}}"-->${parameterNameToTaskName(resultingParameterName)}`), ]) .join('\n'))} ${block(pipelineJson.parameters .filter(({ isOutput }) => isOutput) .map(({ name }) => `${parameterNameToTaskName(name)}--"{${name}}"-->output`) .join('\n'))} output((Output)):::output ${block(pipelineJson.tasks .map((task) => { const link = linkTask(task); if (link === null) { return ''; } const { href, title } = link; const taskName = parameterNameToTaskName(task.resultingParameterName); return `click ${taskName} href "${href}" "${title}";`; }) .filter((line) => line !== '') .join('\n'))} classDef input color: grey; classDef output color: grey; end; `); return promptbookMermaid; } /** * TODO: [๐Ÿง ] FOREACH in mermaid graph * TODO: [๐Ÿง ] Knowledge in mermaid graph * TODO: [๐Ÿง ] Personas in mermaid graph * TODO: Maybe use some Mermaid package instead of string templating * TODO: [๐Ÿ•Œ] When more than 2 functionalities, split into separate functions */ /** * This error indicates problems parsing the format value * * For example, when the format value is not a valid JSON or CSV * This is not thrown directly but in extended classes * * @public exported from `@promptbook/core` */ class AbstractFormatError extends Error { // Note: To allow instanceof do not put here error `name` // public readonly name = 'AbstractFormatError'; constructor(message) { super(message); Object.setPrototypeOf(this, AbstractFormatError.prototype); } } /** * This error indicates problem with parsing of CSV * * @public exported from `@promptbook/core` */ class CsvFormatError extends AbstractFormatError { constructor(message) { super(message); this.name = 'CsvFormatError'; Object.setPrototypeOf(this, CsvFormatError.prototype); } } /** * AuthenticationError is thrown from login function which is dependency of remote server * * @public exported from `@promptbook/core` */ class AuthenticationError extends Error { constructor(message) { super(message); this.name = 'AuthenticationError'; Object.setPrototypeOf(this, AuthenticationError.prototype); } } /** * This error indicates that the pipeline collection cannot be propperly loaded * * @public exported from `@promptbook/core` */ class CollectionError extends Error { constructor(message) { super(message); this.name = 'CollectionError'; Object.setPrototypeOf(this, CollectionError.prototype); } } /** * This error type indicates that you try to use a feature that is not available in the current environment * * @public exported from `@promptbook/core` */ class EnvironmentMismatchError extends Error { constructor(message) { super(message); this.name = 'EnvironmentMismatchError'; Object.setPrototypeOf(this, EnvironmentMismatchError.prototype); } } /** * This error occurs when some expectation is not met in the execution of the pipeline * * @public exported from `@promptbook/core` * Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server * Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError` * Note: This is a kindof subtype of PipelineExecutionError */ class ExpectError extends Error { constructor(message) { super(message); this.name = 'ExpectError'; Object.setPrototypeOf(this, ExpectError.prototype); } } /** * This error indicates that the promptbook can not retrieve knowledge from external sources * * @public exported from `@promptbook/core` */ class KnowledgeScrapeError extends Error { constructor(message) { super(message); this.name = 'KnowledgeScrapeError'; Object.setPrototypeOf(this, KnowledgeScrapeError.prototype); } } /** * This error type indicates that some limit was reached * * @public exported from `@promptbook/core` */ class LimitReachedError extends Error { constructor(message) { super(message); this.name = 'LimitReachedError'; Object.setPrototypeOf(this, LimitReachedError.prototype); } } /** * This error type indicates that some tools are missing for pipeline execution or preparation * * @public exported from `@promptbook/core` */ class MissingToolsError extends Error { constructor(message) { super(spaceTrim$1.spaceTrim((block) => ` ${block(message)} Note: You have probbably forgot to provide some tools for pipeline execution or preparation `)); this.name = 'MissingToolsError'; Object.setPrototypeOf(this, MissingToolsError.prototype); } } /** * This error indicates that promptbook not found in the collection * * @public exported from `@promptbook/core` */ class NotFoundError extends Error { constructor(message) { super(message); this.name = 'NotFoundError'; Object.setPrototypeOf(this, NotFoundError.prototype); } } /** * This error type indicates that some part of the code is not implemented yet * * @public exported from `@promptbook/core` */ class NotYetImplementedError extends Error { constructor(message) { super(spaceTrim$1.spaceTrim((block) => ` ${block(message)} Note: This feature is not implemented yet but it will be soon. If you want speed up the implementation or just read more, look here: https://github.com/webgptorg/promptbook Or contact us on pavol@ptbk.io `)); this.name = 'NotYetImplementedError'; Object.setPrototypeOf(this, NotYetImplementedError.prototype); } } /** * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object * * @public exported from `@promptbook/core` */ class ParseError extends Error { constructor(message) { super(message); this.name = 'ParseError'; Object.setPrototypeOf(this, ParseError.prototype); } } /** * TODO: Maybe split `ParseError` and `ApplyError` */ /** * Generates random token * * Note: This function is cryptographically secure (it uses crypto.randomBytes internally) * * @private internal helper function * @returns secure random token */ function $randomToken(randomness) { return crypto.randomBytes(randomness).toString('hex'); } /** * TODO: Maybe use nanoid instead https://github.com/ai/nanoid */ /** * This error indicates errors during the execution of the pipeline * * @public exported from `@promptbook/core` */ class PipelineExecutionError extends Error { constructor(message) { // Added id parameter super(message); this.name = 'PipelineExecutionError'; // TODO: [๐Ÿ™] DRY - Maybe $randomId this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid simmilar char conflicts */)}`; Object.setPrototypeOf(this, PipelineExecutionError.prototype); } } /** * TODO: [๐Ÿง ][๐ŸŒ‚] Add id to all errors */ /** * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies) * * @public exported from `@promptbook/core` */ class PipelineLogicError extends Error { constructor(message) { super(message); this.name = 'PipelineLogicError'; Object.setPrototypeOf(this, PipelineLogicError.prototype); } } /** * This error indicates errors in referencing promptbooks between each other * * @public exported from `@promptbook/core` */ class PipelineUrlError extends Error { constructor(message) { super(message); this.name = 'PipelineUrlError'; Object.setPrototypeOf(this, PipelineUrlError.prototype); } } /** * Error thrown when a fetch request fails * * @public exported from `@promptbook/core` */ class PromptbookFetchError extends Error { constructor(message) { super(message); this.name = 'PromptbookFetchError'; Object.setPrototypeOf(this, PromptbookFetchError.prototype); } } /** * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError` * * @public exported from `@promptbook/core` */ class WrappedError extends Error { constructor(whatWasThrown) { const tag = `[๐Ÿคฎ]`; console.error(tag, whatWasThrown); super(spaceTrim$1.spaceTrim(` Non-Error object was thrown Note: Look for ${tag} in the console for more details Please report issue on ${ADMIN_EMAIL} `)); this.name = 'WrappedError'; Object.setPrototypeOf(this, WrappedError.prototype); } } /** * Index of all custom errors * * @public exported from `@promptbook/core` */ const PROMPTBOOK_ERRORS = { AbstractFormatError, CsvFormatError, CollectionError, EnvironmentMismatchError, ExpectError, KnowledgeScrapeError, LimitReachedError, MissingToolsError, NotFoundError, NotYetImplementedError, ParseError, PipelineExecutionError, PipelineLogicError, PipelineUrlError, AuthenticationError, PromptbookFetchError, UnexpectedError, WrappedError, // TODO: [๐Ÿช‘]> VersionMismatchError, }; /** * Index of all javascript errors * * @private for internal usage */ const COMMON_JAVASCRIPT_ERRORS = { Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError, /* Note: Not widely supported > InternalError, > ModuleError, > HeapError, > WebAssemblyCompileError, > WebAssemblyRuntimeError, */ }; /** * Index of all errors * * @private for internal usage */ const ALL_ERRORS = { ...PROMPTBOOK_ERRORS, ...COMMON_JAVASCRIPT_ERRORS, }; /** * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name */ /** * Deserializes the error object * * @public exported from `@promptbook/utils` */ function deserializeError(error) { const { name, stack, id } = error; // Added id let { message } = error; let ErrorClass = ALL_ERRORS[error.name]; if (ErrorClass === undefined) { ErrorClass = Error; message = `${name}: ${message}`; } if (stack !== undefined && stack !== '') { message = spaceTrim__default["default"]((block) => ` ${block(message)} Original stack trace: ${block(stack || '')} `); } const deserializedError = new ErrorClass(message); deserializedError.id = id; // Assign id to the error object return deserializedError; } /** * Serializes an error into a [๐Ÿš‰] JSON-serializable object * * @public exported from `@promptbook/utils` */ function serializeError(error) { const { name, message, stack } = error; const { id } = error; if (!Object.keys(ALL_ERRORS).includes(name)) { console.error(spaceTrim__default["default"]((block) => ` Cannot serialize error with name "${name}" Authors of Promptbook probably forgot to add this error into the list of errors: https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts ${block(stack || message)} `)); } return { name: name, message, stack, id, // Include id in the serialized object }; } /** * Async version of Array.forEach * * @param array - Array to iterate over * @param options - Options for the function * @param callbackfunction - Function to call for each item * @public exported from `@promptbook/utils` * @deprecated [๐Ÿช‚] Use queues instead */ async function forEachAsync(array, options, callbackfunction) { const { maxParallelCount = Infinity } = options; let index = 0; let runningTasks = []; const tasks = []; for (const item of array) { const currentIndex = index++; const task = callbackfunction(item, currentIndex, array); tasks.push(task); runningTasks.push(task); /* not await */ Promise.resolve(task).then(() => { runningTasks = runningTasks.filter((t) => t !== task); }); if (maxParallelCount < runningTasks.length) { await Promise.race(runningTasks); } } await Promise.all(tasks); } /** * Helper used in catch blocks to assert that the error is an instance of `Error` * * @param whatWasThrown Any object that was thrown * @returns Nothing if the error is an instance of `Error` * @throws `WrappedError` or `UnexpectedError` if the error is not standard * * @private within the repository */ function assertsError(whatWasThrown) { // Case 1: Handle error which was rethrown as `WrappedError` if (whatWasThrown instanceof WrappedError) { const wrappedError = whatWasThrown; throw wrappedError; } // Case 2: Handle unexpected errors if (whatWasThrown instanceof UnexpectedError) { const unexpectedError = whatWasThrown; throw unexpectedError; } // Case 3: Handle standard errors - keep them up to consumer if (whatWasThrown instanceof Error) { return; } // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw throw new WrappedError(whatWasThrown); } /** * Function isValidJsonString will tell you if the string is valid JSON or not * * @public exported from `@promptbook/utils` */ function isValidJsonString(value /* <- [๐Ÿ‘จโ€โš–๏ธ] */) { try { JSON.parse(value); return true; } catch (error) { assertsError(error); if (error.message.includes('Unexpected token')) { return false; } return false; } } /** * Orders JSON object by keys * * @returns The same type of object as the input re-ordered * @public exported from `@promptbook/utils` */ function orderJson(options) { const { value, order } = options; const orderedValue = { ...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))), ...value, }; return orderedValue; } /** * Freezes the given object and all its nested objects recursively * * Note: `$` is used to indicate that this function is not a pure function - it mutates given object * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object * * @returns The same object as the input, but deeply frozen * @public exported from `@promptbook/utils` */ function $deepFreeze(objectValue) { if (Array.isArray(objectValue)) { return Object.freeze(objectValue.map((item) => $deepFreeze(item))); } const propertyNames = Object.getOwnPropertyNames(objectValue); for (const propertyName of propertyNames) { const value = objectValue[propertyName]; if (value && typeof value === 'object') { $deepFreeze(value); } } Object.freeze(objectValue); return objectValue; } /** * TODO: [๐Ÿง ] Is there a way how to meaningfully test this utility */ /** * Checks if the value is [๐Ÿš‰] serializable as JSON * If not, throws an UnexpectedError with a rich error message and tracking * * - Almost all primitives are serializable BUT: * - `undefined` is not serializable * - `NaN` is not serializable * - Objects and arrays are serializable if all their properties are serializable * - Functions are not serializable * - Circular references are not serializable * - `Date` objects are not serializable * - `Map` and `Set` objects are not serializable * - `RegExp` objects are not serializable * - `Error` objects are not serializable * - `Symbol` objects are not serializable * - And much more... * * @throws UnexpectedError if the value is not serializable as JSON * @public exported from `@promptbook/utils` */ function checkSerializableAsJson(options) { const { value, name, message } = options; if (value === undefined) { throw new UnexpectedError(`${name} is undefined`); } else if (value === null) { return; } else if (typeof value === 'boolean') { return; } else if (typeof value === 'number' && !isNaN(value)) { return; } else if (typeof value === 'string') { return; } else if (typeof value === 'symbol') { throw new UnexpectedError(`${name} is symbol`); } else if (typeof value === 'function') { throw new UnexpectedError(`${name} is function`); } else if (typeof value === 'object' && Array.isArray(value)) { for (let i = 0; i < value.length; i++) { checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message }); } } else if (typeof value === 'object') { if (value instanceof Date) { throw new UnexpectedError(spaceTrim__default["default"]((block) => ` \`${name}\` is Date Use \`string_date_iso8601\` instead Additional message for \`${name}\`: ${block(message || '(nothing)')} `)); } else if (value instanceof Map) { throw new UnexpectedError(`${name} is Map`); } else if (value instanceof Set) { throw new UnexpectedError(`${name} is Set`); } else if (value instanceof RegExp) { throw new UnexpectedError(`${name} is RegExp`); } else if (value instanceof Error) { throw new UnexpectedError(spaceTrim__default["default"]((block) => ` \`${name}\` is unserialized Error Use function \`serializeError\` Additional message for \`${name}\`: ${block(message || '(nothing)')} `)); } else { for (const [subName, subValue] of Object.entries(value)) { if (subValue === undefined) { // Note: undefined in object is serializable - it is just omited continue; } checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message }); } try { JSON.stringify(value); // <- TODO: [0] } catch (error) { assertsError(error); throw new UnexpectedError(spaceTrim__default["default"]((block) => ` \`${name}\` is not serializable ${block(error.stack || error.message)} Additional message for \`${name}\`: ${block(message || '(nothing)')} `)); } /* TODO: [0] Is there some more elegant way to check circular references? const seen = new Set(); const stack = [{ value }]; while (stack.length > 0) { const { value } = stack.pop()!; if (typeof value === 'object' && value !== null) { if (seen.has(value)) { throw new UnexpectedError(`${name} has circular reference`); } seen.add(value); if (Array.isArray(value)) { stack.push(...value.map((value) => ({ value }))); } else { stack.push(...Object.values(value).map((value) => ({ value }))); } } } */ return; } } else { throw new UnexpectedError(spaceTrim__default["default"]((block) => ` \`${name}\` is unknown type Additional message for \`${name}\`: ${block(message || '(nothing)')} `)); } } /** * TODO: Can be return type more type-safe? like `asserts options.value is JsonValue` * TODO: [๐Ÿง ][main] !!3 In-memory cache of same values to prevent multiple checks * Note: [๐Ÿ ] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message */ /** * @@@ * * @public exported from `@promptbook/utils` */ function deepClone(objectValue) { return JSON.parse(JSON.stringify(objectValue)); /* TODO: [๐Ÿง ] Is there a better implementation? > const propertyNames = Object.getOwnPropertyNames(objectValue); > for (const propertyName of propertyNames) { > const value = (objectValue as really_any)[propertyName]; > if (value && typeof value === 'object') { > deepClone(value); > } > } > return Object.assign({}, objectValue); */ } /** * TODO: [๐Ÿง ] Is there a way how to meaningfully test this utility */ /** * Utility to export a JSON object from a f