@promptbook/editable 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
@@ -457,28 +457,28 @@ npx ts-node ./src/cli/test/ptbk.ts coder verify
457
457
 
458
458
  #### Using `ptbk coder` in an external project
459
459
 
460
- 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`.
460
+ If you want to use the workflow in another repository, install the package and invoke the `ptbk` binary directly.
461
461
 
462
462
  ```bash
463
463
  npm install ptbk
464
464
 
465
465
  ptbk coder init
466
466
 
467
- npx ptbk coder generate-boilerplates
467
+ ptbk coder generate-boilerplates
468
468
 
469
- npx ptbk coder generate-boilerplates --template prompts/templates/common.md
469
+ ptbk coder generate-boilerplates --template prompts/templates/common.md
470
470
 
471
- npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --test npm run test
471
+ ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --test npm run test
472
472
 
473
- npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --auto-push
473
+ ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --auto-push
474
474
 
475
- 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
475
+ 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
476
 
477
- npx ptbk coder find-refactor-candidates
477
+ ptbk coder find-refactor-candidates
478
478
 
479
- npx ptbk coder find-refactor-candidates --level xhigh
479
+ ptbk coder find-refactor-candidates --level xhigh
480
480
 
481
- npx ptbk coder verify
481
+ ptbk coder verify
482
482
  ```
483
483
 
484
484
  `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
@@ -17,7 +17,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
17
17
  * @generated
18
18
  * @see https://github.com/webgptorg/promptbook
19
19
  */
20
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
20
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-80';
21
21
  /**
22
22
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
23
23
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -267,6 +267,111 @@ function checkChannelValue(channelName, value) {
267
267
  }
268
268
  }
269
269
 
270
+ /**
271
+ * Shared immutable channel storage and serialization helpers for `Color`.
272
+ *
273
+ * @private base class of Color
274
+ */
275
+ class ColorValue {
276
+ constructor(red, green, blue, alpha = 255) {
277
+ this.red = red;
278
+ this.green = green;
279
+ this.blue = blue;
280
+ this.alpha = alpha;
281
+ checkChannelValue('Red', red);
282
+ checkChannelValue('Green', green);
283
+ checkChannelValue('Blue', blue);
284
+ checkChannelValue('Alpha', alpha);
285
+ }
286
+ /**
287
+ * Shortcut for `red` property
288
+ * Number from 0 to 255
289
+ * @alias red
290
+ */
291
+ get r() {
292
+ return this.red;
293
+ }
294
+ /**
295
+ * Shortcut for `green` property
296
+ * Number from 0 to 255
297
+ * @alias green
298
+ */
299
+ get g() {
300
+ return this.green;
301
+ }
302
+ /**
303
+ * Shortcut for `blue` property
304
+ * Number from 0 to 255
305
+ * @alias blue
306
+ */
307
+ get b() {
308
+ return this.blue;
309
+ }
310
+ /**
311
+ * Shortcut for `alpha` property
312
+ * Number from 0 (transparent) to 255 (opaque)
313
+ * @alias alpha
314
+ */
315
+ get a() {
316
+ return this.alpha;
317
+ }
318
+ /**
319
+ * Shortcut for `alpha` property
320
+ * Number from 0 (transparent) to 255 (opaque)
321
+ * @alias alpha
322
+ */
323
+ get opacity() {
324
+ return this.alpha;
325
+ }
326
+ /**
327
+ * Shortcut for 1-`alpha` property
328
+ */
329
+ get transparency() {
330
+ return 255 - this.alpha;
331
+ }
332
+ clone() {
333
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
334
+ }
335
+ toString() {
336
+ return this.toHex();
337
+ }
338
+ toHex() {
339
+ if (this.alpha === 255) {
340
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
341
+ .toString(16)
342
+ .padStart(2, '0')}`;
343
+ }
344
+ else {
345
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
346
+ .toString(16)
347
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
348
+ }
349
+ }
350
+ toRgb() {
351
+ if (this.alpha === 255) {
352
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
353
+ }
354
+ else {
355
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
356
+ }
357
+ }
358
+ toHsl() {
359
+ throw new Error(`Getting HSL is not implemented`);
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Checks if the given value is a valid hex color string
365
+ *
366
+ * @param value - value to check
367
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
368
+ *
369
+ * @private function of Color
370
+ */
371
+ function isHexColorString(value) {
372
+ 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));
373
+ }
374
+
270
375
  /**
271
376
  * Constant for short hex lengths.
272
377
  */
@@ -478,16 +583,53 @@ function parseAlphaValue(value) {
478
583
 
479
584
  /**
480
585
  * Pattern matching hsl regex.
586
+ *
587
+ * @private function of Color
481
588
  */
482
589
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
483
590
  /**
484
591
  * Pattern matching RGB regex.
592
+ *
593
+ * @private function of Color
485
594
  */
486
595
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
487
596
  /**
488
597
  * Pattern matching rgba regex.
598
+ *
599
+ * @private function of Color
489
600
  */
490
601
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
602
+ /**
603
+ * Parses a supported color string into RGBA channels.
604
+ *
605
+ * @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`,...
606
+ * @returns RGBA channel values.
607
+ *
608
+ * @private function of Color
609
+ */
610
+ function parseColorString(color) {
611
+ const trimmed = color.trim();
612
+ const cssColor = CSS_COLORS[trimmed];
613
+ if (cssColor) {
614
+ return parseColorString(cssColor);
615
+ }
616
+ else if (isHexColorString(trimmed)) {
617
+ return parseHexColor(trimmed);
618
+ }
619
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
620
+ return parseHslColor(trimmed);
621
+ }
622
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
623
+ return parseRgbColor(trimmed);
624
+ }
625
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
626
+ return parseRgbaColor(trimmed);
627
+ }
628
+ else {
629
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
630
+ }
631
+ }
632
+
491
633
  /**
492
634
  * Color object represents an RGB color with alpha channel
493
635
  *
@@ -495,7 +637,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
495
637
  *
496
638
  * @public exported from `@promptbook/color`
497
639
  */
498
- class Color {
640
+ class Color extends ColorValue {
499
641
  /**
500
642
  * Creates a new Color instance from miscellaneous formats
501
643
  * - It can receive Color instance and just return the same instance
@@ -568,25 +710,7 @@ class Color {
568
710
  * @returns Color object
569
711
  */
570
712
  static fromString(color) {
571
- const trimmed = color.trim();
572
- if (CSS_COLORS[trimmed]) {
573
- return Color.fromString(CSS_COLORS[trimmed]);
574
- }
575
- else if (Color.isHexColorString(trimmed)) {
576
- return Color.fromHex(trimmed);
577
- }
578
- if (HSL_REGEX_PATTERN.test(trimmed)) {
579
- return Color.fromHsl(trimmed);
580
- }
581
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
582
- return Color.fromRgbString(trimmed);
583
- }
584
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
585
- return Color.fromRgbaString(trimmed);
586
- }
587
- else {
588
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
589
- }
713
+ return Color.fromColorChannels(parseColorString(color));
590
714
  }
591
715
  /**
592
716
  * Gets common color
@@ -616,8 +740,7 @@ class Color {
616
740
  * @returns Color object
617
741
  */
618
742
  static fromHex(hex) {
619
- const { red, green, blue, alpha } = parseHexColor(hex);
620
- return take(new Color(red, green, blue, alpha));
743
+ return Color.fromColorChannels(parseHexColor(hex));
621
744
  }
622
745
  /**
623
746
  * Creates a new Color instance from color in hsl format
@@ -626,8 +749,7 @@ class Color {
626
749
  * @returns Color object
627
750
  */
628
751
  static fromHsl(hsl) {
629
- const { red, green, blue, alpha } = parseHslColor(hsl);
630
- return take(new Color(red, green, blue, alpha));
752
+ return Color.fromColorChannels(parseHslColor(hsl));
631
753
  }
632
754
  /**
633
755
  * Creates a new Color instance from color in rgb format
@@ -636,8 +758,7 @@ class Color {
636
758
  * @returns Color object
637
759
  */
638
760
  static fromRgbString(rgb) {
639
- const { red, green, blue, alpha } = parseRgbColor(rgb);
640
- return take(new Color(red, green, blue, alpha));
761
+ return Color.fromColorChannels(parseRgbColor(rgb));
641
762
  }
642
763
  /**
643
764
  * Creates a new Color instance from color in rbga format
@@ -646,8 +767,7 @@ class Color {
646
767
  * @returns Color object
647
768
  */
648
769
  static fromRgbaString(rgba) {
649
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
650
- return take(new Color(red, green, blue, alpha));
770
+ return Color.fromColorChannels(parseRgbaColor(rgba));
651
771
  }
652
772
  /**
653
773
  * Creates a new Color for color channels values
@@ -659,7 +779,7 @@ class Color {
659
779
  * @returns Color object
660
780
  */
661
781
  static fromValues(red, green, blue, alpha = 255) {
662
- return take(new Color(red, green, blue, alpha));
782
+ return Color.fromColorChannels({ red, green, blue, alpha });
663
783
  }
664
784
  /**
665
785
  * Checks if the given value is a valid Color object.
@@ -692,8 +812,7 @@ class Color {
692
812
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
693
813
  */
694
814
  static isHexColorString(value) {
695
- return (typeof value === 'string' &&
696
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
815
+ return isHexColorString(value);
697
816
  }
698
817
  /**
699
818
  * Creates new Color object
@@ -706,89 +825,13 @@ class Color {
706
825
  * @param alpha number from 0 (transparent) to 255 (opaque)
707
826
  */
708
827
  constructor(red, green, blue, alpha = 255) {
709
- this.red = red;
710
- this.green = green;
711
- this.blue = blue;
712
- this.alpha = alpha;
713
- checkChannelValue('Red', red);
714
- checkChannelValue('Green', green);
715
- checkChannelValue('Blue', blue);
716
- checkChannelValue('Alpha', alpha);
828
+ super(red, green, blue, alpha);
717
829
  }
718
- /**
719
- * Shortcut for `red` property
720
- * Number from 0 to 255
721
- * @alias red
722
- */
723
- get r() {
724
- return this.red;
830
+ createColor(red, green, blue, alpha) {
831
+ return new Color(red, green, blue, alpha);
725
832
  }
726
- /**
727
- * Shortcut for `green` property
728
- * Number from 0 to 255
729
- * @alias green
730
- */
731
- get g() {
732
- return this.green;
733
- }
734
- /**
735
- * Shortcut for `blue` property
736
- * Number from 0 to 255
737
- * @alias blue
738
- */
739
- get b() {
740
- return this.blue;
741
- }
742
- /**
743
- * Shortcut for `alpha` property
744
- * Number from 0 (transparent) to 255 (opaque)
745
- * @alias alpha
746
- */
747
- get a() {
748
- return this.alpha;
749
- }
750
- /**
751
- * Shortcut for `alpha` property
752
- * Number from 0 (transparent) to 255 (opaque)
753
- * @alias alpha
754
- */
755
- get opacity() {
756
- return this.alpha;
757
- }
758
- /**
759
- * Shortcut for 1-`alpha` property
760
- */
761
- get transparency() {
762
- return 255 - this.alpha;
763
- }
764
- clone() {
765
- return take(new Color(this.red, this.green, this.blue, this.alpha));
766
- }
767
- toString() {
768
- return this.toHex();
769
- }
770
- toHex() {
771
- if (this.alpha === 255) {
772
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
773
- .toString(16)
774
- .padStart(2, '0')}`;
775
- }
776
- else {
777
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
778
- .toString(16)
779
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
780
- }
781
- }
782
- toRgb() {
783
- if (this.alpha === 255) {
784
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
785
- }
786
- else {
787
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
788
- }
789
- }
790
- toHsl() {
791
- throw new Error(`Getting HSL is not implemented`);
833
+ static fromColorChannels({ red, green, blue, alpha }) {
834
+ return take(new Color(red, green, blue, alpha));
792
835
  }
793
836
  }
794
837
 
@@ -2170,120 +2213,183 @@ function $deepFreeze(objectValue) {
2170
2213
  * @public exported from `@promptbook/utils`
2171
2214
  */
2172
2215
  function checkSerializableAsJson(options) {
2173
- const { value, name, message } = options;
2216
+ checkSerializableValue(options);
2217
+ }
2218
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2219
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2220
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2221
+ /**
2222
+ * Checks one value and dispatches to the appropriate specialized validator.
2223
+ *
2224
+ * @private function of `checkSerializableAsJson`
2225
+ */
2226
+ function checkSerializableValue(options) {
2227
+ const { value } = options;
2228
+ if (isSerializablePrimitive(value)) {
2229
+ return;
2230
+ }
2174
2231
  if (value === undefined) {
2175
- throw new UnexpectedError(`${name} is undefined`);
2232
+ throw new UnexpectedError(`${options.name} is undefined`);
2176
2233
  }
2177
- else if (value === null) {
2178
- return;
2234
+ if (typeof value === 'symbol') {
2235
+ throw new UnexpectedError(`${options.name} is symbol`);
2179
2236
  }
2180
- else if (typeof value === 'boolean') {
2181
- return;
2237
+ if (typeof value === 'function') {
2238
+ throw new UnexpectedError(`${options.name} is function`);
2182
2239
  }
2183
- else if (typeof value === 'number' && !isNaN(value)) {
2240
+ if (Array.isArray(value)) {
2241
+ checkSerializableArray(options, value);
2184
2242
  return;
2185
2243
  }
2186
- else if (typeof value === 'string') {
2244
+ if (value !== null && typeof value === 'object') {
2245
+ checkSerializableObject(options, value);
2187
2246
  return;
2188
2247
  }
2189
- else if (typeof value === 'symbol') {
2190
- throw new UnexpectedError(`${name} is symbol`);
2191
- }
2192
- else if (typeof value === 'function') {
2193
- throw new UnexpectedError(`${name} is function`);
2194
- }
2195
- else if (typeof value === 'object' && Array.isArray(value)) {
2196
- for (let i = 0; i < value.length; i++) {
2197
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
2198
- }
2248
+ throwUnknownTypeError(options);
2249
+ }
2250
+ /**
2251
+ * Checks the primitive values that are directly JSON serializable.
2252
+ *
2253
+ * @private function of `checkSerializableAsJson`
2254
+ */
2255
+ function isSerializablePrimitive(value) {
2256
+ return (value === null ||
2257
+ typeof value === 'boolean' ||
2258
+ (typeof value === 'number' && !isNaN(value)) ||
2259
+ typeof value === 'string');
2260
+ }
2261
+ /**
2262
+ * Recursively checks JSON array items.
2263
+ *
2264
+ * @private function of `checkSerializableAsJson`
2265
+ */
2266
+ function checkSerializableArray(context, arrayValue) {
2267
+ for (let index = 0; index < arrayValue.length; index++) {
2268
+ checkSerializableAsJson({
2269
+ ...context,
2270
+ name: `${context.name}[${index}]`,
2271
+ value: arrayValue[index],
2272
+ });
2199
2273
  }
2200
- else if (typeof value === 'object') {
2201
- if (value instanceof Date) {
2202
- throw new UnexpectedError(spaceTrim$1((block) => `
2203
- \`${name}\` is Date
2274
+ }
2275
+ /**
2276
+ * Checks object-like values and dispatches special unsupported built-ins.
2277
+ *
2278
+ * @private function of `checkSerializableAsJson`
2279
+ */
2280
+ function checkSerializableObject(context, objectValue) {
2281
+ checkUnsupportedObjectType(context, objectValue);
2282
+ checkSerializableObjectEntries(context, objectValue);
2283
+ assertJsonStringificationSucceeds(context, objectValue);
2284
+ }
2285
+ /**
2286
+ * Rejects built-in objects that must be converted before JSON serialization.
2287
+ *
2288
+ * @private function of `checkSerializableAsJson`
2289
+ */
2290
+ function checkUnsupportedObjectType(context, objectValue) {
2291
+ if (objectValue instanceof Date) {
2292
+ throw new UnexpectedError(spaceTrim$1((block) => `
2293
+ \`${context.name}\` is Date
2204
2294
 
2205
- Use \`string_date_iso8601\` instead
2295
+ Use \`string_date_iso8601\` instead
2206
2296
 
2207
- Additional message for \`${name}\`:
2208
- ${block(message || '(nothing)')}
2209
- `));
2210
- }
2211
- else if (value instanceof Map) {
2212
- throw new UnexpectedError(`${name} is Map`);
2213
- }
2214
- else if (value instanceof Set) {
2215
- throw new UnexpectedError(`${name} is Set`);
2216
- }
2217
- else if (value instanceof RegExp) {
2218
- throw new UnexpectedError(`${name} is RegExp`);
2219
- }
2220
- else if (value instanceof Error) {
2221
- throw new UnexpectedError(spaceTrim$1((block) => `
2222
- \`${name}\` is unserialized Error
2297
+ Additional message for \`${context.name}\`:
2298
+ ${block(context.message || '(nothing)')}
2299
+ `));
2300
+ }
2301
+ if (objectValue instanceof Map) {
2302
+ throw new UnexpectedError(`${context.name} is Map`);
2303
+ }
2304
+ if (objectValue instanceof Set) {
2305
+ throw new UnexpectedError(`${context.name} is Set`);
2306
+ }
2307
+ if (objectValue instanceof RegExp) {
2308
+ throw new UnexpectedError(`${context.name} is RegExp`);
2309
+ }
2310
+ if (objectValue instanceof Error) {
2311
+ throw new UnexpectedError(spaceTrim$1((block) => `
2312
+ \`${context.name}\` is unserialized Error
2223
2313
 
2224
- Use function \`serializeError\`
2314
+ Use function \`serializeError\`
2225
2315
 
2226
- Additional message for \`${name}\`:
2227
- ${block(message || '(nothing)')}
2316
+ Additional message for \`${context.name}\`:
2317
+ ${block(context.message || '(nothing)')}
2228
2318
 
2229
- `));
2319
+ `));
2320
+ }
2321
+ }
2322
+ /**
2323
+ * Recursively checks object properties while preserving omitted `undefined` keys.
2324
+ *
2325
+ * @private function of `checkSerializableAsJson`
2326
+ */
2327
+ function checkSerializableObjectEntries(context, objectValue) {
2328
+ for (const [subName, subValue] of Object.entries(objectValue)) {
2329
+ if (subValue === undefined) {
2330
+ // Note: undefined in object is serializable - it is just omitted
2331
+ continue;
2230
2332
  }
2231
- else {
2232
- for (const [subName, subValue] of Object.entries(value)) {
2233
- if (subValue === undefined) {
2234
- // Note: undefined in object is serializable - it is just omitted
2235
- continue;
2236
- }
2237
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
2238
- }
2239
- try {
2240
- JSON.stringify(value); // <- TODO: [0]
2241
- }
2242
- catch (error) {
2243
- assertsError(error);
2244
- throw new UnexpectedError(spaceTrim$1((block) => `
2245
- \`${name}\` is not serializable
2333
+ checkSerializableAsJson({
2334
+ ...context,
2335
+ name: `${context.name}.${subName}`,
2336
+ value: subValue,
2337
+ });
2338
+ }
2339
+ }
2340
+ /**
2341
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
2342
+ *
2343
+ * @private function of `checkSerializableAsJson`
2344
+ */
2345
+ function assertJsonStringificationSucceeds(context, objectValue) {
2346
+ try {
2347
+ JSON.stringify(objectValue); // <- TODO: [0]
2348
+ }
2349
+ catch (error) {
2350
+ assertsError(error);
2351
+ throw new UnexpectedError(spaceTrim$1((block) => `
2352
+ \`${context.name}\` is not serializable
2246
2353
 
2247
- ${block(error.stack || error.message)}
2354
+ ${block(error.stack || error.message)}
2248
2355
 
2249
- Additional message for \`${name}\`:
2250
- ${block(message || '(nothing)')}
2251
- `));
2356
+ Additional message for \`${context.name}\`:
2357
+ ${block(context.message || '(nothing)')}
2358
+ `));
2359
+ }
2360
+ /*
2361
+ TODO: [0] Is there some more elegant way to check circular references?
2362
+ const seen = new Set();
2363
+ const stack = [{ value }];
2364
+ while (stack.length > 0) {
2365
+ const { value } = stack.pop()!;
2366
+ if (typeof value === 'object' && value !== null) {
2367
+ if (seen.has(value)) {
2368
+ throw new UnexpectedError(`${name} has circular reference`);
2252
2369
  }
2253
- /*
2254
- TODO: [0] Is there some more elegant way to check circular references?
2255
- const seen = new Set();
2256
- const stack = [{ value }];
2257
- while (stack.length > 0) {
2258
- const { value } = stack.pop()!;
2259
- if (typeof value === 'object' && value !== null) {
2260
- if (seen.has(value)) {
2261
- throw new UnexpectedError(`${name} has circular reference`);
2262
- }
2263
- seen.add(value);
2264
- if (Array.isArray(value)) {
2265
- stack.push(...value.map((value) => ({ value })));
2266
- } else {
2267
- stack.push(...Object.values(value).map((value) => ({ value })));
2268
- }
2269
- }
2370
+ seen.add(value);
2371
+ if (Array.isArray(value)) {
2372
+ stack.push(...value.map((value) => ({ value })));
2373
+ } else {
2374
+ stack.push(...Object.values(value).map((value) => ({ value })));
2270
2375
  }
2271
- */
2272
- return;
2273
2376
  }
2274
2377
  }
2275
- else {
2276
- throw new UnexpectedError(spaceTrim$1((block) => `
2277
- \`${name}\` is unknown type
2378
+ */
2379
+ }
2380
+ /**
2381
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
2382
+ *
2383
+ * @private function of `checkSerializableAsJson`
2384
+ */
2385
+ function throwUnknownTypeError(context) {
2386
+ throw new UnexpectedError(spaceTrim$1((block) => `
2387
+ \`${context.name}\` is unknown type
2278
2388
 
2279
- Additional message for \`${name}\`:
2280
- ${block(message || '(nothing)')}
2281
- `));
2282
- }
2389
+ Additional message for \`${context.name}\`:
2390
+ ${block(context.message || '(nothing)')}
2391
+ `));
2283
2392
  }
2284
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2285
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2286
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2287
2393
 
2288
2394
  /**
2289
2395
  * Creates a deep clone of the given object