@promptbook/fake-llm 0.112.0-73 → 0.112.0-79

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
@@ -458,28 +458,28 @@ npx ts-node ./src/cli/test/ptbk.ts coder verify
458
458
 
459
459
  #### Using `ptbk coder` in an external project
460
460
 
461
- 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`.
461
+ If you want to use the workflow in another repository, install the package and invoke the `ptbk` binary directly.
462
462
 
463
463
  ```bash
464
464
  npm install ptbk
465
465
 
466
466
  ptbk coder init
467
467
 
468
- npx ptbk coder generate-boilerplates
468
+ ptbk coder generate-boilerplates
469
469
 
470
- npx ptbk coder generate-boilerplates --template prompts/templates/common.md
470
+ ptbk coder generate-boilerplates --template prompts/templates/common.md
471
471
 
472
- npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --test npm run test
472
+ ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --test npm run test
473
473
 
474
- npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --auto-push
474
+ ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --auto-push
475
475
 
476
- 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
476
+ 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
477
477
 
478
- npx ptbk coder find-refactor-candidates
478
+ ptbk coder find-refactor-candidates
479
479
 
480
- npx ptbk coder find-refactor-candidates --level xhigh
480
+ ptbk coder find-refactor-candidates --level xhigh
481
481
 
482
- npx ptbk coder verify
482
+ ptbk coder verify
483
483
  ```
484
484
 
485
485
  `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
@@ -20,7 +20,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
20
20
  * @generated
21
21
  * @see https://github.com/webgptorg/promptbook
22
22
  */
23
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
23
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-79';
24
24
  /**
25
25
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
26
26
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -377,6 +377,111 @@ function checkChannelValue(channelName, value) {
377
377
  }
378
378
  }
379
379
 
380
+ /**
381
+ * Shared immutable channel storage and serialization helpers for `Color`.
382
+ *
383
+ * @private base class of Color
384
+ */
385
+ class ColorValue {
386
+ constructor(red, green, blue, alpha = 255) {
387
+ this.red = red;
388
+ this.green = green;
389
+ this.blue = blue;
390
+ this.alpha = alpha;
391
+ checkChannelValue('Red', red);
392
+ checkChannelValue('Green', green);
393
+ checkChannelValue('Blue', blue);
394
+ checkChannelValue('Alpha', alpha);
395
+ }
396
+ /**
397
+ * Shortcut for `red` property
398
+ * Number from 0 to 255
399
+ * @alias red
400
+ */
401
+ get r() {
402
+ return this.red;
403
+ }
404
+ /**
405
+ * Shortcut for `green` property
406
+ * Number from 0 to 255
407
+ * @alias green
408
+ */
409
+ get g() {
410
+ return this.green;
411
+ }
412
+ /**
413
+ * Shortcut for `blue` property
414
+ * Number from 0 to 255
415
+ * @alias blue
416
+ */
417
+ get b() {
418
+ return this.blue;
419
+ }
420
+ /**
421
+ * Shortcut for `alpha` property
422
+ * Number from 0 (transparent) to 255 (opaque)
423
+ * @alias alpha
424
+ */
425
+ get a() {
426
+ return this.alpha;
427
+ }
428
+ /**
429
+ * Shortcut for `alpha` property
430
+ * Number from 0 (transparent) to 255 (opaque)
431
+ * @alias alpha
432
+ */
433
+ get opacity() {
434
+ return this.alpha;
435
+ }
436
+ /**
437
+ * Shortcut for 1-`alpha` property
438
+ */
439
+ get transparency() {
440
+ return 255 - this.alpha;
441
+ }
442
+ clone() {
443
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
444
+ }
445
+ toString() {
446
+ return this.toHex();
447
+ }
448
+ toHex() {
449
+ if (this.alpha === 255) {
450
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
451
+ .toString(16)
452
+ .padStart(2, '0')}`;
453
+ }
454
+ else {
455
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
456
+ .toString(16)
457
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
458
+ }
459
+ }
460
+ toRgb() {
461
+ if (this.alpha === 255) {
462
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
463
+ }
464
+ else {
465
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
466
+ }
467
+ }
468
+ toHsl() {
469
+ throw new Error(`Getting HSL is not implemented`);
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Checks if the given value is a valid hex color string
475
+ *
476
+ * @param value - value to check
477
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
478
+ *
479
+ * @private function of Color
480
+ */
481
+ function isHexColorString(value) {
482
+ 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));
483
+ }
484
+
380
485
  /**
381
486
  * Constant for short hex lengths.
382
487
  */
@@ -588,16 +693,53 @@ function parseAlphaValue(value) {
588
693
 
589
694
  /**
590
695
  * Pattern matching hsl regex.
696
+ *
697
+ * @private function of Color
591
698
  */
592
699
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
593
700
  /**
594
701
  * Pattern matching RGB regex.
702
+ *
703
+ * @private function of Color
595
704
  */
596
705
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
597
706
  /**
598
707
  * Pattern matching rgba regex.
708
+ *
709
+ * @private function of Color
599
710
  */
600
711
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
712
+ /**
713
+ * Parses a supported color string into RGBA channels.
714
+ *
715
+ * @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`,...
716
+ * @returns RGBA channel values.
717
+ *
718
+ * @private function of Color
719
+ */
720
+ function parseColorString(color) {
721
+ const trimmed = color.trim();
722
+ const cssColor = CSS_COLORS[trimmed];
723
+ if (cssColor) {
724
+ return parseColorString(cssColor);
725
+ }
726
+ else if (isHexColorString(trimmed)) {
727
+ return parseHexColor(trimmed);
728
+ }
729
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
730
+ return parseHslColor(trimmed);
731
+ }
732
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
733
+ return parseRgbColor(trimmed);
734
+ }
735
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
736
+ return parseRgbaColor(trimmed);
737
+ }
738
+ else {
739
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
740
+ }
741
+ }
742
+
601
743
  /**
602
744
  * Color object represents an RGB color with alpha channel
603
745
  *
@@ -605,7 +747,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
605
747
  *
606
748
  * @public exported from `@promptbook/color`
607
749
  */
608
- class Color {
750
+ class Color extends ColorValue {
609
751
  /**
610
752
  * Creates a new Color instance from miscellaneous formats
611
753
  * - It can receive Color instance and just return the same instance
@@ -678,25 +820,7 @@ class Color {
678
820
  * @returns Color object
679
821
  */
680
822
  static fromString(color) {
681
- const trimmed = color.trim();
682
- if (CSS_COLORS[trimmed]) {
683
- return Color.fromString(CSS_COLORS[trimmed]);
684
- }
685
- else if (Color.isHexColorString(trimmed)) {
686
- return Color.fromHex(trimmed);
687
- }
688
- if (HSL_REGEX_PATTERN.test(trimmed)) {
689
- return Color.fromHsl(trimmed);
690
- }
691
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
692
- return Color.fromRgbString(trimmed);
693
- }
694
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
695
- return Color.fromRgbaString(trimmed);
696
- }
697
- else {
698
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
699
- }
823
+ return Color.fromColorChannels(parseColorString(color));
700
824
  }
701
825
  /**
702
826
  * Gets common color
@@ -726,8 +850,7 @@ class Color {
726
850
  * @returns Color object
727
851
  */
728
852
  static fromHex(hex) {
729
- const { red, green, blue, alpha } = parseHexColor(hex);
730
- return take(new Color(red, green, blue, alpha));
853
+ return Color.fromColorChannels(parseHexColor(hex));
731
854
  }
732
855
  /**
733
856
  * Creates a new Color instance from color in hsl format
@@ -736,8 +859,7 @@ class Color {
736
859
  * @returns Color object
737
860
  */
738
861
  static fromHsl(hsl) {
739
- const { red, green, blue, alpha } = parseHslColor(hsl);
740
- return take(new Color(red, green, blue, alpha));
862
+ return Color.fromColorChannels(parseHslColor(hsl));
741
863
  }
742
864
  /**
743
865
  * Creates a new Color instance from color in rgb format
@@ -746,8 +868,7 @@ class Color {
746
868
  * @returns Color object
747
869
  */
748
870
  static fromRgbString(rgb) {
749
- const { red, green, blue, alpha } = parseRgbColor(rgb);
750
- return take(new Color(red, green, blue, alpha));
871
+ return Color.fromColorChannels(parseRgbColor(rgb));
751
872
  }
752
873
  /**
753
874
  * Creates a new Color instance from color in rbga format
@@ -756,8 +877,7 @@ class Color {
756
877
  * @returns Color object
757
878
  */
758
879
  static fromRgbaString(rgba) {
759
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
760
- return take(new Color(red, green, blue, alpha));
880
+ return Color.fromColorChannels(parseRgbaColor(rgba));
761
881
  }
762
882
  /**
763
883
  * Creates a new Color for color channels values
@@ -769,7 +889,7 @@ class Color {
769
889
  * @returns Color object
770
890
  */
771
891
  static fromValues(red, green, blue, alpha = 255) {
772
- return take(new Color(red, green, blue, alpha));
892
+ return Color.fromColorChannels({ red, green, blue, alpha });
773
893
  }
774
894
  /**
775
895
  * Checks if the given value is a valid Color object.
@@ -802,8 +922,7 @@ class Color {
802
922
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
803
923
  */
804
924
  static isHexColorString(value) {
805
- return (typeof value === 'string' &&
806
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
925
+ return isHexColorString(value);
807
926
  }
808
927
  /**
809
928
  * Creates new Color object
@@ -816,89 +935,13 @@ class Color {
816
935
  * @param alpha number from 0 (transparent) to 255 (opaque)
817
936
  */
818
937
  constructor(red, green, blue, alpha = 255) {
819
- this.red = red;
820
- this.green = green;
821
- this.blue = blue;
822
- this.alpha = alpha;
823
- checkChannelValue('Red', red);
824
- checkChannelValue('Green', green);
825
- checkChannelValue('Blue', blue);
826
- checkChannelValue('Alpha', alpha);
827
- }
828
- /**
829
- * Shortcut for `red` property
830
- * Number from 0 to 255
831
- * @alias red
832
- */
833
- get r() {
834
- return this.red;
835
- }
836
- /**
837
- * Shortcut for `green` property
838
- * Number from 0 to 255
839
- * @alias green
840
- */
841
- get g() {
842
- return this.green;
843
- }
844
- /**
845
- * Shortcut for `blue` property
846
- * Number from 0 to 255
847
- * @alias blue
848
- */
849
- get b() {
850
- return this.blue;
851
- }
852
- /**
853
- * Shortcut for `alpha` property
854
- * Number from 0 (transparent) to 255 (opaque)
855
- * @alias alpha
856
- */
857
- get a() {
858
- return this.alpha;
859
- }
860
- /**
861
- * Shortcut for `alpha` property
862
- * Number from 0 (transparent) to 255 (opaque)
863
- * @alias alpha
864
- */
865
- get opacity() {
866
- return this.alpha;
867
- }
868
- /**
869
- * Shortcut for 1-`alpha` property
870
- */
871
- get transparency() {
872
- return 255 - this.alpha;
873
- }
874
- clone() {
875
- return take(new Color(this.red, this.green, this.blue, this.alpha));
938
+ super(red, green, blue, alpha);
876
939
  }
877
- toString() {
878
- return this.toHex();
940
+ createColor(red, green, blue, alpha) {
941
+ return new Color(red, green, blue, alpha);
879
942
  }
880
- toHex() {
881
- if (this.alpha === 255) {
882
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
883
- .toString(16)
884
- .padStart(2, '0')}`;
885
- }
886
- else {
887
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
888
- .toString(16)
889
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
890
- }
891
- }
892
- toRgb() {
893
- if (this.alpha === 255) {
894
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
895
- }
896
- else {
897
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
898
- }
899
- }
900
- toHsl() {
901
- throw new Error(`Getting HSL is not implemented`);
943
+ static fromColorChannels({ red, green, blue, alpha }) {
944
+ return take(new Color(red, green, blue, alpha));
902
945
  }
903
946
  }
904
947
 
@@ -1378,120 +1421,183 @@ function assertsError(whatWasThrown) {
1378
1421
  * @public exported from `@promptbook/utils`
1379
1422
  */
1380
1423
  function checkSerializableAsJson(options) {
1381
- const { value, name, message } = options;
1424
+ checkSerializableValue(options);
1425
+ }
1426
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1427
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1428
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1429
+ /**
1430
+ * Checks one value and dispatches to the appropriate specialized validator.
1431
+ *
1432
+ * @private function of `checkSerializableAsJson`
1433
+ */
1434
+ function checkSerializableValue(options) {
1435
+ const { value } = options;
1436
+ if (isSerializablePrimitive(value)) {
1437
+ return;
1438
+ }
1382
1439
  if (value === undefined) {
1383
- throw new UnexpectedError(`${name} is undefined`);
1440
+ throw new UnexpectedError(`${options.name} is undefined`);
1384
1441
  }
1385
- else if (value === null) {
1386
- return;
1442
+ if (typeof value === 'symbol') {
1443
+ throw new UnexpectedError(`${options.name} is symbol`);
1387
1444
  }
1388
- else if (typeof value === 'boolean') {
1389
- return;
1445
+ if (typeof value === 'function') {
1446
+ throw new UnexpectedError(`${options.name} is function`);
1390
1447
  }
1391
- else if (typeof value === 'number' && !isNaN(value)) {
1448
+ if (Array.isArray(value)) {
1449
+ checkSerializableArray(options, value);
1392
1450
  return;
1393
1451
  }
1394
- else if (typeof value === 'string') {
1452
+ if (value !== null && typeof value === 'object') {
1453
+ checkSerializableObject(options, value);
1395
1454
  return;
1396
1455
  }
1397
- else if (typeof value === 'symbol') {
1398
- throw new UnexpectedError(`${name} is symbol`);
1399
- }
1400
- else if (typeof value === 'function') {
1401
- throw new UnexpectedError(`${name} is function`);
1402
- }
1403
- else if (typeof value === 'object' && Array.isArray(value)) {
1404
- for (let i = 0; i < value.length; i++) {
1405
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
1406
- }
1456
+ throwUnknownTypeError(options);
1457
+ }
1458
+ /**
1459
+ * Checks the primitive values that are directly JSON serializable.
1460
+ *
1461
+ * @private function of `checkSerializableAsJson`
1462
+ */
1463
+ function isSerializablePrimitive(value) {
1464
+ return (value === null ||
1465
+ typeof value === 'boolean' ||
1466
+ (typeof value === 'number' && !isNaN(value)) ||
1467
+ typeof value === 'string');
1468
+ }
1469
+ /**
1470
+ * Recursively checks JSON array items.
1471
+ *
1472
+ * @private function of `checkSerializableAsJson`
1473
+ */
1474
+ function checkSerializableArray(context, arrayValue) {
1475
+ for (let index = 0; index < arrayValue.length; index++) {
1476
+ checkSerializableAsJson({
1477
+ ...context,
1478
+ name: `${context.name}[${index}]`,
1479
+ value: arrayValue[index],
1480
+ });
1407
1481
  }
1408
- else if (typeof value === 'object') {
1409
- if (value instanceof Date) {
1410
- throw new UnexpectedError(spaceTrim$1((block) => `
1411
- \`${name}\` is Date
1482
+ }
1483
+ /**
1484
+ * Checks object-like values and dispatches special unsupported built-ins.
1485
+ *
1486
+ * @private function of `checkSerializableAsJson`
1487
+ */
1488
+ function checkSerializableObject(context, objectValue) {
1489
+ checkUnsupportedObjectType(context, objectValue);
1490
+ checkSerializableObjectEntries(context, objectValue);
1491
+ assertJsonStringificationSucceeds(context, objectValue);
1492
+ }
1493
+ /**
1494
+ * Rejects built-in objects that must be converted before JSON serialization.
1495
+ *
1496
+ * @private function of `checkSerializableAsJson`
1497
+ */
1498
+ function checkUnsupportedObjectType(context, objectValue) {
1499
+ if (objectValue instanceof Date) {
1500
+ throw new UnexpectedError(spaceTrim$1((block) => `
1501
+ \`${context.name}\` is Date
1412
1502
 
1413
- Use \`string_date_iso8601\` instead
1503
+ Use \`string_date_iso8601\` instead
1414
1504
 
1415
- Additional message for \`${name}\`:
1416
- ${block(message || '(nothing)')}
1417
- `));
1418
- }
1419
- else if (value instanceof Map) {
1420
- throw new UnexpectedError(`${name} is Map`);
1421
- }
1422
- else if (value instanceof Set) {
1423
- throw new UnexpectedError(`${name} is Set`);
1424
- }
1425
- else if (value instanceof RegExp) {
1426
- throw new UnexpectedError(`${name} is RegExp`);
1427
- }
1428
- else if (value instanceof Error) {
1429
- throw new UnexpectedError(spaceTrim$1((block) => `
1430
- \`${name}\` is unserialized Error
1505
+ Additional message for \`${context.name}\`:
1506
+ ${block(context.message || '(nothing)')}
1507
+ `));
1508
+ }
1509
+ if (objectValue instanceof Map) {
1510
+ throw new UnexpectedError(`${context.name} is Map`);
1511
+ }
1512
+ if (objectValue instanceof Set) {
1513
+ throw new UnexpectedError(`${context.name} is Set`);
1514
+ }
1515
+ if (objectValue instanceof RegExp) {
1516
+ throw new UnexpectedError(`${context.name} is RegExp`);
1517
+ }
1518
+ if (objectValue instanceof Error) {
1519
+ throw new UnexpectedError(spaceTrim$1((block) => `
1520
+ \`${context.name}\` is unserialized Error
1431
1521
 
1432
- Use function \`serializeError\`
1522
+ Use function \`serializeError\`
1433
1523
 
1434
- Additional message for \`${name}\`:
1435
- ${block(message || '(nothing)')}
1524
+ Additional message for \`${context.name}\`:
1525
+ ${block(context.message || '(nothing)')}
1436
1526
 
1437
- `));
1527
+ `));
1528
+ }
1529
+ }
1530
+ /**
1531
+ * Recursively checks object properties while preserving omitted `undefined` keys.
1532
+ *
1533
+ * @private function of `checkSerializableAsJson`
1534
+ */
1535
+ function checkSerializableObjectEntries(context, objectValue) {
1536
+ for (const [subName, subValue] of Object.entries(objectValue)) {
1537
+ if (subValue === undefined) {
1538
+ // Note: undefined in object is serializable - it is just omitted
1539
+ continue;
1438
1540
  }
1439
- else {
1440
- for (const [subName, subValue] of Object.entries(value)) {
1441
- if (subValue === undefined) {
1442
- // Note: undefined in object is serializable - it is just omitted
1443
- continue;
1444
- }
1445
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
1446
- }
1447
- try {
1448
- JSON.stringify(value); // <- TODO: [0]
1449
- }
1450
- catch (error) {
1451
- assertsError(error);
1452
- throw new UnexpectedError(spaceTrim$1((block) => `
1453
- \`${name}\` is not serializable
1541
+ checkSerializableAsJson({
1542
+ ...context,
1543
+ name: `${context.name}.${subName}`,
1544
+ value: subValue,
1545
+ });
1546
+ }
1547
+ }
1548
+ /**
1549
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
1550
+ *
1551
+ * @private function of `checkSerializableAsJson`
1552
+ */
1553
+ function assertJsonStringificationSucceeds(context, objectValue) {
1554
+ try {
1555
+ JSON.stringify(objectValue); // <- TODO: [0]
1556
+ }
1557
+ catch (error) {
1558
+ assertsError(error);
1559
+ throw new UnexpectedError(spaceTrim$1((block) => `
1560
+ \`${context.name}\` is not serializable
1454
1561
 
1455
- ${block(error.stack || error.message)}
1562
+ ${block(error.stack || error.message)}
1456
1563
 
1457
- Additional message for \`${name}\`:
1458
- ${block(message || '(nothing)')}
1459
- `));
1564
+ Additional message for \`${context.name}\`:
1565
+ ${block(context.message || '(nothing)')}
1566
+ `));
1567
+ }
1568
+ /*
1569
+ TODO: [0] Is there some more elegant way to check circular references?
1570
+ const seen = new Set();
1571
+ const stack = [{ value }];
1572
+ while (stack.length > 0) {
1573
+ const { value } = stack.pop()!;
1574
+ if (typeof value === 'object' && value !== null) {
1575
+ if (seen.has(value)) {
1576
+ throw new UnexpectedError(`${name} has circular reference`);
1460
1577
  }
1461
- /*
1462
- TODO: [0] Is there some more elegant way to check circular references?
1463
- const seen = new Set();
1464
- const stack = [{ value }];
1465
- while (stack.length > 0) {
1466
- const { value } = stack.pop()!;
1467
- if (typeof value === 'object' && value !== null) {
1468
- if (seen.has(value)) {
1469
- throw new UnexpectedError(`${name} has circular reference`);
1470
- }
1471
- seen.add(value);
1472
- if (Array.isArray(value)) {
1473
- stack.push(...value.map((value) => ({ value })));
1474
- } else {
1475
- stack.push(...Object.values(value).map((value) => ({ value })));
1476
- }
1477
- }
1578
+ seen.add(value);
1579
+ if (Array.isArray(value)) {
1580
+ stack.push(...value.map((value) => ({ value })));
1581
+ } else {
1582
+ stack.push(...Object.values(value).map((value) => ({ value })));
1478
1583
  }
1479
- */
1480
- return;
1481
1584
  }
1482
1585
  }
1483
- else {
1484
- throw new UnexpectedError(spaceTrim$1((block) => `
1485
- \`${name}\` is unknown type
1586
+ */
1587
+ }
1588
+ /**
1589
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
1590
+ *
1591
+ * @private function of `checkSerializableAsJson`
1592
+ */
1593
+ function throwUnknownTypeError(context) {
1594
+ throw new UnexpectedError(spaceTrim$1((block) => `
1595
+ \`${context.name}\` is unknown type
1486
1596
 
1487
- Additional message for \`${name}\`:
1488
- ${block(message || '(nothing)')}
1489
- `));
1490
- }
1597
+ Additional message for \`${context.name}\`:
1598
+ ${block(context.message || '(nothing)')}
1599
+ `));
1491
1600
  }
1492
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1493
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1494
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1495
1601
 
1496
1602
  /**
1497
1603
  * Creates a deep clone of the given object