@warmhub/sdk-ts 0.47.0 → 0.49.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.
@@ -604,16 +604,28 @@ function illegalOpSequences(operations, errors, checkAddAdd) {
604
604
  }
605
605
 
606
606
  // ../rules/src/reserved-orgs.ts
607
+ var RESERVED_RAW_CONTENT_TOP_LEVEL_NAMES = [
608
+ "_app",
609
+ "_static",
610
+ "api",
611
+ "health",
612
+ "healthz",
613
+ "mcp",
614
+ "readyz",
615
+ "robots.txt",
616
+ "sse",
617
+ "trpc",
618
+ "version"
619
+ ];
607
620
  var RESERVED_ORG_NAMES = [
621
+ ...RESERVED_RAW_CONTENT_TOP_LEVEL_NAMES,
608
622
  "admin",
609
- "api",
610
623
  "billing",
611
624
  "blog",
612
625
  "docs",
613
626
  "help",
614
627
  "login",
615
628
  "public",
616
- "robots.txt",
617
629
  "settings",
618
630
  "signup",
619
631
  "status",
@@ -624,6 +636,15 @@ var RESERVED_ORG_NAMES = [
624
636
  ];
625
637
  new Set(RESERVED_ORG_NAMES);
626
638
 
639
+ // ../rules/src/shape-field-key.ts
640
+ function foldFieldName(name) {
641
+ const stripped = name.endsWith("?") ? name.slice(0, -1) : name;
642
+ return stripped.toLowerCase();
643
+ }
644
+ function foldFieldPath(path) {
645
+ return path.split(".").map((segment) => foldFieldName(segment)).join(".");
646
+ }
647
+
627
648
  // ../rules/src/shape-types.ts
628
649
  var BASE_PRIMITIVE_TYPES = [
629
650
  "number",
@@ -1006,6 +1027,37 @@ function collectIndexableShapeFieldPaths(fields, prefix = "") {
1006
1027
  }
1007
1028
  return paths;
1008
1029
  }
1030
+ function isNestedFieldsRecord(value) {
1031
+ if (!isPlainObject(value) || Array.isArray(value)) return false;
1032
+ if (isTypeSpecObject(value)) return false;
1033
+ if ("type" in value && typeof value.type === "string" && VALID_PRIMITIVE_TYPES.has(value.type) && Object.keys(value).every((k) => VALID_TYPESPEC_KEYS.has(k))) {
1034
+ return false;
1035
+ }
1036
+ return true;
1037
+ }
1038
+ function validateFieldsRecord(fields, prefix, errors) {
1039
+ const seenDisplayByFolded = /* @__PURE__ */ new Map();
1040
+ for (const [key, value] of Object.entries(fields)) {
1041
+ const folded = foldFieldName(key);
1042
+ const prior = seenDisplayByFolded.get(folded);
1043
+ if (prior !== void 0) {
1044
+ errors.push(
1045
+ `${prefix}.${key}: duplicate field name. Shape already declares "${prior}" (case-insensitive match).`
1046
+ );
1047
+ continue;
1048
+ }
1049
+ seenDisplayByFolded.set(folded, key);
1050
+ if (isNestedFieldsRecord(value)) {
1051
+ validateFieldsRecord(
1052
+ value,
1053
+ `${prefix}.${key}`,
1054
+ errors
1055
+ );
1056
+ } else {
1057
+ validateTypeSpec(`${prefix}.${key}`, value, errors);
1058
+ }
1059
+ }
1060
+ }
1009
1061
  function validateShapeDefinition(data) {
1010
1062
  if (typeof data !== "object" || data === null || Array.isArray(data)) {
1011
1063
  return { valid: false, errors: ["Shape definition must be a plain object"] };
@@ -1030,11 +1082,7 @@ function validateShapeDefinition(data) {
1030
1082
  errors: ['"fields" must be a plain object mapping field names to types']
1031
1083
  };
1032
1084
  }
1033
- for (const [key, value] of Object.entries(
1034
- fields
1035
- )) {
1036
- validateTypeSpec(`fields.${key}`, value, errors);
1037
- }
1085
+ validateFieldsRecord(fields, "fields", errors);
1038
1086
  const indexableFieldPaths = collectIndexableShapeFieldPaths(
1039
1087
  fields
1040
1088
  );
@@ -1042,15 +1090,16 @@ function validateShapeDefinition(data) {
1042
1090
  const seenIndexableFieldPaths = /* @__PURE__ */ new Set();
1043
1091
  const duplicateIndexableFieldPaths = /* @__PURE__ */ new Set();
1044
1092
  for (const fieldPath of indexableFieldPaths) {
1045
- if (seenIndexableFieldPaths.has(fieldPath)) {
1046
- duplicateIndexableFieldPaths.add(fieldPath);
1093
+ const foldedPath = foldFieldPath(fieldPath);
1094
+ if (seenIndexableFieldPaths.has(foldedPath)) {
1095
+ duplicateIndexableFieldPaths.add(foldedPath);
1047
1096
  continue;
1048
1097
  }
1049
- seenIndexableFieldPaths.add(fieldPath);
1098
+ seenIndexableFieldPaths.add(foldedPath);
1050
1099
  }
1051
1100
  for (const fieldPath of duplicateIndexableFieldPaths) {
1052
1101
  errors.push(
1053
- `Shape declares ambiguous indexable field path "${fieldPath}"; dotted field names and optional-key suffixes must not collide with another indexable path`
1102
+ `Shape declares ambiguous indexable field path "${fieldPath}"; dotted field names, optional-key suffixes, and case-insensitive folded paths must not collide with another indexable path`
1054
1103
  );
1055
1104
  }
1056
1105
  if (indexableFieldCount > MAX_INDEXABLE_SCALAR_FIELDS_PER_SHAPE) {
@@ -1445,12 +1494,18 @@ new Set(
1445
1494
 
1446
1495
  // src/operation-normalize.ts
1447
1496
  function toBackendStreamOperation(operation) {
1497
+ if (operation.expectedVersion !== void 0 && operation.operation !== "revise") {
1498
+ throw new Error(
1499
+ "expectedVersion is only valid on revise operations \u2014 set operation: 'revise'"
1500
+ );
1501
+ }
1448
1502
  if (operation.operation === "retract") {
1449
1503
  return {
1450
1504
  operation: "retract",
1451
1505
  name: operation.name,
1452
1506
  ...operation.kind ? { kind: operation.kind } : {},
1453
- ...operation.reason ? { reason: operation.reason } : {}
1507
+ ...operation.reason ? { reason: operation.reason } : {},
1508
+ ...operation.leaseId ? { leaseId: operation.leaseId } : {}
1454
1509
  };
1455
1510
  }
1456
1511
  if (operation.operation === "revise") {
@@ -1477,14 +1532,18 @@ function toBackendStreamOperation(operation) {
1477
1532
  operation: "revise",
1478
1533
  kind: "assertion",
1479
1534
  name,
1480
- data: operation.data
1535
+ data: operation.data,
1536
+ ...operation.expectedVersion !== void 0 ? { expectedVersion: operation.expectedVersion } : {},
1537
+ ...operation.leaseId ? { leaseId: operation.leaseId } : {}
1481
1538
  };
1482
1539
  }
1483
1540
  return {
1484
1541
  operation: "revise",
1485
1542
  kind: kind2,
1486
1543
  name,
1487
- data: operation.data
1544
+ data: operation.data,
1545
+ ...operation.expectedVersion !== void 0 ? { expectedVersion: operation.expectedVersion } : {},
1546
+ ...operation.leaseId ? { leaseId: operation.leaseId } : {}
1488
1547
  };
1489
1548
  }
1490
1549
  const kind = operation.kind ?? inferKind(operation.name, operation);
@@ -2206,7 +2265,8 @@ var OperationBuilder = class {
2206
2265
  operation: "retract",
2207
2266
  name: target.name,
2208
2267
  ...target.kind ? { kind: target.kind } : {},
2209
- ...target.reason ? { reason: target.reason } : {}
2268
+ ...target.reason ? { reason: target.reason } : {},
2269
+ ...target.leaseId ? { leaseId: target.leaseId } : {}
2210
2270
  });
2211
2271
  this.ops.push(op);
2212
2272
  return this;
@@ -2450,7 +2510,8 @@ function toBackendOp(op) {
2450
2510
  operation: "retract",
2451
2511
  name: op.name,
2452
2512
  ...op.kind ? { kind: op.kind } : {},
2453
- ...op.reason ? { reason: op.reason } : {}
2513
+ ...op.reason ? { reason: op.reason } : {},
2514
+ ...op.leaseId ? { leaseId: op.leaseId } : {}
2454
2515
  };
2455
2516
  }
2456
2517
  if (op.operation === "add") {
@@ -2516,7 +2577,9 @@ function toBackendOp(op) {
2516
2577
  operation: "revise",
2517
2578
  kind,
2518
2579
  name,
2519
- data: op.data ?? {}
2580
+ data: op.data ?? {},
2581
+ ...op.expectedVersion !== void 0 ? { expectedVersion: op.expectedVersion } : {},
2582
+ ...op.leaseId ? { leaseId: op.leaseId } : {}
2520
2583
  };
2521
2584
  }
2522
2585
  function toPreflightOp(op, kind) {
@@ -2589,7 +2652,7 @@ function preflightCheckRevise(kind, name, data) {
2589
2652
  function normalizeWref(wref) {
2590
2653
  return wref.replace(/@(?:v\d+|HEAD|ALL)$/i, "");
2591
2654
  }
2592
- var SDK_VERSION = "0.47.0" ;
2655
+ var SDK_VERSION = "0.49.0" ;
2593
2656
  var DEFAULT_API_URL = "https://api.warmhub.ai";
2594
2657
  var UNBATCHED_TRPC_PATHS = /* @__PURE__ */ new Set([
2595
2658
  "repo.shapeInstanceCounts",
@@ -2721,7 +2784,16 @@ var WarmHubError = class extends Error {
2721
2784
  * generic `BACKEND` fallback), branch on {@link code} or {@link kind}.
2722
2785
  */
2723
2786
  backendCode;
2724
- constructor(code, message, status, hint, retryAfter, backendCode) {
2787
+ /**
2788
+ * Structured backend error details, when the response carried them. Branch on
2789
+ * `details.reason`: an `expected_version_mismatch` carries
2790
+ * `expectedVersion`/`currentVersion` so an optimistic-concurrency caller can
2791
+ * re-read HEAD and retry (#3624); a `lease_held` carries `leaseExpiresAt` so a
2792
+ * caller can back off until the lease expires (#3625). Present only when the
2793
+ * backend wire carried `data.warmhub.details`. See {@link WarmHubErrorDetails}.
2794
+ */
2795
+ details;
2796
+ constructor(code, message, status, hint, retryAfter, backendCode, details) {
2725
2797
  super(message);
2726
2798
  this.name = "WarmHubError";
2727
2799
  this.code = code;
@@ -2729,6 +2801,7 @@ var WarmHubError = class extends Error {
2729
2801
  this.hint = hint;
2730
2802
  this.retryAfter = retryAfter;
2731
2803
  this.backendCode = backendCode;
2804
+ this.details = details;
2732
2805
  }
2733
2806
  get kind() {
2734
2807
  return this.code;
@@ -2757,7 +2830,8 @@ function toWarmHubError(error) {
2757
2830
  data?.warmhub?.status,
2758
2831
  data?.warmhub?.hint,
2759
2832
  data?.warmhub?.retryAfter,
2760
- wireCode
2833
+ wireCode,
2834
+ data?.warmhub?.details
2761
2835
  );
2762
2836
  }
2763
2837
  if (error instanceof Error) {
@@ -2769,7 +2843,8 @@ function toWarmHubError(error) {
2769
2843
  typeof warmhubLike.status === "number" ? warmhubLike.status : void 0,
2770
2844
  typeof warmhubLike.hint === "string" ? warmhubLike.hint : void 0,
2771
2845
  typeof warmhubLike.retryAfter === "number" ? warmhubLike.retryAfter : void 0,
2772
- typeof warmhubLike.backendCode === "string" ? warmhubLike.backendCode : void 0
2846
+ typeof warmhubLike.backendCode === "string" ? warmhubLike.backendCode : void 0,
2847
+ warmhubLike.details
2773
2848
  );
2774
2849
  }
2775
2850
  if (error.name === "AbortError") {
@@ -4423,6 +4498,56 @@ var WarmHubClient = class _WarmHubClient {
4423
4498
  throw toWarmHubError(error);
4424
4499
  }
4425
4500
  },
4501
+ /**
4502
+ * Acquire a short, bounded, exclusive lease on a thing AND read it in one
4503
+ * atomic round trip (#3625).
4504
+ *
4505
+ * Returns everything {@link WarmHub.thing.get} returns plus a `lease`
4506
+ * block; the holder echoes `lease.id` back as `leaseId` on the subsequent
4507
+ * `revise`/`retract` (auto-releasing the lease) or calls
4508
+ * {@link WarmHub.thing.releaseLease} to return it early. Requires
4509
+ * `things:write` — never anonymous.
4510
+ *
4511
+ * Fail-fast: if another holder already holds an active lease, throws a
4512
+ * `WarmHubError` with `kind === 'LEASE_UNAVAILABLE'` and
4513
+ * `error.details?.reason === 'lease_held'` (read `leaseExpiresAt` to back
4514
+ * off until expiry). `ttlMs` out of the backend's bounds (default 5s /
4515
+ * min 1s / max 30s) is rejected, never clamped.
4516
+ */
4517
+ getWithLease: async (orgName, repoName, wref, opts) => {
4518
+ try {
4519
+ return await this.trpc.thing.getWithLease.mutate({
4520
+ orgName,
4521
+ repoName,
4522
+ wref,
4523
+ ttlMs: opts?.ttlMs
4524
+ });
4525
+ } catch (error) {
4526
+ throw toWarmHubError(error);
4527
+ }
4528
+ },
4529
+ /**
4530
+ * Release a lease early (#3625), closing the acquire↔release loop without
4531
+ * waiting out the TTL.
4532
+ *
4533
+ * Idempotent and owner-gated: releasing an absent, already-released,
4534
+ * expired, or non-matching lease is a benign no-op (no error). A
4535
+ * successful `revise`/`retract` carrying the `leaseId` already
4536
+ * auto-releases the lease, so this is only needed when the holder decides
4537
+ * not to mutate. Requires `things:write`.
4538
+ */
4539
+ releaseLease: async (orgName, repoName, wref, leaseId) => {
4540
+ try {
4541
+ await this.trpc.thing.releaseLease.mutate({
4542
+ orgName,
4543
+ repoName,
4544
+ wref,
4545
+ leaseId
4546
+ });
4547
+ } catch (error) {
4548
+ throw toWarmHubError(error);
4549
+ }
4550
+ },
4426
4551
  /**
4427
4552
  * Get one record and its embedded assertion, about, and wref graph.
4428
4553
  *
@@ -5399,5 +5524,5 @@ function isAbortError(error) {
5399
5524
  }
5400
5525
 
5401
5526
  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 };
5402
- //# sourceMappingURL=chunk-E32PSLWS.js.map
5403
- //# sourceMappingURL=chunk-E32PSLWS.js.map
5527
+ //# sourceMappingURL=chunk-CVMYSRPS.js.map
5528
+ //# sourceMappingURL=chunk-CVMYSRPS.js.map