UNPKG

@promptbook/browser

Version:

Promptbook: Run AI apps in plain human language across multiple models and platforms

1,039 lines (973 loc) โ€ข 35.1 kB
import spaceTrim$1, { spaceTrim } from 'spacetrim'; import { randomBytes } from 'crypto'; import { isRunningInBrowser } from 'openai/core'; // โš ๏ธ 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.100.0-33'; /** * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name */ /** * 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 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 similar char conflicts */)}`; Object.setPrototypeOf(this, PipelineExecutionError.prototype); } } /** * TODO: [๐Ÿง ][๐ŸŒ‚] Add id to all errors */ /** * Wrapper around `window.prompt` synchronous function that interacts with the user via browser prompt * * Warning: It is used for testing and mocking * **NOT intended to use in the production** due to its synchronous nature. * * @public exported from `@promptbook/browser` */ class SimplePromptInterfaceTools { constructor(options = {}) { this.options = options; } /** * Trigger window.prompt dialog */ async promptDialog(options) { const answer = window.prompt(spaceTrim((block) => ` ${block(options.promptTitle)} ${block(options.promptMessage)} `)); if (this.options.isVerbose) { console.info(spaceTrim((block) => ` ๐Ÿ“– ${block(options.promptTitle)} ๐Ÿ‘ค ${block(answer || '๐Ÿšซ User cancelled prompt')} `)); } if (answer === null) { throw new PipelineExecutionError('User cancelled prompt'); } return answer; } } /** * Note: [๐Ÿ”ต] Code in this file should never be published outside of `@promptbook/browser` */ /** * 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); } } /** * Detects if the code is running in a browser environment in main thread (Not in a web worker) * * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment * * @public exported from `@promptbook/utils` */ const $isRunningInBrowser = new Function(` try { return this === window; } catch (e) { return false; } `); /** * TODO: [๐ŸŽบ] */ /** * Detects if the code is running in a web worker * * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment * * @public exported from `@promptbook/utils` */ const $isRunningInWebWorker = new Function(` try { if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) { return true; } else { return false; } } catch (e) { return false; } `); /** * TODO: [๐ŸŽบ] */ /** * 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; // <- TODO: [๐Ÿงœโ€โ™‚๏ธ] /** * Default settings for parsing and generating CSV files in Promptbook. * * @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 */ /** * 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((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); } } /** * 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$1((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((block) => ` ${block(message)} Note: This error should not happen. It's probably 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); } } /** * Safely retrieves the global scope object (window in browser, global in Node.js) * regardless of the JavaScript environment in which the code is running * * Note: `$` is used to indicate that this function is not a pure function - it access global scope * * @private internal function of `$Register` */ function $getGlobalScope() { return Function('return this')(); } /** * Normalizes a text string to SCREAMING_CASE (all uppercase with underscores). * * @param text The text string to be converted to SCREAMING_CASE format. * @returns The normalized text in SCREAMING_CASE format. * @example 'HELLO_WORLD' * @example 'I_LOVE_PROMPTBOOK' * @public exported from `@promptbook/utils` */ function normalizeTo_SCREAMING_CASE(text) { let charType; let lastCharType = 'OTHER'; let normalizedName = ''; for (const char of text) { let normalizedChar; if (/^[a-z]$/.test(char)) { charType = 'LOWERCASE'; normalizedChar = char.toUpperCase(); } else if (/^[A-Z]$/.test(char)) { charType = 'UPPERCASE'; normalizedChar = char; } 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.replace(/_+/g, '_'); normalizedName = normalizedName.replace(/_?\/_?/g, '/'); normalizedName = normalizedName.replace(/^_/, ''); normalizedName = normalizedName.replace(/_$/, ''); return normalizedName; } /** * TODO: Tests * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'Moje tabule' })).toEqual('/VtG7sR9rRJqwNEdM2/Moje tabule'); * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'ฤ›ลกฤล™ลพลพรฝรกรญรบลฏ' })).toEqual('/VtG7sR9rRJqwNEdM2/escrzyaieuu'); * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj'); * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj_ahojAhoj ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj-ahoj-ahoj-ahoj'); * TODO: [๐ŸŒบ] Use some intermediate util splitWords */ /** * Normalizes a text string to snake_case format. * * @param text The text string to be converted to snake_case format. * @returns The normalized text in snake_case format. * @example 'hello_world' * @example 'i_love_promptbook' * @public exported from `@promptbook/utils` */ function normalizeTo_snake_case(text) { return normalizeTo_SCREAMING_CASE(text).toLowerCase(); } /** * Global registry for storing and managing registered entities of a given type. * * Note: `$` is used to indicate that this function is not a pure function - it accesses and adds variables in global scope. * * @private internal utility, exported are only singleton instances of this class */ class $Register { constructor(registerName) { this.registerName = registerName; const storageName = `_promptbook_${normalizeTo_snake_case(registerName)}`; const globalScope = $getGlobalScope(); if (globalScope[storageName] === undefined) { globalScope[storageName] = []; } else if (!Array.isArray(globalScope[storageName])) { throw new UnexpectedError(`Expected (global) ${storageName} to be an array, but got ${typeof globalScope[storageName]}`); } this.storage = globalScope[storageName]; } list() { // <- TODO: ReadonlyDeep<ReadonlyArray<TRegistered>> return this.storage; } register(registered) { const { packageName, className } = registered; const existingRegistrationIndex = this.storage.findIndex((item) => item.packageName === packageName && item.className === className); const existingRegistration = this.storage[existingRegistrationIndex]; if (!existingRegistration) { this.storage.push(registered); } else { this.storage[existingRegistrationIndex] = registered; } return { registerName: this.registerName, packageName, className, get isDestroyed() { return false; }, destroy() { throw new NotYetImplementedError(`Registration to ${this.registerName} is permanent in this version of Promptbook`); }, }; } } /** * Registry for all available scrapers in the system. * Central point for registering and accessing different types of content scrapers. * * Note: `$` is used to indicate that this interacts with the global scope * @singleton Only one instance of each register is created per build, but there can be more than one in different build modules * @public exported from `@promptbook/core` */ const $scrapersRegister = new $Register('scraper_constructors'); /** * TODO: [ยฎ] DRY Register logic */ /** * Provides a collection of scrapers optimized for browser environments. * Only includes scrapers that can safely run in a browser context. * * Note: Browser scrapers have limitations compared to Node.js scrapers. * * 1) `provideScrapersForNode` use as default * 2) `provideScrapersForBrowser` use in limited browser environment * * @public exported from `@promptbook/browser` */ async function $provideScrapersForBrowser(tools, options) { if (!$isRunningInBrowser() || $isRunningInWebWorker()) { throw new EnvironmentMismatchError('Function `$provideScrapersForBrowser` works only in browser environment'); } const { isAutoInstalled /* Note: [0] Intentionally not assigning a default value = IS_AUTO_INSTALLED */ } = options || {}; if (isAutoInstalled === true /* <- Note: [0] Ignoring undefined, just checking EXPLICIT requirement for install */) { throw new EnvironmentMismatchError('Auto-installing is not supported in browser environment'); } const scrapers = []; for (const scraperFactory of $scrapersRegister.list()) { const scraper = await scraperFactory(tools, options || {}); scrapers.push(scraper); } return scrapers; } /** * Creates a PromptbookStorage backed by IndexedDB. * Uses a single object store named 'promptbook'. * @private for `getIndexedDbStorage` */ function makePromptbookStorageFromIndexedDb(options) { const { databaseName, storeName } = options; function getDatabase() { return new Promise((resolve, reject) => { const request = indexedDB.open(databaseName, 1); request.onupgradeneeded = () => { request.result.createObjectStore(storeName); }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } return { async getItem(key) { const database = await getDatabase(); return new Promise((resolve, reject) => { const transaction = database.transaction(storeName, 'readonly'); const objectStore = transaction.objectStore(storeName); const request = objectStore.get(key); request.onsuccess = () => { var _a; return resolve((_a = request.result) !== null && _a !== void 0 ? _a : null); }; request.onerror = () => reject(request.error); }); }, async setItem(key, value) { const database = await getDatabase(); return new Promise((resolve, reject) => { const transaction = database.transaction(storeName, 'readwrite'); const objectStore = transaction.objectStore(storeName); const request = objectStore.put(value, key); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); }, async removeItem(key) { const database = await getDatabase(); return new Promise((resolve, reject) => { const transaction = database.transaction(storeName, 'readwrite'); const objectStore = transaction.objectStore(storeName); const request = objectStore.delete(key); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); }, }; } /** * Cache storage * * @private internal cache for `getIndexedDbStorage` */ const indexedDbStorageCache = new Map(); /** * Gets wrapper around IndexedDB which can be used as PromptbookStorage * * @public exported from `@promptbook/browser` */ function getIndexedDbStorage(options) { if (!isRunningInBrowser()) { throw new EnvironmentMismatchError(`You can get IndexedDB storage only in browser environment`); } const { databaseName, storeName } = options; const cacheKey = `${databaseName}/${storeName}`; if (indexedDbStorageCache.has(cacheKey)) { return indexedDbStorageCache.get(cacheKey); } const storage = makePromptbookStorageFromIndexedDb({ databaseName, storeName }); indexedDbStorageCache.set(cacheKey, storage); return storage; } /** * Note: [๐Ÿ”ต] Code in this file should never be published outside of `@promptbook/browser` */ /** * Converts a JavaScript Object Notation (JSON) string into an object. * * Note: This is wrapper around `JSON.parse()` with better error and type handling * * @public exported from `@promptbook/utils` */ function jsonParse(value) { if (value === undefined) { throw new Error(`Can not parse JSON from undefined value.`); } else if (typeof value !== 'string') { console.error('Can not parse JSON from non-string value.', { text: value }); throw new Error(spaceTrim$1(` Can not parse JSON from non-string value. The value type: ${typeof value} See more in console. `)); } try { return JSON.parse(value); } catch (error) { if (!(error instanceof Error)) { throw error; } throw new Error(spaceTrim$1((block) => ` ${block(error.message)} The expected JSON text: ${block(value)} `)); } } /** * 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 */ /** * 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(` 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); } } /** * 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); } /** * 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$1((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$1((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 omitted continue; } checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message }); } try { JSON.stringify(value); // <- TODO: [0] } catch (error) { assertsError(error); throw new UnexpectedError(spaceTrim$1((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$1((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 */ /** * Creates a deep clone of the given object * * Note: This method only works for objects that are fully serializable to JSON and do not contain functions, Dates, or special types. * * @param objectValue The object to clone. * @returns A deep, writable clone of the input object. * @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 function * * 1) Checks if the value is serializable as JSON * 2) Makes a deep clone of the object * 2) Orders the object properties * 2) Deeply freezes the cloned object * * Note: This function does not mutates the given object * * @returns The same type of object as the input but read-only and re-ordered * @public exported from `@promptbook/utils` */ function exportJson(options) { const { name, value, order, message } = options; checkSerializableAsJson({ name, value, message }); const orderedValue = // TODO: Fix error "Type instantiation is excessively deep and possibly infinite." // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore order === undefined ? deepClone(value) : orderJson({ value: value, // <- Note: checkSerializableAsJson asserts that the value is serializable as JSON order: order, }); $deepFreeze(orderedValue); return orderedValue; } /** * TODO: [๐Ÿง ] Is there a way how to meaningfully test this utility */ /** * Nonce which is used for replacing things in strings * * @private within the repository */ const REPLACING_NONCE = 'ptbkauk42kV2dzao34faw7FudQUHYPtW'; /** * The names of the parameters that are reserved for special purposes * * @public exported from `@promptbook/core` */ exportJson({ name: 'RESERVED_PARAMETER_NAMES', message: `The names of the parameters that are reserved for special purposes`, value: [ 'content', 'context', 'knowledge', 'examples', 'modelName', 'currentDate', // <- TODO: list here all command names // <- TODO: Add more like 'date', 'modelName',... // <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter ], }); /** * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name */ // <- TODO: Auto convert to type `import { ... } from 'type-fest';` /** * Tests if the value is [๐Ÿš‰] serializable as JSON * * - 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... * * * @public exported from `@promptbook/utils` */ function isSerializableAsJson(value) { try { checkSerializableAsJson({ value }); return true; } catch (error) { return false; } } /** * TODO: [๐Ÿง ][main] !!3 In-memory cache of same values to prevent multiple checks * TODO: [๐Ÿง ][๐Ÿ’บ] Can be done this on type-level? */ /** * Stringify the PipelineJson with proper formatting * * Note: [0] It can be used for more JSON types like whole collection of pipelines, single knowledge piece, etc. * Note: In contrast to JSON.stringify, this function ensures that **embedding index** is on single line * * @public exported from `@promptbook/editable` */ function stringifyPipelineJson(pipeline) { if (!isSerializableAsJson(pipeline)) { throw new UnexpectedError(spaceTrim$1(` Cannot stringify the pipeline, because it is not serializable as JSON There can be multiple reasons: 1) The pipeline contains circular references 2) It is not a valid PipelineJson `)); } let pipelineJsonStringified = JSON.stringify(pipeline, null, 4); for (let i = 0; i < LOOP_LIMIT; i++) { pipelineJsonStringified = pipelineJsonStringified.replace(/(-?0\.\d+),[\n\s]+(-?0\.\d+)/gms, `$1${REPLACING_NONCE}$2`); } pipelineJsonStringified = pipelineJsonStringified.split(REPLACING_NONCE).join(', '); pipelineJsonStringified += '\n'; return pipelineJsonStringified; } /** * TODO: [๐Ÿ] Not Working properly @see https://promptbook.studio/examples/mixed-knowledge.book * TODO: [๐Ÿง ][0] Maybe rename to `stringifyPipelineJson`, `stringifyIndexedJson`,... * TODO: [๐Ÿง ] Maybe more elegant solution than replacing via regex * TODO: [๐Ÿ™] Make some standard order of json properties */ /** * Creates a Promptbook storage interface from a web storage object. * Facilitates using Web Storage (localStorage/sessionStorage) as a storage backend. * * @private for `getLocalStorage` and `getSessionStorage` */ function makePromptbookStorageFromWebStorage(webStorage) { return { getItem(key) { const stringValue = webStorage.getItem(key); if (stringValue === null) { return null; } const value = jsonParse(stringValue); // TODO: [๐ŸŒ—] return value; }, setItem(key, value) { if (!isSerializableAsJson(value)) { throw new UnexpectedError(`The "${key}" you want to store in web storage is not serializable as JSON`); } const stringValue = stringifyPipelineJson(value); webStorage.setItem(key, stringValue); }, removeItem(key) { webStorage.removeItem(key); }, }; } /** * TODO: [๐Ÿง ] Should this be named `makePromptbookStorageFromWebStorage` vs `createPromptbookStorageFromWebStorage` * TODO: [๐ŸŒ—] Maybe some checkers, not all valid JSONs are desired and valid values */ /** * Cache storage * * @private internal cache for `getLocalStorage` */ let promptbookLocalStorage = null; /** * Gets wrapper around `localStorage` object which can be used as `PromptbookStorage` * * @public exported from `@promptbook/browser` */ function getLocalStorage() { if (!isRunningInBrowser()) { throw new EnvironmentMismatchError(`You can get localStorage works only in browser environment`); } if (promptbookLocalStorage) { return promptbookLocalStorage; } promptbookLocalStorage = makePromptbookStorageFromWebStorage(localStorage); return promptbookLocalStorage; } /** * Note: [๐Ÿ”ต] Code in this file should never be published outside of `@promptbook/browser` */ /** * Cache storage * * @private internal cache for `getSessionStorage` */ let promptbookSessionStorage = null; /** * Gets wrapper around `sessionStorage` object which can be used as `PromptbookStorage` * * @public exported from `@promptbook/browser` */ function getSessionStorage() { if (!isRunningInBrowser()) { throw new EnvironmentMismatchError(`You can get sessionStorage works only in browser environment`); } if (promptbookSessionStorage) { return promptbookSessionStorage; } promptbookSessionStorage = makePromptbookStorageFromWebStorage(sessionStorage); return promptbookSessionStorage; } /** * Note: [๐Ÿ”ต] Code in this file should never be published outside of `@promptbook/browser` */ export { $provideScrapersForBrowser, BOOK_LANGUAGE_VERSION, PROMPTBOOK_ENGINE_VERSION, SimplePromptInterfaceTools, getIndexedDbStorage, getLocalStorage, getSessionStorage }; //# sourceMappingURL=index.es.js.map