@warmhub/sdk-ts 0.51.0 → 0.52.1

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.
@@ -50,6 +50,19 @@ function splitLocalPath(name) {
50
50
  if (idx === -1) return null;
51
51
  return { shapePrefix: name.slice(0, idx), bareName: name.slice(idx + 1) };
52
52
  }
53
+ function inferOperationKind(op) {
54
+ if (op.kind === "shape" || op.kind === "thing" || op.kind === "assertion" || op.kind === "collection") {
55
+ return op.kind;
56
+ }
57
+ if (op.about !== void 0) {
58
+ return "assertion";
59
+ }
60
+ if (typeof op.type === "string" && op.type.length > 0 && Array.isArray(op.members)) {
61
+ return "collection";
62
+ }
63
+ const segments = op.name ? op.name.split("/").filter(Boolean) : [];
64
+ return segments.length >= 3 ? "assertion" : "thing";
65
+ }
53
66
 
54
67
  // ../rules/src/component-cli-signing.ts
55
68
  var CLI_INSTALL_REPO_HEADER = "X-WarmHub-Install-Repo";
@@ -170,6 +183,114 @@ var REPO_AUTH_SCOPES = [
170
183
 
171
184
  // ../rules/src/component-manifest-contract.ts
172
185
  new Set(REPO_AUTH_SCOPES);
186
+
187
+ // ../rules/src/field-name-safety.ts
188
+ var ESCAPE_DISPLAY_MAX_CHARS = 120;
189
+ var DANGEROUS_FORMAT_CODEPOINTS = /* @__PURE__ */ new Set([
190
+ 8203,
191
+ // zero width space
192
+ 8204,
193
+ // zero width non-joiner
194
+ 8205,
195
+ // zero width joiner
196
+ 8288,
197
+ // word joiner
198
+ 8294,
199
+ // left-to-right isolate
200
+ 8295,
201
+ // right-to-left isolate
202
+ 8296,
203
+ // first strong isolate
204
+ 8297,
205
+ // pop directional isolate
206
+ 8234,
207
+ // left-to-right embedding
208
+ 8235,
209
+ // right-to-left embedding
210
+ 8236,
211
+ // pop directional formatting
212
+ 8237,
213
+ // left-to-right override
214
+ 8238,
215
+ // right-to-left override
216
+ 8232,
217
+ // line separator
218
+ 8233
219
+ // paragraph separator
220
+ ]);
221
+ function isControlCodePoint(codePoint) {
222
+ return codePoint <= 31 || codePoint === 127 || codePoint >= 128 && codePoint <= 159;
223
+ }
224
+ function escapeCodePoint(codePoint) {
225
+ return codePoint <= 65535 ? `\\u${codePoint.toString(16).padStart(4, "0")}` : `\\u{${codePoint.toString(16)}}`;
226
+ }
227
+ function needsEscape(value) {
228
+ for (const char of value) {
229
+ const codePoint = char.codePointAt(0);
230
+ if (codePoint === void 0) continue;
231
+ if (isControlCodePoint(codePoint)) return true;
232
+ if (DANGEROUS_FORMAT_CODEPOINTS.has(codePoint)) return true;
233
+ if (codePoint === 34) return true;
234
+ }
235
+ return false;
236
+ }
237
+ function clip(value) {
238
+ return value.length <= ESCAPE_DISPLAY_MAX_CHARS ? value : `${value.slice(0, ESCAPE_DISPLAY_MAX_CHARS)}...<truncated>`;
239
+ }
240
+ function escapeFieldName(value) {
241
+ if (!needsEscape(value)) return value;
242
+ const jsonEscaped = JSON.stringify(value).slice(1, -1);
243
+ let output = "";
244
+ for (const char of jsonEscaped) {
245
+ const codePoint = char.codePointAt(0);
246
+ if (codePoint === void 0) continue;
247
+ output += isControlCodePoint(codePoint) || DANGEROUS_FORMAT_CODEPOINTS.has(codePoint) ? escapeCodePoint(codePoint) : char;
248
+ }
249
+ return output;
250
+ }
251
+ function escapeFieldNameForDisplay(value) {
252
+ return clip(escapeFieldName(value));
253
+ }
254
+ function escapeFieldNameForIdentity(value) {
255
+ return escapeFieldName(value);
256
+ }
257
+ function isUnsafeFieldName(name) {
258
+ if (name.length === 0) return { unsafe: true, reason: "empty field name" };
259
+ if (name !== name.trim()) {
260
+ return { unsafe: true, reason: "leading or trailing whitespace" };
261
+ }
262
+ for (const char of name) {
263
+ const codePoint = char.codePointAt(0);
264
+ if (codePoint === void 0) continue;
265
+ if (isControlCodePoint(codePoint)) {
266
+ return {
267
+ unsafe: true,
268
+ reason: `control character (U+${codePoint.toString(16).padStart(4, "0").toUpperCase()})`
269
+ };
270
+ }
271
+ if (DANGEROUS_FORMAT_CODEPOINTS.has(codePoint)) {
272
+ return {
273
+ unsafe: true,
274
+ reason: `format or separator character (U+${codePoint.toString(16).padStart(4, "0").toUpperCase()})`
275
+ };
276
+ }
277
+ }
278
+ return { unsafe: false };
279
+ }
280
+ function stripOptionalMarker(key) {
281
+ return key.endsWith("?") ? key.slice(0, -1) : key;
282
+ }
283
+ function joinFieldPath(...segments) {
284
+ return joinFieldPathWithEscaper(escapeFieldNameForDisplay, segments);
285
+ }
286
+ function joinFieldIdentityPath(...segments) {
287
+ return joinFieldPathWithEscaper(escapeFieldNameForIdentity, segments);
288
+ }
289
+ function joinFieldPathWithEscaper(escapeSegment, segments) {
290
+ return segments.map(
291
+ (segment) => typeof segment === "number" ? `[${segment}]` : escapeSegment(segment)
292
+ ).join(".").replace(/\.\[/g, "[");
293
+ }
173
294
  var PLATFORM_STATUS_STATUSES = [
174
295
  {
175
296
  key: "available",
@@ -565,12 +686,6 @@ function tokenHygiene(operations, errors) {
565
686
  }
566
687
  }
567
688
  }
568
- function inferKindFromName(name) {
569
- const parts = name.split("/").filter(Boolean);
570
- if (parts.length >= 3) return "assertion";
571
- if (parts.length === 2) return "thing";
572
- return "thing";
573
- }
574
689
  function illegalOpSequences(operations, errors, checkAddAdd) {
575
690
  const opHistory = /* @__PURE__ */ new Map();
576
691
  for (let i = 0; i < operations.length; i++) {
@@ -579,7 +694,7 @@ function illegalOpSequences(operations, errors, checkAddAdd) {
579
694
  const name = getOpName(op);
580
695
  if (!name) continue;
581
696
  if (hasAnyTokens(name)) continue;
582
- const kind = op.kind ?? (name ? inferKindFromName(name) : "thing");
697
+ const kind = inferOperationKind({ ...op, name });
583
698
  const qualName = kind === "shape" ? `shape:${name}` : `thing:${name}`;
584
699
  const history = opHistory.get(qualName) ?? [];
585
700
  history.push({ operation: op.operation, index: i });
@@ -745,6 +860,12 @@ function basePrimitiveTypeFromSpec(fieldType) {
745
860
  // ../rules/src/shape-validation.ts
746
861
  var MAX_CONTENT_FIELD_BYTES = 64 * 1024;
747
862
  var MAX_INDEXABLE_SCALAR_FIELDS_PER_SHAPE = 256;
863
+ var UNSUPPORTED_REGEX_SYNTAX_REASON = "uses unsupported regex syntax";
864
+ var EXCESSIVE_BACKTRACKING_REGEX_REASON = "uses unsupported regex syntax that can cause excessive backtracking";
865
+ var MAX_FIXED_WIDTH_ALTERNATIVE_TOKENS = MAX_CONTENT_FIELD_BYTES;
866
+ var MAX_REGEX_SCAN_GROUP_DEPTH = 64;
867
+ var MAX_SAFE_OVERLAPPING_GROUP_REPEATS = 8;
868
+ var MAX_SAFE_TOP_LEVEL_ALTERNATIVES = 128;
748
869
  var CONTENT_FIELD_LIMIT_ERROR = `WarmHub content fields are limited to ${MAX_CONTENT_FIELD_BYTES} bytes. WarmHub is not a document store; store large documents in S3, Box, Drive, or another document system and reference them from WarmHub instead.`;
749
870
  var textEncoder = new TextEncoder();
750
871
  function utf8ByteLength(value) {
@@ -760,6 +881,801 @@ function assertContentFieldWithinLimit(path, value, errors) {
760
881
  const message = contentFieldLimitError(path, value);
761
882
  if (message) errors.push(message);
762
883
  }
884
+ function regexTokenSet(chars, negated = false) {
885
+ return { chars: new Set(chars), negated };
886
+ }
887
+ function regexQuantifierAt(pattern, index) {
888
+ const char = pattern[index];
889
+ if (char === "*" || char === "+") {
890
+ return {
891
+ kind: "unbounded",
892
+ endIndex: index,
893
+ minZero: char === "*",
894
+ variableLength: true,
895
+ max: void 0
896
+ };
897
+ }
898
+ if (char === "?") {
899
+ return {
900
+ kind: "bounded",
901
+ endIndex: index,
902
+ minZero: true,
903
+ variableLength: true,
904
+ max: 1
905
+ };
906
+ }
907
+ if (char !== "{") {
908
+ return {
909
+ kind: "none",
910
+ endIndex: index,
911
+ minZero: false,
912
+ variableLength: false,
913
+ max: void 0
914
+ };
915
+ }
916
+ const closeIndex = pattern.indexOf("}", index + 1);
917
+ if (closeIndex === -1) {
918
+ return {
919
+ kind: "none",
920
+ endIndex: index,
921
+ minZero: false,
922
+ variableLength: false,
923
+ max: void 0
924
+ };
925
+ }
926
+ const body = pattern.slice(index + 1, closeIndex);
927
+ if (!/^\d+(?:,\d*)?$/.test(body)) {
928
+ return {
929
+ kind: "none",
930
+ endIndex: index,
931
+ minZero: false,
932
+ variableLength: false,
933
+ max: void 0
934
+ };
935
+ }
936
+ const [minText, maxText] = body.split(",", 2);
937
+ const min = Number(minText);
938
+ const max = maxText === void 0 || maxText === "" ? min : Number(maxText);
939
+ return {
940
+ kind: body.endsWith(",") ? "unbounded" : "bounded",
941
+ endIndex: closeIndex,
942
+ minZero: min === 0,
943
+ variableLength: body.includes(",") && min !== max,
944
+ max: body.endsWith(",") ? void 0 : max
945
+ };
946
+ }
947
+ function skipCharacterClass(pattern, index) {
948
+ for (let i = index + 1; i < pattern.length; i++) {
949
+ if (pattern[i] === "\\") {
950
+ i++;
951
+ continue;
952
+ }
953
+ if (pattern[i] === "]") return i;
954
+ }
955
+ return pattern.length - 1;
956
+ }
957
+ function splitTopLevelAlternatives(pattern, startIndex, endIndex) {
958
+ const alternatives = [];
959
+ let depth = 0;
960
+ let partStart = startIndex;
961
+ for (let i = startIndex; i < endIndex; i++) {
962
+ const char = pattern[i];
963
+ if (char === "\\") {
964
+ i++;
965
+ continue;
966
+ }
967
+ if (char === "[") {
968
+ i = skipCharacterClass(pattern, i);
969
+ continue;
970
+ }
971
+ if (char === "(") {
972
+ depth++;
973
+ continue;
974
+ }
975
+ if (char === ")" && depth > 0) {
976
+ depth--;
977
+ continue;
978
+ }
979
+ if (char === "|" && depth === 0) {
980
+ alternatives.push(pattern.slice(partStart, i));
981
+ partStart = i + 1;
982
+ }
983
+ }
984
+ alternatives.push(pattern.slice(partStart, endIndex));
985
+ return alternatives;
986
+ }
987
+ function asciiRange(start, end) {
988
+ const startCode = start.charCodeAt(0);
989
+ const endCode = end.charCodeAt(0);
990
+ if (start.length !== 1 || end.length !== 1 || startCode > endCode) {
991
+ return void 0;
992
+ }
993
+ const chars = /* @__PURE__ */ new Set();
994
+ for (let code = startCode; code <= endCode; code++) {
995
+ chars.add(String.fromCharCode(code));
996
+ }
997
+ return chars;
998
+ }
999
+ var ASCII_WHITESPACE_CHARS = /* @__PURE__ */ new Set([" ", "\n", "\v", "\f", "\r", " "]);
1000
+ var JS_DOT_EXCLUDED_CHARS = /* @__PURE__ */ new Set(["\n", "\r", "\u2028", "\u2029"]);
1001
+ function escapedAtomChars(value, index, inCharacterClass = false) {
1002
+ const next = value[index + 1];
1003
+ if (!next) return void 0;
1004
+ if (next === "0" && !/^[0-9]$/.test(value[index + 2] ?? "")) {
1005
+ return { tokenSet: regexTokenSet(["\0"]), endIndex: index + 1 };
1006
+ }
1007
+ if (next === "b" && inCharacterClass) {
1008
+ return { tokenSet: regexTokenSet(["\b"]), endIndex: index + 1 };
1009
+ }
1010
+ if (next === "x") {
1011
+ const hex = value.slice(index + 2, index + 4);
1012
+ if (/^[0-9A-Fa-f]{2}$/.test(hex)) {
1013
+ return {
1014
+ tokenSet: regexTokenSet([
1015
+ String.fromCharCode(Number.parseInt(hex, 16))
1016
+ ]),
1017
+ endIndex: index + 3
1018
+ };
1019
+ }
1020
+ return void 0;
1021
+ }
1022
+ if (next === "u") {
1023
+ const hex = value.slice(index + 2, index + 6);
1024
+ if (/^[0-9A-Fa-f]{4}$/.test(hex)) {
1025
+ return {
1026
+ tokenSet: regexTokenSet([
1027
+ String.fromCharCode(Number.parseInt(hex, 16))
1028
+ ]),
1029
+ endIndex: index + 5
1030
+ };
1031
+ }
1032
+ return void 0;
1033
+ }
1034
+ if (next === "d")
1035
+ return {
1036
+ tokenSet: regexTokenSet(asciiRange("0", "9") ?? []),
1037
+ endIndex: index + 1
1038
+ };
1039
+ if (next === "D")
1040
+ return {
1041
+ tokenSet: regexTokenSet(asciiRange("0", "9") ?? [], true),
1042
+ endIndex: index + 1
1043
+ };
1044
+ if (next === "w") {
1045
+ return {
1046
+ tokenSet: regexTokenSet([
1047
+ ...asciiRange("0", "9") ?? [],
1048
+ ...asciiRange("A", "Z") ?? [],
1049
+ ...asciiRange("a", "z") ?? [],
1050
+ "_"
1051
+ ]),
1052
+ endIndex: index + 1
1053
+ };
1054
+ }
1055
+ if (next === "W") {
1056
+ return {
1057
+ tokenSet: regexTokenSet(
1058
+ [
1059
+ ...asciiRange("0", "9") ?? [],
1060
+ ...asciiRange("A", "Z") ?? [],
1061
+ ...asciiRange("a", "z") ?? [],
1062
+ "_"
1063
+ ],
1064
+ true
1065
+ ),
1066
+ endIndex: index + 1
1067
+ };
1068
+ }
1069
+ if (next === "s") {
1070
+ return {
1071
+ tokenSet: regexTokenSet(ASCII_WHITESPACE_CHARS),
1072
+ endIndex: index + 1
1073
+ };
1074
+ }
1075
+ if (next === "S") {
1076
+ return {
1077
+ tokenSet: regexTokenSet(ASCII_WHITESPACE_CHARS, true),
1078
+ endIndex: index + 1
1079
+ };
1080
+ }
1081
+ if (next === "n")
1082
+ return { tokenSet: regexTokenSet(["\n"]), endIndex: index + 1 };
1083
+ if (next === "r")
1084
+ return { tokenSet: regexTokenSet(["\r"]), endIndex: index + 1 };
1085
+ if (next === "t")
1086
+ return { tokenSet: regexTokenSet([" "]), endIndex: index + 1 };
1087
+ if (next === "f")
1088
+ return { tokenSet: regexTokenSet(["\f"]), endIndex: index + 1 };
1089
+ if (next === "v")
1090
+ return { tokenSet: regexTokenSet(["\v"]), endIndex: index + 1 };
1091
+ if (/^[^A-Za-z0-9]$/.test(next)) {
1092
+ return { tokenSet: regexTokenSet([next]), endIndex: index + 1 };
1093
+ }
1094
+ return void 0;
1095
+ }
1096
+ function characterClassAtomChars(value, index) {
1097
+ const negated = value[index + 1] === "^";
1098
+ const chars = /* @__PURE__ */ new Set();
1099
+ let previousLiteral;
1100
+ for (let i = index + (negated ? 2 : 1); i < value.length; i++) {
1101
+ if (value[i] === "]") {
1102
+ return { tokenSet: regexTokenSet(chars, negated), endIndex: i };
1103
+ }
1104
+ if (value[i] === "-" && previousLiteral && value[i + 1] !== "]") {
1105
+ const rangeEnd = value[i + 1] === "\\" ? escapedAtomChars(value, i + 1, true) : { tokenSet: regexTokenSet([value[i + 1]]), endIndex: i + 1 };
1106
+ if (!rangeEnd || rangeEnd.tokenSet.negated || rangeEnd.tokenSet.chars.size !== 1) {
1107
+ return void 0;
1108
+ }
1109
+ const [endLiteral] = rangeEnd.tokenSet.chars;
1110
+ const range = asciiRange(previousLiteral, endLiteral);
1111
+ if (!range) return void 0;
1112
+ chars.delete(previousLiteral);
1113
+ for (const char of range) chars.add(char);
1114
+ previousLiteral = endLiteral;
1115
+ i = rangeEnd.endIndex;
1116
+ continue;
1117
+ }
1118
+ const atom = value[i] === "\\" ? escapedAtomChars(value, i, true) : { tokenSet: regexTokenSet([value[i]]), endIndex: i };
1119
+ if (!atom || atom.tokenSet.negated) return void 0;
1120
+ const [literal] = atom.tokenSet.chars;
1121
+ for (const char of atom.tokenSet.chars) chars.add(char);
1122
+ previousLiteral = atom.tokenSet.chars.size === 1 ? literal : void 0;
1123
+ i = atom.endIndex;
1124
+ }
1125
+ return void 0;
1126
+ }
1127
+ function exactRepeatAt(value, index) {
1128
+ if (value[index] !== "{") return { count: 1, endIndex: index - 1 };
1129
+ const quantifier = regexQuantifierAt(value, index);
1130
+ if (quantifier.kind !== "bounded" || quantifier.variableLength || quantifier.max === void 0 || !Number.isSafeInteger(quantifier.max) || quantifier.max > MAX_FIXED_WIDTH_ALTERNATIVE_TOKENS) {
1131
+ return void 0;
1132
+ }
1133
+ return { count: quantifier.max, endIndex: quantifier.endIndex };
1134
+ }
1135
+ function wholeWrapperGroupContent(value) {
1136
+ if (value[0] !== "(") return void 0;
1137
+ let startIndex = 1;
1138
+ if (value[1] === "?") {
1139
+ if (value[2] === ":") {
1140
+ startIndex = 3;
1141
+ } else if (value[2] === "<") {
1142
+ const nameEndIndex = value.indexOf(">", 3);
1143
+ if (value[3] === "=" || value[3] === "!" || nameEndIndex === -1) {
1144
+ return void 0;
1145
+ }
1146
+ startIndex = nameEndIndex + 1;
1147
+ } else {
1148
+ return void 0;
1149
+ }
1150
+ }
1151
+ let depth = 0;
1152
+ for (let i = 0; i < value.length; i++) {
1153
+ const char = value[i];
1154
+ if (char === "\\") {
1155
+ i++;
1156
+ continue;
1157
+ }
1158
+ if (char === "[") {
1159
+ i = skipCharacterClass(value, i);
1160
+ continue;
1161
+ }
1162
+ if (char === "(") {
1163
+ depth++;
1164
+ continue;
1165
+ }
1166
+ if (char === ")" && depth > 0) {
1167
+ depth--;
1168
+ if (depth === 0) {
1169
+ return i === value.length - 1 ? { startIndex, endIndex: i } : void 0;
1170
+ }
1171
+ }
1172
+ }
1173
+ return void 0;
1174
+ }
1175
+ function fixedWidthAlternativeTokens(value) {
1176
+ const wrapper = wholeWrapperGroupContent(value);
1177
+ if (wrapper) {
1178
+ return fixedWidthAlternativeTokens(
1179
+ value.slice(wrapper.startIndex, wrapper.endIndex)
1180
+ );
1181
+ }
1182
+ const parts = [];
1183
+ let length = 0;
1184
+ for (let i = 0; i < value.length; i++) {
1185
+ const char = value[i];
1186
+ if (char === "^" || char === "$") continue;
1187
+ if ("*+?|(){}".includes(char)) return void 0;
1188
+ const atom = char === "\\" ? escapedAtomChars(value, i) : char === "[" ? characterClassAtomChars(value, i) : char === "." ? {
1189
+ tokenSet: regexTokenSet(JS_DOT_EXCLUDED_CHARS, true),
1190
+ endIndex: i
1191
+ } : { tokenSet: regexTokenSet([char]), endIndex: i };
1192
+ if (!atom || atom.tokenSet.chars.size === 0) return void 0;
1193
+ const repeat = exactRepeatAt(value, atom.endIndex + 1);
1194
+ if (!repeat || repeat.count === 0) return void 0;
1195
+ parts.push({ tokenSet: atom.tokenSet, count: repeat.count });
1196
+ length += repeat.count;
1197
+ i = repeat.endIndex;
1198
+ }
1199
+ return length > 0 ? { parts, length } : void 0;
1200
+ }
1201
+ function hasExcessiveExactRepeat(value) {
1202
+ for (let i = 0; i < value.length; i++) {
1203
+ const char = value[i];
1204
+ if (char === "\\") {
1205
+ i++;
1206
+ continue;
1207
+ }
1208
+ if (char === "[") {
1209
+ i = skipCharacterClass(value, i);
1210
+ continue;
1211
+ }
1212
+ if (char !== "{") continue;
1213
+ const quantifier = regexQuantifierAt(value, i);
1214
+ if (quantifier.kind === "bounded" && !quantifier.variableLength && quantifier.max !== void 0 && quantifier.max > MAX_FIXED_WIDTH_ALTERNATIVE_TOKENS) {
1215
+ return true;
1216
+ }
1217
+ }
1218
+ return false;
1219
+ }
1220
+ function regexAtomTokenAt(value, index) {
1221
+ const char = value[index];
1222
+ if (char === "\\") return escapedAtomChars(value, index);
1223
+ if (char === "[") return characterClassAtomChars(value, index);
1224
+ if (".*+?|(){}".includes(char)) return void 0;
1225
+ return { tokenSet: regexTokenSet([char]), endIndex: index };
1226
+ }
1227
+ function tokenSetsOverlap(left, right) {
1228
+ if (!left.negated && !right.negated) {
1229
+ for (const char of left.chars) {
1230
+ if (right.chars.has(char)) return true;
1231
+ }
1232
+ return false;
1233
+ }
1234
+ if (left.negated && right.negated) return true;
1235
+ const positive = left.negated ? right : left;
1236
+ const negated = left.negated ? left : right;
1237
+ for (const char of positive.chars) {
1238
+ if (!negated.chars.has(char)) return true;
1239
+ }
1240
+ return false;
1241
+ }
1242
+ function quantifierCanRepeat(quantifier) {
1243
+ return quantifier.kind === "unbounded" || quantifier.variableLength;
1244
+ }
1245
+ function repeatableBoundaryFor(tokenSet, quantifier) {
1246
+ return {
1247
+ tokenSet,
1248
+ max: quantifier.kind === "bounded" ? quantifier.max : void 0
1249
+ };
1250
+ }
1251
+ function overlappingRepeatablesCanBacktrack(left, right) {
1252
+ if (!tokenSetsOverlap(left.tokenSet, right.tokenSet)) return false;
1253
+ if (left.max === void 0 || right.max === void 0) return true;
1254
+ return left.max * right.max > MAX_SAFE_OVERLAPPING_GROUP_REPEATS;
1255
+ }
1256
+ function recordRegexToken(group, tokenSet, quantifier) {
1257
+ if (!group.firstTokenSet) {
1258
+ group.firstTokenSet = tokenSet;
1259
+ } else {
1260
+ group.hasMultipleTokenAtoms = true;
1261
+ }
1262
+ if (quantifierCanRepeat(quantifier)) {
1263
+ const boundary = repeatableBoundaryFor(tokenSet, quantifier);
1264
+ if (group.lastRepeatableTokenSets.some(
1265
+ (lastBoundary) => overlappingRepeatablesCanBacktrack(lastBoundary, boundary)
1266
+ )) {
1267
+ return EXCESSIVE_BACKTRACKING_REGEX_REASON;
1268
+ }
1269
+ group.lastRepeatableTokenSets = quantifier.minZero ? [...group.lastRepeatableTokenSets, boundary] : [boundary];
1270
+ } else if (!quantifier.minZero) {
1271
+ group.lastRepeatableTokenSets = [];
1272
+ }
1273
+ if (!quantifier.minZero) {
1274
+ group.branchCanMatchEmpty = false;
1275
+ }
1276
+ return void 0;
1277
+ }
1278
+ function recordRegexRepeatableBoundary(group, boundary) {
1279
+ if (group.lastRepeatableTokenSets.some(
1280
+ (lastBoundary) => overlappingRepeatablesCanBacktrack(lastBoundary, boundary)
1281
+ )) {
1282
+ return EXCESSIVE_BACKTRACKING_REGEX_REASON;
1283
+ }
1284
+ group.lastRepeatableTokenSets = [boundary];
1285
+ return void 0;
1286
+ }
1287
+ function tokenSequenceCanPrefixOverlap(left, right) {
1288
+ let leftPartIndex = 0;
1289
+ let rightPartIndex = 0;
1290
+ let leftPartOffset = 0;
1291
+ let rightPartOffset = 0;
1292
+ let comparedLength = 0;
1293
+ const length = Math.min(left.length, right.length);
1294
+ while (comparedLength < length) {
1295
+ const leftPart = left.parts[leftPartIndex];
1296
+ const rightPart = right.parts[rightPartIndex];
1297
+ if (!leftPart || !rightPart) return false;
1298
+ if (!tokenSetsOverlap(leftPart.tokenSet, rightPart.tokenSet)) return false;
1299
+ const step = Math.min(
1300
+ leftPart.count - leftPartOffset,
1301
+ rightPart.count - rightPartOffset,
1302
+ length - comparedLength
1303
+ );
1304
+ comparedLength += step;
1305
+ leftPartOffset += step;
1306
+ rightPartOffset += step;
1307
+ if (leftPartOffset === leftPart.count) {
1308
+ leftPartIndex++;
1309
+ leftPartOffset = 0;
1310
+ }
1311
+ if (rightPartOffset === rightPart.count) {
1312
+ rightPartIndex++;
1313
+ rightPartOffset = 0;
1314
+ }
1315
+ }
1316
+ return true;
1317
+ }
1318
+ function firstFixedWidthTokenSet(tokens) {
1319
+ return tokens.parts[0]?.tokenSet;
1320
+ }
1321
+ function firstRequiredAlternativeTokenSet(value) {
1322
+ const atoms = [];
1323
+ if (!appendRegexSequenceAtoms(value, 0, value.length, atoms)) return void 0;
1324
+ const first = atoms[0];
1325
+ if (!first || first.quantifier.minZero) return void 0;
1326
+ return first.tokenSet;
1327
+ }
1328
+ function regexAlternativeSafety(value) {
1329
+ const tokens = fixedWidthAlternativeTokens(value);
1330
+ if (tokens) {
1331
+ const firstTokenSet2 = firstFixedWidthTokenSet(tokens);
1332
+ return firstTokenSet2 ? { firstTokenSet: firstTokenSet2, tokens } : void 0;
1333
+ }
1334
+ if (hasExcessiveExactRepeat(value)) return void 0;
1335
+ if (!hasRequiredNonOverlappingDelimiter(value, 0, value.length)) {
1336
+ return void 0;
1337
+ }
1338
+ const firstTokenSet = firstRequiredAlternativeTokenSet(value);
1339
+ return firstTokenSet ? { firstTokenSet, tokens: void 0 } : void 0;
1340
+ }
1341
+ function hasSafeDelimitedTopLevelAlternation(pattern, startIndex, endIndex) {
1342
+ const alternatives = splitTopLevelAlternatives(pattern, startIndex, endIndex);
1343
+ if (alternatives.length < 2) return false;
1344
+ if (alternatives.length > MAX_SAFE_TOP_LEVEL_ALTERNATIVES) return false;
1345
+ const firstTokenSets = [];
1346
+ for (const alternative of alternatives) {
1347
+ if (hasExcessiveExactRepeat(alternative)) return false;
1348
+ if (!hasRequiredNonOverlappingDelimiter(alternative, 0, alternative.length)) {
1349
+ return false;
1350
+ }
1351
+ const firstTokenSet = firstRequiredAlternativeTokenSet(alternative);
1352
+ if (!firstTokenSet) return false;
1353
+ if (firstTokenSets.some((seen) => tokenSetsOverlap(seen, firstTokenSet))) {
1354
+ return false;
1355
+ }
1356
+ firstTokenSets.push(firstTokenSet);
1357
+ }
1358
+ return true;
1359
+ }
1360
+ function regexGroupContentAt(pattern, index) {
1361
+ if (pattern[index] !== "(") return void 0;
1362
+ let contentStart = index + 1;
1363
+ if (pattern[index + 1] === "?") {
1364
+ const next = pattern[index + 2];
1365
+ if (next === ":") {
1366
+ contentStart = index + 3;
1367
+ } else if (next === "<") {
1368
+ const nameEndIndex = pattern.indexOf(">", index + 3);
1369
+ if (pattern[index + 3] === "=" || pattern[index + 3] === "!" || nameEndIndex === -1) {
1370
+ return void 0;
1371
+ }
1372
+ contentStart = nameEndIndex + 1;
1373
+ } else {
1374
+ return void 0;
1375
+ }
1376
+ }
1377
+ let depth = 0;
1378
+ for (let i = index; i < pattern.length; i++) {
1379
+ const char = pattern[i];
1380
+ if (char === "\\") {
1381
+ i++;
1382
+ continue;
1383
+ }
1384
+ if (char === "[") {
1385
+ i = skipCharacterClass(pattern, i);
1386
+ continue;
1387
+ }
1388
+ if (char === "(") {
1389
+ depth++;
1390
+ continue;
1391
+ }
1392
+ if (char === ")" && depth > 0) {
1393
+ depth--;
1394
+ if (depth === 0) return { contentStart, endIndex: i };
1395
+ }
1396
+ }
1397
+ return void 0;
1398
+ }
1399
+ function appendRegexSequenceAtoms(pattern, startIndex, endIndex, atoms) {
1400
+ for (let i = startIndex; i < endIndex; i++) {
1401
+ const char = pattern[i];
1402
+ if (char === "^" || char === "$") continue;
1403
+ if (char === "(") {
1404
+ const group = regexGroupContentAt(pattern, i);
1405
+ if (!group) return false;
1406
+ const quantifier2 = regexQuantifierAt(pattern, group.endIndex + 1);
1407
+ if (quantifier2.kind !== "none") return false;
1408
+ const alternatives = splitTopLevelAlternatives(
1409
+ pattern,
1410
+ group.contentStart,
1411
+ group.endIndex
1412
+ );
1413
+ if (alternatives.length > 1) {
1414
+ const fixedAlternatives = alternatives.map(fixedWidthAlternativeTokens);
1415
+ if (fixedAlternatives.some((tokens) => tokens === void 0)) {
1416
+ return false;
1417
+ }
1418
+ i = group.endIndex;
1419
+ continue;
1420
+ }
1421
+ if (!appendRegexSequenceAtoms(
1422
+ pattern,
1423
+ group.contentStart,
1424
+ group.endIndex,
1425
+ atoms
1426
+ )) {
1427
+ return false;
1428
+ }
1429
+ i = group.endIndex;
1430
+ continue;
1431
+ }
1432
+ const atom = regexAtomTokenAt(pattern, i);
1433
+ if (!atom) return false;
1434
+ const quantifier = regexQuantifierAt(pattern, atom.endIndex + 1);
1435
+ atoms.push({ tokenSet: atom.tokenSet, quantifier });
1436
+ i = quantifier.kind === "none" ? atom.endIndex : quantifier.endIndex;
1437
+ }
1438
+ return true;
1439
+ }
1440
+ function hasRequiredNonOverlappingDelimiter(pattern, startIndex, endIndex) {
1441
+ const atoms = [];
1442
+ if (!appendRegexSequenceAtoms(pattern, startIndex, endIndex, atoms)) {
1443
+ return false;
1444
+ }
1445
+ const hasRequiredDelimiterAt = (index) => {
1446
+ const delimiter = atoms[index];
1447
+ if (!delimiter || delimiter.quantifier.minZero || delimiter.quantifier.kind === "unbounded" || delimiter.quantifier.variableLength) {
1448
+ return false;
1449
+ }
1450
+ for (let i = 0; i < atoms.length; i++) {
1451
+ if (i === index) continue;
1452
+ const atom = atoms[i];
1453
+ if ((atom.quantifier.kind === "unbounded" || atom.quantifier.variableLength) && tokenSetsOverlap(delimiter.tokenSet, atom.tokenSet)) {
1454
+ return false;
1455
+ }
1456
+ }
1457
+ return true;
1458
+ };
1459
+ const hasRequiredDisjointTokenFlow = atoms.length > 1 && atoms.every((atom) => !atom.quantifier.minZero) && !tokenSetsOverlap(atoms[0].tokenSet, atoms[atoms.length - 1].tokenSet) && atoms.slice(1).every(
1460
+ (atom, index) => !tokenSetsOverlap(atoms[index].tokenSet, atom.tokenSet)
1461
+ );
1462
+ return atoms.length > 0 && (hasRequiredDelimiterAt(0) || hasRequiredDelimiterAt(atoms.length - 1) || hasRequiredDisjointTokenFlow);
1463
+ }
1464
+ function hasUnsafeTopLevelAlternation(pattern, startIndex, endIndex) {
1465
+ const alternatives = splitTopLevelAlternatives(pattern, startIndex, endIndex);
1466
+ if (alternatives.length < 2) return false;
1467
+ if (alternatives.length > MAX_SAFE_TOP_LEVEL_ALTERNATIVES) return true;
1468
+ const alternativesSafety = alternatives.map(regexAlternativeSafety);
1469
+ if (alternativesSafety.some((safety) => safety === void 0)) return true;
1470
+ for (let i = 0; i < alternativesSafety.length; i++) {
1471
+ const left = alternativesSafety[i];
1472
+ if (left === void 0) return true;
1473
+ for (let j = i + 1; j < alternativesSafety.length; j++) {
1474
+ const right = alternativesSafety[j];
1475
+ if (right === void 0) return true;
1476
+ if (left.tokens && right.tokens) {
1477
+ if (tokenSequenceCanPrefixOverlap(left.tokens, right.tokens)) {
1478
+ return true;
1479
+ }
1480
+ continue;
1481
+ }
1482
+ if (tokenSetsOverlap(left.firstTokenSet, right.firstTokenSet)) return true;
1483
+ }
1484
+ }
1485
+ return false;
1486
+ }
1487
+ function createRegexScanGroup(contentStart) {
1488
+ return {
1489
+ contentStart,
1490
+ branchCanMatchEmpty: true,
1491
+ hasAlternation: false,
1492
+ hasNullableBranch: false,
1493
+ hasUnboundedQuantifier: false,
1494
+ hasUnsafeAlternation: false,
1495
+ hasVariableLengthQuantifier: false,
1496
+ firstTokenSet: void 0,
1497
+ hasMultipleTokenAtoms: false,
1498
+ lastRepeatableTokenSets: []
1499
+ };
1500
+ }
1501
+ function unsafeRegexPatternReason(pattern) {
1502
+ const groups = [createRegexScanGroup(0)];
1503
+ for (let i = 0; i < pattern.length; i++) {
1504
+ const char = pattern[i];
1505
+ const current = groups[groups.length - 1];
1506
+ if (char === "\\") {
1507
+ const next = pattern[i + 1];
1508
+ if (next !== void 0 && next >= "1" && next <= "9") {
1509
+ return UNSUPPORTED_REGEX_SYNTAX_REASON;
1510
+ }
1511
+ if (next === "k" && pattern[i + 2] === "<") {
1512
+ return UNSUPPORTED_REGEX_SYNTAX_REASON;
1513
+ }
1514
+ const atom = escapedAtomChars(pattern, i);
1515
+ if (!atom && next !== "b" && next !== "B") {
1516
+ return UNSUPPORTED_REGEX_SYNTAX_REASON;
1517
+ }
1518
+ const atomEndIndex = atom?.endIndex ?? i + 1;
1519
+ const quantifier2 = regexQuantifierAt(pattern, atomEndIndex + 1);
1520
+ if (quantifier2.kind === "unbounded") {
1521
+ current.hasUnboundedQuantifier = true;
1522
+ }
1523
+ if (quantifier2.variableLength) {
1524
+ current.hasVariableLengthQuantifier = true;
1525
+ }
1526
+ if (atom) {
1527
+ const unsafeReason = recordRegexToken(
1528
+ current,
1529
+ atom.tokenSet,
1530
+ quantifier2
1531
+ );
1532
+ if (unsafeReason) return unsafeReason;
1533
+ }
1534
+ i = quantifier2.kind === "none" ? atomEndIndex : quantifier2.endIndex;
1535
+ continue;
1536
+ }
1537
+ if (char === "[") {
1538
+ const classEndIndex = skipCharacterClass(pattern, i);
1539
+ if (pattern[classEndIndex] !== "]") return void 0;
1540
+ const atom = characterClassAtomChars(pattern, i);
1541
+ if (!atom) return UNSUPPORTED_REGEX_SYNTAX_REASON;
1542
+ const quantifier2 = regexQuantifierAt(pattern, classEndIndex + 1);
1543
+ if (quantifier2.kind === "unbounded") {
1544
+ current.hasUnboundedQuantifier = true;
1545
+ }
1546
+ if (quantifier2.variableLength) {
1547
+ current.hasVariableLengthQuantifier = true;
1548
+ }
1549
+ const unsafeReason = recordRegexToken(current, atom.tokenSet, quantifier2);
1550
+ if (unsafeReason) return unsafeReason;
1551
+ i = quantifier2.kind === "none" ? classEndIndex : quantifier2.endIndex;
1552
+ continue;
1553
+ }
1554
+ if (char === "|") {
1555
+ current.hasNullableBranch ||= current.branchCanMatchEmpty;
1556
+ current.hasAlternation = true;
1557
+ current.branchCanMatchEmpty = true;
1558
+ current.hasMultipleTokenAtoms = true;
1559
+ current.lastRepeatableTokenSets = [];
1560
+ continue;
1561
+ }
1562
+ if (char === "(") {
1563
+ let contentStart = i + 1;
1564
+ if (pattern[i + 1] === "?") {
1565
+ const next = pattern[i + 2];
1566
+ if (next === ":") {
1567
+ i += 2;
1568
+ } else if (next === "<") {
1569
+ const nameEndIndex = pattern.indexOf(">", i + 3);
1570
+ if (pattern[i + 3] === "=" || pattern[i + 3] === "!" || nameEndIndex === -1) {
1571
+ return UNSUPPORTED_REGEX_SYNTAX_REASON;
1572
+ }
1573
+ i = nameEndIndex;
1574
+ } else {
1575
+ return UNSUPPORTED_REGEX_SYNTAX_REASON;
1576
+ }
1577
+ contentStart = i + 1;
1578
+ }
1579
+ if (groups.length >= MAX_REGEX_SCAN_GROUP_DEPTH) {
1580
+ return EXCESSIVE_BACKTRACKING_REGEX_REASON;
1581
+ }
1582
+ groups.push(createRegexScanGroup(contentStart));
1583
+ continue;
1584
+ }
1585
+ if (char === ")" && groups.length > 1) {
1586
+ const group = groups.pop();
1587
+ if (group) {
1588
+ const groupHasUnsafeAlternation = group.hasUnsafeAlternation || group.hasAlternation && hasUnsafeTopLevelAlternation(pattern, group.contentStart, i);
1589
+ const groupCanMatchEmpty = group.hasNullableBranch || group.branchCanMatchEmpty;
1590
+ const quantifier2 = regexQuantifierAt(pattern, i + 1);
1591
+ const hasSafeDelimiter = hasRequiredNonOverlappingDelimiter(pattern, group.contentStart, i) || hasSafeDelimitedTopLevelAlternation(pattern, group.contentStart, i);
1592
+ const dangerousRepeatedGroup = group.hasUnboundedQuantifier && !hasSafeDelimiter || groupHasUnsafeAlternation && !hasSafeDelimiter || group.hasVariableLengthQuantifier && !hasSafeDelimiter || groupCanMatchEmpty;
1593
+ const boundedRepeatMax = quantifier2.kind === "bounded" ? quantifier2.max ?? 0 : 0;
1594
+ const quantifierCanRepeatGroup = quantifier2.kind === "unbounded" || quantifier2.variableLength || quantifier2.kind === "bounded" && boundedRepeatMax > 1 && (groupHasUnsafeAlternation && !hasSafeDelimiter && boundedRepeatMax >= MAX_SAFE_OVERLAPPING_GROUP_REPEATS || group.hasUnboundedQuantifier || group.hasVariableLengthQuantifier || groupCanMatchEmpty);
1595
+ if (dangerousRepeatedGroup && quantifierCanRepeatGroup) {
1596
+ return EXCESSIVE_BACKTRACKING_REGEX_REASON;
1597
+ }
1598
+ const parent = groups[groups.length - 1];
1599
+ const singleTokenSet = group.hasMultipleTokenAtoms ? void 0 : group.firstTokenSet;
1600
+ if (singleTokenSet) {
1601
+ const groupQuantifier = group.hasUnboundedQuantifier || group.hasVariableLengthQuantifier ? {
1602
+ kind: "unbounded",
1603
+ minZero: quantifier2.minZero || groupCanMatchEmpty,
1604
+ variableLength: true,
1605
+ max: void 0
1606
+ } : quantifier2;
1607
+ const unsafeReason = recordRegexToken(
1608
+ parent,
1609
+ singleTokenSet,
1610
+ groupQuantifier
1611
+ );
1612
+ if (unsafeReason) return unsafeReason;
1613
+ } else {
1614
+ parent.hasMultipleTokenAtoms = true;
1615
+ if (group.lastRepeatableTokenSets.length > 0) {
1616
+ if (hasSafeDelimiter) {
1617
+ parent.lastRepeatableTokenSets = group.lastRepeatableTokenSets;
1618
+ } else {
1619
+ for (const boundary of group.lastRepeatableTokenSets) {
1620
+ const unsafeReason = recordRegexRepeatableBoundary(
1621
+ parent,
1622
+ boundary
1623
+ );
1624
+ if (unsafeReason) return unsafeReason;
1625
+ }
1626
+ }
1627
+ } else if (!groupCanMatchEmpty && !quantifier2.minZero) {
1628
+ parent.lastRepeatableTokenSets = [];
1629
+ }
1630
+ if (!groupCanMatchEmpty && !quantifier2.minZero) {
1631
+ parent.branchCanMatchEmpty = false;
1632
+ }
1633
+ }
1634
+ parent.hasUnboundedQuantifier ||= group.hasUnboundedQuantifier;
1635
+ parent.hasUnsafeAlternation ||= groupHasUnsafeAlternation;
1636
+ parent.hasVariableLengthQuantifier ||= group.hasVariableLengthQuantifier || quantifier2.variableLength;
1637
+ if (quantifier2.kind === "unbounded") {
1638
+ parent.hasUnboundedQuantifier = true;
1639
+ }
1640
+ if (quantifier2.kind !== "none") {
1641
+ i = quantifier2.endIndex;
1642
+ }
1643
+ }
1644
+ continue;
1645
+ }
1646
+ const quantifier = regexQuantifierAt(pattern, i);
1647
+ if (quantifier.kind !== "none") {
1648
+ if (quantifier.kind === "unbounded") {
1649
+ current.hasUnboundedQuantifier = true;
1650
+ }
1651
+ if (quantifier.variableLength) {
1652
+ current.hasVariableLengthQuantifier = true;
1653
+ }
1654
+ i = quantifier.endIndex;
1655
+ continue;
1656
+ }
1657
+ if (!"^$".includes(char)) {
1658
+ const atom = char === "." ? { tokenSet: regexTokenSet(JS_DOT_EXCLUDED_CHARS, true) } : { tokenSet: regexTokenSet([char]) };
1659
+ const nextQuantifier = regexQuantifierAt(pattern, i + 1);
1660
+ if (nextQuantifier.kind === "unbounded") {
1661
+ current.hasUnboundedQuantifier = true;
1662
+ }
1663
+ if (nextQuantifier.variableLength) {
1664
+ current.hasVariableLengthQuantifier = true;
1665
+ }
1666
+ const unsafeReason = recordRegexToken(
1667
+ current,
1668
+ atom.tokenSet,
1669
+ nextQuantifier
1670
+ );
1671
+ if (unsafeReason) return unsafeReason;
1672
+ if (nextQuantifier.kind !== "none") {
1673
+ i = nextQuantifier.endIndex;
1674
+ }
1675
+ }
1676
+ }
1677
+ return void 0;
1678
+ }
763
1679
  function normalizeOptionalTypeSpec(spec) {
764
1680
  if (typeof spec === "string" && spec.endsWith("?")) {
765
1681
  return { spec: spec.slice(0, -1), optionalByType: true };
@@ -772,7 +1688,15 @@ function normalizeOptionalTypeSpec(spec) {
772
1688
  }
773
1689
  return { spec, optionalByType: false };
774
1690
  }
775
- function validateTypedFieldObject(path, spec, errors) {
1691
+ function validationContext(options = {}) {
1692
+ return {
1693
+ allowUnsafeFieldNamePaths: new Set(options.allowUnsafeFieldNamePaths ?? [])
1694
+ };
1695
+ }
1696
+ function isAllowedLegacyUnsafeFieldPath(context, path) {
1697
+ return context.allowUnsafeFieldNamePaths.has(path);
1698
+ }
1699
+ function validateTypedFieldObject(path, identityPath, spec, errors, context) {
776
1700
  const typeValue = spec.type;
777
1701
  if (typeof typeValue !== "string") {
778
1702
  errors.push(
@@ -782,7 +1706,7 @@ function validateTypedFieldObject(path, spec, errors) {
782
1706
  }
783
1707
  if (!VALID_PRIMITIVE_TYPES.has(typeValue)) {
784
1708
  errors.push(
785
- `Invalid type at "${path}.type": "${typeValue}" (expected number|string|boolean|wref|array, optionally with ? suffix)`
1709
+ `Invalid type at "${path}.type": "${escapeFieldNameForDisplay(typeValue)}" (expected number|string|boolean|wref|array, optionally with ? suffix)`
786
1710
  );
787
1711
  return;
788
1712
  }
@@ -794,33 +1718,34 @@ function validateTypedFieldObject(path, spec, errors) {
794
1718
  const baseType = typeValue.endsWith("?") ? typeValue.slice(0, -1) : typeValue;
795
1719
  for (const key of Object.keys(spec)) {
796
1720
  if (key === "type" || key === "description") continue;
1721
+ const safeKey = escapeFieldNameForDisplay(key);
797
1722
  if (baseType === "string") {
798
1723
  if (!STRING_CONSTRAINT_KEYS.has(key)) {
799
1724
  errors.push(
800
- `Constraint "${key}" at "${path}" is not valid for type "string"`
1725
+ `Constraint "${safeKey}" at "${path}" is not valid for type "string"`
801
1726
  );
802
1727
  }
803
1728
  } else if (baseType === "number") {
804
1729
  if (!NUMBER_CONSTRAINT_KEYS.has(key)) {
805
1730
  errors.push(
806
- `Constraint "${key}" at "${path}" is not valid for type "number"`
1731
+ `Constraint "${safeKey}" at "${path}" is not valid for type "number"`
807
1732
  );
808
1733
  }
809
1734
  } else if (baseType === "wref") {
810
1735
  if (!WREF_CONSTRAINT_KEYS.has(key)) {
811
1736
  errors.push(
812
- `Constraint "${key}" at "${path}" is not valid for type "wref"`
1737
+ `Constraint "${safeKey}" at "${path}" is not valid for type "wref"`
813
1738
  );
814
1739
  }
815
1740
  } else if (baseType === "array") {
816
1741
  if (!ARRAY_CONSTRAINT_KEYS.has(key)) {
817
1742
  errors.push(
818
- `Constraint "${key}" at "${path}" is not valid for type "array"`
1743
+ `Constraint "${safeKey}" at "${path}" is not valid for type "array"`
819
1744
  );
820
1745
  }
821
1746
  } else {
822
1747
  errors.push(
823
- `Constraint "${key}" at "${path}" is not valid for type "${baseType}"`
1748
+ `Constraint "${safeKey}" at "${path}" is not valid for type "${baseType}"`
824
1749
  );
825
1750
  }
826
1751
  }
@@ -856,6 +1781,10 @@ function validateTypedFieldObject(path, spec, errors) {
856
1781
  } catch {
857
1782
  errors.push(`"pattern" at "${path}" is not a valid regular expression`);
858
1783
  }
1784
+ const unsafeReason = unsafeRegexPatternReason(spec.pattern);
1785
+ if (unsafeReason) {
1786
+ errors.push(`"pattern" at "${path}" ${unsafeReason}`);
1787
+ }
859
1788
  }
860
1789
  if ("enum" in spec) {
861
1790
  if (!Array.isArray(spec.enum) || spec.enum.length === 0 || !spec.enum.every((v) => typeof v === "string")) {
@@ -906,15 +1835,21 @@ function validateTypedFieldObject(path, spec, errors) {
906
1835
  `Typed array at "${path}" must have an "items" key specifying the element type`
907
1836
  );
908
1837
  } else {
909
- validateTypeSpec(`${path}.items`, spec.items, errors);
1838
+ validateTypeSpec(
1839
+ `${path}.items`,
1840
+ `${identityPath}.items`,
1841
+ spec.items,
1842
+ errors,
1843
+ context
1844
+ );
910
1845
  }
911
1846
  }
912
1847
  }
913
- function validateTypeSpec(path, spec, errors) {
1848
+ function validateTypeSpec(path, identityPath, spec, errors, context) {
914
1849
  if (typeof spec === "string") {
915
1850
  if (!VALID_PRIMITIVE_TYPES.has(spec)) {
916
1851
  errors.push(
917
- `Invalid type at "${path}": "${spec}" (expected number|string|boolean|wref|array, optionally with ? suffix)`
1852
+ `Invalid type at "${path}": "${escapeFieldNameForDisplay(spec)}" (expected number|string|boolean|wref|array, optionally with ? suffix)`
918
1853
  );
919
1854
  }
920
1855
  return;
@@ -926,12 +1861,12 @@ function validateTypeSpec(path, spec, errors) {
926
1861
  );
927
1862
  return;
928
1863
  }
929
- validateTypeSpec(`${path}[]`, spec[0], errors);
1864
+ validateTypeSpec(`${path}[]`, `${identityPath}[]`, spec[0], errors, context);
930
1865
  return;
931
1866
  }
932
1867
  if (isPlainObject(spec)) {
933
1868
  if (isTypeSpecObject(spec)) {
934
- validateTypedFieldObject(path, spec, errors);
1869
+ validateTypedFieldObject(path, identityPath, spec, errors, context);
935
1870
  return;
936
1871
  }
937
1872
  if ("type" in spec && typeof spec.type === "string" && VALID_PRIMITIVE_TYPES.has(spec.type) && Object.keys(spec).every((k) => VALID_TYPESPEC_KEYS.has(k))) {
@@ -949,7 +1884,7 @@ function validateTypeSpec(path, spec, errors) {
949
1884
  if (hasNonTypeSpecValues) {
950
1885
  for (const key of crossTypeKeys) {
951
1886
  errors.push(
952
- `Constraint "${key}" at "${path}" is not valid for type "${baseType}"`
1887
+ `Constraint "${escapeFieldNameForDisplay(key)}" at "${path}" is not valid for type "${baseType}"`
953
1888
  );
954
1889
  }
955
1890
  return;
@@ -995,13 +1930,21 @@ function validateTypeSpec(path, spec, errors) {
995
1930
  });
996
1931
  if (hasWrongValueTypes) {
997
1932
  errors.push(
998
- `Object at "${path}" looks like a typed field object (type: "${spec.type}") but has constraint values with invalid types. Check that constraint values match their expected types (e.g., minLength must be a number, not a string).`
1933
+ `Object at "${path}" looks like a typed field object (type: "${escapeFieldNameForDisplay(String(spec.type))}") but has constraint values with invalid types. Check that constraint values match their expected types (e.g., minLength must be a number, not a string).`
999
1934
  );
1000
1935
  return;
1001
1936
  }
1002
1937
  }
1003
1938
  for (const [key, value] of Object.entries(spec)) {
1004
- validateTypeSpec(`${path}.${key}`, value, errors);
1939
+ const bareKey = stripOptionalMarker(key);
1940
+ const verdict = isUnsafeFieldName(bareKey);
1941
+ const fieldPath = joinFieldPath(path, key);
1942
+ const fieldIdentityPath = joinFieldIdentityPath(identityPath, key);
1943
+ if (verdict.unsafe && !isAllowedLegacyUnsafeFieldPath(context, fieldIdentityPath)) {
1944
+ errors.push(`Invalid field name at "${fieldPath}": ${verdict.reason}`);
1945
+ continue;
1946
+ }
1947
+ validateTypeSpec(fieldPath, fieldIdentityPath, value, errors, context);
1005
1948
  }
1006
1949
  return;
1007
1950
  }
@@ -1035,14 +1978,22 @@ function isNestedFieldsRecord(value) {
1035
1978
  }
1036
1979
  return true;
1037
1980
  }
1038
- function validateFieldsRecord(fields, prefix, errors) {
1981
+ function validateFieldsRecord(fields, prefix, errors, context, identityPrefix = prefix) {
1039
1982
  const seenDisplayByFolded = /* @__PURE__ */ new Map();
1040
1983
  for (const [key, value] of Object.entries(fields)) {
1984
+ const bareKey = stripOptionalMarker(key);
1985
+ const verdict = isUnsafeFieldName(bareKey);
1986
+ const fieldPath = joinFieldPath(prefix, key);
1987
+ const fieldIdentityPath = joinFieldIdentityPath(identityPrefix, key);
1988
+ if (verdict.unsafe && !isAllowedLegacyUnsafeFieldPath(context, fieldIdentityPath)) {
1989
+ errors.push(`Invalid field name at "${fieldPath}": ${verdict.reason}`);
1990
+ continue;
1991
+ }
1041
1992
  const folded = foldFieldName(key);
1042
1993
  const prior = seenDisplayByFolded.get(folded);
1043
1994
  if (prior !== void 0) {
1044
1995
  errors.push(
1045
- `${prefix}.${key}: duplicate field name. Shape already declares "${prior}" (case-insensitive match).`
1996
+ `${joinFieldPath(prefix, key)}: duplicate field name. Shape already declares "${escapeFieldNameForDisplay(prior)}" (case-insensitive match).`
1046
1997
  );
1047
1998
  continue;
1048
1999
  }
@@ -1050,15 +2001,66 @@ function validateFieldsRecord(fields, prefix, errors) {
1050
2001
  if (isNestedFieldsRecord(value)) {
1051
2002
  validateFieldsRecord(
1052
2003
  value,
1053
- `${prefix}.${key}`,
1054
- errors
2004
+ fieldPath,
2005
+ errors,
2006
+ context,
2007
+ fieldIdentityPath
1055
2008
  );
1056
2009
  } else {
1057
- validateTypeSpec(`${prefix}.${key}`, value, errors);
2010
+ validateTypeSpec(fieldPath, fieldIdentityPath, value, errors, context);
1058
2011
  }
1059
2012
  }
1060
2013
  }
1061
- function validateShapeDefinition(data) {
2014
+ function collectUnsafeFieldNamePathsInTypeSpec(path, spec, paths) {
2015
+ if (Array.isArray(spec)) {
2016
+ if (spec.length === 1)
2017
+ collectUnsafeFieldNamePathsInTypeSpec(`${path}[]`, spec[0], paths);
2018
+ return;
2019
+ }
2020
+ if (isTypeSpecObject(spec)) {
2021
+ const normalized = normalizeOptionalTypeSpec(spec).spec;
2022
+ if (isTypeSpecObject(normalized) && typeof normalized.type === "string" && normalized.type === "array" && "items" in normalized) {
2023
+ collectUnsafeFieldNamePathsInTypeSpec(
2024
+ `${path}.items`,
2025
+ normalized.items,
2026
+ paths
2027
+ );
2028
+ }
2029
+ return;
2030
+ }
2031
+ if (!isNestedFieldsRecord(spec)) return;
2032
+ collectUnsafeFieldNamePathsInFieldsRecord(spec, path, paths);
2033
+ }
2034
+ function collectUnsafeFieldNamePathsInFieldsRecord(fields, prefix, paths) {
2035
+ for (const [key, value] of Object.entries(fields)) {
2036
+ const fieldPath = joinFieldIdentityPath(prefix, key);
2037
+ if (isUnsafeFieldName(stripOptionalMarker(key)).unsafe) {
2038
+ paths.push(fieldPath);
2039
+ }
2040
+ if (isNestedFieldsRecord(value)) {
2041
+ collectUnsafeFieldNamePathsInFieldsRecord(value, fieldPath, paths);
2042
+ } else {
2043
+ collectUnsafeFieldNamePathsInTypeSpec(fieldPath, value, paths);
2044
+ }
2045
+ }
2046
+ }
2047
+ function collectUnsafeShapeFieldNamePaths(data) {
2048
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
2049
+ return [];
2050
+ }
2051
+ const fields = data.fields;
2052
+ if (typeof fields !== "object" || fields === null || Array.isArray(fields)) {
2053
+ return [];
2054
+ }
2055
+ const paths = [];
2056
+ collectUnsafeFieldNamePathsInFieldsRecord(
2057
+ fields,
2058
+ "fields",
2059
+ paths
2060
+ );
2061
+ return paths;
2062
+ }
2063
+ function validateShapeDefinition(data, options = {}) {
1062
2064
  if (typeof data !== "object" || data === null || Array.isArray(data)) {
1063
2065
  return { valid: false, errors: ["Shape definition must be a plain object"] };
1064
2066
  }
@@ -1066,7 +2068,9 @@ function validateShapeDefinition(data) {
1066
2068
  const errors = [];
1067
2069
  for (const key of Object.keys(obj)) {
1068
2070
  if (!ALLOWED_SHAPE_KEYS.has(key)) {
1069
- errors.push(`Unsupported shape definition key "${key}"`);
2071
+ errors.push(
2072
+ `Unsupported shape definition key "${escapeFieldNameForDisplay(key)}"`
2073
+ );
1070
2074
  }
1071
2075
  }
1072
2076
  if (!("fields" in obj)) {
@@ -1082,7 +2086,13 @@ function validateShapeDefinition(data) {
1082
2086
  errors: ['"fields" must be a plain object mapping field names to types']
1083
2087
  };
1084
2088
  }
1085
- validateFieldsRecord(fields, "fields", errors);
2089
+ const context = validationContext(options);
2090
+ validateFieldsRecord(
2091
+ fields,
2092
+ "fields",
2093
+ errors,
2094
+ context
2095
+ );
1086
2096
  const indexableFieldPaths = collectIndexableShapeFieldPaths(
1087
2097
  fields
1088
2098
  );
@@ -1099,7 +2109,7 @@ function validateShapeDefinition(data) {
1099
2109
  }
1100
2110
  for (const fieldPath of duplicateIndexableFieldPaths) {
1101
2111
  errors.push(
1102
- `Shape declares ambiguous indexable field path "${fieldPath}"; dotted field names, optional-key suffixes, and case-insensitive folded paths must not collide with another indexable path`
2112
+ `Shape declares ambiguous indexable field path "${escapeFieldNameForDisplay(fieldPath)}"; dotted field names, optional-key suffixes, and case-insensitive folded paths must not collide with another indexable path`
1103
2113
  );
1104
2114
  }
1105
2115
  if (indexableFieldCount > MAX_INDEXABLE_SCALAR_FIELDS_PER_SHAPE) {
@@ -1117,36 +2127,7 @@ function validateShapeDefinition(data) {
1117
2127
  return { valid: true };
1118
2128
  }
1119
2129
  function validateAgainstShape(data, shapeFields) {
1120
- const errors = [];
1121
- if (typeof data !== "object" || data === null || Array.isArray(data)) {
1122
- return { valid: false, errors: ["Data must be a plain object"] };
1123
- }
1124
- for (const [fieldSpec, rawTypeDef] of Object.entries(shapeFields)) {
1125
- const isOptionalByKey = fieldSpec.endsWith("?");
1126
- const fieldName = isOptionalByKey ? fieldSpec.slice(0, -1) : fieldSpec;
1127
- const normalized = normalizeOptionalTypeSpec(rawTypeDef);
1128
- const isOptional = isOptionalByKey || normalized.optionalByType;
1129
- const value = data[fieldName];
1130
- if (value === void 0 || value === null) {
1131
- if (!isOptional) {
1132
- errors.push(`Missing required field: "${fieldName}"`);
1133
- }
1134
- continue;
1135
- }
1136
- validateField(fieldName, value, normalized.spec, errors);
1137
- }
1138
- validatePersistedStringFieldLimits(data, shapeFields, errors);
1139
- const warnings = buildUndeclaredFieldsWarning(data, shapeFields);
1140
- if (errors.length > 0) {
1141
- return warnings ? { valid: false, errors, warnings } : { valid: false, errors };
1142
- }
1143
- return warnings ? { valid: true, warnings } : { valid: true };
1144
- }
1145
- function buildUndeclaredFieldsWarning(data, shapeFields) {
1146
- return buildUndeclaredFieldsWarningFromDeclaredSet(
1147
- data,
1148
- declaredFieldNames(shapeFields)
1149
- );
2130
+ return compileShapeValidator(shapeFields)(data);
1150
2131
  }
1151
2132
  function declaredFieldNames(shapeFields) {
1152
2133
  const declared = /* @__PURE__ */ new Set();
@@ -1176,6 +2157,32 @@ function buildUndeclaredFieldsWarningFromDeclaredSet(data, declared) {
1176
2157
  return { undeclaredFields };
1177
2158
  }
1178
2159
  var MAX_UNDECLARED_FIELDS_REPORTED = 500;
2160
+ function compileShapeValidator(shapeFields) {
2161
+ const compiledFields = compileObjectFields(shapeFields);
2162
+ const declared = declaredFieldNames(shapeFields);
2163
+ return (data) => {
2164
+ const errors = [];
2165
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
2166
+ return { valid: false, errors: ["Data must be a plain object"] };
2167
+ }
2168
+ for (const field of compiledFields) {
2169
+ const value = data[field.fieldName];
2170
+ if (value === void 0 || value === null) {
2171
+ if (!field.isOptional) {
2172
+ errors.push(`Missing required field: "${field.displayName}"`);
2173
+ }
2174
+ continue;
2175
+ }
2176
+ field.validate(field.displayName, value, errors);
2177
+ }
2178
+ validatePersistedStringFieldLimits(data, shapeFields, errors);
2179
+ const warnings = buildUndeclaredFieldsWarningFromDeclaredSet(data, declared);
2180
+ if (errors.length > 0) {
2181
+ return warnings ? { valid: false, errors, warnings } : { valid: false, errors };
2182
+ }
2183
+ return warnings ? { valid: true, warnings } : { valid: true };
2184
+ };
2185
+ }
1179
2186
  function validatePersistedStringFieldLimits(value, typeDef, errors, path) {
1180
2187
  const normalized = normalizeOptionalTypeSpec(typeDef).spec;
1181
2188
  if (typeof value === "string") {
@@ -1206,31 +2213,176 @@ function validatePersistedStringFieldLimits(value, typeDef, errors, path) {
1206
2213
  nestedValue,
1207
2214
  fields?.get(key),
1208
2215
  errors,
1209
- path ? `${path}.${key}` : key
2216
+ path ? joinFieldPath(path, key) : escapeFieldNameForDisplay(key)
1210
2217
  );
1211
2218
  }
1212
2219
  }
1213
- function isDeclaredStringType(typeDef) {
1214
- if (typeDef === "string") return true;
1215
- return isTypeSpecObject(typeDef) && typeDef.type === "string";
1216
- }
1217
- function isDeclaredWrefType(typeDef) {
1218
- if (typeDef === "wref") return true;
1219
- return isTypeSpecObject(typeDef) && typeDef.type === "wref";
1220
- }
1221
- function arrayElementType(typeDef) {
1222
- if (Array.isArray(typeDef)) return typeDef[0];
1223
- if (isTypeSpecObject(typeDef) && typeDef.type === "array")
1224
- return typeDef.items;
1225
- return void 0;
2220
+ function isDeclaredStringType(typeDef) {
2221
+ if (typeDef === "string") return true;
2222
+ return isTypeSpecObject(typeDef) && typeDef.type === "string";
2223
+ }
2224
+ function isDeclaredWrefType(typeDef) {
2225
+ if (typeDef === "wref") return true;
2226
+ return isTypeSpecObject(typeDef) && typeDef.type === "wref";
2227
+ }
2228
+ function arrayElementType(typeDef) {
2229
+ if (Array.isArray(typeDef)) return typeDef[0];
2230
+ if (isTypeSpecObject(typeDef) && typeDef.type === "array")
2231
+ return typeDef.items;
2232
+ return void 0;
2233
+ }
2234
+ function nestedObjectFields(typeDef) {
2235
+ if (!isPlainObject(typeDef) || isTypeSpecObject(typeDef)) return void 0;
2236
+ const fields = /* @__PURE__ */ new Map();
2237
+ for (const [key, value] of Object.entries(typeDef)) {
2238
+ fields.set(key.endsWith("?") ? key.slice(0, -1) : key, value);
2239
+ }
2240
+ return fields;
2241
+ }
2242
+ function compileObjectFields(shapeFields) {
2243
+ return Object.entries(shapeFields).map(([fieldSpec, rawTypeDef]) => {
2244
+ const isOptionalByKey = fieldSpec.endsWith("?");
2245
+ const fieldName = isOptionalByKey ? fieldSpec.slice(0, -1) : fieldSpec;
2246
+ const normalized = normalizeOptionalTypeSpec(rawTypeDef);
2247
+ return {
2248
+ fieldName,
2249
+ displayName: escapeFieldNameForDisplay(fieldName),
2250
+ isOptional: isOptionalByKey || normalized.optionalByType,
2251
+ validate: compileTypeValidator(normalized.spec)
2252
+ };
2253
+ });
2254
+ }
2255
+ function compileTypeValidator(typeDef) {
2256
+ if (typeof typeDef === "string") {
2257
+ return (path, value, errors) => validatePrimitiveField(path, value, typeDef, errors);
2258
+ }
2259
+ if (Array.isArray(typeDef)) {
2260
+ const elementValidator = typeDef.length > 0 ? compileTypeValidator(normalizeOptionalTypeSpec(typeDef[0]).spec) : void 0;
2261
+ return (path, value, errors) => {
2262
+ if (!Array.isArray(value)) {
2263
+ errors.push(`Field "${path}" expected array, got ${typeof value}`);
2264
+ } else if (elementValidator) {
2265
+ for (let i = 0; i < value.length; i++) {
2266
+ elementValidator(`${path}[${i}]`, value[i], errors);
2267
+ }
2268
+ }
2269
+ };
2270
+ }
2271
+ if (isTypeSpecObject(typeDef)) {
2272
+ const baseType = typeDef.type.endsWith("?") ? typeDef.type.slice(0, -1) : typeDef.type;
2273
+ if (baseType === "array") {
2274
+ const elementValidator = "items" in typeDef && typeDef.items != null ? compileTypeValidator(normalizeOptionalTypeSpec(typeDef.items).spec) : void 0;
2275
+ return (path, value, errors) => {
2276
+ if (!Array.isArray(value)) {
2277
+ errors.push(`Field "${path}" expected array, got ${typeof value}`);
2278
+ return;
2279
+ }
2280
+ if (typeof typeDef.minItems === "number" && value.length < typeDef.minItems) {
2281
+ errors.push(
2282
+ `Field "${path}" has ${value.length} items, minimum is ${typeDef.minItems}`
2283
+ );
2284
+ }
2285
+ if (typeof typeDef.maxItems === "number" && value.length > typeDef.maxItems) {
2286
+ errors.push(
2287
+ `Field "${path}" has ${value.length} items, maximum is ${typeDef.maxItems}`
2288
+ );
2289
+ }
2290
+ if (elementValidator) {
2291
+ for (let i = 0; i < value.length; i++) {
2292
+ elementValidator(`${path}[${i}]`, value[i], errors);
2293
+ }
2294
+ }
2295
+ };
2296
+ }
2297
+ const validateConstraintsCompiled = compileConstraintsValidator(typeDef);
2298
+ return (path, value, errors) => {
2299
+ validatePrimitiveField(path, value, typeDef.type, errors);
2300
+ validateConstraintsCompiled(path, value, errors);
2301
+ };
2302
+ }
2303
+ if (isPlainObject(typeDef)) {
2304
+ const compiledFields = compileObjectFields(typeDef);
2305
+ return (path, value, errors) => {
2306
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2307
+ errors.push(`Field "${path}" expected object, got ${typeof value}`);
2308
+ return;
2309
+ }
2310
+ const objValue = value;
2311
+ for (const field of compiledFields) {
2312
+ const subValue = objValue[field.fieldName];
2313
+ const subPath = joinFieldPath(path, field.fieldName);
2314
+ if (subValue === void 0 || subValue === null) {
2315
+ if (!field.isOptional) {
2316
+ errors.push(`Missing required field: "${subPath}"`);
2317
+ }
2318
+ continue;
2319
+ }
2320
+ field.validate(subPath, subValue, errors);
2321
+ }
2322
+ };
2323
+ }
2324
+ return () => {
2325
+ };
2326
+ }
2327
+ function compileConstraintsValidator(spec) {
2328
+ const pattern = typeof spec.pattern === "string" ? spec.pattern : void 0;
2329
+ const patternConstraint = pattern === void 0 ? void 0 : compilePatternConstraint(pattern);
2330
+ return (path, value, errors) => {
2331
+ if (typeof value === "string") {
2332
+ if (typeof spec.minLength === "number" && value.length < spec.minLength) {
2333
+ errors.push(
2334
+ `Field "${path}" length ${value.length} is below minimum ${spec.minLength}`
2335
+ );
2336
+ }
2337
+ if (typeof spec.maxLength === "number" && value.length > spec.maxLength) {
2338
+ errors.push(
2339
+ `Field "${path}" length ${value.length} exceeds maximum ${spec.maxLength}`
2340
+ );
2341
+ }
2342
+ if (pattern !== void 0) {
2343
+ const safePattern = escapeFieldNameForDisplay(pattern);
2344
+ if (patternConstraint?.kind === "unsupported") {
2345
+ errors.push(
2346
+ `Field "${path}" has unsupported pattern "${safePattern}"`
2347
+ );
2348
+ } else if (patternConstraint?.kind === "invalid") {
2349
+ errors.push(
2350
+ `Field "${path}" has invalid pattern "${safePattern}" \u2014 skipping regex check`
2351
+ );
2352
+ } else if (patternConstraint?.kind === "compiled" && !patternConstraint.regex.test(value)) {
2353
+ errors.push(`Field "${path}" does not match pattern "${safePattern}"`);
2354
+ }
2355
+ }
2356
+ if (Array.isArray(spec.enum) && !spec.enum.includes(value)) {
2357
+ errors.push(
2358
+ `Field "${path}" value "${escapeFieldNameForDisplay(String(value))}" is not in allowed values: ${spec.enum.map((entry) => escapeFieldNameForDisplay(String(entry))).join(", ")}`
2359
+ );
2360
+ }
2361
+ }
2362
+ if (typeof value === "number") {
2363
+ if (typeof spec.minimum === "number" && value < spec.minimum) {
2364
+ errors.push(
2365
+ `Field "${path}" value ${value} is below minimum ${spec.minimum}`
2366
+ );
2367
+ }
2368
+ if (typeof spec.maximum === "number" && value > spec.maximum) {
2369
+ errors.push(
2370
+ `Field "${path}" value ${value} exceeds maximum ${spec.maximum}`
2371
+ );
2372
+ }
2373
+ if (spec.integer === true && !Number.isInteger(value)) {
2374
+ errors.push(`Field "${path}" must be an integer, got ${value}`);
2375
+ }
2376
+ }
2377
+ };
1226
2378
  }
1227
- function nestedObjectFields(typeDef) {
1228
- if (!isPlainObject(typeDef) || isTypeSpecObject(typeDef)) return void 0;
1229
- const fields = /* @__PURE__ */ new Map();
1230
- for (const [key, value] of Object.entries(typeDef)) {
1231
- fields.set(key.endsWith("?") ? key.slice(0, -1) : key, value);
2379
+ function compilePatternConstraint(pattern) {
2380
+ if (unsafeRegexPatternReason(pattern)) return { kind: "unsupported" };
2381
+ try {
2382
+ return { kind: "compiled", regex: new RegExp(pattern) };
2383
+ } catch {
2384
+ return { kind: "invalid" };
1232
2385
  }
1233
- return fields;
1234
2386
  }
1235
2387
  function validatePrimitiveField(path, value, typeDef, errors) {
1236
2388
  switch (typeDef) {
@@ -1263,172 +2415,6 @@ function validatePrimitiveField(path, value, typeDef, errors) {
1263
2415
  break;
1264
2416
  }
1265
2417
  }
1266
- function validateNestedObjectField(path, value, typeDef, errors) {
1267
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
1268
- errors.push(`Field "${path}" expected object, got ${typeof value}`);
1269
- return;
1270
- }
1271
- const objValue = value;
1272
- for (const [subKey, rawSubType] of Object.entries(typeDef)) {
1273
- const isOptionalByKey = subKey.endsWith("?");
1274
- const fieldName = isOptionalByKey ? subKey.slice(0, -1) : subKey;
1275
- const normalized = normalizeOptionalTypeSpec(rawSubType);
1276
- const isOptional = isOptionalByKey || normalized.optionalByType;
1277
- const subValue = objValue[fieldName];
1278
- if (subValue === void 0 || subValue === null) {
1279
- if (!isOptional) {
1280
- errors.push(`Missing required field: "${path}.${fieldName}"`);
1281
- }
1282
- continue;
1283
- }
1284
- validateField(`${path}.${fieldName}`, subValue, normalized.spec, errors);
1285
- }
1286
- }
1287
- function validateField(path, value, typeDef, errors) {
1288
- if (typeof typeDef === "string") {
1289
- validatePrimitiveField(path, value, typeDef, errors);
1290
- return;
1291
- }
1292
- if (Array.isArray(typeDef)) {
1293
- if (!Array.isArray(value)) {
1294
- errors.push(`Field "${path}" expected array, got ${typeof value}`);
1295
- } else if (typeDef.length > 0) {
1296
- const rawElementType = typeDef[0];
1297
- const { spec: elementType } = normalizeOptionalTypeSpec(rawElementType);
1298
- for (let i = 0; i < value.length; i++) {
1299
- validateField(`${path}[${i}]`, value[i], elementType, errors);
1300
- }
1301
- }
1302
- return;
1303
- }
1304
- if (isTypeSpecObject(typeDef)) {
1305
- if (typeof typeDef.type === "string") {
1306
- const baseType = typeDef.type.endsWith("?") ? typeDef.type.slice(0, -1) : typeDef.type;
1307
- if (baseType === "array") {
1308
- validateTypedArrayField(path, value, typeDef, errors);
1309
- } else {
1310
- validatePrimitiveField(path, value, typeDef.type, errors);
1311
- validateConstraints(path, value, typeDef, errors);
1312
- }
1313
- } else {
1314
- errors.push(
1315
- `Field "${path}" has malformed type spec: "type" must be a string`
1316
- );
1317
- }
1318
- return;
1319
- }
1320
- if (isPlainObject(typeDef)) {
1321
- validateNestedObjectField(path, value, typeDef, errors);
1322
- }
1323
- }
1324
- function validateTypedArrayField(path, value, spec, errors) {
1325
- if (!Array.isArray(value)) {
1326
- errors.push(`Field "${path}" expected array, got ${typeof value}`);
1327
- return;
1328
- }
1329
- if (typeof spec.minItems === "number" && value.length < spec.minItems) {
1330
- errors.push(
1331
- `Field "${path}" has ${value.length} items, minimum is ${spec.minItems}`
1332
- );
1333
- }
1334
- if (typeof spec.maxItems === "number" && value.length > spec.maxItems) {
1335
- errors.push(
1336
- `Field "${path}" has ${value.length} items, maximum is ${spec.maxItems}`
1337
- );
1338
- }
1339
- if ("items" in spec && spec.items != null) {
1340
- const { spec: elementType } = normalizeOptionalTypeSpec(spec.items);
1341
- if (typeof elementType === "string" || Array.isArray(elementType) || isPlainObject(elementType)) {
1342
- for (let i = 0; i < value.length; i++) {
1343
- validateField(`${path}[${i}]`, value[i], elementType, errors);
1344
- }
1345
- }
1346
- }
1347
- }
1348
- function validateConstraints(path, value, spec, errors) {
1349
- if (typeof value === "string") {
1350
- if (typeof spec.minLength === "number" && value.length < spec.minLength) {
1351
- errors.push(
1352
- `Field "${path}" length ${value.length} is below minimum ${spec.minLength}`
1353
- );
1354
- }
1355
- if (typeof spec.maxLength === "number" && value.length > spec.maxLength) {
1356
- errors.push(
1357
- `Field "${path}" length ${value.length} exceeds maximum ${spec.maxLength}`
1358
- );
1359
- }
1360
- if (typeof spec.pattern === "string") {
1361
- try {
1362
- if (!new RegExp(spec.pattern).test(value)) {
1363
- errors.push(
1364
- `Field "${path}" does not match pattern "${spec.pattern}"`
1365
- );
1366
- }
1367
- } catch {
1368
- errors.push(
1369
- `Field "${path}" has invalid pattern "${spec.pattern}" \u2014 skipping regex check`
1370
- );
1371
- }
1372
- }
1373
- if (Array.isArray(spec.enum) && !spec.enum.includes(value)) {
1374
- errors.push(
1375
- `Field "${path}" value "${value}" is not in allowed values: ${spec.enum.join(", ")}`
1376
- );
1377
- }
1378
- }
1379
- if (typeof value === "number") {
1380
- if (typeof spec.minimum === "number" && value < spec.minimum) {
1381
- errors.push(
1382
- `Field "${path}" value ${value} is below minimum ${spec.minimum}`
1383
- );
1384
- }
1385
- if (typeof spec.maximum === "number" && value > spec.maximum) {
1386
- errors.push(
1387
- `Field "${path}" value ${value} exceeds maximum ${spec.maximum}`
1388
- );
1389
- }
1390
- if (spec.integer === true && !Number.isInteger(value)) {
1391
- errors.push(`Field "${path}" must be an integer, got ${value}`);
1392
- }
1393
- }
1394
- }
1395
-
1396
- // ../rules/src/system-components/identity.ts
1397
- var IDENTITY_COMPONENT_ID = "com.warmhub.identity";
1398
- var IDENTITY_FIELDS = {
1399
- // Required. Conventionally equal to the thing's name (the wref tail) —
1400
- // Identity things are addressed as Identity/<external_id>. Kept as a
1401
- // field for explicit schema visibility and to leave room for callers
1402
- // that read the body without resolving the wref. v1 enforces this only
1403
- // by convention; a commit-time validator can be added when a consumer
1404
- // needs hard guarantees.
1405
- external_id: "string",
1406
- display_name: "string"
1407
- // Deferred fields, added as additive shape revisions when their consumer
1408
- // ticket lands: email, avatar_url, profile, kind. Each consumer (#2548,
1409
- // #2549, #2550) is the trigger for adding the corresponding optional field.
1410
- };
1411
- var IDENTITY_MANIFEST = {
1412
- component: {
1413
- id: IDENTITY_COMPONENT_ID,
1414
- name: "identity",
1415
- version: "1.0.0"
1416
- },
1417
- shapes: [{ name: "Identity", fields: IDENTITY_FIELDS }],
1418
- credentials: [],
1419
- subscriptions: [],
1420
- seeds: [],
1421
- health: { requires: { shapes: ["Identity"] } },
1422
- teardown: {},
1423
- runtimeAccess: { reads: [], writes: [] }
1424
- };
1425
- var IDENTITY_COMPONENT = {
1426
- componentId: IDENTITY_COMPONENT_ID,
1427
- name: IDENTITY_MANIFEST.component.name,
1428
- version: IDENTITY_MANIFEST.component.version,
1429
- manifest: IDENTITY_MANIFEST,
1430
- shapes: [{ name: "Identity", fields: IDENTITY_FIELDS }]
1431
- };
1432
2418
 
1433
2419
  // ../rules/src/system-components/system.ts
1434
2420
  var SYSTEM_COMPONENT_ID = "com.warmhub.system";
@@ -1483,8 +2469,7 @@ var SYSTEM_COMPONENT = {
1483
2469
 
1484
2470
  // ../rules/src/system-components/index.ts
1485
2471
  var SYSTEM_COMPONENTS = [
1486
- SYSTEM_COMPONENT,
1487
- IDENTITY_COMPONENT
2472
+ SYSTEM_COMPONENT
1488
2473
  ];
1489
2474
  new Set(
1490
2475
  SYSTEM_COMPONENTS.filter((entry) => entry.installInfra === true).flatMap(
@@ -1492,6 +2477,16 @@ new Set(
1492
2477
  )
1493
2478
  );
1494
2479
 
2480
+ // src/shape-preflight.ts
2481
+ function shapeDefinitionPreflightError(name, data, verb) {
2482
+ const result = validateShapeDefinition(
2483
+ data,
2484
+ verb === "revise" ? { allowUnsafeFieldNamePaths: collectUnsafeShapeFieldNamePaths(data) } : void 0
2485
+ );
2486
+ if (result.valid) return null;
2487
+ return `Invalid shape definition for "${name}": ${result.errors.join("; ")}`;
2488
+ }
2489
+
1495
2490
  // src/operation-normalize.ts
1496
2491
  function toBackendStreamOperation(operation) {
1497
2492
  if (operation.expectedVersion !== void 0 && operation.operation !== "revise") {
@@ -1513,7 +2508,7 @@ function toBackendStreamOperation(operation) {
1513
2508
  if (!name) {
1514
2509
  throw new Error("revise operation requires a target");
1515
2510
  }
1516
- const kind2 = operation.kind ?? inferKind(name, operation);
2511
+ const kind2 = inferOperationKind({ kind: operation.kind, name });
1517
2512
  if (Object.hasOwn(operation, "active")) {
1518
2513
  throw new Error(
1519
2514
  `${kind2} revise operation no longer supports 'active' \u2014 use retract('${name}') instead`
@@ -1546,8 +2541,13 @@ function toBackendStreamOperation(operation) {
1546
2541
  ...operation.leaseId ? { leaseId: operation.leaseId } : {}
1547
2542
  };
1548
2543
  }
1549
- const kind = operation.kind ?? inferKind(operation.name, operation);
2544
+ const kind = inferOperationKind(operation);
1550
2545
  const skipExisting = "skipExisting" in operation ? operation.skipExisting : void 0;
2546
+ if (kind !== "collection" && ("type" in operation && operation.type !== void 0 || "members" in operation && operation.members !== void 0)) {
2547
+ throw new Error(
2548
+ `add operation '${operation.name ?? ""}' has collection fields but resolved kind '${kind}' \u2014 collection adds require both 'type' and 'members', or set kind: 'collection'`
2549
+ );
2550
+ }
1551
2551
  if (kind === "collection") {
1552
2552
  if (!("members" in operation) || !operation.members) {
1553
2553
  throw new Error("collection add operation requires 'members'");
@@ -1591,21 +2591,6 @@ function toBackendStreamOperation(operation) {
1591
2591
  ...skipExisting === true ? { skipExisting } : {}
1592
2592
  };
1593
2593
  }
1594
- function inferKind(name, operation) {
1595
- if (operation.kind) {
1596
- return operation.kind;
1597
- }
1598
- if ("about" in operation && operation.about !== void 0) {
1599
- return "assertion";
1600
- }
1601
- if ("type" in operation && operation.type !== void 0 || "members" in operation && operation.members !== void 0) {
1602
- return "collection";
1603
- }
1604
- if (!name) {
1605
- return "shape";
1606
- }
1607
- return name.includes("/") ? "thing" : "shape";
1608
- }
1609
2594
 
1610
2595
  // src/stream-submit.ts
1611
2596
  var DEFAULT_STREAM_CHUNK_SIZE = DEFAULT_STREAM_APPEND_CHUNK_SIZE;
@@ -2467,34 +3452,22 @@ function inferShapeForAdd(op) {
2467
3452
  }
2468
3453
  return void 0;
2469
3454
  }
2470
- function inferKindFromName2(name) {
2471
- const parts = name.split("/").filter(Boolean);
2472
- if (parts.length >= 3) return "assertion";
2473
- if (parts.length === 2) return "thing";
2474
- return "thing";
2475
- }
2476
3455
  function asNonEmptyString(value) {
2477
3456
  if (typeof value !== "string") return void 0;
2478
3457
  const trimmed = value.trim();
2479
3458
  return trimmed.length > 0 ? trimmed : void 0;
2480
3459
  }
2481
3460
  function normalizeAddKind(op) {
2482
- if (op.kind === "shape" || op.kind === "thing" || op.kind === "assertion" || op.kind === "collection") {
2483
- return op.kind;
2484
- }
2485
- if (op.type && Array.isArray(op.members)) {
2486
- return "collection";
2487
- }
2488
- if (op.about !== void 0 || op.aboutWref !== void 0) {
2489
- return "assertion";
2490
- }
2491
- return op.name ? inferKindFromName2(op.name) : "thing";
3461
+ return inferOperationKind({
3462
+ kind: op.kind,
3463
+ name: op.name,
3464
+ about: op.about ?? op.aboutWref,
3465
+ type: op.type,
3466
+ members: op.members
3467
+ });
2492
3468
  }
2493
3469
  function normalizeReviseKind(op, name) {
2494
- if (op.kind === "shape" || op.kind === "thing" || op.kind === "assertion" || op.kind === "collection") {
2495
- return op.kind;
2496
- }
2497
- return inferKindFromName2(name);
3470
+ return inferOperationKind({ kind: op.kind, name });
2498
3471
  }
2499
3472
  function toBackendOp(op) {
2500
3473
  if (op.operation === "retract") {
@@ -2605,13 +3578,13 @@ function preflightCheckCommon(verb, kind, name, data) {
2605
3578
  );
2606
3579
  }
2607
3580
  }
2608
- if (kind === "shape" && data !== void 0) {
2609
- const result = validateShapeDefinition(data);
2610
- if (!result.valid) {
2611
- errors.push(
2612
- `Invalid shape definition for "${name}": ${result.errors.join("; ")}`
2613
- );
2614
- }
3581
+ if (kind === "shape" && (verb === "ADD" || verb === "REVISE") && data !== void 0) {
3582
+ const preflight = shapeDefinitionPreflightError(
3583
+ name,
3584
+ data,
3585
+ verb === "REVISE" ? "revise" : "add"
3586
+ );
3587
+ if (preflight) errors.push(preflight);
2615
3588
  }
2616
3589
  return errors;
2617
3590
  }
@@ -2620,6 +3593,18 @@ function preflightCheckAdd(op, kind) {
2620
3593
  if (op.name) {
2621
3594
  messages.push(...preflightCheckCommon("ADD", kind, op.name, op.data));
2622
3595
  }
3596
+ if (kind === "assertion" && op.about === void 0 && op.aboutWref === void 0 && // Token-bearing names defer to validate() so the more specific token
3597
+ // diagnostics surface instead of this eager check.
3598
+ !(op.name && hasAnyTokens(op.name))) {
3599
+ messages.push(
3600
+ `ADD assertion '${op.name}' missing required 'about' \u2014 for hierarchical thing names set kind: 'thing' explicitly`
3601
+ );
3602
+ }
3603
+ if (kind !== "collection" && (op.type !== void 0 || op.members !== void 0)) {
3604
+ messages.push(
3605
+ `ADD '${op.name}' has collection fields but inferred kind '${kind}' \u2014 collection adds require both 'type' and 'members', or set kind: 'collection'`
3606
+ );
3607
+ }
2623
3608
  for (const diagnostic of preflightOpDiagnostics(toPreflightOp(op, kind), 0)) {
2624
3609
  messages.push(diagnostic.message);
2625
3610
  }
@@ -2667,11 +3652,12 @@ function validateWarmHubClientOptions(options) {
2667
3652
  throw new TypeError(`Unknown WarmHubClient option "${key}"${hint}`);
2668
3653
  }
2669
3654
  }
2670
- var SDK_VERSION = "0.51.0" ;
3655
+ var SDK_VERSION = "0.52.1" ;
2671
3656
  var DEFAULT_API_URL = "https://api.warmhub.ai";
2672
3657
  var UNBATCHED_TRPC_PATHS = /* @__PURE__ */ new Set([
2673
3658
  "repo.shapeInstanceCounts",
2674
- "thing.getMany"
3659
+ "thing.getMany",
3660
+ "thing.headVersions"
2675
3661
  ]);
2676
3662
  var DEFAULT_THING_GET_MANY_CHUNK_SIZE = 500;
2677
3663
  var MAX_THING_GET_MANY_CHUNK_SIZE = 500;
@@ -3165,27 +4151,13 @@ var WarmHubClient = class _WarmHubClient {
3165
4151
  throw toWarmHubError(error);
3166
4152
  }
3167
4153
  },
3168
- /**
3169
- * Install an allowlisted bundled system component into a repository.
3170
- */
3171
- installSystem: async (orgName, repoName, componentId) => {
3172
- try {
3173
- return await this.trpc.component.installSystem.mutate({
3174
- orgName,
3175
- repoName,
3176
- componentId
3177
- });
3178
- } catch (error) {
3179
- throw toWarmHubError(error);
3180
- }
3181
- },
3182
4154
  /**
3183
4155
  * Registered-component identity and install-pipeline operations.
3184
4156
  *
3185
- * This sub-surface drives the backend-mediated install flow that powers `wh component install <org/name>`. It manages registered component identities (`register`, `unregister`, `list`, `view`, `update`) and the install pipeline (`resolve` mints an install id, `downloadSource` fetches the source archive through the backend, `setupCall` dispatches the optional setup callback). Use these when building a custom installer; most callers should run the CLI instead.
4157
+ * This sub-surface drives the backend-mediated install flow that powers `wh component install <org/name>`. It manages registered component identities (`register`, `unregister`, `list`, `view`, `update`) and the install pipeline (`resolve` returns the latest published manifest plus an install id, `setupCall` dispatches the optional setup callback). Use these when building a custom installer; most callers should run the CLI instead.
3186
4158
  */
3187
4159
  registry: {
3188
- register: async (orgName, componentName, input = {}) => {
4160
+ register: async (orgName, componentName, input) => {
3189
4161
  try {
3190
4162
  return await this.trpc.component.registry.register.mutate({
3191
4163
  orgName,
@@ -3246,26 +4218,6 @@ var WarmHubClient = class _WarmHubClient {
3246
4218
  }
3247
4219
  );
3248
4220
  },
3249
- downloadSource: async (orgName, componentName, input) => {
3250
- const response = await this.requestResponse(
3251
- `/api/component-registry/${encodeURIComponent(orgName)}/${encodeURIComponent(componentName)}/source`,
3252
- {
3253
- method: "POST",
3254
- headers: {
3255
- "content-type": "application/json"
3256
- },
3257
- body: JSON.stringify(input)
3258
- }
3259
- );
3260
- return {
3261
- archive: new Uint8Array(await response.arrayBuffer()),
3262
- sourceUrl: response.headers.get("x-warmhub-source-url") ?? "",
3263
- sourceRef: response.headers.get("x-warmhub-source-ref") ?? void 0,
3264
- resolvedSha: response.headers.get("x-warmhub-resolved-sha") ?? void 0,
3265
- componentRef: response.headers.get("x-warmhub-component-ref") ?? `${orgName}/${componentName}`,
3266
- contentType: response.headers.get("content-type") || "application/octet-stream"
3267
- };
3268
- },
3269
4221
  setupCall: async (orgName, componentName, input) => {
3270
4222
  return await this.requestJson(
3271
4223
  `/api/component-registry/${encodeURIComponent(orgName)}/${encodeURIComponent(componentName)}/setup-call`,
@@ -4051,6 +5003,15 @@ var WarmHubClient = class _WarmHubClient {
4051
5003
  * @param opts.description Optional human-readable shape description.
4052
5004
  */
4053
5005
  create: async (orgName, repoName, shapeName, fields, opts) => {
5006
+ const preflight = shapeDefinitionPreflightError(
5007
+ shapeName,
5008
+ {
5009
+ fields,
5010
+ ...opts?.description !== void 0 ? { description: opts.description } : {}
5011
+ },
5012
+ "add"
5013
+ );
5014
+ if (preflight) throw new WarmHubError("VALIDATION_ERROR", preflight);
4054
5015
  try {
4055
5016
  return await this.trpc.shape.create.mutate({
4056
5017
  orgName,
@@ -4073,6 +5034,15 @@ var WarmHubClient = class _WarmHubClient {
4073
5034
  * @param opts.description Optional human-readable description for the revised shape.
4074
5035
  */
4075
5036
  revise: async (orgName, repoName, shapeName, newFields, opts) => {
5037
+ const preflight = shapeDefinitionPreflightError(
5038
+ shapeName,
5039
+ {
5040
+ fields: newFields,
5041
+ ...opts?.description !== void 0 ? { description: opts.description } : {}
5042
+ },
5043
+ "revise"
5044
+ );
5045
+ if (preflight) throw new WarmHubError("VALIDATION_ERROR", preflight);
4076
5046
  try {
4077
5047
  return await this.trpc.shape.revise.mutate({
4078
5048
  orgName,
@@ -4137,15 +5107,15 @@ var WarmHubClient = class _WarmHubClient {
4137
5107
  }
4138
5108
  };
4139
5109
  /**
4140
- * Webhook and cron subscription management surface scoped to a repository.
5110
+ * Webhook subscription management surface scoped to a repository.
4141
5111
  *
4142
5112
  * @see https://docs.warmhub.ai/sdk/component-identity/#subscriptions
4143
5113
  */
4144
5114
  subscription = {
4145
5115
  /**
4146
- * Create a webhook or cron subscription.
5116
+ * Create a webhook subscription.
4147
5117
  *
4148
- * Both subscription kinds require a delivery URL. Webhook subscriptions can also forward events from another repository, allow trace reentry, bind fallback delivery, and opt into success notifications. Component identity is set at creation time and follows the same authority rules as commit writes.
5118
+ * Webhook subscriptions require a delivery URL and can forward events from another repository, allow trace reentry, bind fallback delivery, and opt into success notifications. Component identity is set at creation time and follows the same authority rules as commit writes.
4149
5119
  *
4150
5120
  * Webhook subscriptions must specify a shape filter via `shapeName` or `filterJson.shape`, except [shape-lifecycle subscriptions](/subscriptions/creating/#shape-lifecycle-subscriptions) which omit both and rely on a `{ kind: 'shape', ... }` filter.
4151
5121
  */
@@ -4174,6 +5144,27 @@ var WarmHubClient = class _WarmHubClient {
4174
5144
  throw toWarmHubError(error);
4175
5145
  }
4176
5146
  },
5147
+ /**
5148
+ * Reveal the raw webhook URL(s) for a subscription.
5149
+ *
5150
+ * Reads return only `webhookOrigin`/`fallbackWebhookOrigin` (scheme://host);
5151
+ * the raw URL path is a bearer secret. This break-glass call returns the
5152
+ * raw URL(s) and is audit-logged server-side. Requires `repo:configure`.
5153
+ * Because it returns the secret, a name-scoped principal (e.g. a component
5154
+ * setup token) may reveal only the subscriptions it is scoped to — a
5155
+ * stricter contract than the redacted `get`/`list`, which are unscoped.
5156
+ */
5157
+ reveal: async (orgName, repoName, name) => {
5158
+ try {
5159
+ return await this.trpc.subscription.reveal.query({
5160
+ orgName,
5161
+ repoName,
5162
+ name
5163
+ });
5164
+ } catch (error) {
5165
+ throw toWarmHubError(error);
5166
+ }
5167
+ },
4177
5168
  /**
4178
5169
  * List subscriptions attached to a repository.
4179
5170
  */
@@ -4189,9 +5180,9 @@ var WarmHubClient = class _WarmHubClient {
4189
5180
  }
4190
5181
  },
4191
5182
  /**
4192
- * Update an existing webhook or cron subscription.
5183
+ * Update an existing webhook subscription.
4193
5184
  *
4194
- * Use `null` for nullable fields such as fallback webhook URL when you need to clear an existing value.
5185
+ * Use `null` for nullable fields such as fallback webhook URL when you need to clear an existing value. Legacy cron subscriptions cannot be updated — delete them instead.
4195
5186
  */
4196
5187
  update: async (input) => {
4197
5188
  try {
@@ -4652,6 +5643,62 @@ var WarmHubClient = class _WarmHubClient {
4652
5643
  throw toWarmHubError(error);
4653
5644
  }
4654
5645
  },
5646
+ /**
5647
+ * Batched lightweight per-thing change probe — returns
5648
+ * `{ wref, durableId, version, active, versionCreatedAt }` per wref with NO
5649
+ * payload. Use it to check whether locally-cached copies are still fresh
5650
+ * without pulling `data`: a value is stale if `durableId` differs (the wref
5651
+ * now points to a different thing), `version`/`active` differ (the same
5652
+ * thing changed/was retracted), or the wref appears in `missing`.
5653
+ *
5654
+ * Auto-chunks above the backend's 500-wref transport cap; preserves
5655
+ * duplicate requested wrefs and reports inaccessible/unknown refs in
5656
+ * `missing` rather than throwing per item.
5657
+ *
5658
+ * @param opts.chunkSize Maximum wrefs per backend request. Defaults to 500 and is clamped to the backend cap.
5659
+ * @param opts.chunkConcurrency Maximum concurrent chunk requests. Defaults to 1 and is clamped to 8.
5660
+ */
5661
+ headVersions: async (orgName, repoName, wrefs, opts) => {
5662
+ try {
5663
+ if (wrefs.length === 0) {
5664
+ return { requested: 0, items: [], missing: [] };
5665
+ }
5666
+ const chunkSize = normalizePositiveIntegerOption(
5667
+ opts?.chunkSize,
5668
+ DEFAULT_THING_GET_MANY_CHUNK_SIZE,
5669
+ MAX_THING_GET_MANY_CHUNK_SIZE
5670
+ );
5671
+ const chunkConcurrency = normalizePositiveIntegerOption(
5672
+ opts?.chunkConcurrency,
5673
+ DEFAULT_THING_GET_MANY_CHUNK_CONCURRENCY,
5674
+ MAX_THING_GET_MANY_CHUNK_CONCURRENCY
5675
+ );
5676
+ const fetchChunk = async (chunkWrefs) => {
5677
+ return await this.trpc.thing.headVersions.query({
5678
+ orgName,
5679
+ repoName,
5680
+ wrefs: chunkWrefs
5681
+ });
5682
+ };
5683
+ if (wrefs.length <= chunkSize) {
5684
+ return await fetchChunk(wrefs);
5685
+ }
5686
+ const chunks = chunkArray(wrefs, chunkSize);
5687
+ const chunkResults = [];
5688
+ for (let start = 0; start < chunks.length; start += chunkConcurrency) {
5689
+ const batch = chunks.slice(start, start + chunkConcurrency);
5690
+ const batchResults = await Promise.all(batch.map(fetchChunk));
5691
+ chunkResults.push(...batchResults);
5692
+ }
5693
+ return {
5694
+ requested: wrefs.length,
5695
+ items: chunkResults.flatMap((result) => result.items),
5696
+ missing: chunkResults.flatMap((result) => result.missing)
5697
+ };
5698
+ } catch (error) {
5699
+ throw toWarmHubError(error);
5700
+ }
5701
+ },
4655
5702
  /**
4656
5703
  * Return version history and timeline metadata for repository records.
4657
5704
  *
@@ -5558,5 +6605,5 @@ function isAbortError(error) {
5558
6605
  }
5559
6606
 
5560
6607
  export { AllStreamOperationsFailedError, CLI_INSTALL_REPO_HEADER, CLI_SIGNATURE_HEADER, CLI_TIMESTAMP_HEADER, CONTENT_FIELD_LIMIT_ERROR, CliCallVerificationError, DEFAULT_API_URL, DEFAULT_STREAM_CHUNK_SIZE, MAX_CONTENT_FIELD_BYTES, MAX_STREAM_APPEND_OPERATION_COUNT2 as MAX_STREAM_APPEND_OPERATION_COUNT, OperationBuilder, PartialStreamSubmissionError, SDK_VERSION, WarmHubClient, WarmHubError, connectionErrorMessage, contentFieldLimitError, countStreamAppendResultStatuses, isConnectionError, isRetryable, isWarmHubError, normalizeWref, resolveFunctionLogMode, sanitizeErrorMessage, sdkVersionIsBelowMinimum, streamAppendResultStatus, submitOperationsViaStream, toWarmHubError, validateAgainstShape, verifyCliCall };
5561
- //# sourceMappingURL=chunk-ZTDMTDJ6.js.map
5562
- //# sourceMappingURL=chunk-ZTDMTDJ6.js.map
6608
+ //# sourceMappingURL=chunk-OPTB23HY.js.map
6609
+ //# sourceMappingURL=chunk-OPTB23HY.js.map