@warmhub/sdk-ts 0.45.0 → 0.46.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.
- package/dist/{chunk-ERGCQLFH.js → chunk-RX3ZL6P5.js} +525 -28
- package/dist/chunk-RX3ZL6P5.js.map +1 -0
- package/dist/index.d.ts +232 -13
- package/dist/index.js +1 -1
- package/dist/react.js +2 -2
- package/package.json +26 -2
- package/dist/chunk-ERGCQLFH.js.map +0 -1
|
@@ -51,6 +51,115 @@ function splitLocalPath(name) {
|
|
|
51
51
|
return { shapePrefix: name.slice(0, idx), bareName: name.slice(idx + 1) };
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// ../rules/src/component-cli-signing.ts
|
|
55
|
+
var CLI_INSTALL_REPO_HEADER = "X-WarmHub-Install-Repo";
|
|
56
|
+
var CLI_SIGNATURE_HEADER = "X-WarmHub-Signature";
|
|
57
|
+
var CLI_TIMESTAMP_HEADER = "X-WarmHub-Timestamp";
|
|
58
|
+
var RFC3986_UNRESERVED = /^[A-Za-z0-9\-._~]$/;
|
|
59
|
+
function pctEncode(value) {
|
|
60
|
+
let out = "";
|
|
61
|
+
const bytes = new TextEncoder().encode(value);
|
|
62
|
+
for (const byte of bytes) {
|
|
63
|
+
const ch = String.fromCharCode(byte);
|
|
64
|
+
if (RFC3986_UNRESERVED.test(ch)) {
|
|
65
|
+
out += ch;
|
|
66
|
+
} else {
|
|
67
|
+
out += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
function serializeQueryValue(value) {
|
|
73
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
74
|
+
if (typeof value === "number") return JSON.stringify(value);
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
function cliMethodHasBody(method) {
|
|
78
|
+
return method === "POST" || method === "PUT" || method === "PATCH";
|
|
79
|
+
}
|
|
80
|
+
function canonicalCliQueryString(args) {
|
|
81
|
+
const entries = [];
|
|
82
|
+
for (const [k, v] of Object.entries(args)) {
|
|
83
|
+
if (v === void 0 || v === null) continue;
|
|
84
|
+
entries.push([k, serializeQueryValue(v)]);
|
|
85
|
+
}
|
|
86
|
+
entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
87
|
+
return entries.map(([k, v]) => `${pctEncode(k)}=${pctEncode(v)}`).join("&");
|
|
88
|
+
}
|
|
89
|
+
function bytesToHex(bytes) {
|
|
90
|
+
let out = "";
|
|
91
|
+
for (const byte of bytes) {
|
|
92
|
+
out += byte.toString(16).padStart(2, "0");
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
async function sha256Hex(text) {
|
|
97
|
+
const buf = await crypto.subtle.digest(
|
|
98
|
+
"SHA-256",
|
|
99
|
+
new TextEncoder().encode(text)
|
|
100
|
+
);
|
|
101
|
+
return bytesToHex(new Uint8Array(buf));
|
|
102
|
+
}
|
|
103
|
+
var EMPTY_BODY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
104
|
+
function buildCliSigningInput(input) {
|
|
105
|
+
return [
|
|
106
|
+
input.method,
|
|
107
|
+
input.path,
|
|
108
|
+
input.canonicalQuery,
|
|
109
|
+
input.installRepo,
|
|
110
|
+
input.bodyHash,
|
|
111
|
+
String(input.timestamp)
|
|
112
|
+
].join("\n");
|
|
113
|
+
}
|
|
114
|
+
async function hmacSha256Hex(secret, message) {
|
|
115
|
+
const encoder = new TextEncoder();
|
|
116
|
+
const key = await crypto.subtle.importKey(
|
|
117
|
+
"raw",
|
|
118
|
+
encoder.encode(secret),
|
|
119
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
120
|
+
false,
|
|
121
|
+
["sign"]
|
|
122
|
+
);
|
|
123
|
+
const signature = await crypto.subtle.sign(
|
|
124
|
+
"HMAC",
|
|
125
|
+
key,
|
|
126
|
+
encoder.encode(message)
|
|
127
|
+
);
|
|
128
|
+
return bytesToHex(new Uint8Array(signature));
|
|
129
|
+
}
|
|
130
|
+
var SIGNATURE_RE = /^sha256=([0-9a-f]{64})$/;
|
|
131
|
+
var DEFAULT_TOLERANCE_SEC = 300;
|
|
132
|
+
async function verifyCliRequest(input) {
|
|
133
|
+
const match = SIGNATURE_RE.exec(input.signature);
|
|
134
|
+
if (!match) return { ok: false, reason: "invalid-format" };
|
|
135
|
+
const providedHex = match[1];
|
|
136
|
+
const tolerance = input.toleranceSec ?? DEFAULT_TOLERANCE_SEC;
|
|
137
|
+
const skew = Math.abs(input.nowUnixSeconds - input.timestamp);
|
|
138
|
+
if (skew > tolerance) return { ok: false, reason: "expired" };
|
|
139
|
+
const bodyHash = input.body.length === 0 ? EMPTY_BODY_SHA256 : await sha256Hex(input.body);
|
|
140
|
+
const canonical = buildCliSigningInput({
|
|
141
|
+
method: input.method,
|
|
142
|
+
path: input.path,
|
|
143
|
+
canonicalQuery: canonicalCliQueryString(input.query),
|
|
144
|
+
installRepo: input.installRepo,
|
|
145
|
+
bodyHash,
|
|
146
|
+
timestamp: input.timestamp
|
|
147
|
+
});
|
|
148
|
+
const expectedHex = await hmacSha256Hex(input.secret, canonical);
|
|
149
|
+
if (!constantTimeEqualHex(providedHex, expectedHex)) {
|
|
150
|
+
return { ok: false, reason: "invalid-signature" };
|
|
151
|
+
}
|
|
152
|
+
return { ok: true };
|
|
153
|
+
}
|
|
154
|
+
function constantTimeEqualHex(a, b) {
|
|
155
|
+
if (a.length !== b.length) return false;
|
|
156
|
+
let diff = 0;
|
|
157
|
+
for (let i = 0; i < a.length; i++) {
|
|
158
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
159
|
+
}
|
|
160
|
+
return diff === 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
54
163
|
// ../rules/src/repo-auth-scopes.ts
|
|
55
164
|
var REPO_AUTH_SCOPES = [
|
|
56
165
|
"repo:read",
|
|
@@ -1442,6 +1551,23 @@ function inferKind(name, operation) {
|
|
|
1442
1551
|
// src/stream-submit.ts
|
|
1443
1552
|
var DEFAULT_STREAM_CHUNK_SIZE = DEFAULT_STREAM_APPEND_CHUNK_SIZE;
|
|
1444
1553
|
var MAX_STREAM_APPEND_OPERATION_COUNT2 = MAX_STREAM_APPEND_OPERATION_COUNT;
|
|
1554
|
+
function streamAppendResultStatus(result) {
|
|
1555
|
+
if (result.status === "failed") {
|
|
1556
|
+
return "error";
|
|
1557
|
+
}
|
|
1558
|
+
return result.status === "noop" || result.operation === "noop" ? "noop" : "applied";
|
|
1559
|
+
}
|
|
1560
|
+
function countStreamAppendResultStatuses(results) {
|
|
1561
|
+
const statusCounts = {
|
|
1562
|
+
applied: 0,
|
|
1563
|
+
noop: 0,
|
|
1564
|
+
error: 0
|
|
1565
|
+
};
|
|
1566
|
+
for (const result of results) {
|
|
1567
|
+
statusCounts[streamAppendResultStatus(result)]++;
|
|
1568
|
+
}
|
|
1569
|
+
return statusCounts;
|
|
1570
|
+
}
|
|
1445
1571
|
var DEFAULT_RETRY_POLICY = {
|
|
1446
1572
|
maxAttempts: 3,
|
|
1447
1573
|
baseDelayMs: 250,
|
|
@@ -1509,12 +1635,41 @@ var PartialStreamSubmissionError = class extends Error {
|
|
|
1509
1635
|
this.completedOperations = input.completedOperations;
|
|
1510
1636
|
}
|
|
1511
1637
|
};
|
|
1638
|
+
var AllStreamOperationsFailedError = class extends Error {
|
|
1639
|
+
code = "STREAM_ALL_OPERATIONS_FAILED";
|
|
1640
|
+
result;
|
|
1641
|
+
operations;
|
|
1642
|
+
statusCounts;
|
|
1643
|
+
cause;
|
|
1644
|
+
constructor(result) {
|
|
1645
|
+
const primaryFailure = result.operations.find(
|
|
1646
|
+
(operation) => operation.error !== void 0
|
|
1647
|
+
)?.error;
|
|
1648
|
+
super(
|
|
1649
|
+
primaryFailure ? `All ${result.operationCount} stream operations failed: ${primaryFailure.message}` : `All ${result.operationCount} stream operations failed.`
|
|
1650
|
+
);
|
|
1651
|
+
this.name = "WarmHubError";
|
|
1652
|
+
this.cause = primaryFailure;
|
|
1653
|
+
this.result = result;
|
|
1654
|
+
this.operations = result.operations;
|
|
1655
|
+
this.statusCounts = result.statusCounts ?? {
|
|
1656
|
+
applied: 0,
|
|
1657
|
+
noop: 0,
|
|
1658
|
+
error: result.operationCount
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1512
1662
|
async function submitOperationsViaStream(client, args) {
|
|
1513
1663
|
if (args.operations.length === 0) {
|
|
1514
1664
|
throw new StreamValidationError(
|
|
1515
1665
|
"At least one operation is required for stream submission."
|
|
1516
1666
|
);
|
|
1517
1667
|
}
|
|
1668
|
+
if ((args.allocatedTokens?.length ?? 0) > 0 && args.streamId === void 0) {
|
|
1669
|
+
throw new StreamValidationError(
|
|
1670
|
+
"Manual stream resume with allocatedTokens requires the original streamId."
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1518
1673
|
const operations = args.operations.map((operation, index) => {
|
|
1519
1674
|
let streamOperation;
|
|
1520
1675
|
try {
|
|
@@ -1535,8 +1690,13 @@ async function submitOperationsViaStream(client, args) {
|
|
|
1535
1690
|
const isManualResume = args.streamId !== void 0 || (args.allocatedTokens?.length ?? 0) > 0;
|
|
1536
1691
|
const policy = isManualResume ? false : resolveRetryPolicy(args.retry);
|
|
1537
1692
|
let allocatedTokens = args.allocatedTokens ?? [];
|
|
1693
|
+
let createdByEmail;
|
|
1538
1694
|
const chunkResults = [];
|
|
1539
|
-
|
|
1695
|
+
let sawAmbiguousAttempt = false;
|
|
1696
|
+
for (const { operations: chunk, start: chunkStart } of chunkOperations(
|
|
1697
|
+
operations,
|
|
1698
|
+
chunkSize
|
|
1699
|
+
)) {
|
|
1540
1700
|
let attempt = 1;
|
|
1541
1701
|
let priorAttemptAmbiguous = false;
|
|
1542
1702
|
const chunkIsAtomic = chunk.length === 1;
|
|
@@ -1550,10 +1710,16 @@ async function submitOperationsViaStream(client, args) {
|
|
|
1550
1710
|
streamId,
|
|
1551
1711
|
componentId: args.componentId,
|
|
1552
1712
|
committer: args.committer,
|
|
1713
|
+
message: args.message,
|
|
1553
1714
|
operations: chunk
|
|
1554
1715
|
});
|
|
1555
1716
|
allocatedTokens = appendResult.allocatedTokens;
|
|
1556
|
-
|
|
1717
|
+
if (appendResult.createdByEmail !== void 0) {
|
|
1718
|
+
createdByEmail = appendResult.createdByEmail;
|
|
1719
|
+
}
|
|
1720
|
+
chunkResults.push(
|
|
1721
|
+
...offsetChunkResultIndexes(appendResult.results, chunkStart)
|
|
1722
|
+
);
|
|
1557
1723
|
break;
|
|
1558
1724
|
} catch (cause) {
|
|
1559
1725
|
if (chunkResults.length === 0 && !priorAttemptAmbiguous && isDefiniteClientError(cause)) {
|
|
@@ -1563,18 +1729,23 @@ async function submitOperationsViaStream(client, args) {
|
|
|
1563
1729
|
await sleep(computeBackoffDelayMs(attempt, policy));
|
|
1564
1730
|
attempt += 1;
|
|
1565
1731
|
priorAttemptAmbiguous = true;
|
|
1732
|
+
sawAmbiguousAttempt = true;
|
|
1566
1733
|
streamId = createStreamId();
|
|
1567
1734
|
allocatedTokens = [];
|
|
1568
1735
|
continue;
|
|
1569
1736
|
}
|
|
1570
|
-
const completedOperations =
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1737
|
+
const completedOperations = completedOperationsFrom(
|
|
1738
|
+
toSubmittedStreamResult(
|
|
1739
|
+
{
|
|
1740
|
+
operationCount: chunkResults.length
|
|
1741
|
+
},
|
|
1742
|
+
args.committer,
|
|
1743
|
+
createdByEmail,
|
|
1744
|
+
args.message,
|
|
1745
|
+
chunkResults,
|
|
1746
|
+
operations
|
|
1747
|
+
)
|
|
1748
|
+
);
|
|
1578
1749
|
throw new PartialStreamSubmissionError({
|
|
1579
1750
|
cause,
|
|
1580
1751
|
completedOperations
|
|
@@ -1582,20 +1753,54 @@ async function submitOperationsViaStream(client, args) {
|
|
|
1582
1753
|
}
|
|
1583
1754
|
}
|
|
1584
1755
|
}
|
|
1585
|
-
|
|
1756
|
+
const result = toSubmittedStreamResult(
|
|
1586
1757
|
{
|
|
1587
1758
|
operationCount: chunkResults.length
|
|
1588
1759
|
},
|
|
1589
1760
|
args.committer,
|
|
1761
|
+
createdByEmail,
|
|
1590
1762
|
args.message,
|
|
1591
|
-
chunkResults
|
|
1763
|
+
chunkResults,
|
|
1764
|
+
operations
|
|
1592
1765
|
);
|
|
1766
|
+
if (isAllSubmittedOperationsFailed(result, operations.length)) {
|
|
1767
|
+
const failure = new AllStreamOperationsFailedError(result);
|
|
1768
|
+
if (sawAmbiguousAttempt) {
|
|
1769
|
+
throw new PartialStreamSubmissionError({
|
|
1770
|
+
cause: failure,
|
|
1771
|
+
completedOperations: completedOperationsFrom(result)
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
throw failure;
|
|
1775
|
+
}
|
|
1776
|
+
return result;
|
|
1777
|
+
}
|
|
1778
|
+
function isAllSubmittedOperationsFailed(result, submittedOperationCount) {
|
|
1779
|
+
if (submittedOperationCount <= 0) {
|
|
1780
|
+
return false;
|
|
1781
|
+
}
|
|
1782
|
+
const inputRowsFailed = /* @__PURE__ */ new Map();
|
|
1783
|
+
for (const operation of result.operations) {
|
|
1784
|
+
if (operation.opIndex === void 0 || operation.opIndex < 0 || operation.opIndex >= submittedOperationCount) {
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
inputRowsFailed.set(
|
|
1788
|
+
operation.opIndex,
|
|
1789
|
+
(inputRowsFailed.get(operation.opIndex) ?? true) && operation.status === "error"
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
return inputRowsFailed.size === submittedOperationCount && [...inputRowsFailed.values()].every(Boolean);
|
|
1793
|
+
}
|
|
1794
|
+
function completedOperationsFrom(result) {
|
|
1795
|
+
return result.operations.filter((operation) => operation.status !== "error");
|
|
1593
1796
|
}
|
|
1594
1797
|
var DEFINITE_CLIENT_REJECT_CODES = /* @__PURE__ */ new Set([
|
|
1595
1798
|
"UNAUTHENTICATED",
|
|
1596
1799
|
"FORBIDDEN",
|
|
1597
1800
|
"VALIDATION_ERROR",
|
|
1598
1801
|
"SHAPE_MISMATCH",
|
|
1802
|
+
"RESERVED_NAME",
|
|
1803
|
+
"ILLEGAL_OP_SEQUENCE",
|
|
1599
1804
|
"NOT_FOUND",
|
|
1600
1805
|
"KIND_MISMATCH",
|
|
1601
1806
|
"CONFLICT",
|
|
@@ -1625,13 +1830,18 @@ function isDefiniteClientError(cause) {
|
|
|
1625
1830
|
}
|
|
1626
1831
|
function isTransientStreamFailure(cause) {
|
|
1627
1832
|
if (isDefiniteClientError(cause)) return false;
|
|
1833
|
+
if (isFetchNetworkTypeError(cause)) return true;
|
|
1628
1834
|
if (cause instanceof TypeError) return false;
|
|
1629
1835
|
if (cause instanceof SyntaxError) return false;
|
|
1630
1836
|
const inner = cause?.cause;
|
|
1837
|
+
if (isFetchNetworkTypeError(inner)) return true;
|
|
1631
1838
|
if (inner instanceof TypeError) return false;
|
|
1632
1839
|
if (inner instanceof SyntaxError) return false;
|
|
1633
1840
|
return true;
|
|
1634
1841
|
}
|
|
1842
|
+
function isFetchNetworkTypeError(cause) {
|
|
1843
|
+
return cause instanceof TypeError && /fetch/i.test(cause.message);
|
|
1844
|
+
}
|
|
1635
1845
|
function computeBackoffDelayMs(attempt, policy) {
|
|
1636
1846
|
const exponential = policy.baseDelayMs * 2 ** Math.max(0, attempt - 1);
|
|
1637
1847
|
const jitter = Math.random() * policy.baseDelayMs;
|
|
@@ -1644,12 +1854,22 @@ var TOKEN_REF_REGEX = /[$#]\d+/;
|
|
|
1644
1854
|
function stringHasTokenRef(value) {
|
|
1645
1855
|
return typeof value === "string" && TOKEN_REF_REGEX.test(value);
|
|
1646
1856
|
}
|
|
1647
|
-
function
|
|
1648
|
-
if (
|
|
1649
|
-
|
|
1857
|
+
function structuralValueHasTokenRef(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
1858
|
+
if (stringHasTokenRef(value)) return true;
|
|
1859
|
+
if (value !== null && typeof value === "object") {
|
|
1860
|
+
if (seen.has(value)) return false;
|
|
1861
|
+
seen.add(value);
|
|
1862
|
+
if (Array.isArray(value)) {
|
|
1863
|
+
return value.some((entry) => structuralValueHasTokenRef(entry, seen));
|
|
1864
|
+
}
|
|
1865
|
+
return Object.values(value).some(
|
|
1866
|
+
(entry) => structuralValueHasTokenRef(entry, seen)
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
return false;
|
|
1650
1870
|
}
|
|
1651
1871
|
function opUsesTokens(op) {
|
|
1652
|
-
return stringHasTokenRef(op.name) || stringHasTokenRef(op.wref) ||
|
|
1872
|
+
return stringHasTokenRef(op.name) || stringHasTokenRef(op.wref) || structuralValueHasTokenRef(op.about) || stringHasTokenRef(op.aboutWref) || structuralValueHasTokenRef(op.members) || structuralValueHasTokenRef(op.data);
|
|
1653
1873
|
}
|
|
1654
1874
|
function createStreamId() {
|
|
1655
1875
|
return globalThis.crypto?.randomUUID?.() ?? `stream-${Date.now()}-${Math.random()}`;
|
|
@@ -1667,27 +1887,213 @@ function normalizeChunkSize(chunkSize) {
|
|
|
1667
1887
|
function chunkOperations(operations, chunkSize) {
|
|
1668
1888
|
const chunks = [];
|
|
1669
1889
|
for (let start = 0; start < operations.length; start += chunkSize) {
|
|
1670
|
-
chunks.push(
|
|
1890
|
+
chunks.push({
|
|
1891
|
+
operations: operations.slice(start, start + chunkSize),
|
|
1892
|
+
start
|
|
1893
|
+
});
|
|
1671
1894
|
}
|
|
1672
1895
|
return chunks;
|
|
1673
1896
|
}
|
|
1674
|
-
function
|
|
1897
|
+
function offsetChunkResultIndexes(results, chunkStart) {
|
|
1898
|
+
return results.map((result, index) => ({
|
|
1899
|
+
...result,
|
|
1900
|
+
opIndex: chunkStart + (result.opIndex ?? index)
|
|
1901
|
+
}));
|
|
1902
|
+
}
|
|
1903
|
+
function toSubmittedStreamResult(writeResult, committer, createdByEmail, message, results, operations) {
|
|
1904
|
+
const statusCounts = countStreamAppendResultStatuses(results);
|
|
1905
|
+
const partial = statusCounts.error > 0;
|
|
1675
1906
|
return {
|
|
1676
1907
|
committer,
|
|
1908
|
+
...createdByEmail !== void 0 ? { createdByEmail } : {},
|
|
1677
1909
|
message,
|
|
1678
1910
|
operationCount: writeResult.operationCount,
|
|
1911
|
+
...partial ? { partial, statusCounts } : {},
|
|
1679
1912
|
operations: results.map((result) => ({
|
|
1913
|
+
...result.opIndex !== void 0 ? { opIndex: result.opIndex } : {},
|
|
1680
1914
|
name: result.name ?? "",
|
|
1681
1915
|
operation: result.operation === "revise" || result.operation === "retract" || result.operation === "noop" ? result.operation : "add",
|
|
1682
1916
|
dataHash: result.dataHash ?? "",
|
|
1683
1917
|
version: result.version ?? 0,
|
|
1684
|
-
status: result
|
|
1918
|
+
status: streamAppendResultStatus(result),
|
|
1685
1919
|
error: result.error,
|
|
1920
|
+
...result.status === "failed" && result.opIndex !== void 0 && operations[result.opIndex]?.name !== void 0 ? { submittedName: operations[result.opIndex]?.name } : {},
|
|
1921
|
+
...result.resolvedName !== void 0 ? { resolvedName: result.resolvedName } : {},
|
|
1922
|
+
...result.retryable !== void 0 ? { retryable: result.retryable } : {},
|
|
1686
1923
|
...result.warnings ? { warnings: result.warnings } : {}
|
|
1687
1924
|
}))
|
|
1688
1925
|
};
|
|
1689
1926
|
}
|
|
1690
1927
|
|
|
1928
|
+
// src/component-cli.ts
|
|
1929
|
+
var SUPPORTED_METHODS = /* @__PURE__ */ new Set([
|
|
1930
|
+
"GET",
|
|
1931
|
+
"POST",
|
|
1932
|
+
"PUT",
|
|
1933
|
+
"PATCH",
|
|
1934
|
+
"DELETE"
|
|
1935
|
+
]);
|
|
1936
|
+
var CliCallVerificationError = class extends Error {
|
|
1937
|
+
reason;
|
|
1938
|
+
constructor(reason, message) {
|
|
1939
|
+
super(message);
|
|
1940
|
+
this.name = "CliCallVerificationError";
|
|
1941
|
+
this.reason = reason;
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
async function verifyCliCall(request, secrets, opts) {
|
|
1945
|
+
const method = request.method.toUpperCase();
|
|
1946
|
+
if (!SUPPORTED_METHODS.has(method)) {
|
|
1947
|
+
throw new CliCallVerificationError(
|
|
1948
|
+
"unsupported-method",
|
|
1949
|
+
`Unsupported HTTP method "${request.method}" \u2014 component CLI dispatch only emits GET, POST, PUT, or PATCH`
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
const cliMethod = method;
|
|
1953
|
+
const installRepo = request.headers.get(CLI_INSTALL_REPO_HEADER);
|
|
1954
|
+
if (!installRepo) {
|
|
1955
|
+
throw new CliCallVerificationError(
|
|
1956
|
+
"missing-install-repo",
|
|
1957
|
+
`Missing ${CLI_INSTALL_REPO_HEADER} header`
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
const hasSigning = isNonEmpty(secrets.CLI_SIGNING_SECRET);
|
|
1961
|
+
const hasBearer = isNonEmpty(secrets.CLI_BEARER_TOKEN);
|
|
1962
|
+
const hasApiKey = isNonEmpty(secrets.CLI_API_KEY);
|
|
1963
|
+
const hasBasic = isNonEmpty(secrets.CLI_BASIC_USERNAME) && isNonEmpty(secrets.CLI_BASIC_PASSWORD);
|
|
1964
|
+
if (!hasSigning && !hasBearer && !hasApiKey && !hasBasic) {
|
|
1965
|
+
throw new CliCallVerificationError(
|
|
1966
|
+
"no-scheme-configured",
|
|
1967
|
+
"no-scheme-configured: need at least one of CLI_SIGNING_SECRET, CLI_BEARER_TOKEN, CLI_API_KEY, or CLI_BASIC_USERNAME + CLI_BASIC_PASSWORD"
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1970
|
+
const url = new URL(request.url);
|
|
1971
|
+
const path = url.pathname;
|
|
1972
|
+
let query;
|
|
1973
|
+
let body;
|
|
1974
|
+
let args;
|
|
1975
|
+
if (!cliMethodHasBody(cliMethod)) {
|
|
1976
|
+
query = Object.fromEntries(url.searchParams.entries());
|
|
1977
|
+
body = "";
|
|
1978
|
+
args = { ...query };
|
|
1979
|
+
} else {
|
|
1980
|
+
query = {};
|
|
1981
|
+
body = await request.text();
|
|
1982
|
+
let parsed;
|
|
1983
|
+
try {
|
|
1984
|
+
parsed = JSON.parse(body);
|
|
1985
|
+
} catch {
|
|
1986
|
+
throw new CliCallVerificationError(
|
|
1987
|
+
"invalid-body",
|
|
1988
|
+
"POST body is not valid JSON"
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
if (!isArgsRecord(parsed)) {
|
|
1992
|
+
throw new CliCallVerificationError(
|
|
1993
|
+
"invalid-body",
|
|
1994
|
+
"POST body must be a JSON object of primitive args (string / number / boolean)"
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
args = parsed;
|
|
1998
|
+
}
|
|
1999
|
+
if (hasSigning) {
|
|
2000
|
+
const signature = request.headers.get(CLI_SIGNATURE_HEADER);
|
|
2001
|
+
if (!signature) {
|
|
2002
|
+
throw new CliCallVerificationError(
|
|
2003
|
+
"missing-signature",
|
|
2004
|
+
`Missing ${CLI_SIGNATURE_HEADER} header`
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
const timestampHeader = request.headers.get(CLI_TIMESTAMP_HEADER);
|
|
2008
|
+
if (!timestampHeader) {
|
|
2009
|
+
throw new CliCallVerificationError(
|
|
2010
|
+
"missing-timestamp",
|
|
2011
|
+
`Missing ${CLI_TIMESTAMP_HEADER} header`
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
const timestamp = Number(timestampHeader);
|
|
2015
|
+
if (!Number.isFinite(timestamp) || !Number.isInteger(timestamp)) {
|
|
2016
|
+
throw new CliCallVerificationError(
|
|
2017
|
+
"invalid-timestamp",
|
|
2018
|
+
`${CLI_TIMESTAMP_HEADER} must be an integer unix seconds value; got "${timestampHeader}"`
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
const verification = await verifyCliRequest({
|
|
2022
|
+
secret: secrets.CLI_SIGNING_SECRET,
|
|
2023
|
+
method: cliMethod,
|
|
2024
|
+
path,
|
|
2025
|
+
query,
|
|
2026
|
+
installRepo,
|
|
2027
|
+
body,
|
|
2028
|
+
signature,
|
|
2029
|
+
timestamp,
|
|
2030
|
+
nowUnixSeconds: opts?.nowUnixSeconds ?? Math.floor(Date.now() / 1e3),
|
|
2031
|
+
toleranceSec: opts?.toleranceSec
|
|
2032
|
+
});
|
|
2033
|
+
if (!verification.ok) {
|
|
2034
|
+
throw new CliCallVerificationError(
|
|
2035
|
+
verification.reason,
|
|
2036
|
+
`HMAC verification failed: ${verification.reason}`
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
if (hasBearer) {
|
|
2041
|
+
const expected = `Bearer ${secrets.CLI_BEARER_TOKEN}`;
|
|
2042
|
+
const provided = request.headers.get("authorization") ?? "";
|
|
2043
|
+
if (!constantTimeEqual(provided, expected)) {
|
|
2044
|
+
throw new CliCallVerificationError(
|
|
2045
|
+
"invalid-bearer",
|
|
2046
|
+
"invalid-bearer: bearer token mismatch"
|
|
2047
|
+
);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
if (hasApiKey) {
|
|
2051
|
+
const headerName = isNonEmpty(secrets.CLI_API_KEY_HEADER) ? secrets.CLI_API_KEY_HEADER : "X-API-Key";
|
|
2052
|
+
const provided = request.headers.get(headerName) ?? "";
|
|
2053
|
+
if (!constantTimeEqual(provided, secrets.CLI_API_KEY)) {
|
|
2054
|
+
throw new CliCallVerificationError(
|
|
2055
|
+
"invalid-api-key",
|
|
2056
|
+
`invalid-api-key: mismatch on header "${headerName}"`
|
|
2057
|
+
);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
if (hasBasic) {
|
|
2061
|
+
const expected = `Basic ${btoa(`${secrets.CLI_BASIC_USERNAME}:${secrets.CLI_BASIC_PASSWORD}`)}`;
|
|
2062
|
+
const provided = request.headers.get("authorization") ?? "";
|
|
2063
|
+
if (!constantTimeEqual(provided, expected)) {
|
|
2064
|
+
throw new CliCallVerificationError(
|
|
2065
|
+
"invalid-basic",
|
|
2066
|
+
"invalid-basic: basic auth mismatch"
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
return { method: cliMethod, installRepo, args };
|
|
2071
|
+
}
|
|
2072
|
+
function isNonEmpty(value) {
|
|
2073
|
+
return typeof value === "string" && value.length > 0;
|
|
2074
|
+
}
|
|
2075
|
+
function isArgsRecord(value) {
|
|
2076
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2077
|
+
return false;
|
|
2078
|
+
}
|
|
2079
|
+
for (const v of Object.values(value)) {
|
|
2080
|
+
if (v === void 0) continue;
|
|
2081
|
+
if (typeof v === "string") continue;
|
|
2082
|
+
if (typeof v === "number") continue;
|
|
2083
|
+
if (typeof v === "boolean") continue;
|
|
2084
|
+
return false;
|
|
2085
|
+
}
|
|
2086
|
+
return true;
|
|
2087
|
+
}
|
|
2088
|
+
function constantTimeEqual(a, b) {
|
|
2089
|
+
if (a.length !== b.length) return false;
|
|
2090
|
+
let diff = 0;
|
|
2091
|
+
for (let i = 0; i < a.length; i++) {
|
|
2092
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
2093
|
+
}
|
|
2094
|
+
return diff === 0;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
1691
2097
|
// src/operation-builder.ts
|
|
1692
2098
|
var OperationBuilder = class {
|
|
1693
2099
|
ops = [];
|
|
@@ -1952,7 +2358,7 @@ var OperationBuilder = class {
|
|
|
1952
2358
|
/**
|
|
1953
2359
|
* Validate, submit, and seal the builder.
|
|
1954
2360
|
*
|
|
1955
|
-
* A successful call submits operations through the same stream path as `client.commit.apply` and makes the builder single-use. Validation failures throw before any server request is made. Ambiguous multi-chunk failures surface as `PartialStreamSubmissionError
|
|
2361
|
+
* A successful call submits operations through the same stream path as `client.commit.apply` and makes the builder single-use. Validation failures throw before any server request is made. Ambiguous multi-chunk failures surface as `PartialStreamSubmissionError`; deterministic all-failed batches surface as `AllStreamOperationsFailedError` with per-op failure data.
|
|
1956
2362
|
*
|
|
1957
2363
|
* @param params.client WarmHub client or compatible stream client used for submission.
|
|
1958
2364
|
* @param params.message Optional commit message.
|
|
@@ -2183,7 +2589,7 @@ function preflightCheckRevise(kind, name, data) {
|
|
|
2183
2589
|
function normalizeWref(wref) {
|
|
2184
2590
|
return wref.replace(/@(?:v\d+|HEAD|ALL)$/i, "");
|
|
2185
2591
|
}
|
|
2186
|
-
var SDK_VERSION = "0.
|
|
2592
|
+
var SDK_VERSION = "0.46.0" ;
|
|
2187
2593
|
var DEFAULT_API_URL = "https://api.warmhub.ai";
|
|
2188
2594
|
var UNBATCHED_TRPC_PATHS = /* @__PURE__ */ new Set([
|
|
2189
2595
|
"repo.shapeInstanceCounts",
|
|
@@ -2405,6 +2811,57 @@ function isConnectionError(error) {
|
|
|
2405
2811
|
function connectionErrorMessage(url) {
|
|
2406
2812
|
return `Unable to connect to ${url}. Is the computer able to access the url?`;
|
|
2407
2813
|
}
|
|
2814
|
+
function parseSemver(value) {
|
|
2815
|
+
const match = value.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/);
|
|
2816
|
+
if (!match) return void 0;
|
|
2817
|
+
let prerelease = [];
|
|
2818
|
+
if (match[4]) {
|
|
2819
|
+
prerelease = match[4].split(".");
|
|
2820
|
+
for (const id of prerelease) {
|
|
2821
|
+
if (id.length === 0) return void 0;
|
|
2822
|
+
if (/^\d+$/.test(id) && id.length > 1 && id.startsWith("0")) {
|
|
2823
|
+
return void 0;
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
return {
|
|
2828
|
+
major: Number(match[1]),
|
|
2829
|
+
minor: Number(match[2]),
|
|
2830
|
+
patch: Number(match[3]),
|
|
2831
|
+
prerelease
|
|
2832
|
+
};
|
|
2833
|
+
}
|
|
2834
|
+
function comparePrereleaseIdentifiers(a, b) {
|
|
2835
|
+
const aNum = /^\d+$/.test(a);
|
|
2836
|
+
const bNum = /^\d+$/.test(b);
|
|
2837
|
+
if (aNum && bNum) return Number(a) - Number(b);
|
|
2838
|
+
if (aNum && !bNum) return -1;
|
|
2839
|
+
if (!aNum && bNum) return 1;
|
|
2840
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
2841
|
+
}
|
|
2842
|
+
function comparePrerelease(a, b) {
|
|
2843
|
+
if (a.length === 0 && b.length > 0) return 1;
|
|
2844
|
+
if (a.length > 0 && b.length === 0) return -1;
|
|
2845
|
+
const max = Math.max(a.length, b.length);
|
|
2846
|
+
for (let i = 0; i < max; i += 1) {
|
|
2847
|
+
const aPart = a[i];
|
|
2848
|
+
const bPart = b[i];
|
|
2849
|
+
if (aPart === void 0) return -1;
|
|
2850
|
+
if (bPart === void 0) return 1;
|
|
2851
|
+
const cmp = comparePrereleaseIdentifiers(aPart, bPart);
|
|
2852
|
+
if (cmp !== 0) return cmp;
|
|
2853
|
+
}
|
|
2854
|
+
return 0;
|
|
2855
|
+
}
|
|
2856
|
+
function sdkVersionIsBelowMinimum(version, minimum) {
|
|
2857
|
+
const v = parseSemver(version);
|
|
2858
|
+
const m = parseSemver(minimum);
|
|
2859
|
+
if (!v || !m) return false;
|
|
2860
|
+
if (v.major !== m.major) return v.major < m.major;
|
|
2861
|
+
if (v.minor !== m.minor) return v.minor < m.minor;
|
|
2862
|
+
if (v.patch !== m.patch) return v.patch < m.patch;
|
|
2863
|
+
return comparePrerelease(v.prerelease, m.prerelease) < 0;
|
|
2864
|
+
}
|
|
2408
2865
|
var WarmHubClient = class _WarmHubClient {
|
|
2409
2866
|
/**
|
|
2410
2867
|
* The resolved API base URL the client issues requests against. Defaults to
|
|
@@ -2415,6 +2872,11 @@ var WarmHubClient = class _WarmHubClient {
|
|
|
2415
2872
|
accessToken;
|
|
2416
2873
|
functionLogMode;
|
|
2417
2874
|
getToken;
|
|
2875
|
+
// Cached promise for diagnostics.assertCompatible(). Stored at instance
|
|
2876
|
+
// level so a successful check is reused across calls, and a failed check
|
|
2877
|
+
// is also cached so a too-old SDK keeps reporting the same actionable
|
|
2878
|
+
// error rather than re-hitting the network. Cleared on construction.
|
|
2879
|
+
compatibilityCheck;
|
|
2418
2880
|
/**
|
|
2419
2881
|
* Authentication helpers for browser sign-in flows, session checks, and token diagnostics.
|
|
2420
2882
|
*/
|
|
@@ -2533,6 +2995,41 @@ var WarmHubClient = class _WarmHubClient {
|
|
|
2533
2995
|
} catch (error) {
|
|
2534
2996
|
throw toWarmHubError(error);
|
|
2535
2997
|
}
|
|
2998
|
+
},
|
|
2999
|
+
/**
|
|
3000
|
+
* Verify the installed SDK is at or above the backend's advertised
|
|
3001
|
+
* minimum supported version, throwing a `WarmHubError` with a clear
|
|
3002
|
+
* "upgrade to >=X" message when it is not.
|
|
3003
|
+
*
|
|
3004
|
+
* Call this once at startup (e.g. immediately after constructing the
|
|
3005
|
+
* client) to fail fast on SDK version skew, rather than discovering a
|
|
3006
|
+
* removed route deep in the commit pipeline as an opaque error.
|
|
3007
|
+
* The result is cached on the client instance — repeated calls reuse
|
|
3008
|
+
* the first network round-trip and re-throw the same error if too old.
|
|
3009
|
+
*
|
|
3010
|
+
* @see https://github.com/warmhub/warmhub-app/issues/3081
|
|
3011
|
+
*/
|
|
3012
|
+
assertCompatible: () => {
|
|
3013
|
+
if (this.compatibilityCheck) return this.compatibilityCheck;
|
|
3014
|
+
const promise = (async () => {
|
|
3015
|
+
const capabilities = await this.diagnostics.capabilities();
|
|
3016
|
+
if (sdkVersionIsBelowMinimum(SDK_VERSION, capabilities.minSupportedSdk)) {
|
|
3017
|
+
throw new WarmHubError(
|
|
3018
|
+
"VALIDATION_ERROR",
|
|
3019
|
+
`@warmhub/sdk-ts ${SDK_VERSION} is older than the backend's minimum supported SDK (${capabilities.minSupportedSdk}). Upgrade to >=${capabilities.minSupportedSdk}.`,
|
|
3020
|
+
400,
|
|
3021
|
+
`Run: npm install @warmhub/sdk-ts@latest (or your package manager's equivalent).`
|
|
3022
|
+
);
|
|
3023
|
+
}
|
|
3024
|
+
})();
|
|
3025
|
+
this.compatibilityCheck = promise;
|
|
3026
|
+
void promise.catch((error) => {
|
|
3027
|
+
if (isWarmHubError(error) && error.code === "VALIDATION_ERROR") return;
|
|
3028
|
+
if (this.compatibilityCheck === promise) {
|
|
3029
|
+
this.compatibilityCheck = void 0;
|
|
3030
|
+
}
|
|
3031
|
+
});
|
|
3032
|
+
return promise;
|
|
2536
3033
|
}
|
|
2537
3034
|
};
|
|
2538
3035
|
/**
|
|
@@ -2722,7 +3219,7 @@ var WarmHubClient = class _WarmHubClient {
|
|
|
2722
3219
|
/**
|
|
2723
3220
|
* High-level write surface for submitting WarmHub operations through the commit pipeline.
|
|
2724
3221
|
*
|
|
2725
|
-
* @see https://docs.warmhub.ai/sdk/
|
|
3222
|
+
* @see https://docs.warmhub.ai/sdk/write-methods/
|
|
2726
3223
|
*/
|
|
2727
3224
|
commit = {
|
|
2728
3225
|
/**
|
|
@@ -2730,7 +3227,7 @@ var WarmHubClient = class _WarmHubClient {
|
|
|
2730
3227
|
*
|
|
2731
3228
|
* This is the primary write path for SDK callers. It streams operations to the backend, preserves server-side per-operation results, supports chunking for large submissions, and can attribute writes to a committer wref or installed component.
|
|
2732
3229
|
*
|
|
2733
|
-
* Simple first-chunk transport failures are retried by default.
|
|
3230
|
+
* Simple first-chunk transport failures are retried by default. Ambiguous failures surface as `PartialStreamSubmissionError`, including the case where an ambiguous attempt is followed by an all-failed retry; inspect `error.cause` for the underlying `AllStreamOperationsFailedError` or backend error. Deterministic all-failed submissions without prior ambiguity throw `AllStreamOperationsFailedError` directly so callers can inspect per-operation failure rows. Validation, auth, conflict, rate-limit, and other definite backend failures surface as `WarmHubError`. See [Transient Retry](/sdk/transient-retry/) for the full retry and partial-submission rules.
|
|
2734
3231
|
*
|
|
2735
3232
|
* Writing to an archived organization or repository fails with an `ARCHIVED` error before any operations are applied.
|
|
2736
3233
|
*
|
|
@@ -2760,7 +3257,7 @@ var WarmHubClient = class _WarmHubClient {
|
|
|
2760
3257
|
operations
|
|
2761
3258
|
});
|
|
2762
3259
|
} catch (error) {
|
|
2763
|
-
if (error instanceof PartialStreamSubmissionError) {
|
|
3260
|
+
if (error instanceof PartialStreamSubmissionError || error instanceof AllStreamOperationsFailedError) {
|
|
2764
3261
|
throw error;
|
|
2765
3262
|
}
|
|
2766
3263
|
throw toWarmHubError(error);
|
|
@@ -3031,7 +3528,7 @@ var WarmHubClient = class _WarmHubClient {
|
|
|
3031
3528
|
*
|
|
3032
3529
|
* The returned `total` is the sum of active shapes, things, and assertions. Use this when billing, quota checks, health reports, or per-shape breakdowns need single-repo stats.
|
|
3033
3530
|
*
|
|
3034
|
-
* The per-shape breakdown counts active things
|
|
3531
|
+
* The per-shape breakdown counts active things and assertions by shape.
|
|
3035
3532
|
*/
|
|
3036
3533
|
getStats: async (orgName, repoName) => {
|
|
3037
3534
|
try {
|
|
@@ -4871,6 +5368,6 @@ function isAbortError(error) {
|
|
|
4871
5368
|
return error instanceof Error && error.name === "AbortError";
|
|
4872
5369
|
}
|
|
4873
5370
|
|
|
4874
|
-
export { CONTENT_FIELD_LIMIT_ERROR, 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, isConnectionError, isRetryable, isWarmHubError, normalizeWref, resolveFunctionLogMode, sanitizeErrorMessage, submitOperationsViaStream, toWarmHubError, validateAgainstShape };
|
|
4875
|
-
//# sourceMappingURL=chunk-
|
|
4876
|
-
//# sourceMappingURL=chunk-
|
|
5371
|
+
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 };
|
|
5372
|
+
//# sourceMappingURL=chunk-RX3ZL6P5.js.map
|
|
5373
|
+
//# sourceMappingURL=chunk-RX3ZL6P5.js.map
|