@warmhub/sdk-ts 0.44.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/README.md +111 -0
- package/dist/chunk-PKA25PT7.js +4775 -0
- package/dist/chunk-PKA25PT7.js.map +1 -0
- package/dist/index.d.ts +3316 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +27 -0
- package/dist/react.js +75 -0
- package/dist/react.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,4775 @@
|
|
|
1
|
+
import { TRPCClientError, createTRPCProxyClient, splitLink, httpBatchLink, httpLink } from '@trpc/client';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
|
|
5
|
+
// ../rules/src/builtin-shapes.ts
|
|
6
|
+
var BUILTIN_SHAPE_NAMES = ["Pair", "Triple", "Set", "List"];
|
|
7
|
+
function isBuiltinCollectionShape(name) {
|
|
8
|
+
return BUILTIN_SHAPE_NAMES.includes(name);
|
|
9
|
+
}
|
|
10
|
+
var BUILTIN_CONTENT_SHAPE_NAMES = ["Content"];
|
|
11
|
+
var STORED_CONTENT_NAMES = ["Readme", "Agents"];
|
|
12
|
+
var SYNTHESIZED_CONTENT_NAMES = ["LlmsTxt"];
|
|
13
|
+
var isContentShape = (s) => s === "Content";
|
|
14
|
+
var isStoredContentName = (n) => STORED_CONTENT_NAMES.includes(n);
|
|
15
|
+
var isSynthesizedContentName = (n) => SYNTHESIZED_CONTENT_NAMES.includes(n);
|
|
16
|
+
function isBuiltinShape(name) {
|
|
17
|
+
return BUILTIN_SHAPE_NAMES.includes(name) || BUILTIN_CONTENT_SHAPE_NAMES.includes(name);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ../rules/src/commit-stream-limits.ts
|
|
21
|
+
var DEFAULT_STREAM_APPEND_CHUNK_SIZE = 1e3;
|
|
22
|
+
var MAX_STREAM_APPEND_OPERATION_COUNT = 1e4;
|
|
23
|
+
|
|
24
|
+
// ../rules/src/commit-types.ts
|
|
25
|
+
function isCollectionAbout(value) {
|
|
26
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
27
|
+
return false;
|
|
28
|
+
const record = value;
|
|
29
|
+
const keys = Object.keys(record);
|
|
30
|
+
if (keys.length !== 1) return false;
|
|
31
|
+
const key = keys[0];
|
|
32
|
+
if (key !== "pair" && key !== "triple" && key !== "set" && key !== "list")
|
|
33
|
+
return false;
|
|
34
|
+
return Array.isArray(record[key]);
|
|
35
|
+
}
|
|
36
|
+
function collectionAboutType(about) {
|
|
37
|
+
if ("pair" in about) return "pair";
|
|
38
|
+
if ("triple" in about) return "triple";
|
|
39
|
+
if ("set" in about) return "set";
|
|
40
|
+
return "list";
|
|
41
|
+
}
|
|
42
|
+
function collectionAboutMembers(about) {
|
|
43
|
+
if ("pair" in about) return about.pair;
|
|
44
|
+
if ("triple" in about) return about.triple;
|
|
45
|
+
if ("set" in about) return about.set;
|
|
46
|
+
return about.list;
|
|
47
|
+
}
|
|
48
|
+
function splitLocalPath(name) {
|
|
49
|
+
const idx = name.indexOf("/");
|
|
50
|
+
if (idx === -1) return null;
|
|
51
|
+
return { shapePrefix: name.slice(0, idx), bareName: name.slice(idx + 1) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ../rules/src/repo-auth-scopes.ts
|
|
55
|
+
var REPO_AUTH_SCOPES = [
|
|
56
|
+
"repo:read",
|
|
57
|
+
"repo:write",
|
|
58
|
+
"repo:configure",
|
|
59
|
+
"repo:admin"
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// ../rules/src/component-manifest-contract.ts
|
|
63
|
+
new Set(REPO_AUTH_SCOPES);
|
|
64
|
+
var PLATFORM_STATUS_STATUSES = [
|
|
65
|
+
{
|
|
66
|
+
key: "available",
|
|
67
|
+
label: "Available",
|
|
68
|
+
description: "Works as expected."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: "degraded",
|
|
72
|
+
label: "Degraded",
|
|
73
|
+
description: "Works with known caveats or partial reliability."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: "unavailable",
|
|
77
|
+
label: "Unavailable",
|
|
78
|
+
description: "Exists in code but is broken or blocked."
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: "not_yet_built",
|
|
82
|
+
label: "Not Yet Built",
|
|
83
|
+
description: "This feature-surface combination does not exist yet."
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
key: "unknown",
|
|
87
|
+
label: "Unknown",
|
|
88
|
+
description: "Has not been assessed yet."
|
|
89
|
+
}
|
|
90
|
+
];
|
|
91
|
+
({
|
|
92
|
+
statuses: [
|
|
93
|
+
{
|
|
94
|
+
key: {
|
|
95
|
+
type: "string",
|
|
96
|
+
enum: PLATFORM_STATUS_STATUSES.map((status) => status.key),
|
|
97
|
+
description: "Stable status key."
|
|
98
|
+
},
|
|
99
|
+
label: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "Human-readable label for the status."
|
|
102
|
+
},
|
|
103
|
+
description: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "Human-readable definition of the status."
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
]});
|
|
109
|
+
({
|
|
110
|
+
status: {
|
|
111
|
+
enum: PLATFORM_STATUS_STATUSES.map((status) => status.key)},
|
|
112
|
+
"issueMiningBaseline?": {
|
|
113
|
+
status: {
|
|
114
|
+
enum: PLATFORM_STATUS_STATUSES.map((status) => status.key)}}
|
|
115
|
+
});
|
|
116
|
+
({
|
|
117
|
+
"statusHint?": {
|
|
118
|
+
enum: PLATFORM_STATUS_STATUSES.map((status) => status.key)}});
|
|
119
|
+
|
|
120
|
+
// ../rules/src/tokens.ts
|
|
121
|
+
var ALLOC_TOKEN_RE = /\$(\d+)/g;
|
|
122
|
+
var REF_TOKEN_RE = /#(\d+)/g;
|
|
123
|
+
var ANY_TOKEN_RE = /[$#]\d+/;
|
|
124
|
+
function findAllocTokens(s) {
|
|
125
|
+
const tokens = [];
|
|
126
|
+
for (const m of s.matchAll(ALLOC_TOKEN_RE)) {
|
|
127
|
+
tokens.push(Number.parseInt(m[1], 10));
|
|
128
|
+
}
|
|
129
|
+
return tokens;
|
|
130
|
+
}
|
|
131
|
+
function findRefTokens(s) {
|
|
132
|
+
const tokens = [];
|
|
133
|
+
for (const m of s.matchAll(REF_TOKEN_RE)) {
|
|
134
|
+
tokens.push(Number.parseInt(m[1], 10));
|
|
135
|
+
}
|
|
136
|
+
return tokens;
|
|
137
|
+
}
|
|
138
|
+
function hasAnyTokens(s) {
|
|
139
|
+
return ANY_TOKEN_RE.test(s);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ../rules/src/preflight.ts
|
|
143
|
+
var collectionTypes = ["pair", "triple", "set", "list"];
|
|
144
|
+
var collectionOps = ["add", "revise"];
|
|
145
|
+
function preflightOpDiagnostics(op, operationIndex) {
|
|
146
|
+
const errors = [];
|
|
147
|
+
errors.push(...builtinShapeGuard(op, operationIndex));
|
|
148
|
+
errors.push(...contentNameGuard(op, operationIndex));
|
|
149
|
+
errors.push(...plusSignGuard(op, operationIndex));
|
|
150
|
+
errors.push(...validateCollectionAbouts(op, operationIndex));
|
|
151
|
+
errors.push(...validateCollectionOps(op, operationIndex));
|
|
152
|
+
return errors;
|
|
153
|
+
}
|
|
154
|
+
function builtinShapeGuard(op, operationIndex) {
|
|
155
|
+
const errors = [];
|
|
156
|
+
const name = op.name;
|
|
157
|
+
if (op.kind === "shape" && name && isBuiltinShape(name)) {
|
|
158
|
+
errors.push({
|
|
159
|
+
code: "RESERVED_NAME",
|
|
160
|
+
operationIndex,
|
|
161
|
+
message: `Shape "${name}" is a built-in shape and cannot be ${op.operation === "add" ? "created" : "revised"} manually`
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (op.kind === "thing" && name) {
|
|
165
|
+
const local = splitLocalPath(name);
|
|
166
|
+
if (local && isBuiltinCollectionShape(local.shapePrefix)) {
|
|
167
|
+
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.`;
|
|
168
|
+
errors.push({
|
|
169
|
+
code: "VALIDATION_ERROR",
|
|
170
|
+
operationIndex,
|
|
171
|
+
message
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return errors;
|
|
176
|
+
}
|
|
177
|
+
function contentNameGuard(op, operationIndex) {
|
|
178
|
+
if (op.kind !== "thing" || !op.name) return [];
|
|
179
|
+
const local = splitLocalPath(op.name);
|
|
180
|
+
if (!local || !isContentShape(local.shapePrefix)) return [];
|
|
181
|
+
const bareName = local.bareName;
|
|
182
|
+
if (isSynthesizedContentName(bareName)) {
|
|
183
|
+
return [
|
|
184
|
+
{
|
|
185
|
+
code: "READ_ONLY_BUILTIN_CONTENT",
|
|
186
|
+
operationIndex,
|
|
187
|
+
message: `${local.shapePrefix}/${bareName} is synthesized; writes are not allowed`
|
|
188
|
+
}
|
|
189
|
+
];
|
|
190
|
+
}
|
|
191
|
+
if (!isStoredContentName(bareName)) {
|
|
192
|
+
return [
|
|
193
|
+
{
|
|
194
|
+
code: "UNKNOWN_CONTENT_NAME",
|
|
195
|
+
operationIndex,
|
|
196
|
+
message: `${local.shapePrefix}/${bareName} is not a recognized well-known content name`
|
|
197
|
+
}
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
function plusSignGuard(op, operationIndex) {
|
|
203
|
+
const errors = [];
|
|
204
|
+
const name = op.name;
|
|
205
|
+
if (op.kind === "collection") return errors;
|
|
206
|
+
if (name?.includes("+")) {
|
|
207
|
+
const local = splitLocalPath(name);
|
|
208
|
+
if (local && isBuiltinCollectionShape(local.shapePrefix)) return errors;
|
|
209
|
+
errors.push({
|
|
210
|
+
code: "VALIDATION_ERROR",
|
|
211
|
+
operationIndex,
|
|
212
|
+
message: `Name "${name}" contains reserved character "+". The "+" character is reserved for collection names.`
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return errors;
|
|
216
|
+
}
|
|
217
|
+
function validateCollectionAbouts(op, operationIndex) {
|
|
218
|
+
const errors = [];
|
|
219
|
+
if (!op.about || typeof op.about === "string") return errors;
|
|
220
|
+
if (!isCollectionAbout(op.about)) {
|
|
221
|
+
const keys = typeof op.about === "object" && op.about !== null ? Object.keys(op.about) : [];
|
|
222
|
+
errors.push({
|
|
223
|
+
code: "VALIDATION_ERROR",
|
|
224
|
+
operationIndex,
|
|
225
|
+
message: `Structured about must have exactly one key: pair, triple, set, or list. Got: ${keys.join(", ") || typeof op.about}`
|
|
226
|
+
});
|
|
227
|
+
return errors;
|
|
228
|
+
}
|
|
229
|
+
const tag = collectionAboutType(op.about);
|
|
230
|
+
const members = collectionAboutMembers(op.about);
|
|
231
|
+
for (let i = 0; i < members.length; i++) {
|
|
232
|
+
if (typeof members[i] !== "string") {
|
|
233
|
+
errors.push({
|
|
234
|
+
code: "VALIDATION_ERROR",
|
|
235
|
+
operationIndex,
|
|
236
|
+
message: `Collection member at index ${i} must be a string, got ${typeof members[i]}`
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const arityError = collectionArityError(tag, members);
|
|
241
|
+
if (arityError) {
|
|
242
|
+
errors.push({
|
|
243
|
+
code: "VALIDATION_ERROR",
|
|
244
|
+
operationIndex,
|
|
245
|
+
message: arityError
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return errors;
|
|
249
|
+
}
|
|
250
|
+
function validateCollectionOps(op, operationIndex) {
|
|
251
|
+
const errors = [];
|
|
252
|
+
if (op.kind !== "collection") return errors;
|
|
253
|
+
if (!collectionOps.includes(op.operation)) {
|
|
254
|
+
errors.push({
|
|
255
|
+
code: "VALIDATION_ERROR",
|
|
256
|
+
operationIndex,
|
|
257
|
+
message: `Collection kind only supports "add" or "revise" operation, got "${op.operation}"`
|
|
258
|
+
});
|
|
259
|
+
return errors;
|
|
260
|
+
}
|
|
261
|
+
if (op.operation === "add") {
|
|
262
|
+
if (!op.type || !collectionTypes.includes(op.type)) {
|
|
263
|
+
errors.push({
|
|
264
|
+
code: "VALIDATION_ERROR",
|
|
265
|
+
operationIndex,
|
|
266
|
+
message: `Collection "type" must be one of: pair, triple, set, list. Got: "${op.type ?? ""}"`
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (!op.members || !Array.isArray(op.members)) {
|
|
270
|
+
errors.push({
|
|
271
|
+
code: "VALIDATION_ERROR",
|
|
272
|
+
operationIndex,
|
|
273
|
+
message: 'Collection requires a "members" array'
|
|
274
|
+
});
|
|
275
|
+
} else {
|
|
276
|
+
for (let i = 0; i < op.members.length; i++) {
|
|
277
|
+
if (typeof op.members[i] !== "string") {
|
|
278
|
+
errors.push({
|
|
279
|
+
code: "VALIDATION_ERROR",
|
|
280
|
+
operationIndex,
|
|
281
|
+
message: `Collection member at index ${i} must be a string`
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (op.type && op.members) {
|
|
287
|
+
const arityError = collectionArityError(
|
|
288
|
+
op.type,
|
|
289
|
+
op.members
|
|
290
|
+
);
|
|
291
|
+
if (arityError) {
|
|
292
|
+
errors.push({
|
|
293
|
+
code: "VALIDATION_ERROR",
|
|
294
|
+
operationIndex,
|
|
295
|
+
message: arityError
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return errors;
|
|
301
|
+
}
|
|
302
|
+
function preflightCommitDiagnostics(operations, options) {
|
|
303
|
+
const errors = [];
|
|
304
|
+
tokenHygiene(operations, errors);
|
|
305
|
+
illegalOpSequences(operations, errors);
|
|
306
|
+
return errors;
|
|
307
|
+
}
|
|
308
|
+
function collectionArityError(tag, members) {
|
|
309
|
+
switch (tag) {
|
|
310
|
+
case "pair":
|
|
311
|
+
return members.length !== 2 ? `Pair requires exactly 2 members, got ${members.length}` : null;
|
|
312
|
+
case "triple":
|
|
313
|
+
return members.length !== 3 ? `Triple requires exactly 3 members, got ${members.length}` : null;
|
|
314
|
+
case "set":
|
|
315
|
+
case "list":
|
|
316
|
+
return members.length < 1 ? `${tag === "set" ? "Set" : "List"} requires at least 1 member, got 0` : null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function getOpName(op) {
|
|
320
|
+
return op.name;
|
|
321
|
+
}
|
|
322
|
+
function collectionAboutMembersOrEmpty(about) {
|
|
323
|
+
if (about && typeof about !== "string" && isCollectionAbout(about)) {
|
|
324
|
+
return collectionAboutMembers(about);
|
|
325
|
+
}
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
function tokenStringFields(op) {
|
|
329
|
+
const fields = [getOpName(op)];
|
|
330
|
+
if (typeof op.about === "string") {
|
|
331
|
+
fields.push(op.about);
|
|
332
|
+
} else {
|
|
333
|
+
fields.push(...collectionAboutMembersOrEmpty(op.about));
|
|
334
|
+
}
|
|
335
|
+
if (op.members) {
|
|
336
|
+
fields.push(...op.members);
|
|
337
|
+
}
|
|
338
|
+
return fields;
|
|
339
|
+
}
|
|
340
|
+
function collectMisusedAllocFields(op) {
|
|
341
|
+
const result = [];
|
|
342
|
+
if (op.operation === "add") {
|
|
343
|
+
if (typeof op.about === "string") {
|
|
344
|
+
result.push({ value: op.about });
|
|
345
|
+
}
|
|
346
|
+
for (const m of collectionAboutMembersOrEmpty(op.about)) {
|
|
347
|
+
result.push({ value: m, context: "collection member" });
|
|
348
|
+
}
|
|
349
|
+
if (op.members) {
|
|
350
|
+
for (const m of op.members) {
|
|
351
|
+
result.push({ value: m, context: "collection member" });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
const name = getOpName(op);
|
|
356
|
+
if (name) {
|
|
357
|
+
result.push({ value: name });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
function tokenHygiene(operations, errors) {
|
|
363
|
+
let anyTokens = false;
|
|
364
|
+
for (const op of operations) {
|
|
365
|
+
for (const f of tokenStringFields(op)) {
|
|
366
|
+
if (f && hasAnyTokens(f)) {
|
|
367
|
+
anyTokens = true;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (anyTokens) break;
|
|
372
|
+
}
|
|
373
|
+
if (!anyTokens) return;
|
|
374
|
+
const allocations = /* @__PURE__ */ new Map();
|
|
375
|
+
for (let i = 0; i < operations.length; i++) {
|
|
376
|
+
const op = operations[i];
|
|
377
|
+
if (!op || op.operation !== "add" || !op.name) continue;
|
|
378
|
+
const local = splitLocalPath(op.name);
|
|
379
|
+
if (!local) {
|
|
380
|
+
const allocs = findAllocTokens(op.name);
|
|
381
|
+
if (allocs.length > 0) {
|
|
382
|
+
errors.push({
|
|
383
|
+
code: "ALLOC_TOKEN_IN_SHAPE_NAME",
|
|
384
|
+
operationIndex: i,
|
|
385
|
+
message: `Allocation token $${allocs[0]} not allowed in shape name "${op.name}"`
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const segments = local.bareName.split("/");
|
|
391
|
+
const lastSegment = segments[segments.length - 1];
|
|
392
|
+
const lastSegAllocs = lastSegment ? findAllocTokens(lastSegment) : [];
|
|
393
|
+
for (let s = 0; s < segments.length - 1; s++) {
|
|
394
|
+
const seg = segments[s];
|
|
395
|
+
if (!seg) continue;
|
|
396
|
+
const earlyAllocs = findAllocTokens(seg);
|
|
397
|
+
if (earlyAllocs.length > 0) {
|
|
398
|
+
errors.push({
|
|
399
|
+
code: "ALLOC_TOKEN_WRONG_SEGMENT",
|
|
400
|
+
operationIndex: i,
|
|
401
|
+
message: `Allocation token $${earlyAllocs[0]} must be in the last segment of ADD name "${op.name}"`
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
for (const tokenNum of lastSegAllocs) {
|
|
406
|
+
if (tokenNum <= 0) {
|
|
407
|
+
errors.push({
|
|
408
|
+
code: "INVALID_TOKEN_NUMBER",
|
|
409
|
+
operationIndex: i,
|
|
410
|
+
message: `Token number must be positive: $${tokenNum}`
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
if (allocations.has(tokenNum)) {
|
|
414
|
+
errors.push({
|
|
415
|
+
code: "DUPLICATE_ALLOC_TOKEN",
|
|
416
|
+
operationIndex: i,
|
|
417
|
+
message: `Token $${tokenNum} allocated more than once`
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
allocations.set(tokenNum, { opIndex: i });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (let i = 0; i < operations.length; i++) {
|
|
424
|
+
const op = operations[i];
|
|
425
|
+
if (!op) continue;
|
|
426
|
+
for (const field of tokenStringFields(op)) {
|
|
427
|
+
if (!field) continue;
|
|
428
|
+
const refs = findRefTokens(field);
|
|
429
|
+
for (const tokenNum of refs) {
|
|
430
|
+
if (tokenNum <= 0) {
|
|
431
|
+
errors.push({
|
|
432
|
+
code: "INVALID_TOKEN_NUMBER",
|
|
433
|
+
operationIndex: i,
|
|
434
|
+
message: `Token number must be positive: #${tokenNum}`
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
if (!allocations.has(tokenNum)) {
|
|
438
|
+
errors.push({
|
|
439
|
+
code: "UNRESOLVED_REF_TOKEN",
|
|
440
|
+
operationIndex: i,
|
|
441
|
+
message: `Reference #${tokenNum} has no matching allocation $${tokenNum}`
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const misusedFields = collectMisusedAllocFields(op);
|
|
447
|
+
for (const { value, context } of misusedFields) {
|
|
448
|
+
const allocs = findAllocTokens(value);
|
|
449
|
+
if (allocs.length > 0) {
|
|
450
|
+
errors.push({
|
|
451
|
+
code: "MISUSED_ALLOC_TOKEN",
|
|
452
|
+
operationIndex: i,
|
|
453
|
+
message: context ? `Use #${allocs[0]} to reference token ${allocs[0]} in ${context}, not $${allocs[0]}` : `Use #${allocs[0]} to reference token ${allocs[0]}, not $${allocs[0]}`
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function inferKindFromName(name) {
|
|
460
|
+
const parts = name.split("/").filter(Boolean);
|
|
461
|
+
if (parts.length >= 3) return "assertion";
|
|
462
|
+
if (parts.length === 2) return "thing";
|
|
463
|
+
return "thing";
|
|
464
|
+
}
|
|
465
|
+
function illegalOpSequences(operations, errors, checkAddAdd) {
|
|
466
|
+
const opHistory = /* @__PURE__ */ new Map();
|
|
467
|
+
for (let i = 0; i < operations.length; i++) {
|
|
468
|
+
const op = operations[i];
|
|
469
|
+
if (!op) continue;
|
|
470
|
+
const name = getOpName(op);
|
|
471
|
+
if (!name) continue;
|
|
472
|
+
if (hasAnyTokens(name)) continue;
|
|
473
|
+
const kind = op.kind ?? (name ? inferKindFromName(name) : "thing");
|
|
474
|
+
const qualName = kind === "shape" ? `shape:${name}` : `thing:${name}`;
|
|
475
|
+
const history = opHistory.get(qualName) ?? [];
|
|
476
|
+
history.push({ operation: op.operation, index: i });
|
|
477
|
+
opHistory.set(qualName, history);
|
|
478
|
+
}
|
|
479
|
+
for (const [qualName, history] of opHistory) {
|
|
480
|
+
if (history.length < 2) continue;
|
|
481
|
+
for (let i = 1; i < history.length; i++) {
|
|
482
|
+
const prev = history[i - 1];
|
|
483
|
+
const curr = history[i];
|
|
484
|
+
if (!prev || !curr) continue;
|
|
485
|
+
const pair = `${prev.operation}+${curr.operation}`;
|
|
486
|
+
if (pair === "revise+add") {
|
|
487
|
+
errors.push({
|
|
488
|
+
code: "ILLEGAL_OP_SEQUENCE",
|
|
489
|
+
operationIndex: curr.index,
|
|
490
|
+
message: `Cannot revise then add "${qualName}" in the same commit`
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ../rules/src/reserved-orgs.ts
|
|
498
|
+
var RESERVED_ORG_NAMES = [
|
|
499
|
+
"admin",
|
|
500
|
+
"api",
|
|
501
|
+
"billing",
|
|
502
|
+
"blog",
|
|
503
|
+
"docs",
|
|
504
|
+
"help",
|
|
505
|
+
"login",
|
|
506
|
+
"public",
|
|
507
|
+
"robots.txt",
|
|
508
|
+
"settings",
|
|
509
|
+
"signup",
|
|
510
|
+
"status",
|
|
511
|
+
"support",
|
|
512
|
+
"system",
|
|
513
|
+
"warmhub",
|
|
514
|
+
"www"
|
|
515
|
+
];
|
|
516
|
+
new Set(RESERVED_ORG_NAMES);
|
|
517
|
+
|
|
518
|
+
// ../rules/src/shape-types.ts
|
|
519
|
+
var BASE_PRIMITIVE_TYPES = [
|
|
520
|
+
"number",
|
|
521
|
+
"string",
|
|
522
|
+
"boolean",
|
|
523
|
+
"wref",
|
|
524
|
+
"array"
|
|
525
|
+
];
|
|
526
|
+
new Set(BASE_PRIMITIVE_TYPES);
|
|
527
|
+
var VALID_PRIMITIVE_TYPES = /* @__PURE__ */ new Set([
|
|
528
|
+
"number",
|
|
529
|
+
"string",
|
|
530
|
+
"boolean",
|
|
531
|
+
"wref",
|
|
532
|
+
"array",
|
|
533
|
+
"number?",
|
|
534
|
+
"string?",
|
|
535
|
+
"boolean?",
|
|
536
|
+
"wref?",
|
|
537
|
+
"array?"
|
|
538
|
+
]);
|
|
539
|
+
function isPlainObject(value) {
|
|
540
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
541
|
+
}
|
|
542
|
+
var STRING_CONSTRAINT_KEYS = /* @__PURE__ */ new Set([
|
|
543
|
+
"minLength",
|
|
544
|
+
"maxLength",
|
|
545
|
+
"pattern",
|
|
546
|
+
"enum"
|
|
547
|
+
]);
|
|
548
|
+
var NUMBER_CONSTRAINT_KEYS = /* @__PURE__ */ new Set(["minimum", "maximum", "integer"]);
|
|
549
|
+
var WREF_CONSTRAINT_KEYS = /* @__PURE__ */ new Set(["shape"]);
|
|
550
|
+
var ARRAY_CONSTRAINT_KEYS = /* @__PURE__ */ new Set(["items", "minItems", "maxItems"]);
|
|
551
|
+
var COMMON_TYPESPEC_KEYS = /* @__PURE__ */ new Set(["type", "description"]);
|
|
552
|
+
var VALID_TYPESPEC_KEYS = /* @__PURE__ */ new Set([
|
|
553
|
+
...COMMON_TYPESPEC_KEYS,
|
|
554
|
+
...STRING_CONSTRAINT_KEYS,
|
|
555
|
+
...NUMBER_CONSTRAINT_KEYS,
|
|
556
|
+
...WREF_CONSTRAINT_KEYS,
|
|
557
|
+
...ARRAY_CONSTRAINT_KEYS
|
|
558
|
+
]);
|
|
559
|
+
var CONSTRAINT_KEYS_BY_TYPE = {
|
|
560
|
+
string: STRING_CONSTRAINT_KEYS,
|
|
561
|
+
number: NUMBER_CONSTRAINT_KEYS,
|
|
562
|
+
wref: WREF_CONSTRAINT_KEYS,
|
|
563
|
+
array: ARRAY_CONSTRAINT_KEYS,
|
|
564
|
+
boolean: /* @__PURE__ */ new Set()
|
|
565
|
+
// no constraints
|
|
566
|
+
};
|
|
567
|
+
function isTypeSpecObject(value) {
|
|
568
|
+
if (!isPlainObject(value) || !("type" in value)) return false;
|
|
569
|
+
if (typeof value.type !== "string" || !VALID_PRIMITIVE_TYPES.has(value.type))
|
|
570
|
+
return false;
|
|
571
|
+
const baseType = value.type.endsWith("?") ? value.type.slice(0, -1) : value.type;
|
|
572
|
+
const allowedConstraints = CONSTRAINT_KEYS_BY_TYPE[baseType] ?? /* @__PURE__ */ new Set();
|
|
573
|
+
if (!Object.keys(value).every(
|
|
574
|
+
(k) => COMMON_TYPESPEC_KEYS.has(k) || allowedConstraints.has(k)
|
|
575
|
+
))
|
|
576
|
+
return false;
|
|
577
|
+
if ("minLength" in value && typeof value.minLength !== "number") return false;
|
|
578
|
+
if ("maxLength" in value && typeof value.maxLength !== "number") return false;
|
|
579
|
+
if ("pattern" in value && typeof value.pattern !== "string") return false;
|
|
580
|
+
if ("pattern" in value && typeof value.pattern === "string" && VALID_PRIMITIVE_TYPES.has(value.pattern)) {
|
|
581
|
+
const baseType2 = typeof value.type === "string" && value.type.endsWith("?") ? value.type.slice(0, -1) : value.type;
|
|
582
|
+
if (baseType2 !== "string") return false;
|
|
583
|
+
}
|
|
584
|
+
if ("enum" in value && !Array.isArray(value.enum)) return false;
|
|
585
|
+
if ("minimum" in value && typeof value.minimum !== "number") return false;
|
|
586
|
+
if ("maximum" in value && typeof value.maximum !== "number") return false;
|
|
587
|
+
if ("integer" in value && typeof value.integer !== "boolean") return false;
|
|
588
|
+
if ("shape" in value && typeof value.shape !== "string") return false;
|
|
589
|
+
if ("shape" in value && typeof value.shape === "string" && VALID_PRIMITIVE_TYPES.has(value.shape)) {
|
|
590
|
+
const baseType2 = typeof value.type === "string" && value.type.endsWith("?") ? value.type.slice(0, -1) : value.type;
|
|
591
|
+
if (baseType2 !== "wref") return false;
|
|
592
|
+
}
|
|
593
|
+
if ("items" in value && typeof value.items === "number") return false;
|
|
594
|
+
if ("items" in value && typeof value.items === "boolean") return false;
|
|
595
|
+
if ("minItems" in value && typeof value.minItems !== "number") return false;
|
|
596
|
+
if ("maxItems" in value && typeof value.maxItems !== "number") return false;
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ../rules/src/shape-validation.ts
|
|
601
|
+
var MAX_CONTENT_FIELD_BYTES = 64 * 1024;
|
|
602
|
+
var CONTENT_FIELD_LIMIT_ERROR = `WarmHub content fields are limited to ${MAX_CONTENT_FIELD_BYTES} bytes. WarmHub is not a document store; store large documents in S3, Box, Drive, or another document system and reference them from WarmHub instead.`;
|
|
603
|
+
var textEncoder = new TextEncoder();
|
|
604
|
+
function utf8ByteLength(value) {
|
|
605
|
+
return textEncoder.encode(value).byteLength;
|
|
606
|
+
}
|
|
607
|
+
function contentFieldLimitError(path, value) {
|
|
608
|
+
if (value.length * 3 <= MAX_CONTENT_FIELD_BYTES) return null;
|
|
609
|
+
const byteLength = utf8ByteLength(value);
|
|
610
|
+
if (byteLength <= MAX_CONTENT_FIELD_BYTES) return null;
|
|
611
|
+
return `Field "${path}" is ${byteLength} bytes; ${CONTENT_FIELD_LIMIT_ERROR}`;
|
|
612
|
+
}
|
|
613
|
+
function assertContentFieldWithinLimit(path, value, errors) {
|
|
614
|
+
const message = contentFieldLimitError(path, value);
|
|
615
|
+
if (message) errors.push(message);
|
|
616
|
+
}
|
|
617
|
+
function normalizeOptionalTypeSpec(spec) {
|
|
618
|
+
if (typeof spec === "string" && spec.endsWith("?")) {
|
|
619
|
+
return { spec: spec.slice(0, -1), optionalByType: true };
|
|
620
|
+
}
|
|
621
|
+
if (isTypeSpecObject(spec) && typeof spec.type === "string" && spec.type.endsWith("?")) {
|
|
622
|
+
return {
|
|
623
|
+
spec: { ...spec, type: spec.type.slice(0, -1) },
|
|
624
|
+
optionalByType: true
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
return { spec, optionalByType: false };
|
|
628
|
+
}
|
|
629
|
+
function validateTypedFieldObject(path, spec, errors) {
|
|
630
|
+
const typeValue = spec.type;
|
|
631
|
+
if (typeof typeValue !== "string") {
|
|
632
|
+
errors.push(
|
|
633
|
+
`Invalid typed field object at "${path}": "type" must be a string`
|
|
634
|
+
);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (!VALID_PRIMITIVE_TYPES.has(typeValue)) {
|
|
638
|
+
errors.push(
|
|
639
|
+
`Invalid type at "${path}.type": "${typeValue}" (expected number|string|boolean|wref|array, optionally with ? suffix)`
|
|
640
|
+
);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if ("description" in spec && typeof spec.description !== "string") {
|
|
644
|
+
errors.push(
|
|
645
|
+
`Invalid typed field object at "${path}": "description" must be a string`
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
const baseType = typeValue.endsWith("?") ? typeValue.slice(0, -1) : typeValue;
|
|
649
|
+
for (const key of Object.keys(spec)) {
|
|
650
|
+
if (key === "type" || key === "description") continue;
|
|
651
|
+
if (baseType === "string") {
|
|
652
|
+
if (!STRING_CONSTRAINT_KEYS.has(key)) {
|
|
653
|
+
errors.push(
|
|
654
|
+
`Constraint "${key}" at "${path}" is not valid for type "string"`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
} else if (baseType === "number") {
|
|
658
|
+
if (!NUMBER_CONSTRAINT_KEYS.has(key)) {
|
|
659
|
+
errors.push(
|
|
660
|
+
`Constraint "${key}" at "${path}" is not valid for type "number"`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
} else if (baseType === "wref") {
|
|
664
|
+
if (!WREF_CONSTRAINT_KEYS.has(key)) {
|
|
665
|
+
errors.push(
|
|
666
|
+
`Constraint "${key}" at "${path}" is not valid for type "wref"`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
} else if (baseType === "array") {
|
|
670
|
+
if (!ARRAY_CONSTRAINT_KEYS.has(key)) {
|
|
671
|
+
errors.push(
|
|
672
|
+
`Constraint "${key}" at "${path}" is not valid for type "array"`
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
} else {
|
|
676
|
+
errors.push(
|
|
677
|
+
`Constraint "${key}" at "${path}" is not valid for type "${baseType}"`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if ("minLength" in spec && (typeof spec.minLength !== "number" || !Number.isFinite(spec.minLength) || spec.minLength < 0 || !Number.isInteger(spec.minLength))) {
|
|
682
|
+
errors.push(
|
|
683
|
+
`"minLength" at "${path}" must be a non-negative finite integer`
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
if ("maxLength" in spec && (typeof spec.maxLength !== "number" || !Number.isFinite(spec.maxLength) || spec.maxLength < 0 || !Number.isInteger(spec.maxLength))) {
|
|
687
|
+
errors.push(
|
|
688
|
+
`"maxLength" at "${path}" must be a non-negative finite integer`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
if ("minLength" in spec && "maxLength" in spec && typeof spec.minLength === "number" && typeof spec.maxLength === "number" && spec.minLength > spec.maxLength) {
|
|
692
|
+
errors.push(`"minLength" at "${path}" cannot exceed "maxLength"`);
|
|
693
|
+
}
|
|
694
|
+
if (baseType === "string" && typeof spec.minLength === "number" && Number.isFinite(spec.minLength) && Number.isInteger(spec.minLength) && spec.minLength > MAX_CONTENT_FIELD_BYTES) {
|
|
695
|
+
errors.push(
|
|
696
|
+
`"minLength" at "${path}" cannot exceed ${MAX_CONTENT_FIELD_BYTES}`
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
if (baseType === "string" && typeof spec.maxLength === "number" && Number.isFinite(spec.maxLength) && Number.isInteger(spec.maxLength) && spec.maxLength > MAX_CONTENT_FIELD_BYTES) {
|
|
700
|
+
errors.push(
|
|
701
|
+
`"maxLength" at "${path}" cannot exceed ${MAX_CONTENT_FIELD_BYTES}`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
if ("pattern" in spec && typeof spec.pattern !== "string") {
|
|
705
|
+
errors.push(`"pattern" at "${path}" must be a string`);
|
|
706
|
+
}
|
|
707
|
+
if ("pattern" in spec && typeof spec.pattern === "string") {
|
|
708
|
+
try {
|
|
709
|
+
new RegExp(spec.pattern);
|
|
710
|
+
} catch {
|
|
711
|
+
errors.push(`"pattern" at "${path}" is not a valid regular expression`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if ("enum" in spec) {
|
|
715
|
+
if (!Array.isArray(spec.enum) || spec.enum.length === 0 || !spec.enum.every((v) => typeof v === "string")) {
|
|
716
|
+
errors.push(`"enum" at "${path}" must be a non-empty array of strings`);
|
|
717
|
+
} else if (baseType === "string") {
|
|
718
|
+
for (const value of spec.enum) {
|
|
719
|
+
const byteLength = utf8ByteLength(value);
|
|
720
|
+
if (byteLength > MAX_CONTENT_FIELD_BYTES) {
|
|
721
|
+
errors.push(
|
|
722
|
+
`"enum" value at "${path}" is ${byteLength} bytes; string enum values cannot exceed ${MAX_CONTENT_FIELD_BYTES} bytes`
|
|
723
|
+
);
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if ("minimum" in spec && (typeof spec.minimum !== "number" || !Number.isFinite(spec.minimum))) {
|
|
730
|
+
errors.push(`"minimum" at "${path}" must be a finite number`);
|
|
731
|
+
}
|
|
732
|
+
if ("maximum" in spec && (typeof spec.maximum !== "number" || !Number.isFinite(spec.maximum))) {
|
|
733
|
+
errors.push(`"maximum" at "${path}" must be a finite number`);
|
|
734
|
+
}
|
|
735
|
+
if ("minimum" in spec && "maximum" in spec && typeof spec.minimum === "number" && typeof spec.maximum === "number" && spec.minimum > spec.maximum) {
|
|
736
|
+
errors.push(`"minimum" at "${path}" cannot exceed "maximum"`);
|
|
737
|
+
}
|
|
738
|
+
if ("integer" in spec && typeof spec.integer !== "boolean") {
|
|
739
|
+
errors.push(`"integer" at "${path}" must be a boolean`);
|
|
740
|
+
}
|
|
741
|
+
if ("shape" in spec) {
|
|
742
|
+
if (typeof spec.shape !== "string") {
|
|
743
|
+
errors.push(`"shape" at "${path}" must be a string (shape name)`);
|
|
744
|
+
} else if (spec.shape === "") {
|
|
745
|
+
errors.push(`"shape" at "${path}" must not be empty`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if ("minItems" in spec && (typeof spec.minItems !== "number" || !Number.isFinite(spec.minItems) || spec.minItems < 0 || !Number.isInteger(spec.minItems))) {
|
|
749
|
+
errors.push(`"minItems" at "${path}" must be a non-negative finite integer`);
|
|
750
|
+
}
|
|
751
|
+
if ("maxItems" in spec && (typeof spec.maxItems !== "number" || !Number.isFinite(spec.maxItems) || spec.maxItems < 0 || !Number.isInteger(spec.maxItems))) {
|
|
752
|
+
errors.push(`"maxItems" at "${path}" must be a non-negative finite integer`);
|
|
753
|
+
}
|
|
754
|
+
if ("minItems" in spec && "maxItems" in spec && typeof spec.minItems === "number" && typeof spec.maxItems === "number" && spec.minItems > spec.maxItems) {
|
|
755
|
+
errors.push(`"minItems" at "${path}" cannot exceed "maxItems"`);
|
|
756
|
+
}
|
|
757
|
+
if (baseType === "array") {
|
|
758
|
+
if (!("items" in spec)) {
|
|
759
|
+
errors.push(
|
|
760
|
+
`Typed array at "${path}" must have an "items" key specifying the element type`
|
|
761
|
+
);
|
|
762
|
+
} else {
|
|
763
|
+
validateTypeSpec(`${path}.items`, spec.items, errors);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
function validateTypeSpec(path, spec, errors) {
|
|
768
|
+
if (typeof spec === "string") {
|
|
769
|
+
if (!VALID_PRIMITIVE_TYPES.has(spec)) {
|
|
770
|
+
errors.push(
|
|
771
|
+
`Invalid type at "${path}": "${spec}" (expected number|string|boolean|wref|array, optionally with ? suffix)`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (Array.isArray(spec)) {
|
|
777
|
+
if (spec.length !== 1) {
|
|
778
|
+
errors.push(
|
|
779
|
+
`Invalid array type at "${path}": array type spec must have exactly 1 element`
|
|
780
|
+
);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
validateTypeSpec(`${path}[]`, spec[0], errors);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (isPlainObject(spec)) {
|
|
787
|
+
if (isTypeSpecObject(spec)) {
|
|
788
|
+
validateTypedFieldObject(path, spec, errors);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if ("type" in spec && typeof spec.type === "string" && VALID_PRIMITIVE_TYPES.has(spec.type) && Object.keys(spec).every((k) => VALID_TYPESPEC_KEYS.has(k))) {
|
|
792
|
+
const baseType = spec.type.endsWith("?") ? spec.type.slice(0, -1) : spec.type;
|
|
793
|
+
const EMPTY_SET = /* @__PURE__ */ new Set();
|
|
794
|
+
const allowedForType = baseType === "string" ? STRING_CONSTRAINT_KEYS : baseType === "number" ? NUMBER_CONSTRAINT_KEYS : baseType === "wref" ? WREF_CONSTRAINT_KEYS : baseType === "array" ? ARRAY_CONSTRAINT_KEYS : EMPTY_SET;
|
|
795
|
+
const crossTypeKeys = Object.keys(spec).filter(
|
|
796
|
+
(k) => k !== "type" && k !== "description" && !allowedForType.has(k)
|
|
797
|
+
);
|
|
798
|
+
if (crossTypeKeys.length > 0) {
|
|
799
|
+
const hasNonTypeSpecValues = crossTypeKeys.some((k) => {
|
|
800
|
+
const v = spec[k];
|
|
801
|
+
return typeof v !== "string" && !Array.isArray(v) && !isPlainObject(v);
|
|
802
|
+
});
|
|
803
|
+
if (hasNonTypeSpecValues) {
|
|
804
|
+
for (const key of crossTypeKeys) {
|
|
805
|
+
errors.push(
|
|
806
|
+
`Constraint "${key}" at "${path}" is not valid for type "${baseType}"`
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (crossTypeKeys.length === 0) {
|
|
813
|
+
const constraintErrors = [];
|
|
814
|
+
for (const [k, v] of Object.entries(spec)) {
|
|
815
|
+
if (k === "type" || k === "description") continue;
|
|
816
|
+
if (!allowedForType.has(k)) continue;
|
|
817
|
+
if ((k === "minLength" || k === "maxLength" || k === "minimum" || k === "maximum" || k === "minItems" || k === "maxItems") && typeof v !== "number") {
|
|
818
|
+
constraintErrors.push(
|
|
819
|
+
`Constraint "${k}" at "${path}" must be a number, got ${typeof v}`
|
|
820
|
+
);
|
|
821
|
+
} else if (k === "integer" && typeof v !== "boolean") {
|
|
822
|
+
constraintErrors.push(
|
|
823
|
+
`Constraint "${k}" at "${path}" must be a boolean, got ${typeof v}`
|
|
824
|
+
);
|
|
825
|
+
} else if (k === "pattern" && typeof v !== "string") {
|
|
826
|
+
constraintErrors.push(
|
|
827
|
+
`Constraint "${k}" at "${path}" must be a string, got ${typeof v}`
|
|
828
|
+
);
|
|
829
|
+
} else if (k === "enum" && !Array.isArray(v)) {
|
|
830
|
+
constraintErrors.push(
|
|
831
|
+
`Constraint "${k}" at "${path}" must be an array, got ${typeof v}`
|
|
832
|
+
);
|
|
833
|
+
} else if (k === "shape" && typeof v !== "string") {
|
|
834
|
+
constraintErrors.push(
|
|
835
|
+
`Constraint "${k}" at "${path}" must be a string, got ${typeof v}`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (constraintErrors.length > 0) {
|
|
840
|
+
errors.push(...constraintErrors);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const hasWrongValueTypes = Object.entries(spec).some(([k, v]) => {
|
|
845
|
+
if (k === "type" || k === "description") return false;
|
|
846
|
+
if (typeof v === "string" || Array.isArray(v) || isPlainObject(v))
|
|
847
|
+
return false;
|
|
848
|
+
return true;
|
|
849
|
+
});
|
|
850
|
+
if (hasWrongValueTypes) {
|
|
851
|
+
errors.push(
|
|
852
|
+
`Object at "${path}" looks like a typed field object (type: "${spec.type}") but has constraint values with invalid types. Check that constraint values match their expected types (e.g., minLength must be a number, not a string).`
|
|
853
|
+
);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
for (const [key, value] of Object.entries(spec)) {
|
|
858
|
+
validateTypeSpec(`${path}.${key}`, value, errors);
|
|
859
|
+
}
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
errors.push(
|
|
863
|
+
`Invalid type at "${path}": expected string, array, or object, got ${typeof spec}`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
var ALLOWED_SHAPE_KEYS = /* @__PURE__ */ new Set(["fields", "description"]);
|
|
867
|
+
function validateShapeDefinition(data) {
|
|
868
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
869
|
+
return { valid: false, errors: ["Shape definition must be a plain object"] };
|
|
870
|
+
}
|
|
871
|
+
const obj = data;
|
|
872
|
+
const errors = [];
|
|
873
|
+
for (const key of Object.keys(obj)) {
|
|
874
|
+
if (!ALLOWED_SHAPE_KEYS.has(key)) {
|
|
875
|
+
errors.push(`Unsupported shape definition key "${key}"`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (!("fields" in obj)) {
|
|
879
|
+
return {
|
|
880
|
+
valid: false,
|
|
881
|
+
errors: ['Shape definition must have a "fields" property']
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
const fields = obj.fields;
|
|
885
|
+
if (typeof fields !== "object" || fields === null || Array.isArray(fields)) {
|
|
886
|
+
return {
|
|
887
|
+
valid: false,
|
|
888
|
+
errors: ['"fields" must be a plain object mapping field names to types']
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
for (const [key, value] of Object.entries(
|
|
892
|
+
fields
|
|
893
|
+
)) {
|
|
894
|
+
validateTypeSpec(`fields.${key}`, value, errors);
|
|
895
|
+
}
|
|
896
|
+
if ("description" in obj && typeof obj.description !== "string") {
|
|
897
|
+
errors.push('"description" must be a string');
|
|
898
|
+
}
|
|
899
|
+
validatePersistedStringFieldLimits(obj, void 0, errors);
|
|
900
|
+
if (errors.length > 0) {
|
|
901
|
+
return { valid: false, errors };
|
|
902
|
+
}
|
|
903
|
+
return { valid: true };
|
|
904
|
+
}
|
|
905
|
+
function validateAgainstShape(data, shapeFields) {
|
|
906
|
+
const errors = [];
|
|
907
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
908
|
+
return { valid: false, errors: ["Data must be a plain object"] };
|
|
909
|
+
}
|
|
910
|
+
for (const [fieldSpec, rawTypeDef] of Object.entries(shapeFields)) {
|
|
911
|
+
const isOptionalByKey = fieldSpec.endsWith("?");
|
|
912
|
+
const fieldName = isOptionalByKey ? fieldSpec.slice(0, -1) : fieldSpec;
|
|
913
|
+
const normalized = normalizeOptionalTypeSpec(rawTypeDef);
|
|
914
|
+
const isOptional = isOptionalByKey || normalized.optionalByType;
|
|
915
|
+
const value = data[fieldName];
|
|
916
|
+
if (value === void 0 || value === null) {
|
|
917
|
+
if (!isOptional) {
|
|
918
|
+
errors.push(`Missing required field: "${fieldName}"`);
|
|
919
|
+
}
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
validateField(fieldName, value, normalized.spec, errors);
|
|
923
|
+
}
|
|
924
|
+
validatePersistedStringFieldLimits(data, shapeFields, errors);
|
|
925
|
+
const warnings = buildUndeclaredFieldsWarning(data, shapeFields);
|
|
926
|
+
if (errors.length > 0) {
|
|
927
|
+
return warnings ? { valid: false, errors, warnings } : { valid: false, errors };
|
|
928
|
+
}
|
|
929
|
+
return warnings ? { valid: true, warnings } : { valid: true };
|
|
930
|
+
}
|
|
931
|
+
function buildUndeclaredFieldsWarning(data, shapeFields) {
|
|
932
|
+
return buildUndeclaredFieldsWarningFromDeclaredSet(
|
|
933
|
+
data,
|
|
934
|
+
declaredFieldNames(shapeFields)
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
function declaredFieldNames(shapeFields) {
|
|
938
|
+
const declared = /* @__PURE__ */ new Set();
|
|
939
|
+
for (const key of Object.keys(shapeFields)) {
|
|
940
|
+
declared.add(key.endsWith("?") ? key.slice(0, -1) : key);
|
|
941
|
+
}
|
|
942
|
+
return declared;
|
|
943
|
+
}
|
|
944
|
+
function buildUndeclaredFieldsWarningFromDeclaredSet(data, declared) {
|
|
945
|
+
const undeclaredFields = [];
|
|
946
|
+
let totalUndeclared = 0;
|
|
947
|
+
for (const key of Object.keys(data)) {
|
|
948
|
+
if (declared.has(key)) continue;
|
|
949
|
+
totalUndeclared++;
|
|
950
|
+
if (undeclaredFields.length < MAX_UNDECLARED_FIELDS_REPORTED) {
|
|
951
|
+
undeclaredFields.push(key);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (totalUndeclared === 0) return void 0;
|
|
955
|
+
if (totalUndeclared > MAX_UNDECLARED_FIELDS_REPORTED) {
|
|
956
|
+
return {
|
|
957
|
+
undeclaredFields,
|
|
958
|
+
undeclaredFieldsTruncated: true,
|
|
959
|
+
totalUndeclared
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
return { undeclaredFields };
|
|
963
|
+
}
|
|
964
|
+
var MAX_UNDECLARED_FIELDS_REPORTED = 500;
|
|
965
|
+
function validatePersistedStringFieldLimits(value, typeDef, errors, path) {
|
|
966
|
+
const normalized = normalizeOptionalTypeSpec(typeDef).spec;
|
|
967
|
+
if (typeof value === "string") {
|
|
968
|
+
if (isDeclaredStringType(normalized) || isDeclaredWrefType(normalized))
|
|
969
|
+
return;
|
|
970
|
+
if (normalized !== void 0) return;
|
|
971
|
+
assertContentFieldWithinLimit(path ?? "<value>", value, errors);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (Array.isArray(value)) {
|
|
975
|
+
const elementType = arrayElementType(normalized);
|
|
976
|
+
if (normalized !== void 0 && elementType === void 0) return;
|
|
977
|
+
for (let i = 0; i < value.length; i++) {
|
|
978
|
+
validatePersistedStringFieldLimits(
|
|
979
|
+
value[i],
|
|
980
|
+
elementType,
|
|
981
|
+
errors,
|
|
982
|
+
`${path ?? "<value>"}[${i}]`
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (!isPlainObject(value)) return;
|
|
988
|
+
const fields = nestedObjectFields(normalized);
|
|
989
|
+
if (normalized !== void 0 && fields === void 0) return;
|
|
990
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
991
|
+
validatePersistedStringFieldLimits(
|
|
992
|
+
nestedValue,
|
|
993
|
+
fields?.get(key),
|
|
994
|
+
errors,
|
|
995
|
+
path ? `${path}.${key}` : key
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function isDeclaredStringType(typeDef) {
|
|
1000
|
+
if (typeDef === "string") return true;
|
|
1001
|
+
return isTypeSpecObject(typeDef) && typeDef.type === "string";
|
|
1002
|
+
}
|
|
1003
|
+
function isDeclaredWrefType(typeDef) {
|
|
1004
|
+
if (typeDef === "wref") return true;
|
|
1005
|
+
return isTypeSpecObject(typeDef) && typeDef.type === "wref";
|
|
1006
|
+
}
|
|
1007
|
+
function arrayElementType(typeDef) {
|
|
1008
|
+
if (Array.isArray(typeDef)) return typeDef[0];
|
|
1009
|
+
if (isTypeSpecObject(typeDef) && typeDef.type === "array")
|
|
1010
|
+
return typeDef.items;
|
|
1011
|
+
return void 0;
|
|
1012
|
+
}
|
|
1013
|
+
function nestedObjectFields(typeDef) {
|
|
1014
|
+
if (!isPlainObject(typeDef) || isTypeSpecObject(typeDef)) return void 0;
|
|
1015
|
+
const fields = /* @__PURE__ */ new Map();
|
|
1016
|
+
for (const [key, value] of Object.entries(typeDef)) {
|
|
1017
|
+
fields.set(key.endsWith("?") ? key.slice(0, -1) : key, value);
|
|
1018
|
+
}
|
|
1019
|
+
return fields;
|
|
1020
|
+
}
|
|
1021
|
+
function validatePrimitiveField(path, value, typeDef, errors) {
|
|
1022
|
+
switch (typeDef) {
|
|
1023
|
+
case "number":
|
|
1024
|
+
if (typeof value !== "number") {
|
|
1025
|
+
errors.push(`Field "${path}" expected number, got ${typeof value}`);
|
|
1026
|
+
}
|
|
1027
|
+
break;
|
|
1028
|
+
case "string":
|
|
1029
|
+
if (typeof value !== "string") {
|
|
1030
|
+
errors.push(`Field "${path}" expected string, got ${typeof value}`);
|
|
1031
|
+
} else {
|
|
1032
|
+
assertContentFieldWithinLimit(path, value, errors);
|
|
1033
|
+
}
|
|
1034
|
+
break;
|
|
1035
|
+
case "wref":
|
|
1036
|
+
if (typeof value !== "string") {
|
|
1037
|
+
errors.push(`Field "${path}" expected string, got ${typeof value}`);
|
|
1038
|
+
}
|
|
1039
|
+
break;
|
|
1040
|
+
case "boolean":
|
|
1041
|
+
if (typeof value !== "boolean") {
|
|
1042
|
+
errors.push(`Field "${path}" expected boolean, got ${typeof value}`);
|
|
1043
|
+
}
|
|
1044
|
+
break;
|
|
1045
|
+
case "array":
|
|
1046
|
+
if (!Array.isArray(value)) {
|
|
1047
|
+
errors.push(`Field "${path}" expected array, got ${typeof value}`);
|
|
1048
|
+
}
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
function validateNestedObjectField(path, value, typeDef, errors) {
|
|
1053
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1054
|
+
errors.push(`Field "${path}" expected object, got ${typeof value}`);
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
const objValue = value;
|
|
1058
|
+
for (const [subKey, rawSubType] of Object.entries(typeDef)) {
|
|
1059
|
+
const isOptionalByKey = subKey.endsWith("?");
|
|
1060
|
+
const fieldName = isOptionalByKey ? subKey.slice(0, -1) : subKey;
|
|
1061
|
+
const normalized = normalizeOptionalTypeSpec(rawSubType);
|
|
1062
|
+
const isOptional = isOptionalByKey || normalized.optionalByType;
|
|
1063
|
+
const subValue = objValue[fieldName];
|
|
1064
|
+
if (subValue === void 0 || subValue === null) {
|
|
1065
|
+
if (!isOptional) {
|
|
1066
|
+
errors.push(`Missing required field: "${path}.${fieldName}"`);
|
|
1067
|
+
}
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
validateField(`${path}.${fieldName}`, subValue, normalized.spec, errors);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function validateField(path, value, typeDef, errors) {
|
|
1074
|
+
if (typeof typeDef === "string") {
|
|
1075
|
+
validatePrimitiveField(path, value, typeDef, errors);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
if (Array.isArray(typeDef)) {
|
|
1079
|
+
if (!Array.isArray(value)) {
|
|
1080
|
+
errors.push(`Field "${path}" expected array, got ${typeof value}`);
|
|
1081
|
+
} else if (typeDef.length > 0) {
|
|
1082
|
+
const rawElementType = typeDef[0];
|
|
1083
|
+
const { spec: elementType } = normalizeOptionalTypeSpec(rawElementType);
|
|
1084
|
+
for (let i = 0; i < value.length; i++) {
|
|
1085
|
+
validateField(`${path}[${i}]`, value[i], elementType, errors);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
if (isTypeSpecObject(typeDef)) {
|
|
1091
|
+
if (typeof typeDef.type === "string") {
|
|
1092
|
+
const baseType = typeDef.type.endsWith("?") ? typeDef.type.slice(0, -1) : typeDef.type;
|
|
1093
|
+
if (baseType === "array") {
|
|
1094
|
+
validateTypedArrayField(path, value, typeDef, errors);
|
|
1095
|
+
} else {
|
|
1096
|
+
validatePrimitiveField(path, value, typeDef.type, errors);
|
|
1097
|
+
validateConstraints(path, value, typeDef, errors);
|
|
1098
|
+
}
|
|
1099
|
+
} else {
|
|
1100
|
+
errors.push(
|
|
1101
|
+
`Field "${path}" has malformed type spec: "type" must be a string`
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
if (isPlainObject(typeDef)) {
|
|
1107
|
+
validateNestedObjectField(path, value, typeDef, errors);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function validateTypedArrayField(path, value, spec, errors) {
|
|
1111
|
+
if (!Array.isArray(value)) {
|
|
1112
|
+
errors.push(`Field "${path}" expected array, got ${typeof value}`);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
if (typeof spec.minItems === "number" && value.length < spec.minItems) {
|
|
1116
|
+
errors.push(
|
|
1117
|
+
`Field "${path}" has ${value.length} items, minimum is ${spec.minItems}`
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
if (typeof spec.maxItems === "number" && value.length > spec.maxItems) {
|
|
1121
|
+
errors.push(
|
|
1122
|
+
`Field "${path}" has ${value.length} items, maximum is ${spec.maxItems}`
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
if ("items" in spec && spec.items != null) {
|
|
1126
|
+
const { spec: elementType } = normalizeOptionalTypeSpec(spec.items);
|
|
1127
|
+
if (typeof elementType === "string" || Array.isArray(elementType) || isPlainObject(elementType)) {
|
|
1128
|
+
for (let i = 0; i < value.length; i++) {
|
|
1129
|
+
validateField(`${path}[${i}]`, value[i], elementType, errors);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function validateConstraints(path, value, spec, errors) {
|
|
1135
|
+
if (typeof value === "string") {
|
|
1136
|
+
if (typeof spec.minLength === "number" && value.length < spec.minLength) {
|
|
1137
|
+
errors.push(
|
|
1138
|
+
`Field "${path}" length ${value.length} is below minimum ${spec.minLength}`
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
if (typeof spec.maxLength === "number" && value.length > spec.maxLength) {
|
|
1142
|
+
errors.push(
|
|
1143
|
+
`Field "${path}" length ${value.length} exceeds maximum ${spec.maxLength}`
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
if (typeof spec.pattern === "string") {
|
|
1147
|
+
try {
|
|
1148
|
+
if (!new RegExp(spec.pattern).test(value)) {
|
|
1149
|
+
errors.push(
|
|
1150
|
+
`Field "${path}" does not match pattern "${spec.pattern}"`
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
} catch {
|
|
1154
|
+
errors.push(
|
|
1155
|
+
`Field "${path}" has invalid pattern "${spec.pattern}" \u2014 skipping regex check`
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (Array.isArray(spec.enum) && !spec.enum.includes(value)) {
|
|
1160
|
+
errors.push(
|
|
1161
|
+
`Field "${path}" value "${value}" is not in allowed values: ${spec.enum.join(", ")}`
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (typeof value === "number") {
|
|
1166
|
+
if (typeof spec.minimum === "number" && value < spec.minimum) {
|
|
1167
|
+
errors.push(
|
|
1168
|
+
`Field "${path}" value ${value} is below minimum ${spec.minimum}`
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
if (typeof spec.maximum === "number" && value > spec.maximum) {
|
|
1172
|
+
errors.push(
|
|
1173
|
+
`Field "${path}" value ${value} exceeds maximum ${spec.maximum}`
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
if (spec.integer === true && !Number.isInteger(value)) {
|
|
1177
|
+
errors.push(`Field "${path}" must be an integer, got ${value}`);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// ../rules/src/system-components/identity.ts
|
|
1183
|
+
var IDENTITY_COMPONENT_ID = "com.warmhub.identity";
|
|
1184
|
+
var IDENTITY_FIELDS = {
|
|
1185
|
+
// Required. Conventionally equal to the thing's name (the wref tail) —
|
|
1186
|
+
// Identity things are addressed as Identity/<external_id>. Kept as a
|
|
1187
|
+
// field for explicit schema visibility and to leave room for callers
|
|
1188
|
+
// that read the body without resolving the wref. v1 enforces this only
|
|
1189
|
+
// by convention; a commit-time validator can be added when a consumer
|
|
1190
|
+
// needs hard guarantees.
|
|
1191
|
+
external_id: "string",
|
|
1192
|
+
display_name: "string"
|
|
1193
|
+
// Deferred fields, added as additive shape revisions when their consumer
|
|
1194
|
+
// ticket lands: email, avatar_url, profile, kind. Each consumer (#2548,
|
|
1195
|
+
// #2549, #2550) is the trigger for adding the corresponding optional field.
|
|
1196
|
+
};
|
|
1197
|
+
var IDENTITY_MANIFEST = {
|
|
1198
|
+
component: {
|
|
1199
|
+
id: IDENTITY_COMPONENT_ID,
|
|
1200
|
+
name: "identity",
|
|
1201
|
+
version: "1.0.0"
|
|
1202
|
+
},
|
|
1203
|
+
shapes: [{ name: "Identity", fields: IDENTITY_FIELDS }],
|
|
1204
|
+
credentials: [],
|
|
1205
|
+
subscriptions: [],
|
|
1206
|
+
seeds: [],
|
|
1207
|
+
health: { requires: { shapes: ["Identity"] } },
|
|
1208
|
+
teardown: {},
|
|
1209
|
+
runtimeAccess: { reads: [], writes: [] }
|
|
1210
|
+
};
|
|
1211
|
+
var IDENTITY_COMPONENT = {
|
|
1212
|
+
componentId: IDENTITY_COMPONENT_ID,
|
|
1213
|
+
name: IDENTITY_MANIFEST.component.name,
|
|
1214
|
+
version: IDENTITY_MANIFEST.component.version,
|
|
1215
|
+
manifest: IDENTITY_MANIFEST,
|
|
1216
|
+
shapes: [{ name: "Identity", fields: IDENTITY_FIELDS }]
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
// ../rules/src/system-components/system.ts
|
|
1220
|
+
var SYSTEM_COMPONENT_ID = "com.warmhub.system";
|
|
1221
|
+
var COMPONENT_INSTALL_FIELDS = {
|
|
1222
|
+
componentId: "string",
|
|
1223
|
+
name: "string",
|
|
1224
|
+
version: "string",
|
|
1225
|
+
source: "string",
|
|
1226
|
+
"sourceKind?": "string",
|
|
1227
|
+
"sourceRef?": "string",
|
|
1228
|
+
"resolvedSha?": "string",
|
|
1229
|
+
"registeredComponentRef?": "string",
|
|
1230
|
+
manifestHash: "string",
|
|
1231
|
+
state: "string",
|
|
1232
|
+
manifest: "string",
|
|
1233
|
+
installedAt: "string",
|
|
1234
|
+
installedByUserId: "string?",
|
|
1235
|
+
"installId?": "string",
|
|
1236
|
+
"updatedAt?": "string"
|
|
1237
|
+
};
|
|
1238
|
+
var COMPONENT_CONFIG_FIELDS = {};
|
|
1239
|
+
var SYSTEM_MANIFEST = {
|
|
1240
|
+
component: {
|
|
1241
|
+
id: SYSTEM_COMPONENT_ID,
|
|
1242
|
+
name: "system",
|
|
1243
|
+
version: "1.0.0"
|
|
1244
|
+
},
|
|
1245
|
+
shapes: [
|
|
1246
|
+
{ name: "ComponentInstall", fields: COMPONENT_INSTALL_FIELDS },
|
|
1247
|
+
{ name: "ComponentConfig", fields: COMPONENT_CONFIG_FIELDS }
|
|
1248
|
+
],
|
|
1249
|
+
credentials: [],
|
|
1250
|
+
subscriptions: [],
|
|
1251
|
+
seeds: [],
|
|
1252
|
+
health: {
|
|
1253
|
+
requires: { shapes: ["ComponentInstall", "ComponentConfig"] }
|
|
1254
|
+
},
|
|
1255
|
+
teardown: {},
|
|
1256
|
+
runtimeAccess: { reads: [], writes: [] }
|
|
1257
|
+
};
|
|
1258
|
+
var SYSTEM_COMPONENT = {
|
|
1259
|
+
componentId: SYSTEM_COMPONENT_ID,
|
|
1260
|
+
name: SYSTEM_MANIFEST.component.name,
|
|
1261
|
+
version: SYSTEM_MANIFEST.component.version,
|
|
1262
|
+
manifest: SYSTEM_MANIFEST,
|
|
1263
|
+
installInfra: true,
|
|
1264
|
+
shapes: [
|
|
1265
|
+
{ name: "ComponentInstall", fields: COMPONENT_INSTALL_FIELDS },
|
|
1266
|
+
{ name: "ComponentConfig", fields: COMPONENT_CONFIG_FIELDS }
|
|
1267
|
+
]
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
// ../rules/src/system-components/index.ts
|
|
1271
|
+
var SYSTEM_COMPONENTS = [
|
|
1272
|
+
SYSTEM_COMPONENT,
|
|
1273
|
+
IDENTITY_COMPONENT
|
|
1274
|
+
];
|
|
1275
|
+
new Set(
|
|
1276
|
+
SYSTEM_COMPONENTS.filter((entry) => entry.installInfra === true).flatMap(
|
|
1277
|
+
(entry) => entry.shapes.map((s) => s.name)
|
|
1278
|
+
)
|
|
1279
|
+
);
|
|
1280
|
+
|
|
1281
|
+
// src/operation-normalize.ts
|
|
1282
|
+
function toBackendStreamOperation(operation) {
|
|
1283
|
+
if (operation.operation === "retract") {
|
|
1284
|
+
return {
|
|
1285
|
+
operation: "retract",
|
|
1286
|
+
name: operation.name,
|
|
1287
|
+
...operation.kind ? { kind: operation.kind } : {},
|
|
1288
|
+
...operation.reason ? { reason: operation.reason } : {}
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
if (operation.operation === "revise") {
|
|
1292
|
+
const name = operation.name ?? operation.wref;
|
|
1293
|
+
if (!name) {
|
|
1294
|
+
throw new Error("revise operation requires a target");
|
|
1295
|
+
}
|
|
1296
|
+
const kind2 = operation.kind ?? inferKind(name, operation);
|
|
1297
|
+
if (Object.hasOwn(operation, "active")) {
|
|
1298
|
+
throw new Error(
|
|
1299
|
+
`${kind2} revise operation no longer supports 'active' \u2014 use retract('${name}') instead`
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
if (operation.data === void 0) {
|
|
1303
|
+
throw new Error(`${kind2} revise operation requires 'data'`);
|
|
1304
|
+
}
|
|
1305
|
+
if (kind2 === "collection") {
|
|
1306
|
+
throw new Error(
|
|
1307
|
+
`collection revise is no longer supported \u2014 use retract('${name}') instead`
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
if (kind2 === "assertion") {
|
|
1311
|
+
return {
|
|
1312
|
+
operation: "revise",
|
|
1313
|
+
kind: "assertion",
|
|
1314
|
+
name,
|
|
1315
|
+
data: operation.data
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
return {
|
|
1319
|
+
operation: "revise",
|
|
1320
|
+
kind: kind2,
|
|
1321
|
+
name,
|
|
1322
|
+
data: operation.data
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
const kind = operation.kind ?? inferKind(operation.name, operation);
|
|
1326
|
+
const skipExisting = "skipExisting" in operation ? operation.skipExisting : void 0;
|
|
1327
|
+
if (kind === "collection") {
|
|
1328
|
+
if (!("members" in operation) || !operation.members) {
|
|
1329
|
+
throw new Error("collection add operation requires 'members'");
|
|
1330
|
+
}
|
|
1331
|
+
return {
|
|
1332
|
+
operation: "add",
|
|
1333
|
+
kind: "collection",
|
|
1334
|
+
name: operation.name,
|
|
1335
|
+
type: "type" in operation && operation.type || "set",
|
|
1336
|
+
members: operation.members,
|
|
1337
|
+
...skipExisting === true ? { skipExisting } : {}
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
if (!operation.name) {
|
|
1341
|
+
throw new Error("add operation requires a name");
|
|
1342
|
+
}
|
|
1343
|
+
if (kind === "assertion") {
|
|
1344
|
+
if (!("about" in operation) || operation.about === void 0) {
|
|
1345
|
+
throw new Error("assertion add operation requires 'about'");
|
|
1346
|
+
}
|
|
1347
|
+
if (operation.data === void 0) {
|
|
1348
|
+
throw new Error("assertion add operation requires 'data'");
|
|
1349
|
+
}
|
|
1350
|
+
return {
|
|
1351
|
+
operation: "add",
|
|
1352
|
+
kind: "assertion",
|
|
1353
|
+
name: operation.name,
|
|
1354
|
+
about: operation.about,
|
|
1355
|
+
data: operation.data,
|
|
1356
|
+
...skipExisting === true ? { skipExisting } : {}
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
if (operation.data === void 0) {
|
|
1360
|
+
throw new Error(`${kind} add operation requires 'data'`);
|
|
1361
|
+
}
|
|
1362
|
+
return {
|
|
1363
|
+
operation: "add",
|
|
1364
|
+
kind,
|
|
1365
|
+
name: operation.name,
|
|
1366
|
+
data: operation.data,
|
|
1367
|
+
...skipExisting === true ? { skipExisting } : {}
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
function inferKind(name, operation) {
|
|
1371
|
+
if (operation.kind) {
|
|
1372
|
+
return operation.kind;
|
|
1373
|
+
}
|
|
1374
|
+
if ("about" in operation && operation.about !== void 0) {
|
|
1375
|
+
return "assertion";
|
|
1376
|
+
}
|
|
1377
|
+
if ("type" in operation && operation.type !== void 0 || "members" in operation && operation.members !== void 0) {
|
|
1378
|
+
return "collection";
|
|
1379
|
+
}
|
|
1380
|
+
if (!name) {
|
|
1381
|
+
return "shape";
|
|
1382
|
+
}
|
|
1383
|
+
return name.includes("/") ? "thing" : "shape";
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// src/stream-submit.ts
|
|
1387
|
+
var DEFAULT_STREAM_CHUNK_SIZE = DEFAULT_STREAM_APPEND_CHUNK_SIZE;
|
|
1388
|
+
var MAX_STREAM_APPEND_OPERATION_COUNT2 = MAX_STREAM_APPEND_OPERATION_COUNT;
|
|
1389
|
+
var DEFAULT_RETRY_POLICY = {
|
|
1390
|
+
maxAttempts: 3,
|
|
1391
|
+
baseDelayMs: 250,
|
|
1392
|
+
maxDelayMs: 8e3
|
|
1393
|
+
};
|
|
1394
|
+
var MAX_ATTEMPTS_HARD_CAP = 10;
|
|
1395
|
+
var MAX_DELAY_HARD_CAP_MS = 6e4;
|
|
1396
|
+
function resolveRetryPolicy(retry) {
|
|
1397
|
+
if (retry === false) return false;
|
|
1398
|
+
const overrides = {};
|
|
1399
|
+
if (retry !== void 0) {
|
|
1400
|
+
if (Number.isFinite(retry.maxAttempts))
|
|
1401
|
+
overrides.maxAttempts = retry.maxAttempts;
|
|
1402
|
+
if (Number.isFinite(retry.baseDelayMs))
|
|
1403
|
+
overrides.baseDelayMs = retry.baseDelayMs;
|
|
1404
|
+
if (Number.isFinite(retry.maxDelayMs))
|
|
1405
|
+
overrides.maxDelayMs = retry.maxDelayMs;
|
|
1406
|
+
}
|
|
1407
|
+
const merged = { ...DEFAULT_RETRY_POLICY, ...overrides };
|
|
1408
|
+
return {
|
|
1409
|
+
maxAttempts: Math.min(
|
|
1410
|
+
MAX_ATTEMPTS_HARD_CAP,
|
|
1411
|
+
Math.max(1, Math.trunc(merged.maxAttempts))
|
|
1412
|
+
),
|
|
1413
|
+
baseDelayMs: Math.min(
|
|
1414
|
+
MAX_DELAY_HARD_CAP_MS,
|
|
1415
|
+
Math.max(0, Math.trunc(merged.baseDelayMs))
|
|
1416
|
+
),
|
|
1417
|
+
maxDelayMs: Math.min(
|
|
1418
|
+
MAX_DELAY_HARD_CAP_MS,
|
|
1419
|
+
Math.max(0, Math.trunc(merged.maxDelayMs))
|
|
1420
|
+
)
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
var StreamValidationError = class extends Error {
|
|
1424
|
+
code = "VALIDATION_ERROR";
|
|
1425
|
+
status = 400;
|
|
1426
|
+
constructor(message) {
|
|
1427
|
+
super(message);
|
|
1428
|
+
this.name = "WarmHubError";
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
var PartialStreamSubmissionError = class extends Error {
|
|
1432
|
+
/**
|
|
1433
|
+
* Stable error code. Always `'PARTIAL_STREAM_SUBMISSION'`.
|
|
1434
|
+
*/
|
|
1435
|
+
code = "PARTIAL_STREAM_SUBMISSION";
|
|
1436
|
+
/**
|
|
1437
|
+
* Operations the SDK confirmed completed before the ambiguous or failing
|
|
1438
|
+
* append. The remaining operations may or may not have landed; inspect
|
|
1439
|
+
* repository state before retrying manually.
|
|
1440
|
+
*/
|
|
1441
|
+
completedOperations;
|
|
1442
|
+
/**
|
|
1443
|
+
* Underlying error that triggered the partial submission, preserved for
|
|
1444
|
+
* diagnostics. Inspect via `isWarmHubError(err.cause)` / `err.cause.kind`.
|
|
1445
|
+
*/
|
|
1446
|
+
cause;
|
|
1447
|
+
constructor(input) {
|
|
1448
|
+
super(
|
|
1449
|
+
"Stream submission failed. A timed-out append may already have landed, so inspect repository state before sending any further operations."
|
|
1450
|
+
);
|
|
1451
|
+
this.name = "WarmHubError";
|
|
1452
|
+
this.cause = input.cause;
|
|
1453
|
+
this.completedOperations = input.completedOperations;
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
async function submitOperationsViaStream(client, args) {
|
|
1457
|
+
if (args.operations.length === 0) {
|
|
1458
|
+
throw new StreamValidationError(
|
|
1459
|
+
"At least one operation is required for stream submission."
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1462
|
+
const operations = args.operations.map((operation, index) => {
|
|
1463
|
+
let streamOperation;
|
|
1464
|
+
try {
|
|
1465
|
+
streamOperation = toBackendStreamOperation(operation);
|
|
1466
|
+
} catch (cause) {
|
|
1467
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
1468
|
+
throw new StreamValidationError(
|
|
1469
|
+
`Invalid operation at index ${index}: ${message}`
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
if (args.skipExisting === true && streamOperation.operation === "add") {
|
|
1473
|
+
return { ...streamOperation, skipExisting: true };
|
|
1474
|
+
}
|
|
1475
|
+
return streamOperation;
|
|
1476
|
+
});
|
|
1477
|
+
const chunkSize = normalizeChunkSize(args.chunkSize);
|
|
1478
|
+
let streamId = args.streamId ?? createStreamId();
|
|
1479
|
+
const isManualResume = args.streamId !== void 0 || (args.allocatedTokens?.length ?? 0) > 0;
|
|
1480
|
+
const policy = isManualResume ? false : resolveRetryPolicy(args.retry);
|
|
1481
|
+
let allocatedTokens = args.allocatedTokens ?? [];
|
|
1482
|
+
const chunkResults = [];
|
|
1483
|
+
for (const chunk of chunkOperations(operations, chunkSize)) {
|
|
1484
|
+
let attempt = 1;
|
|
1485
|
+
let priorAttemptAmbiguous = false;
|
|
1486
|
+
const chunkIsAtomic = chunk.length === 1;
|
|
1487
|
+
const chunkUsesTokens = chunk.some(opUsesTokens);
|
|
1488
|
+
while (true) {
|
|
1489
|
+
try {
|
|
1490
|
+
const appendResult = await client.stream.append({
|
|
1491
|
+
allocatedTokens,
|
|
1492
|
+
orgName: args.orgName,
|
|
1493
|
+
repoName: args.repoName,
|
|
1494
|
+
streamId,
|
|
1495
|
+
componentId: args.componentId,
|
|
1496
|
+
committer: args.committer,
|
|
1497
|
+
operations: chunk
|
|
1498
|
+
});
|
|
1499
|
+
allocatedTokens = appendResult.allocatedTokens;
|
|
1500
|
+
chunkResults.push(...appendResult.results);
|
|
1501
|
+
break;
|
|
1502
|
+
} catch (cause) {
|
|
1503
|
+
if (chunkResults.length === 0 && !priorAttemptAmbiguous && isDefiniteClientError(cause)) {
|
|
1504
|
+
throw cause;
|
|
1505
|
+
}
|
|
1506
|
+
if (chunkResults.length === 0 && chunkIsAtomic && !chunkUsesTokens && policy !== false && attempt < policy.maxAttempts && isTransientStreamFailure(cause)) {
|
|
1507
|
+
await sleep(computeBackoffDelayMs(attempt, policy));
|
|
1508
|
+
attempt += 1;
|
|
1509
|
+
priorAttemptAmbiguous = true;
|
|
1510
|
+
streamId = createStreamId();
|
|
1511
|
+
allocatedTokens = [];
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
const completedOperations = toSubmittedStreamResult(
|
|
1515
|
+
{
|
|
1516
|
+
operationCount: chunkResults.length
|
|
1517
|
+
},
|
|
1518
|
+
args.committer,
|
|
1519
|
+
args.message,
|
|
1520
|
+
chunkResults
|
|
1521
|
+
).operations;
|
|
1522
|
+
throw new PartialStreamSubmissionError({
|
|
1523
|
+
cause,
|
|
1524
|
+
completedOperations
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
return toSubmittedStreamResult(
|
|
1530
|
+
{
|
|
1531
|
+
operationCount: chunkResults.length
|
|
1532
|
+
},
|
|
1533
|
+
args.committer,
|
|
1534
|
+
args.message,
|
|
1535
|
+
chunkResults
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
var DEFINITE_CLIENT_REJECT_CODES = /* @__PURE__ */ new Set([
|
|
1539
|
+
"UNAUTHENTICATED",
|
|
1540
|
+
"FORBIDDEN",
|
|
1541
|
+
"VALIDATION_ERROR",
|
|
1542
|
+
"SHAPE_MISMATCH",
|
|
1543
|
+
"NOT_FOUND",
|
|
1544
|
+
"KIND_MISMATCH",
|
|
1545
|
+
"CONFLICT",
|
|
1546
|
+
"ALREADY_RETRACTED",
|
|
1547
|
+
"ARCHIVED",
|
|
1548
|
+
// Pre-apply rejections from stream/service.ts: applyRateLimit() and the
|
|
1549
|
+
// unresolved-token check both throw before any operation lands, so the
|
|
1550
|
+
// first-chunk wrapper would discard structured retry/validation detail
|
|
1551
|
+
// (retryAfter, the failing token, etc.).
|
|
1552
|
+
"RATE_LIMITED",
|
|
1553
|
+
"UNRESOLVED_TOKEN"
|
|
1554
|
+
]);
|
|
1555
|
+
function extractErrorCode(cause) {
|
|
1556
|
+
if (!cause || typeof cause !== "object") return void 0;
|
|
1557
|
+
const direct = cause.code;
|
|
1558
|
+
if (typeof direct === "string") return direct;
|
|
1559
|
+
const data = cause.data;
|
|
1560
|
+
const wh = data?.warmhub?.code;
|
|
1561
|
+
if (typeof wh === "string") return wh;
|
|
1562
|
+
const dc = data?.code;
|
|
1563
|
+
if (typeof dc === "string") return dc;
|
|
1564
|
+
return void 0;
|
|
1565
|
+
}
|
|
1566
|
+
function isDefiniteClientError(cause) {
|
|
1567
|
+
const code = extractErrorCode(cause);
|
|
1568
|
+
return code !== void 0 && DEFINITE_CLIENT_REJECT_CODES.has(code);
|
|
1569
|
+
}
|
|
1570
|
+
function isTransientStreamFailure(cause) {
|
|
1571
|
+
if (isDefiniteClientError(cause)) return false;
|
|
1572
|
+
if (cause instanceof TypeError) return false;
|
|
1573
|
+
if (cause instanceof SyntaxError) return false;
|
|
1574
|
+
const inner = cause?.cause;
|
|
1575
|
+
if (inner instanceof TypeError) return false;
|
|
1576
|
+
if (inner instanceof SyntaxError) return false;
|
|
1577
|
+
return true;
|
|
1578
|
+
}
|
|
1579
|
+
function computeBackoffDelayMs(attempt, policy) {
|
|
1580
|
+
const exponential = policy.baseDelayMs * 2 ** Math.max(0, attempt - 1);
|
|
1581
|
+
const jitter = Math.random() * policy.baseDelayMs;
|
|
1582
|
+
return Math.min(policy.maxDelayMs, exponential + jitter);
|
|
1583
|
+
}
|
|
1584
|
+
function sleep(ms) {
|
|
1585
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1586
|
+
}
|
|
1587
|
+
var TOKEN_REF_REGEX = /[$#]\d+/;
|
|
1588
|
+
function stringHasTokenRef(value) {
|
|
1589
|
+
return typeof value === "string" && TOKEN_REF_REGEX.test(value);
|
|
1590
|
+
}
|
|
1591
|
+
function membersHaveTokenRef(members) {
|
|
1592
|
+
if (!Array.isArray(members)) return false;
|
|
1593
|
+
return members.some(stringHasTokenRef);
|
|
1594
|
+
}
|
|
1595
|
+
function opUsesTokens(op) {
|
|
1596
|
+
return stringHasTokenRef(op.name) || stringHasTokenRef(op.wref) || stringHasTokenRef(op.about) || stringHasTokenRef(op.aboutWref) || membersHaveTokenRef(op.members);
|
|
1597
|
+
}
|
|
1598
|
+
function createStreamId() {
|
|
1599
|
+
return globalThis.crypto?.randomUUID?.() ?? `stream-${Date.now()}-${Math.random()}`;
|
|
1600
|
+
}
|
|
1601
|
+
function normalizeChunkSize(chunkSize) {
|
|
1602
|
+
if (!Number.isFinite(chunkSize)) return DEFAULT_STREAM_CHUNK_SIZE;
|
|
1603
|
+
return Math.max(
|
|
1604
|
+
1,
|
|
1605
|
+
Math.min(
|
|
1606
|
+
MAX_STREAM_APPEND_OPERATION_COUNT2,
|
|
1607
|
+
Math.trunc(chunkSize)
|
|
1608
|
+
)
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
function chunkOperations(operations, chunkSize) {
|
|
1612
|
+
const chunks = [];
|
|
1613
|
+
for (let start = 0; start < operations.length; start += chunkSize) {
|
|
1614
|
+
chunks.push(operations.slice(start, start + chunkSize));
|
|
1615
|
+
}
|
|
1616
|
+
return chunks;
|
|
1617
|
+
}
|
|
1618
|
+
function toSubmittedStreamResult(writeResult, committer, message, results) {
|
|
1619
|
+
return {
|
|
1620
|
+
committer,
|
|
1621
|
+
message,
|
|
1622
|
+
operationCount: writeResult.operationCount,
|
|
1623
|
+
operations: results.map((result) => ({
|
|
1624
|
+
name: result.name ?? "",
|
|
1625
|
+
operation: result.operation === "revise" || result.operation === "retract" || result.operation === "noop" ? result.operation : "add",
|
|
1626
|
+
dataHash: result.dataHash ?? "",
|
|
1627
|
+
version: result.version ?? 0,
|
|
1628
|
+
status: result.status,
|
|
1629
|
+
error: result.error,
|
|
1630
|
+
...result.warnings ? { warnings: result.warnings } : {}
|
|
1631
|
+
}))
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
// src/operation-builder.ts
|
|
1636
|
+
var OperationBuilder = class {
|
|
1637
|
+
ops = [];
|
|
1638
|
+
pendingNames = /* @__PURE__ */ new Set();
|
|
1639
|
+
shapes;
|
|
1640
|
+
wrefToShape = /* @__PURE__ */ new Map();
|
|
1641
|
+
submitted = false;
|
|
1642
|
+
inFlight = false;
|
|
1643
|
+
/**
|
|
1644
|
+
* Create an empty operation builder.
|
|
1645
|
+
*
|
|
1646
|
+
* Pass known shape definitions when you want local data validation before submitting operations.
|
|
1647
|
+
*/
|
|
1648
|
+
constructor(options) {
|
|
1649
|
+
this.shapes = options?.shapes;
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Add a shape, thing, assertion, or collection operation.
|
|
1653
|
+
*
|
|
1654
|
+
* For things and assertions, names use `Shape/localName`. Supplying `about` makes the operation an assertion. Supplying collection `type` and `members` makes it a collection. Otherwise the builder infers kind from the name.
|
|
1655
|
+
*
|
|
1656
|
+
* Set `kind: 'thing'` explicitly for hierarchical thing names that would otherwise look like assertion paths.
|
|
1657
|
+
*/
|
|
1658
|
+
add(op) {
|
|
1659
|
+
this.assertNotSubmitted();
|
|
1660
|
+
const normalized = { ...op };
|
|
1661
|
+
delete normalized.operation;
|
|
1662
|
+
const shapeWref = inferShapeForAdd(normalized);
|
|
1663
|
+
if (this.shapes && shapeWref && normalized.data !== void 0) {
|
|
1664
|
+
const fields = this.shapes[shapeWref];
|
|
1665
|
+
if (fields) {
|
|
1666
|
+
const result = validateAgainstShape(
|
|
1667
|
+
normalized.data,
|
|
1668
|
+
fields
|
|
1669
|
+
);
|
|
1670
|
+
if (!result.valid) {
|
|
1671
|
+
throw new Error(
|
|
1672
|
+
`Shape validation failed for '${normalized.name}' (shape: ${shapeWref}): ${result.errors.join("; ")}`
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
const addAsOp = { operation: "add", ...normalized };
|
|
1678
|
+
const addKind = normalizeAddKind(addAsOp);
|
|
1679
|
+
if (addAsOp.name) {
|
|
1680
|
+
preflightCheckAdd(addAsOp, addKind);
|
|
1681
|
+
}
|
|
1682
|
+
const frozen = Object.freeze({
|
|
1683
|
+
operation: "add",
|
|
1684
|
+
...normalized
|
|
1685
|
+
});
|
|
1686
|
+
this.ops.push(frozen);
|
|
1687
|
+
if (normalized.name) {
|
|
1688
|
+
this.pendingNames.add(normalized.name);
|
|
1689
|
+
if (shapeWref) {
|
|
1690
|
+
this.wrefToShape.set(normalized.name, shapeWref);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return this;
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Add a revise operation.
|
|
1697
|
+
*
|
|
1698
|
+
* The target can be supplied as `name` or `wref`. When shape definitions were provided to the constructor, revise data is validated locally before the operation is queued.
|
|
1699
|
+
*/
|
|
1700
|
+
revise(op) {
|
|
1701
|
+
this.assertNotSubmitted();
|
|
1702
|
+
const normalized = { ...op };
|
|
1703
|
+
delete normalized.operation;
|
|
1704
|
+
const target = normalized.name ?? normalized.wref;
|
|
1705
|
+
if (this.shapes && target && normalized.data !== void 0) {
|
|
1706
|
+
const shapeWref = this.wrefToShape.get(target) ?? inferShapeFromLocalPath(target);
|
|
1707
|
+
if (shapeWref) {
|
|
1708
|
+
const fields = this.shapes[shapeWref];
|
|
1709
|
+
if (fields) {
|
|
1710
|
+
const result = validateAgainstShape(
|
|
1711
|
+
normalized.data,
|
|
1712
|
+
fields
|
|
1713
|
+
);
|
|
1714
|
+
if (!result.valid) {
|
|
1715
|
+
throw new Error(
|
|
1716
|
+
`Shape validation failed for revise '${target}' (shape: ${shapeWref}): ${result.errors.join("; ")}`
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
if (target) {
|
|
1723
|
+
const reviseKind = normalizeReviseKind(
|
|
1724
|
+
{ ...normalized },
|
|
1725
|
+
target
|
|
1726
|
+
);
|
|
1727
|
+
preflightCheckRevise(reviseKind, target, normalized.data);
|
|
1728
|
+
}
|
|
1729
|
+
const frozen = Object.freeze({
|
|
1730
|
+
operation: "revise",
|
|
1731
|
+
...normalized
|
|
1732
|
+
});
|
|
1733
|
+
this.ops.push(frozen);
|
|
1734
|
+
return this;
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Add a retract operation.
|
|
1738
|
+
*
|
|
1739
|
+
* Pass a wref string shorthand or an object with a target name, optional kind safety hint, and optional reason.
|
|
1740
|
+
*/
|
|
1741
|
+
retract(target) {
|
|
1742
|
+
this.assertNotSubmitted();
|
|
1743
|
+
const op = typeof target === "string" ? Object.freeze({ operation: "retract", name: target }) : Object.freeze({
|
|
1744
|
+
operation: "retract",
|
|
1745
|
+
name: target.name,
|
|
1746
|
+
...target.kind ? { kind: target.kind } : {},
|
|
1747
|
+
...target.reason ? { reason: target.reason } : {}
|
|
1748
|
+
});
|
|
1749
|
+
this.ops.push(op);
|
|
1750
|
+
return this;
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Return the queued operations.
|
|
1754
|
+
*
|
|
1755
|
+
* The returned array is read-only; use `add`, `revise`, and `retract` to modify the builder.
|
|
1756
|
+
*/
|
|
1757
|
+
get operations() {
|
|
1758
|
+
return this.ops;
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Return the number of queued operations.
|
|
1762
|
+
*/
|
|
1763
|
+
get size() {
|
|
1764
|
+
return this.ops.length;
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Return whether this builder has queued an add operation for a name.
|
|
1768
|
+
*/
|
|
1769
|
+
has(name) {
|
|
1770
|
+
return this.pendingNames.has(name);
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Run client-side validation without contacting the server.
|
|
1774
|
+
*
|
|
1775
|
+
* Validation checks for empty commits, duplicate adds, missing revise targets, revise-after-retract patterns, local shape-data errors when shapes were provided, and the same preflight diagnostics used before commit submission.
|
|
1776
|
+
*
|
|
1777
|
+
* Returns a {@link ValidationResult} object (`{ valid, errors, warnings }`) —
|
|
1778
|
+
* not a boolean. Read `result.valid` to gate submission, and iterate
|
|
1779
|
+
* `result.errors` for blocking diagnostics or `result.warnings` for
|
|
1780
|
+
* informational ones.
|
|
1781
|
+
*
|
|
1782
|
+
* @example
|
|
1783
|
+
* ```ts
|
|
1784
|
+
* const cb = new OperationBuilder();
|
|
1785
|
+
* cb.add({ name: "Location/cave", data: { x: 0, y: 0 } });
|
|
1786
|
+
* const result = cb.validate();
|
|
1787
|
+
* if (!result.valid) {
|
|
1788
|
+
* for (const err of result.errors) {
|
|
1789
|
+
* console.error(`op ${err.operationIndex}: ${err.code} — ${err.message}`);
|
|
1790
|
+
* }
|
|
1791
|
+
* throw new Error("validation failed");
|
|
1792
|
+
* }
|
|
1793
|
+
* for (const warn of result.warnings) console.warn(warn.message);
|
|
1794
|
+
* ```
|
|
1795
|
+
*/
|
|
1796
|
+
validate() {
|
|
1797
|
+
const errors = [];
|
|
1798
|
+
const warnings = [];
|
|
1799
|
+
const seenAdds = /* @__PURE__ */ new Set();
|
|
1800
|
+
const seenRetracts = /* @__PURE__ */ new Set();
|
|
1801
|
+
for (let index = 0; index < this.ops.length; index += 1) {
|
|
1802
|
+
const op = this.ops[index];
|
|
1803
|
+
if (!op) continue;
|
|
1804
|
+
if (op.operation === "add") {
|
|
1805
|
+
if (!op.name) {
|
|
1806
|
+
errors.push({
|
|
1807
|
+
code: "MISSING_NAME",
|
|
1808
|
+
operationIndex: index,
|
|
1809
|
+
message: `Add operation at index ${index} is missing required 'name' field`
|
|
1810
|
+
});
|
|
1811
|
+
} else {
|
|
1812
|
+
if (seenAdds.has(op.name)) {
|
|
1813
|
+
errors.push({
|
|
1814
|
+
code: "DUPLICATE_NAME",
|
|
1815
|
+
operationIndex: index,
|
|
1816
|
+
message: `Duplicate name '${op.name}' at index ${index} (already added earlier in commit)`
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
seenAdds.add(op.name);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
if (op.operation === "revise") {
|
|
1823
|
+
const target = op.name ?? op.wref;
|
|
1824
|
+
if (!target) {
|
|
1825
|
+
errors.push({
|
|
1826
|
+
code: "MISSING_WREF",
|
|
1827
|
+
operationIndex: index,
|
|
1828
|
+
message: `Revise operation at index ${index} is missing required target ('name' or 'wref')`
|
|
1829
|
+
});
|
|
1830
|
+
} else if (seenRetracts.has(target)) {
|
|
1831
|
+
errors.push({
|
|
1832
|
+
code: "REVISE_AFTER_RETRACT",
|
|
1833
|
+
operationIndex: index,
|
|
1834
|
+
message: `Revise at index ${index} targets '${target}' which was already retracted in this commit`
|
|
1835
|
+
});
|
|
1836
|
+
} else if (!seenAdds.has(target)) {
|
|
1837
|
+
warnings.push({
|
|
1838
|
+
code: "REVISE_UNKNOWN_WREF",
|
|
1839
|
+
operationIndex: index,
|
|
1840
|
+
message: `Revise at index ${index} references '${target}' which was not added in this commit (assuming it exists server-side)`
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
if (op.operation === "retract") {
|
|
1845
|
+
if (!op.name) {
|
|
1846
|
+
errors.push({
|
|
1847
|
+
code: "MISSING_NAME",
|
|
1848
|
+
operationIndex: index,
|
|
1849
|
+
message: `Retract operation at index ${index} is missing required 'name' field`
|
|
1850
|
+
});
|
|
1851
|
+
} else if (seenRetracts.has(op.name)) {
|
|
1852
|
+
warnings.push({
|
|
1853
|
+
code: "RETRACT_AFTER_RETRACT",
|
|
1854
|
+
operationIndex: index,
|
|
1855
|
+
message: `Retract at index ${index} targets '${op.name}' which was already retracted in this commit`
|
|
1856
|
+
});
|
|
1857
|
+
} else {
|
|
1858
|
+
if (!seenAdds.has(op.name)) {
|
|
1859
|
+
warnings.push({
|
|
1860
|
+
code: "RETRACT_UNKNOWN_WREF",
|
|
1861
|
+
operationIndex: index,
|
|
1862
|
+
message: `Retract at index ${index} references '${op.name}' which was not added in this commit (assuming it exists server-side)`
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
seenRetracts.add(op.name);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
const preflightOps = this.ops.filter((op) => op.operation !== "retract").map((op) => {
|
|
1870
|
+
if (op.operation === "add") {
|
|
1871
|
+
return toPreflightOp(op, normalizeAddKind(op));
|
|
1872
|
+
}
|
|
1873
|
+
const target = op.name ?? op.wref;
|
|
1874
|
+
return toPreflightOp(
|
|
1875
|
+
op,
|
|
1876
|
+
target ? normalizeReviseKind(op, target) : "thing"
|
|
1877
|
+
);
|
|
1878
|
+
});
|
|
1879
|
+
const diagnostics = preflightCommitDiagnostics(preflightOps);
|
|
1880
|
+
for (const diagnostic of diagnostics) {
|
|
1881
|
+
errors.push({
|
|
1882
|
+
code: diagnostic.code,
|
|
1883
|
+
operationIndex: diagnostic.operationIndex,
|
|
1884
|
+
message: diagnostic.message
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
if (this.ops.length === 0) {
|
|
1888
|
+
errors.push({
|
|
1889
|
+
code: "EMPTY_COMMIT",
|
|
1890
|
+
operationIndex: -1,
|
|
1891
|
+
message: "OperationBuilder has no operations"
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Validate, submit, and seal the builder.
|
|
1898
|
+
*
|
|
1899
|
+
* 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`.
|
|
1900
|
+
*
|
|
1901
|
+
* @param params.client WarmHub client or compatible stream client used for submission.
|
|
1902
|
+
* @param params.message Optional commit message.
|
|
1903
|
+
* @param params.committer Optional wref identifying the actor on whose behalf the write is made.
|
|
1904
|
+
* @param params.chunkSize Maximum operations per stream append.
|
|
1905
|
+
* @param params.streamId Advanced continuation hook for caller-managed streams.
|
|
1906
|
+
* @param params.allocatedTokens Advanced continuation hook for `$N` / `#N` token allocation state.
|
|
1907
|
+
* @param params.retry Retry policy for transient first-chunk failures, or `false` to disable automatic retry.
|
|
1908
|
+
*/
|
|
1909
|
+
async commit(params) {
|
|
1910
|
+
this.assertNotSubmitted();
|
|
1911
|
+
if (this.inFlight) {
|
|
1912
|
+
throw new Error("OperationBuilder: stream submission already in flight");
|
|
1913
|
+
}
|
|
1914
|
+
const validation = this.validate();
|
|
1915
|
+
if (!validation.valid) {
|
|
1916
|
+
const messages = validation.errors.map((entry) => entry.message).join("; ");
|
|
1917
|
+
throw new Error(`OperationBuilder validation failed: ${messages}`);
|
|
1918
|
+
}
|
|
1919
|
+
this.inFlight = true;
|
|
1920
|
+
try {
|
|
1921
|
+
const operations = this.ops.map((op) => toBackendOp(op));
|
|
1922
|
+
const result = await submitOperationsViaStream(params.client, {
|
|
1923
|
+
orgName: params.orgName,
|
|
1924
|
+
repoName: params.repoName,
|
|
1925
|
+
committer: params.committer,
|
|
1926
|
+
message: params.message,
|
|
1927
|
+
chunkSize: params.chunkSize,
|
|
1928
|
+
streamId: params.streamId,
|
|
1929
|
+
allocatedTokens: params.allocatedTokens,
|
|
1930
|
+
retry: params.retry,
|
|
1931
|
+
operations
|
|
1932
|
+
});
|
|
1933
|
+
this.submitted = true;
|
|
1934
|
+
return result;
|
|
1935
|
+
} finally {
|
|
1936
|
+
this.inFlight = false;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
assertNotSubmitted() {
|
|
1940
|
+
if (this.submitted) {
|
|
1941
|
+
throw new Error("OperationBuilder: already submitted, cannot modify");
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
};
|
|
1945
|
+
function inferShapeFromLocalPath(name) {
|
|
1946
|
+
if (!name.includes("/")) return void 0;
|
|
1947
|
+
return name.split("/")[0];
|
|
1948
|
+
}
|
|
1949
|
+
function inferShapeForAdd(op) {
|
|
1950
|
+
if (op.shapeWref) return op.shapeWref;
|
|
1951
|
+
if (op.name) {
|
|
1952
|
+
return inferShapeFromLocalPath(op.name);
|
|
1953
|
+
}
|
|
1954
|
+
return void 0;
|
|
1955
|
+
}
|
|
1956
|
+
function inferKindFromName2(name) {
|
|
1957
|
+
const parts = name.split("/").filter(Boolean);
|
|
1958
|
+
if (parts.length >= 3) return "assertion";
|
|
1959
|
+
if (parts.length === 2) return "thing";
|
|
1960
|
+
return "thing";
|
|
1961
|
+
}
|
|
1962
|
+
function asNonEmptyString(value) {
|
|
1963
|
+
if (typeof value !== "string") return void 0;
|
|
1964
|
+
const trimmed = value.trim();
|
|
1965
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
1966
|
+
}
|
|
1967
|
+
function normalizeAddKind(op) {
|
|
1968
|
+
if (op.kind === "shape" || op.kind === "thing" || op.kind === "assertion" || op.kind === "collection") {
|
|
1969
|
+
return op.kind;
|
|
1970
|
+
}
|
|
1971
|
+
if (op.type && Array.isArray(op.members)) {
|
|
1972
|
+
return "collection";
|
|
1973
|
+
}
|
|
1974
|
+
if (op.about !== void 0 || op.aboutWref !== void 0) {
|
|
1975
|
+
return "assertion";
|
|
1976
|
+
}
|
|
1977
|
+
return op.name ? inferKindFromName2(op.name) : "thing";
|
|
1978
|
+
}
|
|
1979
|
+
function normalizeReviseKind(op, name) {
|
|
1980
|
+
if (op.kind === "shape" || op.kind === "thing" || op.kind === "assertion" || op.kind === "collection") {
|
|
1981
|
+
return op.kind;
|
|
1982
|
+
}
|
|
1983
|
+
return inferKindFromName2(name);
|
|
1984
|
+
}
|
|
1985
|
+
function toBackendOp(op) {
|
|
1986
|
+
if (op.operation === "retract") {
|
|
1987
|
+
return {
|
|
1988
|
+
operation: "retract",
|
|
1989
|
+
name: op.name,
|
|
1990
|
+
...op.kind ? { kind: op.kind } : {},
|
|
1991
|
+
...op.reason ? { reason: op.reason } : {}
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
if (op.operation === "add") {
|
|
1995
|
+
const name2 = asNonEmptyString(op.name);
|
|
1996
|
+
if (!name2) {
|
|
1997
|
+
throw new Error("OperationBuilder: add operation missing name");
|
|
1998
|
+
}
|
|
1999
|
+
const kind2 = normalizeAddKind(op);
|
|
2000
|
+
if (kind2 === "collection") {
|
|
2001
|
+
const type = asNonEmptyString(op.type);
|
|
2002
|
+
if (!type) {
|
|
2003
|
+
throw new Error(
|
|
2004
|
+
`OperationBuilder: collection add '${name2}' missing required 'type'`
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
const members = Array.isArray(op.members) ? op.members : [];
|
|
2008
|
+
return {
|
|
2009
|
+
operation: "add",
|
|
2010
|
+
kind: kind2,
|
|
2011
|
+
type,
|
|
2012
|
+
members,
|
|
2013
|
+
name: name2,
|
|
2014
|
+
...op.skipExisting === true ? { skipExisting: true } : {}
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
if (kind2 === "assertion") {
|
|
2018
|
+
const about = op.about ?? op.aboutWref;
|
|
2019
|
+
if (about === void 0) {
|
|
2020
|
+
throw new Error(
|
|
2021
|
+
`OperationBuilder: assertion add '${name2}' missing required 'about'`
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
return {
|
|
2025
|
+
operation: "add",
|
|
2026
|
+
kind: kind2,
|
|
2027
|
+
name: name2,
|
|
2028
|
+
about,
|
|
2029
|
+
data: op.data ?? {},
|
|
2030
|
+
...op.skipExisting === true ? { skipExisting: true } : {}
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
return {
|
|
2034
|
+
operation: "add",
|
|
2035
|
+
kind: kind2,
|
|
2036
|
+
name: name2,
|
|
2037
|
+
data: op.data ?? {},
|
|
2038
|
+
...op.skipExisting === true ? { skipExisting: true } : {}
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
const name = asNonEmptyString(op.name) ?? asNonEmptyString(op.wref);
|
|
2042
|
+
if (!name) {
|
|
2043
|
+
throw new Error(
|
|
2044
|
+
"OperationBuilder: revise operation missing required target ('name' or 'wref')"
|
|
2045
|
+
);
|
|
2046
|
+
}
|
|
2047
|
+
const kind = normalizeReviseKind(op, name);
|
|
2048
|
+
if (kind === "collection") {
|
|
2049
|
+
throw new Error(
|
|
2050
|
+
`OperationBuilder: collection revise is no longer supported \u2014 use retract('${name}') instead`
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
2053
|
+
return {
|
|
2054
|
+
operation: "revise",
|
|
2055
|
+
kind,
|
|
2056
|
+
name,
|
|
2057
|
+
data: op.data ?? {}
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
function toPreflightOp(op, kind) {
|
|
2061
|
+
if (op.operation === "add") {
|
|
2062
|
+
return {
|
|
2063
|
+
operation: op.operation,
|
|
2064
|
+
kind,
|
|
2065
|
+
name: op.name,
|
|
2066
|
+
about: op.about ?? op.aboutWref,
|
|
2067
|
+
type: op.type,
|
|
2068
|
+
members: op.members
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
return {
|
|
2072
|
+
operation: op.operation,
|
|
2073
|
+
kind,
|
|
2074
|
+
name: op.name ?? op.wref
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
function throwAggregatedMessages(messages) {
|
|
2078
|
+
if (messages.length === 0) return;
|
|
2079
|
+
throw new Error(messages.join("; "));
|
|
2080
|
+
}
|
|
2081
|
+
function preflightCheckCommon(verb, kind, name, data) {
|
|
2082
|
+
const errors = [];
|
|
2083
|
+
const requiresLocalPath = kind === "thing" || kind === "assertion" || kind === "collection" && verb === "REVISE";
|
|
2084
|
+
if (requiresLocalPath) {
|
|
2085
|
+
if (!splitLocalPath(name)) {
|
|
2086
|
+
errors.push(
|
|
2087
|
+
`${verb} ${kind} name must be a local path (Shape/name), got: "${name}"`
|
|
2088
|
+
);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
if (kind === "shape" && data !== void 0) {
|
|
2092
|
+
const result = validateShapeDefinition(data);
|
|
2093
|
+
if (!result.valid) {
|
|
2094
|
+
errors.push(
|
|
2095
|
+
`Invalid shape definition for "${name}": ${result.errors.join("; ")}`
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
return errors;
|
|
2100
|
+
}
|
|
2101
|
+
function preflightCheckAdd(op, kind) {
|
|
2102
|
+
const messages = [];
|
|
2103
|
+
if (op.name) {
|
|
2104
|
+
messages.push(...preflightCheckCommon("ADD", kind, op.name, op.data));
|
|
2105
|
+
}
|
|
2106
|
+
for (const diagnostic of preflightOpDiagnostics(toPreflightOp(op, kind), 0)) {
|
|
2107
|
+
messages.push(diagnostic.message);
|
|
2108
|
+
}
|
|
2109
|
+
throwAggregatedMessages(messages);
|
|
2110
|
+
}
|
|
2111
|
+
function preflightCheckRevise(kind, name, data) {
|
|
2112
|
+
const messages = [];
|
|
2113
|
+
if (data === void 0) {
|
|
2114
|
+
messages.push(`OperationBuilder: ${kind} revise requires 'data'`);
|
|
2115
|
+
}
|
|
2116
|
+
messages.push(
|
|
2117
|
+
...preflightCheckCommon("REVISE", kind, name, data),
|
|
2118
|
+
...preflightOpDiagnostics(
|
|
2119
|
+
toPreflightOp({ operation: "revise", name}, kind),
|
|
2120
|
+
0
|
|
2121
|
+
).map((diagnostic) => diagnostic.message)
|
|
2122
|
+
);
|
|
2123
|
+
throwAggregatedMessages(messages);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// src/index.ts
|
|
2127
|
+
function normalizeWref(wref) {
|
|
2128
|
+
return wref.replace(/@(?:v\d+|HEAD|ALL)$/i, "");
|
|
2129
|
+
}
|
|
2130
|
+
var SDK_VERSION = "0.44.0" ;
|
|
2131
|
+
var DEFAULT_API_URL = "https://api.warmhub.ai";
|
|
2132
|
+
var UNBATCHED_TRPC_PATHS = /* @__PURE__ */ new Set([
|
|
2133
|
+
"repo.shapeInstanceCounts",
|
|
2134
|
+
"thing.getMany"
|
|
2135
|
+
]);
|
|
2136
|
+
var DEFAULT_THING_GET_MANY_CHUNK_SIZE = 500;
|
|
2137
|
+
var MAX_THING_GET_MANY_CHUNK_SIZE = 500;
|
|
2138
|
+
var DEFAULT_THING_GET_MANY_CHUNK_CONCURRENCY = 1;
|
|
2139
|
+
var MAX_THING_GET_MANY_CHUNK_CONCURRENCY = 8;
|
|
2140
|
+
function normalizeSubscriptionInfo(subscription) {
|
|
2141
|
+
return {
|
|
2142
|
+
...subscription,
|
|
2143
|
+
actionContainer: void 0,
|
|
2144
|
+
actionContainerConfig: void 0
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
function normalizeSubscriptionList(subscriptions) {
|
|
2148
|
+
return subscriptions.map(
|
|
2149
|
+
(subscription) => normalizeSubscriptionInfo(subscription)
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
async function* paginate(fetchPage, extractItems, initialCursor) {
|
|
2153
|
+
let cursor = initialCursor;
|
|
2154
|
+
for (; ; ) {
|
|
2155
|
+
const page = await fetchPage(cursor);
|
|
2156
|
+
for (const item of extractItems(page)) {
|
|
2157
|
+
yield item;
|
|
2158
|
+
}
|
|
2159
|
+
if (!page.nextCursor) return;
|
|
2160
|
+
cursor = page.nextCursor;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
async function collectPaginatedPages(fetchPage, extractItems, max, initialCursor) {
|
|
2164
|
+
if (max !== void 0 && (!Number.isFinite(max) || Math.trunc(max) !== max || max < 0)) {
|
|
2165
|
+
throw new WarmHubError(
|
|
2166
|
+
"VALIDATION_ERROR",
|
|
2167
|
+
"max must be a nonnegative integer"
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
const items = [];
|
|
2171
|
+
let cursor = initialCursor;
|
|
2172
|
+
for (; ; ) {
|
|
2173
|
+
const page = await fetchPage(cursor);
|
|
2174
|
+
const pageItems = extractItems(page);
|
|
2175
|
+
if (max !== void 0 && items.length + pageItems.length > max) {
|
|
2176
|
+
throw new WarmHubError(
|
|
2177
|
+
"VALIDATION_ERROR",
|
|
2178
|
+
`Paginated result exceeded max=${max}`
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
items.push(...pageItems);
|
|
2182
|
+
if (!page.nextCursor) return items;
|
|
2183
|
+
cursor = page.nextCursor;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
function normalizePositiveIntegerOption(value, defaultValue, maxValue) {
|
|
2187
|
+
if (!Number.isFinite(value)) return defaultValue;
|
|
2188
|
+
return Math.max(1, Math.min(maxValue, Math.trunc(value)));
|
|
2189
|
+
}
|
|
2190
|
+
function chunkArray(items, chunkSize) {
|
|
2191
|
+
const chunks = [];
|
|
2192
|
+
for (let start = 0; start < items.length; start += chunkSize) {
|
|
2193
|
+
chunks.push(items.slice(start, start + chunkSize));
|
|
2194
|
+
}
|
|
2195
|
+
return chunks;
|
|
2196
|
+
}
|
|
2197
|
+
function sanitizeSubscriptionCreateInput(input) {
|
|
2198
|
+
const {
|
|
2199
|
+
actionContainer: _actionContainer,
|
|
2200
|
+
actionContainerConfig: _actionContainerConfig,
|
|
2201
|
+
...supported
|
|
2202
|
+
} = input;
|
|
2203
|
+
return supported;
|
|
2204
|
+
}
|
|
2205
|
+
function sanitizeSubscriptionUpdateInput(input) {
|
|
2206
|
+
const {
|
|
2207
|
+
actionContainer: _actionContainer,
|
|
2208
|
+
actionContainerConfig: _actionContainerConfig,
|
|
2209
|
+
...supported
|
|
2210
|
+
} = input;
|
|
2211
|
+
return supported;
|
|
2212
|
+
}
|
|
2213
|
+
function resolveFunctionLogMode(mode) {
|
|
2214
|
+
return mode ?? "off";
|
|
2215
|
+
}
|
|
2216
|
+
var INTERNAL_PATTERNS = [
|
|
2217
|
+
/\bselect\b.+?\bfrom\b/i,
|
|
2218
|
+
/\binsert\b.+?\binto\b/i,
|
|
2219
|
+
/\bupdate\b.+?\bset\b/i,
|
|
2220
|
+
/\bdelete\b.+?\bfrom\b/i,
|
|
2221
|
+
/\bparams:\s/i,
|
|
2222
|
+
/\.ts\(\d+,\d+\)/,
|
|
2223
|
+
/at\s+\S+\s+\(.*:\d+:\d+\)/,
|
|
2224
|
+
/node_modules\//,
|
|
2225
|
+
/drizzle/i
|
|
2226
|
+
];
|
|
2227
|
+
function sanitizeErrorMessage(message) {
|
|
2228
|
+
for (const pattern of INTERNAL_PATTERNS) {
|
|
2229
|
+
if (pattern.test(message)) {
|
|
2230
|
+
return "An internal error occurred. Please try again or contact support.";
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
return message;
|
|
2234
|
+
}
|
|
2235
|
+
var WarmHubError = class extends Error {
|
|
2236
|
+
/**
|
|
2237
|
+
* Stable SDK error code or pass-through backend domain code. Branch on this
|
|
2238
|
+
* (or the alias {@link kind}) to handle expected failure modes.
|
|
2239
|
+
*/
|
|
2240
|
+
code;
|
|
2241
|
+
/**
|
|
2242
|
+
* HTTP status from the failing response when one was available.
|
|
2243
|
+
*/
|
|
2244
|
+
status;
|
|
2245
|
+
/**
|
|
2246
|
+
* Optional human-readable hint surfaced by the backend.
|
|
2247
|
+
*/
|
|
2248
|
+
hint;
|
|
2249
|
+
/**
|
|
2250
|
+
* Seconds the caller should wait before retrying. Present on `RATE_LIMITED`
|
|
2251
|
+
* responses and other backend signals that carry a Retry-After header.
|
|
2252
|
+
*/
|
|
2253
|
+
retryAfter;
|
|
2254
|
+
constructor(code, message, status, hint, retryAfter) {
|
|
2255
|
+
super(message);
|
|
2256
|
+
this.name = "WarmHubError";
|
|
2257
|
+
this.code = code;
|
|
2258
|
+
this.status = status;
|
|
2259
|
+
this.hint = hint;
|
|
2260
|
+
this.retryAfter = retryAfter;
|
|
2261
|
+
}
|
|
2262
|
+
get kind() {
|
|
2263
|
+
return this.code;
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
function assertContentWithinLimit(fieldLabel, value) {
|
|
2267
|
+
if (typeof value !== "string") return;
|
|
2268
|
+
const message = contentFieldLimitError(fieldLabel, value);
|
|
2269
|
+
if (!message) return;
|
|
2270
|
+
throw new WarmHubError("VALIDATION_ERROR", message);
|
|
2271
|
+
}
|
|
2272
|
+
function toWarmHubError(error) {
|
|
2273
|
+
if (error instanceof WarmHubError) {
|
|
2274
|
+
return error;
|
|
2275
|
+
}
|
|
2276
|
+
if (error instanceof TRPCClientError) {
|
|
2277
|
+
if (error.cause instanceof WarmHubError) {
|
|
2278
|
+
return error.cause;
|
|
2279
|
+
}
|
|
2280
|
+
const data = error.data;
|
|
2281
|
+
const message = data?.warmhub?.message ?? sanitizeErrorMessage(error.message);
|
|
2282
|
+
return new WarmHubError(
|
|
2283
|
+
data?.warmhub?.code ?? "BACKEND",
|
|
2284
|
+
message,
|
|
2285
|
+
data?.warmhub?.status,
|
|
2286
|
+
data?.warmhub?.hint,
|
|
2287
|
+
data?.warmhub?.retryAfter
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
if (error instanceof Error) {
|
|
2291
|
+
const warmhubLike = error;
|
|
2292
|
+
if (error.name === "WarmHubError" && typeof warmhubLike.code === "string") {
|
|
2293
|
+
return new WarmHubError(
|
|
2294
|
+
warmhubLike.code,
|
|
2295
|
+
sanitizeErrorMessage(error.message),
|
|
2296
|
+
typeof warmhubLike.status === "number" ? warmhubLike.status : void 0,
|
|
2297
|
+
typeof warmhubLike.hint === "string" ? warmhubLike.hint : void 0,
|
|
2298
|
+
typeof warmhubLike.retryAfter === "number" ? warmhubLike.retryAfter : void 0
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
if (error.name === "AbortError") {
|
|
2302
|
+
return new WarmHubError("CANCELLED", error.message);
|
|
2303
|
+
}
|
|
2304
|
+
if (error instanceof TypeError && /fetch/i.test(error.message)) {
|
|
2305
|
+
return new WarmHubError("NETWORK", error.message);
|
|
2306
|
+
}
|
|
2307
|
+
return new WarmHubError("BACKEND", sanitizeErrorMessage(error.message));
|
|
2308
|
+
}
|
|
2309
|
+
return new WarmHubError("BACKEND", "Unknown SDK error");
|
|
2310
|
+
}
|
|
2311
|
+
function isWarmHubError(error) {
|
|
2312
|
+
return error instanceof WarmHubError;
|
|
2313
|
+
}
|
|
2314
|
+
function isRetryable(error) {
|
|
2315
|
+
const mapped = toWarmHubError(error);
|
|
2316
|
+
return mapped.kind === "NETWORK" || mapped.kind === "CANCELLED" || mapped.kind === "BACKEND" || mapped.kind === "RATE_LIMITED";
|
|
2317
|
+
}
|
|
2318
|
+
var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
2319
|
+
"ConnectionRefused",
|
|
2320
|
+
"ECONNREFUSED",
|
|
2321
|
+
"ENOTFOUND",
|
|
2322
|
+
"ETIMEDOUT",
|
|
2323
|
+
"EAI_AGAIN"
|
|
2324
|
+
]);
|
|
2325
|
+
function isConnectionError(error) {
|
|
2326
|
+
if (error instanceof TypeError && /fetch/i.test(error.message)) {
|
|
2327
|
+
return true;
|
|
2328
|
+
}
|
|
2329
|
+
if (error && typeof error === "object") {
|
|
2330
|
+
const code = error.code;
|
|
2331
|
+
if (typeof code === "string" && CONNECTION_ERROR_CODES.has(code)) {
|
|
2332
|
+
return true;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
return false;
|
|
2336
|
+
}
|
|
2337
|
+
function connectionErrorMessage(url) {
|
|
2338
|
+
return `Unable to connect to ${url}. Is the computer able to access the url?`;
|
|
2339
|
+
}
|
|
2340
|
+
var WarmHubClient = class _WarmHubClient {
|
|
2341
|
+
/**
|
|
2342
|
+
* The resolved API base URL the client issues requests against. Defaults to
|
|
2343
|
+
* {@link DEFAULT_API_URL} when no `apiUrl` is passed to the constructor.
|
|
2344
|
+
*/
|
|
2345
|
+
apiUrl;
|
|
2346
|
+
fetchImpl;
|
|
2347
|
+
accessToken;
|
|
2348
|
+
functionLogMode;
|
|
2349
|
+
getToken;
|
|
2350
|
+
/**
|
|
2351
|
+
* Authentication helpers for browser sign-in flows, session checks, and token diagnostics.
|
|
2352
|
+
*/
|
|
2353
|
+
auth = {
|
|
2354
|
+
/**
|
|
2355
|
+
* Return the configured browser authentication client ID.
|
|
2356
|
+
*
|
|
2357
|
+
* Use this in browser sign-in flows that need to initialize the configured auth provider before redirecting or opening a login UI.
|
|
2358
|
+
*/
|
|
2359
|
+
getClientId: async () => {
|
|
2360
|
+
try {
|
|
2361
|
+
return await this.trpc.auth.getClientId.query();
|
|
2362
|
+
} catch (error) {
|
|
2363
|
+
throw toWarmHubError(error);
|
|
2364
|
+
}
|
|
2365
|
+
},
|
|
2366
|
+
/**
|
|
2367
|
+
* Sync the authenticated identity with WarmHub.
|
|
2368
|
+
*
|
|
2369
|
+
* Call after a browser or server session is established so WarmHub can provision or refresh the corresponding user and personal organization records.
|
|
2370
|
+
*/
|
|
2371
|
+
sync: async () => {
|
|
2372
|
+
try {
|
|
2373
|
+
return await this.trpc.auth.sync.mutate();
|
|
2374
|
+
} catch (error) {
|
|
2375
|
+
throw toWarmHubError(error);
|
|
2376
|
+
}
|
|
2377
|
+
},
|
|
2378
|
+
/**
|
|
2379
|
+
* Return the current authenticated WarmHub user.
|
|
2380
|
+
*
|
|
2381
|
+
* Throws when the request is unauthenticated or the token cannot be resolved.
|
|
2382
|
+
*/
|
|
2383
|
+
currentUser: async () => {
|
|
2384
|
+
try {
|
|
2385
|
+
return await this.trpc.auth.currentUser.query();
|
|
2386
|
+
} catch (error) {
|
|
2387
|
+
throw toWarmHubError(error);
|
|
2388
|
+
}
|
|
2389
|
+
},
|
|
2390
|
+
/**
|
|
2391
|
+
* Return authentication status, identity details, and token scope diagnostics for the current request.
|
|
2392
|
+
*/
|
|
2393
|
+
whoami: async () => {
|
|
2394
|
+
try {
|
|
2395
|
+
return await this.trpc.auth.whoami.query();
|
|
2396
|
+
} catch (error) {
|
|
2397
|
+
throw toWarmHubError(error);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
/**
|
|
2402
|
+
* Lightweight permission checks for UI gating and service-side authorization probes.
|
|
2403
|
+
*/
|
|
2404
|
+
access = {
|
|
2405
|
+
/**
|
|
2406
|
+
* Check whether the caller has a repo-scoped permission.
|
|
2407
|
+
*
|
|
2408
|
+
* @param permission Permission string such as `repo:read`, `repo:write`, `repo:configure`, or `repo:admin`.
|
|
2409
|
+
*/
|
|
2410
|
+
checkRepoPermission: async (orgName, repoName, permission) => {
|
|
2411
|
+
try {
|
|
2412
|
+
return await this.trpc.access.checkRepoPermission.query({
|
|
2413
|
+
orgName,
|
|
2414
|
+
repoName,
|
|
2415
|
+
permission
|
|
2416
|
+
});
|
|
2417
|
+
} catch (error) {
|
|
2418
|
+
throw toWarmHubError(error);
|
|
2419
|
+
}
|
|
2420
|
+
},
|
|
2421
|
+
/**
|
|
2422
|
+
* Check whether the caller has an org-scoped permission.
|
|
2423
|
+
*
|
|
2424
|
+
* @param permission Permission string such as `org:read`, `org:configure`, or `org:admin`.
|
|
2425
|
+
*/
|
|
2426
|
+
checkOrgPermission: async (orgName, permission) => {
|
|
2427
|
+
try {
|
|
2428
|
+
return await this.trpc.access.checkOrgPermission.query({
|
|
2429
|
+
orgName,
|
|
2430
|
+
permission
|
|
2431
|
+
});
|
|
2432
|
+
} catch (error) {
|
|
2433
|
+
throw toWarmHubError(error);
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
};
|
|
2437
|
+
/**
|
|
2438
|
+
* Connectivity and compatibility helpers for checking the configured WarmHub backend.
|
|
2439
|
+
*/
|
|
2440
|
+
diagnostics = {
|
|
2441
|
+
/**
|
|
2442
|
+
* Perform a health-check request against the configured backend URL.
|
|
2443
|
+
*
|
|
2444
|
+
* This uses the HTTP health endpoint rather than tRPC, so it is useful for distinguishing connection failures from procedure-level errors.
|
|
2445
|
+
*/
|
|
2446
|
+
ping: async () => {
|
|
2447
|
+
const response = await this.fetchWithAuth(
|
|
2448
|
+
`${this.apiUrl.replace(/\/$/, "")}/health`
|
|
2449
|
+
);
|
|
2450
|
+
if (!response.ok) {
|
|
2451
|
+
throw new WarmHubError(
|
|
2452
|
+
"BACKEND",
|
|
2453
|
+
`Health request failed with status ${response.status}`,
|
|
2454
|
+
response.status
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
return await response.json();
|
|
2458
|
+
},
|
|
2459
|
+
/**
|
|
2460
|
+
* Return backend API version, minimum supported SDK version, and feature flags.
|
|
2461
|
+
*/
|
|
2462
|
+
capabilities: async () => {
|
|
2463
|
+
try {
|
|
2464
|
+
return await this.trpc.diagnostics.capabilities.query();
|
|
2465
|
+
} catch (error) {
|
|
2466
|
+
throw toWarmHubError(error);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
};
|
|
2470
|
+
/**
|
|
2471
|
+
* Installed component inspection for packages that add shapes, subscriptions, credentials, and seed data to a repository.
|
|
2472
|
+
*
|
|
2473
|
+
* The top-level methods cover per-repo installation queries, bundled system installs, and the registry-backed install pipeline used by `wh component install <org/name>`.
|
|
2474
|
+
*/
|
|
2475
|
+
component = {
|
|
2476
|
+
/**
|
|
2477
|
+
* List components installed in a repository.
|
|
2478
|
+
*
|
|
2479
|
+
* Pass pagination options when a repository may have many installed components.
|
|
2480
|
+
*/
|
|
2481
|
+
list: async (orgName, repoName, opts) => {
|
|
2482
|
+
try {
|
|
2483
|
+
return await this.trpc.component.list.query({
|
|
2484
|
+
orgName,
|
|
2485
|
+
repoName,
|
|
2486
|
+
limit: opts?.limit,
|
|
2487
|
+
cursor: opts?.cursor
|
|
2488
|
+
});
|
|
2489
|
+
} catch (error) {
|
|
2490
|
+
throw toWarmHubError(error);
|
|
2491
|
+
}
|
|
2492
|
+
},
|
|
2493
|
+
/**
|
|
2494
|
+
* Fetch one installed component by component ID.
|
|
2495
|
+
*/
|
|
2496
|
+
get: async (orgName, repoName, componentId) => {
|
|
2497
|
+
try {
|
|
2498
|
+
return await this.trpc.component.get.query({
|
|
2499
|
+
orgName,
|
|
2500
|
+
repoName,
|
|
2501
|
+
componentId
|
|
2502
|
+
});
|
|
2503
|
+
} catch (error) {
|
|
2504
|
+
throw toWarmHubError(error);
|
|
2505
|
+
}
|
|
2506
|
+
},
|
|
2507
|
+
/**
|
|
2508
|
+
* Install an allowlisted bundled system component into a repository.
|
|
2509
|
+
*/
|
|
2510
|
+
installSystem: async (orgName, repoName, componentId) => {
|
|
2511
|
+
try {
|
|
2512
|
+
return await this.trpc.component.installSystem.mutate({
|
|
2513
|
+
orgName,
|
|
2514
|
+
repoName,
|
|
2515
|
+
componentId
|
|
2516
|
+
});
|
|
2517
|
+
} catch (error) {
|
|
2518
|
+
throw toWarmHubError(error);
|
|
2519
|
+
}
|
|
2520
|
+
},
|
|
2521
|
+
/**
|
|
2522
|
+
* Registered-component identity and install-pipeline operations.
|
|
2523
|
+
*
|
|
2524
|
+
* This sub-surface drives the backend-mediated install flow that powers `wh component install <org/name>`. It manages registered component identities (`register`, `unregister`, `list`, `view`, `update`) and the install pipeline (`resolve` mints an install id, `downloadSource` fetches the source archive through the backend, `setupCall` dispatches the optional setup callback). Use these when building a custom installer; most callers should run the CLI instead.
|
|
2525
|
+
*/
|
|
2526
|
+
registry: {
|
|
2527
|
+
register: async (orgName, componentName, input = {}) => {
|
|
2528
|
+
try {
|
|
2529
|
+
return await this.trpc.component.registry.register.mutate({
|
|
2530
|
+
orgName,
|
|
2531
|
+
componentName,
|
|
2532
|
+
...input
|
|
2533
|
+
});
|
|
2534
|
+
} catch (error) {
|
|
2535
|
+
throw toWarmHubError(error);
|
|
2536
|
+
}
|
|
2537
|
+
},
|
|
2538
|
+
unregister: async (orgName, componentName) => {
|
|
2539
|
+
try {
|
|
2540
|
+
return await this.trpc.component.registry.unregister.mutate({
|
|
2541
|
+
orgName,
|
|
2542
|
+
componentName
|
|
2543
|
+
});
|
|
2544
|
+
} catch (error) {
|
|
2545
|
+
throw toWarmHubError(error);
|
|
2546
|
+
}
|
|
2547
|
+
},
|
|
2548
|
+
list: async (orgName) => {
|
|
2549
|
+
try {
|
|
2550
|
+
return await this.trpc.component.registry.list.query({ orgName });
|
|
2551
|
+
} catch (error) {
|
|
2552
|
+
throw toWarmHubError(error);
|
|
2553
|
+
}
|
|
2554
|
+
},
|
|
2555
|
+
view: async (orgName, componentName) => {
|
|
2556
|
+
try {
|
|
2557
|
+
return await this.trpc.component.registry.view.query({
|
|
2558
|
+
orgName,
|
|
2559
|
+
componentName
|
|
2560
|
+
});
|
|
2561
|
+
} catch (error) {
|
|
2562
|
+
throw toWarmHubError(error);
|
|
2563
|
+
}
|
|
2564
|
+
},
|
|
2565
|
+
update: async (orgName, componentName, input) => {
|
|
2566
|
+
try {
|
|
2567
|
+
return await this.trpc.component.registry.update.mutate({
|
|
2568
|
+
orgName,
|
|
2569
|
+
componentName,
|
|
2570
|
+
...input
|
|
2571
|
+
});
|
|
2572
|
+
} catch (error) {
|
|
2573
|
+
throw toWarmHubError(error);
|
|
2574
|
+
}
|
|
2575
|
+
},
|
|
2576
|
+
resolve: async (orgName, componentName, installRepo) => {
|
|
2577
|
+
return await this.requestJson(
|
|
2578
|
+
`/api/component-registry/${encodeURIComponent(orgName)}/${encodeURIComponent(componentName)}/resolve`,
|
|
2579
|
+
{
|
|
2580
|
+
method: "POST",
|
|
2581
|
+
headers: {
|
|
2582
|
+
"content-type": "application/json"
|
|
2583
|
+
},
|
|
2584
|
+
body: JSON.stringify({ installRepo })
|
|
2585
|
+
}
|
|
2586
|
+
);
|
|
2587
|
+
},
|
|
2588
|
+
downloadSource: async (orgName, componentName, input) => {
|
|
2589
|
+
const response = await this.requestResponse(
|
|
2590
|
+
`/api/component-registry/${encodeURIComponent(orgName)}/${encodeURIComponent(componentName)}/source`,
|
|
2591
|
+
{
|
|
2592
|
+
method: "POST",
|
|
2593
|
+
headers: {
|
|
2594
|
+
"content-type": "application/json"
|
|
2595
|
+
},
|
|
2596
|
+
body: JSON.stringify(input)
|
|
2597
|
+
}
|
|
2598
|
+
);
|
|
2599
|
+
return {
|
|
2600
|
+
archive: new Uint8Array(await response.arrayBuffer()),
|
|
2601
|
+
sourceUrl: response.headers.get("x-warmhub-source-url") ?? "",
|
|
2602
|
+
sourceRef: response.headers.get("x-warmhub-source-ref") ?? void 0,
|
|
2603
|
+
resolvedSha: response.headers.get("x-warmhub-resolved-sha") ?? void 0,
|
|
2604
|
+
componentRef: response.headers.get("x-warmhub-component-ref") ?? `${orgName}/${componentName}`,
|
|
2605
|
+
contentType: response.headers.get("content-type") || "application/octet-stream"
|
|
2606
|
+
};
|
|
2607
|
+
},
|
|
2608
|
+
setupCall: async (orgName, componentName, input) => {
|
|
2609
|
+
return await this.requestJson(
|
|
2610
|
+
`/api/component-registry/${encodeURIComponent(orgName)}/${encodeURIComponent(componentName)}/setup-call`,
|
|
2611
|
+
{
|
|
2612
|
+
method: "POST",
|
|
2613
|
+
headers: {
|
|
2614
|
+
"content-type": "application/json"
|
|
2615
|
+
},
|
|
2616
|
+
body: JSON.stringify(input)
|
|
2617
|
+
}
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
},
|
|
2621
|
+
/**
|
|
2622
|
+
* Operator-invoked CLI methods (GH-3193).
|
|
2623
|
+
*
|
|
2624
|
+
* `cli.call(orgName, componentName, method, { installRepo, args })`
|
|
2625
|
+
* dispatches a component-declared method via the backend. The backend
|
|
2626
|
+
* loads the install's manifest snapshot, verifies the method exists,
|
|
2627
|
+
* authorizes the operator, signs the request with the install-repo
|
|
2628
|
+
* credential set (HMAC over `${timestamp}.${body}` by default), and
|
|
2629
|
+
* proxies the component service's JSON response back as the envelope's
|
|
2630
|
+
* `body`.
|
|
2631
|
+
*
|
|
2632
|
+
* WarmHub-level failures (component not installed, method not in snapshot,
|
|
2633
|
+
* `ComponentConfig.cliBaseUrl` missing, credentials missing, operator
|
|
2634
|
+
* lacks `requiresPermission`) come back as a thrown `WarmHubError`.
|
|
2635
|
+
* Upstream non-2xx responses do **not** throw — they arrive inside the
|
|
2636
|
+
* envelope as `{ ok: false, status, body }` so the CLI can pretty-print
|
|
2637
|
+
* the component's own error payload.
|
|
2638
|
+
*/
|
|
2639
|
+
cli: {
|
|
2640
|
+
call: async (orgName, componentName, method, input) => {
|
|
2641
|
+
return await this.requestJson(
|
|
2642
|
+
`/api/component-registry/${encodeURIComponent(orgName)}/${encodeURIComponent(componentName)}/cli/${encodeURIComponent(method)}`,
|
|
2643
|
+
{
|
|
2644
|
+
method: "POST",
|
|
2645
|
+
headers: {
|
|
2646
|
+
"content-type": "application/json"
|
|
2647
|
+
},
|
|
2648
|
+
body: JSON.stringify(input)
|
|
2649
|
+
}
|
|
2650
|
+
);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
};
|
|
2654
|
+
/**
|
|
2655
|
+
* High-level write surface for submitting WarmHub operations through the commit pipeline.
|
|
2656
|
+
*
|
|
2657
|
+
* @see https://docs.warmhub.ai/sdk/commit-vs-builder/
|
|
2658
|
+
*/
|
|
2659
|
+
commit = {
|
|
2660
|
+
/**
|
|
2661
|
+
* Submit one or more operations through WarmHub's commit pipeline.
|
|
2662
|
+
*
|
|
2663
|
+
* 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.
|
|
2664
|
+
*
|
|
2665
|
+
* Simple first-chunk transport failures are retried by default. Multi-chunk ambiguous failures are surfaced as `PartialStreamSubmissionError` so callers can inspect repository state before deciding whether to resume or retry. See [Transient Retry](/sdk/transient-retry/) for the full retry and partial-submission rules.
|
|
2666
|
+
*
|
|
2667
|
+
* Writing to an archived organization or repository fails with an `ARCHIVED` error before any operations are applied.
|
|
2668
|
+
*
|
|
2669
|
+
* @param message Optional commit message stored with the submitted operations.
|
|
2670
|
+
* @param operations Add, revise, or retract operations to submit in order.
|
|
2671
|
+
* @param opts.committer Optional wref identifying the actor on whose behalf the write is made. Must be a full wref string like `"Agent/bot-1"` (a thing that already exists), or a cross-repo wref like `"wh:other-org/other-repo/Agent/bot-1"`. Bare names such as `"eval-runner"` are rejected by the backend with a "Thing wref required" error — there is no implicit shape.
|
|
2672
|
+
* @param opts.componentId Attribute writes to an installed component when the caller is allowed to claim it.
|
|
2673
|
+
* @param opts.chunkSize Maximum operations per stream append. Values are clamped by the SDK.
|
|
2674
|
+
* @param opts.skipExisting Return `noop` for add operations whose target already exists.
|
|
2675
|
+
* @param opts.streamId Advanced continuation hook for caller-managed streams.
|
|
2676
|
+
* @param opts.allocatedTokens Advanced continuation hook for `$N` / `#N` token allocation state.
|
|
2677
|
+
* @param opts.retry Retry policy for transient first-chunk failures, or `false` to disable automatic retry.
|
|
2678
|
+
*/
|
|
2679
|
+
apply: async (orgName, repoName, message, operations, opts) => {
|
|
2680
|
+
try {
|
|
2681
|
+
return await submitOperationsViaStream(this, {
|
|
2682
|
+
orgName,
|
|
2683
|
+
repoName,
|
|
2684
|
+
committer: opts?.committer,
|
|
2685
|
+
message,
|
|
2686
|
+
componentId: opts?.componentId,
|
|
2687
|
+
chunkSize: opts?.chunkSize,
|
|
2688
|
+
skipExisting: opts?.skipExisting,
|
|
2689
|
+
streamId: opts?.streamId,
|
|
2690
|
+
allocatedTokens: opts?.allocatedTokens,
|
|
2691
|
+
retry: opts?.retry,
|
|
2692
|
+
operations
|
|
2693
|
+
});
|
|
2694
|
+
} catch (error) {
|
|
2695
|
+
if (error instanceof PartialStreamSubmissionError) {
|
|
2696
|
+
throw error;
|
|
2697
|
+
}
|
|
2698
|
+
throw toWarmHubError(error);
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
};
|
|
2702
|
+
/**
|
|
2703
|
+
* Organization management surface for namespaces, membership, roles, and scoped member permissions.
|
|
2704
|
+
*/
|
|
2705
|
+
org = {
|
|
2706
|
+
/**
|
|
2707
|
+
* Get an organization by name.
|
|
2708
|
+
*/
|
|
2709
|
+
get: async (orgName) => {
|
|
2710
|
+
try {
|
|
2711
|
+
return await this.trpc.org.get.query({ orgName });
|
|
2712
|
+
} catch (error) {
|
|
2713
|
+
throw toWarmHubError(error);
|
|
2714
|
+
}
|
|
2715
|
+
},
|
|
2716
|
+
/**
|
|
2717
|
+
* Return the caller's role in an organization, or `null` when the caller is not a member.
|
|
2718
|
+
*
|
|
2719
|
+
* Useful for UI gating before showing organization-level controls.
|
|
2720
|
+
*/
|
|
2721
|
+
getCallerRole: async (orgName) => {
|
|
2722
|
+
try {
|
|
2723
|
+
return await this.trpc.org.getCallerRole.query({ orgName });
|
|
2724
|
+
} catch (error) {
|
|
2725
|
+
throw toWarmHubError(error);
|
|
2726
|
+
}
|
|
2727
|
+
},
|
|
2728
|
+
/**
|
|
2729
|
+
* List organizations visible to the caller.
|
|
2730
|
+
*
|
|
2731
|
+
* Archived organizations are hidden unless `includeArchived` is set.
|
|
2732
|
+
*/
|
|
2733
|
+
list: async (opts) => {
|
|
2734
|
+
try {
|
|
2735
|
+
const items = await this.trpc.org.list.query({
|
|
2736
|
+
includeArchived: opts?.includeArchived
|
|
2737
|
+
});
|
|
2738
|
+
return { items };
|
|
2739
|
+
} catch (error) {
|
|
2740
|
+
throw toWarmHubError(error);
|
|
2741
|
+
}
|
|
2742
|
+
},
|
|
2743
|
+
/**
|
|
2744
|
+
* Create a new organization.
|
|
2745
|
+
*
|
|
2746
|
+
* The description is trimmed and empty strings are ignored. Organization names must avoid reserved public slugs such as `docs`, `api`, `login`, and `warmhub`.
|
|
2747
|
+
*
|
|
2748
|
+
* @param displayName Optional display label. Defaults to the organization name when omitted.
|
|
2749
|
+
*/
|
|
2750
|
+
create: async (name, displayName, description) => {
|
|
2751
|
+
try {
|
|
2752
|
+
return await this.trpc.org.create.mutate({
|
|
2753
|
+
name,
|
|
2754
|
+
displayName,
|
|
2755
|
+
description
|
|
2756
|
+
});
|
|
2757
|
+
} catch (error) {
|
|
2758
|
+
throw toWarmHubError(error);
|
|
2759
|
+
}
|
|
2760
|
+
},
|
|
2761
|
+
/**
|
|
2762
|
+
* Set or clear an organization description.
|
|
2763
|
+
*
|
|
2764
|
+
* Descriptions are trimmed; empty strings clear the stored value.
|
|
2765
|
+
*/
|
|
2766
|
+
setDescription: async (orgName, description) => {
|
|
2767
|
+
try {
|
|
2768
|
+
return await this.trpc.org.setDescription.mutate({
|
|
2769
|
+
orgName,
|
|
2770
|
+
description
|
|
2771
|
+
});
|
|
2772
|
+
} catch (error) {
|
|
2773
|
+
throw toWarmHubError(error);
|
|
2774
|
+
}
|
|
2775
|
+
},
|
|
2776
|
+
/**
|
|
2777
|
+
* Update an organization's display name.
|
|
2778
|
+
*
|
|
2779
|
+
* Display names are trimmed; empty or whitespace-only values are rejected.
|
|
2780
|
+
*/
|
|
2781
|
+
setDisplayName: async (orgName, displayName) => {
|
|
2782
|
+
try {
|
|
2783
|
+
return await this.trpc.org.setDisplayName.mutate({
|
|
2784
|
+
orgName,
|
|
2785
|
+
displayName
|
|
2786
|
+
});
|
|
2787
|
+
} catch (error) {
|
|
2788
|
+
throw toWarmHubError(error);
|
|
2789
|
+
}
|
|
2790
|
+
},
|
|
2791
|
+
/**
|
|
2792
|
+
* Rename an organization in place.
|
|
2793
|
+
*
|
|
2794
|
+
* The new slug must satisfy the same naming and reserved-name rules as organization creation.
|
|
2795
|
+
*/
|
|
2796
|
+
rename: async (orgName, newName) => {
|
|
2797
|
+
try {
|
|
2798
|
+
return await this.trpc.org.rename.mutate({
|
|
2799
|
+
orgName,
|
|
2800
|
+
newName
|
|
2801
|
+
});
|
|
2802
|
+
} catch (error) {
|
|
2803
|
+
throw toWarmHubError(error);
|
|
2804
|
+
}
|
|
2805
|
+
},
|
|
2806
|
+
/**
|
|
2807
|
+
* Atomic combined update of an organization's display name and/or slug.
|
|
2808
|
+
*
|
|
2809
|
+
* Both writes run in a single backend transaction so a slug conflict cannot leave a partial display-name change behind. Pass at least one of `displayName` or `newName`.
|
|
2810
|
+
*/
|
|
2811
|
+
update: async (input) => {
|
|
2812
|
+
try {
|
|
2813
|
+
return await this.trpc.org.update.mutate(input);
|
|
2814
|
+
} catch (error) {
|
|
2815
|
+
throw toWarmHubError(error);
|
|
2816
|
+
}
|
|
2817
|
+
},
|
|
2818
|
+
/**
|
|
2819
|
+
* Add a member to an organization or create a pending invite.
|
|
2820
|
+
*
|
|
2821
|
+
* The role defaults to `editor`. If the email address does not belong to an existing WarmHub user, WarmHub creates a pending invite and attempts to send the invite email asynchronously. Only owners can assign the `owner` role.
|
|
2822
|
+
*
|
|
2823
|
+
* @param role Organization role to assign. Defaults to `editor`.
|
|
2824
|
+
*/
|
|
2825
|
+
addMember: async (orgName, email, role = "editor") => {
|
|
2826
|
+
try {
|
|
2827
|
+
return await this.trpc.org.addMember.mutate({
|
|
2828
|
+
orgName,
|
|
2829
|
+
email,
|
|
2830
|
+
role
|
|
2831
|
+
});
|
|
2832
|
+
} catch (error) {
|
|
2833
|
+
throw toWarmHubError(error);
|
|
2834
|
+
}
|
|
2835
|
+
},
|
|
2836
|
+
/**
|
|
2837
|
+
* Remove an active member or revoke a pending invite by email address.
|
|
2838
|
+
*/
|
|
2839
|
+
removeMember: async (orgName, email) => {
|
|
2840
|
+
try {
|
|
2841
|
+
await this.trpc.org.removeMember.mutate({
|
|
2842
|
+
orgName,
|
|
2843
|
+
email
|
|
2844
|
+
});
|
|
2845
|
+
} catch (error) {
|
|
2846
|
+
throw toWarmHubError(error);
|
|
2847
|
+
}
|
|
2848
|
+
},
|
|
2849
|
+
/**
|
|
2850
|
+
* Change a member's organization role.
|
|
2851
|
+
*
|
|
2852
|
+
* Only owners can promote another member to owner or demote an existing owner. WarmHub rejects attempts to remove the final owner.
|
|
2853
|
+
*/
|
|
2854
|
+
changeMemberRole: async (orgName, email, role) => {
|
|
2855
|
+
try {
|
|
2856
|
+
return await this.trpc.org.changeMemberRole.mutate({
|
|
2857
|
+
orgName,
|
|
2858
|
+
email,
|
|
2859
|
+
role
|
|
2860
|
+
});
|
|
2861
|
+
} catch (error) {
|
|
2862
|
+
throw toWarmHubError(error);
|
|
2863
|
+
}
|
|
2864
|
+
},
|
|
2865
|
+
/**
|
|
2866
|
+
* Replace a member's scoped permission entries.
|
|
2867
|
+
*
|
|
2868
|
+
* Each entry targets either the organization (`acme`) or one repository (`acme/world`) and carries the full desired permission set for that resource. Matching entries replace the role-derived permission set for that resource; include every permission the member should retain.
|
|
2869
|
+
*
|
|
2870
|
+
* Member scope entries share the same wire shape as personal access token scopes, but `allowedMatches` is enforced for PATs only. Member scopes cannot restrict access by thing-name glob.
|
|
2871
|
+
*
|
|
2872
|
+
* @param scopes Scoped permission entries with `resource` and `permissions` fields.
|
|
2873
|
+
*/
|
|
2874
|
+
setMemberScopes: async (orgName, email, scopes) => {
|
|
2875
|
+
try {
|
|
2876
|
+
await this.trpc.org.setMemberScopes.mutate({
|
|
2877
|
+
orgName,
|
|
2878
|
+
email,
|
|
2879
|
+
scopes
|
|
2880
|
+
});
|
|
2881
|
+
} catch (error) {
|
|
2882
|
+
throw toWarmHubError(error);
|
|
2883
|
+
}
|
|
2884
|
+
},
|
|
2885
|
+
/**
|
|
2886
|
+
* Remove all scoped permission entries from a member.
|
|
2887
|
+
*
|
|
2888
|
+
* After clearing, the member's effective access comes from their organization role only.
|
|
2889
|
+
*/
|
|
2890
|
+
clearMemberScopes: async (orgName, email) => {
|
|
2891
|
+
try {
|
|
2892
|
+
await this.trpc.org.clearMemberScopes.mutate({
|
|
2893
|
+
orgName,
|
|
2894
|
+
email
|
|
2895
|
+
});
|
|
2896
|
+
} catch (error) {
|
|
2897
|
+
throw toWarmHubError(error);
|
|
2898
|
+
}
|
|
2899
|
+
},
|
|
2900
|
+
/**
|
|
2901
|
+
* List organization members and pending invites.
|
|
2902
|
+
*
|
|
2903
|
+
* The response includes the caller's current organization role so frontend settings pages can gate owner/admin-only controls without making a second request.
|
|
2904
|
+
*
|
|
2905
|
+
* @param opts.pending When `true`, return only pending invites.
|
|
2906
|
+
*/
|
|
2907
|
+
listMembers: async (orgName, opts) => {
|
|
2908
|
+
try {
|
|
2909
|
+
return await this.trpc.org.listMembers.query({
|
|
2910
|
+
orgName,
|
|
2911
|
+
pending: opts?.pending
|
|
2912
|
+
});
|
|
2913
|
+
} catch (error) {
|
|
2914
|
+
throw toWarmHubError(error);
|
|
2915
|
+
}
|
|
2916
|
+
},
|
|
2917
|
+
/**
|
|
2918
|
+
* Archive an organization, blocking new repositories and membership changes.
|
|
2919
|
+
*/
|
|
2920
|
+
archive: async (orgName) => {
|
|
2921
|
+
try {
|
|
2922
|
+
return await this.trpc.org.archive.mutate({
|
|
2923
|
+
orgName
|
|
2924
|
+
});
|
|
2925
|
+
} catch (error) {
|
|
2926
|
+
throw toWarmHubError(error);
|
|
2927
|
+
}
|
|
2928
|
+
},
|
|
2929
|
+
/**
|
|
2930
|
+
* Unarchive an organization.
|
|
2931
|
+
*/
|
|
2932
|
+
unarchive: async (orgName) => {
|
|
2933
|
+
try {
|
|
2934
|
+
return await this.trpc.org.unarchive.mutate({
|
|
2935
|
+
orgName
|
|
2936
|
+
});
|
|
2937
|
+
} catch (error) {
|
|
2938
|
+
throw toWarmHubError(error);
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
};
|
|
2942
|
+
/**
|
|
2943
|
+
* Repository management surface for lifecycle operations, metadata, statistics, and content documents.
|
|
2944
|
+
*
|
|
2945
|
+
* @see https://docs.warmhub.ai/sdk/repo-stats/
|
|
2946
|
+
*/
|
|
2947
|
+
repo = {
|
|
2948
|
+
/**
|
|
2949
|
+
* Get a repository by organization and repository name.
|
|
2950
|
+
*/
|
|
2951
|
+
get: async (orgName, repoName) => {
|
|
2952
|
+
try {
|
|
2953
|
+
return await this.trpc.repo.get.query({
|
|
2954
|
+
orgName,
|
|
2955
|
+
repoName
|
|
2956
|
+
});
|
|
2957
|
+
} catch (error) {
|
|
2958
|
+
throw toWarmHubError(error);
|
|
2959
|
+
}
|
|
2960
|
+
},
|
|
2961
|
+
/**
|
|
2962
|
+
* Return authoritative active-item totals for a single repository.
|
|
2963
|
+
*
|
|
2964
|
+
* 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.
|
|
2965
|
+
*
|
|
2966
|
+
* The per-shape breakdown counts active things by shape; assertions are not included in that map.
|
|
2967
|
+
*/
|
|
2968
|
+
getStats: async (orgName, repoName) => {
|
|
2969
|
+
try {
|
|
2970
|
+
return await this.trpc.repo.stats.query({
|
|
2971
|
+
orgName,
|
|
2972
|
+
repoName
|
|
2973
|
+
});
|
|
2974
|
+
} catch (error) {
|
|
2975
|
+
throw toWarmHubError(error);
|
|
2976
|
+
}
|
|
2977
|
+
},
|
|
2978
|
+
/**
|
|
2979
|
+
* Return active-item totals for up to 100 repositories in one request.
|
|
2980
|
+
*
|
|
2981
|
+
* Use this instead of issuing one `getStats` request per repository when building organization dashboards. Batch entries include the exact `total` for visible repositories; call `getStats` for an individual repository when you need the per-shape map.
|
|
2982
|
+
*/
|
|
2983
|
+
getStatsBatch: async (orgName, repoNames) => {
|
|
2984
|
+
try {
|
|
2985
|
+
return await this.trpc.repo.statsBatch.query({
|
|
2986
|
+
orgName,
|
|
2987
|
+
repoNames
|
|
2988
|
+
});
|
|
2989
|
+
} catch (error) {
|
|
2990
|
+
throw toWarmHubError(error);
|
|
2991
|
+
}
|
|
2992
|
+
},
|
|
2993
|
+
/**
|
|
2994
|
+
* Return configuration-surface counts for a repository.
|
|
2995
|
+
*
|
|
2996
|
+
* Currently this reports the number of subscriptions attached to the repository, which is useful before delete or visibility-change flows.
|
|
2997
|
+
*/
|
|
2998
|
+
getConfigureStats: async (orgName, repoName) => {
|
|
2999
|
+
try {
|
|
3000
|
+
return await this.trpc.repo.configureStats.query({
|
|
3001
|
+
orgName,
|
|
3002
|
+
repoName
|
|
3003
|
+
});
|
|
3004
|
+
} catch (error) {
|
|
3005
|
+
throw toWarmHubError(error);
|
|
3006
|
+
}
|
|
3007
|
+
},
|
|
3008
|
+
/**
|
|
3009
|
+
* Return per-shape thing and assertion counts for a repository.
|
|
3010
|
+
*
|
|
3011
|
+
* The server computes the totals directly, so callers do not need to page through repository contents to build shape summary UI.
|
|
3012
|
+
*/
|
|
3013
|
+
getShapeInstanceCounts: async (orgName, repoName) => {
|
|
3014
|
+
try {
|
|
3015
|
+
return await this.trpc.repo.shapeInstanceCounts.query({
|
|
3016
|
+
orgName,
|
|
3017
|
+
repoName
|
|
3018
|
+
});
|
|
3019
|
+
} catch (error) {
|
|
3020
|
+
throw toWarmHubError(error);
|
|
3021
|
+
}
|
|
3022
|
+
},
|
|
3023
|
+
/**
|
|
3024
|
+
* List repositories in an organization.
|
|
3025
|
+
*
|
|
3026
|
+
* Archived repositories are hidden by default. Search and sort options are applied before pagination.
|
|
3027
|
+
*/
|
|
3028
|
+
list: async (orgName, opts) => {
|
|
3029
|
+
try {
|
|
3030
|
+
return await this.trpc.repo.list.query({
|
|
3031
|
+
orgName,
|
|
3032
|
+
limit: opts?.limit,
|
|
3033
|
+
cursor: opts?.cursor,
|
|
3034
|
+
includeArchived: opts?.includeArchived,
|
|
3035
|
+
search: opts?.search,
|
|
3036
|
+
sort: opts?.sort
|
|
3037
|
+
});
|
|
3038
|
+
} catch (error) {
|
|
3039
|
+
throw toWarmHubError(error);
|
|
3040
|
+
}
|
|
3041
|
+
},
|
|
3042
|
+
/**
|
|
3043
|
+
* Create a repository inside an organization.
|
|
3044
|
+
*
|
|
3045
|
+
* Repositories are private by default. Descriptions are trimmed and capped by the backend.
|
|
3046
|
+
*
|
|
3047
|
+
* @param visibility `public` or `private`; defaults to `private`.
|
|
3048
|
+
*/
|
|
3049
|
+
create: async (orgName, repoName, description, visibility) => {
|
|
3050
|
+
try {
|
|
3051
|
+
return await this.trpc.repo.create.mutate({
|
|
3052
|
+
orgName,
|
|
3053
|
+
repoName,
|
|
3054
|
+
description,
|
|
3055
|
+
visibility
|
|
3056
|
+
});
|
|
3057
|
+
} catch (error) {
|
|
3058
|
+
throw toWarmHubError(error);
|
|
3059
|
+
}
|
|
3060
|
+
},
|
|
3061
|
+
/**
|
|
3062
|
+
* Set or clear a repository description.
|
|
3063
|
+
*
|
|
3064
|
+
* Descriptions are trimmed; empty strings clear the stored value.
|
|
3065
|
+
*/
|
|
3066
|
+
setDescription: async (orgName, repoName, description) => {
|
|
3067
|
+
try {
|
|
3068
|
+
return await this.trpc.repo.setDescription.mutate({
|
|
3069
|
+
orgName,
|
|
3070
|
+
repoName,
|
|
3071
|
+
description
|
|
3072
|
+
});
|
|
3073
|
+
} catch (error) {
|
|
3074
|
+
throw toWarmHubError(error);
|
|
3075
|
+
}
|
|
3076
|
+
},
|
|
3077
|
+
/**
|
|
3078
|
+
* Set a repository's visibility to `public` or `private`.
|
|
3079
|
+
*/
|
|
3080
|
+
setVisibility: async (orgName, repoName, visibility) => {
|
|
3081
|
+
try {
|
|
3082
|
+
return await this.trpc.repo.setVisibility.mutate({
|
|
3083
|
+
orgName,
|
|
3084
|
+
repoName,
|
|
3085
|
+
visibility
|
|
3086
|
+
});
|
|
3087
|
+
} catch (error) {
|
|
3088
|
+
throw toWarmHubError(error);
|
|
3089
|
+
}
|
|
3090
|
+
},
|
|
3091
|
+
/**
|
|
3092
|
+
* Rename a repository within its organization.
|
|
3093
|
+
*
|
|
3094
|
+
* The new name must be unused in the organization and follow the same path-segment rules as repository creation.
|
|
3095
|
+
*/
|
|
3096
|
+
rename: async (orgName, repoName, newName) => {
|
|
3097
|
+
try {
|
|
3098
|
+
return await this.trpc.repo.rename.mutate({
|
|
3099
|
+
orgName,
|
|
3100
|
+
repoName,
|
|
3101
|
+
newName
|
|
3102
|
+
});
|
|
3103
|
+
} catch (error) {
|
|
3104
|
+
throw toWarmHubError(error);
|
|
3105
|
+
}
|
|
3106
|
+
},
|
|
3107
|
+
/**
|
|
3108
|
+
* Archive a repository, blocking new writes.
|
|
3109
|
+
*/
|
|
3110
|
+
archive: async (orgName, repoName) => {
|
|
3111
|
+
try {
|
|
3112
|
+
return await this.trpc.repo.archive.mutate({
|
|
3113
|
+
orgName,
|
|
3114
|
+
repoName
|
|
3115
|
+
});
|
|
3116
|
+
} catch (error) {
|
|
3117
|
+
throw toWarmHubError(error);
|
|
3118
|
+
}
|
|
3119
|
+
},
|
|
3120
|
+
/**
|
|
3121
|
+
* Unarchive a repository.
|
|
3122
|
+
*/
|
|
3123
|
+
unarchive: async (orgName, repoName) => {
|
|
3124
|
+
try {
|
|
3125
|
+
return await this.trpc.repo.unarchive.mutate({
|
|
3126
|
+
orgName,
|
|
3127
|
+
repoName
|
|
3128
|
+
});
|
|
3129
|
+
} catch (error) {
|
|
3130
|
+
throw toWarmHubError(error);
|
|
3131
|
+
}
|
|
3132
|
+
},
|
|
3133
|
+
/**
|
|
3134
|
+
* Soft-delete a repository.
|
|
3135
|
+
*
|
|
3136
|
+
* The repository is hidden immediately and scheduled for permanent purge after a 30-day grace window. WarmHub blocks deletion when another repository still has inbound cross-repo references, active subscriptions, or active credential grants that depend on the repository.
|
|
3137
|
+
*/
|
|
3138
|
+
delete: async (orgName, repoName) => {
|
|
3139
|
+
try {
|
|
3140
|
+
const result = await this.trpc.repo.delete.mutate({
|
|
3141
|
+
orgName,
|
|
3142
|
+
repoName
|
|
3143
|
+
});
|
|
3144
|
+
return { graceExpiresAt: new Date(result.graceExpiresAt) };
|
|
3145
|
+
} catch (error) {
|
|
3146
|
+
throw toWarmHubError(error);
|
|
3147
|
+
}
|
|
3148
|
+
},
|
|
3149
|
+
/**
|
|
3150
|
+
* Immediately and irreversibly purge a repository.
|
|
3151
|
+
*
|
|
3152
|
+
* This owner-only operation has no grace window and uses the same inbound-reference guard as `delete`.
|
|
3153
|
+
*/
|
|
3154
|
+
hardDelete: async (orgName, repoName) => {
|
|
3155
|
+
try {
|
|
3156
|
+
await this.trpc.repo.hardDelete.mutate({
|
|
3157
|
+
orgName,
|
|
3158
|
+
repoName
|
|
3159
|
+
});
|
|
3160
|
+
} catch (error) {
|
|
3161
|
+
throw toWarmHubError(error);
|
|
3162
|
+
}
|
|
3163
|
+
},
|
|
3164
|
+
/**
|
|
3165
|
+
* List repositories with dashboard-oriented per-repository metadata.
|
|
3166
|
+
*
|
|
3167
|
+
* Each item includes exact active counts, an activity-oriented `lastWriteAt`, and a `hasErrors` flag for terminal action failures.
|
|
3168
|
+
*
|
|
3169
|
+
* Search and sort are applied before pagination, so cursors remain stable across the filtered and ordered list.
|
|
3170
|
+
*/
|
|
3171
|
+
listPage: async (orgName, opts) => {
|
|
3172
|
+
try {
|
|
3173
|
+
return await this.trpc.repo.listPage.query({
|
|
3174
|
+
orgName,
|
|
3175
|
+
limit: opts?.limit,
|
|
3176
|
+
cursor: opts?.cursor,
|
|
3177
|
+
includeArchived: opts?.includeArchived,
|
|
3178
|
+
search: opts?.search,
|
|
3179
|
+
sort: opts?.sort
|
|
3180
|
+
});
|
|
3181
|
+
} catch (error) {
|
|
3182
|
+
throw toWarmHubError(error);
|
|
3183
|
+
}
|
|
3184
|
+
},
|
|
3185
|
+
/**
|
|
3186
|
+
* Fetch a repository's `Content/Readme` markdown record.
|
|
3187
|
+
*
|
|
3188
|
+
* The SDK contract allows `null`; current backend behavior returns a synthesized empty stub for repositories that have not committed README content yet. Callers should still null-check defensively.
|
|
3189
|
+
*/
|
|
3190
|
+
getReadme: async (orgName, repoName) => {
|
|
3191
|
+
try {
|
|
3192
|
+
return await this.trpc.repo.getReadme.query({
|
|
3193
|
+
orgName,
|
|
3194
|
+
repoName
|
|
3195
|
+
});
|
|
3196
|
+
} catch (error) {
|
|
3197
|
+
throw toWarmHubError(error);
|
|
3198
|
+
}
|
|
3199
|
+
},
|
|
3200
|
+
/**
|
|
3201
|
+
* Generate a README draft from the repository's current shapes, things, and assertions.
|
|
3202
|
+
*
|
|
3203
|
+
* This does not commit the draft. Save returned content with `setReadme` after review.
|
|
3204
|
+
*/
|
|
3205
|
+
generateReadme: async (orgName, repoName) => {
|
|
3206
|
+
try {
|
|
3207
|
+
return await this.trpc.repo.generateReadme.mutate({
|
|
3208
|
+
orgName,
|
|
3209
|
+
repoName
|
|
3210
|
+
});
|
|
3211
|
+
} catch (error) {
|
|
3212
|
+
throw toWarmHubError(error);
|
|
3213
|
+
}
|
|
3214
|
+
},
|
|
3215
|
+
/**
|
|
3216
|
+
* Fetch a repository's `Content/Agents` markdown record.
|
|
3217
|
+
*
|
|
3218
|
+
* The SDK contract allows `null`; current backend behavior mirrors `getReadme` and returns a synthesized empty stub when no AGENTS.md content has been committed yet.
|
|
3219
|
+
*/
|
|
3220
|
+
getAgents: async (orgName, repoName) => {
|
|
3221
|
+
try {
|
|
3222
|
+
return await this.trpc.repo.getAgents.query({
|
|
3223
|
+
orgName,
|
|
3224
|
+
repoName
|
|
3225
|
+
});
|
|
3226
|
+
} catch (error) {
|
|
3227
|
+
throw toWarmHubError(error);
|
|
3228
|
+
}
|
|
3229
|
+
},
|
|
3230
|
+
/**
|
|
3231
|
+
* Commit a new `Content/Readme` value.
|
|
3232
|
+
*
|
|
3233
|
+
* The backend adds or revises the content record through the normal commit pipeline.
|
|
3234
|
+
*/
|
|
3235
|
+
setReadme: async (orgName, repoName, content) => {
|
|
3236
|
+
try {
|
|
3237
|
+
assertContentWithinLimit("Content/Readme.content", content);
|
|
3238
|
+
return await this.trpc.repo.setReadme.mutate({
|
|
3239
|
+
orgName,
|
|
3240
|
+
repoName,
|
|
3241
|
+
content
|
|
3242
|
+
});
|
|
3243
|
+
} catch (error) {
|
|
3244
|
+
throw toWarmHubError(error);
|
|
3245
|
+
}
|
|
3246
|
+
},
|
|
3247
|
+
/**
|
|
3248
|
+
* Commit a new `Content/Agents` value through the normal commit pipeline.
|
|
3249
|
+
*/
|
|
3250
|
+
setAgents: async (orgName, repoName, content) => {
|
|
3251
|
+
try {
|
|
3252
|
+
assertContentWithinLimit("Content/Agents.content", content);
|
|
3253
|
+
return await this.trpc.repo.setAgents.mutate({
|
|
3254
|
+
orgName,
|
|
3255
|
+
repoName,
|
|
3256
|
+
content
|
|
3257
|
+
});
|
|
3258
|
+
} catch (error) {
|
|
3259
|
+
throw toWarmHubError(error);
|
|
3260
|
+
}
|
|
3261
|
+
},
|
|
3262
|
+
/**
|
|
3263
|
+
* Generate an AGENTS.md draft from the repository's current content and schema.
|
|
3264
|
+
*
|
|
3265
|
+
* This does not commit the draft. Save returned content with `setAgents` after review.
|
|
3266
|
+
*/
|
|
3267
|
+
generateAgents: async (orgName, repoName) => {
|
|
3268
|
+
try {
|
|
3269
|
+
return await this.trpc.repo.generateAgents.mutate({
|
|
3270
|
+
orgName,
|
|
3271
|
+
repoName
|
|
3272
|
+
});
|
|
3273
|
+
} catch (error) {
|
|
3274
|
+
throw toWarmHubError(error);
|
|
3275
|
+
}
|
|
3276
|
+
},
|
|
3277
|
+
/**
|
|
3278
|
+
* Fetch the synthesized `Content/LlmsTxt` sitemap for a repository.
|
|
3279
|
+
*
|
|
3280
|
+
* The returned markdown follows the llms.txt convention. Authenticated callers also receive structured reference metadata partitioned by readable outbound and inbound references; cross-org references the caller cannot read are omitted.
|
|
3281
|
+
*/
|
|
3282
|
+
getLlmsTxt: async (orgName, repoName) => {
|
|
3283
|
+
try {
|
|
3284
|
+
return await this.trpc.repo.getLlmsTxt.query({
|
|
3285
|
+
orgName,
|
|
3286
|
+
repoName
|
|
3287
|
+
});
|
|
3288
|
+
} catch (error) {
|
|
3289
|
+
throw toWarmHubError(error);
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
};
|
|
3293
|
+
/**
|
|
3294
|
+
* Shape management surface for schema definitions that validate things and assertions.
|
|
3295
|
+
*/
|
|
3296
|
+
shape = {
|
|
3297
|
+
/**
|
|
3298
|
+
* List shape definitions in a repository.
|
|
3299
|
+
*
|
|
3300
|
+
* Options can include retracted shapes, filter by component ownership, or hide component-owned shapes.
|
|
3301
|
+
*/
|
|
3302
|
+
list: async (orgName, repoName, opts) => {
|
|
3303
|
+
try {
|
|
3304
|
+
return await this.trpc.shape.list.query({
|
|
3305
|
+
orgName,
|
|
3306
|
+
repoName,
|
|
3307
|
+
match: opts?.match,
|
|
3308
|
+
componentId: opts?.componentId,
|
|
3309
|
+
excludeComponents: opts?.excludeComponents,
|
|
3310
|
+
includeRetracted: opts?.includeRetracted
|
|
3311
|
+
});
|
|
3312
|
+
} catch (error) {
|
|
3313
|
+
throw toWarmHubError(error);
|
|
3314
|
+
}
|
|
3315
|
+
},
|
|
3316
|
+
/**
|
|
3317
|
+
* Get one shape definition by name.
|
|
3318
|
+
*
|
|
3319
|
+
* Returns the full shape thing record, with `name`,
|
|
3320
|
+
* `kind: "shape"`, `active`, and a nested `version: { version, operation,
|
|
3321
|
+
* data, dataHash } | null`. This is **not** the same shape as the
|
|
3322
|
+
* shape returned by `create` and `revise`: the change result
|
|
3323
|
+
* is flat (`name`, `operation`, `version: number`, `dataHash`) and does
|
|
3324
|
+
* not carry `data`. To read shape fields, call `get` and read
|
|
3325
|
+
* `result.version?.data` — the change result alone is not enough.
|
|
3326
|
+
*
|
|
3327
|
+
* @param opts.includeRetracted Include a retracted shape instead of treating it as missing.
|
|
3328
|
+
*/
|
|
3329
|
+
get: async (orgName, repoName, shapeName, opts) => {
|
|
3330
|
+
try {
|
|
3331
|
+
return await this.trpc.shape.get.query({
|
|
3332
|
+
orgName,
|
|
3333
|
+
repoName,
|
|
3334
|
+
shapeName,
|
|
3335
|
+
includeRetracted: opts?.includeRetracted
|
|
3336
|
+
});
|
|
3337
|
+
} catch (error) {
|
|
3338
|
+
throw toWarmHubError(error);
|
|
3339
|
+
}
|
|
3340
|
+
},
|
|
3341
|
+
/**
|
|
3342
|
+
* Create a shape definition.
|
|
3343
|
+
*
|
|
3344
|
+
* Shape data should describe the fields used to validate things and assertions with that shape.
|
|
3345
|
+
*
|
|
3346
|
+
* Returns `{ name, operation, version, dataHash }`.
|
|
3347
|
+
* The result confirms the write and exposes the new version number, but it
|
|
3348
|
+
* does **not** include the shape data — call `client.shape.get` for the
|
|
3349
|
+
* full shape record (with `kind`, `active`, and nested
|
|
3350
|
+
* `version.data`).
|
|
3351
|
+
*
|
|
3352
|
+
* @param opts.description Optional human-readable shape description.
|
|
3353
|
+
*/
|
|
3354
|
+
create: async (orgName, repoName, shapeName, fields, opts) => {
|
|
3355
|
+
try {
|
|
3356
|
+
return await this.trpc.shape.create.mutate({
|
|
3357
|
+
orgName,
|
|
3358
|
+
repoName,
|
|
3359
|
+
shapeName,
|
|
3360
|
+
fields,
|
|
3361
|
+
description: opts?.description
|
|
3362
|
+
});
|
|
3363
|
+
} catch (error) {
|
|
3364
|
+
throw toWarmHubError(error);
|
|
3365
|
+
}
|
|
3366
|
+
},
|
|
3367
|
+
/**
|
|
3368
|
+
* Revise a shape definition, creating a new shape version.
|
|
3369
|
+
*
|
|
3370
|
+
* Returns `{ name, operation, version, dataHash }`,
|
|
3371
|
+
* the same flat shape as `create`. To read the revised fields back, call
|
|
3372
|
+
* `client.shape.get` for the full shape record.
|
|
3373
|
+
*
|
|
3374
|
+
* @param opts.description Optional human-readable description for the revised shape.
|
|
3375
|
+
*/
|
|
3376
|
+
revise: async (orgName, repoName, shapeName, newFields, opts) => {
|
|
3377
|
+
try {
|
|
3378
|
+
return await this.trpc.shape.revise.mutate({
|
|
3379
|
+
orgName,
|
|
3380
|
+
repoName,
|
|
3381
|
+
shapeName,
|
|
3382
|
+
newFields,
|
|
3383
|
+
description: opts?.description
|
|
3384
|
+
});
|
|
3385
|
+
} catch (error) {
|
|
3386
|
+
throw toWarmHubError(error);
|
|
3387
|
+
}
|
|
3388
|
+
},
|
|
3389
|
+
/**
|
|
3390
|
+
* Retract a shape definition.
|
|
3391
|
+
*/
|
|
3392
|
+
remove: async (orgName, repoName, shapeName) => {
|
|
3393
|
+
try {
|
|
3394
|
+
return await this.trpc.shape.remove.mutate({
|
|
3395
|
+
orgName,
|
|
3396
|
+
repoName,
|
|
3397
|
+
shapeName
|
|
3398
|
+
});
|
|
3399
|
+
} catch (error) {
|
|
3400
|
+
throw toWarmHubError(error);
|
|
3401
|
+
}
|
|
3402
|
+
},
|
|
3403
|
+
/**
|
|
3404
|
+
* Rename a shape within a repository.
|
|
3405
|
+
*
|
|
3406
|
+
* The rename is applied in place: the existing shape history is preserved and no new version is created.
|
|
3407
|
+
*/
|
|
3408
|
+
rename: async (orgName, repoName, oldName, newName) => {
|
|
3409
|
+
try {
|
|
3410
|
+
return await this.trpc.shape.rename.mutate({
|
|
3411
|
+
orgName,
|
|
3412
|
+
repoName,
|
|
3413
|
+
oldName,
|
|
3414
|
+
newName
|
|
3415
|
+
});
|
|
3416
|
+
} catch (error) {
|
|
3417
|
+
throw toWarmHubError(error);
|
|
3418
|
+
}
|
|
3419
|
+
},
|
|
3420
|
+
/**
|
|
3421
|
+
* Return add, revise, retract, and rename history for a shape.
|
|
3422
|
+
*
|
|
3423
|
+
* Use pagination options for long-lived shapes with many revisions.
|
|
3424
|
+
*/
|
|
3425
|
+
history: async (orgName, repoName, name, opts = {}) => {
|
|
3426
|
+
try {
|
|
3427
|
+
return await this.trpc.shape.history.query({
|
|
3428
|
+
orgName,
|
|
3429
|
+
repoName,
|
|
3430
|
+
name,
|
|
3431
|
+
includeRetracted: opts.includeRetracted,
|
|
3432
|
+
limit: opts.limit,
|
|
3433
|
+
cursor: opts.cursor
|
|
3434
|
+
});
|
|
3435
|
+
} catch (error) {
|
|
3436
|
+
throw toWarmHubError(error);
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
};
|
|
3440
|
+
/**
|
|
3441
|
+
* Webhook and cron subscription management surface scoped to a repository.
|
|
3442
|
+
*
|
|
3443
|
+
* @see https://docs.warmhub.ai/sdk/component-identity/#subscriptions
|
|
3444
|
+
*/
|
|
3445
|
+
subscription = {
|
|
3446
|
+
/**
|
|
3447
|
+
* Create a webhook or cron subscription.
|
|
3448
|
+
*
|
|
3449
|
+
* Both subscription kinds require a delivery URL. Webhook subscriptions can also forward events from another repository, allow trace reentry, bind fallback delivery, and opt into success notifications. Component identity is set at creation time and follows the same authority rules as commit writes.
|
|
3450
|
+
*
|
|
3451
|
+
* Webhook subscriptions must specify a shape filter via `shapeName` or `filterJson.shape`, except [shape-lifecycle subscriptions](/subscriptions/creating/#shape-lifecycle-subscriptions) which omit both and rely on a `{ kind: 'shape', ... }` filter.
|
|
3452
|
+
*/
|
|
3453
|
+
create: async (input) => {
|
|
3454
|
+
try {
|
|
3455
|
+
const result = await this.trpc.subscription.create.mutate(
|
|
3456
|
+
sanitizeSubscriptionCreateInput(input)
|
|
3457
|
+
);
|
|
3458
|
+
return normalizeSubscriptionInfo(result);
|
|
3459
|
+
} catch (error) {
|
|
3460
|
+
throw toWarmHubError(error);
|
|
3461
|
+
}
|
|
3462
|
+
},
|
|
3463
|
+
/**
|
|
3464
|
+
* Get one subscription by name.
|
|
3465
|
+
*/
|
|
3466
|
+
get: async (orgName, repoName, name) => {
|
|
3467
|
+
try {
|
|
3468
|
+
const result = await this.trpc.subscription.get.query({
|
|
3469
|
+
orgName,
|
|
3470
|
+
repoName,
|
|
3471
|
+
name
|
|
3472
|
+
});
|
|
3473
|
+
return normalizeSubscriptionInfo(result);
|
|
3474
|
+
} catch (error) {
|
|
3475
|
+
throw toWarmHubError(error);
|
|
3476
|
+
}
|
|
3477
|
+
},
|
|
3478
|
+
/**
|
|
3479
|
+
* List subscriptions attached to a repository.
|
|
3480
|
+
*/
|
|
3481
|
+
list: async (orgName, repoName) => {
|
|
3482
|
+
try {
|
|
3483
|
+
const result = await this.trpc.subscription.list.query({
|
|
3484
|
+
orgName,
|
|
3485
|
+
repoName
|
|
3486
|
+
});
|
|
3487
|
+
return normalizeSubscriptionList(result);
|
|
3488
|
+
} catch (error) {
|
|
3489
|
+
throw toWarmHubError(error);
|
|
3490
|
+
}
|
|
3491
|
+
},
|
|
3492
|
+
/**
|
|
3493
|
+
* Update an existing webhook or cron subscription.
|
|
3494
|
+
*
|
|
3495
|
+
* Use `null` for nullable fields such as fallback webhook URL when you need to clear an existing value.
|
|
3496
|
+
*/
|
|
3497
|
+
update: async (input) => {
|
|
3498
|
+
try {
|
|
3499
|
+
const result = await this.trpc.subscription.update.mutate(
|
|
3500
|
+
sanitizeSubscriptionUpdateInput(input)
|
|
3501
|
+
);
|
|
3502
|
+
return normalizeSubscriptionInfo(result);
|
|
3503
|
+
} catch (error) {
|
|
3504
|
+
throw toWarmHubError(error);
|
|
3505
|
+
}
|
|
3506
|
+
},
|
|
3507
|
+
/**
|
|
3508
|
+
* Pause a subscription.
|
|
3509
|
+
*/
|
|
3510
|
+
pause: async (orgName, repoName, name) => {
|
|
3511
|
+
try {
|
|
3512
|
+
return await this.trpc.subscription.pause.mutate({
|
|
3513
|
+
orgName,
|
|
3514
|
+
repoName,
|
|
3515
|
+
name
|
|
3516
|
+
});
|
|
3517
|
+
} catch (error) {
|
|
3518
|
+
throw toWarmHubError(error);
|
|
3519
|
+
}
|
|
3520
|
+
},
|
|
3521
|
+
/**
|
|
3522
|
+
* Resume a paused subscription.
|
|
3523
|
+
*/
|
|
3524
|
+
resume: async (orgName, repoName, name) => {
|
|
3525
|
+
try {
|
|
3526
|
+
return await this.trpc.subscription.resume.mutate({
|
|
3527
|
+
orgName,
|
|
3528
|
+
repoName,
|
|
3529
|
+
name
|
|
3530
|
+
});
|
|
3531
|
+
} catch (error) {
|
|
3532
|
+
throw toWarmHubError(error);
|
|
3533
|
+
}
|
|
3534
|
+
},
|
|
3535
|
+
/**
|
|
3536
|
+
* Delete a subscription.
|
|
3537
|
+
*/
|
|
3538
|
+
remove: async (orgName, repoName, name) => {
|
|
3539
|
+
try {
|
|
3540
|
+
return await this.trpc.subscription.remove.mutate({
|
|
3541
|
+
orgName,
|
|
3542
|
+
repoName,
|
|
3543
|
+
name
|
|
3544
|
+
});
|
|
3545
|
+
} catch (error) {
|
|
3546
|
+
throw toWarmHubError(error);
|
|
3547
|
+
}
|
|
3548
|
+
},
|
|
3549
|
+
/**
|
|
3550
|
+
* Bind a credential set to a subscription for outbound webhook authentication.
|
|
3551
|
+
*/
|
|
3552
|
+
bindCredentials: async (orgName, repoName, subscriptionName, credentialSetName) => {
|
|
3553
|
+
try {
|
|
3554
|
+
return await this.trpc.subscription.bindCredentials.mutate({
|
|
3555
|
+
orgName,
|
|
3556
|
+
repoName,
|
|
3557
|
+
subscriptionName,
|
|
3558
|
+
credentialSetName
|
|
3559
|
+
});
|
|
3560
|
+
} catch (error) {
|
|
3561
|
+
throw toWarmHubError(error);
|
|
3562
|
+
}
|
|
3563
|
+
},
|
|
3564
|
+
/**
|
|
3565
|
+
* Remove the credential set currently bound to a subscription.
|
|
3566
|
+
*/
|
|
3567
|
+
unbindCredentials: async (orgName, repoName, subscriptionName) => {
|
|
3568
|
+
try {
|
|
3569
|
+
return await this.trpc.subscription.unbindCredentials.mutate({
|
|
3570
|
+
orgName,
|
|
3571
|
+
repoName,
|
|
3572
|
+
subscriptionName
|
|
3573
|
+
});
|
|
3574
|
+
} catch (error) {
|
|
3575
|
+
throw toWarmHubError(error);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
};
|
|
3579
|
+
/**
|
|
3580
|
+
* Low-level action lease, delivery, run, and notification primitives for subscription consumers.
|
|
3581
|
+
*/
|
|
3582
|
+
action = {
|
|
3583
|
+
/**
|
|
3584
|
+
* Acquire an exclusive processing lease for a subscription consumer.
|
|
3585
|
+
*
|
|
3586
|
+
* @param holderId Stable identifier for the process claiming the lease.
|
|
3587
|
+
* @param holderType Kind of consumer claiming the lease.
|
|
3588
|
+
*/
|
|
3589
|
+
acquireLease: async (orgName, repoName, subscriptionName, holderId, holderType, opts) => {
|
|
3590
|
+
try {
|
|
3591
|
+
return await this.trpc.action.acquireLease.mutate({
|
|
3592
|
+
orgName,
|
|
3593
|
+
repoName,
|
|
3594
|
+
subscriptionName,
|
|
3595
|
+
holderId,
|
|
3596
|
+
holderType,
|
|
3597
|
+
graceMs: opts?.graceMs,
|
|
3598
|
+
ttlMs: opts?.ttlMs
|
|
3599
|
+
});
|
|
3600
|
+
} catch (error) {
|
|
3601
|
+
throw toWarmHubError(error);
|
|
3602
|
+
}
|
|
3603
|
+
},
|
|
3604
|
+
/**
|
|
3605
|
+
* Extend the TTL for an existing processing lease.
|
|
3606
|
+
*/
|
|
3607
|
+
heartbeatLease: async (orgName, repoName, subscriptionName, holderId, ttlMs) => {
|
|
3608
|
+
try {
|
|
3609
|
+
return await this.trpc.action.heartbeatLease.mutate({
|
|
3610
|
+
orgName,
|
|
3611
|
+
repoName,
|
|
3612
|
+
subscriptionName,
|
|
3613
|
+
holderId,
|
|
3614
|
+
ttlMs
|
|
3615
|
+
});
|
|
3616
|
+
} catch (error) {
|
|
3617
|
+
throw toWarmHubError(error);
|
|
3618
|
+
}
|
|
3619
|
+
},
|
|
3620
|
+
/**
|
|
3621
|
+
* Release an existing processing lease.
|
|
3622
|
+
*/
|
|
3623
|
+
releaseLease: async (orgName, repoName, subscriptionName, holderId) => {
|
|
3624
|
+
try {
|
|
3625
|
+
return await this.trpc.action.releaseLease.mutate({
|
|
3626
|
+
orgName,
|
|
3627
|
+
repoName,
|
|
3628
|
+
subscriptionName,
|
|
3629
|
+
holderId
|
|
3630
|
+
});
|
|
3631
|
+
} catch (error) {
|
|
3632
|
+
throw toWarmHubError(error);
|
|
3633
|
+
}
|
|
3634
|
+
},
|
|
3635
|
+
/**
|
|
3636
|
+
* Claim one action delivery run for processing.
|
|
3637
|
+
*/
|
|
3638
|
+
claimDelivery: async (orgName, repoName, runId, holderId) => {
|
|
3639
|
+
try {
|
|
3640
|
+
return await this.trpc.action.claimDelivery.mutate({
|
|
3641
|
+
orgName,
|
|
3642
|
+
repoName,
|
|
3643
|
+
runId,
|
|
3644
|
+
holderId
|
|
3645
|
+
});
|
|
3646
|
+
} catch (error) {
|
|
3647
|
+
throw toWarmHubError(error);
|
|
3648
|
+
}
|
|
3649
|
+
},
|
|
3650
|
+
/**
|
|
3651
|
+
* Mark one claimed action delivery run as complete.
|
|
3652
|
+
*/
|
|
3653
|
+
completeDelivery: async (orgName, repoName, runId, holderId) => {
|
|
3654
|
+
try {
|
|
3655
|
+
return await this.trpc.action.completeDelivery.mutate({
|
|
3656
|
+
orgName,
|
|
3657
|
+
repoName,
|
|
3658
|
+
runId,
|
|
3659
|
+
holderId
|
|
3660
|
+
});
|
|
3661
|
+
} catch (error) {
|
|
3662
|
+
throw toWarmHubError(error);
|
|
3663
|
+
}
|
|
3664
|
+
},
|
|
3665
|
+
/**
|
|
3666
|
+
* Query the live delivery feed for a subscription.
|
|
3667
|
+
*
|
|
3668
|
+
* Use this for polling or live-log views that need recent delivery status entries.
|
|
3669
|
+
*/
|
|
3670
|
+
liveFeed: async (orgName, repoName, subscriptionName, opts) => {
|
|
3671
|
+
try {
|
|
3672
|
+
return await this.trpc.action.liveFeed.query({
|
|
3673
|
+
orgName,
|
|
3674
|
+
repoName,
|
|
3675
|
+
subscriptionName,
|
|
3676
|
+
limit: opts?.limit,
|
|
3677
|
+
cursor: opts?.cursor
|
|
3678
|
+
});
|
|
3679
|
+
} catch (error) {
|
|
3680
|
+
throw toWarmHubError(error);
|
|
3681
|
+
}
|
|
3682
|
+
},
|
|
3683
|
+
/**
|
|
3684
|
+
* List subscription action runs for a repository.
|
|
3685
|
+
*
|
|
3686
|
+
* The result can be filtered by subscription name, status, and start time.
|
|
3687
|
+
*/
|
|
3688
|
+
listRuns: async (orgName, repoName, opts) => {
|
|
3689
|
+
try {
|
|
3690
|
+
return await this.trpc.action.listRuns.query({
|
|
3691
|
+
orgName,
|
|
3692
|
+
repoName,
|
|
3693
|
+
subscriptionName: opts?.subscriptionName,
|
|
3694
|
+
status: opts?.status,
|
|
3695
|
+
since: opts?.since,
|
|
3696
|
+
limit: opts?.limit
|
|
3697
|
+
});
|
|
3698
|
+
} catch (error) {
|
|
3699
|
+
throw toWarmHubError(error);
|
|
3700
|
+
}
|
|
3701
|
+
},
|
|
3702
|
+
/**
|
|
3703
|
+
* List delivery attempts for one action run.
|
|
3704
|
+
*/
|
|
3705
|
+
getRunAttempts: async (orgName, repoName, runId) => {
|
|
3706
|
+
try {
|
|
3707
|
+
return await this.trpc.action.getRunAttempts.query({
|
|
3708
|
+
orgName,
|
|
3709
|
+
repoName,
|
|
3710
|
+
runId
|
|
3711
|
+
});
|
|
3712
|
+
} catch (error) {
|
|
3713
|
+
throw toWarmHubError(error);
|
|
3714
|
+
}
|
|
3715
|
+
},
|
|
3716
|
+
/**
|
|
3717
|
+
* List repo-scoped action notifications.
|
|
3718
|
+
*
|
|
3719
|
+
* Use `since` or `limit` to bound notification-center style reads.
|
|
3720
|
+
*/
|
|
3721
|
+
listNotifications: async (orgName, repoName, opts) => {
|
|
3722
|
+
try {
|
|
3723
|
+
return await this.trpc.action.listNotifications.query({
|
|
3724
|
+
orgName,
|
|
3725
|
+
repoName,
|
|
3726
|
+
since: opts?.since,
|
|
3727
|
+
limit: opts?.limit
|
|
3728
|
+
});
|
|
3729
|
+
} catch (error) {
|
|
3730
|
+
throw toWarmHubError(error);
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
};
|
|
3734
|
+
/**
|
|
3735
|
+
* Read surface for things, assertions, histories, references, search, and in-place thing renames.
|
|
3736
|
+
*/
|
|
3737
|
+
thing = {
|
|
3738
|
+
/**
|
|
3739
|
+
* Return the current HEAD snapshot for repository contents.
|
|
3740
|
+
*
|
|
3741
|
+
* Filter by shape, kind, assertion target, or glob `match` pattern, and choose the data mode appropriate for the payload size. Component filters can narrow results to component-owned records or hide component infrastructure records.
|
|
3742
|
+
*
|
|
3743
|
+
* Tokenless reads of public repositories have stricter page-size and page-count limits than authenticated reads.
|
|
3744
|
+
*/
|
|
3745
|
+
head: async (orgName, repoName, opts) => {
|
|
3746
|
+
try {
|
|
3747
|
+
const kind = opts?.kind === "shape" || opts?.kind === "thing" || opts?.kind === "assertion" || opts?.kind === "collection" ? opts.kind : void 0;
|
|
3748
|
+
return await this.trpc.thing.head.query({
|
|
3749
|
+
orgName,
|
|
3750
|
+
repoName,
|
|
3751
|
+
shape: opts?.shape,
|
|
3752
|
+
kind,
|
|
3753
|
+
match: opts?.match,
|
|
3754
|
+
dataMode: opts?.dataMode,
|
|
3755
|
+
includeRetracted: opts?.includeRetracted,
|
|
3756
|
+
limit: opts?.limit,
|
|
3757
|
+
cursor: opts?.cursor,
|
|
3758
|
+
componentId: opts?.componentId,
|
|
3759
|
+
excludeComponents: opts?.excludeComponents,
|
|
3760
|
+
excludeInfraShapes: opts?.excludeInfraShapes
|
|
3761
|
+
});
|
|
3762
|
+
} catch (error) {
|
|
3763
|
+
throw toWarmHubError(error);
|
|
3764
|
+
}
|
|
3765
|
+
},
|
|
3766
|
+
/**
|
|
3767
|
+
* Iterate every current HEAD row matching the supplied filters.
|
|
3768
|
+
*
|
|
3769
|
+
* Prefer this over hand-written cursor loops when scanning all matching records. Pass `opts.cursor` to resume from a saved cursor; the iterator advances the cursor automatically after the first request. Pass `limit` to control page size.
|
|
3770
|
+
*/
|
|
3771
|
+
headIter: (orgName, repoName, opts) => {
|
|
3772
|
+
return paginate(
|
|
3773
|
+
(cursor) => this.thing.head(orgName, repoName, { ...opts, cursor }),
|
|
3774
|
+
(page) => page.items,
|
|
3775
|
+
opts?.cursor
|
|
3776
|
+
);
|
|
3777
|
+
},
|
|
3778
|
+
/**
|
|
3779
|
+
* Materialize every current HEAD row matching the supplied filters.
|
|
3780
|
+
*
|
|
3781
|
+
* Use `max` to guard memory usage; throws a `WarmHubError` with kind `VALIDATION_ERROR` once more than `max` items have actually been observed across pages.
|
|
3782
|
+
*/
|
|
3783
|
+
headAll: async (orgName, repoName, opts) => {
|
|
3784
|
+
const { max, ...pageOpts } = opts ?? {};
|
|
3785
|
+
return await collectPaginatedPages(
|
|
3786
|
+
(cursor) => this.thing.head(orgName, repoName, { ...pageOpts, cursor }),
|
|
3787
|
+
(page) => page.items,
|
|
3788
|
+
max,
|
|
3789
|
+
pageOpts.cursor
|
|
3790
|
+
);
|
|
3791
|
+
},
|
|
3792
|
+
/**
|
|
3793
|
+
* Get one thing, assertion, shape, or collection by wref.
|
|
3794
|
+
*
|
|
3795
|
+
* @param version Optional exact version to pin when the wref is not already version-qualified.
|
|
3796
|
+
* @param opts.includeRetracted Include retracted records instead of treating them as missing.
|
|
3797
|
+
*/
|
|
3798
|
+
get: async (orgName, repoName, wref, version, opts) => {
|
|
3799
|
+
try {
|
|
3800
|
+
return await this.trpc.thing.get.query({
|
|
3801
|
+
orgName,
|
|
3802
|
+
repoName,
|
|
3803
|
+
wref,
|
|
3804
|
+
version,
|
|
3805
|
+
includeRetracted: opts?.includeRetracted
|
|
3806
|
+
});
|
|
3807
|
+
} catch (error) {
|
|
3808
|
+
throw toWarmHubError(error);
|
|
3809
|
+
}
|
|
3810
|
+
},
|
|
3811
|
+
/**
|
|
3812
|
+
* Get one record and its embedded assertion, about, and wref graph.
|
|
3813
|
+
*
|
|
3814
|
+
* Depth and limit options bound traversal size. References the caller cannot read remain string wrefs in the returned graph.
|
|
3815
|
+
*/
|
|
3816
|
+
graph: async (orgName, repoName, wref, opts) => {
|
|
3817
|
+
try {
|
|
3818
|
+
return await this.trpc.thing.graph.query({
|
|
3819
|
+
orgName,
|
|
3820
|
+
repoName,
|
|
3821
|
+
wref,
|
|
3822
|
+
version: opts?.version,
|
|
3823
|
+
depth: opts?.depth,
|
|
3824
|
+
limit: opts?.limit
|
|
3825
|
+
});
|
|
3826
|
+
} catch (error) {
|
|
3827
|
+
throw toWarmHubError(error);
|
|
3828
|
+
}
|
|
3829
|
+
},
|
|
3830
|
+
/**
|
|
3831
|
+
* Batch-fetch wrefs, auto-chunking above the backend's 500-wref transport cap.
|
|
3832
|
+
*
|
|
3833
|
+
* The result preserves duplicate requested wrefs and reports inaccessible or missing refs in `missing` rather than throwing per item. A top-level version pins every unqualified wref; per-wref version pins remain intact.
|
|
3834
|
+
*
|
|
3835
|
+
* @param version Optional exact version to apply to unqualified wrefs.
|
|
3836
|
+
* @param opts.includeRetracted Include retracted records in `items` instead of reporting them in `missing`.
|
|
3837
|
+
* @param opts.chunkSize Maximum wrefs per backend request. Defaults to 500 and is clamped to the backend cap.
|
|
3838
|
+
* @param opts.chunkConcurrency Maximum concurrent chunk requests. Defaults to 1 and is clamped to 8.
|
|
3839
|
+
*/
|
|
3840
|
+
getMany: async (orgName, repoName, wrefs, version, opts) => {
|
|
3841
|
+
try {
|
|
3842
|
+
if (wrefs.length === 0) {
|
|
3843
|
+
return {
|
|
3844
|
+
requested: 0,
|
|
3845
|
+
items: [],
|
|
3846
|
+
missing: []
|
|
3847
|
+
};
|
|
3848
|
+
}
|
|
3849
|
+
const chunkSize = normalizePositiveIntegerOption(
|
|
3850
|
+
opts?.chunkSize,
|
|
3851
|
+
DEFAULT_THING_GET_MANY_CHUNK_SIZE,
|
|
3852
|
+
MAX_THING_GET_MANY_CHUNK_SIZE
|
|
3853
|
+
);
|
|
3854
|
+
const chunkConcurrency = normalizePositiveIntegerOption(
|
|
3855
|
+
opts?.chunkConcurrency,
|
|
3856
|
+
DEFAULT_THING_GET_MANY_CHUNK_CONCURRENCY,
|
|
3857
|
+
MAX_THING_GET_MANY_CHUNK_CONCURRENCY
|
|
3858
|
+
);
|
|
3859
|
+
const fetchChunk = async (chunkWrefs) => {
|
|
3860
|
+
return await this.trpc.thing.getMany.query({
|
|
3861
|
+
orgName,
|
|
3862
|
+
repoName,
|
|
3863
|
+
wrefs: chunkWrefs,
|
|
3864
|
+
version,
|
|
3865
|
+
includeRetracted: opts?.includeRetracted
|
|
3866
|
+
});
|
|
3867
|
+
};
|
|
3868
|
+
if (wrefs.length <= chunkSize) {
|
|
3869
|
+
return await fetchChunk(wrefs);
|
|
3870
|
+
}
|
|
3871
|
+
const chunks = chunkArray(wrefs, chunkSize);
|
|
3872
|
+
const chunkResults = [];
|
|
3873
|
+
for (let start = 0; start < chunks.length; start += chunkConcurrency) {
|
|
3874
|
+
const batch = chunks.slice(start, start + chunkConcurrency);
|
|
3875
|
+
const batchResults = await Promise.all(batch.map(fetchChunk));
|
|
3876
|
+
chunkResults.push(...batchResults);
|
|
3877
|
+
}
|
|
3878
|
+
return {
|
|
3879
|
+
requested: wrefs.length,
|
|
3880
|
+
items: chunkResults.flatMap((result) => result.items),
|
|
3881
|
+
missing: chunkResults.flatMap((result) => result.missing)
|
|
3882
|
+
};
|
|
3883
|
+
} catch (error) {
|
|
3884
|
+
throw toWarmHubError(error);
|
|
3885
|
+
}
|
|
3886
|
+
},
|
|
3887
|
+
/**
|
|
3888
|
+
* Return version history and timeline metadata for repository records.
|
|
3889
|
+
*
|
|
3890
|
+
* Provide at least one selector: a concrete wref, a shape filter, or an assertion target. Shape- and target-filtered histories support pagination and optional collection resolution.
|
|
3891
|
+
*/
|
|
3892
|
+
history: async (orgName, repoName, opts) => {
|
|
3893
|
+
try {
|
|
3894
|
+
return await this.trpc.thing.history.query({
|
|
3895
|
+
orgName,
|
|
3896
|
+
repoName,
|
|
3897
|
+
wref: opts.wref,
|
|
3898
|
+
shape: opts.shape,
|
|
3899
|
+
about: opts.about,
|
|
3900
|
+
includeRetracted: opts.includeRetracted,
|
|
3901
|
+
resolveCollections: opts.resolveCollections,
|
|
3902
|
+
match: opts.match,
|
|
3903
|
+
limit: opts.limit,
|
|
3904
|
+
cursor: opts.cursor
|
|
3905
|
+
});
|
|
3906
|
+
} catch (error) {
|
|
3907
|
+
throw toWarmHubError(error);
|
|
3908
|
+
}
|
|
3909
|
+
},
|
|
3910
|
+
/**
|
|
3911
|
+
* Rename a thing within its shape namespace.
|
|
3912
|
+
*
|
|
3913
|
+
* The rename is applied in place: the thing's existing history is preserved and no new version is created.
|
|
3914
|
+
*/
|
|
3915
|
+
rename: async (orgName, repoName, shapeName, oldName, newName) => {
|
|
3916
|
+
try {
|
|
3917
|
+
return await this.trpc.thing.rename.mutate({
|
|
3918
|
+
orgName,
|
|
3919
|
+
repoName,
|
|
3920
|
+
shapeName,
|
|
3921
|
+
oldName,
|
|
3922
|
+
newName
|
|
3923
|
+
});
|
|
3924
|
+
} catch (error) {
|
|
3925
|
+
throw toWarmHubError(error);
|
|
3926
|
+
}
|
|
3927
|
+
},
|
|
3928
|
+
/**
|
|
3929
|
+
* Resolve a wref to its current projected record.
|
|
3930
|
+
*/
|
|
3931
|
+
resolve: async (orgName, repoName, wref) => {
|
|
3932
|
+
try {
|
|
3933
|
+
return await this.trpc.thing.resolve.query({
|
|
3934
|
+
orgName,
|
|
3935
|
+
repoName,
|
|
3936
|
+
wref
|
|
3937
|
+
});
|
|
3938
|
+
} catch (error) {
|
|
3939
|
+
throw toWarmHubError(error);
|
|
3940
|
+
}
|
|
3941
|
+
},
|
|
3942
|
+
/**
|
|
3943
|
+
* Return assertions about a thing or collection target.
|
|
3944
|
+
*
|
|
3945
|
+
* Filter by assertion shape or glob `match` pattern, optionally resolve collection targets, and page through large assertion sets with `limit` and `cursor`.
|
|
3946
|
+
*
|
|
3947
|
+
* Returns `{ target?, assertions, nextCursor? }`.
|
|
3948
|
+
* The array is named `assertions`, **not** `items`. This breaks the repo-wide
|
|
3949
|
+
* `items` convention used by `HeadResult`, `FilterResult`, `SearchResult`,
|
|
3950
|
+
* `RefsResult`, and `LogResult`; destructure explicitly to avoid the trap:
|
|
3951
|
+
*
|
|
3952
|
+
* ```ts
|
|
3953
|
+
* const { target, assertions } = await client.thing.about(org, repo, "Location/cave");
|
|
3954
|
+
* for (const a of assertions) console.log(a.wref);
|
|
3955
|
+
* ```
|
|
3956
|
+
*
|
|
3957
|
+
* Any returned subjective-logic opinion tuple `(b, d, u, α)` is a binomial opinion — well-formed only when the underlying assertion expresses a binary proposition. See [Opinions as Separate Assertions](/data-modeling/patterns/#opinions-as-separate-assertions).
|
|
3958
|
+
*/
|
|
3959
|
+
about: async (orgName, repoName, wref, opts) => {
|
|
3960
|
+
try {
|
|
3961
|
+
return await this.trpc.thing.about.query({
|
|
3962
|
+
orgName,
|
|
3963
|
+
repoName,
|
|
3964
|
+
wref,
|
|
3965
|
+
shape: opts?.shape,
|
|
3966
|
+
match: opts?.match,
|
|
3967
|
+
includeRetracted: opts?.includeRetracted,
|
|
3968
|
+
depth: opts?.depth,
|
|
3969
|
+
resolveCollections: opts?.resolveCollections,
|
|
3970
|
+
limit: opts?.limit,
|
|
3971
|
+
cursor: opts?.cursor
|
|
3972
|
+
});
|
|
3973
|
+
} catch (error) {
|
|
3974
|
+
throw toWarmHubError(error);
|
|
3975
|
+
}
|
|
3976
|
+
},
|
|
3977
|
+
/**
|
|
3978
|
+
* Iterate every assertion about a thing or collection target.
|
|
3979
|
+
*
|
|
3980
|
+
* Prefer this over hand-written cursor loops when scanning all matching assertions. The iterator reads the `assertions` envelope field and advances the cursor automatically; pass `opts.cursor` to resume from a saved cursor and `limit` to control page size.
|
|
3981
|
+
*/
|
|
3982
|
+
aboutIter: (orgName, repoName, wref, opts) => {
|
|
3983
|
+
return paginate(
|
|
3984
|
+
(cursor) => this.thing.about(orgName, repoName, wref, { ...opts, cursor }),
|
|
3985
|
+
(page) => page.assertions,
|
|
3986
|
+
opts?.cursor
|
|
3987
|
+
);
|
|
3988
|
+
},
|
|
3989
|
+
/**
|
|
3990
|
+
* Materialize every assertion about a thing or collection target.
|
|
3991
|
+
*
|
|
3992
|
+
* Use `max` to guard memory usage; throws a `WarmHubError` with kind `VALIDATION_ERROR` once more than `max` assertions have actually been observed across pages.
|
|
3993
|
+
*/
|
|
3994
|
+
aboutAll: async (orgName, repoName, wref, opts) => {
|
|
3995
|
+
const { max, ...pageOpts } = opts ?? {};
|
|
3996
|
+
return await collectPaginatedPages(
|
|
3997
|
+
(cursor) => this.thing.about(orgName, repoName, wref, { ...pageOpts, cursor }),
|
|
3998
|
+
(page) => page.assertions,
|
|
3999
|
+
max,
|
|
4000
|
+
pageOpts.cursor
|
|
4001
|
+
);
|
|
4002
|
+
},
|
|
4003
|
+
/**
|
|
4004
|
+
* Query repository records by shape, kind, assertion target, text filters, or glob `match` pattern.
|
|
4005
|
+
*
|
|
4006
|
+
* Use this for structured reads where the caller controls filters. For ranked text or vector search, use `thing.search`. For count-only reads, use `thing.count`.
|
|
4007
|
+
*/
|
|
4008
|
+
query: async (orgName, repoName, opts) => {
|
|
4009
|
+
try {
|
|
4010
|
+
const kind = opts?.kind === "shape" || opts?.kind === "thing" || opts?.kind === "assertion" || opts?.kind === "collection" ? opts.kind : void 0;
|
|
4011
|
+
return await this.trpc.thing.query.query({
|
|
4012
|
+
orgName,
|
|
4013
|
+
repoName,
|
|
4014
|
+
shape: opts?.shape,
|
|
4015
|
+
about: opts?.about,
|
|
4016
|
+
kind,
|
|
4017
|
+
match: opts?.match,
|
|
4018
|
+
includeRetracted: opts?.includeRetracted,
|
|
4019
|
+
resolveCollections: opts?.resolveCollections,
|
|
4020
|
+
limit: opts?.limit,
|
|
4021
|
+
cursor: opts?.cursor,
|
|
4022
|
+
componentId: opts?.componentId,
|
|
4023
|
+
excludeComponents: opts?.excludeComponents,
|
|
4024
|
+
excludeInfraShapes: opts?.excludeInfraShapes
|
|
4025
|
+
});
|
|
4026
|
+
} catch (error) {
|
|
4027
|
+
throw toWarmHubError(error);
|
|
4028
|
+
}
|
|
4029
|
+
},
|
|
4030
|
+
/**
|
|
4031
|
+
* Iterate every repository record matching the supplied filters.
|
|
4032
|
+
*
|
|
4033
|
+
* Prefer this over hand-written cursor loops when scanning all matching records. Pass `opts.cursor` to resume from a saved cursor; the iterator advances the cursor automatically after the first request. Pass `limit` to control page size.
|
|
4034
|
+
*/
|
|
4035
|
+
queryIter: (orgName, repoName, opts) => {
|
|
4036
|
+
return paginate(
|
|
4037
|
+
(cursor) => this.thing.query(orgName, repoName, { ...opts, cursor }),
|
|
4038
|
+
(page) => page.items,
|
|
4039
|
+
opts?.cursor
|
|
4040
|
+
);
|
|
4041
|
+
},
|
|
4042
|
+
/**
|
|
4043
|
+
* Materialize every repository record matching the supplied filters.
|
|
4044
|
+
*
|
|
4045
|
+
* Use `max` to guard memory usage; throws a `WarmHubError` with kind `VALIDATION_ERROR` once more than `max` items have actually been observed across pages.
|
|
4046
|
+
*/
|
|
4047
|
+
queryAll: async (orgName, repoName, opts) => {
|
|
4048
|
+
const { max, ...pageOpts } = opts ?? {};
|
|
4049
|
+
return await collectPaginatedPages(
|
|
4050
|
+
(cursor) => this.thing.query(orgName, repoName, { ...pageOpts, cursor }),
|
|
4051
|
+
(page) => page.items,
|
|
4052
|
+
max,
|
|
4053
|
+
pageOpts.cursor
|
|
4054
|
+
);
|
|
4055
|
+
},
|
|
4056
|
+
/**
|
|
4057
|
+
* Search repository records with text, vector, or hybrid mode.
|
|
4058
|
+
*
|
|
4059
|
+
* When searching with an assertion target or collection resolution, pages may be sparse; keep paginating until `nextCursor` is absent.
|
|
4060
|
+
*/
|
|
4061
|
+
search: async (orgName, repoName, query, opts) => {
|
|
4062
|
+
try {
|
|
4063
|
+
const kind = opts?.kind === "shape" || opts?.kind === "thing" || opts?.kind === "assertion" || opts?.kind === "collection" ? opts.kind : void 0;
|
|
4064
|
+
return await this.trpc.thing.search.query({
|
|
4065
|
+
orgName,
|
|
4066
|
+
repoName,
|
|
4067
|
+
query,
|
|
4068
|
+
shape: opts?.shape,
|
|
4069
|
+
about: opts?.about,
|
|
4070
|
+
kind,
|
|
4071
|
+
match: opts?.match,
|
|
4072
|
+
includeRetracted: opts?.includeRetracted,
|
|
4073
|
+
resolveCollections: opts?.resolveCollections,
|
|
4074
|
+
limit: opts?.limit,
|
|
4075
|
+
cursor: opts?.cursor,
|
|
4076
|
+
componentId: opts?.componentId,
|
|
4077
|
+
excludeComponents: opts?.excludeComponents,
|
|
4078
|
+
excludeInfraShapes: opts?.excludeInfraShapes,
|
|
4079
|
+
mode: opts?.mode
|
|
4080
|
+
});
|
|
4081
|
+
} catch (error) {
|
|
4082
|
+
throw toWarmHubError(error);
|
|
4083
|
+
}
|
|
4084
|
+
},
|
|
4085
|
+
/**
|
|
4086
|
+
* Count matching repository records without returning record data.
|
|
4087
|
+
*/
|
|
4088
|
+
count: async (orgName, repoName, opts) => {
|
|
4089
|
+
try {
|
|
4090
|
+
const kind = opts?.kind === "shape" || opts?.kind === "thing" || opts?.kind === "assertion" || opts?.kind === "collection" ? opts.kind : void 0;
|
|
4091
|
+
return await this.trpc.thing.count.query({
|
|
4092
|
+
orgName,
|
|
4093
|
+
repoName,
|
|
4094
|
+
shape: opts?.shape,
|
|
4095
|
+
about: opts?.about,
|
|
4096
|
+
kind,
|
|
4097
|
+
match: opts?.match,
|
|
4098
|
+
includeRetracted: opts?.includeRetracted,
|
|
4099
|
+
resolveCollections: opts?.resolveCollections,
|
|
4100
|
+
componentId: opts?.componentId,
|
|
4101
|
+
excludeComponents: opts?.excludeComponents,
|
|
4102
|
+
excludeInfraShapes: opts?.excludeInfraShapes
|
|
4103
|
+
});
|
|
4104
|
+
} catch (error) {
|
|
4105
|
+
throw toWarmHubError(error);
|
|
4106
|
+
}
|
|
4107
|
+
},
|
|
4108
|
+
/**
|
|
4109
|
+
* Query wref-typed field references for a record.
|
|
4110
|
+
*
|
|
4111
|
+
* Inbound mode finds records whose wref fields point at the supplied wref. Outbound mode finds records that the supplied record points to. Inbound queries can be narrowed to a field path.
|
|
4112
|
+
*/
|
|
4113
|
+
refs: async (orgName, repoName, wref, opts) => {
|
|
4114
|
+
try {
|
|
4115
|
+
return await this.trpc.thing.refs.query({
|
|
4116
|
+
orgName,
|
|
4117
|
+
repoName,
|
|
4118
|
+
wref,
|
|
4119
|
+
direction: opts?.direction,
|
|
4120
|
+
fieldPath: opts?.fieldPath,
|
|
4121
|
+
limit: opts?.limit,
|
|
4122
|
+
cursor: opts?.cursor
|
|
4123
|
+
});
|
|
4124
|
+
} catch (error) {
|
|
4125
|
+
throw toWarmHubError(error);
|
|
4126
|
+
}
|
|
4127
|
+
},
|
|
4128
|
+
/**
|
|
4129
|
+
* Iterate every wref-typed field reference for a record.
|
|
4130
|
+
*
|
|
4131
|
+
* Prefer this over hand-written cursor loops when scanning all matching references. Pass `opts.cursor` to resume from a saved cursor; the iterator advances the cursor automatically after the first request. Pass `limit` to control page size.
|
|
4132
|
+
*/
|
|
4133
|
+
refsIter: (orgName, repoName, wref, opts) => {
|
|
4134
|
+
return paginate(
|
|
4135
|
+
(cursor) => this.thing.refs(orgName, repoName, wref, { ...opts, cursor }),
|
|
4136
|
+
(page) => page.items,
|
|
4137
|
+
opts?.cursor
|
|
4138
|
+
);
|
|
4139
|
+
},
|
|
4140
|
+
/**
|
|
4141
|
+
* Materialize every wref-typed field reference for a record.
|
|
4142
|
+
*
|
|
4143
|
+
* Use `max` to guard memory usage; throws a `WarmHubError` with kind `VALIDATION_ERROR` once more than `max` refs have actually been observed across pages.
|
|
4144
|
+
*/
|
|
4145
|
+
refsAll: async (orgName, repoName, wref, opts) => {
|
|
4146
|
+
const { max, ...pageOpts } = opts ?? {};
|
|
4147
|
+
return await collectPaginatedPages(
|
|
4148
|
+
(cursor) => this.thing.refs(orgName, repoName, wref, { ...pageOpts, cursor }),
|
|
4149
|
+
(page) => page.items,
|
|
4150
|
+
max,
|
|
4151
|
+
pageOpts.cursor
|
|
4152
|
+
);
|
|
4153
|
+
}
|
|
4154
|
+
};
|
|
4155
|
+
/**
|
|
4156
|
+
* Live repository update surface backed by server-sent events.
|
|
4157
|
+
*/
|
|
4158
|
+
live = {
|
|
4159
|
+
/**
|
|
4160
|
+
* Stream refreshed `thing.head` results whenever the repository changes.
|
|
4161
|
+
*
|
|
4162
|
+
* The SDK re-runs the underlying `thing.head` query after each invalidation and passes the latest snapshot to `onUpdate`.
|
|
4163
|
+
*/
|
|
4164
|
+
thingHead: async (orgName, repoName, opts, onUpdate) => {
|
|
4165
|
+
return this.watchRepoQuery(
|
|
4166
|
+
orgName,
|
|
4167
|
+
repoName,
|
|
4168
|
+
opts?.signal,
|
|
4169
|
+
() => this.thing.head(orgName, repoName, {
|
|
4170
|
+
shape: opts?.shape,
|
|
4171
|
+
kind: opts?.kind,
|
|
4172
|
+
match: opts?.match,
|
|
4173
|
+
dataMode: opts?.dataMode,
|
|
4174
|
+
limit: opts?.limit,
|
|
4175
|
+
cursor: opts?.cursor,
|
|
4176
|
+
includeRetracted: opts?.includeRetracted
|
|
4177
|
+
}),
|
|
4178
|
+
onUpdate
|
|
4179
|
+
);
|
|
4180
|
+
},
|
|
4181
|
+
/**
|
|
4182
|
+
* Stream refreshed history results for a single wref whenever the repository changes.
|
|
4183
|
+
*/
|
|
4184
|
+
thingHistory: async (orgName, repoName, opts, onUpdate) => {
|
|
4185
|
+
return this.watchRepoQuery(
|
|
4186
|
+
orgName,
|
|
4187
|
+
repoName,
|
|
4188
|
+
opts.signal,
|
|
4189
|
+
() => this.thing.history(orgName, repoName, {
|
|
4190
|
+
wref: opts.wref,
|
|
4191
|
+
limit: opts.limit,
|
|
4192
|
+
cursor: opts.cursor,
|
|
4193
|
+
includeRetracted: opts.includeRetracted
|
|
4194
|
+
}),
|
|
4195
|
+
onUpdate
|
|
4196
|
+
);
|
|
4197
|
+
},
|
|
4198
|
+
/**
|
|
4199
|
+
* Stream refreshed action live-feed entries for a subscription.
|
|
4200
|
+
*/
|
|
4201
|
+
subscriptionLog: async (orgName, repoName, subscriptionName, opts, onUpdate) => {
|
|
4202
|
+
return this.watchRepoQuery(
|
|
4203
|
+
orgName,
|
|
4204
|
+
repoName,
|
|
4205
|
+
opts?.signal,
|
|
4206
|
+
() => this.action.liveFeed(orgName, repoName, subscriptionName, {
|
|
4207
|
+
limit: opts?.limit,
|
|
4208
|
+
cursor: opts?.cursor
|
|
4209
|
+
}),
|
|
4210
|
+
onUpdate
|
|
4211
|
+
);
|
|
4212
|
+
},
|
|
4213
|
+
/**
|
|
4214
|
+
* Subscribe to raw repository invalidation events.
|
|
4215
|
+
*
|
|
4216
|
+
* Unlike the higher-level live helpers, this method does not re-query. It
|
|
4217
|
+
* forwards invalidation metadata such as affected shapes, affected things,
|
|
4218
|
+
* affected assertion targets, and whether the event corresponds to a new
|
|
4219
|
+
* commit.
|
|
4220
|
+
*
|
|
4221
|
+
* @param opts.signal Optional abort signal used to close the SSE stream.
|
|
4222
|
+
* @param onEvent Callback invoked for each repository invalidation event.
|
|
4223
|
+
*/
|
|
4224
|
+
subscribe: async (orgName, repoName, opts, onEvent) => {
|
|
4225
|
+
const controller = new AbortController();
|
|
4226
|
+
const signal = linkAbortSignal(opts?.signal, controller);
|
|
4227
|
+
let updates = 0;
|
|
4228
|
+
const closed = (async () => {
|
|
4229
|
+
const ticket = await this.trpc.live.issueTicket.mutate({
|
|
4230
|
+
orgName,
|
|
4231
|
+
repoName
|
|
4232
|
+
});
|
|
4233
|
+
const stream = await this.openSse(
|
|
4234
|
+
ticket.ticket,
|
|
4235
|
+
signal,
|
|
4236
|
+
async (event) => {
|
|
4237
|
+
if (event.type !== "invalidate") {
|
|
4238
|
+
return;
|
|
4239
|
+
}
|
|
4240
|
+
const data = event.data;
|
|
4241
|
+
updates++;
|
|
4242
|
+
await onEvent({
|
|
4243
|
+
topic: data.topic,
|
|
4244
|
+
affectedShapes: data.affectedShapes,
|
|
4245
|
+
affectedThings: data.affectedThings,
|
|
4246
|
+
affectedTargets: data.affectedTargets,
|
|
4247
|
+
hasNewCommit: data.hasNewCommit
|
|
4248
|
+
});
|
|
4249
|
+
}
|
|
4250
|
+
);
|
|
4251
|
+
await stream.ready;
|
|
4252
|
+
if (signal.aborted) {
|
|
4253
|
+
return { updates };
|
|
4254
|
+
}
|
|
4255
|
+
await stream.closed;
|
|
4256
|
+
return { updates };
|
|
4257
|
+
})();
|
|
4258
|
+
return {
|
|
4259
|
+
close() {
|
|
4260
|
+
controller.abort();
|
|
4261
|
+
},
|
|
4262
|
+
closed
|
|
4263
|
+
};
|
|
4264
|
+
}
|
|
4265
|
+
};
|
|
4266
|
+
/**
|
|
4267
|
+
* Personal access token management for the authenticated user.
|
|
4268
|
+
*/
|
|
4269
|
+
token = {
|
|
4270
|
+
/**
|
|
4271
|
+
* Create a personal access token for the authenticated user.
|
|
4272
|
+
*
|
|
4273
|
+
* Omit `scopes` to mint a token with the same authority as the calling principal. PATs cannot create or revoke other PATs — token-management permissions are excluded from the grantable set. Server enforces a maximum lifetime; pass an `expiresAt` unix-millis value to clamp earlier.
|
|
4274
|
+
*
|
|
4275
|
+
* See [Personal Access Tokens](/auth/personal-access-tokens/) for scope grammar (resource format, permission strings, `allowedMatches`), rotation, and CI usage.
|
|
4276
|
+
*
|
|
4277
|
+
* @param input.name Caller-chosen identifier for the token, returned in `list`, `get`, and `revoke`.
|
|
4278
|
+
* @param input.scopes Scope entries narrowing the token's authority. Omit for full-principal authority.
|
|
4279
|
+
* @param input.expiresAt Unix epoch milliseconds at which the token expires.
|
|
4280
|
+
* @param input.description Human-readable description shown in token listings.
|
|
4281
|
+
*/
|
|
4282
|
+
create: async (input) => {
|
|
4283
|
+
try {
|
|
4284
|
+
return await this.trpc.token.create.mutate(input);
|
|
4285
|
+
} catch (error) {
|
|
4286
|
+
throw toWarmHubError(error);
|
|
4287
|
+
}
|
|
4288
|
+
},
|
|
4289
|
+
/**
|
|
4290
|
+
* List personal access tokens for the authenticated user.
|
|
4291
|
+
*/
|
|
4292
|
+
list: async () => {
|
|
4293
|
+
try {
|
|
4294
|
+
return await this.trpc.token.list.query();
|
|
4295
|
+
} catch (error) {
|
|
4296
|
+
throw toWarmHubError(error);
|
|
4297
|
+
}
|
|
4298
|
+
},
|
|
4299
|
+
/**
|
|
4300
|
+
* Get one personal access token by name.
|
|
4301
|
+
*/
|
|
4302
|
+
get: async (name) => {
|
|
4303
|
+
try {
|
|
4304
|
+
return await this.trpc.token.get.query({ name });
|
|
4305
|
+
} catch (error) {
|
|
4306
|
+
throw toWarmHubError(error);
|
|
4307
|
+
}
|
|
4308
|
+
},
|
|
4309
|
+
/**
|
|
4310
|
+
* Revoke a personal access token by name.
|
|
4311
|
+
*/
|
|
4312
|
+
revoke: async (name) => {
|
|
4313
|
+
try {
|
|
4314
|
+
return await this.trpc.token.revoke.mutate({ name });
|
|
4315
|
+
} catch (error) {
|
|
4316
|
+
throw toWarmHubError(error);
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
};
|
|
4320
|
+
/**
|
|
4321
|
+
* Low-level stream append surface for callers that already have backend stream operations.
|
|
4322
|
+
*/
|
|
4323
|
+
stream = {
|
|
4324
|
+
/**
|
|
4325
|
+
* Append one non-empty chunk of stream operations to a repository.
|
|
4326
|
+
*
|
|
4327
|
+
* Most callers should prefer `commit.apply` or `OperationBuilder`. Use this low-level surface only when you already have stream operations, a stream ID, and any allocated `$N` / `#N` token state you need to continue.
|
|
4328
|
+
*/
|
|
4329
|
+
append: async (input) => {
|
|
4330
|
+
try {
|
|
4331
|
+
return await this.trpc.stream.append.mutate(input);
|
|
4332
|
+
} catch (error) {
|
|
4333
|
+
throw toWarmHubError(error);
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
};
|
|
4337
|
+
/**
|
|
4338
|
+
* Credential set management for subscription webhook authentication and component integrations.
|
|
4339
|
+
*
|
|
4340
|
+
* Sets are scoped at creation time. Repo-scoped sets are visible only to the owning repo; org-scoped sets can be granted to multiple repositories in the same organization. Methods that operate on a specific set accept `repoName: string | undefined` — pass the owning repo name for repo-scoped sets or `undefined` for org-scoped sets.
|
|
4341
|
+
*/
|
|
4342
|
+
credential = {
|
|
4343
|
+
/**
|
|
4344
|
+
* Create a credential set.
|
|
4345
|
+
*
|
|
4346
|
+
* Credential sets are repo-scoped by default. Org-scoped sets can be granted to multiple repositories in the same organization.
|
|
4347
|
+
*/
|
|
4348
|
+
createSet: async (orgName, repoName, name, opts) => {
|
|
4349
|
+
try {
|
|
4350
|
+
return await this.trpc.credential.createSet.mutate({
|
|
4351
|
+
orgName,
|
|
4352
|
+
repoName,
|
|
4353
|
+
name,
|
|
4354
|
+
scope: opts?.scope ?? "repo",
|
|
4355
|
+
description: opts?.description
|
|
4356
|
+
});
|
|
4357
|
+
} catch (error) {
|
|
4358
|
+
throw toWarmHubError(error);
|
|
4359
|
+
}
|
|
4360
|
+
},
|
|
4361
|
+
/**
|
|
4362
|
+
* List credential sets visible from a repository.
|
|
4363
|
+
*/
|
|
4364
|
+
listSets: async (orgName, repoName) => {
|
|
4365
|
+
try {
|
|
4366
|
+
return await this.trpc.credential.listSets.query({ orgName, repoName });
|
|
4367
|
+
} catch (error) {
|
|
4368
|
+
throw toWarmHubError(error);
|
|
4369
|
+
}
|
|
4370
|
+
},
|
|
4371
|
+
/**
|
|
4372
|
+
* Get credential set metadata without secret values.
|
|
4373
|
+
*/
|
|
4374
|
+
getSet: async (orgName, repoName, name) => {
|
|
4375
|
+
try {
|
|
4376
|
+
return await this.trpc.credential.getSet.query({
|
|
4377
|
+
orgName,
|
|
4378
|
+
repoName,
|
|
4379
|
+
name
|
|
4380
|
+
});
|
|
4381
|
+
} catch (error) {
|
|
4382
|
+
throw toWarmHubError(error);
|
|
4383
|
+
}
|
|
4384
|
+
},
|
|
4385
|
+
/**
|
|
4386
|
+
* Set or replace one secret key in a credential set.
|
|
4387
|
+
*/
|
|
4388
|
+
setKey: async (orgName, repoName, setName, keyName, value) => {
|
|
4389
|
+
try {
|
|
4390
|
+
return await this.trpc.credential.setKey.mutate({
|
|
4391
|
+
orgName,
|
|
4392
|
+
repoName,
|
|
4393
|
+
setName,
|
|
4394
|
+
keyName,
|
|
4395
|
+
value
|
|
4396
|
+
});
|
|
4397
|
+
} catch (error) {
|
|
4398
|
+
throw toWarmHubError(error);
|
|
4399
|
+
}
|
|
4400
|
+
},
|
|
4401
|
+
/**
|
|
4402
|
+
* Set or replace multiple secret keys in one request.
|
|
4403
|
+
*/
|
|
4404
|
+
setKeys: async (orgName, repoName, setName, secrets) => {
|
|
4405
|
+
try {
|
|
4406
|
+
return await this.trpc.credential.setKeys.mutate({
|
|
4407
|
+
orgName,
|
|
4408
|
+
repoName,
|
|
4409
|
+
setName,
|
|
4410
|
+
secrets
|
|
4411
|
+
});
|
|
4412
|
+
} catch (error) {
|
|
4413
|
+
throw toWarmHubError(error);
|
|
4414
|
+
}
|
|
4415
|
+
},
|
|
4416
|
+
/**
|
|
4417
|
+
* Remove one secret key from a credential set.
|
|
4418
|
+
*/
|
|
4419
|
+
unsetKey: async (orgName, repoName, setName, keyName) => {
|
|
4420
|
+
try {
|
|
4421
|
+
return await this.trpc.credential.unsetKey.mutate({
|
|
4422
|
+
orgName,
|
|
4423
|
+
repoName,
|
|
4424
|
+
setName,
|
|
4425
|
+
keyName
|
|
4426
|
+
});
|
|
4427
|
+
} catch (error) {
|
|
4428
|
+
throw toWarmHubError(error);
|
|
4429
|
+
}
|
|
4430
|
+
},
|
|
4431
|
+
/**
|
|
4432
|
+
* Delete a credential set and its stored secrets.
|
|
4433
|
+
*/
|
|
4434
|
+
deleteSet: async (orgName, repoName, setName) => {
|
|
4435
|
+
try {
|
|
4436
|
+
return await this.trpc.credential.deleteSet.mutate({
|
|
4437
|
+
orgName,
|
|
4438
|
+
repoName,
|
|
4439
|
+
setName
|
|
4440
|
+
});
|
|
4441
|
+
} catch (error) {
|
|
4442
|
+
throw toWarmHubError(error);
|
|
4443
|
+
}
|
|
4444
|
+
},
|
|
4445
|
+
/**
|
|
4446
|
+
* List audit entries for a credential set.
|
|
4447
|
+
*/
|
|
4448
|
+
listAuditLog: async (orgName, repoName, setName, opts) => {
|
|
4449
|
+
try {
|
|
4450
|
+
return await this.trpc.credential.listAuditLog.query({
|
|
4451
|
+
orgName,
|
|
4452
|
+
repoName,
|
|
4453
|
+
setName,
|
|
4454
|
+
limit: opts?.limit
|
|
4455
|
+
});
|
|
4456
|
+
} catch (error) {
|
|
4457
|
+
throw toWarmHubError(error);
|
|
4458
|
+
}
|
|
4459
|
+
},
|
|
4460
|
+
/**
|
|
4461
|
+
* Revoke a credential set so it can no longer be exported or bound.
|
|
4462
|
+
*/
|
|
4463
|
+
revokeSet: async (orgName, repoName, setName, opts) => {
|
|
4464
|
+
try {
|
|
4465
|
+
return await this.trpc.credential.revokeSet.mutate({
|
|
4466
|
+
orgName,
|
|
4467
|
+
repoName,
|
|
4468
|
+
setName,
|
|
4469
|
+
reason: opts?.reason
|
|
4470
|
+
});
|
|
4471
|
+
} catch (error) {
|
|
4472
|
+
throw toWarmHubError(error);
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
};
|
|
4476
|
+
/**
|
|
4477
|
+
* Alias for `credential`.
|
|
4478
|
+
*
|
|
4479
|
+
* @hidden
|
|
4480
|
+
*/
|
|
4481
|
+
credentials = this.credential;
|
|
4482
|
+
constructor(apiUrlOrOptions, maybeOptions) {
|
|
4483
|
+
const options = typeof apiUrlOrOptions === "string" ? maybeOptions : apiUrlOrOptions;
|
|
4484
|
+
const apiUrl = typeof apiUrlOrOptions === "string" ? apiUrlOrOptions : apiUrlOrOptions?.apiUrl ?? DEFAULT_API_URL;
|
|
4485
|
+
this.apiUrl = apiUrl;
|
|
4486
|
+
this.fetchImpl = options?.fetch;
|
|
4487
|
+
this.accessToken = options?.accessToken ?? options?.auth?.getToken;
|
|
4488
|
+
this.functionLogMode = options?.functionLogs ?? "off";
|
|
4489
|
+
if (typeof this.accessToken === "function") {
|
|
4490
|
+
const provider = this.accessToken;
|
|
4491
|
+
this.getToken = async () => await provider();
|
|
4492
|
+
} else if (this.accessToken) {
|
|
4493
|
+
const token = this.accessToken;
|
|
4494
|
+
this.getToken = async () => token;
|
|
4495
|
+
}
|
|
4496
|
+
const url = `${apiUrl.replace(/\/$/, "")}/trpc`;
|
|
4497
|
+
const fetch = ((input, init) => this.fetchWithAuth(input, init));
|
|
4498
|
+
Object.defineProperty(this, "trpc", {
|
|
4499
|
+
value: createTRPCProxyClient({
|
|
4500
|
+
links: [
|
|
4501
|
+
splitLink({
|
|
4502
|
+
// Procedures that can be slow on large repos get their own
|
|
4503
|
+
// un-batched HTTP request so a single slow query can't 502 the
|
|
4504
|
+
// render-critical batch alongside it. See issue #1943.
|
|
4505
|
+
condition: (op) => UNBATCHED_TRPC_PATHS.has(op.path),
|
|
4506
|
+
// methodOverride puts inputs in the request body so single-op
|
|
4507
|
+
// queries with large array payloads (e.g. thing.getMany with
|
|
4508
|
+
// hundreds of wrefs) don't blow past Render's ~8 KB URL cap.
|
|
4509
|
+
// tRPC's maxURLLength only splits multi-op batches. See #2390.
|
|
4510
|
+
true: httpLink({ url, fetch, methodOverride: "POST" }),
|
|
4511
|
+
false: httpBatchLink({ url, fetch, methodOverride: "POST" })
|
|
4512
|
+
})
|
|
4513
|
+
]
|
|
4514
|
+
}),
|
|
4515
|
+
enumerable: false,
|
|
4516
|
+
writable: false,
|
|
4517
|
+
configurable: true
|
|
4518
|
+
});
|
|
4519
|
+
}
|
|
4520
|
+
/**
|
|
4521
|
+
* Return a new client that shares this client's backend URL and fetch implementation but uses a different access-token provider.
|
|
4522
|
+
*/
|
|
4523
|
+
withAccessToken(accessToken) {
|
|
4524
|
+
return new _WarmHubClient({
|
|
4525
|
+
apiUrl: this.apiUrl,
|
|
4526
|
+
fetch: this.fetchImpl,
|
|
4527
|
+
accessToken
|
|
4528
|
+
});
|
|
4529
|
+
}
|
|
4530
|
+
/**
|
|
4531
|
+
* Alias for `action`.
|
|
4532
|
+
*
|
|
4533
|
+
* @hidden
|
|
4534
|
+
*/
|
|
4535
|
+
actions = this.action;
|
|
4536
|
+
async openSse(ticket, signal, onEvent) {
|
|
4537
|
+
const response = await this.fetchWithAuth(
|
|
4538
|
+
`${this.apiUrl.replace(/\/$/, "")}/sse?ticket=${encodeURIComponent(ticket)}`,
|
|
4539
|
+
{
|
|
4540
|
+
headers: {
|
|
4541
|
+
accept: "text/event-stream"
|
|
4542
|
+
},
|
|
4543
|
+
signal
|
|
4544
|
+
}
|
|
4545
|
+
);
|
|
4546
|
+
if (!response.ok) {
|
|
4547
|
+
throw new WarmHubError(
|
|
4548
|
+
"BACKEND",
|
|
4549
|
+
`SSE request failed with status ${response.status}`,
|
|
4550
|
+
response.status
|
|
4551
|
+
);
|
|
4552
|
+
}
|
|
4553
|
+
const reader = response.body?.getReader();
|
|
4554
|
+
if (!reader) {
|
|
4555
|
+
throw new WarmHubError("BACKEND", "SSE response body is missing");
|
|
4556
|
+
}
|
|
4557
|
+
let resolveReady = () => {
|
|
4558
|
+
};
|
|
4559
|
+
const ready = new Promise((resolve) => {
|
|
4560
|
+
resolveReady = resolve;
|
|
4561
|
+
});
|
|
4562
|
+
const closed = (async () => {
|
|
4563
|
+
const decoder = new TextDecoder();
|
|
4564
|
+
let buffer = "";
|
|
4565
|
+
try {
|
|
4566
|
+
while (true) {
|
|
4567
|
+
const { done, value } = await reader.read();
|
|
4568
|
+
if (done) {
|
|
4569
|
+
break;
|
|
4570
|
+
}
|
|
4571
|
+
buffer += decoder.decode(value, { stream: true }).replaceAll("\r\n", "\n");
|
|
4572
|
+
while (true) {
|
|
4573
|
+
const separatorIndex = buffer.indexOf("\n\n");
|
|
4574
|
+
if (separatorIndex === -1) {
|
|
4575
|
+
break;
|
|
4576
|
+
}
|
|
4577
|
+
const chunk = buffer.slice(0, separatorIndex);
|
|
4578
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
4579
|
+
const parsed = parseSseChunk(chunk);
|
|
4580
|
+
if (!parsed) {
|
|
4581
|
+
continue;
|
|
4582
|
+
}
|
|
4583
|
+
if (parsed.type === "ready") {
|
|
4584
|
+
resolveReady();
|
|
4585
|
+
continue;
|
|
4586
|
+
}
|
|
4587
|
+
await onEvent(parsed);
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
} catch (error) {
|
|
4591
|
+
if (isAbortError(error) || signal.aborted) {
|
|
4592
|
+
return;
|
|
4593
|
+
}
|
|
4594
|
+
throw error;
|
|
4595
|
+
} finally {
|
|
4596
|
+
resolveReady();
|
|
4597
|
+
reader.releaseLock();
|
|
4598
|
+
}
|
|
4599
|
+
})();
|
|
4600
|
+
return {
|
|
4601
|
+
ready,
|
|
4602
|
+
closed
|
|
4603
|
+
};
|
|
4604
|
+
}
|
|
4605
|
+
async fetchWithAuth(input, init) {
|
|
4606
|
+
const fetchImpl = this.fetchImpl ?? globalThis.fetch;
|
|
4607
|
+
const headers = new Headers(init?.headers);
|
|
4608
|
+
const token = await resolveAccessToken(this.accessToken);
|
|
4609
|
+
if (token) {
|
|
4610
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
4611
|
+
}
|
|
4612
|
+
const doFetch = async (reqInit) => {
|
|
4613
|
+
try {
|
|
4614
|
+
return await fetchImpl(input, reqInit);
|
|
4615
|
+
} catch (err) {
|
|
4616
|
+
if (isConnectionError(err)) {
|
|
4617
|
+
throw new WarmHubError("NETWORK", connectionErrorMessage(this.apiUrl));
|
|
4618
|
+
}
|
|
4619
|
+
throw err;
|
|
4620
|
+
}
|
|
4621
|
+
};
|
|
4622
|
+
const response = await doFetch({ ...init, headers });
|
|
4623
|
+
if (response.status === 401 && typeof this.accessToken === "function") {
|
|
4624
|
+
const freshToken = await resolveAccessToken(this.accessToken);
|
|
4625
|
+
if (freshToken && freshToken !== token) {
|
|
4626
|
+
const retryHeaders = new Headers(init?.headers);
|
|
4627
|
+
retryHeaders.set("authorization", `Bearer ${freshToken}`);
|
|
4628
|
+
return doFetch({ ...init, headers: retryHeaders });
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
return response;
|
|
4632
|
+
}
|
|
4633
|
+
async requestResponse(path, init) {
|
|
4634
|
+
const response = await this.fetchWithAuth(
|
|
4635
|
+
`${this.apiUrl.replace(/\/$/, "")}${path}`,
|
|
4636
|
+
init
|
|
4637
|
+
);
|
|
4638
|
+
if (!response.ok) {
|
|
4639
|
+
let message = `Request failed with status ${response.status}`;
|
|
4640
|
+
let code = httpStatusToWarmHubCode(response.status);
|
|
4641
|
+
let hint;
|
|
4642
|
+
let retryAfter;
|
|
4643
|
+
try {
|
|
4644
|
+
const body = await response.json();
|
|
4645
|
+
if (typeof body.error?.message === "string") {
|
|
4646
|
+
message = body.error.message;
|
|
4647
|
+
}
|
|
4648
|
+
if (typeof body.error?.code === "string") {
|
|
4649
|
+
code = body.error.code;
|
|
4650
|
+
}
|
|
4651
|
+
if (typeof body.error?.hint === "string") {
|
|
4652
|
+
hint = body.error.hint;
|
|
4653
|
+
}
|
|
4654
|
+
if (typeof body.error?.retryAfter === "number") {
|
|
4655
|
+
retryAfter = body.error.retryAfter;
|
|
4656
|
+
}
|
|
4657
|
+
} catch {
|
|
4658
|
+
}
|
|
4659
|
+
throw new WarmHubError(code, message, response.status, hint, retryAfter);
|
|
4660
|
+
}
|
|
4661
|
+
return response;
|
|
4662
|
+
}
|
|
4663
|
+
async requestJson(path, init) {
|
|
4664
|
+
const response = await this.requestResponse(path, init);
|
|
4665
|
+
return await response.json();
|
|
4666
|
+
}
|
|
4667
|
+
async watchRepoQuery(orgName, repoName, signalInput, query, onUpdate) {
|
|
4668
|
+
const controller = new AbortController();
|
|
4669
|
+
const signal = linkAbortSignal(signalInput, controller);
|
|
4670
|
+
let updates = 0;
|
|
4671
|
+
const closed = (async () => {
|
|
4672
|
+
const emit = async () => {
|
|
4673
|
+
const result = await query();
|
|
4674
|
+
updates += 1;
|
|
4675
|
+
await onUpdate(result);
|
|
4676
|
+
};
|
|
4677
|
+
const ticket = await this.trpc.live.issueTicket.mutate({
|
|
4678
|
+
orgName,
|
|
4679
|
+
repoName
|
|
4680
|
+
});
|
|
4681
|
+
const stream = await this.openSse(
|
|
4682
|
+
ticket.ticket,
|
|
4683
|
+
signal,
|
|
4684
|
+
async (event) => {
|
|
4685
|
+
if (event.type !== "invalidate") {
|
|
4686
|
+
return;
|
|
4687
|
+
}
|
|
4688
|
+
await emit();
|
|
4689
|
+
}
|
|
4690
|
+
);
|
|
4691
|
+
await stream.ready;
|
|
4692
|
+
if (signal.aborted) {
|
|
4693
|
+
return { updates };
|
|
4694
|
+
}
|
|
4695
|
+
await emit();
|
|
4696
|
+
await stream.closed;
|
|
4697
|
+
return { updates };
|
|
4698
|
+
})();
|
|
4699
|
+
return {
|
|
4700
|
+
close() {
|
|
4701
|
+
controller.abort();
|
|
4702
|
+
},
|
|
4703
|
+
closed
|
|
4704
|
+
};
|
|
4705
|
+
}
|
|
4706
|
+
};
|
|
4707
|
+
function httpStatusToWarmHubCode(status) {
|
|
4708
|
+
switch (status) {
|
|
4709
|
+
case 400:
|
|
4710
|
+
return "VALIDATION_ERROR";
|
|
4711
|
+
case 401:
|
|
4712
|
+
return "UNAUTHENTICATED";
|
|
4713
|
+
case 403:
|
|
4714
|
+
return "FORBIDDEN";
|
|
4715
|
+
case 404:
|
|
4716
|
+
return "NOT_FOUND";
|
|
4717
|
+
case 409:
|
|
4718
|
+
return "CONFLICT";
|
|
4719
|
+
case 429:
|
|
4720
|
+
return "RATE_LIMITED";
|
|
4721
|
+
default:
|
|
4722
|
+
return "BACKEND";
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
async function resolveAccessToken(provider) {
|
|
4726
|
+
if (!provider) {
|
|
4727
|
+
return void 0;
|
|
4728
|
+
}
|
|
4729
|
+
if (typeof provider === "string") {
|
|
4730
|
+
return provider;
|
|
4731
|
+
}
|
|
4732
|
+
return provider();
|
|
4733
|
+
}
|
|
4734
|
+
function linkAbortSignal(external, controller) {
|
|
4735
|
+
if (!external) {
|
|
4736
|
+
return controller.signal;
|
|
4737
|
+
}
|
|
4738
|
+
if (external.aborted) {
|
|
4739
|
+
controller.abort();
|
|
4740
|
+
return controller.signal;
|
|
4741
|
+
}
|
|
4742
|
+
external.addEventListener("abort", () => controller.abort(), { once: true });
|
|
4743
|
+
return controller.signal;
|
|
4744
|
+
}
|
|
4745
|
+
function parseSseChunk(chunk) {
|
|
4746
|
+
let event = "message";
|
|
4747
|
+
const dataLines = [];
|
|
4748
|
+
for (const line of chunk.split("\n")) {
|
|
4749
|
+
if (!line || line.startsWith(":")) {
|
|
4750
|
+
continue;
|
|
4751
|
+
}
|
|
4752
|
+
if (line.startsWith("event:")) {
|
|
4753
|
+
event = line.slice("event:".length).trim();
|
|
4754
|
+
continue;
|
|
4755
|
+
}
|
|
4756
|
+
if (line.startsWith("data:")) {
|
|
4757
|
+
dataLines.push(line.slice("data:".length).trim());
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
if (dataLines.length === 0) {
|
|
4761
|
+
return null;
|
|
4762
|
+
}
|
|
4763
|
+
const joined = dataLines.join("\n");
|
|
4764
|
+
return {
|
|
4765
|
+
type: event,
|
|
4766
|
+
data: JSON.parse(joined)
|
|
4767
|
+
};
|
|
4768
|
+
}
|
|
4769
|
+
function isAbortError(error) {
|
|
4770
|
+
return error instanceof Error && error.name === "AbortError";
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4773
|
+
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 };
|
|
4774
|
+
//# sourceMappingURL=chunk-PKA25PT7.js.map
|
|
4775
|
+
//# sourceMappingURL=chunk-PKA25PT7.js.map
|