@warmhub/sdk-ts 0.60.1 → 0.62.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.
@@ -17,6 +17,13 @@ function isBuiltinShape(name) {
17
17
  return BUILTIN_SHAPE_NAMES.includes(name) || BUILTIN_CONTENT_SHAPE_NAMES.includes(name);
18
18
  }
19
19
 
20
+ // ../rules/src/client-header.ts
21
+ var CLIENT_HEADER = "X-WarmHub-Client";
22
+ var WARMHUB_SDK_CLIENT_NAME = "@warmhub/sdk-ts";
23
+ function formatClientHeader(name, version) {
24
+ return `${name}/${version}`;
25
+ }
26
+
20
27
  // ../rules/src/commit-stream-limits.ts
21
28
  var DEFAULT_STREAM_APPEND_CHUNK_SIZE = 1e3;
22
29
  var MAX_STREAM_APPEND_OPERATION_COUNT = 1e4;
@@ -190,7 +197,7 @@ var REPO_AUTH_SCOPES = [
190
197
  "repo:action-callback"
191
198
  ];
192
199
 
193
- // ../rules/src/component-manifest-contract.ts
200
+ // ../rules/src/component-manifest-contract-internals.ts
194
201
  new Set(REPO_AUTH_SCOPES);
195
202
 
196
203
  // ../rules/src/field-name-safety.ts
@@ -378,183 +385,13 @@ function hasAnyTokens(s) {
378
385
  return ANY_TOKEN_RE.test(s);
379
386
  }
380
387
 
381
- // ../rules/src/preflight.ts
382
- var collectionTypes = ["pair", "triple", "set", "list"];
383
- var collectionOps = ["add", "revise"];
384
- function preflightOpDiagnostics(op, operationIndex) {
385
- const errors = [];
386
- errors.push(...builtinShapeGuard(op, operationIndex));
387
- errors.push(...contentNameGuard(op, operationIndex));
388
- errors.push(...plusSignGuard(op, operationIndex));
389
- errors.push(...validateCollectionAbouts(op, operationIndex));
390
- errors.push(...validateCollectionOps(op, operationIndex));
391
- return errors;
392
- }
393
- function builtinShapeGuard(op, operationIndex) {
394
- const errors = [];
395
- const name = op.name;
396
- if (op.kind === "shape" && name && isBuiltinShape(name)) {
397
- errors.push({
398
- code: "RESERVED_NAME",
399
- operationIndex,
400
- message: `Shape "${name}" is a built-in shape and cannot be ${op.operation === "add" ? "created" : "revised"} manually`
401
- });
402
- }
403
- if (op.kind === "thing" && name) {
404
- const local = splitLocalPath(name);
405
- if (local && isBuiltinCollectionShape(local.shapePrefix)) {
406
- const message = op.operation === "add" ? `Cannot add kind="thing" under built-in shape "${local.shapePrefix}". Use kind="collection" or the about sugar instead.` : `Cannot revise kind="thing" under built-in shape "${local.shapePrefix}". Collections are immutable.`;
407
- errors.push({
408
- code: "VALIDATION_ERROR",
409
- operationIndex,
410
- message
411
- });
412
- }
413
- }
414
- return errors;
415
- }
416
- function contentNameGuard(op, operationIndex) {
417
- if (op.kind !== "thing" || !op.name) return [];
418
- const local = splitLocalPath(op.name);
419
- if (!local || !isContentShape(local.shapePrefix)) return [];
420
- const bareName = local.bareName;
421
- if (isSynthesizedContentName(bareName)) {
422
- return [
423
- {
424
- code: "READ_ONLY_BUILTIN_CONTENT",
425
- operationIndex,
426
- message: `${local.shapePrefix}/${bareName} is synthesized; writes are not allowed`
427
- }
428
- ];
429
- }
430
- if (!isStoredContentName(bareName)) {
431
- return [
432
- {
433
- code: "UNKNOWN_CONTENT_NAME",
434
- operationIndex,
435
- message: `${local.shapePrefix}/${bareName} is not a recognized well-known content name`
436
- }
437
- ];
438
- }
439
- return [];
440
- }
441
- function plusSignGuard(op, operationIndex) {
442
- const errors = [];
443
- const name = op.name;
444
- if (op.kind === "collection") return errors;
445
- if (name?.includes("+")) {
446
- const local = splitLocalPath(name);
447
- if (local && isBuiltinCollectionShape(local.shapePrefix)) return errors;
448
- errors.push({
449
- code: "VALIDATION_ERROR",
450
- operationIndex,
451
- message: `Name "${name}" contains reserved character "+". The "+" character is reserved for collection names.`
452
- });
453
- }
454
- return errors;
455
- }
456
- function validateCollectionAbouts(op, operationIndex) {
457
- const errors = [];
458
- if (!op.about || typeof op.about === "string") return errors;
459
- if (!isCollectionAbout(op.about)) {
460
- const keys = typeof op.about === "object" && op.about !== null ? Object.keys(op.about) : [];
461
- errors.push({
462
- code: "VALIDATION_ERROR",
463
- operationIndex,
464
- message: `Structured about must have exactly one key: pair, triple, set, or list. Got: ${keys.join(", ") || typeof op.about}`
465
- });
466
- return errors;
467
- }
468
- const tag = collectionAboutType(op.about);
469
- const members = collectionAboutMembers(op.about);
470
- for (let i = 0; i < members.length; i++) {
471
- if (typeof members[i] !== "string") {
472
- errors.push({
473
- code: "VALIDATION_ERROR",
474
- operationIndex,
475
- message: `Collection member at index ${i} must be a string, got ${typeof members[i]}`
476
- });
477
- }
478
- }
479
- const arityError = collectionArityError(tag, members);
480
- if (arityError) {
481
- errors.push({
482
- code: "VALIDATION_ERROR",
483
- operationIndex,
484
- message: arityError
485
- });
486
- }
487
- return errors;
488
- }
489
- function validateCollectionOps(op, operationIndex) {
490
- const errors = [];
491
- if (op.kind !== "collection") return errors;
492
- if (!collectionOps.includes(op.operation)) {
493
- errors.push({
494
- code: "VALIDATION_ERROR",
495
- operationIndex,
496
- message: `Collection kind only supports "add" or "revise" operation, got "${op.operation}"`
497
- });
498
- return errors;
499
- }
500
- if (op.operation === "add") {
501
- if (!op.type || !collectionTypes.includes(op.type)) {
502
- errors.push({
503
- code: "VALIDATION_ERROR",
504
- operationIndex,
505
- message: `Collection "type" must be one of: pair, triple, set, list. Got: "${op.type ?? ""}"`
506
- });
507
- }
508
- if (!op.members || !Array.isArray(op.members)) {
509
- errors.push({
510
- code: "VALIDATION_ERROR",
511
- operationIndex,
512
- message: 'Collection requires a "members" array'
513
- });
514
- } else {
515
- for (let i = 0; i < op.members.length; i++) {
516
- if (typeof op.members[i] !== "string") {
517
- errors.push({
518
- code: "VALIDATION_ERROR",
519
- operationIndex,
520
- message: `Collection member at index ${i} must be a string`
521
- });
522
- }
523
- }
524
- }
525
- if (op.type && op.members) {
526
- const arityError = collectionArityError(
527
- op.type,
528
- op.members
529
- );
530
- if (arityError) {
531
- errors.push({
532
- code: "VALIDATION_ERROR",
533
- operationIndex,
534
- message: arityError
535
- });
536
- }
537
- }
538
- }
539
- return errors;
540
- }
388
+ // ../rules/src/preflight-commit.ts
541
389
  function preflightCommitDiagnostics(operations, options) {
542
390
  const errors = [];
543
391
  tokenHygiene(operations, errors);
544
392
  illegalOpSequences(operations, errors);
545
393
  return errors;
546
394
  }
547
- function collectionArityError(tag, members) {
548
- switch (tag) {
549
- case "pair":
550
- return members.length !== 2 ? `Pair requires exactly 2 members, got ${members.length}` : null;
551
- case "triple":
552
- return members.length !== 3 ? `Triple requires exactly 3 members, got ${members.length}` : null;
553
- case "set":
554
- case "list":
555
- return members.length < 1 ? `${tag === "set" ? "Set" : "List"} requires at least 1 member, got 0` : null;
556
- }
557
- }
558
395
  function getOpName(op) {
559
396
  return op.name;
560
397
  }
@@ -727,59 +564,239 @@ function illegalOpSequences(operations, errors, checkAddAdd) {
727
564
  }
728
565
  }
729
566
 
730
- // ../rules/src/reserved-orgs.ts
731
- var RESERVED_RAW_CONTENT_TOP_LEVEL_NAMES = [
732
- "_app",
733
- "_static",
734
- "api",
735
- "health",
736
- "healthz",
737
- "mcp",
738
- "readyz",
739
- "robots.txt",
740
- "sse",
741
- "trpc",
742
- "version"
743
- ];
744
- var RESERVED_ORG_NAMES = [
745
- ...RESERVED_RAW_CONTENT_TOP_LEVEL_NAMES,
746
- "admin",
747
- "billing",
748
- "blog",
749
- "docs",
750
- "help",
751
- "login",
752
- "public",
753
- "settings",
754
- "signup",
755
- "status",
756
- "support",
757
- "system",
758
- "warmhub",
759
- "www"
760
- ];
761
- new Set(RESERVED_ORG_NAMES);
762
-
763
- // ../rules/src/shape-field-key.ts
764
- function foldFieldName(name) {
765
- const stripped = name.endsWith("?") ? name.slice(0, -1) : name;
766
- return stripped.toLowerCase();
567
+ // ../rules/src/preflight-operation.ts
568
+ var collectionTypes = ["pair", "triple", "set", "list"];
569
+ var collectionOps = ["add", "revise"];
570
+ function preflightOpDiagnostics(op, operationIndex) {
571
+ const errors = [];
572
+ errors.push(...builtinShapeGuard(op, operationIndex));
573
+ errors.push(...contentNameGuard(op, operationIndex));
574
+ errors.push(...plusSignGuard(op, operationIndex));
575
+ errors.push(...validateCollectionAbouts(op, operationIndex));
576
+ errors.push(...validateCollectionOps(op, operationIndex));
577
+ return errors;
767
578
  }
768
- function foldFieldPath(path) {
769
- return path.split(".").map((segment) => foldFieldName(segment)).join(".");
579
+ function builtinShapeGuard(op, operationIndex) {
580
+ const errors = [];
581
+ const name = op.name;
582
+ if (op.kind === "shape" && name && isBuiltinShape(name)) {
583
+ errors.push({
584
+ code: "RESERVED_NAME",
585
+ operationIndex,
586
+ message: `Shape "${name}" is a built-in shape and cannot be ${op.operation === "add" ? "created" : "revised"} manually`
587
+ });
588
+ }
589
+ if (op.kind === "thing" && name) {
590
+ const local = splitLocalPath(name);
591
+ if (local && isBuiltinCollectionShape(local.shapePrefix)) {
592
+ const message = op.operation === "add" ? `Cannot add kind="thing" under built-in shape "${local.shapePrefix}". Use kind="collection" or the about sugar instead.` : `Cannot revise kind="thing" under built-in shape "${local.shapePrefix}". Collections are immutable.`;
593
+ errors.push({
594
+ code: "VALIDATION_ERROR",
595
+ operationIndex,
596
+ message
597
+ });
598
+ }
599
+ }
600
+ return errors;
770
601
  }
771
-
772
- // ../rules/src/shape-types.ts
773
- var BASE_PRIMITIVE_TYPES = [
774
- "number",
775
- "string",
776
- "boolean",
777
- "wref",
778
- "array"
779
- ];
780
- var BASE_PRIMITIVE_TYPE_SET = new Set(BASE_PRIMITIVE_TYPES);
781
- var VALID_PRIMITIVE_TYPES = /* @__PURE__ */ new Set([
782
- "number",
602
+ function contentNameGuard(op, operationIndex) {
603
+ if (op.kind !== "thing" || !op.name) return [];
604
+ const local = splitLocalPath(op.name);
605
+ if (!local || !isContentShape(local.shapePrefix)) return [];
606
+ const bareName = local.bareName;
607
+ if (isSynthesizedContentName(bareName)) {
608
+ return [
609
+ {
610
+ code: "READ_ONLY_BUILTIN_CONTENT",
611
+ operationIndex,
612
+ message: `${local.shapePrefix}/${bareName} is synthesized; writes are not allowed`
613
+ }
614
+ ];
615
+ }
616
+ if (!isStoredContentName(bareName)) {
617
+ return [
618
+ {
619
+ code: "UNKNOWN_CONTENT_NAME",
620
+ operationIndex,
621
+ message: `${local.shapePrefix}/${bareName} is not a recognized well-known content name`
622
+ }
623
+ ];
624
+ }
625
+ return [];
626
+ }
627
+ function plusSignGuard(op, operationIndex) {
628
+ const errors = [];
629
+ const name = op.name;
630
+ if (op.kind === "collection") return errors;
631
+ if (name?.includes("+")) {
632
+ const local = splitLocalPath(name);
633
+ if (local && isBuiltinCollectionShape(local.shapePrefix)) return errors;
634
+ errors.push({
635
+ code: "VALIDATION_ERROR",
636
+ operationIndex,
637
+ message: `Name "${name}" contains reserved character "+". The "+" character is reserved for collection names.`
638
+ });
639
+ }
640
+ return errors;
641
+ }
642
+ function validateCollectionAbouts(op, operationIndex) {
643
+ const errors = [];
644
+ if (!op.about || typeof op.about === "string") return errors;
645
+ if (!isCollectionAbout(op.about)) {
646
+ const keys = typeof op.about === "object" && op.about !== null ? Object.keys(op.about) : [];
647
+ errors.push({
648
+ code: "VALIDATION_ERROR",
649
+ operationIndex,
650
+ message: `Structured about must have exactly one key: pair, triple, set, or list. Got: ${keys.join(", ") || typeof op.about}`
651
+ });
652
+ return errors;
653
+ }
654
+ const tag = collectionAboutType(op.about);
655
+ const members = collectionAboutMembers(op.about);
656
+ for (let i = 0; i < members.length; i++) {
657
+ if (typeof members[i] !== "string") {
658
+ errors.push({
659
+ code: "VALIDATION_ERROR",
660
+ operationIndex,
661
+ message: `Collection member at index ${i} must be a string, got ${typeof members[i]}`
662
+ });
663
+ }
664
+ }
665
+ const arityError = collectionArityError(tag, members);
666
+ if (arityError) {
667
+ errors.push({
668
+ code: "VALIDATION_ERROR",
669
+ operationIndex,
670
+ message: arityError
671
+ });
672
+ }
673
+ return errors;
674
+ }
675
+ function validateCollectionOps(op, operationIndex) {
676
+ const errors = [];
677
+ if (op.kind !== "collection") return errors;
678
+ if (!collectionOps.includes(op.operation)) {
679
+ errors.push({
680
+ code: "VALIDATION_ERROR",
681
+ operationIndex,
682
+ message: `Collection kind only supports "add" or "revise" operation, got "${op.operation}"`
683
+ });
684
+ return errors;
685
+ }
686
+ if (op.operation === "add") {
687
+ if (!op.type || !collectionTypes.includes(op.type)) {
688
+ errors.push({
689
+ code: "VALIDATION_ERROR",
690
+ operationIndex,
691
+ message: `Collection "type" must be one of: pair, triple, set, list. Got: "${op.type ?? ""}"`
692
+ });
693
+ }
694
+ if (!op.members || !Array.isArray(op.members)) {
695
+ errors.push({
696
+ code: "VALIDATION_ERROR",
697
+ operationIndex,
698
+ message: 'Collection requires a "members" array'
699
+ });
700
+ } else {
701
+ for (let i = 0; i < op.members.length; i++) {
702
+ if (typeof op.members[i] !== "string") {
703
+ errors.push({
704
+ code: "VALIDATION_ERROR",
705
+ operationIndex,
706
+ message: `Collection member at index ${i} must be a string`
707
+ });
708
+ }
709
+ }
710
+ }
711
+ if (op.type && op.members) {
712
+ const arityError = collectionArityError(
713
+ op.type,
714
+ op.members
715
+ );
716
+ if (arityError) {
717
+ errors.push({
718
+ code: "VALIDATION_ERROR",
719
+ operationIndex,
720
+ message: arityError
721
+ });
722
+ }
723
+ }
724
+ }
725
+ return errors;
726
+ }
727
+ function collectionArityError(tag, members) {
728
+ switch (tag) {
729
+ case "pair":
730
+ return members.length !== 2 ? `Pair requires exactly 2 members, got ${members.length}` : null;
731
+ case "triple":
732
+ return members.length !== 3 ? `Triple requires exactly 3 members, got ${members.length}` : null;
733
+ case "set":
734
+ case "list":
735
+ return members.length < 1 ? `${tag === "set" ? "Set" : "List"} requires at least 1 member, got 0` : null;
736
+ }
737
+ }
738
+
739
+ // ../rules/src/preflight.ts
740
+ function preflightOpDiagnostics2(op, operationIndex) {
741
+ return preflightOpDiagnostics(op, operationIndex);
742
+ }
743
+ function preflightCommitDiagnostics2(operations, options) {
744
+ return preflightCommitDiagnostics(operations);
745
+ }
746
+
747
+ // ../rules/src/reserved-orgs.ts
748
+ var RESERVED_RAW_CONTENT_TOP_LEVEL_NAMES = [
749
+ "_app",
750
+ "_static",
751
+ "api",
752
+ "health",
753
+ "healthz",
754
+ "mcp",
755
+ "readyz",
756
+ "robots.txt",
757
+ "sse",
758
+ "trpc",
759
+ "version"
760
+ ];
761
+ var RESERVED_ORG_NAMES = [
762
+ ...RESERVED_RAW_CONTENT_TOP_LEVEL_NAMES,
763
+ "admin",
764
+ "billing",
765
+ "blog",
766
+ "docs",
767
+ "help",
768
+ "login",
769
+ "public",
770
+ "settings",
771
+ "signup",
772
+ "status",
773
+ "support",
774
+ "system",
775
+ "warmhub",
776
+ "www"
777
+ ];
778
+ new Set(RESERVED_ORG_NAMES);
779
+
780
+ // ../rules/src/shape-field-key.ts
781
+ function foldFieldName(name) {
782
+ const stripped = name.endsWith("?") ? name.slice(0, -1) : name;
783
+ return stripped.toLowerCase();
784
+ }
785
+ function foldFieldPath(path) {
786
+ return path.split(".").map((segment) => foldFieldName(segment)).join(".");
787
+ }
788
+
789
+ // ../rules/src/shape-types.ts
790
+ var BASE_PRIMITIVE_TYPES = [
791
+ "number",
792
+ "string",
793
+ "boolean",
794
+ "wref",
795
+ "array"
796
+ ];
797
+ var BASE_PRIMITIVE_TYPE_SET = new Set(BASE_PRIMITIVE_TYPES);
798
+ var VALID_PRIMITIVE_TYPES = /* @__PURE__ */ new Set([
799
+ "number",
783
800
  "string",
784
801
  "boolean",
785
802
  "wref",
@@ -7129,7 +7146,7 @@ var RE2JS = class RE2JS2 {
7129
7146
  }
7130
7147
  };
7131
7148
 
7132
- // ../rules/src/shape-validation.ts
7149
+ // ../rules/src/shape-validation-types.ts
7133
7150
  var MAX_CONTENT_FIELD_BYTES = 64 * 1024;
7134
7151
  var MAX_INDEXABLE_SCALAR_FIELDS_PER_SHAPE = 256;
7135
7152
  var UNSUPPORTED_REGEX_SYNTAX_REASON = "uses unsupported regex syntax";
@@ -7206,162 +7223,301 @@ function normalizeOptionalTypeSpec(spec) {
7206
7223
  }
7207
7224
  return { spec, optionalByType: false };
7208
7225
  }
7209
- function validationContext(options = {}) {
7210
- return {
7211
- allowUnsafeFieldNamePaths: new Set(options.allowUnsafeFieldNamePaths ?? [])
7212
- };
7213
- }
7214
- function isAllowedLegacyUnsafeFieldPath(context, path) {
7215
- return context.allowUnsafeFieldNamePaths.has(path);
7226
+
7227
+ // ../rules/src/shape-validation-runtime.ts
7228
+ function validateAgainstShape(data, shapeFields) {
7229
+ return compileShapeValidator(shapeFields)(data);
7216
7230
  }
7217
- function validateTypedFieldObject(path, identityPath, spec, errors, context) {
7218
- const typeValue = spec.type;
7219
- if (typeof typeValue !== "string") {
7220
- errors.push(
7221
- `Invalid typed field object at "${path}": "type" must be a string`
7222
- );
7223
- return;
7231
+ function declaredFieldNames(shapeFields) {
7232
+ const declared = /* @__PURE__ */ new Set();
7233
+ for (const key of Object.keys(shapeFields)) {
7234
+ declared.add(key.endsWith("?") ? key.slice(0, -1) : key);
7224
7235
  }
7225
- if (!VALID_PRIMITIVE_TYPES.has(typeValue)) {
7226
- errors.push(
7227
- `Invalid type at "${path}.type": "${escapeFieldNameForDisplay(typeValue)}" (expected number|string|boolean|wref|array, optionally with ? suffix)`
7228
- );
7229
- return;
7236
+ return declared;
7237
+ }
7238
+ function buildUndeclaredFieldsWarningFromDeclaredSet(data, declared) {
7239
+ const undeclaredFields = [];
7240
+ let totalUndeclared = 0;
7241
+ for (const key of Object.keys(data)) {
7242
+ if (declared.has(key)) continue;
7243
+ totalUndeclared++;
7244
+ if (undeclaredFields.length < MAX_UNDECLARED_FIELDS_REPORTED) {
7245
+ undeclaredFields.push(key);
7246
+ }
7230
7247
  }
7231
- if ("description" in spec && typeof spec.description !== "string") {
7232
- errors.push(
7233
- `Invalid typed field object at "${path}": "description" must be a string`
7234
- );
7248
+ if (totalUndeclared === 0) return void 0;
7249
+ if (totalUndeclared > MAX_UNDECLARED_FIELDS_REPORTED) {
7250
+ return {
7251
+ undeclaredFields,
7252
+ undeclaredFieldsTruncated: true,
7253
+ totalUndeclared
7254
+ };
7235
7255
  }
7236
- const baseType = typeValue.endsWith("?") ? typeValue.slice(0, -1) : typeValue;
7237
- for (const key of Object.keys(spec)) {
7238
- if (key === "type" || key === "description") continue;
7239
- const safeKey = escapeFieldNameForDisplay(key);
7240
- if (baseType === "string") {
7241
- if (!STRING_CONSTRAINT_KEYS.has(key)) {
7242
- errors.push(
7243
- `Constraint "${safeKey}" at "${path}" is not valid for type "string"`
7244
- );
7245
- }
7246
- } else if (baseType === "number") {
7247
- if (!NUMBER_CONSTRAINT_KEYS.has(key)) {
7248
- errors.push(
7249
- `Constraint "${safeKey}" at "${path}" is not valid for type "number"`
7250
- );
7251
- }
7252
- } else if (baseType === "wref") {
7253
- if (!WREF_CONSTRAINT_KEYS.has(key)) {
7254
- errors.push(
7255
- `Constraint "${safeKey}" at "${path}" is not valid for type "wref"`
7256
- );
7257
- }
7258
- } else if (baseType === "array") {
7259
- if (!ARRAY_CONSTRAINT_KEYS.has(key)) {
7260
- errors.push(
7261
- `Constraint "${safeKey}" at "${path}" is not valid for type "array"`
7262
- );
7256
+ return { undeclaredFields };
7257
+ }
7258
+ var MAX_UNDECLARED_FIELDS_REPORTED = 500;
7259
+ function compileShapeValidator(shapeFields) {
7260
+ const compiledFields = compileObjectFields(shapeFields);
7261
+ const declared = declaredFieldNames(shapeFields);
7262
+ return (data) => {
7263
+ const errors = [];
7264
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
7265
+ return { valid: false, errors: ["Data must be a plain object"] };
7266
+ }
7267
+ for (const field of compiledFields) {
7268
+ const value = data[field.fieldName];
7269
+ if (value === void 0 || value === null) {
7270
+ if (!field.isOptional) {
7271
+ errors.push(`Missing required field: "${field.displayName}"`);
7272
+ }
7273
+ continue;
7263
7274
  }
7264
- } else {
7265
- errors.push(
7266
- `Constraint "${safeKey}" at "${path}" is not valid for type "${baseType}"`
7267
- );
7275
+ field.validate(field.displayName, value, errors);
7268
7276
  }
7277
+ validatePersistedStringFieldLimits(data, shapeFields, errors);
7278
+ const warnings = buildUndeclaredFieldsWarningFromDeclaredSet(data, declared);
7279
+ if (errors.length > 0) {
7280
+ return warnings ? { valid: false, errors, warnings } : { valid: false, errors };
7281
+ }
7282
+ return warnings ? { valid: true, warnings } : { valid: true };
7283
+ };
7284
+ }
7285
+ function validatePersistedStringFieldLimits(value, typeDef, errors, path) {
7286
+ const normalized = normalizeOptionalTypeSpec(typeDef).spec;
7287
+ if (typeof value === "string") {
7288
+ if (isDeclaredStringType(normalized) || isDeclaredWrefType(normalized))
7289
+ return;
7290
+ if (normalized !== void 0) return;
7291
+ assertContentFieldWithinLimit(path ?? "<value>", value, errors);
7292
+ return;
7269
7293
  }
7270
- if ("minLength" in spec && (typeof spec.minLength !== "number" || !Number.isFinite(spec.minLength) || spec.minLength < 0 || !Number.isInteger(spec.minLength))) {
7271
- errors.push(
7272
- `"minLength" at "${path}" must be a non-negative finite integer`
7273
- );
7294
+ if (Array.isArray(value)) {
7295
+ const elementType = arrayElementType(normalized);
7296
+ if (normalized !== void 0 && elementType === void 0) return;
7297
+ for (let i = 0; i < value.length; i++) {
7298
+ validatePersistedStringFieldLimits(
7299
+ value[i],
7300
+ elementType,
7301
+ errors,
7302
+ `${path ?? "<value>"}[${i}]`
7303
+ );
7304
+ }
7305
+ return;
7274
7306
  }
7275
- if ("maxLength" in spec && (typeof spec.maxLength !== "number" || !Number.isFinite(spec.maxLength) || spec.maxLength < 0 || !Number.isInteger(spec.maxLength))) {
7276
- errors.push(
7277
- `"maxLength" at "${path}" must be a non-negative finite integer`
7307
+ if (!isPlainObject(value)) return;
7308
+ const fields = nestedObjectFields(normalized);
7309
+ if (normalized !== void 0 && fields === void 0) return;
7310
+ for (const [key, nestedValue] of Object.entries(value)) {
7311
+ validatePersistedStringFieldLimits(
7312
+ nestedValue,
7313
+ fields?.get(key),
7314
+ errors,
7315
+ path ? joinFieldPath(path, key) : escapeFieldNameForDisplay(key)
7278
7316
  );
7279
7317
  }
7280
- if ("minLength" in spec && "maxLength" in spec && typeof spec.minLength === "number" && typeof spec.maxLength === "number" && spec.minLength > spec.maxLength) {
7281
- errors.push(`"minLength" at "${path}" cannot exceed "maxLength"`);
7318
+ }
7319
+ function isDeclaredStringType(typeDef) {
7320
+ if (typeDef === "string") return true;
7321
+ return isTypeSpecObject(typeDef) && typeDef.type === "string";
7322
+ }
7323
+ function isDeclaredWrefType(typeDef) {
7324
+ if (typeDef === "wref") return true;
7325
+ return isTypeSpecObject(typeDef) && typeDef.type === "wref";
7326
+ }
7327
+ function arrayElementType(typeDef) {
7328
+ if (Array.isArray(typeDef)) return typeDef[0];
7329
+ if (isTypeSpecObject(typeDef) && typeDef.type === "array")
7330
+ return typeDef.items;
7331
+ return void 0;
7332
+ }
7333
+ function nestedObjectFields(typeDef) {
7334
+ if (!isPlainObject(typeDef) || isTypeSpecObject(typeDef)) return void 0;
7335
+ const fields = /* @__PURE__ */ new Map();
7336
+ for (const [key, value] of Object.entries(typeDef)) {
7337
+ fields.set(key.endsWith("?") ? key.slice(0, -1) : key, value);
7282
7338
  }
7283
- if (baseType === "string" && typeof spec.minLength === "number" && Number.isFinite(spec.minLength) && Number.isInteger(spec.minLength) && spec.minLength > MAX_CONTENT_FIELD_BYTES) {
7284
- errors.push(
7285
- `"minLength" at "${path}" cannot exceed ${MAX_CONTENT_FIELD_BYTES}`
7286
- );
7339
+ return fields;
7340
+ }
7341
+ function compileObjectFields(shapeFields) {
7342
+ return Object.entries(shapeFields).map(([fieldSpec, rawTypeDef]) => {
7343
+ const isOptionalByKey = fieldSpec.endsWith("?");
7344
+ const fieldName = isOptionalByKey ? fieldSpec.slice(0, -1) : fieldSpec;
7345
+ const normalized = normalizeOptionalTypeSpec(rawTypeDef);
7346
+ return {
7347
+ fieldName,
7348
+ displayName: escapeFieldNameForDisplay(fieldName),
7349
+ isOptional: isOptionalByKey || normalized.optionalByType,
7350
+ validate: compileTypeValidator(normalized.spec)
7351
+ };
7352
+ });
7353
+ }
7354
+ function compileTypeValidator(typeDef) {
7355
+ if (typeof typeDef === "string") {
7356
+ return (path, value, errors) => validatePrimitiveField(path, value, typeDef, errors);
7287
7357
  }
7288
- if (baseType === "string" && typeof spec.maxLength === "number" && Number.isFinite(spec.maxLength) && Number.isInteger(spec.maxLength) && spec.maxLength > MAX_CONTENT_FIELD_BYTES) {
7289
- errors.push(
7290
- `"maxLength" at "${path}" cannot exceed ${MAX_CONTENT_FIELD_BYTES}`
7291
- );
7358
+ if (Array.isArray(typeDef)) {
7359
+ const elementValidator = typeDef.length > 0 ? compileTypeValidator(normalizeOptionalTypeSpec(typeDef[0]).spec) : void 0;
7360
+ return (path, value, errors) => {
7361
+ if (!Array.isArray(value)) {
7362
+ errors.push(`Field "${path}" expected array, got ${typeof value}`);
7363
+ } else if (elementValidator) {
7364
+ for (let i = 0; i < value.length; i++) {
7365
+ elementValidator(`${path}[${i}]`, value[i], errors);
7366
+ }
7367
+ }
7368
+ };
7292
7369
  }
7293
- if ("pattern" in spec && typeof spec.pattern !== "string") {
7294
- errors.push(`"pattern" at "${path}" must be a string`);
7370
+ if (isTypeSpecObject(typeDef)) {
7371
+ const baseType = typeDef.type.endsWith("?") ? typeDef.type.slice(0, -1) : typeDef.type;
7372
+ if (baseType === "array") {
7373
+ const elementValidator = "items" in typeDef && typeDef.items != null ? compileTypeValidator(normalizeOptionalTypeSpec(typeDef.items).spec) : void 0;
7374
+ return (path, value, errors) => {
7375
+ if (!Array.isArray(value)) {
7376
+ errors.push(`Field "${path}" expected array, got ${typeof value}`);
7377
+ return;
7378
+ }
7379
+ if (typeof typeDef.minItems === "number" && value.length < typeDef.minItems) {
7380
+ errors.push(
7381
+ `Field "${path}" has ${value.length} items, minimum is ${typeDef.minItems}`
7382
+ );
7383
+ }
7384
+ if (typeof typeDef.maxItems === "number" && value.length > typeDef.maxItems) {
7385
+ errors.push(
7386
+ `Field "${path}" has ${value.length} items, maximum is ${typeDef.maxItems}`
7387
+ );
7388
+ }
7389
+ if (elementValidator) {
7390
+ for (let i = 0; i < value.length; i++) {
7391
+ elementValidator(`${path}[${i}]`, value[i], errors);
7392
+ }
7393
+ }
7394
+ };
7395
+ }
7396
+ const validateConstraintsCompiled = compileConstraintsValidator(typeDef);
7397
+ return (path, value, errors) => {
7398
+ validatePrimitiveField(path, value, typeDef.type, errors);
7399
+ validateConstraintsCompiled(path, value, errors);
7400
+ };
7295
7401
  }
7296
- if ("pattern" in spec && typeof spec.pattern === "string") {
7297
- if (nativeRegexSyntaxError(spec.pattern)) {
7298
- errors.push(`"pattern" at "${path}" is not a valid regular expression`);
7299
- } else {
7300
- const unsupportedReason = unsupportedPortablePatternReason(spec.pattern);
7301
- if (unsupportedReason) {
7302
- errors.push(`"pattern" at "${path}" ${unsupportedReason}`);
7402
+ if (isPlainObject(typeDef)) {
7403
+ const compiledFields = compileObjectFields(typeDef);
7404
+ return (path, value, errors) => {
7405
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
7406
+ errors.push(`Field "${path}" expected object, got ${typeof value}`);
7407
+ return;
7303
7408
  }
7304
- }
7409
+ const objValue = value;
7410
+ for (const field of compiledFields) {
7411
+ const subValue = objValue[field.fieldName];
7412
+ const subPath = joinFieldPath(path, field.fieldName);
7413
+ if (subValue === void 0 || subValue === null) {
7414
+ if (!field.isOptional) {
7415
+ errors.push(`Missing required field: "${subPath}"`);
7416
+ }
7417
+ continue;
7418
+ }
7419
+ field.validate(subPath, subValue, errors);
7420
+ }
7421
+ };
7305
7422
  }
7306
- if ("enum" in spec) {
7307
- if (!Array.isArray(spec.enum) || spec.enum.length === 0 || !spec.enum.every((v) => typeof v === "string")) {
7308
- errors.push(`"enum" at "${path}" must be a non-empty array of strings`);
7309
- } else if (baseType === "string") {
7310
- for (const value of spec.enum) {
7311
- const byteLength = utf8ByteLength(value);
7312
- if (byteLength > MAX_CONTENT_FIELD_BYTES) {
7423
+ return () => {
7424
+ };
7425
+ }
7426
+ function compileConstraintsValidator(spec) {
7427
+ const pattern = typeof spec.pattern === "string" ? spec.pattern : void 0;
7428
+ const patternConstraint = pattern === void 0 ? void 0 : compilePatternConstraint(pattern);
7429
+ return (path, value, errors) => {
7430
+ if (typeof value === "string") {
7431
+ if (typeof spec.minLength === "number" && value.length < spec.minLength) {
7432
+ errors.push(
7433
+ `Field "${path}" length ${value.length} is below minimum ${spec.minLength}`
7434
+ );
7435
+ }
7436
+ if (typeof spec.maxLength === "number" && value.length > spec.maxLength) {
7437
+ errors.push(
7438
+ `Field "${path}" length ${value.length} exceeds maximum ${spec.maxLength}`
7439
+ );
7440
+ }
7441
+ if (pattern !== void 0) {
7442
+ const safePattern = escapeFieldNameForDisplay(pattern);
7443
+ if (patternConstraint?.kind === "unsupported") {
7313
7444
  errors.push(
7314
- `"enum" value at "${path}" is ${byteLength} bytes; string enum values cannot exceed ${MAX_CONTENT_FIELD_BYTES} bytes`
7445
+ `Field "${path}" has unsupported pattern "${safePattern}"`
7315
7446
  );
7316
- break;
7447
+ } else if (patternConstraint?.kind === "invalid") {
7448
+ errors.push(
7449
+ `Field "${path}" has invalid pattern "${safePattern}" \u2014 skipping regex check`
7450
+ );
7451
+ } else if (patternConstraint?.kind === "compiled" && !patternConstraint.regex.test(value)) {
7452
+ errors.push(`Field "${path}" does not match pattern "${safePattern}"`);
7317
7453
  }
7318
7454
  }
7455
+ if (Array.isArray(spec.enum) && !spec.enum.includes(value)) {
7456
+ errors.push(
7457
+ `Field "${path}" value "${escapeFieldNameForDisplay(String(value))}" is not in allowed values: ${spec.enum.map((entry) => escapeFieldNameForDisplay(String(entry))).join(", ")}`
7458
+ );
7459
+ }
7319
7460
  }
7320
- }
7321
- if ("minimum" in spec && (typeof spec.minimum !== "number" || !Number.isFinite(spec.minimum))) {
7322
- errors.push(`"minimum" at "${path}" must be a finite number`);
7323
- }
7324
- if ("maximum" in spec && (typeof spec.maximum !== "number" || !Number.isFinite(spec.maximum))) {
7325
- errors.push(`"maximum" at "${path}" must be a finite number`);
7326
- }
7327
- if ("minimum" in spec && "maximum" in spec && typeof spec.minimum === "number" && typeof spec.maximum === "number" && spec.minimum > spec.maximum) {
7328
- errors.push(`"minimum" at "${path}" cannot exceed "maximum"`);
7329
- }
7330
- if ("integer" in spec && typeof spec.integer !== "boolean") {
7331
- errors.push(`"integer" at "${path}" must be a boolean`);
7332
- }
7333
- if ("shape" in spec) {
7334
- if (typeof spec.shape !== "string") {
7335
- errors.push(`"shape" at "${path}" must be a string (shape name)`);
7336
- } else if (spec.shape === "") {
7337
- errors.push(`"shape" at "${path}" must not be empty`);
7461
+ if (typeof value === "number") {
7462
+ if (typeof spec.minimum === "number" && value < spec.minimum) {
7463
+ errors.push(
7464
+ `Field "${path}" value ${value} is below minimum ${spec.minimum}`
7465
+ );
7466
+ }
7467
+ if (typeof spec.maximum === "number" && value > spec.maximum) {
7468
+ errors.push(
7469
+ `Field "${path}" value ${value} exceeds maximum ${spec.maximum}`
7470
+ );
7471
+ }
7472
+ if (spec.integer === true && !Number.isInteger(value)) {
7473
+ errors.push(`Field "${path}" must be an integer, got ${value}`);
7474
+ }
7338
7475
  }
7476
+ };
7477
+ }
7478
+ function compilePatternConstraint(pattern) {
7479
+ if (nativeRegexSyntaxError(pattern)) {
7480
+ return { kind: "invalid" };
7339
7481
  }
7340
- if ("minItems" in spec && (typeof spec.minItems !== "number" || !Number.isFinite(spec.minItems) || spec.minItems < 0 || !Number.isInteger(spec.minItems))) {
7341
- errors.push(`"minItems" at "${path}" must be a non-negative finite integer`);
7342
- }
7343
- if ("maxItems" in spec && (typeof spec.maxItems !== "number" || !Number.isFinite(spec.maxItems) || spec.maxItems < 0 || !Number.isInteger(spec.maxItems))) {
7344
- errors.push(`"maxItems" at "${path}" must be a non-negative finite integer`);
7345
- }
7346
- if ("minItems" in spec && "maxItems" in spec && typeof spec.minItems === "number" && typeof spec.maxItems === "number" && spec.minItems > spec.maxItems) {
7347
- errors.push(`"minItems" at "${path}" cannot exceed "maxItems"`);
7348
- }
7349
- if (baseType === "array") {
7350
- if (!("items" in spec)) {
7351
- errors.push(
7352
- `Typed array at "${path}" must have an "items" key specifying the element type`
7353
- );
7354
- } else {
7355
- validateTypeSpec(
7356
- `${path}.items`,
7357
- `${identityPath}.items`,
7358
- spec.items,
7359
- errors,
7360
- context
7361
- );
7362
- }
7482
+ const regex = compilePortablePattern(pattern);
7483
+ return regex ? { kind: "compiled", regex } : { kind: "unsupported" };
7484
+ }
7485
+ function validatePrimitiveField(path, value, typeDef, errors) {
7486
+ switch (typeDef) {
7487
+ case "number":
7488
+ if (typeof value !== "number") {
7489
+ errors.push(`Field "${path}" expected number, got ${typeof value}`);
7490
+ }
7491
+ break;
7492
+ case "string":
7493
+ if (typeof value !== "string") {
7494
+ errors.push(`Field "${path}" expected string, got ${typeof value}`);
7495
+ } else {
7496
+ assertContentFieldWithinLimit(path, value, errors);
7497
+ }
7498
+ break;
7499
+ case "wref":
7500
+ if (typeof value !== "string") {
7501
+ errors.push(`Field "${path}" expected string, got ${typeof value}`);
7502
+ }
7503
+ break;
7504
+ case "boolean":
7505
+ if (typeof value !== "boolean") {
7506
+ errors.push(`Field "${path}" expected boolean, got ${typeof value}`);
7507
+ }
7508
+ break;
7509
+ case "array":
7510
+ if (!Array.isArray(value)) {
7511
+ errors.push(`Field "${path}" expected array, got ${typeof value}`);
7512
+ }
7513
+ break;
7363
7514
  }
7364
7515
  }
7516
+
7517
+ // ../rules/src/shape-validation-type-spec.ts
7518
+ function isAllowedLegacyUnsafeFieldPath(context, path) {
7519
+ return context.allowUnsafeFieldNamePaths.has(path);
7520
+ }
7365
7521
  function validateTypeSpec(path, identityPath, spec, errors, context) {
7366
7522
  if (typeof spec === "string") {
7367
7523
  if (!VALID_PRIMITIVE_TYPES.has(spec)) {
@@ -7452,22 +7608,180 @@ function validateTypeSpec(path, identityPath, spec, errors, context) {
7452
7608
  return;
7453
7609
  }
7454
7610
  }
7455
- for (const [key, value] of Object.entries(spec)) {
7456
- const bareKey = stripOptionalMarker(key);
7457
- const verdict = isUnsafeFieldName(bareKey);
7458
- const fieldPath = joinFieldPath(path, key);
7459
- const fieldIdentityPath = joinFieldIdentityPath(identityPath, key);
7460
- if (verdict.unsafe && !isAllowedLegacyUnsafeFieldPath(context, fieldIdentityPath)) {
7461
- errors.push(`Invalid field name at "${fieldPath}": ${verdict.reason}`);
7462
- continue;
7463
- }
7464
- validateTypeSpec(fieldPath, fieldIdentityPath, value, errors, context);
7611
+ for (const [key, value] of Object.entries(spec)) {
7612
+ const bareKey = stripOptionalMarker(key);
7613
+ const verdict = isUnsafeFieldName(bareKey);
7614
+ const fieldPath = joinFieldPath(path, key);
7615
+ const fieldIdentityPath = joinFieldIdentityPath(identityPath, key);
7616
+ if (verdict.unsafe && !isAllowedLegacyUnsafeFieldPath(context, fieldIdentityPath)) {
7617
+ errors.push(`Invalid field name at "${fieldPath}": ${verdict.reason}`);
7618
+ continue;
7619
+ }
7620
+ validateTypeSpec(fieldPath, fieldIdentityPath, value, errors, context);
7621
+ }
7622
+ return;
7623
+ }
7624
+ errors.push(
7625
+ `Invalid type at "${path}": expected string, array, or object, got ${typeof spec}`
7626
+ );
7627
+ }
7628
+ function validateTypedFieldObject(path, identityPath, spec, errors, context) {
7629
+ const typeValue = spec.type;
7630
+ if (typeof typeValue !== "string") {
7631
+ errors.push(
7632
+ `Invalid typed field object at "${path}": "type" must be a string`
7633
+ );
7634
+ return;
7635
+ }
7636
+ if (!VALID_PRIMITIVE_TYPES.has(typeValue)) {
7637
+ errors.push(
7638
+ `Invalid type at "${path}.type": "${escapeFieldNameForDisplay(typeValue)}" (expected number|string|boolean|wref|array, optionally with ? suffix)`
7639
+ );
7640
+ return;
7641
+ }
7642
+ if ("description" in spec && typeof spec.description !== "string") {
7643
+ errors.push(
7644
+ `Invalid typed field object at "${path}": "description" must be a string`
7645
+ );
7646
+ }
7647
+ const baseType = typeValue.endsWith("?") ? typeValue.slice(0, -1) : typeValue;
7648
+ for (const key of Object.keys(spec)) {
7649
+ if (key === "type" || key === "description") continue;
7650
+ const safeKey = escapeFieldNameForDisplay(key);
7651
+ if (baseType === "string") {
7652
+ if (!STRING_CONSTRAINT_KEYS.has(key)) {
7653
+ errors.push(
7654
+ `Constraint "${safeKey}" at "${path}" is not valid for type "string"`
7655
+ );
7656
+ }
7657
+ } else if (baseType === "number") {
7658
+ if (!NUMBER_CONSTRAINT_KEYS.has(key)) {
7659
+ errors.push(
7660
+ `Constraint "${safeKey}" at "${path}" is not valid for type "number"`
7661
+ );
7662
+ }
7663
+ } else if (baseType === "wref") {
7664
+ if (!WREF_CONSTRAINT_KEYS.has(key)) {
7665
+ errors.push(
7666
+ `Constraint "${safeKey}" at "${path}" is not valid for type "wref"`
7667
+ );
7668
+ }
7669
+ } else if (baseType === "array") {
7670
+ if (!ARRAY_CONSTRAINT_KEYS.has(key)) {
7671
+ errors.push(
7672
+ `Constraint "${safeKey}" at "${path}" is not valid for type "array"`
7673
+ );
7674
+ }
7675
+ } else {
7676
+ errors.push(
7677
+ `Constraint "${safeKey}" at "${path}" is not valid for type "${baseType}"`
7678
+ );
7679
+ }
7680
+ }
7681
+ if ("minLength" in spec && (typeof spec.minLength !== "number" || !Number.isFinite(spec.minLength) || spec.minLength < 0 || !Number.isInteger(spec.minLength))) {
7682
+ errors.push(
7683
+ `"minLength" at "${path}" must be a non-negative finite integer`
7684
+ );
7685
+ }
7686
+ if ("maxLength" in spec && (typeof spec.maxLength !== "number" || !Number.isFinite(spec.maxLength) || spec.maxLength < 0 || !Number.isInteger(spec.maxLength))) {
7687
+ errors.push(
7688
+ `"maxLength" at "${path}" must be a non-negative finite integer`
7689
+ );
7690
+ }
7691
+ if ("minLength" in spec && "maxLength" in spec && typeof spec.minLength === "number" && typeof spec.maxLength === "number" && spec.minLength > spec.maxLength) {
7692
+ errors.push(`"minLength" at "${path}" cannot exceed "maxLength"`);
7693
+ }
7694
+ if (baseType === "string" && typeof spec.minLength === "number" && Number.isFinite(spec.minLength) && Number.isInteger(spec.minLength) && spec.minLength > MAX_CONTENT_FIELD_BYTES) {
7695
+ errors.push(
7696
+ `"minLength" at "${path}" cannot exceed ${MAX_CONTENT_FIELD_BYTES}`
7697
+ );
7698
+ }
7699
+ if (baseType === "string" && typeof spec.maxLength === "number" && Number.isFinite(spec.maxLength) && Number.isInteger(spec.maxLength) && spec.maxLength > MAX_CONTENT_FIELD_BYTES) {
7700
+ errors.push(
7701
+ `"maxLength" at "${path}" cannot exceed ${MAX_CONTENT_FIELD_BYTES}`
7702
+ );
7703
+ }
7704
+ if ("pattern" in spec && typeof spec.pattern !== "string") {
7705
+ errors.push(`"pattern" at "${path}" must be a string`);
7706
+ }
7707
+ if ("pattern" in spec && typeof spec.pattern === "string") {
7708
+ if (nativeRegexSyntaxError(spec.pattern)) {
7709
+ errors.push(`"pattern" at "${path}" is not a valid regular expression`);
7710
+ } else {
7711
+ const unsupportedReason = unsupportedPortablePatternReason(spec.pattern);
7712
+ if (unsupportedReason) {
7713
+ errors.push(`"pattern" at "${path}" ${unsupportedReason}`);
7714
+ }
7715
+ }
7716
+ }
7717
+ if ("enum" in spec) {
7718
+ if (!Array.isArray(spec.enum) || spec.enum.length === 0 || !spec.enum.every((v) => typeof v === "string")) {
7719
+ errors.push(`"enum" at "${path}" must be a non-empty array of strings`);
7720
+ } else if (baseType === "string") {
7721
+ for (const value of spec.enum) {
7722
+ const byteLength = utf8ByteLength(value);
7723
+ if (byteLength > MAX_CONTENT_FIELD_BYTES) {
7724
+ errors.push(
7725
+ `"enum" value at "${path}" is ${byteLength} bytes; string enum values cannot exceed ${MAX_CONTENT_FIELD_BYTES} bytes`
7726
+ );
7727
+ break;
7728
+ }
7729
+ }
7730
+ }
7731
+ }
7732
+ if ("minimum" in spec && (typeof spec.minimum !== "number" || !Number.isFinite(spec.minimum))) {
7733
+ errors.push(`"minimum" at "${path}" must be a finite number`);
7734
+ }
7735
+ if ("maximum" in spec && (typeof spec.maximum !== "number" || !Number.isFinite(spec.maximum))) {
7736
+ errors.push(`"maximum" at "${path}" must be a finite number`);
7737
+ }
7738
+ if ("minimum" in spec && "maximum" in spec && typeof spec.minimum === "number" && typeof spec.maximum === "number" && spec.minimum > spec.maximum) {
7739
+ errors.push(`"minimum" at "${path}" cannot exceed "maximum"`);
7740
+ }
7741
+ if ("integer" in spec && typeof spec.integer !== "boolean") {
7742
+ errors.push(`"integer" at "${path}" must be a boolean`);
7743
+ }
7744
+ if ("shape" in spec) {
7745
+ if (typeof spec.shape !== "string") {
7746
+ errors.push(`"shape" at "${path}" must be a string (shape name)`);
7747
+ } else if (spec.shape === "") {
7748
+ errors.push(`"shape" at "${path}" must not be empty`);
7749
+ }
7750
+ }
7751
+ if ("minItems" in spec && (typeof spec.minItems !== "number" || !Number.isFinite(spec.minItems) || spec.minItems < 0 || !Number.isInteger(spec.minItems))) {
7752
+ errors.push(`"minItems" at "${path}" must be a non-negative finite integer`);
7753
+ }
7754
+ if ("maxItems" in spec && (typeof spec.maxItems !== "number" || !Number.isFinite(spec.maxItems) || spec.maxItems < 0 || !Number.isInteger(spec.maxItems))) {
7755
+ errors.push(`"maxItems" at "${path}" must be a non-negative finite integer`);
7756
+ }
7757
+ if ("minItems" in spec && "maxItems" in spec && typeof spec.minItems === "number" && typeof spec.maxItems === "number" && spec.minItems > spec.maxItems) {
7758
+ errors.push(`"minItems" at "${path}" cannot exceed "maxItems"`);
7759
+ }
7760
+ if (baseType === "array") {
7761
+ if (!("items" in spec)) {
7762
+ errors.push(
7763
+ `Typed array at "${path}" must have an "items" key specifying the element type`
7764
+ );
7765
+ } else {
7766
+ validateTypeSpec(
7767
+ `${path}.items`,
7768
+ `${identityPath}.items`,
7769
+ spec.items,
7770
+ errors,
7771
+ context
7772
+ );
7465
7773
  }
7466
- return;
7467
7774
  }
7468
- errors.push(
7469
- `Invalid type at "${path}": expected string, array, or object, got ${typeof spec}`
7470
- );
7775
+ }
7776
+
7777
+ // ../rules/src/shape-validation-definition.ts
7778
+ function validationContext(options = {}) {
7779
+ return {
7780
+ allowUnsafeFieldNamePaths: new Set(options.allowUnsafeFieldNamePaths ?? [])
7781
+ };
7782
+ }
7783
+ function isAllowedLegacyUnsafeFieldPath2(context, path) {
7784
+ return context.allowUnsafeFieldNamePaths.has(path);
7471
7785
  }
7472
7786
  var ALLOWED_SHAPE_KEYS = /* @__PURE__ */ new Set(["fields", "description"]);
7473
7787
  function collectIndexableShapeFieldPaths(fields, prefix = "") {
@@ -7502,7 +7816,7 @@ function validateFieldsRecord(fields, prefix, errors, context, identityPrefix =
7502
7816
  const verdict = isUnsafeFieldName(bareKey);
7503
7817
  const fieldPath = joinFieldPath(prefix, key);
7504
7818
  const fieldIdentityPath = joinFieldIdentityPath(identityPrefix, key);
7505
- if (verdict.unsafe && !isAllowedLegacyUnsafeFieldPath(context, fieldIdentityPath)) {
7819
+ if (verdict.unsafe && !isAllowedLegacyUnsafeFieldPath2(context, fieldIdentityPath)) {
7506
7820
  errors.push(`Invalid field name at "${fieldPath}": ${verdict.reason}`);
7507
7821
  continue;
7508
7822
  }
@@ -7530,8 +7844,9 @@ function validateFieldsRecord(fields, prefix, errors, context, identityPrefix =
7530
7844
  }
7531
7845
  function collectUnsafeFieldNamePathsInTypeSpec(path, spec, paths) {
7532
7846
  if (Array.isArray(spec)) {
7533
- if (spec.length === 1)
7847
+ if (spec.length === 1) {
7534
7848
  collectUnsafeFieldNamePathsInTypeSpec(`${path}[]`, spec[0], paths);
7849
+ }
7535
7850
  return;
7536
7851
  }
7537
7852
  if (isTypeSpecObject(spec)) {
@@ -7576,360 +7891,72 @@ function collectUnsafeShapeFieldNamePaths(data) {
7576
7891
  paths
7577
7892
  );
7578
7893
  return paths;
7579
- }
7580
- function validateShapeDefinition(data, options = {}) {
7581
- if (typeof data !== "object" || data === null || Array.isArray(data)) {
7582
- return { valid: false, errors: ["Shape definition must be a plain object"] };
7583
- }
7584
- const obj = data;
7585
- const errors = [];
7586
- for (const key of Object.keys(obj)) {
7587
- if (!ALLOWED_SHAPE_KEYS.has(key)) {
7588
- errors.push(
7589
- `Unsupported shape definition key "${escapeFieldNameForDisplay(key)}"`
7590
- );
7591
- }
7592
- }
7593
- if (!("fields" in obj)) {
7594
- return {
7595
- valid: false,
7596
- errors: ['Shape definition must have a "fields" property']
7597
- };
7598
- }
7599
- const fields = obj.fields;
7600
- if (typeof fields !== "object" || fields === null || Array.isArray(fields)) {
7601
- return {
7602
- valid: false,
7603
- errors: ['"fields" must be a plain object mapping field names to types']
7604
- };
7605
- }
7606
- const context = validationContext(options);
7607
- validateFieldsRecord(
7608
- fields,
7609
- "fields",
7610
- errors,
7611
- context
7612
- );
7613
- const indexableFieldPaths = collectIndexableShapeFieldPaths(
7614
- fields
7615
- );
7616
- const indexableFieldCount = indexableFieldPaths.length;
7617
- const seenIndexableFieldPaths = /* @__PURE__ */ new Set();
7618
- const duplicateIndexableFieldPaths = /* @__PURE__ */ new Set();
7619
- for (const fieldPath of indexableFieldPaths) {
7620
- const foldedPath = foldFieldPath(fieldPath);
7621
- if (seenIndexableFieldPaths.has(foldedPath)) {
7622
- duplicateIndexableFieldPaths.add(foldedPath);
7623
- continue;
7624
- }
7625
- seenIndexableFieldPaths.add(foldedPath);
7626
- }
7627
- for (const fieldPath of duplicateIndexableFieldPaths) {
7628
- errors.push(
7629
- `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`
7630
- );
7631
- }
7632
- if (indexableFieldCount > MAX_INDEXABLE_SCALAR_FIELDS_PER_SHAPE) {
7633
- errors.push(
7634
- `Shape declares ${indexableFieldCount} indexable scalar fields; maximum is ${MAX_INDEXABLE_SCALAR_FIELDS_PER_SHAPE}`
7635
- );
7636
- }
7637
- if ("description" in obj && typeof obj.description !== "string") {
7638
- errors.push('"description" must be a string');
7639
- }
7640
- validatePersistedStringFieldLimits(obj, void 0, errors);
7641
- if (errors.length > 0) {
7642
- return { valid: false, errors };
7643
- }
7644
- return { valid: true };
7645
- }
7646
- function validateAgainstShape(data, shapeFields) {
7647
- return compileShapeValidator(shapeFields)(data);
7648
- }
7649
- function declaredFieldNames(shapeFields) {
7650
- const declared = /* @__PURE__ */ new Set();
7651
- for (const key of Object.keys(shapeFields)) {
7652
- declared.add(key.endsWith("?") ? key.slice(0, -1) : key);
7653
- }
7654
- return declared;
7655
- }
7656
- function buildUndeclaredFieldsWarningFromDeclaredSet(data, declared) {
7657
- const undeclaredFields = [];
7658
- let totalUndeclared = 0;
7659
- for (const key of Object.keys(data)) {
7660
- if (declared.has(key)) continue;
7661
- totalUndeclared++;
7662
- if (undeclaredFields.length < MAX_UNDECLARED_FIELDS_REPORTED) {
7663
- undeclaredFields.push(key);
7664
- }
7665
- }
7666
- if (totalUndeclared === 0) return void 0;
7667
- if (totalUndeclared > MAX_UNDECLARED_FIELDS_REPORTED) {
7668
- return {
7669
- undeclaredFields,
7670
- undeclaredFieldsTruncated: true,
7671
- totalUndeclared
7672
- };
7673
- }
7674
- return { undeclaredFields };
7675
- }
7676
- var MAX_UNDECLARED_FIELDS_REPORTED = 500;
7677
- function compileShapeValidator(shapeFields) {
7678
- const compiledFields = compileObjectFields(shapeFields);
7679
- const declared = declaredFieldNames(shapeFields);
7680
- return (data) => {
7681
- const errors = [];
7682
- if (typeof data !== "object" || data === null || Array.isArray(data)) {
7683
- return { valid: false, errors: ["Data must be a plain object"] };
7684
- }
7685
- for (const field of compiledFields) {
7686
- const value = data[field.fieldName];
7687
- if (value === void 0 || value === null) {
7688
- if (!field.isOptional) {
7689
- errors.push(`Missing required field: "${field.displayName}"`);
7690
- }
7691
- continue;
7692
- }
7693
- field.validate(field.displayName, value, errors);
7694
- }
7695
- validatePersistedStringFieldLimits(data, shapeFields, errors);
7696
- const warnings = buildUndeclaredFieldsWarningFromDeclaredSet(data, declared);
7697
- if (errors.length > 0) {
7698
- return warnings ? { valid: false, errors, warnings } : { valid: false, errors };
7699
- }
7700
- return warnings ? { valid: true, warnings } : { valid: true };
7701
- };
7702
- }
7703
- function validatePersistedStringFieldLimits(value, typeDef, errors, path) {
7704
- const normalized = normalizeOptionalTypeSpec(typeDef).spec;
7705
- if (typeof value === "string") {
7706
- if (isDeclaredStringType(normalized) || isDeclaredWrefType(normalized))
7707
- return;
7708
- if (normalized !== void 0) return;
7709
- assertContentFieldWithinLimit(path ?? "<value>", value, errors);
7710
- return;
7711
- }
7712
- if (Array.isArray(value)) {
7713
- const elementType = arrayElementType(normalized);
7714
- if (normalized !== void 0 && elementType === void 0) return;
7715
- for (let i = 0; i < value.length; i++) {
7716
- validatePersistedStringFieldLimits(
7717
- value[i],
7718
- elementType,
7719
- errors,
7720
- `${path ?? "<value>"}[${i}]`
7721
- );
7722
- }
7723
- return;
7724
- }
7725
- if (!isPlainObject(value)) return;
7726
- const fields = nestedObjectFields(normalized);
7727
- if (normalized !== void 0 && fields === void 0) return;
7728
- for (const [key, nestedValue] of Object.entries(value)) {
7729
- validatePersistedStringFieldLimits(
7730
- nestedValue,
7731
- fields?.get(key),
7732
- errors,
7733
- path ? joinFieldPath(path, key) : escapeFieldNameForDisplay(key)
7734
- );
7735
- }
7736
- }
7737
- function isDeclaredStringType(typeDef) {
7738
- if (typeDef === "string") return true;
7739
- return isTypeSpecObject(typeDef) && typeDef.type === "string";
7740
- }
7741
- function isDeclaredWrefType(typeDef) {
7742
- if (typeDef === "wref") return true;
7743
- return isTypeSpecObject(typeDef) && typeDef.type === "wref";
7744
- }
7745
- function arrayElementType(typeDef) {
7746
- if (Array.isArray(typeDef)) return typeDef[0];
7747
- if (isTypeSpecObject(typeDef) && typeDef.type === "array")
7748
- return typeDef.items;
7749
- return void 0;
7750
- }
7751
- function nestedObjectFields(typeDef) {
7752
- if (!isPlainObject(typeDef) || isTypeSpecObject(typeDef)) return void 0;
7753
- const fields = /* @__PURE__ */ new Map();
7754
- for (const [key, value] of Object.entries(typeDef)) {
7755
- fields.set(key.endsWith("?") ? key.slice(0, -1) : key, value);
7756
- }
7757
- return fields;
7758
- }
7759
- function compileObjectFields(shapeFields) {
7760
- return Object.entries(shapeFields).map(([fieldSpec, rawTypeDef]) => {
7761
- const isOptionalByKey = fieldSpec.endsWith("?");
7762
- const fieldName = isOptionalByKey ? fieldSpec.slice(0, -1) : fieldSpec;
7763
- const normalized = normalizeOptionalTypeSpec(rawTypeDef);
7764
- return {
7765
- fieldName,
7766
- displayName: escapeFieldNameForDisplay(fieldName),
7767
- isOptional: isOptionalByKey || normalized.optionalByType,
7768
- validate: compileTypeValidator(normalized.spec)
7769
- };
7770
- });
7771
- }
7772
- function compileTypeValidator(typeDef) {
7773
- if (typeof typeDef === "string") {
7774
- return (path, value, errors) => validatePrimitiveField(path, value, typeDef, errors);
7775
- }
7776
- if (Array.isArray(typeDef)) {
7777
- const elementValidator = typeDef.length > 0 ? compileTypeValidator(normalizeOptionalTypeSpec(typeDef[0]).spec) : void 0;
7778
- return (path, value, errors) => {
7779
- if (!Array.isArray(value)) {
7780
- errors.push(`Field "${path}" expected array, got ${typeof value}`);
7781
- } else if (elementValidator) {
7782
- for (let i = 0; i < value.length; i++) {
7783
- elementValidator(`${path}[${i}]`, value[i], errors);
7784
- }
7785
- }
7786
- };
7894
+ }
7895
+ function validateShapeDefinition(data, options = {}) {
7896
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
7897
+ return { valid: false, errors: ["Shape definition must be a plain object"] };
7787
7898
  }
7788
- if (isTypeSpecObject(typeDef)) {
7789
- const baseType = typeDef.type.endsWith("?") ? typeDef.type.slice(0, -1) : typeDef.type;
7790
- if (baseType === "array") {
7791
- const elementValidator = "items" in typeDef && typeDef.items != null ? compileTypeValidator(normalizeOptionalTypeSpec(typeDef.items).spec) : void 0;
7792
- return (path, value, errors) => {
7793
- if (!Array.isArray(value)) {
7794
- errors.push(`Field "${path}" expected array, got ${typeof value}`);
7795
- return;
7796
- }
7797
- if (typeof typeDef.minItems === "number" && value.length < typeDef.minItems) {
7798
- errors.push(
7799
- `Field "${path}" has ${value.length} items, minimum is ${typeDef.minItems}`
7800
- );
7801
- }
7802
- if (typeof typeDef.maxItems === "number" && value.length > typeDef.maxItems) {
7803
- errors.push(
7804
- `Field "${path}" has ${value.length} items, maximum is ${typeDef.maxItems}`
7805
- );
7806
- }
7807
- if (elementValidator) {
7808
- for (let i = 0; i < value.length; i++) {
7809
- elementValidator(`${path}[${i}]`, value[i], errors);
7810
- }
7811
- }
7812
- };
7899
+ const obj = data;
7900
+ const errors = [];
7901
+ for (const key of Object.keys(obj)) {
7902
+ if (!ALLOWED_SHAPE_KEYS.has(key)) {
7903
+ errors.push(
7904
+ `Unsupported shape definition key "${escapeFieldNameForDisplay(key)}"`
7905
+ );
7813
7906
  }
7814
- const validateConstraintsCompiled = compileConstraintsValidator(typeDef);
7815
- return (path, value, errors) => {
7816
- validatePrimitiveField(path, value, typeDef.type, errors);
7817
- validateConstraintsCompiled(path, value, errors);
7907
+ }
7908
+ if (!("fields" in obj)) {
7909
+ return {
7910
+ valid: false,
7911
+ errors: ['Shape definition must have a "fields" property']
7818
7912
  };
7819
7913
  }
7820
- if (isPlainObject(typeDef)) {
7821
- const compiledFields = compileObjectFields(typeDef);
7822
- return (path, value, errors) => {
7823
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
7824
- errors.push(`Field "${path}" expected object, got ${typeof value}`);
7825
- return;
7826
- }
7827
- const objValue = value;
7828
- for (const field of compiledFields) {
7829
- const subValue = objValue[field.fieldName];
7830
- const subPath = joinFieldPath(path, field.fieldName);
7831
- if (subValue === void 0 || subValue === null) {
7832
- if (!field.isOptional) {
7833
- errors.push(`Missing required field: "${subPath}"`);
7834
- }
7835
- continue;
7836
- }
7837
- field.validate(subPath, subValue, errors);
7838
- }
7914
+ const fields = obj.fields;
7915
+ if (typeof fields !== "object" || fields === null || Array.isArray(fields)) {
7916
+ return {
7917
+ valid: false,
7918
+ errors: ['"fields" must be a plain object mapping field names to types']
7839
7919
  };
7840
7920
  }
7841
- return () => {
7842
- };
7843
- }
7844
- function compileConstraintsValidator(spec) {
7845
- const pattern = typeof spec.pattern === "string" ? spec.pattern : void 0;
7846
- const patternConstraint = pattern === void 0 ? void 0 : compilePatternConstraint(pattern);
7847
- return (path, value, errors) => {
7848
- if (typeof value === "string") {
7849
- if (typeof spec.minLength === "number" && value.length < spec.minLength) {
7850
- errors.push(
7851
- `Field "${path}" length ${value.length} is below minimum ${spec.minLength}`
7852
- );
7853
- }
7854
- if (typeof spec.maxLength === "number" && value.length > spec.maxLength) {
7855
- errors.push(
7856
- `Field "${path}" length ${value.length} exceeds maximum ${spec.maxLength}`
7857
- );
7858
- }
7859
- if (pattern !== void 0) {
7860
- const safePattern = escapeFieldNameForDisplay(pattern);
7861
- if (patternConstraint?.kind === "unsupported") {
7862
- errors.push(
7863
- `Field "${path}" has unsupported pattern "${safePattern}"`
7864
- );
7865
- } else if (patternConstraint?.kind === "invalid") {
7866
- errors.push(
7867
- `Field "${path}" has invalid pattern "${safePattern}" \u2014 skipping regex check`
7868
- );
7869
- } else if (patternConstraint?.kind === "compiled" && !patternConstraint.regex.test(value)) {
7870
- errors.push(`Field "${path}" does not match pattern "${safePattern}"`);
7871
- }
7872
- }
7873
- if (Array.isArray(spec.enum) && !spec.enum.includes(value)) {
7874
- errors.push(
7875
- `Field "${path}" value "${escapeFieldNameForDisplay(String(value))}" is not in allowed values: ${spec.enum.map((entry) => escapeFieldNameForDisplay(String(entry))).join(", ")}`
7876
- );
7877
- }
7878
- }
7879
- if (typeof value === "number") {
7880
- if (typeof spec.minimum === "number" && value < spec.minimum) {
7881
- errors.push(
7882
- `Field "${path}" value ${value} is below minimum ${spec.minimum}`
7883
- );
7884
- }
7885
- if (typeof spec.maximum === "number" && value > spec.maximum) {
7886
- errors.push(
7887
- `Field "${path}" value ${value} exceeds maximum ${spec.maximum}`
7888
- );
7889
- }
7890
- if (spec.integer === true && !Number.isInteger(value)) {
7891
- errors.push(`Field "${path}" must be an integer, got ${value}`);
7892
- }
7921
+ const context = validationContext(options);
7922
+ validateFieldsRecord(
7923
+ fields,
7924
+ "fields",
7925
+ errors,
7926
+ context
7927
+ );
7928
+ const indexableFieldPaths = collectIndexableShapeFieldPaths(
7929
+ fields
7930
+ );
7931
+ const indexableFieldCount = indexableFieldPaths.length;
7932
+ const seenIndexableFieldPaths = /* @__PURE__ */ new Set();
7933
+ const duplicateIndexableFieldPaths = /* @__PURE__ */ new Set();
7934
+ for (const fieldPath of indexableFieldPaths) {
7935
+ const foldedPath = foldFieldPath(fieldPath);
7936
+ if (seenIndexableFieldPaths.has(foldedPath)) {
7937
+ duplicateIndexableFieldPaths.add(foldedPath);
7938
+ continue;
7893
7939
  }
7894
- };
7895
- }
7896
- function compilePatternConstraint(pattern) {
7897
- if (nativeRegexSyntaxError(pattern)) {
7898
- return { kind: "invalid" };
7940
+ seenIndexableFieldPaths.add(foldedPath);
7899
7941
  }
7900
- const regex = compilePortablePattern(pattern);
7901
- return regex ? { kind: "compiled", regex } : { kind: "unsupported" };
7902
- }
7903
- function validatePrimitiveField(path, value, typeDef, errors) {
7904
- switch (typeDef) {
7905
- case "number":
7906
- if (typeof value !== "number") {
7907
- errors.push(`Field "${path}" expected number, got ${typeof value}`);
7908
- }
7909
- break;
7910
- case "string":
7911
- if (typeof value !== "string") {
7912
- errors.push(`Field "${path}" expected string, got ${typeof value}`);
7913
- } else {
7914
- assertContentFieldWithinLimit(path, value, errors);
7915
- }
7916
- break;
7917
- case "wref":
7918
- if (typeof value !== "string") {
7919
- errors.push(`Field "${path}" expected string, got ${typeof value}`);
7920
- }
7921
- break;
7922
- case "boolean":
7923
- if (typeof value !== "boolean") {
7924
- errors.push(`Field "${path}" expected boolean, got ${typeof value}`);
7925
- }
7926
- break;
7927
- case "array":
7928
- if (!Array.isArray(value)) {
7929
- errors.push(`Field "${path}" expected array, got ${typeof value}`);
7930
- }
7931
- break;
7942
+ for (const fieldPath of duplicateIndexableFieldPaths) {
7943
+ errors.push(
7944
+ `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`
7945
+ );
7946
+ }
7947
+ if (indexableFieldCount > MAX_INDEXABLE_SCALAR_FIELDS_PER_SHAPE) {
7948
+ errors.push(
7949
+ `Shape declares ${indexableFieldCount} indexable scalar fields; maximum is ${MAX_INDEXABLE_SCALAR_FIELDS_PER_SHAPE}`
7950
+ );
7951
+ }
7952
+ if ("description" in obj && typeof obj.description !== "string") {
7953
+ errors.push('"description" must be a string');
7954
+ }
7955
+ validatePersistedStringFieldLimits(obj, void 0, errors);
7956
+ if (errors.length > 0) {
7957
+ return { valid: false, errors };
7932
7958
  }
7959
+ return { valid: true };
7933
7960
  }
7934
7961
 
7935
7962
  // ../rules/src/system-components/system.ts
@@ -8011,7 +8038,7 @@ var collectionTypes2 = /* @__PURE__ */ new Set([
8011
8038
  "list"
8012
8039
  ]);
8013
8040
  function normalizeBackendCollectionAdd(operation, source) {
8014
- const diagnostics = preflightOpDiagnostics(
8041
+ const diagnostics = preflightOpDiagnostics2(
8015
8042
  {
8016
8043
  operation: "add",
8017
8044
  kind: "collection",
@@ -8166,21 +8193,72 @@ function toBackendStreamOperation(operation) {
8166
8193
  ...skipExisting === true ? { skipExisting } : {}
8167
8194
  };
8168
8195
  }
8169
- if (operation.data === void 0) {
8170
- throw new Error(`${kind} add operation requires 'data'`);
8171
- }
8172
- return {
8173
- operation: "add",
8174
- kind,
8175
- name: operation.name,
8176
- data: operation.data,
8177
- ...skipExisting === true ? { skipExisting } : {}
8178
- };
8179
- }
8196
+ if (operation.data === void 0) {
8197
+ throw new Error(`${kind} add operation requires 'data'`);
8198
+ }
8199
+ return {
8200
+ operation: "add",
8201
+ kind,
8202
+ name: operation.name,
8203
+ data: operation.data,
8204
+ ...skipExisting === true ? { skipExisting } : {}
8205
+ };
8206
+ }
8207
+
8208
+ // src/stream-submit-types.ts
8209
+ var DEFAULT_STREAM_CHUNK_SIZE = DEFAULT_STREAM_APPEND_CHUNK_SIZE;
8210
+ var MAX_STREAM_APPEND_OPERATION_COUNT2 = MAX_STREAM_APPEND_OPERATION_COUNT;
8211
+ var PartialStreamSubmissionError = class extends Error {
8212
+ /**
8213
+ * Stable error code. Always `'PARTIAL_STREAM_SUBMISSION'`.
8214
+ */
8215
+ code = "PARTIAL_STREAM_SUBMISSION";
8216
+ /**
8217
+ * Operations the SDK confirmed completed before the ambiguous or failing
8218
+ * append. The remaining operations may or may not have landed; inspect
8219
+ * repository state before retrying manually.
8220
+ */
8221
+ completedOperations;
8222
+ /**
8223
+ * Underlying error that triggered the partial submission, preserved for
8224
+ * diagnostics. Inspect via `isWarmHubError(err.cause)` / `err.cause.kind`.
8225
+ */
8226
+ cause;
8227
+ constructor(input) {
8228
+ super(
8229
+ "Stream submission failed. A timed-out append may already have landed, so inspect repository state before sending any further operations."
8230
+ );
8231
+ this.name = "WarmHubError";
8232
+ this.cause = input.cause;
8233
+ this.completedOperations = input.completedOperations;
8234
+ }
8235
+ };
8236
+ var AllStreamOperationsFailedError = class extends Error {
8237
+ code = "STREAM_ALL_OPERATIONS_FAILED";
8238
+ result;
8239
+ operations;
8240
+ statusCounts;
8241
+ cause;
8242
+ constructor(result) {
8243
+ const primaryFailure = result.operations.find(
8244
+ (operation) => operation.error !== void 0
8245
+ )?.error;
8246
+ super(
8247
+ primaryFailure ? `All ${result.operationCount} stream operations failed: ${primaryFailure.message}` : `All ${result.operationCount} stream operations failed.`
8248
+ );
8249
+ this.name = "WarmHubError";
8250
+ this.cause = primaryFailure;
8251
+ this.result = result;
8252
+ this.operations = result.operations;
8253
+ this.statusCounts = result.statusCounts ?? {
8254
+ applied: 0,
8255
+ noop: 0,
8256
+ error: result.operationCount
8257
+ };
8258
+ }
8259
+ };
8180
8260
 
8181
- // src/stream-submit.ts
8182
- var DEFAULT_STREAM_CHUNK_SIZE = DEFAULT_STREAM_APPEND_CHUNK_SIZE;
8183
- var MAX_STREAM_APPEND_OPERATION_COUNT2 = MAX_STREAM_APPEND_OPERATION_COUNT;
8261
+ // src/stream-submit-utils.ts
8184
8262
  function streamAppendResultStatus(result) {
8185
8263
  if (result.status === "failed") {
8186
8264
  return "error";
@@ -8232,6 +8310,85 @@ function resolveRetryPolicy(retry) {
8232
8310
  )
8233
8311
  };
8234
8312
  }
8313
+ var DEFINITE_CLIENT_REJECT_CODES = /* @__PURE__ */ new Set([
8314
+ "UNAUTHENTICATED",
8315
+ "FORBIDDEN",
8316
+ "VALIDATION_ERROR",
8317
+ "SHAPE_MISMATCH",
8318
+ "RESERVED_NAME",
8319
+ "ILLEGAL_OP_SEQUENCE",
8320
+ "NOT_FOUND",
8321
+ "KIND_MISMATCH",
8322
+ "CONFLICT",
8323
+ "ALREADY_RETRACTED",
8324
+ "ARCHIVED",
8325
+ // Pre-apply rejections from stream/service.ts: applyRateLimit() and the
8326
+ // unresolved-token check both throw before any operation lands, so the
8327
+ // first-chunk wrapper would discard structured retry/validation detail
8328
+ // (retryAfter, the failing token, etc.).
8329
+ "RATE_LIMITED",
8330
+ "UNRESOLVED_TOKEN"
8331
+ ]);
8332
+ function extractErrorCode(cause) {
8333
+ if (!cause || typeof cause !== "object") return void 0;
8334
+ const direct = cause.code;
8335
+ if (typeof direct === "string") return direct;
8336
+ const data = cause.data;
8337
+ const wh = data?.warmhub?.code;
8338
+ if (typeof wh === "string") return wh;
8339
+ const dc = data?.code;
8340
+ if (typeof dc === "string") return dc;
8341
+ return void 0;
8342
+ }
8343
+ function isDefiniteClientError(cause) {
8344
+ const code = extractErrorCode(cause);
8345
+ return code !== void 0 && DEFINITE_CLIENT_REJECT_CODES.has(code);
8346
+ }
8347
+ function isTransientStreamFailure(cause) {
8348
+ if (isDefiniteClientError(cause)) return false;
8349
+ if (isFetchNetworkTypeError(cause)) return true;
8350
+ if (cause instanceof TypeError) return false;
8351
+ if (cause instanceof SyntaxError) return false;
8352
+ const inner = cause?.cause;
8353
+ if (isFetchNetworkTypeError(inner)) return true;
8354
+ if (inner instanceof TypeError) return false;
8355
+ if (inner instanceof SyntaxError) return false;
8356
+ return true;
8357
+ }
8358
+ function isFetchNetworkTypeError(cause) {
8359
+ return cause instanceof TypeError && /fetch/i.test(cause.message);
8360
+ }
8361
+ function computeBackoffDelayMs(attempt, policy) {
8362
+ const exponential = policy.baseDelayMs * 2 ** Math.max(0, attempt - 1);
8363
+ const jitter = Math.random() * policy.baseDelayMs;
8364
+ return Math.min(policy.maxDelayMs, exponential + jitter);
8365
+ }
8366
+ function sleep(ms) {
8367
+ return new Promise((resolve) => setTimeout(resolve, ms));
8368
+ }
8369
+ var TOKEN_REF_REGEX = /[$#]\d+/;
8370
+ function stringHasTokenRef(value) {
8371
+ return typeof value === "string" && TOKEN_REF_REGEX.test(value);
8372
+ }
8373
+ function structuralValueHasTokenRef(value, seen = /* @__PURE__ */ new WeakSet()) {
8374
+ if (stringHasTokenRef(value)) return true;
8375
+ if (value !== null && typeof value === "object") {
8376
+ if (seen.has(value)) return false;
8377
+ seen.add(value);
8378
+ if (Array.isArray(value)) {
8379
+ return value.some((entry) => structuralValueHasTokenRef(entry, seen));
8380
+ }
8381
+ return Object.values(value).some(
8382
+ (entry) => structuralValueHasTokenRef(entry, seen)
8383
+ );
8384
+ }
8385
+ return false;
8386
+ }
8387
+ function opUsesTokens(op) {
8388
+ return stringHasTokenRef(op.name) || stringHasTokenRef(op.wref) || structuralValueHasTokenRef(op.about) || stringHasTokenRef(op.aboutWref) || structuralValueHasTokenRef(op.members) || structuralValueHasTokenRef(op.data);
8389
+ }
8390
+
8391
+ // src/stream-submit-submit.ts
8235
8392
  var StreamValidationError = class extends Error {
8236
8393
  code = "VALIDATION_ERROR";
8237
8394
  status = 400;
@@ -8240,55 +8397,6 @@ var StreamValidationError = class extends Error {
8240
8397
  this.name = "WarmHubError";
8241
8398
  }
8242
8399
  };
8243
- var PartialStreamSubmissionError = class extends Error {
8244
- /**
8245
- * Stable error code. Always `'PARTIAL_STREAM_SUBMISSION'`.
8246
- */
8247
- code = "PARTIAL_STREAM_SUBMISSION";
8248
- /**
8249
- * Operations the SDK confirmed completed before the ambiguous or failing
8250
- * append. The remaining operations may or may not have landed; inspect
8251
- * repository state before retrying manually.
8252
- */
8253
- completedOperations;
8254
- /**
8255
- * Underlying error that triggered the partial submission, preserved for
8256
- * diagnostics. Inspect via `isWarmHubError(err.cause)` / `err.cause.kind`.
8257
- */
8258
- cause;
8259
- constructor(input) {
8260
- super(
8261
- "Stream submission failed. A timed-out append may already have landed, so inspect repository state before sending any further operations."
8262
- );
8263
- this.name = "WarmHubError";
8264
- this.cause = input.cause;
8265
- this.completedOperations = input.completedOperations;
8266
- }
8267
- };
8268
- var AllStreamOperationsFailedError = class extends Error {
8269
- code = "STREAM_ALL_OPERATIONS_FAILED";
8270
- result;
8271
- operations;
8272
- statusCounts;
8273
- cause;
8274
- constructor(result) {
8275
- const primaryFailure = result.operations.find(
8276
- (operation) => operation.error !== void 0
8277
- )?.error;
8278
- super(
8279
- primaryFailure ? `All ${result.operationCount} stream operations failed: ${primaryFailure.message}` : `All ${result.operationCount} stream operations failed.`
8280
- );
8281
- this.name = "WarmHubError";
8282
- this.cause = primaryFailure;
8283
- this.result = result;
8284
- this.operations = result.operations;
8285
- this.statusCounts = result.statusCounts ?? {
8286
- applied: 0,
8287
- noop: 0,
8288
- error: result.operationCount
8289
- };
8290
- }
8291
- };
8292
8400
  async function submitOperationsViaStream(client, args) {
8293
8401
  if (args.operations.length === 0) {
8294
8402
  throw new StreamValidationError(
@@ -8362,138 +8470,61 @@ async function submitOperationsViaStream(client, args) {
8362
8470
  toSubmittedStreamResult(
8363
8471
  {
8364
8472
  operationCount: chunkResults.length
8365
- },
8366
- args.committer,
8367
- createdByEmail,
8368
- args.message,
8369
- chunkResults,
8370
- operations
8371
- )
8372
- );
8373
- throw new PartialStreamSubmissionError({
8374
- cause,
8375
- completedOperations
8376
- });
8377
- }
8378
- }
8379
- }
8380
- const result = toSubmittedStreamResult(
8381
- {
8382
- operationCount: chunkResults.length
8383
- },
8384
- args.committer,
8385
- createdByEmail,
8386
- args.message,
8387
- chunkResults,
8388
- operations
8389
- );
8390
- if (isAllSubmittedOperationsFailed(result, operations.length)) {
8391
- const failure = new AllStreamOperationsFailedError(result);
8392
- if (sawAmbiguousAttempt) {
8393
- throw new PartialStreamSubmissionError({
8394
- cause: failure,
8395
- completedOperations: completedOperationsFrom(result)
8396
- });
8397
- }
8398
- throw failure;
8399
- }
8400
- return result;
8401
- }
8402
- function isAllSubmittedOperationsFailed(result, submittedOperationCount) {
8403
- if (submittedOperationCount <= 0) {
8404
- return false;
8405
- }
8406
- const inputRowsFailed = /* @__PURE__ */ new Map();
8407
- for (const operation of result.operations) {
8408
- if (operation.opIndex === void 0 || operation.opIndex < 0 || operation.opIndex >= submittedOperationCount) {
8409
- continue;
8410
- }
8411
- inputRowsFailed.set(
8412
- operation.opIndex,
8413
- (inputRowsFailed.get(operation.opIndex) ?? true) && operation.status === "error"
8414
- );
8415
- }
8416
- return inputRowsFailed.size === submittedOperationCount && [...inputRowsFailed.values()].every(Boolean);
8417
- }
8418
- function completedOperationsFrom(result) {
8419
- return result.operations.filter((operation) => operation.status !== "error");
8420
- }
8421
- var DEFINITE_CLIENT_REJECT_CODES = /* @__PURE__ */ new Set([
8422
- "UNAUTHENTICATED",
8423
- "FORBIDDEN",
8424
- "VALIDATION_ERROR",
8425
- "SHAPE_MISMATCH",
8426
- "RESERVED_NAME",
8427
- "ILLEGAL_OP_SEQUENCE",
8428
- "NOT_FOUND",
8429
- "KIND_MISMATCH",
8430
- "CONFLICT",
8431
- "ALREADY_RETRACTED",
8432
- "ARCHIVED",
8433
- // Pre-apply rejections from stream/service.ts: applyRateLimit() and the
8434
- // unresolved-token check both throw before any operation lands, so the
8435
- // first-chunk wrapper would discard structured retry/validation detail
8436
- // (retryAfter, the failing token, etc.).
8437
- "RATE_LIMITED",
8438
- "UNRESOLVED_TOKEN"
8439
- ]);
8440
- function extractErrorCode(cause) {
8441
- if (!cause || typeof cause !== "object") return void 0;
8442
- const direct = cause.code;
8443
- if (typeof direct === "string") return direct;
8444
- const data = cause.data;
8445
- const wh = data?.warmhub?.code;
8446
- if (typeof wh === "string") return wh;
8447
- const dc = data?.code;
8448
- if (typeof dc === "string") return dc;
8449
- return void 0;
8450
- }
8451
- function isDefiniteClientError(cause) {
8452
- const code = extractErrorCode(cause);
8453
- return code !== void 0 && DEFINITE_CLIENT_REJECT_CODES.has(code);
8454
- }
8455
- function isTransientStreamFailure(cause) {
8456
- if (isDefiniteClientError(cause)) return false;
8457
- if (isFetchNetworkTypeError(cause)) return true;
8458
- if (cause instanceof TypeError) return false;
8459
- if (cause instanceof SyntaxError) return false;
8460
- const inner = cause?.cause;
8461
- if (isFetchNetworkTypeError(inner)) return true;
8462
- if (inner instanceof TypeError) return false;
8463
- if (inner instanceof SyntaxError) return false;
8464
- return true;
8465
- }
8466
- function isFetchNetworkTypeError(cause) {
8467
- return cause instanceof TypeError && /fetch/i.test(cause.message);
8468
- }
8469
- function computeBackoffDelayMs(attempt, policy) {
8470
- const exponential = policy.baseDelayMs * 2 ** Math.max(0, attempt - 1);
8471
- const jitter = Math.random() * policy.baseDelayMs;
8472
- return Math.min(policy.maxDelayMs, exponential + jitter);
8473
- }
8474
- function sleep(ms) {
8475
- return new Promise((resolve) => setTimeout(resolve, ms));
8476
- }
8477
- var TOKEN_REF_REGEX = /[$#]\d+/;
8478
- function stringHasTokenRef(value) {
8479
- return typeof value === "string" && TOKEN_REF_REGEX.test(value);
8473
+ },
8474
+ args.committer,
8475
+ createdByEmail,
8476
+ args.message,
8477
+ chunkResults,
8478
+ operations
8479
+ )
8480
+ );
8481
+ throw new PartialStreamSubmissionError({
8482
+ cause,
8483
+ completedOperations
8484
+ });
8485
+ }
8486
+ }
8487
+ }
8488
+ const result = toSubmittedStreamResult(
8489
+ {
8490
+ operationCount: chunkResults.length
8491
+ },
8492
+ args.committer,
8493
+ createdByEmail,
8494
+ args.message,
8495
+ chunkResults,
8496
+ operations
8497
+ );
8498
+ if (isAllSubmittedOperationsFailed(result, operations.length)) {
8499
+ const failure = new AllStreamOperationsFailedError(result);
8500
+ if (sawAmbiguousAttempt) {
8501
+ throw new PartialStreamSubmissionError({
8502
+ cause: failure,
8503
+ completedOperations: completedOperationsFrom(result)
8504
+ });
8505
+ }
8506
+ throw failure;
8507
+ }
8508
+ return result;
8480
8509
  }
8481
- function structuralValueHasTokenRef(value, seen = /* @__PURE__ */ new WeakSet()) {
8482
- if (stringHasTokenRef(value)) return true;
8483
- if (value !== null && typeof value === "object") {
8484
- if (seen.has(value)) return false;
8485
- seen.add(value);
8486
- if (Array.isArray(value)) {
8487
- return value.some((entry) => structuralValueHasTokenRef(entry, seen));
8510
+ function isAllSubmittedOperationsFailed(result, submittedOperationCount) {
8511
+ if (submittedOperationCount <= 0) {
8512
+ return false;
8513
+ }
8514
+ const inputRowsFailed = /* @__PURE__ */ new Map();
8515
+ for (const operation of result.operations) {
8516
+ if (operation.opIndex === void 0 || operation.opIndex < 0 || operation.opIndex >= submittedOperationCount) {
8517
+ continue;
8488
8518
  }
8489
- return Object.values(value).some(
8490
- (entry) => structuralValueHasTokenRef(entry, seen)
8519
+ inputRowsFailed.set(
8520
+ operation.opIndex,
8521
+ (inputRowsFailed.get(operation.opIndex) ?? true) && operation.status === "error"
8491
8522
  );
8492
8523
  }
8493
- return false;
8524
+ return inputRowsFailed.size === submittedOperationCount && [...inputRowsFailed.values()].every(Boolean);
8494
8525
  }
8495
- function opUsesTokens(op) {
8496
- return stringHasTokenRef(op.name) || stringHasTokenRef(op.wref) || structuralValueHasTokenRef(op.about) || stringHasTokenRef(op.aboutWref) || structuralValueHasTokenRef(op.members) || structuralValueHasTokenRef(op.data);
8526
+ function completedOperationsFrom(result) {
8527
+ return result.operations.filter((operation) => operation.status !== "error");
8497
8528
  }
8498
8529
  function createStreamId() {
8499
8530
  return globalThis.crypto?.randomUUID?.() ?? `stream-${Date.now()}-${Math.random()}`;
@@ -8697,39 +8728,234 @@ async function verifyCliCall(request, secrets, opts) {
8697
8728
  );
8698
8729
  }
8699
8730
  }
8700
- return { method: cliMethod, installRepo, args };
8701
- }
8702
- function isNonEmpty(value) {
8703
- return typeof value === "string" && value.length > 0;
8704
- }
8705
- function isArgsRecord(value) {
8706
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
8707
- return false;
8731
+ return { method: cliMethod, installRepo, args };
8732
+ }
8733
+ function isNonEmpty(value) {
8734
+ return typeof value === "string" && value.length > 0;
8735
+ }
8736
+ function isArgsRecord(value) {
8737
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
8738
+ return false;
8739
+ }
8740
+ for (const v of Object.values(value)) {
8741
+ if (v === void 0) continue;
8742
+ if (typeof v === "string") continue;
8743
+ if (typeof v === "number") continue;
8744
+ if (typeof v === "boolean") continue;
8745
+ return false;
8746
+ }
8747
+ return true;
8748
+ }
8749
+ async function constantTimeEqual(a, b) {
8750
+ const [aDigest, bDigest] = await Promise.all([sha256(a), sha256(b)]);
8751
+ let diff = 0;
8752
+ for (let i = 0; i < aDigest.length; i++) {
8753
+ diff |= aDigest[i] ^ bDigest[i];
8754
+ }
8755
+ return diff === 0;
8756
+ }
8757
+ async function sha256(value) {
8758
+ return new Uint8Array(
8759
+ await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value))
8760
+ );
8761
+ }
8762
+
8763
+ // src/operation-builder-helpers.ts
8764
+ function inferShapeFromLocalPath(name) {
8765
+ if (!name.includes("/")) return void 0;
8766
+ return name.split("/")[0];
8767
+ }
8768
+ function inferShapeForAdd(op) {
8769
+ if ("shapeWref" in op && typeof op.shapeWref === "string") {
8770
+ return op.shapeWref;
8771
+ }
8772
+ if (op.name) {
8773
+ return inferShapeFromLocalPath(op.name);
8774
+ }
8775
+ return void 0;
8776
+ }
8777
+ function asNonEmptyString(value) {
8778
+ if (typeof value !== "string") return void 0;
8779
+ const trimmed = value.trim();
8780
+ return trimmed.length > 0 ? trimmed : void 0;
8781
+ }
8782
+ function normalizeAddKind(op) {
8783
+ return inferOperationKind({
8784
+ kind: op.kind,
8785
+ name: op.name,
8786
+ about: getAddAbout(op),
8787
+ type: getCollectionType(op),
8788
+ members: getCollectionMembers(op)
8789
+ });
8790
+ }
8791
+ function getAddAbout(op) {
8792
+ if ("about" in op && op.about !== void 0) return op.about;
8793
+ if ("aboutWref" in op) return op.aboutWref;
8794
+ return void 0;
8795
+ }
8796
+ function getAddData(op) {
8797
+ return "data" in op ? op.data : void 0;
8798
+ }
8799
+ function getCollectionType(op) {
8800
+ return "type" in op ? op.type : void 0;
8801
+ }
8802
+ function getCollectionMembers(op) {
8803
+ return "members" in op ? op.members : void 0;
8804
+ }
8805
+ function normalizeReviseKind(op, name) {
8806
+ return inferOperationKind({ kind: op.kind, name });
8807
+ }
8808
+ function toBackendOp(op) {
8809
+ if (op.operation === "retract") {
8810
+ return {
8811
+ operation: "retract",
8812
+ name: op.name,
8813
+ ...op.kind ? { kind: op.kind } : {},
8814
+ ...op.reason ? { reason: op.reason } : {},
8815
+ ...op.leaseId ? { leaseId: op.leaseId } : {}
8816
+ };
8817
+ }
8818
+ if (op.operation === "add") {
8819
+ const kind2 = normalizeAddKind(op);
8820
+ if (kind2 === "collection") {
8821
+ return normalizeBackendCollectionAdd(op, "OperationBuilder");
8822
+ }
8823
+ const addOp = op;
8824
+ const name2 = asNonEmptyString(addOp.name);
8825
+ if (!name2) {
8826
+ throw new Error("OperationBuilder: add operation missing name");
8827
+ }
8828
+ if (kind2 === "assertion") {
8829
+ const about = getAddAbout(addOp);
8830
+ if (about === void 0) {
8831
+ throw new Error(
8832
+ `OperationBuilder: assertion add '${name2}' missing required 'about'`
8833
+ );
8834
+ }
8835
+ return {
8836
+ operation: "add",
8837
+ kind: kind2,
8838
+ name: name2,
8839
+ about,
8840
+ data: addOp.data ?? {},
8841
+ ...addOp.skipExisting === true ? { skipExisting: true } : {}
8842
+ };
8843
+ }
8844
+ return {
8845
+ operation: "add",
8846
+ kind: kind2,
8847
+ name: name2,
8848
+ data: addOp.data ?? {},
8849
+ ...addOp.skipExisting === true ? { skipExisting: true } : {}
8850
+ };
8851
+ }
8852
+ const name = asNonEmptyString(op.name) ?? asNonEmptyString(op.wref);
8853
+ if (!name) {
8854
+ throw new Error(
8855
+ "OperationBuilder: revise operation missing required target ('name' or 'wref')"
8856
+ );
8857
+ }
8858
+ const kind = normalizeReviseKind(op, name);
8859
+ if (kind === "collection") {
8860
+ throw new Error(
8861
+ `OperationBuilder: collection revise is no longer supported \u2014 use retract('${name}') instead`
8862
+ );
8863
+ }
8864
+ return {
8865
+ operation: "revise",
8866
+ kind,
8867
+ name,
8868
+ data: op.data ?? {},
8869
+ ...op.expectedVersion !== void 0 ? { expectedVersion: op.expectedVersion } : {},
8870
+ ...op.leaseId ? { leaseId: op.leaseId } : {}
8871
+ };
8872
+ }
8873
+ function toPreflightOp(op, kind) {
8874
+ if (op.operation === "add") {
8875
+ return {
8876
+ operation: op.operation,
8877
+ kind,
8878
+ name: op.name,
8879
+ about: getAddAbout(op),
8880
+ type: getCollectionType(op),
8881
+ members: getCollectionMembers(op)
8882
+ };
8883
+ }
8884
+ return {
8885
+ operation: op.operation,
8886
+ kind,
8887
+ name: op.name ?? op.wref
8888
+ };
8889
+ }
8890
+ function throwAggregatedMessages(messages) {
8891
+ if (messages.length === 0) return;
8892
+ throw new Error(messages.join("; "));
8893
+ }
8894
+ function preflightCheckCommon(verb, kind, name, data) {
8895
+ const errors = [];
8896
+ const requiresLocalPath = kind === "thing" || kind === "assertion" || kind === "collection" && verb === "REVISE";
8897
+ if (requiresLocalPath) {
8898
+ if (!splitLocalPath(name)) {
8899
+ errors.push(
8900
+ `${verb} ${kind} name must be a local path (Shape/name), got: "${name}"`
8901
+ );
8902
+ }
8903
+ }
8904
+ if (kind === "shape" && (verb === "ADD" || verb === "REVISE") && data !== void 0) {
8905
+ const preflight = shapeDefinitionPreflightError(
8906
+ name,
8907
+ data,
8908
+ verb === "REVISE" ? "revise" : "add"
8909
+ );
8910
+ if (preflight) errors.push(preflight);
8911
+ }
8912
+ return errors;
8913
+ }
8914
+ function preflightCheckAdd(op, kind) {
8915
+ const messages = [];
8916
+ if (op.name) {
8917
+ messages.push(...preflightCheckCommon("ADD", kind, op.name, getAddData(op)));
8918
+ }
8919
+ if (kind === "assertion" && getAddAbout(op) === void 0 && // Token-bearing names defer to validate() so the more specific token
8920
+ // diagnostics surface instead of this eager check.
8921
+ !(op.name && hasAnyTokens(op.name))) {
8922
+ messages.push(
8923
+ `ADD assertion '${op.name}' missing required 'about' \u2014 for hierarchical thing names set kind: 'thing' explicitly`
8924
+ );
8925
+ }
8926
+ if (kind !== "collection" && (getCollectionType(op) !== void 0 || getCollectionMembers(op) !== void 0)) {
8927
+ messages.push(
8928
+ `ADD '${op.name}' has collection fields but inferred kind '${kind}' \u2014 collection adds require both 'type' and 'members', or set kind: 'collection'`
8929
+ );
8708
8930
  }
8709
- for (const v of Object.values(value)) {
8710
- if (v === void 0) continue;
8711
- if (typeof v === "string") continue;
8712
- if (typeof v === "number") continue;
8713
- if (typeof v === "boolean") continue;
8714
- return false;
8931
+ if (kind === "collection") {
8932
+ try {
8933
+ normalizeBackendCollectionAdd(op, "OperationBuilder");
8934
+ } catch (error) {
8935
+ messages.push(error instanceof Error ? error.message : String(error));
8936
+ }
8715
8937
  }
8716
- return true;
8717
- }
8718
- async function constantTimeEqual(a, b) {
8719
- const [aDigest, bDigest] = await Promise.all([sha256(a), sha256(b)]);
8720
- let diff = 0;
8721
- for (let i = 0; i < aDigest.length; i++) {
8722
- diff |= aDigest[i] ^ bDigest[i];
8938
+ for (const diagnostic of preflightOpDiagnostics2(toPreflightOp(op, kind), 0)) {
8939
+ messages.push(diagnostic.message);
8723
8940
  }
8724
- return diff === 0;
8941
+ throwAggregatedMessages(messages);
8725
8942
  }
8726
- async function sha256(value) {
8727
- return new Uint8Array(
8728
- await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value))
8943
+ function preflightCheckRevise(kind, name, data) {
8944
+ const messages = [];
8945
+ if (data === void 0) {
8946
+ messages.push(`OperationBuilder: ${kind} revise requires 'data'`);
8947
+ }
8948
+ messages.push(
8949
+ ...preflightCheckCommon("REVISE", kind, name, data),
8950
+ ...preflightOpDiagnostics2(
8951
+ toPreflightOp({ operation: "revise", name, data }, kind),
8952
+ 0
8953
+ ).map((diagnostic) => diagnostic.message)
8729
8954
  );
8955
+ throwAggregatedMessages(messages);
8730
8956
  }
8731
8957
 
8732
- // src/operation-builder.ts
8958
+ // src/operation-builder-core.ts
8733
8959
  var OperationBuilder = class {
8734
8960
  ops = [];
8735
8961
  pendingNames = /* @__PURE__ */ new Set();
@@ -8979,7 +9205,7 @@ var OperationBuilder = class {
8979
9205
  target ? normalizeReviseKind(op, target) : "thing"
8980
9206
  );
8981
9207
  });
8982
- const diagnostics = preflightCommitDiagnostics(preflightOps);
9208
+ const diagnostics = preflightCommitDiagnostics2(preflightOps);
8983
9209
  for (const diagnostic of diagnostics) {
8984
9210
  errors.push({
8985
9211
  code: diagnostic.code,
@@ -9043,199 +9269,6 @@ var OperationBuilder = class {
9043
9269
  }
9044
9270
  }
9045
9271
  };
9046
- function inferShapeFromLocalPath(name) {
9047
- if (!name.includes("/")) return void 0;
9048
- return name.split("/")[0];
9049
- }
9050
- function inferShapeForAdd(op) {
9051
- if ("shapeWref" in op && typeof op.shapeWref === "string") {
9052
- return op.shapeWref;
9053
- }
9054
- if (op.name) {
9055
- return inferShapeFromLocalPath(op.name);
9056
- }
9057
- return void 0;
9058
- }
9059
- function asNonEmptyString(value) {
9060
- if (typeof value !== "string") return void 0;
9061
- const trimmed = value.trim();
9062
- return trimmed.length > 0 ? trimmed : void 0;
9063
- }
9064
- function normalizeAddKind(op) {
9065
- return inferOperationKind({
9066
- kind: op.kind,
9067
- name: op.name,
9068
- about: getAddAbout(op),
9069
- type: getCollectionType(op),
9070
- members: getCollectionMembers(op)
9071
- });
9072
- }
9073
- function getAddAbout(op) {
9074
- if ("about" in op && op.about !== void 0) return op.about;
9075
- if ("aboutWref" in op) return op.aboutWref;
9076
- return void 0;
9077
- }
9078
- function getAddData(op) {
9079
- return "data" in op ? op.data : void 0;
9080
- }
9081
- function getCollectionType(op) {
9082
- return "type" in op ? op.type : void 0;
9083
- }
9084
- function getCollectionMembers(op) {
9085
- return "members" in op ? op.members : void 0;
9086
- }
9087
- function normalizeReviseKind(op, name) {
9088
- return inferOperationKind({ kind: op.kind, name });
9089
- }
9090
- function toBackendOp(op) {
9091
- if (op.operation === "retract") {
9092
- return {
9093
- operation: "retract",
9094
- name: op.name,
9095
- ...op.kind ? { kind: op.kind } : {},
9096
- ...op.reason ? { reason: op.reason } : {},
9097
- ...op.leaseId ? { leaseId: op.leaseId } : {}
9098
- };
9099
- }
9100
- if (op.operation === "add") {
9101
- const kind2 = normalizeAddKind(op);
9102
- if (kind2 === "collection") {
9103
- return normalizeBackendCollectionAdd(op, "OperationBuilder");
9104
- }
9105
- const addOp = op;
9106
- const name2 = asNonEmptyString(addOp.name);
9107
- if (!name2) {
9108
- throw new Error("OperationBuilder: add operation missing name");
9109
- }
9110
- if (kind2 === "assertion") {
9111
- const about = getAddAbout(addOp);
9112
- if (about === void 0) {
9113
- throw new Error(
9114
- `OperationBuilder: assertion add '${name2}' missing required 'about'`
9115
- );
9116
- }
9117
- return {
9118
- operation: "add",
9119
- kind: kind2,
9120
- name: name2,
9121
- about,
9122
- data: addOp.data ?? {},
9123
- ...addOp.skipExisting === true ? { skipExisting: true } : {}
9124
- };
9125
- }
9126
- return {
9127
- operation: "add",
9128
- kind: kind2,
9129
- name: name2,
9130
- data: addOp.data ?? {},
9131
- ...addOp.skipExisting === true ? { skipExisting: true } : {}
9132
- };
9133
- }
9134
- const name = asNonEmptyString(op.name) ?? asNonEmptyString(op.wref);
9135
- if (!name) {
9136
- throw new Error(
9137
- "OperationBuilder: revise operation missing required target ('name' or 'wref')"
9138
- );
9139
- }
9140
- const kind = normalizeReviseKind(op, name);
9141
- if (kind === "collection") {
9142
- throw new Error(
9143
- `OperationBuilder: collection revise is no longer supported \u2014 use retract('${name}') instead`
9144
- );
9145
- }
9146
- return {
9147
- operation: "revise",
9148
- kind,
9149
- name,
9150
- data: op.data ?? {},
9151
- ...op.expectedVersion !== void 0 ? { expectedVersion: op.expectedVersion } : {},
9152
- ...op.leaseId ? { leaseId: op.leaseId } : {}
9153
- };
9154
- }
9155
- function toPreflightOp(op, kind) {
9156
- if (op.operation === "add") {
9157
- return {
9158
- operation: op.operation,
9159
- kind,
9160
- name: op.name,
9161
- about: getAddAbout(op),
9162
- type: getCollectionType(op),
9163
- members: getCollectionMembers(op)
9164
- };
9165
- }
9166
- return {
9167
- operation: op.operation,
9168
- kind,
9169
- name: op.name ?? op.wref
9170
- };
9171
- }
9172
- function throwAggregatedMessages(messages) {
9173
- if (messages.length === 0) return;
9174
- throw new Error(messages.join("; "));
9175
- }
9176
- function preflightCheckCommon(verb, kind, name, data) {
9177
- const errors = [];
9178
- const requiresLocalPath = kind === "thing" || kind === "assertion" || kind === "collection" && verb === "REVISE";
9179
- if (requiresLocalPath) {
9180
- if (!splitLocalPath(name)) {
9181
- errors.push(
9182
- `${verb} ${kind} name must be a local path (Shape/name), got: "${name}"`
9183
- );
9184
- }
9185
- }
9186
- if (kind === "shape" && (verb === "ADD" || verb === "REVISE") && data !== void 0) {
9187
- const preflight = shapeDefinitionPreflightError(
9188
- name,
9189
- data,
9190
- verb === "REVISE" ? "revise" : "add"
9191
- );
9192
- if (preflight) errors.push(preflight);
9193
- }
9194
- return errors;
9195
- }
9196
- function preflightCheckAdd(op, kind) {
9197
- const messages = [];
9198
- if (op.name) {
9199
- messages.push(...preflightCheckCommon("ADD", kind, op.name, getAddData(op)));
9200
- }
9201
- if (kind === "assertion" && getAddAbout(op) === void 0 && // Token-bearing names defer to validate() so the more specific token
9202
- // diagnostics surface instead of this eager check.
9203
- !(op.name && hasAnyTokens(op.name))) {
9204
- messages.push(
9205
- `ADD assertion '${op.name}' missing required 'about' \u2014 for hierarchical thing names set kind: 'thing' explicitly`
9206
- );
9207
- }
9208
- if (kind !== "collection" && (getCollectionType(op) !== void 0 || getCollectionMembers(op) !== void 0)) {
9209
- messages.push(
9210
- `ADD '${op.name}' has collection fields but inferred kind '${kind}' \u2014 collection adds require both 'type' and 'members', or set kind: 'collection'`
9211
- );
9212
- }
9213
- if (kind === "collection") {
9214
- try {
9215
- normalizeBackendCollectionAdd(op, "OperationBuilder");
9216
- } catch (error) {
9217
- messages.push(error instanceof Error ? error.message : String(error));
9218
- }
9219
- }
9220
- for (const diagnostic of preflightOpDiagnostics(toPreflightOp(op, kind), 0)) {
9221
- messages.push(diagnostic.message);
9222
- }
9223
- throwAggregatedMessages(messages);
9224
- }
9225
- function preflightCheckRevise(kind, name, data) {
9226
- const messages = [];
9227
- if (data === void 0) {
9228
- messages.push(`OperationBuilder: ${kind} revise requires 'data'`);
9229
- }
9230
- messages.push(
9231
- ...preflightCheckCommon("REVISE", kind, name, data),
9232
- ...preflightOpDiagnostics(
9233
- toPreflightOp({ operation: "revise", name, data }, kind),
9234
- 0
9235
- ).map((diagnostic) => diagnostic.message)
9236
- );
9237
- throwAggregatedMessages(messages);
9238
- }
9239
9272
 
9240
9273
  // src/index.ts
9241
9274
  function normalizeWref(wref) {
@@ -9246,7 +9279,8 @@ var WARMHUB_CLIENT_OPTION_NAMES = [
9246
9279
  "fetch",
9247
9280
  "accessToken",
9248
9281
  "auth",
9249
- "functionLogs"
9282
+ "functionLogs",
9283
+ "client"
9250
9284
  ];
9251
9285
  var WARMHUB_CLIENT_OPTION_NAME_SET = new Set(
9252
9286
  WARMHUB_CLIENT_OPTION_NAMES
@@ -9264,7 +9298,7 @@ function validateWarmHubClientOptions(options) {
9264
9298
  throw new TypeError(`Unknown WarmHubClient option "${key}"${hint}`);
9265
9299
  }
9266
9300
  }
9267
- var SDK_VERSION = "0.60.1" ;
9301
+ var SDK_VERSION = "0.62.0" ;
9268
9302
  var DEFAULT_API_URL = "https://api.warmhub.ai";
9269
9303
  var UNBATCHED_TRPC_PATHS = /* @__PURE__ */ new Set([
9270
9304
  "repo.shapeInstanceCounts",
@@ -9599,6 +9633,8 @@ var WarmHubClient = class _WarmHubClient {
9599
9633
  apiUrl;
9600
9634
  fetchImpl;
9601
9635
  accessToken;
9636
+ /** Resolved `X-WarmHub-Client` identity; defaults to this SDK's name/version. */
9637
+ clientIdentity;
9602
9638
  functionLogMode;
9603
9639
  getToken;
9604
9640
  // Cached promise for diagnostics.assertCompatible(). Stored at instance
@@ -10515,21 +10551,6 @@ var WarmHubClient = class _WarmHubClient {
10515
10551
  throw toWarmHubError(error);
10516
10552
  }
10517
10553
  },
10518
- /**
10519
- * Immediately and irreversibly purge a repository.
10520
- *
10521
- * This owner-only operation has no grace window and uses the same inbound-reference guard as `delete`.
10522
- */
10523
- hardDelete: async (orgName, repoName) => {
10524
- try {
10525
- await this.trpc.repo.hardDelete.mutate({
10526
- orgName,
10527
- repoName
10528
- });
10529
- } catch (error) {
10530
- throw toWarmHubError(error);
10531
- }
10532
- },
10533
10554
  /**
10534
10555
  * List repositories with dashboard-oriented per-repository metadata.
10535
10556
  *
@@ -12162,6 +12183,10 @@ var WarmHubClient = class _WarmHubClient {
12162
12183
  this.fetchImpl = options?.fetch;
12163
12184
  this.accessToken = options?.accessToken ?? options?.auth?.getToken;
12164
12185
  this.functionLogMode = options?.functionLogs ?? "off";
12186
+ this.clientIdentity = {
12187
+ name: options?.client?.name ?? WARMHUB_SDK_CLIENT_NAME,
12188
+ version: options?.client?.version ?? SDK_VERSION
12189
+ };
12165
12190
  if (typeof this.accessToken === "function") {
12166
12191
  const provider = this.accessToken;
12167
12192
  this.getToken = async () => await provider();
@@ -12200,7 +12225,10 @@ var WarmHubClient = class _WarmHubClient {
12200
12225
  return new _WarmHubClient({
12201
12226
  apiUrl: this.apiUrl,
12202
12227
  fetch: this.fetchImpl,
12203
- accessToken
12228
+ accessToken,
12229
+ // Preserve the client identity so a token-scoped clone (e.g. a CLI clone)
12230
+ // keeps reporting `warmhub-cli/…` rather than reverting to the SDK default.
12231
+ client: this.clientIdentity
12204
12232
  });
12205
12233
  }
12206
12234
  /**
@@ -12278,6 +12306,21 @@ var WarmHubClient = class _WarmHubClient {
12278
12306
  closed
12279
12307
  };
12280
12308
  }
12309
+ /**
12310
+ * Stamp the `X-WarmHub-Client` self-identification header, unless the caller
12311
+ * already set it on the request (a per-request header wins over the default).
12312
+ */
12313
+ applyClientHeader(headers) {
12314
+ if (!headers.has(CLIENT_HEADER)) {
12315
+ headers.set(
12316
+ CLIENT_HEADER,
12317
+ formatClientHeader(
12318
+ this.clientIdentity.name,
12319
+ this.clientIdentity.version
12320
+ )
12321
+ );
12322
+ }
12323
+ }
12281
12324
  async fetchWithAuth(input, init) {
12282
12325
  const fetchImpl = this.fetchImpl ?? globalThis.fetch;
12283
12326
  const headers = new Headers(init?.headers);
@@ -12285,6 +12328,7 @@ var WarmHubClient = class _WarmHubClient {
12285
12328
  if (token) {
12286
12329
  headers.set("authorization", `Bearer ${token}`);
12287
12330
  }
12331
+ this.applyClientHeader(headers);
12288
12332
  const doFetch = async (reqInit) => {
12289
12333
  try {
12290
12334
  return await fetchImpl(input, reqInit);
@@ -12301,6 +12345,7 @@ var WarmHubClient = class _WarmHubClient {
12301
12345
  if (freshToken && freshToken !== token) {
12302
12346
  const retryHeaders = new Headers(init?.headers);
12303
12347
  retryHeaders.set("authorization", `Bearer ${freshToken}`);
12348
+ this.applyClientHeader(retryHeaders);
12304
12349
  return doFetch({ ...init, headers: retryHeaders });
12305
12350
  }
12306
12351
  }
@@ -12470,5 +12515,5 @@ re2js/build/index.js:
12470
12515
  */
12471
12516
 
12472
12517
  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 };
12473
- //# sourceMappingURL=chunk-D5A5SSUU.js.map
12474
- //# sourceMappingURL=chunk-D5A5SSUU.js.map
12518
+ //# sourceMappingURL=chunk-CN4352BX.js.map
12519
+ //# sourceMappingURL=chunk-CN4352BX.js.map