@promptbook/javascript 0.92.0-13 → 0.92.0-14
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 +352 -352
- package/esm/index.es.js.map +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +352 -352
- package/umd/index.umd.js.map +1 -1
package/umd/index.umd.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* @generated
|
|
24
24
|
* @see https://github.com/webgptorg/promptbook
|
|
25
25
|
*/
|
|
26
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.92.0-
|
|
26
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.92.0-14';
|
|
27
27
|
/**
|
|
28
28
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
29
29
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -94,6 +94,48 @@
|
|
|
94
94
|
* TODO: [🧠][🧜♂️] Maybe join remoteServerUrl and path into single value
|
|
95
95
|
*/
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Orders JSON object by keys
|
|
99
|
+
*
|
|
100
|
+
* @returns The same type of object as the input re-ordered
|
|
101
|
+
* @public exported from `@promptbook/utils`
|
|
102
|
+
*/
|
|
103
|
+
function orderJson(options) {
|
|
104
|
+
const { value, order } = options;
|
|
105
|
+
const orderedValue = {
|
|
106
|
+
...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
|
|
107
|
+
...value,
|
|
108
|
+
};
|
|
109
|
+
return orderedValue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Freezes the given object and all its nested objects recursively
|
|
114
|
+
*
|
|
115
|
+
* Note: `$` is used to indicate that this function is not a pure function - it mutates given object
|
|
116
|
+
* Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
|
|
117
|
+
*
|
|
118
|
+
* @returns The same object as the input, but deeply frozen
|
|
119
|
+
* @public exported from `@promptbook/utils`
|
|
120
|
+
*/
|
|
121
|
+
function $deepFreeze(objectValue) {
|
|
122
|
+
if (Array.isArray(objectValue)) {
|
|
123
|
+
return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
|
|
124
|
+
}
|
|
125
|
+
const propertyNames = Object.getOwnPropertyNames(objectValue);
|
|
126
|
+
for (const propertyName of propertyNames) {
|
|
127
|
+
const value = objectValue[propertyName];
|
|
128
|
+
if (value && typeof value === 'object') {
|
|
129
|
+
$deepFreeze(value);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
Object.freeze(objectValue);
|
|
133
|
+
return objectValue;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
137
|
+
*/
|
|
138
|
+
|
|
97
139
|
/**
|
|
98
140
|
* Make error report URL for the given error
|
|
99
141
|
*
|
|
@@ -163,66 +205,333 @@
|
|
|
163
205
|
}
|
|
164
206
|
|
|
165
207
|
/**
|
|
166
|
-
*
|
|
208
|
+
* This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
|
|
167
209
|
*
|
|
168
|
-
* @
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
210
|
+
* @public exported from `@promptbook/core`
|
|
211
|
+
*/
|
|
212
|
+
class WrappedError extends Error {
|
|
213
|
+
constructor(whatWasThrown) {
|
|
214
|
+
const tag = `[🤮]`;
|
|
215
|
+
console.error(tag, whatWasThrown);
|
|
216
|
+
super(spaceTrim.spaceTrim(`
|
|
217
|
+
Non-Error object was thrown
|
|
218
|
+
|
|
219
|
+
Note: Look for ${tag} in the console for more details
|
|
220
|
+
Please report issue on ${ADMIN_EMAIL}
|
|
221
|
+
`));
|
|
222
|
+
this.name = 'WrappedError';
|
|
223
|
+
Object.setPrototypeOf(this, WrappedError.prototype);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Helper used in catch blocks to assert that the error is an instance of `Error`
|
|
229
|
+
*
|
|
230
|
+
* @param whatWasThrown Any object that was thrown
|
|
231
|
+
* @returns Nothing if the error is an instance of `Error`
|
|
232
|
+
* @throws `WrappedError` or `UnexpectedError` if the error is not standard
|
|
233
|
+
*
|
|
234
|
+
* @private within the repository
|
|
235
|
+
*/
|
|
236
|
+
function assertsError(whatWasThrown) {
|
|
237
|
+
// Case 1: Handle error which was rethrown as `WrappedError`
|
|
238
|
+
if (whatWasThrown instanceof WrappedError) {
|
|
239
|
+
const wrappedError = whatWasThrown;
|
|
240
|
+
throw wrappedError;
|
|
241
|
+
}
|
|
242
|
+
// Case 2: Handle unexpected errors
|
|
243
|
+
if (whatWasThrown instanceof UnexpectedError) {
|
|
244
|
+
const unexpectedError = whatWasThrown;
|
|
245
|
+
throw unexpectedError;
|
|
246
|
+
}
|
|
247
|
+
// Case 3: Handle standard errors - keep them up to consumer
|
|
248
|
+
if (whatWasThrown instanceof Error) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
|
|
252
|
+
throw new WrappedError(whatWasThrown);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Checks if the value is [🚉] serializable as JSON
|
|
257
|
+
* If not, throws an UnexpectedError with a rich error message and tracking
|
|
258
|
+
*
|
|
259
|
+
* - Almost all primitives are serializable BUT:
|
|
260
|
+
* - `undefined` is not serializable
|
|
261
|
+
* - `NaN` is not serializable
|
|
262
|
+
* - Objects and arrays are serializable if all their properties are serializable
|
|
263
|
+
* - Functions are not serializable
|
|
264
|
+
* - Circular references are not serializable
|
|
265
|
+
* - `Date` objects are not serializable
|
|
266
|
+
* - `Map` and `Set` objects are not serializable
|
|
267
|
+
* - `RegExp` objects are not serializable
|
|
268
|
+
* - `Error` objects are not serializable
|
|
269
|
+
* - `Symbol` objects are not serializable
|
|
270
|
+
* - And much more...
|
|
271
|
+
*
|
|
272
|
+
* @throws UnexpectedError if the value is not serializable as JSON
|
|
173
273
|
* @public exported from `@promptbook/utils`
|
|
174
274
|
*/
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
275
|
+
function checkSerializableAsJson(options) {
|
|
276
|
+
const { value, name, message } = options;
|
|
277
|
+
if (value === undefined) {
|
|
278
|
+
throw new UnexpectedError(`${name} is undefined`);
|
|
279
|
+
}
|
|
280
|
+
else if (value === null) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
else if (typeof value === 'boolean') {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
else if (typeof value === 'number' && !isNaN(value)) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
else if (typeof value === 'string') {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
else if (typeof value === 'symbol') {
|
|
293
|
+
throw new UnexpectedError(`${name} is symbol`);
|
|
294
|
+
}
|
|
295
|
+
else if (typeof value === 'function') {
|
|
296
|
+
throw new UnexpectedError(`${name} is function`);
|
|
297
|
+
}
|
|
298
|
+
else if (typeof value === 'object' && Array.isArray(value)) {
|
|
299
|
+
for (let i = 0; i < value.length; i++) {
|
|
300
|
+
checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
|
|
184
301
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
302
|
+
}
|
|
303
|
+
else if (typeof value === 'object') {
|
|
304
|
+
if (value instanceof Date) {
|
|
305
|
+
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
306
|
+
\`${name}\` is Date
|
|
307
|
+
|
|
308
|
+
Use \`string_date_iso8601\` instead
|
|
309
|
+
|
|
310
|
+
Additional message for \`${name}\`:
|
|
311
|
+
${block(message || '(nothing)')}
|
|
312
|
+
`));
|
|
188
313
|
}
|
|
189
|
-
else if (
|
|
190
|
-
|
|
191
|
-
normalizedChar = char;
|
|
314
|
+
else if (value instanceof Map) {
|
|
315
|
+
throw new UnexpectedError(`${name} is Map`);
|
|
192
316
|
}
|
|
193
|
-
else {
|
|
194
|
-
|
|
195
|
-
normalizedChar = '';
|
|
317
|
+
else if (value instanceof Set) {
|
|
318
|
+
throw new UnexpectedError(`${name} is Set`);
|
|
196
319
|
}
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
normalizedChar = normalizedChar.toUpperCase(); //TODO: DRY
|
|
200
|
-
}
|
|
320
|
+
else if (value instanceof RegExp) {
|
|
321
|
+
throw new UnexpectedError(`${name} is RegExp`);
|
|
201
322
|
}
|
|
202
|
-
else if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
323
|
+
else if (value instanceof Error) {
|
|
324
|
+
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
325
|
+
\`${name}\` is unserialized Error
|
|
326
|
+
|
|
327
|
+
Use function \`serializeError\`
|
|
328
|
+
|
|
329
|
+
Additional message for \`${name}\`:
|
|
330
|
+
${block(message || '(nothing)')}
|
|
331
|
+
|
|
332
|
+
`));
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
for (const [subName, subValue] of Object.entries(value)) {
|
|
336
|
+
if (subValue === undefined) {
|
|
337
|
+
// Note: undefined in object is serializable - it is just omited
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
JSON.stringify(value); // <- TODO: [0]
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
assertsError(error);
|
|
347
|
+
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
348
|
+
\`${name}\` is not serializable
|
|
349
|
+
|
|
350
|
+
${block(error.stack || error.message)}
|
|
351
|
+
|
|
352
|
+
Additional message for \`${name}\`:
|
|
353
|
+
${block(message || '(nothing)')}
|
|
354
|
+
`));
|
|
355
|
+
}
|
|
356
|
+
/*
|
|
357
|
+
TODO: [0] Is there some more elegant way to check circular references?
|
|
358
|
+
const seen = new Set();
|
|
359
|
+
const stack = [{ value }];
|
|
360
|
+
while (stack.length > 0) {
|
|
361
|
+
const { value } = stack.pop()!;
|
|
362
|
+
if (typeof value === 'object' && value !== null) {
|
|
363
|
+
if (seen.has(value)) {
|
|
364
|
+
throw new UnexpectedError(`${name} has circular reference`);
|
|
365
|
+
}
|
|
366
|
+
seen.add(value);
|
|
367
|
+
if (Array.isArray(value)) {
|
|
368
|
+
stack.push(...value.map((value) => ({ value })));
|
|
369
|
+
} else {
|
|
370
|
+
stack.push(...Object.values(value).map((value) => ({ value })));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
*/
|
|
375
|
+
return;
|
|
207
376
|
}
|
|
208
|
-
normalizedName += normalizedChar;
|
|
209
|
-
lastCharType = charType;
|
|
210
377
|
}
|
|
211
|
-
|
|
378
|
+
else {
|
|
379
|
+
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
380
|
+
\`${name}\` is unknown type
|
|
381
|
+
|
|
382
|
+
Additional message for \`${name}\`:
|
|
383
|
+
${block(message || '(nothing)')}
|
|
384
|
+
`));
|
|
385
|
+
}
|
|
212
386
|
}
|
|
213
387
|
/**
|
|
214
|
-
* TODO:
|
|
388
|
+
* TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
|
|
389
|
+
* TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
|
|
390
|
+
* Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
|
|
215
391
|
*/
|
|
216
392
|
|
|
217
393
|
/**
|
|
218
|
-
*
|
|
394
|
+
* @@@
|
|
219
395
|
*
|
|
220
|
-
* @param text with emojis
|
|
221
|
-
* @returns text without emojis
|
|
222
396
|
* @public exported from `@promptbook/utils`
|
|
223
397
|
*/
|
|
224
|
-
function
|
|
225
|
-
|
|
398
|
+
function deepClone(objectValue) {
|
|
399
|
+
return JSON.parse(JSON.stringify(objectValue));
|
|
400
|
+
/*
|
|
401
|
+
TODO: [🧠] Is there a better implementation?
|
|
402
|
+
> const propertyNames = Object.getOwnPropertyNames(objectValue);
|
|
403
|
+
> for (const propertyName of propertyNames) {
|
|
404
|
+
> const value = (objectValue as really_any)[propertyName];
|
|
405
|
+
> if (value && typeof value === 'object') {
|
|
406
|
+
> deepClone(value);
|
|
407
|
+
> }
|
|
408
|
+
> }
|
|
409
|
+
> return Object.assign({}, objectValue);
|
|
410
|
+
*/
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
414
|
+
*/
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Utility to export a JSON object from a function
|
|
418
|
+
*
|
|
419
|
+
* 1) Checks if the value is serializable as JSON
|
|
420
|
+
* 2) Makes a deep clone of the object
|
|
421
|
+
* 2) Orders the object properties
|
|
422
|
+
* 2) Deeply freezes the cloned object
|
|
423
|
+
*
|
|
424
|
+
* Note: This function does not mutates the given object
|
|
425
|
+
*
|
|
426
|
+
* @returns The same type of object as the input but read-only and re-ordered
|
|
427
|
+
* @public exported from `@promptbook/utils`
|
|
428
|
+
*/
|
|
429
|
+
function exportJson(options) {
|
|
430
|
+
const { name, value, order, message } = options;
|
|
431
|
+
checkSerializableAsJson({ name, value, message });
|
|
432
|
+
const orderedValue =
|
|
433
|
+
// TODO: Fix error "Type instantiation is excessively deep and possibly infinite."
|
|
434
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
435
|
+
// @ts-ignore
|
|
436
|
+
order === undefined
|
|
437
|
+
? deepClone(value)
|
|
438
|
+
: orderJson({
|
|
439
|
+
value: value,
|
|
440
|
+
// <- Note: checkSerializableAsJson asserts that the value is serializable as JSON
|
|
441
|
+
order: order,
|
|
442
|
+
});
|
|
443
|
+
$deepFreeze(orderedValue);
|
|
444
|
+
return orderedValue;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
448
|
+
*/
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* The names of the parameters that are reserved for special purposes
|
|
452
|
+
*
|
|
453
|
+
* @public exported from `@promptbook/core`
|
|
454
|
+
*/
|
|
455
|
+
exportJson({
|
|
456
|
+
name: 'RESERVED_PARAMETER_NAMES',
|
|
457
|
+
message: `The names of the parameters that are reserved for special purposes`,
|
|
458
|
+
value: [
|
|
459
|
+
'content',
|
|
460
|
+
'context',
|
|
461
|
+
'knowledge',
|
|
462
|
+
'examples',
|
|
463
|
+
'modelName',
|
|
464
|
+
'currentDate',
|
|
465
|
+
// <- TODO: list here all command names
|
|
466
|
+
// <- TODO: Add more like 'date', 'modelName',...
|
|
467
|
+
// <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter
|
|
468
|
+
],
|
|
469
|
+
});
|
|
470
|
+
/**
|
|
471
|
+
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
472
|
+
*/
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* @@@
|
|
476
|
+
*
|
|
477
|
+
* @param text @@@
|
|
478
|
+
* @param _isFirstLetterCapital @@@
|
|
479
|
+
* @returns @@@
|
|
480
|
+
* @example 'helloWorld'
|
|
481
|
+
* @example 'iLovePromptbook'
|
|
482
|
+
* @public exported from `@promptbook/utils`
|
|
483
|
+
*/
|
|
484
|
+
function normalizeTo_camelCase(text, _isFirstLetterCapital = false) {
|
|
485
|
+
let charType;
|
|
486
|
+
let lastCharType = null;
|
|
487
|
+
let normalizedName = '';
|
|
488
|
+
for (const char of text) {
|
|
489
|
+
let normalizedChar;
|
|
490
|
+
if (/^[a-z]$/.test(char)) {
|
|
491
|
+
charType = 'LOWERCASE';
|
|
492
|
+
normalizedChar = char;
|
|
493
|
+
}
|
|
494
|
+
else if (/^[A-Z]$/.test(char)) {
|
|
495
|
+
charType = 'UPPERCASE';
|
|
496
|
+
normalizedChar = char.toLowerCase();
|
|
497
|
+
}
|
|
498
|
+
else if (/^[0-9]$/.test(char)) {
|
|
499
|
+
charType = 'NUMBER';
|
|
500
|
+
normalizedChar = char;
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
charType = 'OTHER';
|
|
504
|
+
normalizedChar = '';
|
|
505
|
+
}
|
|
506
|
+
if (!lastCharType) {
|
|
507
|
+
if (_isFirstLetterCapital) {
|
|
508
|
+
normalizedChar = normalizedChar.toUpperCase(); //TODO: DRY
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
else if (charType !== lastCharType &&
|
|
512
|
+
!(charType === 'LOWERCASE' && lastCharType === 'UPPERCASE') &&
|
|
513
|
+
!(lastCharType === 'NUMBER') &&
|
|
514
|
+
!(charType === 'NUMBER')) {
|
|
515
|
+
normalizedChar = normalizedChar.toUpperCase(); //TODO: [🌺] DRY
|
|
516
|
+
}
|
|
517
|
+
normalizedName += normalizedChar;
|
|
518
|
+
lastCharType = charType;
|
|
519
|
+
}
|
|
520
|
+
return normalizedName;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* TODO: [🌺] Use some intermediate util splitWords
|
|
524
|
+
*/
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Removes emojis from a string and fix whitespaces
|
|
528
|
+
*
|
|
529
|
+
* @param text with emojis
|
|
530
|
+
* @returns text without emojis
|
|
531
|
+
* @public exported from `@promptbook/utils`
|
|
532
|
+
*/
|
|
533
|
+
function removeEmojis(text) {
|
|
534
|
+
// Replace emojis (and also ZWJ sequence) with hyphens
|
|
226
535
|
text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
|
|
227
536
|
text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
|
|
228
537
|
text = text.replace(/(\p{Extended_Pictographic})(\u{200D}\p{Extended_Pictographic})*/gu, '$1');
|
|
@@ -592,26 +901,6 @@
|
|
|
592
901
|
* TODO: [🧠][🌂] Add id to all errors
|
|
593
902
|
*/
|
|
594
903
|
|
|
595
|
-
/**
|
|
596
|
-
* This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
|
|
597
|
-
*
|
|
598
|
-
* @public exported from `@promptbook/core`
|
|
599
|
-
*/
|
|
600
|
-
class WrappedError extends Error {
|
|
601
|
-
constructor(whatWasThrown) {
|
|
602
|
-
const tag = `[🤮]`;
|
|
603
|
-
console.error(tag, whatWasThrown);
|
|
604
|
-
super(spaceTrim.spaceTrim(`
|
|
605
|
-
Non-Error object was thrown
|
|
606
|
-
|
|
607
|
-
Note: Look for ${tag} in the console for more details
|
|
608
|
-
Please report issue on ${ADMIN_EMAIL}
|
|
609
|
-
`));
|
|
610
|
-
this.name = 'WrappedError';
|
|
611
|
-
Object.setPrototypeOf(this, WrappedError.prototype);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
904
|
/**
|
|
616
905
|
* Index of all javascript errors
|
|
617
906
|
*
|
|
@@ -639,295 +928,6 @@
|
|
|
639
928
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
640
929
|
*/
|
|
641
930
|
|
|
642
|
-
/**
|
|
643
|
-
* Helper used in catch blocks to assert that the error is an instance of `Error`
|
|
644
|
-
*
|
|
645
|
-
* @param whatWasThrown Any object that was thrown
|
|
646
|
-
* @returns Nothing if the error is an instance of `Error`
|
|
647
|
-
* @throws `WrappedError` or `UnexpectedError` if the error is not standard
|
|
648
|
-
*
|
|
649
|
-
* @private within the repository
|
|
650
|
-
*/
|
|
651
|
-
function assertsError(whatWasThrown) {
|
|
652
|
-
// Case 1: Handle error which was rethrown as `WrappedError`
|
|
653
|
-
if (whatWasThrown instanceof WrappedError) {
|
|
654
|
-
const wrappedError = whatWasThrown;
|
|
655
|
-
throw wrappedError;
|
|
656
|
-
}
|
|
657
|
-
// Case 2: Handle unexpected errors
|
|
658
|
-
if (whatWasThrown instanceof UnexpectedError) {
|
|
659
|
-
const unexpectedError = whatWasThrown;
|
|
660
|
-
throw unexpectedError;
|
|
661
|
-
}
|
|
662
|
-
// Case 3: Handle standard errors - keep them up to consumer
|
|
663
|
-
if (whatWasThrown instanceof Error) {
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
// Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
|
|
667
|
-
throw new WrappedError(whatWasThrown);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* Orders JSON object by keys
|
|
672
|
-
*
|
|
673
|
-
* @returns The same type of object as the input re-ordered
|
|
674
|
-
* @public exported from `@promptbook/utils`
|
|
675
|
-
*/
|
|
676
|
-
function orderJson(options) {
|
|
677
|
-
const { value, order } = options;
|
|
678
|
-
const orderedValue = {
|
|
679
|
-
...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
|
|
680
|
-
...value,
|
|
681
|
-
};
|
|
682
|
-
return orderedValue;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* Freezes the given object and all its nested objects recursively
|
|
687
|
-
*
|
|
688
|
-
* Note: `$` is used to indicate that this function is not a pure function - it mutates given object
|
|
689
|
-
* Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
|
|
690
|
-
*
|
|
691
|
-
* @returns The same object as the input, but deeply frozen
|
|
692
|
-
* @public exported from `@promptbook/utils`
|
|
693
|
-
*/
|
|
694
|
-
function $deepFreeze(objectValue) {
|
|
695
|
-
if (Array.isArray(objectValue)) {
|
|
696
|
-
return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
|
|
697
|
-
}
|
|
698
|
-
const propertyNames = Object.getOwnPropertyNames(objectValue);
|
|
699
|
-
for (const propertyName of propertyNames) {
|
|
700
|
-
const value = objectValue[propertyName];
|
|
701
|
-
if (value && typeof value === 'object') {
|
|
702
|
-
$deepFreeze(value);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
Object.freeze(objectValue);
|
|
706
|
-
return objectValue;
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
|
-
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
710
|
-
*/
|
|
711
|
-
|
|
712
|
-
/**
|
|
713
|
-
* Checks if the value is [🚉] serializable as JSON
|
|
714
|
-
* If not, throws an UnexpectedError with a rich error message and tracking
|
|
715
|
-
*
|
|
716
|
-
* - Almost all primitives are serializable BUT:
|
|
717
|
-
* - `undefined` is not serializable
|
|
718
|
-
* - `NaN` is not serializable
|
|
719
|
-
* - Objects and arrays are serializable if all their properties are serializable
|
|
720
|
-
* - Functions are not serializable
|
|
721
|
-
* - Circular references are not serializable
|
|
722
|
-
* - `Date` objects are not serializable
|
|
723
|
-
* - `Map` and `Set` objects are not serializable
|
|
724
|
-
* - `RegExp` objects are not serializable
|
|
725
|
-
* - `Error` objects are not serializable
|
|
726
|
-
* - `Symbol` objects are not serializable
|
|
727
|
-
* - And much more...
|
|
728
|
-
*
|
|
729
|
-
* @throws UnexpectedError if the value is not serializable as JSON
|
|
730
|
-
* @public exported from `@promptbook/utils`
|
|
731
|
-
*/
|
|
732
|
-
function checkSerializableAsJson(options) {
|
|
733
|
-
const { value, name, message } = options;
|
|
734
|
-
if (value === undefined) {
|
|
735
|
-
throw new UnexpectedError(`${name} is undefined`);
|
|
736
|
-
}
|
|
737
|
-
else if (value === null) {
|
|
738
|
-
return;
|
|
739
|
-
}
|
|
740
|
-
else if (typeof value === 'boolean') {
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
else if (typeof value === 'number' && !isNaN(value)) {
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
else if (typeof value === 'string') {
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
else if (typeof value === 'symbol') {
|
|
750
|
-
throw new UnexpectedError(`${name} is symbol`);
|
|
751
|
-
}
|
|
752
|
-
else if (typeof value === 'function') {
|
|
753
|
-
throw new UnexpectedError(`${name} is function`);
|
|
754
|
-
}
|
|
755
|
-
else if (typeof value === 'object' && Array.isArray(value)) {
|
|
756
|
-
for (let i = 0; i < value.length; i++) {
|
|
757
|
-
checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
else if (typeof value === 'object') {
|
|
761
|
-
if (value instanceof Date) {
|
|
762
|
-
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
763
|
-
\`${name}\` is Date
|
|
764
|
-
|
|
765
|
-
Use \`string_date_iso8601\` instead
|
|
766
|
-
|
|
767
|
-
Additional message for \`${name}\`:
|
|
768
|
-
${block(message || '(nothing)')}
|
|
769
|
-
`));
|
|
770
|
-
}
|
|
771
|
-
else if (value instanceof Map) {
|
|
772
|
-
throw new UnexpectedError(`${name} is Map`);
|
|
773
|
-
}
|
|
774
|
-
else if (value instanceof Set) {
|
|
775
|
-
throw new UnexpectedError(`${name} is Set`);
|
|
776
|
-
}
|
|
777
|
-
else if (value instanceof RegExp) {
|
|
778
|
-
throw new UnexpectedError(`${name} is RegExp`);
|
|
779
|
-
}
|
|
780
|
-
else if (value instanceof Error) {
|
|
781
|
-
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
782
|
-
\`${name}\` is unserialized Error
|
|
783
|
-
|
|
784
|
-
Use function \`serializeError\`
|
|
785
|
-
|
|
786
|
-
Additional message for \`${name}\`:
|
|
787
|
-
${block(message || '(nothing)')}
|
|
788
|
-
|
|
789
|
-
`));
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
for (const [subName, subValue] of Object.entries(value)) {
|
|
793
|
-
if (subValue === undefined) {
|
|
794
|
-
// Note: undefined in object is serializable - it is just omited
|
|
795
|
-
continue;
|
|
796
|
-
}
|
|
797
|
-
checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
|
|
798
|
-
}
|
|
799
|
-
try {
|
|
800
|
-
JSON.stringify(value); // <- TODO: [0]
|
|
801
|
-
}
|
|
802
|
-
catch (error) {
|
|
803
|
-
assertsError(error);
|
|
804
|
-
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
805
|
-
\`${name}\` is not serializable
|
|
806
|
-
|
|
807
|
-
${block(error.stack || error.message)}
|
|
808
|
-
|
|
809
|
-
Additional message for \`${name}\`:
|
|
810
|
-
${block(message || '(nothing)')}
|
|
811
|
-
`));
|
|
812
|
-
}
|
|
813
|
-
/*
|
|
814
|
-
TODO: [0] Is there some more elegant way to check circular references?
|
|
815
|
-
const seen = new Set();
|
|
816
|
-
const stack = [{ value }];
|
|
817
|
-
while (stack.length > 0) {
|
|
818
|
-
const { value } = stack.pop()!;
|
|
819
|
-
if (typeof value === 'object' && value !== null) {
|
|
820
|
-
if (seen.has(value)) {
|
|
821
|
-
throw new UnexpectedError(`${name} has circular reference`);
|
|
822
|
-
}
|
|
823
|
-
seen.add(value);
|
|
824
|
-
if (Array.isArray(value)) {
|
|
825
|
-
stack.push(...value.map((value) => ({ value })));
|
|
826
|
-
} else {
|
|
827
|
-
stack.push(...Object.values(value).map((value) => ({ value })));
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
*/
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
else {
|
|
836
|
-
throw new UnexpectedError(spaceTrim__default["default"]((block) => `
|
|
837
|
-
\`${name}\` is unknown type
|
|
838
|
-
|
|
839
|
-
Additional message for \`${name}\`:
|
|
840
|
-
${block(message || '(nothing)')}
|
|
841
|
-
`));
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
/**
|
|
845
|
-
* TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
|
|
846
|
-
* TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
|
|
847
|
-
* Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
|
|
848
|
-
*/
|
|
849
|
-
|
|
850
|
-
/**
|
|
851
|
-
* @@@
|
|
852
|
-
*
|
|
853
|
-
* @public exported from `@promptbook/utils`
|
|
854
|
-
*/
|
|
855
|
-
function deepClone(objectValue) {
|
|
856
|
-
return JSON.parse(JSON.stringify(objectValue));
|
|
857
|
-
/*
|
|
858
|
-
TODO: [🧠] Is there a better implementation?
|
|
859
|
-
> const propertyNames = Object.getOwnPropertyNames(objectValue);
|
|
860
|
-
> for (const propertyName of propertyNames) {
|
|
861
|
-
> const value = (objectValue as really_any)[propertyName];
|
|
862
|
-
> if (value && typeof value === 'object') {
|
|
863
|
-
> deepClone(value);
|
|
864
|
-
> }
|
|
865
|
-
> }
|
|
866
|
-
> return Object.assign({}, objectValue);
|
|
867
|
-
*/
|
|
868
|
-
}
|
|
869
|
-
/**
|
|
870
|
-
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
871
|
-
*/
|
|
872
|
-
|
|
873
|
-
/**
|
|
874
|
-
* Utility to export a JSON object from a function
|
|
875
|
-
*
|
|
876
|
-
* 1) Checks if the value is serializable as JSON
|
|
877
|
-
* 2) Makes a deep clone of the object
|
|
878
|
-
* 2) Orders the object properties
|
|
879
|
-
* 2) Deeply freezes the cloned object
|
|
880
|
-
*
|
|
881
|
-
* Note: This function does not mutates the given object
|
|
882
|
-
*
|
|
883
|
-
* @returns The same type of object as the input but read-only and re-ordered
|
|
884
|
-
* @public exported from `@promptbook/utils`
|
|
885
|
-
*/
|
|
886
|
-
function exportJson(options) {
|
|
887
|
-
const { name, value, order, message } = options;
|
|
888
|
-
checkSerializableAsJson({ name, value, message });
|
|
889
|
-
const orderedValue =
|
|
890
|
-
// TODO: Fix error "Type instantiation is excessively deep and possibly infinite."
|
|
891
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
892
|
-
// @ts-ignore
|
|
893
|
-
order === undefined
|
|
894
|
-
? deepClone(value)
|
|
895
|
-
: orderJson({
|
|
896
|
-
value: value,
|
|
897
|
-
// <- Note: checkSerializableAsJson asserts that the value is serializable as JSON
|
|
898
|
-
order: order,
|
|
899
|
-
});
|
|
900
|
-
$deepFreeze(orderedValue);
|
|
901
|
-
return orderedValue;
|
|
902
|
-
}
|
|
903
|
-
/**
|
|
904
|
-
* TODO: [🧠] Is there a way how to meaningfully test this utility
|
|
905
|
-
*/
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* The names of the parameters that are reserved for special purposes
|
|
909
|
-
*
|
|
910
|
-
* @public exported from `@promptbook/core`
|
|
911
|
-
*/
|
|
912
|
-
exportJson({
|
|
913
|
-
name: 'RESERVED_PARAMETER_NAMES',
|
|
914
|
-
message: `The names of the parameters that are reserved for special purposes`,
|
|
915
|
-
value: [
|
|
916
|
-
'content',
|
|
917
|
-
'context',
|
|
918
|
-
'knowledge',
|
|
919
|
-
'examples',
|
|
920
|
-
'modelName',
|
|
921
|
-
'currentDate',
|
|
922
|
-
// <- TODO: list here all command names
|
|
923
|
-
// <- TODO: Add more like 'date', 'modelName',...
|
|
924
|
-
// <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter
|
|
925
|
-
],
|
|
926
|
-
});
|
|
927
|
-
/**
|
|
928
|
-
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
929
|
-
*/
|
|
930
|
-
|
|
931
931
|
/**
|
|
932
932
|
* Format either small or big number
|
|
933
933
|
*
|