@voxgig/sdkgen 0.36.0 → 0.37.0

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 (135) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/Main.js +0 -12
  3. package/dist/cmp/Main.js.map +1 -1
  4. package/package.json +1 -1
  5. package/project/.sdk/src/cmp/go/Config_go.ts +6 -2
  6. package/project/.sdk/src/cmp/js/EntityBase_js.ts +34 -0
  7. package/project/.sdk/src/cmp/js/Main_js.ts +10 -0
  8. package/project/.sdk/src/cmp/js/ReadmeExplanation_js.ts +33 -0
  9. package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +123 -0
  10. package/project/.sdk/src/cmp/js/ReadmeModel_js.ts +152 -0
  11. package/project/.sdk/src/cmp/js/ReadmeTopHowto_js.ts +25 -0
  12. package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +65 -0
  13. package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +36 -0
  14. package/project/.sdk/src/cmp/js/TestDirect_js.ts +53 -5
  15. package/project/.sdk/src/cmp/js/TestEntity_js.ts +114 -20
  16. package/project/.sdk/src/cmp/js/fragment/Entity.fragment.js +7 -139
  17. package/project/.sdk/src/cmp/js/fragment/EntityBase.fragment.js +149 -0
  18. package/project/.sdk/src/cmp/js/fragment/EntityCreateOp.fragment.js +6 -10
  19. package/project/.sdk/src/cmp/js/fragment/EntityListOp.fragment.js +6 -10
  20. package/project/.sdk/src/cmp/js/fragment/EntityLoadOp.fragment.js +7 -11
  21. package/project/.sdk/src/cmp/js/fragment/EntityRemoveOp.fragment.js +7 -11
  22. package/project/.sdk/src/cmp/js/fragment/EntityUpdateOp.fragment.js +7 -11
  23. package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +2 -0
  24. package/project/.sdk/src/cmp/js/fragment/SdkError.fragment.js +0 -2
  25. package/project/.sdk/src/cmp/lua/Config_lua.ts +6 -2
  26. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +3 -1
  27. package/project/.sdk/src/cmp/php/Config_php.ts +6 -2
  28. package/project/.sdk/src/cmp/php/TestDirect_php.ts +2 -2
  29. package/project/.sdk/src/cmp/php/TestEntity_php.ts +10 -15
  30. package/project/.sdk/src/cmp/py/Config_py.ts +6 -2
  31. package/project/.sdk/src/cmp/py/TestEntity_py.ts +3 -1
  32. package/project/.sdk/src/cmp/rb/Config_rb.ts +6 -2
  33. package/project/.sdk/src/cmp/ts/Main_ts.ts +7 -0
  34. package/project/.sdk/tm/go/feature/log_feature.go +1 -1
  35. package/project/.sdk/tm/go/test/runner_test.go +16 -2
  36. package/project/.sdk/tm/js/src/Context.js +142 -0
  37. package/project/.sdk/tm/js/src/Control.js +16 -0
  38. package/project/.sdk/tm/js/src/Operation.js +19 -0
  39. package/project/.sdk/tm/js/src/Point.js +24 -0
  40. package/project/.sdk/tm/js/src/README.md +1 -0
  41. package/project/.sdk/tm/js/src/Response.js +19 -0
  42. package/project/.sdk/tm/js/src/Result.js +21 -0
  43. package/project/.sdk/tm/js/src/Spec.js +26 -0
  44. package/project/.sdk/tm/js/src/feature/README.md +1 -0
  45. package/project/.sdk/tm/js/src/feature/base/BaseFeature.js +45 -0
  46. package/project/.sdk/tm/js/src/feature/log/LogFeature.js +46 -47
  47. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +207 -0
  48. package/project/.sdk/tm/js/src/types.js +22 -0
  49. package/project/.sdk/tm/js/src/utility/CleanUtility.js +31 -0
  50. package/project/.sdk/tm/js/src/utility/DoneUtility.js +11 -4
  51. package/project/.sdk/tm/js/src/utility/FeatureAddUtility.js +42 -0
  52. package/project/.sdk/tm/js/src/utility/FeatureHookUtility.js +25 -0
  53. package/project/.sdk/tm/js/src/utility/FeatureInitUtility.js +11 -0
  54. package/project/.sdk/tm/js/src/utility/FetcherUtility.js +28 -0
  55. package/project/.sdk/tm/js/src/utility/MakeContextUtility.js +11 -0
  56. package/project/.sdk/tm/js/src/utility/MakeErrorUtility.js +55 -0
  57. package/project/.sdk/tm/js/src/utility/MakeFetchDefUtility.js +44 -0
  58. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +93 -0
  59. package/project/.sdk/tm/js/src/utility/MakePointUtility.js +77 -0
  60. package/project/.sdk/tm/js/src/utility/MakeRequestUtility.js +63 -0
  61. package/project/.sdk/tm/js/src/utility/MakeResponseUtility.js +55 -0
  62. package/project/.sdk/tm/js/src/utility/MakeResultUtility.js +54 -0
  63. package/project/.sdk/tm/js/src/utility/MakeSpecUtility.js +58 -0
  64. package/project/.sdk/tm/js/src/utility/MakeUrlUtility.js +40 -0
  65. package/project/.sdk/tm/js/src/utility/ParamUtility.js +61 -0
  66. package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +41 -0
  67. package/project/.sdk/tm/js/src/utility/PrepareBodyUtility.js +25 -0
  68. package/project/.sdk/tm/js/src/utility/PrepareHeadersUtility.js +18 -0
  69. package/project/.sdk/tm/js/src/utility/{MethodUtility.js → PrepareMethodUtility.js} +7 -7
  70. package/project/.sdk/tm/js/src/utility/PrepareParamsUtility.js +25 -0
  71. package/project/.sdk/tm/js/src/utility/PreparePathUtility.js +13 -0
  72. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +26 -0
  73. package/project/.sdk/tm/js/src/utility/README.md +1 -0
  74. package/project/.sdk/tm/js/src/utility/ResultBasicUtility.js +34 -0
  75. package/project/.sdk/tm/js/src/utility/ResultBodyUtility.js +18 -0
  76. package/project/.sdk/tm/js/src/utility/ResultHeadersUtility.js +22 -0
  77. package/project/.sdk/tm/js/src/utility/StructUtility.js +2219 -1078
  78. package/project/.sdk/tm/js/src/utility/TransformRequestUtility.js +28 -0
  79. package/project/.sdk/tm/js/src/utility/TransformResponseUtility.js +31 -0
  80. package/project/.sdk/tm/js/src/utility/Utility.js +61 -61
  81. package/project/.sdk/tm/js/test/README.md +1 -0
  82. package/project/.sdk/tm/js/test/exists.test.js +16 -0
  83. package/project/.sdk/tm/js/test/runner.js +323 -107
  84. package/project/.sdk/tm/js/test/utility/Custom.test.js +41 -63
  85. package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +390 -116
  86. package/project/.sdk/tm/js/test/utility/StructUtility.test.js +728 -175
  87. package/project/.sdk/tm/js/test/utility/index.js +9 -0
  88. package/project/.sdk/tm/js/test/utility.js +72 -0
  89. package/project/.sdk/tm/lua/test/primary_utility_test.lua +1213 -0
  90. package/project/.sdk/tm/lua/test/runner.lua +2 -2
  91. package/project/.sdk/tm/lua/test/struct_runner.lua +602 -0
  92. package/project/.sdk/tm/lua/test/struct_utility_test.lua +959 -0
  93. package/project/.sdk/tm/lua/utility/struct/struct.lua +10 -0
  94. package/project/.sdk/tm/php/feature/TestFeature.php +59 -96
  95. package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +1309 -0
  96. package/project/.sdk/tm/php/test/Runner.php +24 -1
  97. package/project/.sdk/tm/php/test/StructRunner.php +275 -0
  98. package/project/.sdk/tm/php/test/StructUtilityTest.php +1336 -0
  99. package/project/.sdk/tm/php/utility/Fetcher.php +6 -2
  100. package/project/.sdk/tm/php/utility/MakeOptions.php +5 -1
  101. package/project/.sdk/tm/php/utility/MakeResult.php +3 -0
  102. package/project/.sdk/tm/php/utility/Param.php +9 -7
  103. package/project/.sdk/tm/php/utility/struct/Struct.php +312 -208
  104. package/project/.sdk/tm/py/test/runner.py +13 -0
  105. package/project/.sdk/tm/py/test/struct_runner.py +411 -0
  106. package/project/.sdk/tm/py/test/test_primary_utility.py +1101 -0
  107. package/project/.sdk/tm/py/test/test_struct_utility.py +751 -0
  108. package/project/.sdk/tm/rb/test/primary_utility_test.rb +1083 -0
  109. package/project/.sdk/tm/rb/test/runner.rb +5 -0
  110. package/project/.sdk/tm/rb/test/struct_runner.rb +309 -0
  111. package/project/.sdk/tm/rb/test/struct_utility_test.rb +670 -0
  112. package/src/cmp/Main.ts +1 -16
  113. package/project/.sdk/src/cmp/js/Quick_js.ts +0 -78
  114. package/project/.sdk/src/cmp/js/TestAcceptEntity_js.ts +0 -13
  115. package/project/.sdk/src/cmp/js/TestAccept_js.ts +0 -18
  116. package/project/.sdk/tm/js/src/utility/AuthUtility.js +0 -21
  117. package/project/.sdk/tm/js/src/utility/BodyUtility.js +0 -29
  118. package/project/.sdk/tm/js/src/utility/ErrorUtility.js +0 -33
  119. package/project/.sdk/tm/js/src/utility/FindparamUtility.js +0 -31
  120. package/project/.sdk/tm/js/src/utility/FullurlUtility.js +0 -39
  121. package/project/.sdk/tm/js/src/utility/HeadersUtility.js +0 -13
  122. package/project/.sdk/tm/js/src/utility/JoinurlUtility.js +0 -14
  123. package/project/.sdk/tm/js/src/utility/OperatorUtility.js +0 -44
  124. package/project/.sdk/tm/js/src/utility/OptionsUtility.js +0 -54
  125. package/project/.sdk/tm/js/src/utility/ParamsUtility.js +0 -21
  126. package/project/.sdk/tm/js/src/utility/QueryUtility.js +0 -21
  127. package/project/.sdk/tm/js/src/utility/ReqformUtility.js +0 -32
  128. package/project/.sdk/tm/js/src/utility/RequestUtility.js +0 -48
  129. package/project/.sdk/tm/js/src/utility/ResbasicUtility.js +0 -27
  130. package/project/.sdk/tm/js/src/utility/ResbodyUtility.js +0 -15
  131. package/project/.sdk/tm/js/src/utility/ResformUtility.js +0 -34
  132. package/project/.sdk/tm/js/src/utility/ResheadersUtility.js +0 -19
  133. package/project/.sdk/tm/js/src/utility/ResponseUtility.js +0 -37
  134. package/project/.sdk/tm/js/src/utility/ResultUtility.js +0 -28
  135. package/project/.sdk/tm/js/src/utility/SpecUtility.js +0 -35
@@ -100,12 +100,23 @@ class Struct
100
100
  public const S_BASE = 'base';
101
101
 
102
102
  /**
103
- * Standard undefined value represented by a unique string marker.
104
- *
105
- * NOTE: This marker should be chosen to minimize collision with real data.
103
+ * Legacy string marker for undefined. Kept for backward compatibility.
104
+ * Internal code uses the $UNDEF sentinel object via undef().
106
105
  */
107
106
  public const UNDEF = '__UNDEFINED__';
108
107
 
108
+ /** Sentinel object for undefined — can never collide with real data. */
109
+ private static ?\stdClass $UNDEF = null;
110
+
111
+ /** Return the sentinel object for undefined. */
112
+ public static function undef(): \stdClass
113
+ {
114
+ if (self::$UNDEF === null) {
115
+ self::$UNDEF = new \stdClass();
116
+ }
117
+ return self::$UNDEF;
118
+ }
119
+
109
120
  public const T_any = (1 << 31) - 1;
110
121
  public const T_noval = 1 << 30;
111
122
  public const T_boolean = 1 << 29;
@@ -184,7 +195,7 @@ class Struct
184
195
 
185
196
  public static function isnode(mixed $val): bool
186
197
  {
187
- if ($val === self::UNDEF || $val === null) {
198
+ if ($val === self::undef() || $val === null) {
188
199
  return false;
189
200
  }
190
201
  if ($val instanceof \Closure) {
@@ -212,6 +223,9 @@ class Struct
212
223
  if ($val instanceof \Closure) {
213
224
  return false;
214
225
  }
226
+ if ($val === self::undef()) {
227
+ return false;
228
+ }
215
229
  if (is_object($val)) {
216
230
  return true;
217
231
  }
@@ -259,7 +273,7 @@ class Struct
259
273
  */
260
274
  public static function iskey(mixed $key): bool
261
275
  {
262
- if ($key === self::UNDEF) { // Explicit check for UNDEF
276
+ if ($key === self::undef()) { // Explicit check for UNDEF
263
277
  return false;
264
278
  }
265
279
  if (is_string($key)) {
@@ -276,7 +290,7 @@ class Struct
276
290
  */
277
291
  public static function isempty(mixed $val): bool
278
292
  {
279
- if ($val === self::UNDEF || $val === null || $val === self::S_MT) {
293
+ if ($val === self::undef() || $val === null || $val === self::S_MT) {
280
294
  return true;
281
295
  }
282
296
  if (is_array($val) && count($val) === 0) {
@@ -301,7 +315,7 @@ class Struct
301
315
 
302
316
  public static function typify(mixed $value): int
303
317
  {
304
- if ($value === self::UNDEF) {
318
+ if ($value === self::undef()) {
305
319
  return self::T_noval;
306
320
  }
307
321
  if ($value === null) {
@@ -355,7 +369,7 @@ class Struct
355
369
  */
356
370
  public static function getdef(mixed $val, mixed $alt): mixed
357
371
  {
358
- if ($val === self::UNDEF || $val === null) {
372
+ if ($val === self::undef() || $val === null) {
359
373
  return $alt;
360
374
  }
361
375
  return $val;
@@ -402,10 +416,25 @@ class Struct
402
416
  return array_values($v);
403
417
  }
404
418
 
405
- public static function getprop(mixed $val, mixed $key, mixed $alt = self::UNDEF): mixed
419
+ public static function getprop(mixed $val, mixed $key, mixed $alt = null): mixed
406
420
  {
421
+ $altExplicit = func_num_args() >= 3;
422
+ $out = self::_getprop($val, $key, self::undef());
423
+ if ($out === self::undef()) {
424
+ return $altExplicit ? $alt : null;
425
+ }
426
+ return $out;
427
+ }
428
+
429
+
430
+ // Internal getprop returning the UNDEF sentinel for missing keys so the
431
+ // injection/transform machinery can distinguish "missing" from a stored null.
432
+ // External callers should use getprop(), which normalises UNDEF to null (or $alt).
433
+ public static function _getprop(mixed $val, mixed $key, mixed $alt = null): mixed
434
+ {
435
+ if ($alt === null) { $alt = self::undef(); }
407
436
  // 1) undefined‐marker or invalid key → alt
408
- if ($val === self::UNDEF || $key === self::UNDEF) {
437
+ if ($val === self::undef() || $key === self::undef()) {
409
438
  return $alt;
410
439
  }
411
440
  if (!self::iskey($key)) {
@@ -439,13 +468,13 @@ class Struct
439
468
  }
440
469
 
441
470
  // 5) JSON‐null‐marker check
442
- return ($out === self::UNDEF ? $alt : $out);
471
+ return ($out === self::undef() ? $alt : $out);
443
472
  }
444
473
 
445
474
 
446
- public static function strkey(mixed $key = self::UNDEF): string
475
+ public static function strkey(mixed $key = null): string
447
476
  {
448
- if ($key === self::UNDEF) {
477
+ if ($key === null || $key === self::undef()) {
449
478
  return self::S_MT;
450
479
  }
451
480
  if (is_string($key)) {
@@ -493,7 +522,7 @@ class Struct
493
522
  * @param mixed $key
494
523
  * @return bool
495
524
  */
496
- public static function haskey(mixed $val = self::UNDEF, mixed $key = self::UNDEF): bool
525
+ public static function haskey(mixed $val = null, mixed $key = null): bool
497
526
  {
498
527
  // 1. Validate $val is a node
499
528
  if (!self::isnode($val)) {
@@ -507,7 +536,7 @@ class Struct
507
536
 
508
537
  // 3. Check property existence
509
538
  $marker = new \stdClass();
510
- return self::getprop($val, $key, $marker) !== $marker;
539
+ return self::_getprop($val, $key, $marker) !== $marker;
511
540
  }
512
541
 
513
542
  public static function items(mixed $val, ?callable $apply = null): array
@@ -519,7 +548,7 @@ class Struct
519
548
  }
520
549
  } else {
521
550
  foreach (self::keysof($val) as $k) {
522
- $result[] = [$k, self::getprop($val, $k)];
551
+ $result[] = [$k, self::_getprop($val, $k)];
523
552
  }
524
553
  }
525
554
  if ($apply !== null) {
@@ -605,11 +634,11 @@ class Struct
605
634
  {
606
635
  $str = 'null';
607
636
 
608
- if ($val !== null && $val !== self::UNDEF && !($val instanceof \Closure)) {
637
+ if ($val !== null && $val !== self::undef() && !($val instanceof \Closure)) {
609
638
  if ($val instanceof ListRef) {
610
639
  $val = self::cloneUnwrap($val);
611
640
  }
612
- $indent = self::getprop($flags, 'indent', 2);
641
+ $indent = self::_getprop($flags, 'indent', 2);
613
642
  try {
614
643
  $encoded = json_encode($val, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
615
644
  if ($encoded === false) {
@@ -624,7 +653,7 @@ class Struct
624
653
 
625
654
  $str = $encoded;
626
655
 
627
- $offset = self::getprop($flags, 'offset', 0);
656
+ $offset = self::_getprop($flags, 'offset', 0);
628
657
  if (0 < $offset) {
629
658
  $lines = explode("\n", $str);
630
659
  $rest = array_slice($lines, 1);
@@ -650,7 +679,7 @@ class Struct
650
679
  */
651
680
  public static function size(mixed $val): int
652
681
  {
653
- if ($val === null || $val === self::UNDEF) {
682
+ if ($val === null || $val === self::undef()) {
654
683
  return 0;
655
684
  }
656
685
 
@@ -791,7 +820,7 @@ class Struct
791
820
 
792
821
  public static function stringify(mixed $val, ?int $maxlen = null, mixed $pretty = null): string
793
822
  {
794
- if ($val === self::UNDEF) {
823
+ if ($val === self::undef()) {
795
824
  return $pretty ? '<>' : self::S_MT;
796
825
  }
797
826
 
@@ -833,7 +862,7 @@ class Struct
833
862
 
834
863
  public static function pathify(mixed $val, ?int $startin = null, ?int $endin = null): string
835
864
  {
836
- $UNDEF = self::UNDEF;
865
+ $UNDEF = self::undef();
837
866
  $S_MT = self::S_MT;
838
867
  $S_CN = self::S_CN;
839
868
  $S_DT = self::S_DT;
@@ -912,15 +941,12 @@ class Struct
912
941
 
913
942
  public static function clone(mixed $val): mixed
914
943
  {
915
- if ($val === self::UNDEF) {
916
- return self::UNDEF;
944
+ if ($val === self::undef()) {
945
+ return self::undef();
917
946
  }
918
947
  $refs = [];
919
948
  $replacer = function (mixed $v) use (&$refs, &$replacer): mixed {
920
- if ($v instanceof \Closure) {
921
- $refs[] = $v;
922
- return '`$FUNCTION:' . (count($refs) - 1) . '`';
923
- } elseif (is_callable($v) && !is_array($v) && !($v instanceof ListRef)) {
949
+ if ($v instanceof \Closure || (is_object($v) && !($v instanceof \stdClass) && !($v instanceof ListRef) && method_exists($v, '__invoke'))) {
924
950
  $refs[] = $v;
925
951
  return '`$FUNCTION:' . (count($refs) - 1) . '`';
926
952
  } elseif ($v instanceof ListRef) {
@@ -950,7 +976,10 @@ class Struct
950
976
  $reviver = function (mixed $v) use (&$refs, &$reviver): mixed {
951
977
  if (is_string($v)) {
952
978
  if (preg_match('/^`\$FUNCTION:([0-9]+)`$/', $v, $matches)) {
953
- return $refs[(int) $matches[1]];
979
+ $idx = (int) $matches[1];
980
+ if (isset($refs[$idx])) {
981
+ return $refs[$idx];
982
+ }
954
983
  }
955
984
  return $v;
956
985
  } elseif ($v instanceof ListRef) {
@@ -985,10 +1014,10 @@ class Struct
985
1014
  */
986
1015
  public static function cloneWrap(mixed $val): mixed
987
1016
  {
988
- if ($val === null || $val === self::UNDEF) {
1017
+ if ($val === null || $val === self::undef()) {
989
1018
  return $val;
990
1019
  }
991
- if (is_callable($val) && !is_array($val) && !is_object($val)) {
1020
+ if ($val instanceof \Closure) {
992
1021
  return $val;
993
1022
  }
994
1023
  if ($val instanceof ListRef) {
@@ -1076,7 +1105,7 @@ class Struct
1076
1105
  return $parent;
1077
1106
  }
1078
1107
  $keyI = (int) floor((float) $key);
1079
- if ($val === self::UNDEF) {
1108
+ if ($val === self::undef()) {
1080
1109
  if ($keyI >= 0 && $keyI < count($parent->list)) {
1081
1110
  array_splice($parent->list, $keyI, 1);
1082
1111
  }
@@ -1095,7 +1124,7 @@ class Struct
1095
1124
  // ─── OBJECT (map) ───────────────────────────────────────────
1096
1125
  if (is_object($parent)) {
1097
1126
  $keyStr = self::strkey($key);
1098
- if ($val === self::UNDEF) {
1127
+ if ($val === self::undef()) {
1099
1128
  unset($parent->$keyStr);
1100
1129
  } else {
1101
1130
  $parent->$keyStr = $val;
@@ -1108,7 +1137,7 @@ class Struct
1108
1137
  if (!self::islist($parent)) {
1109
1138
  // map‐array
1110
1139
  $keyStr = self::strkey($key);
1111
- if ($val === self::UNDEF) {
1140
+ if ($val === self::undef()) {
1112
1141
  unset($parent[$keyStr]);
1113
1142
  } elseif (ctype_digit((string) $key)) {
1114
1143
  // numeric string key: unshift (TS always merges maps by overwriting)
@@ -1122,7 +1151,7 @@ class Struct
1122
1151
  return $parent;
1123
1152
  }
1124
1153
  $keyI = (int) floor((float) $key);
1125
- if ($val === self::UNDEF) {
1154
+ if ($val === self::undef()) {
1126
1155
  if ($keyI >= 0 && $keyI < count($parent)) {
1127
1156
  array_splice($parent, $keyI, 1);
1128
1157
  }
@@ -1199,12 +1228,12 @@ class Struct
1199
1228
  $lenlist = count($list);
1200
1229
 
1201
1230
  if (0 === $lenlist) {
1202
- return self::UNDEF;
1231
+ return self::undef();
1203
1232
  } elseif (1 === $lenlist) {
1204
1233
  return $list[0];
1205
1234
  }
1206
1235
 
1207
- $out = self::getprop($list, 0, new \stdClass());
1236
+ $out = self::_getprop($list, 0, new \stdClass());
1208
1237
 
1209
1238
  for ($oI = 1; $oI < $lenlist; $oI++) {
1210
1239
  $obj = $list[$oI];
@@ -1223,16 +1252,16 @@ class Struct
1223
1252
  } elseif (!self::isnode($val)) {
1224
1253
  $cur[$pI] = $val;
1225
1254
  } else {
1226
- $dst[$pI] = 0 < $pI ? self::getprop($dst[$pI - 1], $key) : $dst[$pI];
1255
+ $dst[$pI] = 0 < $pI ? self::_getprop($dst[$pI - 1], $key) : $dst[$pI];
1227
1256
  $tval = $dst[$pI];
1228
1257
 
1229
- if (self::UNDEF === $tval && 0 === (self::T_instance & self::typify($val))) {
1258
+ if (self::undef() === $tval && 0 === (self::T_instance & self::typify($val))) {
1230
1259
  $cur[$pI] = self::islist($val) ? [] : new \stdClass();
1231
1260
  } elseif (self::typify($val) === self::typify($tval)) {
1232
1261
  $cur[$pI] = $tval;
1233
1262
  } else {
1234
1263
  $cur[$pI] = $val;
1235
- $val = self::UNDEF;
1264
+ $val = self::undef();
1236
1265
  }
1237
1266
  }
1238
1267
 
@@ -1268,17 +1297,17 @@ class Struct
1268
1297
  // Convert path to array of parts
1269
1298
  $parts = is_array($path) ? $path :
1270
1299
  (is_string($path) ? explode('.', $path) :
1271
- (is_numeric($path) ? [self::strkey($path)] : self::UNDEF));
1300
+ (is_numeric($path) ? [self::strkey($path)] : self::undef()));
1272
1301
 
1273
- if ($parts === self::UNDEF) {
1274
- return self::UNDEF;
1302
+ if ($parts === self::undef()) {
1303
+ return self::undef();
1275
1304
  }
1276
1305
 
1277
1306
  $val = $store;
1278
- $base = self::getprop($injdef, 'base');
1279
- $src = self::getprop($store, $base, $store);
1307
+ $base = self::_getprop($injdef, 'base');
1308
+ $src = self::_getprop($store, $base, $store);
1280
1309
  $numparts = count($parts);
1281
- $dparent = self::getprop($injdef, 'dparent');
1310
+ $dparent = self::_getprop($injdef, 'dparent');
1282
1311
 
1283
1312
  // An empty path (incl empty string) just finds the src (base data)
1284
1313
  if ($path === null || $store === null || ($numparts === 1 && $parts[0] === '')) {
@@ -1286,7 +1315,7 @@ class Struct
1286
1315
  } else if ($numparts > 0) {
1287
1316
  // Check for $ACTIONs
1288
1317
  if ($numparts === 1) {
1289
- $val = self::getprop($store, $parts[0]);
1318
+ $val = self::_getprop($store, $parts[0]);
1290
1319
  }
1291
1320
 
1292
1321
  if (!self::isfunc($val)) {
@@ -1294,17 +1323,17 @@ class Struct
1294
1323
 
1295
1324
  // Check for meta path in first part
1296
1325
  if (preg_match('/^([^$]+)\$([=~])(.+)$/', $parts[0], $m) && $injdef && isset($injdef->meta)) {
1297
- $val = self::getprop($injdef->meta, $m[1]);
1326
+ $val = self::_getprop($injdef->meta, $m[1]);
1298
1327
  $parts[0] = $m[3];
1299
1328
  }
1300
1329
 
1301
- $dpath = self::getprop($injdef, 'dpath');
1330
+ $dpath = self::_getprop($injdef, 'dpath');
1302
1331
 
1303
- for ($pI = 0; $val !== self::UNDEF && $pI < count($parts); $pI++) {
1332
+ for ($pI = 0; $val !== self::undef() && $pI < count($parts); $pI++) {
1304
1333
  $part = $parts[$pI];
1305
1334
 
1306
1335
  if ($injdef && $part === '$KEY') {
1307
- $part = self::getprop($injdef, 'key');
1336
+ $part = self::_getprop($injdef, 'key');
1308
1337
  } else if ($injdef && str_starts_with($part, '$GET:')) {
1309
1338
  // $GET:path$ -> get store value, use as path part (string)
1310
1339
  $getpath = substr($part, 5, -1);
@@ -1313,10 +1342,10 @@ class Struct
1313
1342
  } else if ($injdef && str_starts_with($part, '$REF:')) {
1314
1343
  // $REF:refpath$ -> get spec value, use as path part (string)
1315
1344
  $refpath = substr($part, 5, -1);
1316
- $part = self::stringify(self::getpath(self::getprop($store, '$SPEC'), self::slice($part, 5, -1)));
1345
+ $part = self::stringify(self::getpath(self::_getprop($store, '$SPEC'), self::slice($part, 5, -1)));
1317
1346
  } else if ($injdef && str_starts_with($part, '$META:')) {
1318
1347
  // $META:metapath$ -> get meta value, use as path part (string)
1319
- $part = self::stringify(self::getpath(self::getprop($injdef, 'meta'), substr($part, 6, -1)));
1348
+ $part = self::stringify(self::getpath(self::_getprop($injdef, 'meta'), substr($part, 6, -1)));
1320
1349
  }
1321
1350
 
1322
1351
  // $$ escapes $
@@ -1342,27 +1371,27 @@ class Struct
1342
1371
  if (is_array($dpath) && $ascends <= count($dpath)) {
1343
1372
  $val = self::getpath($store, $fullpath);
1344
1373
  } else {
1345
- $val = self::UNDEF;
1374
+ $val = self::undef();
1346
1375
  }
1347
1376
  break;
1348
1377
  }
1349
1378
  } else {
1350
1379
  // Special case for single dot: use dparent if available
1351
- if ($dparent !== null && $dparent !== self::UNDEF) {
1380
+ if ($dparent !== null && $dparent !== self::undef()) {
1352
1381
  $val = $dparent;
1353
1382
  } else {
1354
1383
  $val = $src;
1355
1384
  }
1356
1385
  }
1357
1386
  } else {
1358
- $val = self::getprop($val, $part);
1387
+ $val = self::_getprop($val, $part);
1359
1388
  }
1360
1389
  }
1361
1390
  }
1362
1391
  }
1363
1392
 
1364
1393
  // Inj may provide a custom handler to modify found value
1365
- $handler = self::getprop($injdef, 'handler');
1394
+ $handler = self::_getprop($injdef, 'handler');
1366
1395
  if ($injdef !== null && self::isfunc($handler)) {
1367
1396
  $ref = self::pathify($path);
1368
1397
  $val = call_user_func($handler, $injdef, $val, $ref, $store);
@@ -1384,15 +1413,15 @@ class Struct
1384
1413
 
1385
1414
  // Create state if at root of injection. The input value is placed
1386
1415
  // inside a virtual parent holder to simplify edge cases.
1387
- if (self::UNDEF === $injdef || null === $injdef || !($injdef instanceof Injection)) {
1416
+ if (self::undef() === $injdef || null === $injdef || !($injdef instanceof Injection)) {
1388
1417
  $inj = new Injection($val, (object) [self::S_DTOP => $val]);
1389
1418
  $inj->dparent = $store;
1390
- $inj->errs = self::getprop($store, self::S_DERRS, []);
1419
+ $inj->errs = self::_getprop($store, self::S_DERRS, []);
1391
1420
  if (!isset($inj->meta->__d)) {
1392
1421
  $inj->meta->__d = 0;
1393
1422
  }
1394
1423
 
1395
- if (self::UNDEF !== $injdef && null !== $injdef) {
1424
+ if (self::undef() !== $injdef && null !== $injdef) {
1396
1425
  $inj->modify = (is_object($injdef) && property_exists($injdef, 'modify') && null !== $injdef->modify) ? $injdef->modify : $inj->modify;
1397
1426
  $inj->extra = (is_object($injdef) && property_exists($injdef, 'extra') && null !== $injdef->extra) ? $injdef->extra : ($inj->extra ?? null);
1398
1427
  $inj->meta = (is_object($injdef) && property_exists($injdef, 'meta') && null !== $injdef->meta) ? $injdef->meta : $inj->meta;
@@ -1434,8 +1463,8 @@ class Struct
1434
1463
  $nodekeys = $childinj->keys;
1435
1464
 
1436
1465
  // Prevent further processing by returning an undefined prekey
1437
- if (self::UNDEF !== $prekey) {
1438
- $childinj->val = self::getprop($val, $prekey);
1466
+ if (self::undef() !== $prekey) {
1467
+ $childinj->val = self::_getprop($val, $prekey);
1439
1468
  $childinj->mode = self::M_VAL;
1440
1469
 
1441
1470
  // Perform the val mode injection on the child value.
@@ -1460,7 +1489,7 @@ class Struct
1460
1489
  if (is_array($val) && is_array($childinj->parent)) {
1461
1490
  // Check that the grandparent (inj->parent) still references our list.
1462
1491
  // If a transform like $REF replaced/deleted it, the stored value will differ.
1463
- $storedVal = self::getprop($inj->parent, $inj->key);
1492
+ $storedVal = self::_getprop($inj->parent, $inj->key);
1464
1493
  if (is_array($storedVal)) {
1465
1494
  $val = $childinj->parent;
1466
1495
  $inj->val = $val;
@@ -1482,7 +1511,7 @@ class Struct
1482
1511
  if ($inj->modify && self::SKIP !== $val) {
1483
1512
  $mkey = $inj->key;
1484
1513
  $mparent = $inj->parent;
1485
- $mval = self::getprop($mparent, $mkey);
1514
+ $mval = self::_getprop($mparent, $mkey);
1486
1515
 
1487
1516
  call_user_func(
1488
1517
  $inj->modify,
@@ -1498,7 +1527,7 @@ class Struct
1498
1527
 
1499
1528
  // Original val reference may no longer be correct.
1500
1529
  // This return value is only used as the top level result.
1501
- return self::getprop($inj->parent, self::S_DTOP);
1530
+ return self::_getprop($inj->parent, self::S_DTOP);
1502
1531
  }
1503
1532
 
1504
1533
 
@@ -1551,7 +1580,7 @@ class Struct
1551
1580
  $found = self::getpath($store, $ref, $inj);
1552
1581
 
1553
1582
  // Ensure inject value is a string.
1554
- if ($found === self::UNDEF) {
1583
+ if ($found === self::undef()) {
1555
1584
  return self::S_MT;
1556
1585
  }
1557
1586
  if (is_string($found)) {
@@ -1581,7 +1610,7 @@ class Struct
1581
1610
  $out = $val;
1582
1611
 
1583
1612
  // Check if val is a function (command transforms)
1584
- $iscmd = self::isfunc($val) && (self::UNDEF === $ref || str_starts_with($ref, self::S_DS));
1613
+ $iscmd = self::isfunc($val) && (self::undef() === $ref || str_starts_with($ref, self::S_DS));
1585
1614
 
1586
1615
  // Only call val function if it is a special command ($NAME format).
1587
1616
  if ($iscmd) {
@@ -1614,8 +1643,8 @@ class Struct
1614
1643
  mixed $store
1615
1644
  ): mixed {
1616
1645
  // _setparentprop(state, UNDEF)
1617
- $state->setval(self::UNDEF);
1618
- return self::UNDEF;
1646
+ $state->setval(self::undef());
1647
+ return self::undef();
1619
1648
  }
1620
1649
 
1621
1650
  /**
@@ -1629,10 +1658,10 @@ class Struct
1629
1658
  mixed $store
1630
1659
  ): mixed {
1631
1660
  if (self::M_VAL !== $state->mode) {
1632
- return self::UNDEF;
1661
+ return self::undef();
1633
1662
  }
1634
1663
 
1635
- $out = self::getprop($state->dparent, $state->key);
1664
+ $out = self::_getprop($state->dparent, $state->key);
1636
1665
  $state->setval($out);
1637
1666
 
1638
1667
  return $out;
@@ -1651,24 +1680,24 @@ class Struct
1651
1680
  ): mixed {
1652
1681
  // only in "val" mode do anything
1653
1682
  if (self::M_VAL !== $state->mode) {
1654
- return self::UNDEF;
1683
+ return self::undef();
1655
1684
  }
1656
1685
 
1657
1686
  // if parent has a "$KEY" override, use that
1658
- $keyspec = self::getprop($state->parent, self::S_DKEY);
1659
- if ($keyspec !== self::UNDEF) {
1687
+ $keyspec = self::_getprop($state->parent, self::S_DKEY);
1688
+ if ($keyspec !== self::undef()) {
1660
1689
  // remove the marker
1661
- self::setprop($state->parent, self::S_DKEY, self::UNDEF);
1662
- return self::getprop($state->dparent, $keyspec);
1690
+ self::setprop($state->parent, self::S_DKEY, self::undef());
1691
+ return self::_getprop($state->dparent, $keyspec);
1663
1692
  }
1664
1693
 
1665
1694
  // otherwise pull from $ANNO.KEY or fallback to the path index
1666
- $meta = self::getprop($state->parent, self::S_BANNO);
1695
+ $meta = self::_getprop($state->parent, self::S_BANNO);
1667
1696
  $idx = count($state->path) - 2;
1668
- return self::getprop(
1697
+ return self::_getprop(
1669
1698
  $meta,
1670
1699
  self::S_KEY,
1671
- self::getprop($state->path, $idx)
1700
+ self::_getprop($state->path, $idx)
1672
1701
  );
1673
1702
  }
1674
1703
 
@@ -1683,8 +1712,8 @@ class Struct
1683
1712
  mixed $store
1684
1713
  ): mixed {
1685
1714
  // remove the $META marker
1686
- self::setprop($state->parent, self::S_DMETA, self::UNDEF);
1687
- return self::UNDEF;
1715
+ self::setprop($state->parent, self::S_DMETA, self::undef());
1716
+ return self::undef();
1688
1717
  }
1689
1718
 
1690
1719
  /**
@@ -1698,8 +1727,8 @@ class Struct
1698
1727
  mixed $store
1699
1728
  ): mixed {
1700
1729
  // remove the $ANNO marker
1701
- self::setprop($state->parent, self::S_BANNO, self::UNDEF);
1702
- return self::UNDEF;
1730
+ self::setprop($state->parent, self::S_BANNO, self::undef());
1731
+ return self::undef();
1703
1732
  }
1704
1733
 
1705
1734
  /**
@@ -1717,7 +1746,7 @@ class Struct
1717
1746
  $parent = $state->parent;
1718
1747
 
1719
1748
  // Ensures $MERGE is removed from parent list (val mode).
1720
- $out = self::UNDEF;
1749
+ $out = self::undef();
1721
1750
 
1722
1751
  if (self::M_KEYPRE === $mode) {
1723
1752
  $out = $key;
@@ -1726,11 +1755,11 @@ class Struct
1726
1755
  elseif (self::M_KEYPOST === $mode) {
1727
1756
  $out = $key;
1728
1757
 
1729
- $args = self::getprop($parent, $key);
1758
+ $args = self::_getprop($parent, $key);
1730
1759
  $args = self::islist($args) ? (($args instanceof ListRef) ? $args->list : $args) : [$args];
1731
1760
 
1732
1761
  // Remove the $MERGE command from a parent map.
1733
- $state->setval(self::UNDEF);
1762
+ $state->setval(self::undef());
1734
1763
 
1735
1764
  // Literals in the parent have precedence, but we still merge onto
1736
1765
  // the parent object, so that node tree references are not changed.
@@ -1753,15 +1782,15 @@ class Struct
1753
1782
  $state->keys = array_slice($state->keys, 0, 1);
1754
1783
 
1755
1784
  if (self::M_VAL !== $state->mode) {
1756
- return self::UNDEF;
1785
+ return self::undef();
1757
1786
  }
1758
1787
 
1759
1788
  // Get arguments: ['`$EACH`', 'source-path', child-template]
1760
- $srcpath = self::getprop($state->parent, 1);
1761
- $child = self::clone(self::getprop($state->parent, 2));
1789
+ $srcpath = self::_getprop($state->parent, 1);
1790
+ $child = self::clone(self::_getprop($state->parent, 2));
1762
1791
 
1763
1792
  // Source data.
1764
- $srcstore = self::getprop($store, $state->base, $store);
1793
+ $srcstore = self::_getprop($store, $state->base, $store);
1765
1794
  $src = self::getpath($srcstore, $srcpath, $state);
1766
1795
 
1767
1796
  // Create parallel data structures: source entries :: child templates
@@ -1791,7 +1820,7 @@ class Struct
1791
1820
  $rval = [];
1792
1821
 
1793
1822
  if (0 < self::size($tval)) {
1794
- $tcur = (null == $src) ? self::UNDEF : ($src instanceof ListRef ? $src->list : array_values((array) $src));
1823
+ $tcur = (null == $src) ? self::undef() : ($src instanceof ListRef ? $src->list : array_values((array) $src));
1795
1824
 
1796
1825
  $ckey = self::getelem($state->path, -2);
1797
1826
 
@@ -1826,7 +1855,7 @@ class Struct
1826
1855
  self::setprop($target, $tkey, $rval);
1827
1856
 
1828
1857
  // Prevent callee from damaging first list entry (since we are in `val` mode).
1829
- return $rval[0] ?? self::UNDEF;
1858
+ return $rval[0] ?? self::undef();
1830
1859
  }
1831
1860
 
1832
1861
 
@@ -1836,7 +1865,7 @@ class Struct
1836
1865
  public static function transform_PACK(
1837
1866
  object $state,
1838
1867
  mixed $_val,
1839
- string $_ref,
1868
+ mixed $_ref,
1840
1869
  mixed $store
1841
1870
  ): mixed {
1842
1871
  $mode = $state->mode;
@@ -1847,13 +1876,17 @@ class Struct
1847
1876
 
1848
1877
  // Only run in key:pre mode.
1849
1878
  if (self::M_KEYPRE !== $mode) {
1850
- return self::UNDEF;
1879
+ return self::undef();
1851
1880
  }
1852
1881
 
1853
- // Get arguments.
1854
- $args = self::getprop($parent, $key);
1882
+ // Get arguments. Spec arrays are wrapped in ListRef by transform() so
1883
+ // accept either a plain array or a ListRef here.
1884
+ $args = self::_getprop($parent, $key);
1885
+ if ($args instanceof ListRef) {
1886
+ $args = $args->list;
1887
+ }
1855
1888
  if (!is_array($args) || count($args) < 2) {
1856
- return self::UNDEF;
1889
+ return self::undef();
1857
1890
  }
1858
1891
 
1859
1892
  $srcpath = $args[0];
@@ -1865,7 +1898,7 @@ class Struct
1865
1898
  $target = self::getelem($nodes, $pathsize - 2) ?? self::getelem($nodes, $pathsize - 1);
1866
1899
 
1867
1900
  // Source data
1868
- $srcstore = self::getprop($store, $state->base, $store);
1901
+ $srcstore = self::_getprop($store, $state->base, $store);
1869
1902
  $src = self::getpath($srcstore, $srcpath, $state);
1870
1903
 
1871
1904
  // Prepare source as a list.
@@ -1878,16 +1911,16 @@ class Struct
1878
1911
  }
1879
1912
  $src = $newSrc;
1880
1913
  } else {
1881
- return self::UNDEF;
1914
+ return self::undef();
1882
1915
  }
1883
1916
  }
1884
1917
 
1885
1918
  if (null == $src) {
1886
- return self::UNDEF;
1919
+ return self::undef();
1887
1920
  }
1888
1921
 
1889
1922
  // Get keypath.
1890
- $keypath = self::getprop($origchildspec, self::S_BKEY);
1923
+ $keypath = self::_getprop($origchildspec, self::S_BKEY);
1891
1924
  $childspec = self::delprop($origchildspec, self::S_BKEY);
1892
1925
 
1893
1926
  $child = $childspec;
@@ -1900,7 +1933,7 @@ class Struct
1900
1933
  $srcnode = $item[1];
1901
1934
 
1902
1935
  $nkey = $srckey;
1903
- if (self::UNDEF !== $keypath) {
1936
+ if (self::undef() !== $keypath) {
1904
1937
  if (is_string($keypath) && str_starts_with($keypath, '`')) {
1905
1938
  $nkey = self::inject($keypath, self::merge([new \stdClass(), $store, (object) ['$TOP' => $srcnode]], 1));
1906
1939
  } else {
@@ -1911,8 +1944,8 @@ class Struct
1911
1944
  $tchild = self::clone($child);
1912
1945
  self::setprop($tval, $nkey, $tchild);
1913
1946
 
1914
- $anno = self::getprop($srcnode, self::S_BANNO);
1915
- if (self::UNDEF === $anno) {
1947
+ $anno = self::_getprop($srcnode, self::S_BANNO);
1948
+ if (self::undef() === $anno) {
1916
1949
  self::delprop($tchild, self::S_BANNO);
1917
1950
  } else {
1918
1951
  self::setprop($tchild, self::S_BANNO, $anno);
@@ -1926,7 +1959,7 @@ class Struct
1926
1959
  $tsrc = new \stdClass();
1927
1960
  foreach ($src as $i => $n) {
1928
1961
  $kn = null;
1929
- if (self::UNDEF === $keypath) {
1962
+ if (self::undef() === $keypath) {
1930
1963
  $kn = $i;
1931
1964
  } elseif (is_string($keypath) && str_starts_with($keypath, '`')) {
1932
1965
  $kn = self::inject($keypath, self::merge([new \stdClass(), $store, (object) ['$TOP' => $n]], 1));
@@ -1967,7 +2000,7 @@ class Struct
1967
2000
  self::setprop($target, $tkey, $rval);
1968
2001
 
1969
2002
  // Drop transform key.
1970
- return self::UNDEF;
2003
+ return self::undef();
1971
2004
  }
1972
2005
 
1973
2006
 
@@ -1977,16 +2010,16 @@ class Struct
1977
2010
  $nodes = $state->nodes;
1978
2011
 
1979
2012
  if (self::M_VAL !== $state->mode) {
1980
- return self::UNDEF;
2013
+ return self::undef();
1981
2014
  }
1982
2015
 
1983
2016
  // Get arguments: ['`$REF`', 'ref-path'].
1984
- $refpath = self::getprop($state->parent, 1);
2017
+ $refpath = self::_getprop($state->parent, 1);
1985
2018
  $state->keyI = self::size($state->keys);
1986
2019
 
1987
2020
  // Spec reference.
1988
- $specFn = self::getprop($store, '$SPEC');
1989
- $spec = is_callable($specFn) ? $specFn() : self::UNDEF;
2021
+ $specFn = self::_getprop($store, '$SPEC');
2022
+ $spec = is_callable($specFn) ? $specFn() : self::undef();
1990
2023
 
1991
2024
  $dpath = self::slice($state->path, 1);
1992
2025
  $ref = self::getpath($spec, $refpath, (object) [
@@ -2010,9 +2043,9 @@ class Struct
2010
2043
  $tpath = self::slice($state->path, -1);
2011
2044
  $tcur = self::getpath($store, $cpath);
2012
2045
  $tval = self::getpath($store, $tpath);
2013
- $rval = self::UNDEF;
2046
+ $rval = self::undef();
2014
2047
 
2015
- if (!$hasSubRef || self::UNDEF !== $tval) {
2048
+ if (!$hasSubRef || self::undef() !== $tval) {
2016
2049
  $tinj = $state->child(0, [self::getelem($tpath, -1)]);
2017
2050
 
2018
2051
  $tinj->path = $tpath;
@@ -2027,12 +2060,12 @@ class Struct
2027
2060
 
2028
2061
  // If inject returned SKIP, use tref (mutated in place) not tinj->val (which may be SKIP)
2029
2062
  if ($injResult === self::SKIP || $tinj->val === self::SKIP) {
2030
- $rval = is_object($tref) ? $tref : self::UNDEF;
2063
+ $rval = is_object($tref) ? $tref : self::undef();
2031
2064
  } else {
2032
2065
  $rval = $tinj->val;
2033
2066
  }
2034
2067
  } else {
2035
- $rval = self::UNDEF;
2068
+ $rval = self::undef();
2036
2069
  }
2037
2070
 
2038
2071
  $grandparent = $state->setval($rval, 2);
@@ -2041,7 +2074,7 @@ class Struct
2041
2074
  // Sync the prior injection's parent if it's an array.
2042
2075
  if ($state->prior && is_array($state->prior->parent)) {
2043
2076
  $akey = self::getelem($state->path, -2);
2044
- if (self::UNDEF === $rval) {
2077
+ if (self::undef() === $rval) {
2045
2078
  $state->prior->parent = self::delprop($state->prior->parent, $akey);
2046
2079
  } else {
2047
2080
  self::setprop($state->prior->parent, $akey, $rval);
@@ -2100,12 +2133,12 @@ class Struct
2100
2133
  self::slice($inj->keys, 0, 1, true);
2101
2134
 
2102
2135
  if (self::M_VAL !== $inj->mode) {
2103
- return self::UNDEF;
2136
+ return self::undef();
2104
2137
  }
2105
2138
 
2106
2139
  // Get arguments: ['`$FORMAT`', 'name', child].
2107
- $name = self::getprop($inj->parent, 1);
2108
- $child = self::getprop($inj->parent, 2);
2140
+ $name = self::_getprop($inj->parent, 1);
2141
+ $child = self::_getprop($inj->parent, 2);
2109
2142
 
2110
2143
  // Source data.
2111
2144
  $tkey = self::getelem($inj->path, -2);
@@ -2115,11 +2148,11 @@ class Struct
2115
2148
  $resolved = $cinj->val;
2116
2149
 
2117
2150
  $formatters = self::_getFormatters();
2118
- $formatter = (0 < (self::T_function & self::typify($name))) ? $name : ($formatters[$name] ?? self::UNDEF);
2151
+ $formatter = (0 < (self::T_function & self::typify($name))) ? $name : ($formatters[$name] ?? self::undef());
2119
2152
 
2120
- if (self::UNDEF === $formatter) {
2153
+ if (self::undef() === $formatter) {
2121
2154
  $inj->errs[] = '$FORMAT: unknown format: ' . $name . '.';
2122
- return self::UNDEF;
2155
+ return self::undef();
2123
2156
  }
2124
2157
 
2125
2158
  $out = self::walk($resolved, $formatter);
@@ -2136,7 +2169,7 @@ class Struct
2136
2169
  $ijname = 'APPLY';
2137
2170
 
2138
2171
  if (!self::checkPlacement(self::M_VAL, $ijname, self::T_list, $inj)) {
2139
- return self::UNDEF;
2172
+ return self::undef();
2140
2173
  }
2141
2174
 
2142
2175
  $args = self::slice($inj->parent, 1);
@@ -2149,9 +2182,9 @@ class Struct
2149
2182
  }
2150
2183
  }
2151
2184
  [$err, $apply, $child] = self::injectorArgs([self::T_function, self::T_any], $argsList);
2152
- if (self::UNDEF !== $err) {
2185
+ if (self::undef() !== $err) {
2153
2186
  $inj->errs[] = '$' . $ijname . ': ' . $err;
2154
- return self::UNDEF;
2187
+ return self::undef();
2155
2188
  }
2156
2189
 
2157
2190
  $tkey = self::getelem($inj->path, -2);
@@ -2262,10 +2295,39 @@ class Struct
2262
2295
 
2263
2296
  // When a child transform (e.g. $REF) deletes the key, inject returns SKIP; return mutated spec
2264
2297
  if ($result === self::SKIP) {
2265
- return self::cloneUnwrap($specClone);
2298
+ return self::_stdClassToArray(self::cloneUnwrap($specClone));
2266
2299
  }
2267
2300
 
2268
- return self::cloneUnwrap($result);
2301
+ // Return maps as PHP associative arrays (the native map type) so callers
2302
+ // can use is_array()/array access directly. Internal processing may use
2303
+ // stdClass; the conversion here happens only at the public boundary.
2304
+ return self::_stdClassToArray(self::cloneUnwrap($result));
2305
+ }
2306
+
2307
+
2308
+ // Deeply convert stdClass map nodes to associative arrays, leaving lists
2309
+ // (sequential arrays) and scalar values untouched. Used at the transform()
2310
+ // public boundary so consumers receive PHP-idiomatic arrays.
2311
+ private static function _stdClassToArray(mixed $val, int $depth = 0): mixed
2312
+ {
2313
+ if ($depth > 64) {
2314
+ return $val;
2315
+ }
2316
+ if ($val instanceof \stdClass) {
2317
+ $out = [];
2318
+ foreach (get_object_vars($val) as $k => $v) {
2319
+ $out[$k] = self::_stdClassToArray($v, $depth + 1);
2320
+ }
2321
+ return $out;
2322
+ }
2323
+ if (is_array($val)) {
2324
+ $out = [];
2325
+ foreach ($val as $k => $v) {
2326
+ $out[$k] = self::_stdClassToArray($v, $depth + 1);
2327
+ }
2328
+ return $out;
2329
+ }
2330
+ return $val;
2269
2331
  }
2270
2332
 
2271
2333
  /**
@@ -2275,7 +2337,7 @@ class Struct
2275
2337
  private static function _cleanRefEntries(array $list): array {
2276
2338
  $cleaned = [];
2277
2339
  foreach ($list as $item) {
2278
- if (self::islist($item) && count($item) >= 1 && self::getprop($item, 0) === '`$REF`') {
2340
+ if (self::islist($item) && count($item) >= 1 && self::_getprop($item, 0) === '`$REF`') {
2279
2341
  // This is an unresolved $REF entry - remove it
2280
2342
  continue;
2281
2343
  }
@@ -2290,11 +2352,12 @@ class Struct
2290
2352
  /** @internal */
2291
2353
  private static function _invalidTypeMsg(array $path, string $needtype, int $vt, mixed $v): string
2292
2354
  {
2293
- $vs = $v === null ? 'no value' : self::stringify($v);
2355
+ $missing = ($v === null || $v === self::undef());
2356
+ $vs = $missing ? 'no value' : self::stringify($v);
2294
2357
  return 'Expected ' .
2295
2358
  (1 < self::size($path) ? ('field ' . self::pathify($path, 1) . ' to be ') : '') .
2296
2359
  $needtype . ', but found ' .
2297
- ($v !== null ? self::typename($vt) . ': ' : '') . $vs . '.';
2360
+ ($missing ? '' : self::typename($vt) . ': ') . $vs . '.';
2298
2361
  }
2299
2362
 
2300
2363
  /* =======================
@@ -2306,19 +2369,19 @@ class Struct
2306
2369
  */
2307
2370
  public static function validate_STRING(object $inj): mixed
2308
2371
  {
2309
- $out = self::getprop($inj->dparent, $inj->key);
2372
+ $out = self::_getprop($inj->dparent, $inj->key);
2310
2373
 
2311
2374
  $t = self::typify($out);
2312
2375
  if (0 === (self::T_string & $t)) {
2313
2376
  $msg = self::_invalidTypeMsg($inj->path, self::S_string, $t, $out);
2314
2377
  $inj->errs[] = $msg;
2315
- return self::UNDEF;
2378
+ return self::undef();
2316
2379
  }
2317
2380
 
2318
2381
  if (self::S_MT === $out) {
2319
2382
  $msg = 'Empty string at ' . self::pathify($inj->path, 1);
2320
2383
  $inj->errs[] = $msg;
2321
- return self::UNDEF;
2384
+ return self::undef();
2322
2385
  }
2323
2386
 
2324
2387
  return $out;
@@ -2329,12 +2392,12 @@ class Struct
2329
2392
  */
2330
2393
  public static function validate_NUMBER(object $inj): mixed
2331
2394
  {
2332
- $out = self::getprop($inj->dparent, $inj->key);
2395
+ $out = self::_getprop($inj->dparent, $inj->key);
2333
2396
 
2334
2397
  $t = self::typify($out);
2335
2398
  if (0 === (self::T_number & $t)) {
2336
2399
  $inj->errs[] = self::_invalidTypeMsg($inj->path, self::S_number, $t, $out);
2337
- return self::UNDEF;
2400
+ return self::undef();
2338
2401
  }
2339
2402
 
2340
2403
  return $out;
@@ -2345,12 +2408,12 @@ class Struct
2345
2408
  */
2346
2409
  public static function validate_BOOLEAN(object $inj): mixed
2347
2410
  {
2348
- $out = self::getprop($inj->dparent, $inj->key);
2411
+ $out = self::_getprop($inj->dparent, $inj->key);
2349
2412
 
2350
2413
  $t = self::typify($out);
2351
2414
  if (0 === (self::T_boolean & $t)) {
2352
2415
  $inj->errs[] = self::_invalidTypeMsg($inj->path, self::S_boolean, $t, $out);
2353
- return self::UNDEF;
2416
+ return self::undef();
2354
2417
  }
2355
2418
 
2356
2419
  return $out;
@@ -2361,12 +2424,12 @@ class Struct
2361
2424
  */
2362
2425
  public static function validate_OBJECT(object $inj): mixed
2363
2426
  {
2364
- $out = self::getprop($inj->dparent, $inj->key);
2427
+ $out = self::_getprop($inj->dparent, $inj->key);
2365
2428
 
2366
2429
  $t = self::typify($out);
2367
2430
  if (0 === (self::T_map & $t)) {
2368
2431
  $inj->errs[] = self::_invalidTypeMsg($inj->path, self::S_object, $t, $out);
2369
- return self::UNDEF;
2432
+ return self::undef();
2370
2433
  }
2371
2434
 
2372
2435
  return $out;
@@ -2377,12 +2440,12 @@ class Struct
2377
2440
  */
2378
2441
  public static function validate_ARRAY(object $inj): mixed
2379
2442
  {
2380
- $out = self::getprop($inj->dparent, $inj->key);
2443
+ $out = self::_getprop($inj->dparent, $inj->key);
2381
2444
 
2382
2445
  $t = self::typify($out);
2383
2446
  if (0 === (self::T_list & $t)) {
2384
2447
  $inj->errs[] = self::_invalidTypeMsg($inj->path, 'list', $t, $out);
2385
- return self::UNDEF;
2448
+ return self::undef();
2386
2449
  }
2387
2450
 
2388
2451
  return $out;
@@ -2393,12 +2456,12 @@ class Struct
2393
2456
  */
2394
2457
  public static function validate_FUNCTION(object $inj): mixed
2395
2458
  {
2396
- $out = self::getprop($inj->dparent, $inj->key);
2459
+ $out = self::_getprop($inj->dparent, $inj->key);
2397
2460
 
2398
2461
  $t = self::typify($out);
2399
2462
  if (0 === (self::T_function & $t)) {
2400
2463
  $inj->errs[] = self::_invalidTypeMsg($inj->path, self::S_function, $t, $out);
2401
- return self::UNDEF;
2464
+ return self::undef();
2402
2465
  }
2403
2466
 
2404
2467
  return $out;
@@ -2412,12 +2475,12 @@ class Struct
2412
2475
  $tname = strtolower(substr($ref ?? '', 1));
2413
2476
  $idx = array_search($tname, self::TYPENAME);
2414
2477
  $typev = ($idx !== false) ? (1 << (31 - $idx)) : 0;
2415
- $out = self::getprop($inj->dparent, $inj->key);
2478
+ $out = self::_getprop($inj->dparent, $inj->key);
2416
2479
 
2417
2480
  $t = self::typify($out);
2418
2481
  if (0 === ($t & $typev)) {
2419
2482
  $inj->errs[] = self::_invalidTypeMsg($inj->path, $tname, $t, $out);
2420
- return self::UNDEF;
2483
+ return self::undef();
2421
2484
  }
2422
2485
 
2423
2486
  return $out;
@@ -2428,7 +2491,7 @@ class Struct
2428
2491
  */
2429
2492
  public static function validate_ANY(object $inj): mixed
2430
2493
  {
2431
- $out = self::getprop($inj->dparent, $inj->key);
2494
+ $out = self::_getprop($inj->dparent, $inj->key);
2432
2495
  return $out;
2433
2496
  }
2434
2497
 
@@ -2447,18 +2510,18 @@ class Struct
2447
2510
 
2448
2511
  // Map syntax.
2449
2512
  if (self::M_KEYPRE === $mode) {
2450
- $childtm = self::getprop($parent, $key);
2513
+ $childtm = self::_getprop($parent, $key);
2451
2514
 
2452
2515
  // Get corresponding current object.
2453
- $pkey = self::getprop($path, count($path) - 2);
2454
- $tval = self::getprop($inj->dparent, $pkey);
2516
+ $pkey = self::_getprop($path, count($path) - 2);
2517
+ $tval = self::_getprop($inj->dparent, $pkey);
2455
2518
 
2456
- if (self::UNDEF == $tval) {
2519
+ if (self::undef() == $tval) {
2457
2520
  $tval = new \stdClass();
2458
2521
  } elseif (!self::ismap($tval)) {
2459
2522
  $inj->errs[] = self::_invalidTypeMsg(
2460
2523
  self::slice($inj->path, 0, -1), self::S_object, self::typify($tval), $tval);
2461
- return self::UNDEF;
2524
+ return self::undef();
2462
2525
 
2463
2526
  }
2464
2527
 
@@ -2471,8 +2534,8 @@ class Struct
2471
2534
  $inj->keys = $keys;
2472
2535
 
2473
2536
  // Remove $CHILD to cleanup output.
2474
- $inj->setval(self::UNDEF);
2475
- return self::UNDEF;
2537
+ $inj->setval(self::undef());
2538
+ return self::undef();
2476
2539
  }
2477
2540
 
2478
2541
  // List syntax.
@@ -2480,17 +2543,17 @@ class Struct
2480
2543
  if (!self::islist($parent)) {
2481
2544
  // $CHILD was not inside a list.
2482
2545
  $inj->errs[] = 'Invalid $CHILD as value';
2483
- return self::UNDEF;
2546
+ return self::undef();
2484
2547
  }
2485
2548
 
2486
- $childtm = self::getprop($parent, 1);
2549
+ $childtm = self::_getprop($parent, 1);
2487
2550
 
2488
- if (self::UNDEF === $inj->dparent) {
2551
+ if (self::undef() === $inj->dparent) {
2489
2552
  // Empty list as default.
2490
2553
  while (count($parent) > 0) {
2491
2554
  array_pop($parent);
2492
2555
  }
2493
- return self::UNDEF;
2556
+ return self::undef();
2494
2557
  }
2495
2558
 
2496
2559
  if (!self::islist($inj->dparent)) {
@@ -2510,11 +2573,11 @@ class Struct
2510
2573
  array_pop($parent);
2511
2574
  }
2512
2575
  $inj->keyI = 0;
2513
- $out = self::getprop($inj->dparent, 0);
2576
+ $out = self::_getprop($inj->dparent, 0);
2514
2577
  return $out;
2515
2578
  }
2516
2579
 
2517
- return self::UNDEF;
2580
+ return self::undef();
2518
2581
  }
2519
2582
 
2520
2583
  /**
@@ -2537,7 +2600,7 @@ class Struct
2537
2600
  $inj->errs[] = 'The $ONE validator at field ' .
2538
2601
  self::pathify($inj->path, 1, 1) .
2539
2602
  ' must be the first element of an array.';
2540
- return self::UNDEF;
2603
+ return self::undef();
2541
2604
  }
2542
2605
 
2543
2606
  $inj->keyI = count($inj->keys ?? []);
@@ -2553,7 +2616,7 @@ class Struct
2553
2616
  $inj->errs[] = 'The $ONE validator at field ' .
2554
2617
  self::pathify($inj->path, 1, 1) .
2555
2618
  ' must have at least one argument.';
2556
- return self::UNDEF;
2619
+ return self::undef();
2557
2620
  }
2558
2621
 
2559
2622
  // See if we can find a match.
@@ -2573,7 +2636,7 @@ class Struct
2573
2636
 
2574
2637
  // Accept current value if there was a match
2575
2638
  if (0 === count($terrs)) {
2576
- return self::UNDEF;
2639
+ return self::undef();
2577
2640
  }
2578
2641
  }
2579
2642
 
@@ -2588,7 +2651,7 @@ class Struct
2588
2651
  self::typify($inj->dparent), $inj->dparent);
2589
2652
  }
2590
2653
 
2591
- return self::UNDEF;
2654
+ return self::undef();
2592
2655
  }
2593
2656
 
2594
2657
  /**
@@ -2607,7 +2670,7 @@ class Struct
2607
2670
  $inj->errs[] = 'The $EXACT validator at field ' .
2608
2671
  self::pathify($inj->path, 1, 1) .
2609
2672
  ' must be the first element of an array.';
2610
- return self::UNDEF;
2673
+ return self::undef();
2611
2674
  }
2612
2675
 
2613
2676
  $inj->keyI = count($inj->keys ?? []);
@@ -2623,7 +2686,7 @@ class Struct
2623
2686
  $inj->errs[] = 'The $EXACT validator at field ' .
2624
2687
  self::pathify($inj->path, 1, 1) .
2625
2688
  ' must have at least one argument.';
2626
- return self::UNDEF;
2689
+ return self::undef();
2627
2690
  }
2628
2691
 
2629
2692
  // See if we can find an exact value match.
@@ -2638,7 +2701,7 @@ class Struct
2638
2701
  }
2639
2702
 
2640
2703
  if ($exactmatch) {
2641
- return self::UNDEF;
2704
+ return self::undef();
2642
2705
  }
2643
2706
  }
2644
2707
 
@@ -2655,7 +2718,7 @@ class Struct
2655
2718
  self::delprop($parent, $key);
2656
2719
  }
2657
2720
 
2658
- return self::UNDEF;
2721
+ return self::undef();
2659
2722
  }
2660
2723
 
2661
2724
  /**
@@ -2666,10 +2729,10 @@ class Struct
2666
2729
  mixed $pval,
2667
2730
  mixed $key = null,
2668
2731
  mixed $parent = null,
2669
- object $inj = null,
2732
+ ?object $inj = null,
2670
2733
  mixed $store = null
2671
2734
  ): void {
2672
- if (self::UNDEF === $inj) {
2735
+ if (self::undef() === $inj) {
2673
2736
  return;
2674
2737
  }
2675
2738
 
@@ -2678,12 +2741,12 @@ class Struct
2678
2741
  }
2679
2742
 
2680
2743
  // select needs exact matches
2681
- $exact = self::getprop($inj->meta, '`$EXACT`');
2744
+ $exact = self::_getprop($inj->meta, '`$EXACT`', false);
2682
2745
 
2683
2746
  // Current val to verify.
2684
- $cval = self::getprop($inj->dparent, $key);
2747
+ $cval = self::_getprop($inj->dparent, $key);
2685
2748
 
2686
- if (self::UNDEF === $inj || (!$exact && self::UNDEF === $cval)) {
2749
+ if (self::undef() === $inj || (!$exact && self::undef() === $cval)) {
2687
2750
  return;
2688
2751
  }
2689
2752
 
@@ -2708,7 +2771,7 @@ class Struct
2708
2771
  }
2709
2772
 
2710
2773
  // Type mismatch.
2711
- if ($ptype !== $ctype && self::UNDEF !== $pval) {
2774
+ if ($ptype !== $ctype && self::undef() !== $pval) {
2712
2775
  $inj->errs[] = self::_invalidTypeMsg($inj->path, self::typename($ptype), $ctype, $cval);
2713
2776
  return;
2714
2777
  }
@@ -2723,7 +2786,7 @@ class Struct
2723
2786
  $pkeys = self::keysof($pval);
2724
2787
 
2725
2788
  // Empty spec object {} means object can be open (any keys).
2726
- if (0 < count($pkeys) && true !== self::getprop($pval, '`$OPEN`')) {
2789
+ if (0 < count($pkeys) && true !== self::_getprop($pval, '`$OPEN`')) {
2727
2790
  $badkeys = [];
2728
2791
  foreach ($ckeys as $ckey) {
2729
2792
  if (!self::haskey($pval, $ckey)) {
@@ -2802,7 +2865,17 @@ class Struct
2802
2865
  $extra = is_object($injdef) && property_exists($injdef, 'extra') ? $injdef->extra : null;
2803
2866
 
2804
2867
  $collect = null != $injdef && property_exists($injdef, 'errs');
2868
+
2869
+ // PHP arrays are value-copied, so a plain array on $injdef->errs would be
2870
+ // detached from $inj->errs deep inside inject. Wrap in ArrayObject so the
2871
+ // same storage is shared through the whole transform/inject chain.
2805
2872
  $errs = (is_object($injdef) && property_exists($injdef, 'errs')) ? $injdef->errs : [];
2873
+ if (is_array($errs)) {
2874
+ $errs = new \ArrayObject($errs);
2875
+ }
2876
+ if ($collect) {
2877
+ $injdef->errs = $errs;
2878
+ }
2806
2879
 
2807
2880
  $store = array_merge([
2808
2881
  // Remove the transform commands.
@@ -2846,9 +2919,15 @@ class Struct
2846
2919
  $transformOpts->errs = $errs;
2847
2920
  $out = self::transform($data, $spec, $transformOpts);
2848
2921
 
2922
+ // Unwrap shared ArrayObject back to a plain array on the caller's injdef
2923
+ // so count()/foreach work as callers (including select) expect.
2924
+ if ($collect && $injdef->errs instanceof \ArrayObject) {
2925
+ $injdef->errs = $injdef->errs->getArrayCopy();
2926
+ }
2927
+
2849
2928
  $generr = (0 < count($errs) && !$collect);
2850
2929
  if ($generr) {
2851
- throw new \Exception('Invalid data: ' . implode(' | ', $errs));
2930
+ throw new \Exception('Invalid data: ' . implode(' | ', (array) $errs));
2852
2931
  }
2853
2932
 
2854
2933
  return $out;
@@ -2900,13 +2979,7 @@ class Struct
2900
2979
  ];
2901
2980
 
2902
2981
  $q = self::clone($query);
2903
-
2904
- self::walk($q, function($k, $v) {
2905
- if (self::ismap($v)) {
2906
- self::setprop($v, '`$OPEN`', self::getprop($v, '`$OPEN`', true));
2907
- }
2908
- return $v;
2909
- });
2982
+ $q = self::_select_add_open($q);
2910
2983
 
2911
2984
  foreach ($children as $child) {
2912
2985
  $injdef->errs = [];
@@ -2920,13 +2993,41 @@ class Struct
2920
2993
  return $results;
2921
2994
  }
2922
2995
 
2996
+ // Recursively add `$OPEN` to all maps in a query for select.
2997
+ // PHP arrays are value types, so walk can't modify in-place.
2998
+ private static function _select_add_open(mixed $val): mixed
2999
+ {
3000
+ if (self::ismap($val)) {
3001
+ if (is_array($val)) {
3002
+ if (!array_key_exists('`$OPEN`', $val)) {
3003
+ $val['`$OPEN`'] = true;
3004
+ }
3005
+ foreach ($val as $k => $v) {
3006
+ $val[$k] = self::_select_add_open($v);
3007
+ }
3008
+ } elseif ($val instanceof \stdClass) {
3009
+ if (!property_exists($val, '`$OPEN`')) {
3010
+ $val->{'`$OPEN`'} = true;
3011
+ }
3012
+ foreach (get_object_vars($val) as $k => $v) {
3013
+ $val->$k = self::_select_add_open($v);
3014
+ }
3015
+ }
3016
+ } elseif (self::islist($val) && is_array($val)) {
3017
+ foreach ($val as $i => $v) {
3018
+ $val[$i] = self::_select_add_open($v);
3019
+ }
3020
+ }
3021
+ return $val;
3022
+ }
3023
+
2923
3024
  /**
2924
3025
  * Helper method for $AND operator in select queries
2925
3026
  */
2926
3027
  private static function select_AND(object $state, mixed $_val, mixed $_ref, mixed $store): mixed
2927
3028
  {
2928
3029
  if (self::M_KEYPRE === $state->mode) {
2929
- $terms = self::getprop($state->parent, $state->key);
3030
+ $terms = self::_getprop($state->parent, $state->key);
2930
3031
 
2931
3032
  $ppath = self::slice($state->path, -1);
2932
3033
  $point = self::getpath($store, $ppath);
@@ -2960,7 +3061,7 @@ class Struct
2960
3061
  private static function select_OR(object $state, mixed $_val, mixed $_ref, mixed $store): mixed
2961
3062
  {
2962
3063
  if (self::M_KEYPRE === $state->mode) {
2963
- $terms = self::getprop($state->parent, $state->key);
3064
+ $terms = self::_getprop($state->parent, $state->key);
2964
3065
 
2965
3066
  $ppath = self::slice($state->path, -1);
2966
3067
  $point = self::getpath($store, $ppath);
@@ -2996,7 +3097,7 @@ class Struct
2996
3097
  private static function select_NOT(object $state, mixed $_val, mixed $_ref, mixed $store): mixed
2997
3098
  {
2998
3099
  if (self::M_KEYPRE === $state->mode) {
2999
- $term = self::getprop($state->parent, $state->key);
3100
+ $term = self::_getprop($state->parent, $state->key);
3000
3101
 
3001
3102
  $ppath = self::slice($state->path, -1);
3002
3103
  $point = self::getpath($store, $ppath);
@@ -3028,7 +3129,7 @@ class Struct
3028
3129
  private static function select_CMP(object $state, mixed $_val, mixed $ref, mixed $store): mixed
3029
3130
  {
3030
3131
  if (self::M_KEYPRE === $state->mode) {
3031
- $term = self::getprop($state->parent, $state->key);
3132
+ $term = self::_getprop($state->parent, $state->key);
3032
3133
  $gkey = self::getelem($state->path, -2);
3033
3134
 
3034
3135
  $ppath = self::slice($state->path, -1);
@@ -3070,12 +3171,13 @@ class Struct
3070
3171
  * The key should be an integer, or a string that can parse to an integer only.
3071
3172
  * Negative integers count from the end of the list.
3072
3173
  */
3073
- public static function getelem(mixed $val, mixed $key, mixed $alt = self::UNDEF): mixed
3174
+ public static function getelem(mixed $val, mixed $key, mixed $alt = null): mixed
3074
3175
  {
3075
- $out = self::UNDEF;
3176
+ $altIsDefault = (func_num_args() < 3);
3177
+ $out = self::undef();
3076
3178
 
3077
- if ($val === self::UNDEF || $key === self::UNDEF) {
3078
- return $alt === self::UNDEF ? null : (is_callable($alt) ? $alt() : $alt);
3179
+ if ($val === null || $val === self::undef() || $key === null || $key === self::undef()) {
3180
+ return $altIsDefault ? null : (is_callable($alt) ? $alt() : $alt);
3079
3181
  }
3080
3182
 
3081
3183
  if (self::islist($val)) {
@@ -3083,25 +3185,25 @@ class Struct
3083
3185
  $listLen = count($listArr);
3084
3186
  if (is_string($key)) {
3085
3187
  if (!preg_match('/^[-0-9]+$/', $key)) {
3086
- $out = self::UNDEF;
3188
+ $out = self::undef();
3087
3189
  } else {
3088
3190
  $nkey = (int) $key;
3089
3191
  if ($nkey < 0) {
3090
3192
  $nkey = $listLen + $nkey;
3091
3193
  }
3092
- $out = ($nkey >= 0 && $nkey < $listLen) ? $listArr[$nkey] : self::UNDEF;
3194
+ $out = ($nkey >= 0 && $nkey < $listLen) ? $listArr[$nkey] : self::undef();
3093
3195
  }
3094
3196
  } elseif (is_int($key)) {
3095
3197
  $nkey = $key;
3096
3198
  if ($nkey < 0) {
3097
3199
  $nkey = $listLen + $nkey;
3098
3200
  }
3099
- $out = ($nkey >= 0 && $nkey < $listLen) ? $listArr[$nkey] : self::UNDEF;
3201
+ $out = ($nkey >= 0 && $nkey < $listLen) ? $listArr[$nkey] : self::undef();
3100
3202
  }
3101
3203
  }
3102
3204
 
3103
- if ($out === self::UNDEF) {
3104
- if ($alt === self::UNDEF) {
3205
+ if ($out === self::undef()) {
3206
+ if ($altIsDefault) {
3105
3207
  return null;
3106
3208
  }
3107
3209
  return is_callable($alt) ? $alt() : $alt;
@@ -3168,19 +3270,19 @@ class Struct
3168
3270
 
3169
3271
  $parts = (0 < (self::T_list & $pathType)) ? $path :
3170
3272
  ((0 < (self::T_string & $pathType)) ? explode('.', $path) :
3171
- ((0 < (self::T_number & $pathType)) ? [$path] : self::UNDEF));
3273
+ ((0 < (self::T_number & $pathType)) ? [$path] : self::undef()));
3172
3274
 
3173
- if (self::UNDEF === $parts) {
3174
- return self::UNDEF;
3275
+ if (self::undef() === $parts) {
3276
+ return self::undef();
3175
3277
  }
3176
3278
 
3177
- $base = self::getprop($injdef, self::S_BASE);
3279
+ $base = self::_getprop($injdef, self::S_BASE);
3178
3280
  $numparts = self::size($parts);
3179
- $parent = self::getprop($store, $base, $store);
3281
+ $parent = self::_getprop($store, $base, $store);
3180
3282
 
3181
3283
  for ($pI = 0; $pI < $numparts - 1; $pI++) {
3182
3284
  $partKey = self::getelem($parts, $pI);
3183
- $nextParent = self::getprop($parent, $partKey);
3285
+ $nextParent = self::_getprop($parent, $partKey);
3184
3286
  if (!self::isnode($nextParent)) {
3185
3287
  $nextParent = (0 < (self::T_number & self::typify(self::getelem($parts, $pI + 1))))
3186
3288
  ? [] : new \stdClass();
@@ -3230,10 +3332,10 @@ class Struct
3230
3332
  public static function injectorArgs(array $argTypes, array $args): array
3231
3333
  {
3232
3334
  $numargs = self::size($argTypes);
3233
- $found = array_fill(0, 1 + $numargs, self::UNDEF);
3234
- $found[0] = self::UNDEF;
3335
+ $found = array_fill(0, 1 + $numargs, self::undef());
3336
+ $found[0] = self::undef();
3235
3337
  for ($argI = 0; $argI < $numargs; $argI++) {
3236
- $arg = $args[$argI] ?? self::UNDEF;
3338
+ $arg = $args[$argI] ?? self::undef();
3237
3339
  $argType = self::typify($arg);
3238
3340
  if (0 === ($argTypes[$argI] & $argType)) {
3239
3341
  $found[0] = 'invalid argument: ' . self::stringify($arg, 22) .
@@ -3287,7 +3389,9 @@ class Injection
3287
3389
  public array $nodes;
3288
3390
  /** @var callable */
3289
3391
  public mixed $handler;
3290
- public array $errs;
3392
+ // Accepts plain array or ArrayObject. ArrayObject is used by validate/select
3393
+ // so that mutations propagate back through the inject/transform call chain.
3394
+ public array|\ArrayObject $errs;
3291
3395
  public object $meta;
3292
3396
  public mixed $dparent;
3293
3397
  public array $dpath;
@@ -3336,7 +3440,7 @@ class Injection
3336
3440
  ' p=' . Struct::stringify($this->parent, -1, 1) .
3337
3441
  ' m=' . Struct::stringify($this->meta, -1, 1) .
3338
3442
  ' d/' . Struct::pathify($this->dpath, 1) . '=' . Struct::stringify($this->dparent, -1, 1) .
3339
- ' r=' . Struct::stringify(Struct::getprop($this->nodes[0] ?? null, '$TOP'), -1, 1);
3443
+ ' r=' . Struct::stringify(Struct::_getprop($this->nodes[0] ?? null, '$TOP'), -1, 1);
3340
3444
  }
3341
3445
 
3342
3446
 
@@ -3360,7 +3464,7 @@ class Injection
3360
3464
  else {
3361
3465
  // this->dparent is the containing node of the current store value.
3362
3466
  if (null !== $parentkey && Struct::UNDEF !== $parentkey) {
3363
- $this->dparent = Struct::getprop($this->dparent, $parentkey);
3467
+ $this->dparent = Struct::_getprop($this->dparent, $parentkey);
3364
3468
 
3365
3469
  $lastpart = Struct::getelem($this->dpath, -1);
3366
3470
  if ($lastpart === '$:' . $parentkey) {
@@ -3381,7 +3485,7 @@ class Injection
3381
3485
  $key = Struct::strkey($keys[$keyI] ?? null);
3382
3486
  $val = $this->val;
3383
3487
 
3384
- $cinj = new Injection(Struct::getprop($val, $key), $val);
3488
+ $cinj = new Injection(Struct::_getprop($val, $key), $val);
3385
3489
  $cinj->keyI = $keyI;
3386
3490
  $cinj->keys = $keys;
3387
3491
  $cinj->key = $key;
@@ -3428,4 +3532,4 @@ class Injection
3428
3532
  return $parent;
3429
3533
  }
3430
3534
  }
3431
- ?>
3535
+ ?>