@promptbook/javascript 0.112.0-73 → 0.112.0-80

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.
Files changed (74) hide show
  1. package/README.md +9 -9
  2. package/esm/index.es.js +310 -204
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  5. package/esm/src/avatars/visuals/octopus3d2AvatarVisual.d.ts +7 -0
  6. package/esm/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +37 -0
  7. package/esm/src/book-components/Chat/save/_common/chatExportRendering.d.ts +47 -0
  8. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +12 -0
  9. package/esm/src/book-components/Chat/save/index.d.ts +2 -2
  10. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +5 -3
  11. package/esm/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -3
  12. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +1 -1
  13. package/esm/src/cli/cli-commands/agent/agentProjectPaths.d.ts +8 -8
  14. package/esm/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -1
  15. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +56 -0
  16. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.test.d.ts +1 -0
  17. package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +7 -0
  18. package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +7 -0
  19. package/esm/src/cli/cli-commands/agents-server/init.d.ts +9 -0
  20. package/esm/src/cli/cli-commands/agents-server/init.test.d.ts +1 -0
  21. package/esm/src/cli/cli-commands/agents-server/initializeAgentsServerProjectConfiguration.d.ts +17 -0
  22. package/esm/src/cli/cli-commands/agents-server/printAgentsServerInitializationSummary.d.ts +7 -0
  23. package/esm/src/cli/cli-commands/agents-server/run.d.ts +14 -0
  24. package/esm/src/cli/cli-commands/agents-server/run.test.d.ts +1 -0
  25. package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +23 -0
  26. package/esm/src/cli/cli-commands/agents-server.d.ts +8 -0
  27. package/esm/src/cli/cli-commands/common/projectInitialization.d.ts +65 -0
  28. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +44 -0
  29. package/esm/src/cli/common/$deprecateCliCommand.d.ts +8 -0
  30. package/esm/src/cli/common/$deprecateCliCommand.test.d.ts +1 -0
  31. package/esm/src/utils/color/Color.d.ts +4 -44
  32. package/esm/src/utils/color/ColorValue.d.ts +55 -0
  33. package/esm/src/utils/color/isHexColorString.d.ts +10 -0
  34. package/esm/src/utils/color/parseColorString.d.ts +11 -0
  35. package/esm/src/version.d.ts +1 -1
  36. package/package.json +2 -2
  37. package/umd/index.umd.js +310 -204
  38. package/umd/index.umd.js.map +1 -1
  39. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  40. package/umd/src/avatars/visuals/octopus3d2AvatarVisual.d.ts +7 -0
  41. package/umd/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +37 -0
  42. package/umd/src/book-components/Chat/save/_common/chatExportRendering.d.ts +47 -0
  43. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +12 -0
  44. package/umd/src/book-components/Chat/save/index.d.ts +2 -2
  45. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +5 -3
  46. package/umd/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -3
  47. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +1 -1
  48. package/umd/src/cli/cli-commands/agent/agentProjectPaths.d.ts +8 -8
  49. package/umd/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -1
  50. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +56 -0
  51. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.test.d.ts +1 -0
  52. package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +7 -0
  53. package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +7 -0
  54. package/umd/src/cli/cli-commands/agents-server/init.d.ts +9 -0
  55. package/umd/src/cli/cli-commands/agents-server/init.test.d.ts +1 -0
  56. package/umd/src/cli/cli-commands/agents-server/initializeAgentsServerProjectConfiguration.d.ts +17 -0
  57. package/umd/src/cli/cli-commands/agents-server/printAgentsServerInitializationSummary.d.ts +7 -0
  58. package/umd/src/cli/cli-commands/agents-server/run.d.ts +14 -0
  59. package/umd/src/cli/cli-commands/agents-server/run.test.d.ts +1 -0
  60. package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +23 -0
  61. package/umd/src/cli/cli-commands/agents-server.d.ts +8 -0
  62. package/umd/src/cli/cli-commands/common/projectInitialization.d.ts +65 -0
  63. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +44 -0
  64. package/umd/src/cli/common/$deprecateCliCommand.d.ts +8 -0
  65. package/umd/src/cli/common/$deprecateCliCommand.test.d.ts +1 -0
  66. package/umd/src/utils/color/Color.d.ts +4 -44
  67. package/umd/src/utils/color/ColorValue.d.ts +55 -0
  68. package/umd/src/utils/color/isHexColorString.d.ts +10 -0
  69. package/umd/src/utils/color/parseColorString.d.ts +11 -0
  70. package/umd/src/version.d.ts +1 -1
  71. package/esm/src/cli/cli-commands/coder/appendBlock.d.ts +0 -6
  72. package/esm/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +0 -6
  73. package/umd/src/cli/cli-commands/coder/appendBlock.d.ts +0 -6
  74. package/umd/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +0 -6
package/README.md CHANGED
@@ -501,28 +501,28 @@ npx ts-node ./src/cli/test/ptbk.ts coder verify
501
501
 
502
502
  #### Using `ptbk coder` in an external project
503
503
 
504
- If you want to use the workflow in another repository, install the package and invoke the `ptbk` binary. After local installation, `npx ptbk ...` is the most portable form; plain `ptbk ...` also works when your environment exposes the local binary on `PATH`.
504
+ If you want to use the workflow in another repository, install the package and invoke the `ptbk` binary directly.
505
505
 
506
506
  ```bash
507
507
  npm install ptbk
508
508
 
509
509
  ptbk coder init
510
510
 
511
- npx ptbk coder generate-boilerplates
511
+ ptbk coder generate-boilerplates
512
512
 
513
- npx ptbk coder generate-boilerplates --template prompts/templates/common.md
513
+ ptbk coder generate-boilerplates --template prompts/templates/common.md
514
514
 
515
- npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --test npm run test
515
+ ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --test npm run test
516
516
 
517
- npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --auto-push
517
+ ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --auto-push
518
518
 
519
- npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --test npm run test --ignore-git-changes --no-wait
519
+ ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --test npm run test --ignore-git-changes --no-wait
520
520
 
521
- npx ptbk coder find-refactor-candidates
521
+ ptbk coder find-refactor-candidates
522
522
 
523
- npx ptbk coder find-refactor-candidates --level xhigh
523
+ ptbk coder find-refactor-candidates --level xhigh
524
524
 
525
- npx ptbk coder verify
525
+ ptbk coder verify
526
526
  ```
527
527
 
528
528
  `ptbk coder init` also bootstraps a starter `AGENTS.md`, adds `package.json` scripts for the four main coder commands, adds the shared `/.promptbook` temp ignore to `.gitignore`, and configures `.vscode/settings.json` so pasted images from `prompts/*.md` land in `prompts/screenshots/`.
package/esm/index.es.js CHANGED
@@ -18,7 +18,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
18
18
  * @generated
19
19
  * @see https://github.com/webgptorg/promptbook
20
20
  */
21
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
21
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-80';
22
22
  /**
23
23
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
24
24
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -268,6 +268,111 @@ function checkChannelValue(channelName, value) {
268
268
  }
269
269
  }
270
270
 
271
+ /**
272
+ * Shared immutable channel storage and serialization helpers for `Color`.
273
+ *
274
+ * @private base class of Color
275
+ */
276
+ class ColorValue {
277
+ constructor(red, green, blue, alpha = 255) {
278
+ this.red = red;
279
+ this.green = green;
280
+ this.blue = blue;
281
+ this.alpha = alpha;
282
+ checkChannelValue('Red', red);
283
+ checkChannelValue('Green', green);
284
+ checkChannelValue('Blue', blue);
285
+ checkChannelValue('Alpha', alpha);
286
+ }
287
+ /**
288
+ * Shortcut for `red` property
289
+ * Number from 0 to 255
290
+ * @alias red
291
+ */
292
+ get r() {
293
+ return this.red;
294
+ }
295
+ /**
296
+ * Shortcut for `green` property
297
+ * Number from 0 to 255
298
+ * @alias green
299
+ */
300
+ get g() {
301
+ return this.green;
302
+ }
303
+ /**
304
+ * Shortcut for `blue` property
305
+ * Number from 0 to 255
306
+ * @alias blue
307
+ */
308
+ get b() {
309
+ return this.blue;
310
+ }
311
+ /**
312
+ * Shortcut for `alpha` property
313
+ * Number from 0 (transparent) to 255 (opaque)
314
+ * @alias alpha
315
+ */
316
+ get a() {
317
+ return this.alpha;
318
+ }
319
+ /**
320
+ * Shortcut for `alpha` property
321
+ * Number from 0 (transparent) to 255 (opaque)
322
+ * @alias alpha
323
+ */
324
+ get opacity() {
325
+ return this.alpha;
326
+ }
327
+ /**
328
+ * Shortcut for 1-`alpha` property
329
+ */
330
+ get transparency() {
331
+ return 255 - this.alpha;
332
+ }
333
+ clone() {
334
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
335
+ }
336
+ toString() {
337
+ return this.toHex();
338
+ }
339
+ toHex() {
340
+ if (this.alpha === 255) {
341
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
342
+ .toString(16)
343
+ .padStart(2, '0')}`;
344
+ }
345
+ else {
346
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
347
+ .toString(16)
348
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
349
+ }
350
+ }
351
+ toRgb() {
352
+ if (this.alpha === 255) {
353
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
354
+ }
355
+ else {
356
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
357
+ }
358
+ }
359
+ toHsl() {
360
+ throw new Error(`Getting HSL is not implemented`);
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Checks if the given value is a valid hex color string
366
+ *
367
+ * @param value - value to check
368
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
369
+ *
370
+ * @private function of Color
371
+ */
372
+ function isHexColorString(value) {
373
+ return (typeof value === 'string' && /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
374
+ }
375
+
271
376
  /**
272
377
  * Constant for short hex lengths.
273
378
  */
@@ -479,16 +584,53 @@ function parseAlphaValue(value) {
479
584
 
480
585
  /**
481
586
  * Pattern matching hsl regex.
587
+ *
588
+ * @private function of Color
482
589
  */
483
590
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
484
591
  /**
485
592
  * Pattern matching RGB regex.
593
+ *
594
+ * @private function of Color
486
595
  */
487
596
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
488
597
  /**
489
598
  * Pattern matching rgba regex.
599
+ *
600
+ * @private function of Color
490
601
  */
491
602
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
603
+ /**
604
+ * Parses a supported color string into RGBA channels.
605
+ *
606
+ * @param color as a string for example `#009edd`, `rgb(0,158,221)`, `rgb(0%,62%,86.7%)`, `hsl(197.1,100%,43.3%)`, `red`, `darkgrey`,...
607
+ * @returns RGBA channel values.
608
+ *
609
+ * @private function of Color
610
+ */
611
+ function parseColorString(color) {
612
+ const trimmed = color.trim();
613
+ const cssColor = CSS_COLORS[trimmed];
614
+ if (cssColor) {
615
+ return parseColorString(cssColor);
616
+ }
617
+ else if (isHexColorString(trimmed)) {
618
+ return parseHexColor(trimmed);
619
+ }
620
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
621
+ return parseHslColor(trimmed);
622
+ }
623
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
624
+ return parseRgbColor(trimmed);
625
+ }
626
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
627
+ return parseRgbaColor(trimmed);
628
+ }
629
+ else {
630
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
631
+ }
632
+ }
633
+
492
634
  /**
493
635
  * Color object represents an RGB color with alpha channel
494
636
  *
@@ -496,7 +638,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
496
638
  *
497
639
  * @public exported from `@promptbook/color`
498
640
  */
499
- class Color {
641
+ class Color extends ColorValue {
500
642
  /**
501
643
  * Creates a new Color instance from miscellaneous formats
502
644
  * - It can receive Color instance and just return the same instance
@@ -569,25 +711,7 @@ class Color {
569
711
  * @returns Color object
570
712
  */
571
713
  static fromString(color) {
572
- const trimmed = color.trim();
573
- if (CSS_COLORS[trimmed]) {
574
- return Color.fromString(CSS_COLORS[trimmed]);
575
- }
576
- else if (Color.isHexColorString(trimmed)) {
577
- return Color.fromHex(trimmed);
578
- }
579
- if (HSL_REGEX_PATTERN.test(trimmed)) {
580
- return Color.fromHsl(trimmed);
581
- }
582
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
583
- return Color.fromRgbString(trimmed);
584
- }
585
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
586
- return Color.fromRgbaString(trimmed);
587
- }
588
- else {
589
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
590
- }
714
+ return Color.fromColorChannels(parseColorString(color));
591
715
  }
592
716
  /**
593
717
  * Gets common color
@@ -617,8 +741,7 @@ class Color {
617
741
  * @returns Color object
618
742
  */
619
743
  static fromHex(hex) {
620
- const { red, green, blue, alpha } = parseHexColor(hex);
621
- return take(new Color(red, green, blue, alpha));
744
+ return Color.fromColorChannels(parseHexColor(hex));
622
745
  }
623
746
  /**
624
747
  * Creates a new Color instance from color in hsl format
@@ -627,8 +750,7 @@ class Color {
627
750
  * @returns Color object
628
751
  */
629
752
  static fromHsl(hsl) {
630
- const { red, green, blue, alpha } = parseHslColor(hsl);
631
- return take(new Color(red, green, blue, alpha));
753
+ return Color.fromColorChannels(parseHslColor(hsl));
632
754
  }
633
755
  /**
634
756
  * Creates a new Color instance from color in rgb format
@@ -637,8 +759,7 @@ class Color {
637
759
  * @returns Color object
638
760
  */
639
761
  static fromRgbString(rgb) {
640
- const { red, green, blue, alpha } = parseRgbColor(rgb);
641
- return take(new Color(red, green, blue, alpha));
762
+ return Color.fromColorChannels(parseRgbColor(rgb));
642
763
  }
643
764
  /**
644
765
  * Creates a new Color instance from color in rbga format
@@ -647,8 +768,7 @@ class Color {
647
768
  * @returns Color object
648
769
  */
649
770
  static fromRgbaString(rgba) {
650
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
651
- return take(new Color(red, green, blue, alpha));
771
+ return Color.fromColorChannels(parseRgbaColor(rgba));
652
772
  }
653
773
  /**
654
774
  * Creates a new Color for color channels values
@@ -660,7 +780,7 @@ class Color {
660
780
  * @returns Color object
661
781
  */
662
782
  static fromValues(red, green, blue, alpha = 255) {
663
- return take(new Color(red, green, blue, alpha));
783
+ return Color.fromColorChannels({ red, green, blue, alpha });
664
784
  }
665
785
  /**
666
786
  * Checks if the given value is a valid Color object.
@@ -693,8 +813,7 @@ class Color {
693
813
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
694
814
  */
695
815
  static isHexColorString(value) {
696
- return (typeof value === 'string' &&
697
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
816
+ return isHexColorString(value);
698
817
  }
699
818
  /**
700
819
  * Creates new Color object
@@ -707,89 +826,13 @@ class Color {
707
826
  * @param alpha number from 0 (transparent) to 255 (opaque)
708
827
  */
709
828
  constructor(red, green, blue, alpha = 255) {
710
- this.red = red;
711
- this.green = green;
712
- this.blue = blue;
713
- this.alpha = alpha;
714
- checkChannelValue('Red', red);
715
- checkChannelValue('Green', green);
716
- checkChannelValue('Blue', blue);
717
- checkChannelValue('Alpha', alpha);
718
- }
719
- /**
720
- * Shortcut for `red` property
721
- * Number from 0 to 255
722
- * @alias red
723
- */
724
- get r() {
725
- return this.red;
726
- }
727
- /**
728
- * Shortcut for `green` property
729
- * Number from 0 to 255
730
- * @alias green
731
- */
732
- get g() {
733
- return this.green;
734
- }
735
- /**
736
- * Shortcut for `blue` property
737
- * Number from 0 to 255
738
- * @alias blue
739
- */
740
- get b() {
741
- return this.blue;
742
- }
743
- /**
744
- * Shortcut for `alpha` property
745
- * Number from 0 (transparent) to 255 (opaque)
746
- * @alias alpha
747
- */
748
- get a() {
749
- return this.alpha;
750
- }
751
- /**
752
- * Shortcut for `alpha` property
753
- * Number from 0 (transparent) to 255 (opaque)
754
- * @alias alpha
755
- */
756
- get opacity() {
757
- return this.alpha;
758
- }
759
- /**
760
- * Shortcut for 1-`alpha` property
761
- */
762
- get transparency() {
763
- return 255 - this.alpha;
764
- }
765
- clone() {
766
- return take(new Color(this.red, this.green, this.blue, this.alpha));
829
+ super(red, green, blue, alpha);
767
830
  }
768
- toString() {
769
- return this.toHex();
831
+ createColor(red, green, blue, alpha) {
832
+ return new Color(red, green, blue, alpha);
770
833
  }
771
- toHex() {
772
- if (this.alpha === 255) {
773
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
774
- .toString(16)
775
- .padStart(2, '0')}`;
776
- }
777
- else {
778
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
779
- .toString(16)
780
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
781
- }
782
- }
783
- toRgb() {
784
- if (this.alpha === 255) {
785
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
786
- }
787
- else {
788
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
789
- }
790
- }
791
- toHsl() {
792
- throw new Error(`Getting HSL is not implemented`);
834
+ static fromColorChannels({ red, green, blue, alpha }) {
835
+ return take(new Color(red, green, blue, alpha));
793
836
  }
794
837
  }
795
838
 
@@ -1289,120 +1332,183 @@ function assertsError(whatWasThrown) {
1289
1332
  * @public exported from `@promptbook/utils`
1290
1333
  */
1291
1334
  function checkSerializableAsJson(options) {
1292
- const { value, name, message } = options;
1335
+ checkSerializableValue(options);
1336
+ }
1337
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1338
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1339
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1340
+ /**
1341
+ * Checks one value and dispatches to the appropriate specialized validator.
1342
+ *
1343
+ * @private function of `checkSerializableAsJson`
1344
+ */
1345
+ function checkSerializableValue(options) {
1346
+ const { value } = options;
1347
+ if (isSerializablePrimitive(value)) {
1348
+ return;
1349
+ }
1293
1350
  if (value === undefined) {
1294
- throw new UnexpectedError(`${name} is undefined`);
1351
+ throw new UnexpectedError(`${options.name} is undefined`);
1295
1352
  }
1296
- else if (value === null) {
1297
- return;
1353
+ if (typeof value === 'symbol') {
1354
+ throw new UnexpectedError(`${options.name} is symbol`);
1298
1355
  }
1299
- else if (typeof value === 'boolean') {
1300
- return;
1356
+ if (typeof value === 'function') {
1357
+ throw new UnexpectedError(`${options.name} is function`);
1301
1358
  }
1302
- else if (typeof value === 'number' && !isNaN(value)) {
1359
+ if (Array.isArray(value)) {
1360
+ checkSerializableArray(options, value);
1303
1361
  return;
1304
1362
  }
1305
- else if (typeof value === 'string') {
1363
+ if (value !== null && typeof value === 'object') {
1364
+ checkSerializableObject(options, value);
1306
1365
  return;
1307
1366
  }
1308
- else if (typeof value === 'symbol') {
1309
- throw new UnexpectedError(`${name} is symbol`);
1310
- }
1311
- else if (typeof value === 'function') {
1312
- throw new UnexpectedError(`${name} is function`);
1313
- }
1314
- else if (typeof value === 'object' && Array.isArray(value)) {
1315
- for (let i = 0; i < value.length; i++) {
1316
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
1317
- }
1367
+ throwUnknownTypeError(options);
1368
+ }
1369
+ /**
1370
+ * Checks the primitive values that are directly JSON serializable.
1371
+ *
1372
+ * @private function of `checkSerializableAsJson`
1373
+ */
1374
+ function isSerializablePrimitive(value) {
1375
+ return (value === null ||
1376
+ typeof value === 'boolean' ||
1377
+ (typeof value === 'number' && !isNaN(value)) ||
1378
+ typeof value === 'string');
1379
+ }
1380
+ /**
1381
+ * Recursively checks JSON array items.
1382
+ *
1383
+ * @private function of `checkSerializableAsJson`
1384
+ */
1385
+ function checkSerializableArray(context, arrayValue) {
1386
+ for (let index = 0; index < arrayValue.length; index++) {
1387
+ checkSerializableAsJson({
1388
+ ...context,
1389
+ name: `${context.name}[${index}]`,
1390
+ value: arrayValue[index],
1391
+ });
1318
1392
  }
1319
- else if (typeof value === 'object') {
1320
- if (value instanceof Date) {
1321
- throw new UnexpectedError(spaceTrim$1((block) => `
1322
- \`${name}\` is Date
1393
+ }
1394
+ /**
1395
+ * Checks object-like values and dispatches special unsupported built-ins.
1396
+ *
1397
+ * @private function of `checkSerializableAsJson`
1398
+ */
1399
+ function checkSerializableObject(context, objectValue) {
1400
+ checkUnsupportedObjectType(context, objectValue);
1401
+ checkSerializableObjectEntries(context, objectValue);
1402
+ assertJsonStringificationSucceeds(context, objectValue);
1403
+ }
1404
+ /**
1405
+ * Rejects built-in objects that must be converted before JSON serialization.
1406
+ *
1407
+ * @private function of `checkSerializableAsJson`
1408
+ */
1409
+ function checkUnsupportedObjectType(context, objectValue) {
1410
+ if (objectValue instanceof Date) {
1411
+ throw new UnexpectedError(spaceTrim$1((block) => `
1412
+ \`${context.name}\` is Date
1323
1413
 
1324
- Use \`string_date_iso8601\` instead
1414
+ Use \`string_date_iso8601\` instead
1325
1415
 
1326
- Additional message for \`${name}\`:
1327
- ${block(message || '(nothing)')}
1328
- `));
1329
- }
1330
- else if (value instanceof Map) {
1331
- throw new UnexpectedError(`${name} is Map`);
1332
- }
1333
- else if (value instanceof Set) {
1334
- throw new UnexpectedError(`${name} is Set`);
1335
- }
1336
- else if (value instanceof RegExp) {
1337
- throw new UnexpectedError(`${name} is RegExp`);
1338
- }
1339
- else if (value instanceof Error) {
1340
- throw new UnexpectedError(spaceTrim$1((block) => `
1341
- \`${name}\` is unserialized Error
1416
+ Additional message for \`${context.name}\`:
1417
+ ${block(context.message || '(nothing)')}
1418
+ `));
1419
+ }
1420
+ if (objectValue instanceof Map) {
1421
+ throw new UnexpectedError(`${context.name} is Map`);
1422
+ }
1423
+ if (objectValue instanceof Set) {
1424
+ throw new UnexpectedError(`${context.name} is Set`);
1425
+ }
1426
+ if (objectValue instanceof RegExp) {
1427
+ throw new UnexpectedError(`${context.name} is RegExp`);
1428
+ }
1429
+ if (objectValue instanceof Error) {
1430
+ throw new UnexpectedError(spaceTrim$1((block) => `
1431
+ \`${context.name}\` is unserialized Error
1342
1432
 
1343
- Use function \`serializeError\`
1433
+ Use function \`serializeError\`
1344
1434
 
1345
- Additional message for \`${name}\`:
1346
- ${block(message || '(nothing)')}
1435
+ Additional message for \`${context.name}\`:
1436
+ ${block(context.message || '(nothing)')}
1347
1437
 
1348
- `));
1438
+ `));
1439
+ }
1440
+ }
1441
+ /**
1442
+ * Recursively checks object properties while preserving omitted `undefined` keys.
1443
+ *
1444
+ * @private function of `checkSerializableAsJson`
1445
+ */
1446
+ function checkSerializableObjectEntries(context, objectValue) {
1447
+ for (const [subName, subValue] of Object.entries(objectValue)) {
1448
+ if (subValue === undefined) {
1449
+ // Note: undefined in object is serializable - it is just omitted
1450
+ continue;
1349
1451
  }
1350
- else {
1351
- for (const [subName, subValue] of Object.entries(value)) {
1352
- if (subValue === undefined) {
1353
- // Note: undefined in object is serializable - it is just omitted
1354
- continue;
1355
- }
1356
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
1357
- }
1358
- try {
1359
- JSON.stringify(value); // <- TODO: [0]
1360
- }
1361
- catch (error) {
1362
- assertsError(error);
1363
- throw new UnexpectedError(spaceTrim$1((block) => `
1364
- \`${name}\` is not serializable
1452
+ checkSerializableAsJson({
1453
+ ...context,
1454
+ name: `${context.name}.${subName}`,
1455
+ value: subValue,
1456
+ });
1457
+ }
1458
+ }
1459
+ /**
1460
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
1461
+ *
1462
+ * @private function of `checkSerializableAsJson`
1463
+ */
1464
+ function assertJsonStringificationSucceeds(context, objectValue) {
1465
+ try {
1466
+ JSON.stringify(objectValue); // <- TODO: [0]
1467
+ }
1468
+ catch (error) {
1469
+ assertsError(error);
1470
+ throw new UnexpectedError(spaceTrim$1((block) => `
1471
+ \`${context.name}\` is not serializable
1365
1472
 
1366
- ${block(error.stack || error.message)}
1473
+ ${block(error.stack || error.message)}
1367
1474
 
1368
- Additional message for \`${name}\`:
1369
- ${block(message || '(nothing)')}
1370
- `));
1475
+ Additional message for \`${context.name}\`:
1476
+ ${block(context.message || '(nothing)')}
1477
+ `));
1478
+ }
1479
+ /*
1480
+ TODO: [0] Is there some more elegant way to check circular references?
1481
+ const seen = new Set();
1482
+ const stack = [{ value }];
1483
+ while (stack.length > 0) {
1484
+ const { value } = stack.pop()!;
1485
+ if (typeof value === 'object' && value !== null) {
1486
+ if (seen.has(value)) {
1487
+ throw new UnexpectedError(`${name} has circular reference`);
1371
1488
  }
1372
- /*
1373
- TODO: [0] Is there some more elegant way to check circular references?
1374
- const seen = new Set();
1375
- const stack = [{ value }];
1376
- while (stack.length > 0) {
1377
- const { value } = stack.pop()!;
1378
- if (typeof value === 'object' && value !== null) {
1379
- if (seen.has(value)) {
1380
- throw new UnexpectedError(`${name} has circular reference`);
1381
- }
1382
- seen.add(value);
1383
- if (Array.isArray(value)) {
1384
- stack.push(...value.map((value) => ({ value })));
1385
- } else {
1386
- stack.push(...Object.values(value).map((value) => ({ value })));
1387
- }
1388
- }
1489
+ seen.add(value);
1490
+ if (Array.isArray(value)) {
1491
+ stack.push(...value.map((value) => ({ value })));
1492
+ } else {
1493
+ stack.push(...Object.values(value).map((value) => ({ value })));
1389
1494
  }
1390
- */
1391
- return;
1392
1495
  }
1393
1496
  }
1394
- else {
1395
- throw new UnexpectedError(spaceTrim$1((block) => `
1396
- \`${name}\` is unknown type
1497
+ */
1498
+ }
1499
+ /**
1500
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
1501
+ *
1502
+ * @private function of `checkSerializableAsJson`
1503
+ */
1504
+ function throwUnknownTypeError(context) {
1505
+ throw new UnexpectedError(spaceTrim$1((block) => `
1506
+ \`${context.name}\` is unknown type
1397
1507
 
1398
- Additional message for \`${name}\`:
1399
- ${block(message || '(nothing)')}
1400
- `));
1401
- }
1508
+ Additional message for \`${context.name}\`:
1509
+ ${block(context.message || '(nothing)')}
1510
+ `));
1402
1511
  }
1403
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1404
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1405
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1406
1512
 
1407
1513
  /**
1408
1514
  * Creates a deep clone of the given object