@promptbook/vercel 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
@@ -515,28 +515,28 @@ npx ts-node ./src/cli/test/ptbk.ts coder verify
515
515
 
516
516
  #### Using `ptbk coder` in an external project
517
517
 
518
- 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`.
518
+ If you want to use the workflow in another repository, install the package and invoke the `ptbk` binary directly.
519
519
 
520
520
  ```bash
521
521
  npm install ptbk
522
522
 
523
523
  ptbk coder init
524
524
 
525
- npx ptbk coder generate-boilerplates
525
+ ptbk coder generate-boilerplates
526
526
 
527
- npx ptbk coder generate-boilerplates --template prompts/templates/common.md
527
+ ptbk coder generate-boilerplates --template prompts/templates/common.md
528
528
 
529
- npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --test npm run test
529
+ ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --test npm run test
530
530
 
531
- npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --auto-push
531
+ ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --auto-push
532
532
 
533
- 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
533
+ 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
534
534
 
535
- npx ptbk coder find-refactor-candidates
535
+ ptbk coder find-refactor-candidates
536
536
 
537
- npx ptbk coder find-refactor-candidates --level xhigh
537
+ ptbk coder find-refactor-candidates --level xhigh
538
538
 
539
- npx ptbk coder verify
539
+ ptbk coder verify
540
540
  ```
541
541
 
542
542
  `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
@@ -16,7 +16,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
16
16
  * @generated
17
17
  * @see https://github.com/webgptorg/promptbook
18
18
  */
19
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
19
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-79';
20
20
  /**
21
21
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
22
22
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -818,6 +818,111 @@ function checkChannelValue(channelName, value) {
818
818
  }
819
819
  }
820
820
 
821
+ /**
822
+ * Shared immutable channel storage and serialization helpers for `Color`.
823
+ *
824
+ * @private base class of Color
825
+ */
826
+ class ColorValue {
827
+ constructor(red, green, blue, alpha = 255) {
828
+ this.red = red;
829
+ this.green = green;
830
+ this.blue = blue;
831
+ this.alpha = alpha;
832
+ checkChannelValue('Red', red);
833
+ checkChannelValue('Green', green);
834
+ checkChannelValue('Blue', blue);
835
+ checkChannelValue('Alpha', alpha);
836
+ }
837
+ /**
838
+ * Shortcut for `red` property
839
+ * Number from 0 to 255
840
+ * @alias red
841
+ */
842
+ get r() {
843
+ return this.red;
844
+ }
845
+ /**
846
+ * Shortcut for `green` property
847
+ * Number from 0 to 255
848
+ * @alias green
849
+ */
850
+ get g() {
851
+ return this.green;
852
+ }
853
+ /**
854
+ * Shortcut for `blue` property
855
+ * Number from 0 to 255
856
+ * @alias blue
857
+ */
858
+ get b() {
859
+ return this.blue;
860
+ }
861
+ /**
862
+ * Shortcut for `alpha` property
863
+ * Number from 0 (transparent) to 255 (opaque)
864
+ * @alias alpha
865
+ */
866
+ get a() {
867
+ return this.alpha;
868
+ }
869
+ /**
870
+ * Shortcut for `alpha` property
871
+ * Number from 0 (transparent) to 255 (opaque)
872
+ * @alias alpha
873
+ */
874
+ get opacity() {
875
+ return this.alpha;
876
+ }
877
+ /**
878
+ * Shortcut for 1-`alpha` property
879
+ */
880
+ get transparency() {
881
+ return 255 - this.alpha;
882
+ }
883
+ clone() {
884
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
885
+ }
886
+ toString() {
887
+ return this.toHex();
888
+ }
889
+ toHex() {
890
+ if (this.alpha === 255) {
891
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
892
+ .toString(16)
893
+ .padStart(2, '0')}`;
894
+ }
895
+ else {
896
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
897
+ .toString(16)
898
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
899
+ }
900
+ }
901
+ toRgb() {
902
+ if (this.alpha === 255) {
903
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
904
+ }
905
+ else {
906
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
907
+ }
908
+ }
909
+ toHsl() {
910
+ throw new Error(`Getting HSL is not implemented`);
911
+ }
912
+ }
913
+
914
+ /**
915
+ * Checks if the given value is a valid hex color string
916
+ *
917
+ * @param value - value to check
918
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
919
+ *
920
+ * @private function of Color
921
+ */
922
+ function isHexColorString(value) {
923
+ 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));
924
+ }
925
+
821
926
  /**
822
927
  * Constant for short hex lengths.
823
928
  */
@@ -1029,16 +1134,53 @@ function parseAlphaValue(value) {
1029
1134
 
1030
1135
  /**
1031
1136
  * Pattern matching hsl regex.
1137
+ *
1138
+ * @private function of Color
1032
1139
  */
1033
1140
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
1034
1141
  /**
1035
1142
  * Pattern matching RGB regex.
1143
+ *
1144
+ * @private function of Color
1036
1145
  */
1037
1146
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
1038
1147
  /**
1039
1148
  * Pattern matching rgba regex.
1149
+ *
1150
+ * @private function of Color
1040
1151
  */
1041
1152
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
1153
+ /**
1154
+ * Parses a supported color string into RGBA channels.
1155
+ *
1156
+ * @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`,...
1157
+ * @returns RGBA channel values.
1158
+ *
1159
+ * @private function of Color
1160
+ */
1161
+ function parseColorString(color) {
1162
+ const trimmed = color.trim();
1163
+ const cssColor = CSS_COLORS[trimmed];
1164
+ if (cssColor) {
1165
+ return parseColorString(cssColor);
1166
+ }
1167
+ else if (isHexColorString(trimmed)) {
1168
+ return parseHexColor(trimmed);
1169
+ }
1170
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
1171
+ return parseHslColor(trimmed);
1172
+ }
1173
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
1174
+ return parseRgbColor(trimmed);
1175
+ }
1176
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
1177
+ return parseRgbaColor(trimmed);
1178
+ }
1179
+ else {
1180
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
1181
+ }
1182
+ }
1183
+
1042
1184
  /**
1043
1185
  * Color object represents an RGB color with alpha channel
1044
1186
  *
@@ -1046,7 +1188,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
1046
1188
  *
1047
1189
  * @public exported from `@promptbook/color`
1048
1190
  */
1049
- class Color {
1191
+ class Color extends ColorValue {
1050
1192
  /**
1051
1193
  * Creates a new Color instance from miscellaneous formats
1052
1194
  * - It can receive Color instance and just return the same instance
@@ -1119,25 +1261,7 @@ class Color {
1119
1261
  * @returns Color object
1120
1262
  */
1121
1263
  static fromString(color) {
1122
- const trimmed = color.trim();
1123
- if (CSS_COLORS[trimmed]) {
1124
- return Color.fromString(CSS_COLORS[trimmed]);
1125
- }
1126
- else if (Color.isHexColorString(trimmed)) {
1127
- return Color.fromHex(trimmed);
1128
- }
1129
- if (HSL_REGEX_PATTERN.test(trimmed)) {
1130
- return Color.fromHsl(trimmed);
1131
- }
1132
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
1133
- return Color.fromRgbString(trimmed);
1134
- }
1135
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
1136
- return Color.fromRgbaString(trimmed);
1137
- }
1138
- else {
1139
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
1140
- }
1264
+ return Color.fromColorChannels(parseColorString(color));
1141
1265
  }
1142
1266
  /**
1143
1267
  * Gets common color
@@ -1167,8 +1291,7 @@ class Color {
1167
1291
  * @returns Color object
1168
1292
  */
1169
1293
  static fromHex(hex) {
1170
- const { red, green, blue, alpha } = parseHexColor(hex);
1171
- return take(new Color(red, green, blue, alpha));
1294
+ return Color.fromColorChannels(parseHexColor(hex));
1172
1295
  }
1173
1296
  /**
1174
1297
  * Creates a new Color instance from color in hsl format
@@ -1177,8 +1300,7 @@ class Color {
1177
1300
  * @returns Color object
1178
1301
  */
1179
1302
  static fromHsl(hsl) {
1180
- const { red, green, blue, alpha } = parseHslColor(hsl);
1181
- return take(new Color(red, green, blue, alpha));
1303
+ return Color.fromColorChannels(parseHslColor(hsl));
1182
1304
  }
1183
1305
  /**
1184
1306
  * Creates a new Color instance from color in rgb format
@@ -1187,8 +1309,7 @@ class Color {
1187
1309
  * @returns Color object
1188
1310
  */
1189
1311
  static fromRgbString(rgb) {
1190
- const { red, green, blue, alpha } = parseRgbColor(rgb);
1191
- return take(new Color(red, green, blue, alpha));
1312
+ return Color.fromColorChannels(parseRgbColor(rgb));
1192
1313
  }
1193
1314
  /**
1194
1315
  * Creates a new Color instance from color in rbga format
@@ -1197,8 +1318,7 @@ class Color {
1197
1318
  * @returns Color object
1198
1319
  */
1199
1320
  static fromRgbaString(rgba) {
1200
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
1201
- return take(new Color(red, green, blue, alpha));
1321
+ return Color.fromColorChannels(parseRgbaColor(rgba));
1202
1322
  }
1203
1323
  /**
1204
1324
  * Creates a new Color for color channels values
@@ -1210,7 +1330,7 @@ class Color {
1210
1330
  * @returns Color object
1211
1331
  */
1212
1332
  static fromValues(red, green, blue, alpha = 255) {
1213
- return take(new Color(red, green, blue, alpha));
1333
+ return Color.fromColorChannels({ red, green, blue, alpha });
1214
1334
  }
1215
1335
  /**
1216
1336
  * Checks if the given value is a valid Color object.
@@ -1243,8 +1363,7 @@ class Color {
1243
1363
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
1244
1364
  */
1245
1365
  static isHexColorString(value) {
1246
- return (typeof value === 'string' &&
1247
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
1366
+ return isHexColorString(value);
1248
1367
  }
1249
1368
  /**
1250
1369
  * Creates new Color object
@@ -1257,89 +1376,13 @@ class Color {
1257
1376
  * @param alpha number from 0 (transparent) to 255 (opaque)
1258
1377
  */
1259
1378
  constructor(red, green, blue, alpha = 255) {
1260
- this.red = red;
1261
- this.green = green;
1262
- this.blue = blue;
1263
- this.alpha = alpha;
1264
- checkChannelValue('Red', red);
1265
- checkChannelValue('Green', green);
1266
- checkChannelValue('Blue', blue);
1267
- checkChannelValue('Alpha', alpha);
1379
+ super(red, green, blue, alpha);
1268
1380
  }
1269
- /**
1270
- * Shortcut for `red` property
1271
- * Number from 0 to 255
1272
- * @alias red
1273
- */
1274
- get r() {
1275
- return this.red;
1381
+ createColor(red, green, blue, alpha) {
1382
+ return new Color(red, green, blue, alpha);
1276
1383
  }
1277
- /**
1278
- * Shortcut for `green` property
1279
- * Number from 0 to 255
1280
- * @alias green
1281
- */
1282
- get g() {
1283
- return this.green;
1284
- }
1285
- /**
1286
- * Shortcut for `blue` property
1287
- * Number from 0 to 255
1288
- * @alias blue
1289
- */
1290
- get b() {
1291
- return this.blue;
1292
- }
1293
- /**
1294
- * Shortcut for `alpha` property
1295
- * Number from 0 (transparent) to 255 (opaque)
1296
- * @alias alpha
1297
- */
1298
- get a() {
1299
- return this.alpha;
1300
- }
1301
- /**
1302
- * Shortcut for `alpha` property
1303
- * Number from 0 (transparent) to 255 (opaque)
1304
- * @alias alpha
1305
- */
1306
- get opacity() {
1307
- return this.alpha;
1308
- }
1309
- /**
1310
- * Shortcut for 1-`alpha` property
1311
- */
1312
- get transparency() {
1313
- return 255 - this.alpha;
1314
- }
1315
- clone() {
1316
- return take(new Color(this.red, this.green, this.blue, this.alpha));
1317
- }
1318
- toString() {
1319
- return this.toHex();
1320
- }
1321
- toHex() {
1322
- if (this.alpha === 255) {
1323
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
1324
- .toString(16)
1325
- .padStart(2, '0')}`;
1326
- }
1327
- else {
1328
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
1329
- .toString(16)
1330
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
1331
- }
1332
- }
1333
- toRgb() {
1334
- if (this.alpha === 255) {
1335
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
1336
- }
1337
- else {
1338
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
1339
- }
1340
- }
1341
- toHsl() {
1342
- throw new Error(`Getting HSL is not implemented`);
1384
+ static fromColorChannels({ red, green, blue, alpha }) {
1385
+ return take(new Color(red, green, blue, alpha));
1343
1386
  }
1344
1387
  }
1345
1388
 
@@ -1813,120 +1856,183 @@ function assertsError(whatWasThrown) {
1813
1856
  * @public exported from `@promptbook/utils`
1814
1857
  */
1815
1858
  function checkSerializableAsJson(options) {
1816
- const { value, name, message } = options;
1859
+ checkSerializableValue(options);
1860
+ }
1861
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1862
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1863
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1864
+ /**
1865
+ * Checks one value and dispatches to the appropriate specialized validator.
1866
+ *
1867
+ * @private function of `checkSerializableAsJson`
1868
+ */
1869
+ function checkSerializableValue(options) {
1870
+ const { value } = options;
1871
+ if (isSerializablePrimitive(value)) {
1872
+ return;
1873
+ }
1817
1874
  if (value === undefined) {
1818
- throw new UnexpectedError(`${name} is undefined`);
1875
+ throw new UnexpectedError(`${options.name} is undefined`);
1819
1876
  }
1820
- else if (value === null) {
1821
- return;
1877
+ if (typeof value === 'symbol') {
1878
+ throw new UnexpectedError(`${options.name} is symbol`);
1822
1879
  }
1823
- else if (typeof value === 'boolean') {
1824
- return;
1880
+ if (typeof value === 'function') {
1881
+ throw new UnexpectedError(`${options.name} is function`);
1825
1882
  }
1826
- else if (typeof value === 'number' && !isNaN(value)) {
1883
+ if (Array.isArray(value)) {
1884
+ checkSerializableArray(options, value);
1827
1885
  return;
1828
1886
  }
1829
- else if (typeof value === 'string') {
1887
+ if (value !== null && typeof value === 'object') {
1888
+ checkSerializableObject(options, value);
1830
1889
  return;
1831
1890
  }
1832
- else if (typeof value === 'symbol') {
1833
- throw new UnexpectedError(`${name} is symbol`);
1834
- }
1835
- else if (typeof value === 'function') {
1836
- throw new UnexpectedError(`${name} is function`);
1837
- }
1838
- else if (typeof value === 'object' && Array.isArray(value)) {
1839
- for (let i = 0; i < value.length; i++) {
1840
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
1841
- }
1891
+ throwUnknownTypeError(options);
1892
+ }
1893
+ /**
1894
+ * Checks the primitive values that are directly JSON serializable.
1895
+ *
1896
+ * @private function of `checkSerializableAsJson`
1897
+ */
1898
+ function isSerializablePrimitive(value) {
1899
+ return (value === null ||
1900
+ typeof value === 'boolean' ||
1901
+ (typeof value === 'number' && !isNaN(value)) ||
1902
+ typeof value === 'string');
1903
+ }
1904
+ /**
1905
+ * Recursively checks JSON array items.
1906
+ *
1907
+ * @private function of `checkSerializableAsJson`
1908
+ */
1909
+ function checkSerializableArray(context, arrayValue) {
1910
+ for (let index = 0; index < arrayValue.length; index++) {
1911
+ checkSerializableAsJson({
1912
+ ...context,
1913
+ name: `${context.name}[${index}]`,
1914
+ value: arrayValue[index],
1915
+ });
1842
1916
  }
1843
- else if (typeof value === 'object') {
1844
- if (value instanceof Date) {
1845
- throw new UnexpectedError(spaceTrim$1((block) => `
1846
- \`${name}\` is Date
1917
+ }
1918
+ /**
1919
+ * Checks object-like values and dispatches special unsupported built-ins.
1920
+ *
1921
+ * @private function of `checkSerializableAsJson`
1922
+ */
1923
+ function checkSerializableObject(context, objectValue) {
1924
+ checkUnsupportedObjectType(context, objectValue);
1925
+ checkSerializableObjectEntries(context, objectValue);
1926
+ assertJsonStringificationSucceeds(context, objectValue);
1927
+ }
1928
+ /**
1929
+ * Rejects built-in objects that must be converted before JSON serialization.
1930
+ *
1931
+ * @private function of `checkSerializableAsJson`
1932
+ */
1933
+ function checkUnsupportedObjectType(context, objectValue) {
1934
+ if (objectValue instanceof Date) {
1935
+ throw new UnexpectedError(spaceTrim$1((block) => `
1936
+ \`${context.name}\` is Date
1847
1937
 
1848
- Use \`string_date_iso8601\` instead
1938
+ Use \`string_date_iso8601\` instead
1849
1939
 
1850
- Additional message for \`${name}\`:
1851
- ${block(message || '(nothing)')}
1852
- `));
1853
- }
1854
- else if (value instanceof Map) {
1855
- throw new UnexpectedError(`${name} is Map`);
1856
- }
1857
- else if (value instanceof Set) {
1858
- throw new UnexpectedError(`${name} is Set`);
1859
- }
1860
- else if (value instanceof RegExp) {
1861
- throw new UnexpectedError(`${name} is RegExp`);
1862
- }
1863
- else if (value instanceof Error) {
1864
- throw new UnexpectedError(spaceTrim$1((block) => `
1865
- \`${name}\` is unserialized Error
1940
+ Additional message for \`${context.name}\`:
1941
+ ${block(context.message || '(nothing)')}
1942
+ `));
1943
+ }
1944
+ if (objectValue instanceof Map) {
1945
+ throw new UnexpectedError(`${context.name} is Map`);
1946
+ }
1947
+ if (objectValue instanceof Set) {
1948
+ throw new UnexpectedError(`${context.name} is Set`);
1949
+ }
1950
+ if (objectValue instanceof RegExp) {
1951
+ throw new UnexpectedError(`${context.name} is RegExp`);
1952
+ }
1953
+ if (objectValue instanceof Error) {
1954
+ throw new UnexpectedError(spaceTrim$1((block) => `
1955
+ \`${context.name}\` is unserialized Error
1866
1956
 
1867
- Use function \`serializeError\`
1957
+ Use function \`serializeError\`
1868
1958
 
1869
- Additional message for \`${name}\`:
1870
- ${block(message || '(nothing)')}
1959
+ Additional message for \`${context.name}\`:
1960
+ ${block(context.message || '(nothing)')}
1871
1961
 
1872
- `));
1962
+ `));
1963
+ }
1964
+ }
1965
+ /**
1966
+ * Recursively checks object properties while preserving omitted `undefined` keys.
1967
+ *
1968
+ * @private function of `checkSerializableAsJson`
1969
+ */
1970
+ function checkSerializableObjectEntries(context, objectValue) {
1971
+ for (const [subName, subValue] of Object.entries(objectValue)) {
1972
+ if (subValue === undefined) {
1973
+ // Note: undefined in object is serializable - it is just omitted
1974
+ continue;
1873
1975
  }
1874
- else {
1875
- for (const [subName, subValue] of Object.entries(value)) {
1876
- if (subValue === undefined) {
1877
- // Note: undefined in object is serializable - it is just omitted
1878
- continue;
1879
- }
1880
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
1881
- }
1882
- try {
1883
- JSON.stringify(value); // <- TODO: [0]
1884
- }
1885
- catch (error) {
1886
- assertsError(error);
1887
- throw new UnexpectedError(spaceTrim$1((block) => `
1888
- \`${name}\` is not serializable
1976
+ checkSerializableAsJson({
1977
+ ...context,
1978
+ name: `${context.name}.${subName}`,
1979
+ value: subValue,
1980
+ });
1981
+ }
1982
+ }
1983
+ /**
1984
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
1985
+ *
1986
+ * @private function of `checkSerializableAsJson`
1987
+ */
1988
+ function assertJsonStringificationSucceeds(context, objectValue) {
1989
+ try {
1990
+ JSON.stringify(objectValue); // <- TODO: [0]
1991
+ }
1992
+ catch (error) {
1993
+ assertsError(error);
1994
+ throw new UnexpectedError(spaceTrim$1((block) => `
1995
+ \`${context.name}\` is not serializable
1889
1996
 
1890
- ${block(error.stack || error.message)}
1997
+ ${block(error.stack || error.message)}
1891
1998
 
1892
- Additional message for \`${name}\`:
1893
- ${block(message || '(nothing)')}
1894
- `));
1999
+ Additional message for \`${context.name}\`:
2000
+ ${block(context.message || '(nothing)')}
2001
+ `));
2002
+ }
2003
+ /*
2004
+ TODO: [0] Is there some more elegant way to check circular references?
2005
+ const seen = new Set();
2006
+ const stack = [{ value }];
2007
+ while (stack.length > 0) {
2008
+ const { value } = stack.pop()!;
2009
+ if (typeof value === 'object' && value !== null) {
2010
+ if (seen.has(value)) {
2011
+ throw new UnexpectedError(`${name} has circular reference`);
1895
2012
  }
1896
- /*
1897
- TODO: [0] Is there some more elegant way to check circular references?
1898
- const seen = new Set();
1899
- const stack = [{ value }];
1900
- while (stack.length > 0) {
1901
- const { value } = stack.pop()!;
1902
- if (typeof value === 'object' && value !== null) {
1903
- if (seen.has(value)) {
1904
- throw new UnexpectedError(`${name} has circular reference`);
1905
- }
1906
- seen.add(value);
1907
- if (Array.isArray(value)) {
1908
- stack.push(...value.map((value) => ({ value })));
1909
- } else {
1910
- stack.push(...Object.values(value).map((value) => ({ value })));
1911
- }
1912
- }
2013
+ seen.add(value);
2014
+ if (Array.isArray(value)) {
2015
+ stack.push(...value.map((value) => ({ value })));
2016
+ } else {
2017
+ stack.push(...Object.values(value).map((value) => ({ value })));
1913
2018
  }
1914
- */
1915
- return;
1916
2019
  }
1917
2020
  }
1918
- else {
1919
- throw new UnexpectedError(spaceTrim$1((block) => `
1920
- \`${name}\` is unknown type
2021
+ */
2022
+ }
2023
+ /**
2024
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
2025
+ *
2026
+ * @private function of `checkSerializableAsJson`
2027
+ */
2028
+ function throwUnknownTypeError(context) {
2029
+ throw new UnexpectedError(spaceTrim$1((block) => `
2030
+ \`${context.name}\` is unknown type
1921
2031
 
1922
- Additional message for \`${name}\`:
1923
- ${block(message || '(nothing)')}
1924
- `));
1925
- }
2032
+ Additional message for \`${context.name}\`:
2033
+ ${block(context.message || '(nothing)')}
2034
+ `));
1926
2035
  }
1927
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1928
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1929
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1930
2036
 
1931
2037
  /**
1932
2038
  * Creates a deep clone of the given object