@promptbook/utils 0.92.0-13 → 0.92.0-15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/index.es.js CHANGED
@@ -16,7 +16,7 @@ const BOOK_LANGUAGE_VERSION = '1.0.0';
16
16
  * @generated
17
17
  * @see https://github.com/webgptorg/promptbook
18
18
  */
19
- const PROMPTBOOK_ENGINE_VERSION = '0.92.0-13';
19
+ const PROMPTBOOK_ENGINE_VERSION = '0.92.0-15';
20
20
  /**
21
21
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
22
22
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -87,6 +87,48 @@ Object.freeze({
87
87
  * TODO: [🧠][🧜‍♂️] Maybe join remoteServerUrl and path into single value
88
88
  */
89
89
 
90
+ /**
91
+ * Orders JSON object by keys
92
+ *
93
+ * @returns The same type of object as the input re-ordered
94
+ * @public exported from `@promptbook/utils`
95
+ */
96
+ function orderJson(options) {
97
+ const { value, order } = options;
98
+ const orderedValue = {
99
+ ...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
100
+ ...value,
101
+ };
102
+ return orderedValue;
103
+ }
104
+
105
+ /**
106
+ * Freezes the given object and all its nested objects recursively
107
+ *
108
+ * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
109
+ * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
110
+ *
111
+ * @returns The same object as the input, but deeply frozen
112
+ * @public exported from `@promptbook/utils`
113
+ */
114
+ function $deepFreeze(objectValue) {
115
+ if (Array.isArray(objectValue)) {
116
+ return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
117
+ }
118
+ const propertyNames = Object.getOwnPropertyNames(objectValue);
119
+ for (const propertyName of propertyNames) {
120
+ const value = objectValue[propertyName];
121
+ if (value && typeof value === 'object') {
122
+ $deepFreeze(value);
123
+ }
124
+ }
125
+ Object.freeze(objectValue);
126
+ return objectValue;
127
+ }
128
+ /**
129
+ * TODO: [🧠] Is there a way how to meaningfully test this utility
130
+ */
131
+
90
132
  /**
91
133
  * Make error report URL for the given error
92
134
  *
@@ -156,153 +198,438 @@ class UnexpectedError extends Error {
156
198
  }
157
199
 
158
200
  /**
159
- * @@@
201
+ * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
160
202
  *
161
- * @param text @@@
162
- * @param _isFirstLetterCapital @@@
163
- * @returns @@@
164
- * @example 'helloWorld'
165
- * @example 'iLovePromptbook'
166
- * @public exported from `@promptbook/utils`
203
+ * @public exported from `@promptbook/core`
167
204
  */
168
- function normalizeTo_camelCase(text, _isFirstLetterCapital = false) {
169
- let charType;
170
- let lastCharType = null;
171
- let normalizedName = '';
172
- for (const char of text) {
173
- let normalizedChar;
174
- if (/^[a-z]$/.test(char)) {
175
- charType = 'LOWERCASE';
176
- normalizedChar = char;
177
- }
178
- else if (/^[A-Z]$/.test(char)) {
179
- charType = 'UPPERCASE';
180
- normalizedChar = char.toLowerCase();
181
- }
182
- else if (/^[0-9]$/.test(char)) {
183
- charType = 'NUMBER';
184
- normalizedChar = char;
185
- }
186
- else {
187
- charType = 'OTHER';
188
- normalizedChar = '';
189
- }
190
- if (!lastCharType) {
191
- if (_isFirstLetterCapital) {
192
- normalizedChar = normalizedChar.toUpperCase(); //TODO: DRY
193
- }
194
- }
195
- else if (charType !== lastCharType &&
196
- !(charType === 'LOWERCASE' && lastCharType === 'UPPERCASE') &&
197
- !(lastCharType === 'NUMBER') &&
198
- !(charType === 'NUMBER')) {
199
- normalizedChar = normalizedChar.toUpperCase(); //TODO: [🌺] DRY
200
- }
201
- normalizedName += normalizedChar;
202
- lastCharType = charType;
205
+ class WrappedError extends Error {
206
+ constructor(whatWasThrown) {
207
+ const tag = `[🤮]`;
208
+ console.error(tag, whatWasThrown);
209
+ super(spaceTrim$2(`
210
+ Non-Error object was thrown
211
+
212
+ Note: Look for ${tag} in the console for more details
213
+ Please report issue on ${ADMIN_EMAIL}
214
+ `));
215
+ this.name = 'WrappedError';
216
+ Object.setPrototypeOf(this, WrappedError.prototype);
203
217
  }
204
- return normalizedName;
205
218
  }
206
- /**
207
- * TODO: [🌺] Use some intermediate util splitWords
208
- */
209
219
 
210
220
  /**
211
- * Removes emojis from a string and fix whitespaces
221
+ * Helper used in catch blocks to assert that the error is an instance of `Error`
212
222
  *
213
- * @param text with emojis
214
- * @returns text without emojis
215
- * @public exported from `@promptbook/utils`
223
+ * @param whatWasThrown Any object that was thrown
224
+ * @returns Nothing if the error is an instance of `Error`
225
+ * @throws `WrappedError` or `UnexpectedError` if the error is not standard
226
+ *
227
+ * @private within the repository
216
228
  */
217
- function removeEmojis(text) {
218
- // Replace emojis (and also ZWJ sequence) with hyphens
219
- text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
220
- text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
221
- text = text.replace(/(\p{Extended_Pictographic})(\u{200D}\p{Extended_Pictographic})*/gu, '$1');
222
- text = text.replace(/\p{Extended_Pictographic}/gu, '');
223
- return text;
229
+ function assertsError(whatWasThrown) {
230
+ // Case 1: Handle error which was rethrown as `WrappedError`
231
+ if (whatWasThrown instanceof WrappedError) {
232
+ const wrappedError = whatWasThrown;
233
+ throw wrappedError;
234
+ }
235
+ // Case 2: Handle unexpected errors
236
+ if (whatWasThrown instanceof UnexpectedError) {
237
+ const unexpectedError = whatWasThrown;
238
+ throw unexpectedError;
239
+ }
240
+ // Case 3: Handle standard errors - keep them up to consumer
241
+ if (whatWasThrown instanceof Error) {
242
+ return;
243
+ }
244
+ // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
245
+ throw new WrappedError(whatWasThrown);
224
246
  }
225
247
 
226
248
  /**
227
- * Tests if given string is valid URL.
249
+ * Checks if the value is [🚉] serializable as JSON
250
+ * If not, throws an UnexpectedError with a rich error message and tracking
228
251
  *
229
- * Note: This does not check if the file exists only if the path is valid
252
+ * - Almost all primitives are serializable BUT:
253
+ * - `undefined` is not serializable
254
+ * - `NaN` is not serializable
255
+ * - Objects and arrays are serializable if all their properties are serializable
256
+ * - Functions are not serializable
257
+ * - Circular references are not serializable
258
+ * - `Date` objects are not serializable
259
+ * - `Map` and `Set` objects are not serializable
260
+ * - `RegExp` objects are not serializable
261
+ * - `Error` objects are not serializable
262
+ * - `Symbol` objects are not serializable
263
+ * - And much more...
264
+ *
265
+ * @throws UnexpectedError if the value is not serializable as JSON
230
266
  * @public exported from `@promptbook/utils`
231
267
  */
232
- function isValidFilePath(filename) {
233
- if (typeof filename !== 'string') {
234
- return false;
268
+ function checkSerializableAsJson(options) {
269
+ const { value, name, message } = options;
270
+ if (value === undefined) {
271
+ throw new UnexpectedError(`${name} is undefined`);
235
272
  }
236
- if (filename.split('\n').length > 1) {
237
- return false;
273
+ else if (value === null) {
274
+ return;
238
275
  }
239
- if (filename.split(' ').length >
240
- 5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
241
- return false;
276
+ else if (typeof value === 'boolean') {
277
+ return;
242
278
  }
243
- const filenameSlashes = filename.split('\\').join('/');
244
- // Absolute Unix path: /hello.txt
245
- if (/^(\/)/i.test(filenameSlashes)) {
246
- // console.log(filename, 'Absolute Unix path: /hello.txt');
247
- return true;
279
+ else if (typeof value === 'number' && !isNaN(value)) {
280
+ return;
248
281
  }
249
- // Absolute Windows path: /hello.txt
250
- if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
251
- // console.log(filename, 'Absolute Windows path: /hello.txt');
252
- return true;
282
+ else if (typeof value === 'string') {
283
+ return;
253
284
  }
254
- // Relative path: ./hello.txt
255
- if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
256
- // console.log(filename, 'Relative path: ./hello.txt');
257
- return true;
285
+ else if (typeof value === 'symbol') {
286
+ throw new UnexpectedError(`${name} is symbol`);
258
287
  }
259
- // Allow paths like foo/hello
260
- if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
261
- // console.log(filename, 'Allow paths like foo/hello');
262
- return true;
288
+ else if (typeof value === 'function') {
289
+ throw new UnexpectedError(`${name} is function`);
263
290
  }
264
- // Allow paths like hello.book
265
- if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
266
- // console.log(filename, 'Allow paths like hello.book');
267
- return true;
291
+ else if (typeof value === 'object' && Array.isArray(value)) {
292
+ for (let i = 0; i < value.length; i++) {
293
+ checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
294
+ }
268
295
  }
269
- return false;
270
- }
271
- /**
272
- * TODO: [🍏] Implement for MacOs
273
- */
296
+ else if (typeof value === 'object') {
297
+ if (value instanceof Date) {
298
+ throw new UnexpectedError(spaceTrim$1((block) => `
299
+ \`${name}\` is Date
274
300
 
275
- /**
276
- * Tests if given string is valid URL.
277
- *
278
- * Note: Dataurl are considered perfectly valid.
279
- * Note: There are two simmilar functions:
280
- * - `isValidUrl` which tests any URL
281
- * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
282
- *
283
- * @public exported from `@promptbook/utils`
284
- */
285
- function isValidUrl(url) {
286
- if (typeof url !== 'string') {
287
- return false;
288
- }
289
- try {
290
- if (url.startsWith('blob:')) {
291
- url = url.replace(/^blob:/, '');
301
+ Use \`string_date_iso8601\` instead
302
+
303
+ Additional message for \`${name}\`:
304
+ ${block(message || '(nothing)')}
305
+ `));
292
306
  }
293
- const urlObject = new URL(url /* because fail is handled */);
294
- if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
295
- return false;
307
+ else if (value instanceof Map) {
308
+ throw new UnexpectedError(`${name} is Map`);
296
309
  }
297
- return true;
298
- }
299
- catch (error) {
300
- return false;
301
- }
302
- }
310
+ else if (value instanceof Set) {
311
+ throw new UnexpectedError(`${name} is Set`);
312
+ }
313
+ else if (value instanceof RegExp) {
314
+ throw new UnexpectedError(`${name} is RegExp`);
315
+ }
316
+ else if (value instanceof Error) {
317
+ throw new UnexpectedError(spaceTrim$1((block) => `
318
+ \`${name}\` is unserialized Error
303
319
 
304
- const defaultDiacriticsRemovalMap = [
305
- {
320
+ Use function \`serializeError\`
321
+
322
+ Additional message for \`${name}\`:
323
+ ${block(message || '(nothing)')}
324
+
325
+ `));
326
+ }
327
+ else {
328
+ for (const [subName, subValue] of Object.entries(value)) {
329
+ if (subValue === undefined) {
330
+ // Note: undefined in object is serializable - it is just omited
331
+ continue;
332
+ }
333
+ checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
334
+ }
335
+ try {
336
+ JSON.stringify(value); // <- TODO: [0]
337
+ }
338
+ catch (error) {
339
+ assertsError(error);
340
+ throw new UnexpectedError(spaceTrim$1((block) => `
341
+ \`${name}\` is not serializable
342
+
343
+ ${block(error.stack || error.message)}
344
+
345
+ Additional message for \`${name}\`:
346
+ ${block(message || '(nothing)')}
347
+ `));
348
+ }
349
+ /*
350
+ TODO: [0] Is there some more elegant way to check circular references?
351
+ const seen = new Set();
352
+ const stack = [{ value }];
353
+ while (stack.length > 0) {
354
+ const { value } = stack.pop()!;
355
+ if (typeof value === 'object' && value !== null) {
356
+ if (seen.has(value)) {
357
+ throw new UnexpectedError(`${name} has circular reference`);
358
+ }
359
+ seen.add(value);
360
+ if (Array.isArray(value)) {
361
+ stack.push(...value.map((value) => ({ value })));
362
+ } else {
363
+ stack.push(...Object.values(value).map((value) => ({ value })));
364
+ }
365
+ }
366
+ }
367
+ */
368
+ return;
369
+ }
370
+ }
371
+ else {
372
+ throw new UnexpectedError(spaceTrim$1((block) => `
373
+ \`${name}\` is unknown type
374
+
375
+ Additional message for \`${name}\`:
376
+ ${block(message || '(nothing)')}
377
+ `));
378
+ }
379
+ }
380
+ /**
381
+ * TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
382
+ * TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
383
+ * Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
384
+ */
385
+
386
+ /**
387
+ * @@@
388
+ *
389
+ * @public exported from `@promptbook/utils`
390
+ */
391
+ function deepClone(objectValue) {
392
+ return JSON.parse(JSON.stringify(objectValue));
393
+ /*
394
+ TODO: [🧠] Is there a better implementation?
395
+ > const propertyNames = Object.getOwnPropertyNames(objectValue);
396
+ > for (const propertyName of propertyNames) {
397
+ > const value = (objectValue as really_any)[propertyName];
398
+ > if (value && typeof value === 'object') {
399
+ > deepClone(value);
400
+ > }
401
+ > }
402
+ > return Object.assign({}, objectValue);
403
+ */
404
+ }
405
+ /**
406
+ * TODO: [🧠] Is there a way how to meaningfully test this utility
407
+ */
408
+
409
+ /**
410
+ * Utility to export a JSON object from a function
411
+ *
412
+ * 1) Checks if the value is serializable as JSON
413
+ * 2) Makes a deep clone of the object
414
+ * 2) Orders the object properties
415
+ * 2) Deeply freezes the cloned object
416
+ *
417
+ * Note: This function does not mutates the given object
418
+ *
419
+ * @returns The same type of object as the input but read-only and re-ordered
420
+ * @public exported from `@promptbook/utils`
421
+ */
422
+ function exportJson(options) {
423
+ const { name, value, order, message } = options;
424
+ checkSerializableAsJson({ name, value, message });
425
+ const orderedValue =
426
+ // TODO: Fix error "Type instantiation is excessively deep and possibly infinite."
427
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
428
+ // @ts-ignore
429
+ order === undefined
430
+ ? deepClone(value)
431
+ : orderJson({
432
+ value: value,
433
+ // <- Note: checkSerializableAsJson asserts that the value is serializable as JSON
434
+ order: order,
435
+ });
436
+ $deepFreeze(orderedValue);
437
+ return orderedValue;
438
+ }
439
+ /**
440
+ * TODO: [🧠] Is there a way how to meaningfully test this utility
441
+ */
442
+
443
+ /**
444
+ * Nonce which is used for replacing things in strings
445
+ *
446
+ * @private within the repository
447
+ */
448
+ const REPLACING_NONCE = 'ptbkauk42kV2dzao34faw7FudQUHYPtW';
449
+ /**
450
+ * @@@
451
+ *
452
+ * @private within the repository
453
+ */
454
+ const RESERVED_PARAMETER_MISSING_VALUE = 'MISSING-' + REPLACING_NONCE;
455
+ /**
456
+ * @@@
457
+ *
458
+ * @private within the repository
459
+ */
460
+ const RESERVED_PARAMETER_RESTRICTED = 'RESTRICTED-' + REPLACING_NONCE;
461
+ /**
462
+ * The names of the parameters that are reserved for special purposes
463
+ *
464
+ * @public exported from `@promptbook/core`
465
+ */
466
+ const RESERVED_PARAMETER_NAMES = exportJson({
467
+ name: 'RESERVED_PARAMETER_NAMES',
468
+ message: `The names of the parameters that are reserved for special purposes`,
469
+ value: [
470
+ 'content',
471
+ 'context',
472
+ 'knowledge',
473
+ 'examples',
474
+ 'modelName',
475
+ 'currentDate',
476
+ // <- TODO: list here all command names
477
+ // <- TODO: Add more like 'date', 'modelName',...
478
+ // <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter
479
+ ],
480
+ });
481
+ /**
482
+ * Note: [💞] Ignore a discrepancy between file name and entity name
483
+ */
484
+
485
+ /**
486
+ * @@@
487
+ *
488
+ * @param text @@@
489
+ * @param _isFirstLetterCapital @@@
490
+ * @returns @@@
491
+ * @example 'helloWorld'
492
+ * @example 'iLovePromptbook'
493
+ * @public exported from `@promptbook/utils`
494
+ */
495
+ function normalizeTo_camelCase(text, _isFirstLetterCapital = false) {
496
+ let charType;
497
+ let lastCharType = null;
498
+ let normalizedName = '';
499
+ for (const char of text) {
500
+ let normalizedChar;
501
+ if (/^[a-z]$/.test(char)) {
502
+ charType = 'LOWERCASE';
503
+ normalizedChar = char;
504
+ }
505
+ else if (/^[A-Z]$/.test(char)) {
506
+ charType = 'UPPERCASE';
507
+ normalizedChar = char.toLowerCase();
508
+ }
509
+ else if (/^[0-9]$/.test(char)) {
510
+ charType = 'NUMBER';
511
+ normalizedChar = char;
512
+ }
513
+ else {
514
+ charType = 'OTHER';
515
+ normalizedChar = '';
516
+ }
517
+ if (!lastCharType) {
518
+ if (_isFirstLetterCapital) {
519
+ normalizedChar = normalizedChar.toUpperCase(); //TODO: DRY
520
+ }
521
+ }
522
+ else if (charType !== lastCharType &&
523
+ !(charType === 'LOWERCASE' && lastCharType === 'UPPERCASE') &&
524
+ !(lastCharType === 'NUMBER') &&
525
+ !(charType === 'NUMBER')) {
526
+ normalizedChar = normalizedChar.toUpperCase(); //TODO: [🌺] DRY
527
+ }
528
+ normalizedName += normalizedChar;
529
+ lastCharType = charType;
530
+ }
531
+ return normalizedName;
532
+ }
533
+ /**
534
+ * TODO: [🌺] Use some intermediate util splitWords
535
+ */
536
+
537
+ /**
538
+ * Removes emojis from a string and fix whitespaces
539
+ *
540
+ * @param text with emojis
541
+ * @returns text without emojis
542
+ * @public exported from `@promptbook/utils`
543
+ */
544
+ function removeEmojis(text) {
545
+ // Replace emojis (and also ZWJ sequence) with hyphens
546
+ text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
547
+ text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
548
+ text = text.replace(/(\p{Extended_Pictographic})(\u{200D}\p{Extended_Pictographic})*/gu, '$1');
549
+ text = text.replace(/\p{Extended_Pictographic}/gu, '');
550
+ return text;
551
+ }
552
+
553
+ /**
554
+ * Tests if given string is valid URL.
555
+ *
556
+ * Note: This does not check if the file exists only if the path is valid
557
+ * @public exported from `@promptbook/utils`
558
+ */
559
+ function isValidFilePath(filename) {
560
+ if (typeof filename !== 'string') {
561
+ return false;
562
+ }
563
+ if (filename.split('\n').length > 1) {
564
+ return false;
565
+ }
566
+ if (filename.split(' ').length >
567
+ 5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
568
+ return false;
569
+ }
570
+ const filenameSlashes = filename.split('\\').join('/');
571
+ // Absolute Unix path: /hello.txt
572
+ if (/^(\/)/i.test(filenameSlashes)) {
573
+ // console.log(filename, 'Absolute Unix path: /hello.txt');
574
+ return true;
575
+ }
576
+ // Absolute Windows path: /hello.txt
577
+ if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
578
+ // console.log(filename, 'Absolute Windows path: /hello.txt');
579
+ return true;
580
+ }
581
+ // Relative path: ./hello.txt
582
+ if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
583
+ // console.log(filename, 'Relative path: ./hello.txt');
584
+ return true;
585
+ }
586
+ // Allow paths like foo/hello
587
+ if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
588
+ // console.log(filename, 'Allow paths like foo/hello');
589
+ return true;
590
+ }
591
+ // Allow paths like hello.book
592
+ if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
593
+ // console.log(filename, 'Allow paths like hello.book');
594
+ return true;
595
+ }
596
+ return false;
597
+ }
598
+ /**
599
+ * TODO: [🍏] Implement for MacOs
600
+ */
601
+
602
+ /**
603
+ * Tests if given string is valid URL.
604
+ *
605
+ * Note: Dataurl are considered perfectly valid.
606
+ * Note: There are two simmilar functions:
607
+ * - `isValidUrl` which tests any URL
608
+ * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
609
+ *
610
+ * @public exported from `@promptbook/utils`
611
+ */
612
+ function isValidUrl(url) {
613
+ if (typeof url !== 'string') {
614
+ return false;
615
+ }
616
+ try {
617
+ if (url.startsWith('blob:')) {
618
+ url = url.replace(/^blob:/, '');
619
+ }
620
+ const urlObject = new URL(url /* because fail is handled */);
621
+ if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
622
+ return false;
623
+ }
624
+ return true;
625
+ }
626
+ catch (error) {
627
+ return false;
628
+ }
629
+ }
630
+
631
+ const defaultDiacriticsRemovalMap = [
632
+ {
306
633
  base: 'A',
307
634
  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',
308
635
  },
@@ -647,6 +974,13 @@ function titleToName(value) {
647
974
  function renderPromptbookMermaid(pipelineJson, options) {
648
975
  const { linkTask = () => null } = options || {};
649
976
  const parameterNameToTaskName = (parameterName) => {
977
+ if (parameterName === 'knowledge') {
978
+ return 'knowledge';
979
+ // <- TODO: !!!! Check that this works
980
+ }
981
+ else if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
982
+ return 'reserved';
983
+ }
650
984
  const parameter = pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
651
985
  if (!parameter) {
652
986
  throw new UnexpectedError(`Could not find {${parameterName}}`);
@@ -671,6 +1005,8 @@ function renderPromptbookMermaid(pipelineJson, options) {
671
1005
  direction TB
672
1006
 
673
1007
  input((Input)):::input
1008
+ other((Other)):::other
1009
+ knowledge((Knowledgebase)):::knowledge
674
1010
  ${block(pipelineJson.tasks
675
1011
  .flatMap(({ title, dependentParameterNames, resultingParameterName }) => [
676
1012
  `${parameterNameToTaskName(resultingParameterName)}("${title}")`,
@@ -799,753 +1135,426 @@ class ExpectError extends Error {
799
1135
  }
800
1136
 
801
1137
  /**
802
- * This error indicates that the promptbook can not retrieve knowledge from external sources
803
- *
804
- * @public exported from `@promptbook/core`
805
- */
806
- class KnowledgeScrapeError extends Error {
807
- constructor(message) {
808
- super(message);
809
- this.name = 'KnowledgeScrapeError';
810
- Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
811
- }
812
- }
813
-
814
- /**
815
- * This error type indicates that some limit was reached
816
- *
817
- * @public exported from `@promptbook/core`
818
- */
819
- class LimitReachedError extends Error {
820
- constructor(message) {
821
- super(message);
822
- this.name = 'LimitReachedError';
823
- Object.setPrototypeOf(this, LimitReachedError.prototype);
824
- }
825
- }
826
-
827
- /**
828
- * This error type indicates that some tools are missing for pipeline execution or preparation
829
- *
830
- * @public exported from `@promptbook/core`
831
- */
832
- class MissingToolsError extends Error {
833
- constructor(message) {
834
- super(spaceTrim$2((block) => `
835
- ${block(message)}
836
-
837
- Note: You have probbably forgot to provide some tools for pipeline execution or preparation
838
-
839
- `));
840
- this.name = 'MissingToolsError';
841
- Object.setPrototypeOf(this, MissingToolsError.prototype);
842
- }
843
- }
844
-
845
- /**
846
- * This error indicates that promptbook not found in the collection
847
- *
848
- * @public exported from `@promptbook/core`
849
- */
850
- class NotFoundError extends Error {
851
- constructor(message) {
852
- super(message);
853
- this.name = 'NotFoundError';
854
- Object.setPrototypeOf(this, NotFoundError.prototype);
855
- }
856
- }
857
-
858
- /**
859
- * This error type indicates that some part of the code is not implemented yet
860
- *
861
- * @public exported from `@promptbook/core`
862
- */
863
- class NotYetImplementedError extends Error {
864
- constructor(message) {
865
- super(spaceTrim$2((block) => `
866
- ${block(message)}
867
-
868
- Note: This feature is not implemented yet but it will be soon.
869
-
870
- If you want speed up the implementation or just read more, look here:
871
- https://github.com/webgptorg/promptbook
872
-
873
- Or contact us on pavol@ptbk.io
874
-
875
- `));
876
- this.name = 'NotYetImplementedError';
877
- Object.setPrototypeOf(this, NotYetImplementedError.prototype);
878
- }
879
- }
880
-
881
- /**
882
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
883
- *
884
- * @public exported from `@promptbook/core`
885
- */
886
- class ParseError extends Error {
887
- constructor(message) {
888
- super(message);
889
- this.name = 'ParseError';
890
- Object.setPrototypeOf(this, ParseError.prototype);
891
- }
892
- }
893
- /**
894
- * TODO: Maybe split `ParseError` and `ApplyError`
895
- */
896
-
897
- /**
898
- * Generates random token
899
- *
900
- * Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
901
- *
902
- * @private internal helper function
903
- * @returns secure random token
904
- */
905
- function $randomToken(randomness) {
906
- return randomBytes(randomness).toString('hex');
907
- }
908
- /**
909
- * TODO: Maybe use nanoid instead https://github.com/ai/nanoid
910
- */
911
-
912
- /**
913
- * This error indicates errors during the execution of the pipeline
914
- *
915
- * @public exported from `@promptbook/core`
916
- */
917
- class PipelineExecutionError extends Error {
918
- constructor(message) {
919
- // Added id parameter
920
- super(message);
921
- this.name = 'PipelineExecutionError';
922
- // TODO: [🐙] DRY - Maybe $randomId
923
- this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid simmilar char conflicts */)}`;
924
- Object.setPrototypeOf(this, PipelineExecutionError.prototype);
925
- }
926
- }
927
- /**
928
- * TODO: [🧠][🌂] Add id to all errors
929
- */
930
-
931
- /**
932
- * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
933
- *
934
- * @public exported from `@promptbook/core`
935
- */
936
- class PipelineLogicError extends Error {
937
- constructor(message) {
938
- super(message);
939
- this.name = 'PipelineLogicError';
940
- Object.setPrototypeOf(this, PipelineLogicError.prototype);
941
- }
942
- }
943
-
944
- /**
945
- * This error indicates errors in referencing promptbooks between each other
1138
+ * This error indicates that the promptbook can not retrieve knowledge from external sources
946
1139
  *
947
1140
  * @public exported from `@promptbook/core`
948
1141
  */
949
- class PipelineUrlError extends Error {
1142
+ class KnowledgeScrapeError extends Error {
950
1143
  constructor(message) {
951
1144
  super(message);
952
- this.name = 'PipelineUrlError';
953
- Object.setPrototypeOf(this, PipelineUrlError.prototype);
1145
+ this.name = 'KnowledgeScrapeError';
1146
+ Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
954
1147
  }
955
1148
  }
956
1149
 
957
1150
  /**
958
- * Error thrown when a fetch request fails
1151
+ * This error type indicates that some limit was reached
959
1152
  *
960
1153
  * @public exported from `@promptbook/core`
961
1154
  */
962
- class PromptbookFetchError extends Error {
1155
+ class LimitReachedError extends Error {
963
1156
  constructor(message) {
964
1157
  super(message);
965
- this.name = 'PromptbookFetchError';
966
- Object.setPrototypeOf(this, PromptbookFetchError.prototype);
1158
+ this.name = 'LimitReachedError';
1159
+ Object.setPrototypeOf(this, LimitReachedError.prototype);
967
1160
  }
968
1161
  }
969
1162
 
970
1163
  /**
971
- * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
1164
+ * This error type indicates that some tools are missing for pipeline execution or preparation
972
1165
  *
973
1166
  * @public exported from `@promptbook/core`
974
1167
  */
975
- class WrappedError extends Error {
976
- constructor(whatWasThrown) {
977
- const tag = `[🤮]`;
978
- console.error(tag, whatWasThrown);
979
- super(spaceTrim$2(`
980
- Non-Error object was thrown
1168
+ class MissingToolsError extends Error {
1169
+ constructor(message) {
1170
+ super(spaceTrim$2((block) => `
1171
+ ${block(message)}
981
1172
 
982
- Note: Look for ${tag} in the console for more details
983
- Please report issue on ${ADMIN_EMAIL}
984
- `));
985
- this.name = 'WrappedError';
986
- Object.setPrototypeOf(this, WrappedError.prototype);
1173
+ Note: You have probbably forgot to provide some tools for pipeline execution or preparation
1174
+
1175
+ `));
1176
+ this.name = 'MissingToolsError';
1177
+ Object.setPrototypeOf(this, MissingToolsError.prototype);
987
1178
  }
988
1179
  }
989
1180
 
990
1181
  /**
991
- * Index of all custom errors
1182
+ * This error indicates that promptbook not found in the collection
992
1183
  *
993
1184
  * @public exported from `@promptbook/core`
994
1185
  */
995
- const PROMPTBOOK_ERRORS = {
996
- AbstractFormatError,
997
- CsvFormatError,
998
- CollectionError,
999
- EnvironmentMismatchError,
1000
- ExpectError,
1001
- KnowledgeScrapeError,
1002
- LimitReachedError,
1003
- MissingToolsError,
1004
- NotFoundError,
1005
- NotYetImplementedError,
1006
- ParseError,
1007
- PipelineExecutionError,
1008
- PipelineLogicError,
1009
- PipelineUrlError,
1010
- AuthenticationError,
1011
- PromptbookFetchError,
1012
- UnexpectedError,
1013
- WrappedError,
1014
- // TODO: [🪑]> VersionMismatchError,
1015
- };
1016
- /**
1017
- * Index of all javascript errors
1018
- *
1019
- * @private for internal usage
1020
- */
1021
- const COMMON_JAVASCRIPT_ERRORS = {
1022
- Error,
1023
- EvalError,
1024
- RangeError,
1025
- ReferenceError,
1026
- SyntaxError,
1027
- TypeError,
1028
- URIError,
1029
- AggregateError,
1030
- /*
1031
- Note: Not widely supported
1032
- > InternalError,
1033
- > ModuleError,
1034
- > HeapError,
1035
- > WebAssemblyCompileError,
1036
- > WebAssemblyRuntimeError,
1037
- */
1038
- };
1039
- /**
1040
- * Index of all errors
1041
- *
1042
- * @private for internal usage
1043
- */
1044
- const ALL_ERRORS = {
1045
- ...PROMPTBOOK_ERRORS,
1046
- ...COMMON_JAVASCRIPT_ERRORS,
1047
- };
1048
- /**
1049
- * Note: [💞] Ignore a discrepancy between file name and entity name
1050
- */
1051
-
1052
- /**
1053
- * Deserializes the error object
1054
- *
1055
- * @public exported from `@promptbook/utils`
1056
- */
1057
- function deserializeError(error) {
1058
- const { name, stack, id } = error; // Added id
1059
- let { message } = error;
1060
- let ErrorClass = ALL_ERRORS[error.name];
1061
- if (ErrorClass === undefined) {
1062
- ErrorClass = Error;
1063
- message = `${name}: ${message}`;
1064
- }
1065
- if (stack !== undefined && stack !== '') {
1066
- message = spaceTrim$1((block) => `
1067
- ${block(message)}
1068
-
1069
- Original stack trace:
1070
- ${block(stack || '')}
1071
- `);
1186
+ class NotFoundError extends Error {
1187
+ constructor(message) {
1188
+ super(message);
1189
+ this.name = 'NotFoundError';
1190
+ Object.setPrototypeOf(this, NotFoundError.prototype);
1072
1191
  }
1073
- const deserializedError = new ErrorClass(message);
1074
- deserializedError.id = id; // Assign id to the error object
1075
- return deserializedError;
1076
1192
  }
1077
1193
 
1078
1194
  /**
1079
- * Serializes an error into a [🚉] JSON-serializable object
1195
+ * This error type indicates that some part of the code is not implemented yet
1080
1196
  *
1081
- * @public exported from `@promptbook/utils`
1197
+ * @public exported from `@promptbook/core`
1082
1198
  */
1083
- function serializeError(error) {
1084
- const { name, message, stack } = error;
1085
- const { id } = error;
1086
- if (!Object.keys(ALL_ERRORS).includes(name)) {
1087
- console.error(spaceTrim$1((block) => `
1088
-
1089
- Cannot serialize error with name "${name}"
1199
+ class NotYetImplementedError extends Error {
1200
+ constructor(message) {
1201
+ super(spaceTrim$2((block) => `
1202
+ ${block(message)}
1090
1203
 
1091
- Authors of Promptbook probably forgot to add this error into the list of errors:
1092
- https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
1204
+ Note: This feature is not implemented yet but it will be soon.
1093
1205
 
1206
+ If you want speed up the implementation or just read more, look here:
1207
+ https://github.com/webgptorg/promptbook
1094
1208
 
1095
- ${block(stack || message)}
1209
+ Or contact us on pavol@ptbk.io
1096
1210
 
1097
1211
  `));
1212
+ this.name = 'NotYetImplementedError';
1213
+ Object.setPrototypeOf(this, NotYetImplementedError.prototype);
1098
1214
  }
1099
- return {
1100
- name: name,
1101
- message,
1102
- stack,
1103
- id, // Include id in the serialized object
1104
- };
1105
1215
  }
1106
1216
 
1107
1217
  /**
1108
- * Async version of Array.forEach
1218
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
1109
1219
  *
1110
- * @param array - Array to iterate over
1111
- * @param options - Options for the function
1112
- * @param callbackfunction - Function to call for each item
1113
- * @public exported from `@promptbook/utils`
1114
- * @deprecated [🪂] Use queues instead
1220
+ * @public exported from `@promptbook/core`
1115
1221
  */
1116
- async function forEachAsync(array, options, callbackfunction) {
1117
- const { maxParallelCount = Infinity } = options;
1118
- let index = 0;
1119
- let runningTasks = [];
1120
- const tasks = [];
1121
- for (const item of array) {
1122
- const currentIndex = index++;
1123
- const task = callbackfunction(item, currentIndex, array);
1124
- tasks.push(task);
1125
- runningTasks.push(task);
1126
- /* not await */ Promise.resolve(task).then(() => {
1127
- runningTasks = runningTasks.filter((t) => t !== task);
1128
- });
1129
- if (maxParallelCount < runningTasks.length) {
1130
- await Promise.race(runningTasks);
1131
- }
1222
+ class ParseError extends Error {
1223
+ constructor(message) {
1224
+ super(message);
1225
+ this.name = 'ParseError';
1226
+ Object.setPrototypeOf(this, ParseError.prototype);
1132
1227
  }
1133
- await Promise.all(tasks);
1134
1228
  }
1135
-
1136
1229
  /**
1137
- * Helper used in catch blocks to assert that the error is an instance of `Error`
1138
- *
1139
- * @param whatWasThrown Any object that was thrown
1140
- * @returns Nothing if the error is an instance of `Error`
1141
- * @throws `WrappedError` or `UnexpectedError` if the error is not standard
1142
- *
1143
- * @private within the repository
1230
+ * TODO: Maybe split `ParseError` and `ApplyError`
1144
1231
  */
1145
- function assertsError(whatWasThrown) {
1146
- // Case 1: Handle error which was rethrown as `WrappedError`
1147
- if (whatWasThrown instanceof WrappedError) {
1148
- const wrappedError = whatWasThrown;
1149
- throw wrappedError;
1150
- }
1151
- // Case 2: Handle unexpected errors
1152
- if (whatWasThrown instanceof UnexpectedError) {
1153
- const unexpectedError = whatWasThrown;
1154
- throw unexpectedError;
1155
- }
1156
- // Case 3: Handle standard errors - keep them up to consumer
1157
- if (whatWasThrown instanceof Error) {
1158
- return;
1159
- }
1160
- // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
1161
- throw new WrappedError(whatWasThrown);
1162
- }
1163
1232
 
1164
1233
  /**
1165
- * Function to check if a string is valid CSV
1234
+ * Generates random token
1166
1235
  *
1167
- * @param value The string to check
1168
- * @returns True if the string is a valid CSV string, false otherwise
1236
+ * Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
1169
1237
  *
1170
- * @public exported from `@promptbook/utils`
1238
+ * @private internal helper function
1239
+ * @returns secure random token
1171
1240
  */
1172
- function isValidCsvString(value) {
1173
- try {
1174
- // A simple check for CSV format: at least one comma and no invalid characters
1175
- if (value.includes(',') && /^[\w\s,"']+$/.test(value)) {
1176
- return true;
1177
- }
1178
- return false;
1179
- }
1180
- catch (error) {
1181
- assertsError(error);
1182
- return false;
1183
- }
1241
+ function $randomToken(randomness) {
1242
+ return randomBytes(randomness).toString('hex');
1184
1243
  }
1244
+ /**
1245
+ * TODO: Maybe use nanoid instead https://github.com/ai/nanoid
1246
+ */
1185
1247
 
1186
1248
  /**
1187
- * Function isValidJsonString will tell you if the string is valid JSON or not
1188
- *
1189
- * @param value The string to check
1190
- * @returns True if the string is a valid JSON string, false otherwise
1249
+ * This error indicates errors during the execution of the pipeline
1191
1250
  *
1192
- * @public exported from `@promptbook/utils`
1251
+ * @public exported from `@promptbook/core`
1193
1252
  */
1194
- function isValidJsonString(value /* <- [👨‍⚖️] */) {
1195
- try {
1196
- JSON.parse(value);
1197
- return true;
1198
- }
1199
- catch (error) {
1200
- assertsError(error);
1201
- if (error.message.includes('Unexpected token')) {
1202
- return false;
1203
- }
1204
- return false;
1253
+ class PipelineExecutionError extends Error {
1254
+ constructor(message) {
1255
+ // Added id parameter
1256
+ super(message);
1257
+ this.name = 'PipelineExecutionError';
1258
+ // TODO: [🐙] DRY - Maybe $randomId
1259
+ this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid simmilar char conflicts */)}`;
1260
+ Object.setPrototypeOf(this, PipelineExecutionError.prototype);
1205
1261
  }
1206
1262
  }
1263
+ /**
1264
+ * TODO: [🧠][🌂] Add id to all errors
1265
+ */
1207
1266
 
1208
1267
  /**
1209
- * Converts a JavaScript Object Notation (JSON) string into an object.
1210
- *
1211
- * Note: This is wrapper around `JSON.parse()` with better error and type handling
1268
+ * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
1212
1269
  *
1213
- * @public exported from `@promptbook/utils`
1270
+ * @public exported from `@promptbook/core`
1214
1271
  */
1215
- function jsonParse(value) {
1216
- if (value === undefined) {
1217
- throw new Error(`Can not parse JSON from undefined value.`);
1218
- }
1219
- else if (typeof value !== 'string') {
1220
- console.error('Can not parse JSON from non-string value.', { text: value });
1221
- throw new Error(spaceTrim$1(`
1222
- Can not parse JSON from non-string value.
1223
-
1224
- The value type: ${typeof value}
1225
- See more in console.
1226
- `));
1227
- }
1228
- try {
1229
- return JSON.parse(value);
1230
- }
1231
- catch (error) {
1232
- if (!(error instanceof Error)) {
1233
- throw error;
1234
- }
1235
- throw new Error(spaceTrim$1((block) => `
1236
- ${block(error.message)}
1237
-
1238
- The JSON text:
1239
- ${block(value)}
1240
- `));
1272
+ class PipelineLogicError extends Error {
1273
+ constructor(message) {
1274
+ super(message);
1275
+ this.name = 'PipelineLogicError';
1276
+ Object.setPrototypeOf(this, PipelineLogicError.prototype);
1241
1277
  }
1242
1278
  }
1243
- /**
1244
- * TODO: !!!! Use in Promptbook.studio
1245
- */
1246
1279
 
1247
1280
  /**
1248
- * Function to check if a string is valid XML
1249
- *
1250
- * @param value
1251
- * @returns True if the string is a valid XML string, false otherwise
1281
+ * This error indicates errors in referencing promptbooks between each other
1252
1282
  *
1253
- * @public exported from `@promptbook/utils`
1283
+ * @public exported from `@promptbook/core`
1254
1284
  */
1255
- function isValidXmlString(value) {
1256
- try {
1257
- const parser = new DOMParser();
1258
- const parsedDocument = parser.parseFromString(value, 'application/xml');
1259
- const parserError = parsedDocument.getElementsByTagName('parsererror');
1260
- if (parserError.length > 0) {
1261
- return false;
1262
- }
1263
- return true;
1264
- }
1265
- catch (error) {
1266
- assertsError(error);
1267
- return false;
1285
+ class PipelineUrlError extends Error {
1286
+ constructor(message) {
1287
+ super(message);
1288
+ this.name = 'PipelineUrlError';
1289
+ Object.setPrototypeOf(this, PipelineUrlError.prototype);
1268
1290
  }
1269
1291
  }
1270
1292
 
1271
1293
  /**
1272
- * Orders JSON object by keys
1294
+ * Error thrown when a fetch request fails
1273
1295
  *
1274
- * @returns The same type of object as the input re-ordered
1275
- * @public exported from `@promptbook/utils`
1296
+ * @public exported from `@promptbook/core`
1276
1297
  */
1277
- function orderJson(options) {
1278
- const { value, order } = options;
1279
- const orderedValue = {
1280
- ...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
1281
- ...value,
1282
- };
1283
- return orderedValue;
1298
+ class PromptbookFetchError extends Error {
1299
+ constructor(message) {
1300
+ super(message);
1301
+ this.name = 'PromptbookFetchError';
1302
+ Object.setPrototypeOf(this, PromptbookFetchError.prototype);
1303
+ }
1284
1304
  }
1285
1305
 
1286
1306
  /**
1287
- * Freezes the given object and all its nested objects recursively
1307
+ * Index of all custom errors
1288
1308
  *
1289
- * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
1290
- * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
1309
+ * @public exported from `@promptbook/core`
1310
+ */
1311
+ const PROMPTBOOK_ERRORS = {
1312
+ AbstractFormatError,
1313
+ CsvFormatError,
1314
+ CollectionError,
1315
+ EnvironmentMismatchError,
1316
+ ExpectError,
1317
+ KnowledgeScrapeError,
1318
+ LimitReachedError,
1319
+ MissingToolsError,
1320
+ NotFoundError,
1321
+ NotYetImplementedError,
1322
+ ParseError,
1323
+ PipelineExecutionError,
1324
+ PipelineLogicError,
1325
+ PipelineUrlError,
1326
+ AuthenticationError,
1327
+ PromptbookFetchError,
1328
+ UnexpectedError,
1329
+ WrappedError,
1330
+ // TODO: [🪑]> VersionMismatchError,
1331
+ };
1332
+ /**
1333
+ * Index of all javascript errors
1291
1334
  *
1292
- * @returns The same object as the input, but deeply frozen
1293
- * @public exported from `@promptbook/utils`
1335
+ * @private for internal usage
1294
1336
  */
1295
- function $deepFreeze(objectValue) {
1296
- if (Array.isArray(objectValue)) {
1297
- return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
1298
- }
1299
- const propertyNames = Object.getOwnPropertyNames(objectValue);
1300
- for (const propertyName of propertyNames) {
1301
- const value = objectValue[propertyName];
1302
- if (value && typeof value === 'object') {
1303
- $deepFreeze(value);
1304
- }
1305
- }
1306
- Object.freeze(objectValue);
1307
- return objectValue;
1308
- }
1337
+ const COMMON_JAVASCRIPT_ERRORS = {
1338
+ Error,
1339
+ EvalError,
1340
+ RangeError,
1341
+ ReferenceError,
1342
+ SyntaxError,
1343
+ TypeError,
1344
+ URIError,
1345
+ AggregateError,
1346
+ /*
1347
+ Note: Not widely supported
1348
+ > InternalError,
1349
+ > ModuleError,
1350
+ > HeapError,
1351
+ > WebAssemblyCompileError,
1352
+ > WebAssemblyRuntimeError,
1353
+ */
1354
+ };
1309
1355
  /**
1310
- * TODO: [🧠] Is there a way how to meaningfully test this utility
1356
+ * Index of all errors
1357
+ *
1358
+ * @private for internal usage
1359
+ */
1360
+ const ALL_ERRORS = {
1361
+ ...PROMPTBOOK_ERRORS,
1362
+ ...COMMON_JAVASCRIPT_ERRORS,
1363
+ };
1364
+ /**
1365
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1311
1366
  */
1312
1367
 
1313
1368
  /**
1314
- * Checks if the value is [🚉] serializable as JSON
1315
- * If not, throws an UnexpectedError with a rich error message and tracking
1316
- *
1317
- * - Almost all primitives are serializable BUT:
1318
- * - `undefined` is not serializable
1319
- * - `NaN` is not serializable
1320
- * - Objects and arrays are serializable if all their properties are serializable
1321
- * - Functions are not serializable
1322
- * - Circular references are not serializable
1323
- * - `Date` objects are not serializable
1324
- * - `Map` and `Set` objects are not serializable
1325
- * - `RegExp` objects are not serializable
1326
- * - `Error` objects are not serializable
1327
- * - `Symbol` objects are not serializable
1328
- * - And much more...
1369
+ * Deserializes the error object
1329
1370
  *
1330
- * @throws UnexpectedError if the value is not serializable as JSON
1331
1371
  * @public exported from `@promptbook/utils`
1332
1372
  */
1333
- function checkSerializableAsJson(options) {
1334
- const { value, name, message } = options;
1335
- if (value === undefined) {
1336
- throw new UnexpectedError(`${name} is undefined`);
1337
- }
1338
- else if (value === null) {
1339
- return;
1340
- }
1341
- else if (typeof value === 'boolean') {
1342
- return;
1343
- }
1344
- else if (typeof value === 'number' && !isNaN(value)) {
1345
- return;
1346
- }
1347
- else if (typeof value === 'string') {
1348
- return;
1349
- }
1350
- else if (typeof value === 'symbol') {
1351
- throw new UnexpectedError(`${name} is symbol`);
1352
- }
1353
- else if (typeof value === 'function') {
1354
- throw new UnexpectedError(`${name} is function`);
1355
- }
1356
- else if (typeof value === 'object' && Array.isArray(value)) {
1357
- for (let i = 0; i < value.length; i++) {
1358
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
1359
- }
1373
+ function deserializeError(error) {
1374
+ const { name, stack, id } = error; // Added id
1375
+ let { message } = error;
1376
+ let ErrorClass = ALL_ERRORS[error.name];
1377
+ if (ErrorClass === undefined) {
1378
+ ErrorClass = Error;
1379
+ message = `${name}: ${message}`;
1360
1380
  }
1361
- else if (typeof value === 'object') {
1362
- if (value instanceof Date) {
1363
- throw new UnexpectedError(spaceTrim$1((block) => `
1364
- \`${name}\` is Date
1365
-
1366
- Use \`string_date_iso8601\` instead
1381
+ if (stack !== undefined && stack !== '') {
1382
+ message = spaceTrim$1((block) => `
1383
+ ${block(message)}
1367
1384
 
1368
- Additional message for \`${name}\`:
1369
- ${block(message || '(nothing)')}
1370
- `));
1371
- }
1372
- else if (value instanceof Map) {
1373
- throw new UnexpectedError(`${name} is Map`);
1374
- }
1375
- else if (value instanceof Set) {
1376
- throw new UnexpectedError(`${name} is Set`);
1377
- }
1378
- else if (value instanceof RegExp) {
1379
- throw new UnexpectedError(`${name} is RegExp`);
1380
- }
1381
- else if (value instanceof Error) {
1382
- throw new UnexpectedError(spaceTrim$1((block) => `
1383
- \`${name}\` is unserialized Error
1385
+ Original stack trace:
1386
+ ${block(stack || '')}
1387
+ `);
1388
+ }
1389
+ const deserializedError = new ErrorClass(message);
1390
+ deserializedError.id = id; // Assign id to the error object
1391
+ return deserializedError;
1392
+ }
1384
1393
 
1385
- Use function \`serializeError\`
1394
+ /**
1395
+ * Serializes an error into a [🚉] JSON-serializable object
1396
+ *
1397
+ * @public exported from `@promptbook/utils`
1398
+ */
1399
+ function serializeError(error) {
1400
+ const { name, message, stack } = error;
1401
+ const { id } = error;
1402
+ if (!Object.keys(ALL_ERRORS).includes(name)) {
1403
+ console.error(spaceTrim$1((block) => `
1386
1404
 
1387
- Additional message for \`${name}\`:
1388
- ${block(message || '(nothing)')}
1405
+ Cannot serialize error with name "${name}"
1389
1406
 
1390
- `));
1391
- }
1392
- else {
1393
- for (const [subName, subValue] of Object.entries(value)) {
1394
- if (subValue === undefined) {
1395
- // Note: undefined in object is serializable - it is just omited
1396
- continue;
1397
- }
1398
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
1399
- }
1400
- try {
1401
- JSON.stringify(value); // <- TODO: [0]
1402
- }
1403
- catch (error) {
1404
- assertsError(error);
1405
- throw new UnexpectedError(spaceTrim$1((block) => `
1406
- \`${name}\` is not serializable
1407
+ Authors of Promptbook probably forgot to add this error into the list of errors:
1408
+ https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
1407
1409
 
1408
- ${block(error.stack || error.message)}
1409
1410
 
1410
- Additional message for \`${name}\`:
1411
- ${block(message || '(nothing)')}
1412
- `));
1413
- }
1414
- /*
1415
- TODO: [0] Is there some more elegant way to check circular references?
1416
- const seen = new Set();
1417
- const stack = [{ value }];
1418
- while (stack.length > 0) {
1419
- const { value } = stack.pop()!;
1420
- if (typeof value === 'object' && value !== null) {
1421
- if (seen.has(value)) {
1422
- throw new UnexpectedError(`${name} has circular reference`);
1423
- }
1424
- seen.add(value);
1425
- if (Array.isArray(value)) {
1426
- stack.push(...value.map((value) => ({ value })));
1427
- } else {
1428
- stack.push(...Object.values(value).map((value) => ({ value })));
1429
- }
1430
- }
1431
- }
1432
- */
1433
- return;
1434
- }
1435
- }
1436
- else {
1437
- throw new UnexpectedError(spaceTrim$1((block) => `
1438
- \`${name}\` is unknown type
1411
+ ${block(stack || message)}
1439
1412
 
1440
- Additional message for \`${name}\`:
1441
- ${block(message || '(nothing)')}
1442
1413
  `));
1443
1414
  }
1415
+ return {
1416
+ name: name,
1417
+ message,
1418
+ stack,
1419
+ id, // Include id in the serialized object
1420
+ };
1444
1421
  }
1445
- /**
1446
- * TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1447
- * TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1448
- * Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1449
- */
1450
1422
 
1451
1423
  /**
1452
- * @@@
1424
+ * Async version of Array.forEach
1453
1425
  *
1426
+ * @param array - Array to iterate over
1427
+ * @param options - Options for the function
1428
+ * @param callbackfunction - Function to call for each item
1454
1429
  * @public exported from `@promptbook/utils`
1430
+ * @deprecated [🪂] Use queues instead
1455
1431
  */
1456
- function deepClone(objectValue) {
1457
- return JSON.parse(JSON.stringify(objectValue));
1458
- /*
1459
- TODO: [🧠] Is there a better implementation?
1460
- > const propertyNames = Object.getOwnPropertyNames(objectValue);
1461
- > for (const propertyName of propertyNames) {
1462
- > const value = (objectValue as really_any)[propertyName];
1463
- > if (value && typeof value === 'object') {
1464
- > deepClone(value);
1465
- > }
1466
- > }
1467
- > return Object.assign({}, objectValue);
1468
- */
1432
+ async function forEachAsync(array, options, callbackfunction) {
1433
+ const { maxParallelCount = Infinity } = options;
1434
+ let index = 0;
1435
+ let runningTasks = [];
1436
+ const tasks = [];
1437
+ for (const item of array) {
1438
+ const currentIndex = index++;
1439
+ const task = callbackfunction(item, currentIndex, array);
1440
+ tasks.push(task);
1441
+ runningTasks.push(task);
1442
+ /* not await */ Promise.resolve(task).then(() => {
1443
+ runningTasks = runningTasks.filter((t) => t !== task);
1444
+ });
1445
+ if (maxParallelCount < runningTasks.length) {
1446
+ await Promise.race(runningTasks);
1447
+ }
1448
+ }
1449
+ await Promise.all(tasks);
1469
1450
  }
1470
- /**
1471
- * TODO: [🧠] Is there a way how to meaningfully test this utility
1472
- */
1473
1451
 
1474
1452
  /**
1475
- * Utility to export a JSON object from a function
1476
- *
1477
- * 1) Checks if the value is serializable as JSON
1478
- * 2) Makes a deep clone of the object
1479
- * 2) Orders the object properties
1480
- * 2) Deeply freezes the cloned object
1453
+ * Function to check if a string is valid CSV
1481
1454
  *
1482
- * Note: This function does not mutates the given object
1455
+ * @param value The string to check
1456
+ * @returns True if the string is a valid CSV string, false otherwise
1483
1457
  *
1484
- * @returns The same type of object as the input but read-only and re-ordered
1485
1458
  * @public exported from `@promptbook/utils`
1486
1459
  */
1487
- function exportJson(options) {
1488
- const { name, value, order, message } = options;
1489
- checkSerializableAsJson({ name, value, message });
1490
- const orderedValue =
1491
- // TODO: Fix error "Type instantiation is excessively deep and possibly infinite."
1492
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1493
- // @ts-ignore
1494
- order === undefined
1495
- ? deepClone(value)
1496
- : orderJson({
1497
- value: value,
1498
- // <- Note: checkSerializableAsJson asserts that the value is serializable as JSON
1499
- order: order,
1500
- });
1501
- $deepFreeze(orderedValue);
1502
- return orderedValue;
1460
+ function isValidCsvString(value) {
1461
+ try {
1462
+ // A simple check for CSV format: at least one comma and no invalid characters
1463
+ if (value.includes(',') && /^[\w\s,"']+$/.test(value)) {
1464
+ return true;
1465
+ }
1466
+ return false;
1467
+ }
1468
+ catch (error) {
1469
+ assertsError(error);
1470
+ return false;
1471
+ }
1503
1472
  }
1504
- /**
1505
- * TODO: [🧠] Is there a way how to meaningfully test this utility
1506
- */
1507
1473
 
1508
1474
  /**
1509
- * Nonce which is used for replacing things in strings
1475
+ * Function isValidJsonString will tell you if the string is valid JSON or not
1510
1476
  *
1511
- * @private within the repository
1512
- */
1513
- const REPLACING_NONCE = 'ptbkauk42kV2dzao34faw7FudQUHYPtW';
1514
- /**
1515
- * @@@
1477
+ * @param value The string to check
1478
+ * @returns True if the string is a valid JSON string, false otherwise
1516
1479
  *
1517
- * @private within the repository
1480
+ * @public exported from `@promptbook/utils`
1518
1481
  */
1519
- const RESERVED_PARAMETER_MISSING_VALUE = 'MISSING-' + REPLACING_NONCE;
1482
+ function isValidJsonString(value /* <- [👨‍⚖️] */) {
1483
+ try {
1484
+ JSON.parse(value);
1485
+ return true;
1486
+ }
1487
+ catch (error) {
1488
+ assertsError(error);
1489
+ if (error.message.includes('Unexpected token')) {
1490
+ return false;
1491
+ }
1492
+ return false;
1493
+ }
1494
+ }
1495
+
1520
1496
  /**
1521
- * @@@
1497
+ * Converts a JavaScript Object Notation (JSON) string into an object.
1522
1498
  *
1523
- * @private within the repository
1499
+ * Note: This is wrapper around `JSON.parse()` with better error and type handling
1500
+ *
1501
+ * @public exported from `@promptbook/utils`
1524
1502
  */
1525
- const RESERVED_PARAMETER_RESTRICTED = 'RESTRICTED-' + REPLACING_NONCE;
1503
+ function jsonParse(value) {
1504
+ if (value === undefined) {
1505
+ throw new Error(`Can not parse JSON from undefined value.`);
1506
+ }
1507
+ else if (typeof value !== 'string') {
1508
+ console.error('Can not parse JSON from non-string value.', { text: value });
1509
+ throw new Error(spaceTrim$1(`
1510
+ Can not parse JSON from non-string value.
1511
+
1512
+ The value type: ${typeof value}
1513
+ See more in console.
1514
+ `));
1515
+ }
1516
+ try {
1517
+ return JSON.parse(value);
1518
+ }
1519
+ catch (error) {
1520
+ if (!(error instanceof Error)) {
1521
+ throw error;
1522
+ }
1523
+ throw new Error(spaceTrim$1((block) => `
1524
+ ${block(error.message)}
1525
+
1526
+ The JSON text:
1527
+ ${block(value)}
1528
+ `));
1529
+ }
1530
+ }
1526
1531
  /**
1527
- * The names of the parameters that are reserved for special purposes
1528
- *
1529
- * @public exported from `@promptbook/core`
1532
+ * TODO: !!!! Use in Promptbook.studio
1530
1533
  */
1531
- exportJson({
1532
- name: 'RESERVED_PARAMETER_NAMES',
1533
- message: `The names of the parameters that are reserved for special purposes`,
1534
- value: [
1535
- 'content',
1536
- 'context',
1537
- 'knowledge',
1538
- 'examples',
1539
- 'modelName',
1540
- 'currentDate',
1541
- // <- TODO: list here all command names
1542
- // <- TODO: Add more like 'date', 'modelName',...
1543
- // <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter
1544
- ],
1545
- });
1534
+
1546
1535
  /**
1547
- * Note: [💞] Ignore a discrepancy between file name and entity name
1536
+ * Function to check if a string is valid XML
1537
+ *
1538
+ * @param value
1539
+ * @returns True if the string is a valid XML string, false otherwise
1540
+ *
1541
+ * @public exported from `@promptbook/utils`
1548
1542
  */
1543
+ function isValidXmlString(value) {
1544
+ try {
1545
+ const parser = new DOMParser();
1546
+ const parsedDocument = parser.parseFromString(value, 'application/xml');
1547
+ const parserError = parsedDocument.getElementsByTagName('parsererror');
1548
+ if (parserError.length > 0) {
1549
+ return false;
1550
+ }
1551
+ return true;
1552
+ }
1553
+ catch (error) {
1554
+ assertsError(error);
1555
+ return false;
1556
+ }
1557
+ }
1549
1558
 
1550
1559
  /**
1551
1560
  * Format either small or big number