@shipeasy/devtools-core 1.0.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.
Files changed (55) hide show
  1. package/LICENSE +40 -0
  2. package/README.md +19 -0
  3. package/dist/api.d.mts +143 -0
  4. package/dist/api.d.ts +143 -0
  5. package/dist/api.js +10 -0
  6. package/dist/api.mjs +10 -0
  7. package/dist/auth.d.mts +57 -0
  8. package/dist/auth.d.ts +57 -0
  9. package/dist/auth.js +12 -0
  10. package/dist/auth.mjs +12 -0
  11. package/dist/chunk-2B75JLTX.js +25 -0
  12. package/dist/chunk-5VQLXGNR.mjs +101 -0
  13. package/dist/chunk-6SU5REDK.mjs +2848 -0
  14. package/dist/chunk-7NO55TFC.mjs +25 -0
  15. package/dist/chunk-GUTBZI2O.js +2848 -0
  16. package/dist/chunk-I2KLQ2HA.js +22 -0
  17. package/dist/chunk-KPO3R7ND.js +28 -0
  18. package/dist/chunk-LCRTLHGL.js +101 -0
  19. package/dist/chunk-LZDNBMH6.mjs +1394 -0
  20. package/dist/chunk-MK56AKZR.js +178 -0
  21. package/dist/chunk-RANNOC72.js +72 -0
  22. package/dist/chunk-RH6X24HG.mjs +28 -0
  23. package/dist/chunk-U5DDQYSF.mjs +22 -0
  24. package/dist/chunk-VHYOR3XA.mjs +178 -0
  25. package/dist/chunk-WP2TWHG3.mjs +72 -0
  26. package/dist/chunk-YQHCUZGP.js +1394 -0
  27. package/dist/feedback-state.d.mts +29 -0
  28. package/dist/feedback-state.d.ts +29 -0
  29. package/dist/feedback-state.js +9 -0
  30. package/dist/feedback-state.mjs +9 -0
  31. package/dist/forms.d.mts +95 -0
  32. package/dist/forms.d.ts +95 -0
  33. package/dist/forms.js +21 -0
  34. package/dist/forms.mjs +21 -0
  35. package/dist/gate-flow.d.mts +41 -0
  36. package/dist/gate-flow.d.ts +41 -0
  37. package/dist/gate-flow.js +17 -0
  38. package/dist/gate-flow.mjs +17 -0
  39. package/dist/index.d.mts +10 -0
  40. package/dist/index.d.ts +10 -0
  41. package/dist/index.js +86 -0
  42. package/dist/index.mjs +86 -0
  43. package/dist/public-report.d.mts +56 -0
  44. package/dist/public-report.d.ts +56 -0
  45. package/dist/public-report.js +12 -0
  46. package/dist/public-report.mjs +12 -0
  47. package/dist/self-report.d.mts +25 -0
  48. package/dist/self-report.d.ts +25 -0
  49. package/dist/self-report.js +73 -0
  50. package/dist/self-report.mjs +73 -0
  51. package/dist/types-D0sroUDD.d.mts +278 -0
  52. package/dist/types-D0sroUDD.d.ts +278 -0
  53. package/dist/zod.gen-DTKpgw-T.d.mts +64 -0
  54. package/dist/zod.gen-DTKpgw-T.d.ts +64 -0
  55. package/package.json +58 -0
@@ -0,0 +1,1394 @@
1
+ import {
2
+ DEFAULT_ADMIN_BASE_URL,
3
+ PERMISSIVE_CONFIG_SCHEMA,
4
+ types_exports
5
+ } from "./chunk-RH6X24HG.mjs";
6
+
7
+ // src/generated/core/bodySerializer.gen.ts
8
+ var jsonBodySerializer = {
9
+ bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value)
10
+ };
11
+
12
+ // src/generated/core/params.gen.ts
13
+ var extraPrefixesMap = {
14
+ $body_: "body",
15
+ $headers_: "headers",
16
+ $path_: "path",
17
+ $query_: "query"
18
+ };
19
+ var extraPrefixes = Object.entries(extraPrefixesMap);
20
+
21
+ // src/generated/core/serverSentEvents.gen.ts
22
+ function createSseClient({
23
+ onRequest,
24
+ onSseError,
25
+ onSseEvent,
26
+ responseTransformer,
27
+ responseValidator,
28
+ sseDefaultRetryDelay,
29
+ sseMaxRetryAttempts,
30
+ sseMaxRetryDelay,
31
+ sseSleepFn,
32
+ url,
33
+ ...options
34
+ }) {
35
+ let lastEventId;
36
+ const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
37
+ const createStream = async function* () {
38
+ let retryDelay = sseDefaultRetryDelay ?? 3e3;
39
+ let attempt = 0;
40
+ const signal = options.signal ?? new AbortController().signal;
41
+ while (true) {
42
+ if (signal.aborted) break;
43
+ attempt++;
44
+ const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
45
+ if (lastEventId !== void 0) {
46
+ headers.set("Last-Event-ID", lastEventId);
47
+ }
48
+ try {
49
+ const requestInit = {
50
+ redirect: "follow",
51
+ ...options,
52
+ body: options.serializedBody,
53
+ headers,
54
+ signal
55
+ };
56
+ let request = new Request(url, requestInit);
57
+ if (onRequest) {
58
+ request = await onRequest(url, requestInit);
59
+ }
60
+ const _fetch = options.fetch ?? globalThis.fetch;
61
+ const response = await _fetch(request);
62
+ if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
63
+ if (!response.body) throw new Error("No body in SSE response");
64
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
65
+ let buffer = "";
66
+ const abortHandler = () => {
67
+ try {
68
+ reader.cancel();
69
+ } catch {
70
+ }
71
+ };
72
+ signal.addEventListener("abort", abortHandler);
73
+ try {
74
+ while (true) {
75
+ const { done, value } = await reader.read();
76
+ if (done) break;
77
+ buffer += value;
78
+ buffer = buffer.replace(/\r\n?/g, "\n");
79
+ const chunks = buffer.split("\n\n");
80
+ buffer = chunks.pop() ?? "";
81
+ for (const chunk of chunks) {
82
+ const lines = chunk.split("\n");
83
+ const dataLines = [];
84
+ let eventName;
85
+ for (const line of lines) {
86
+ if (line.startsWith("data:")) {
87
+ dataLines.push(line.replace(/^data:\s*/, ""));
88
+ } else if (line.startsWith("event:")) {
89
+ eventName = line.replace(/^event:\s*/, "");
90
+ } else if (line.startsWith("id:")) {
91
+ lastEventId = line.replace(/^id:\s*/, "");
92
+ } else if (line.startsWith("retry:")) {
93
+ const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
94
+ if (!Number.isNaN(parsed)) {
95
+ retryDelay = parsed;
96
+ }
97
+ }
98
+ }
99
+ let data;
100
+ let parsedJson = false;
101
+ if (dataLines.length) {
102
+ const rawData = dataLines.join("\n");
103
+ try {
104
+ data = JSON.parse(rawData);
105
+ parsedJson = true;
106
+ } catch {
107
+ data = rawData;
108
+ }
109
+ }
110
+ if (parsedJson) {
111
+ if (responseValidator) {
112
+ await responseValidator(data);
113
+ }
114
+ if (responseTransformer) {
115
+ data = await responseTransformer(data);
116
+ }
117
+ }
118
+ onSseEvent?.({
119
+ data,
120
+ event: eventName,
121
+ id: lastEventId,
122
+ retry: retryDelay
123
+ });
124
+ if (dataLines.length) {
125
+ yield data;
126
+ }
127
+ }
128
+ }
129
+ } finally {
130
+ signal.removeEventListener("abort", abortHandler);
131
+ reader.releaseLock();
132
+ }
133
+ break;
134
+ } catch (error) {
135
+ onSseError?.(error);
136
+ if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) {
137
+ break;
138
+ }
139
+ const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 3e4);
140
+ await sleep(backoff);
141
+ }
142
+ }
143
+ };
144
+ const stream = createStream();
145
+ return { stream };
146
+ }
147
+
148
+ // src/generated/core/pathSerializer.gen.ts
149
+ var separatorArrayExplode = (style) => {
150
+ switch (style) {
151
+ case "label":
152
+ return ".";
153
+ case "matrix":
154
+ return ";";
155
+ case "simple":
156
+ return ",";
157
+ default:
158
+ return "&";
159
+ }
160
+ };
161
+ var separatorArrayNoExplode = (style) => {
162
+ switch (style) {
163
+ case "form":
164
+ return ",";
165
+ case "pipeDelimited":
166
+ return "|";
167
+ case "spaceDelimited":
168
+ return "%20";
169
+ default:
170
+ return ",";
171
+ }
172
+ };
173
+ var separatorObjectExplode = (style) => {
174
+ switch (style) {
175
+ case "label":
176
+ return ".";
177
+ case "matrix":
178
+ return ";";
179
+ case "simple":
180
+ return ",";
181
+ default:
182
+ return "&";
183
+ }
184
+ };
185
+ var serializeArrayParam = ({
186
+ allowReserved,
187
+ explode,
188
+ name,
189
+ style,
190
+ value
191
+ }) => {
192
+ if (!explode) {
193
+ const joinedValues2 = (allowReserved ? value : value.map((v) => encodeURIComponent(v))).join(separatorArrayNoExplode(style));
194
+ switch (style) {
195
+ case "label":
196
+ return `.${joinedValues2}`;
197
+ case "matrix":
198
+ return `;${name}=${joinedValues2}`;
199
+ case "simple":
200
+ return joinedValues2;
201
+ default:
202
+ return `${name}=${joinedValues2}`;
203
+ }
204
+ }
205
+ const separator = separatorArrayExplode(style);
206
+ const joinedValues = value.map((v) => {
207
+ if (style === "label" || style === "simple") {
208
+ return allowReserved ? v : encodeURIComponent(v);
209
+ }
210
+ return serializePrimitiveParam({
211
+ allowReserved,
212
+ name,
213
+ value: v
214
+ });
215
+ }).join(separator);
216
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
217
+ };
218
+ var serializePrimitiveParam = ({
219
+ allowReserved,
220
+ name,
221
+ value
222
+ }) => {
223
+ if (value === void 0 || value === null) {
224
+ return "";
225
+ }
226
+ if (typeof value === "object") {
227
+ throw new Error(
228
+ "Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these."
229
+ );
230
+ }
231
+ return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
232
+ };
233
+ var serializeObjectParam = ({
234
+ allowReserved,
235
+ explode,
236
+ name,
237
+ style,
238
+ value,
239
+ valueOnly
240
+ }) => {
241
+ if (value instanceof Date) {
242
+ return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
243
+ }
244
+ if (style !== "deepObject" && !explode) {
245
+ let values = [];
246
+ Object.entries(value).forEach(([key, v]) => {
247
+ values = [...values, key, allowReserved ? v : encodeURIComponent(v)];
248
+ });
249
+ const joinedValues2 = values.join(",");
250
+ switch (style) {
251
+ case "form":
252
+ return `${name}=${joinedValues2}`;
253
+ case "label":
254
+ return `.${joinedValues2}`;
255
+ case "matrix":
256
+ return `;${name}=${joinedValues2}`;
257
+ default:
258
+ return joinedValues2;
259
+ }
260
+ }
261
+ const separator = separatorObjectExplode(style);
262
+ const joinedValues = Object.entries(value).map(
263
+ ([key, v]) => serializePrimitiveParam({
264
+ allowReserved,
265
+ name: style === "deepObject" ? `${name}[${key}]` : key,
266
+ value: v
267
+ })
268
+ ).join(separator);
269
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
270
+ };
271
+
272
+ // src/generated/core/utils.gen.ts
273
+ var PATH_PARAM_RE = /\{[^{}]+\}/g;
274
+ var defaultPathSerializer = ({ path, url: _url }) => {
275
+ let url = _url;
276
+ const matches = _url.match(PATH_PARAM_RE);
277
+ if (matches) {
278
+ for (const match of matches) {
279
+ let explode = false;
280
+ let name = match.substring(1, match.length - 1);
281
+ let style = "simple";
282
+ if (name.endsWith("*")) {
283
+ explode = true;
284
+ name = name.substring(0, name.length - 1);
285
+ }
286
+ if (name.startsWith(".")) {
287
+ name = name.substring(1);
288
+ style = "label";
289
+ } else if (name.startsWith(";")) {
290
+ name = name.substring(1);
291
+ style = "matrix";
292
+ }
293
+ const value = path[name];
294
+ if (value === void 0 || value === null) {
295
+ continue;
296
+ }
297
+ if (Array.isArray(value)) {
298
+ url = url.replace(match, serializeArrayParam({ explode, name, style, value }));
299
+ continue;
300
+ }
301
+ if (typeof value === "object") {
302
+ url = url.replace(
303
+ match,
304
+ serializeObjectParam({
305
+ explode,
306
+ name,
307
+ style,
308
+ value,
309
+ valueOnly: true
310
+ })
311
+ );
312
+ continue;
313
+ }
314
+ if (style === "matrix") {
315
+ url = url.replace(
316
+ match,
317
+ `;${serializePrimitiveParam({
318
+ name,
319
+ value
320
+ })}`
321
+ );
322
+ continue;
323
+ }
324
+ const replaceValue = encodeURIComponent(
325
+ style === "label" ? `.${value}` : value
326
+ );
327
+ url = url.replace(match, replaceValue);
328
+ }
329
+ }
330
+ return url;
331
+ };
332
+ var getUrl = ({
333
+ baseUrl,
334
+ path,
335
+ query,
336
+ querySerializer,
337
+ url: _url
338
+ }) => {
339
+ const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
340
+ let url = (baseUrl ?? "") + pathUrl;
341
+ if (path) {
342
+ url = defaultPathSerializer({ path, url });
343
+ }
344
+ let search = query ? querySerializer(query) : "";
345
+ if (search.startsWith("?")) {
346
+ search = search.substring(1);
347
+ }
348
+ if (search) {
349
+ url += `?${search}`;
350
+ }
351
+ return url;
352
+ };
353
+ function getValidRequestBody(options) {
354
+ const hasBody = options.body !== void 0;
355
+ const isSerializedBody = hasBody && options.bodySerializer;
356
+ if (isSerializedBody) {
357
+ if ("serializedBody" in options) {
358
+ const hasSerializedBody = options.serializedBody !== void 0 && options.serializedBody !== "";
359
+ return hasSerializedBody ? options.serializedBody : null;
360
+ }
361
+ return options.body !== "" ? options.body : null;
362
+ }
363
+ if (hasBody) {
364
+ return options.body;
365
+ }
366
+ return void 0;
367
+ }
368
+
369
+ // src/generated/core/auth.gen.ts
370
+ var getAuthToken = async (auth, callback) => {
371
+ const token = typeof callback === "function" ? await callback(auth) : callback;
372
+ if (!token) {
373
+ return;
374
+ }
375
+ if (auth.scheme === "bearer") {
376
+ return `Bearer ${token}`;
377
+ }
378
+ if (auth.scheme === "basic") {
379
+ return `Basic ${btoa(token)}`;
380
+ }
381
+ return token;
382
+ };
383
+
384
+ // src/generated/client/utils.gen.ts
385
+ var createQuerySerializer = ({
386
+ parameters = {},
387
+ ...args
388
+ } = {}) => {
389
+ const querySerializer = (queryParams) => {
390
+ const search = [];
391
+ if (queryParams && typeof queryParams === "object") {
392
+ for (const name in queryParams) {
393
+ const value = queryParams[name];
394
+ if (value === void 0 || value === null) {
395
+ continue;
396
+ }
397
+ const options = parameters[name] || args;
398
+ if (Array.isArray(value)) {
399
+ const serializedArray = serializeArrayParam({
400
+ allowReserved: options.allowReserved,
401
+ explode: true,
402
+ name,
403
+ style: "form",
404
+ value,
405
+ ...options.array
406
+ });
407
+ if (serializedArray) search.push(serializedArray);
408
+ } else if (typeof value === "object") {
409
+ const serializedObject = serializeObjectParam({
410
+ allowReserved: options.allowReserved,
411
+ explode: true,
412
+ name,
413
+ style: "deepObject",
414
+ value,
415
+ ...options.object
416
+ });
417
+ if (serializedObject) search.push(serializedObject);
418
+ } else {
419
+ const serializedPrimitive = serializePrimitiveParam({
420
+ allowReserved: options.allowReserved,
421
+ name,
422
+ value
423
+ });
424
+ if (serializedPrimitive) search.push(serializedPrimitive);
425
+ }
426
+ }
427
+ }
428
+ return search.join("&");
429
+ };
430
+ return querySerializer;
431
+ };
432
+ var getParseAs = (contentType) => {
433
+ if (!contentType) {
434
+ return "stream";
435
+ }
436
+ const cleanContent = contentType.split(";")[0]?.trim();
437
+ if (!cleanContent) {
438
+ return;
439
+ }
440
+ if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) {
441
+ return "json";
442
+ }
443
+ if (cleanContent === "multipart/form-data") {
444
+ return "formData";
445
+ }
446
+ if (["application/", "audio/", "image/", "video/"].some((type) => cleanContent.startsWith(type))) {
447
+ return "blob";
448
+ }
449
+ if (cleanContent.startsWith("text/")) {
450
+ return "text";
451
+ }
452
+ return;
453
+ };
454
+ var checkForExistence = (options, name) => {
455
+ if (!name) {
456
+ return false;
457
+ }
458
+ if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) {
459
+ return true;
460
+ }
461
+ return false;
462
+ };
463
+ async function setAuthParams(options) {
464
+ for (const auth of options.security ?? []) {
465
+ if (checkForExistence(options, auth.name)) {
466
+ continue;
467
+ }
468
+ const token = await getAuthToken(auth, options.auth);
469
+ if (!token) {
470
+ continue;
471
+ }
472
+ const name = auth.name ?? "Authorization";
473
+ switch (auth.in) {
474
+ case "query":
475
+ if (!options.query) {
476
+ options.query = {};
477
+ }
478
+ options.query[name] = token;
479
+ break;
480
+ case "cookie":
481
+ options.headers.append("Cookie", `${name}=${token}`);
482
+ break;
483
+ case "header":
484
+ default:
485
+ options.headers.set(name, token);
486
+ break;
487
+ }
488
+ }
489
+ }
490
+ var buildUrl = (options) => getUrl({
491
+ baseUrl: options.baseUrl,
492
+ path: options.path,
493
+ query: options.query,
494
+ querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
495
+ url: options.url
496
+ });
497
+ var mergeConfigs = (a, b) => {
498
+ const config = { ...a, ...b };
499
+ if (config.baseUrl?.endsWith("/")) {
500
+ config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
501
+ }
502
+ config.headers = mergeHeaders(a.headers, b.headers);
503
+ return config;
504
+ };
505
+ var headersEntries = (headers) => {
506
+ const entries = [];
507
+ headers.forEach((value, key) => {
508
+ entries.push([key, value]);
509
+ });
510
+ return entries;
511
+ };
512
+ var mergeHeaders = (...headers) => {
513
+ const mergedHeaders = new Headers();
514
+ for (const header of headers) {
515
+ if (!header) {
516
+ continue;
517
+ }
518
+ const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
519
+ for (const [key, value] of iterator) {
520
+ if (value === null) {
521
+ mergedHeaders.delete(key);
522
+ } else if (Array.isArray(value)) {
523
+ for (const v of value) {
524
+ mergedHeaders.append(key, v);
525
+ }
526
+ } else if (value !== void 0) {
527
+ mergedHeaders.set(
528
+ key,
529
+ typeof value === "object" ? JSON.stringify(value) : value
530
+ );
531
+ }
532
+ }
533
+ }
534
+ return mergedHeaders;
535
+ };
536
+ var Interceptors = class {
537
+ fns = [];
538
+ clear() {
539
+ this.fns = [];
540
+ }
541
+ eject(id) {
542
+ const index = this.getInterceptorIndex(id);
543
+ if (this.fns[index]) {
544
+ this.fns[index] = null;
545
+ }
546
+ }
547
+ exists(id) {
548
+ const index = this.getInterceptorIndex(id);
549
+ return Boolean(this.fns[index]);
550
+ }
551
+ getInterceptorIndex(id) {
552
+ if (typeof id === "number") {
553
+ return this.fns[id] ? id : -1;
554
+ }
555
+ return this.fns.indexOf(id);
556
+ }
557
+ update(id, fn) {
558
+ const index = this.getInterceptorIndex(id);
559
+ if (this.fns[index]) {
560
+ this.fns[index] = fn;
561
+ return id;
562
+ }
563
+ return false;
564
+ }
565
+ use(fn) {
566
+ this.fns.push(fn);
567
+ return this.fns.length - 1;
568
+ }
569
+ };
570
+ var createInterceptors = () => ({
571
+ error: new Interceptors(),
572
+ request: new Interceptors(),
573
+ response: new Interceptors()
574
+ });
575
+ var defaultQuerySerializer = createQuerySerializer({
576
+ allowReserved: false,
577
+ array: {
578
+ explode: true,
579
+ style: "form"
580
+ },
581
+ object: {
582
+ explode: true,
583
+ style: "deepObject"
584
+ }
585
+ });
586
+ var defaultHeaders = {
587
+ "Content-Type": "application/json"
588
+ };
589
+ var createConfig = (override = {}) => ({
590
+ ...jsonBodySerializer,
591
+ headers: defaultHeaders,
592
+ parseAs: "auto",
593
+ querySerializer: defaultQuerySerializer,
594
+ ...override
595
+ });
596
+
597
+ // src/generated/client/client.gen.ts
598
+ var createClient = (config = {}) => {
599
+ let _config = mergeConfigs(createConfig(), config);
600
+ const getConfig2 = () => ({ ..._config });
601
+ const setConfig = (config2) => {
602
+ _config = mergeConfigs(_config, config2);
603
+ return getConfig2();
604
+ };
605
+ const interceptors = createInterceptors();
606
+ const beforeRequest = async (options) => {
607
+ const opts = {
608
+ ..._config,
609
+ ...options,
610
+ fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
611
+ headers: mergeHeaders(_config.headers, options.headers),
612
+ serializedBody: void 0
613
+ };
614
+ if (opts.security) {
615
+ await setAuthParams(opts);
616
+ }
617
+ if (opts.requestValidator) {
618
+ await opts.requestValidator(opts);
619
+ }
620
+ if (opts.body !== void 0 && opts.bodySerializer) {
621
+ opts.serializedBody = opts.bodySerializer(opts.body);
622
+ }
623
+ if (opts.body === void 0 || opts.serializedBody === "") {
624
+ opts.headers.delete("Content-Type");
625
+ }
626
+ const resolvedOpts = opts;
627
+ const url = buildUrl(resolvedOpts);
628
+ return { opts: resolvedOpts, url };
629
+ };
630
+ const request = async (options) => {
631
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
632
+ const responseStyle = options.responseStyle ?? _config.responseStyle;
633
+ let request2;
634
+ let response;
635
+ try {
636
+ const { opts, url } = await beforeRequest(options);
637
+ const requestInit = {
638
+ redirect: "follow",
639
+ ...opts,
640
+ body: getValidRequestBody(opts)
641
+ };
642
+ request2 = new Request(url, requestInit);
643
+ for (const fn of interceptors.request.fns) {
644
+ if (fn) {
645
+ request2 = await fn(request2, opts);
646
+ }
647
+ }
648
+ const _fetch = opts.fetch;
649
+ response = await _fetch(request2);
650
+ for (const fn of interceptors.response.fns) {
651
+ if (fn) {
652
+ response = await fn(response, request2, opts);
653
+ }
654
+ }
655
+ const result = {
656
+ request: request2,
657
+ response
658
+ };
659
+ if (response.ok) {
660
+ const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
661
+ if (response.status === 204 || response.headers.get("Content-Length") === "0") {
662
+ let emptyData;
663
+ switch (parseAs) {
664
+ case "arrayBuffer":
665
+ case "blob":
666
+ case "text":
667
+ emptyData = await response[parseAs]();
668
+ break;
669
+ case "formData":
670
+ emptyData = new FormData();
671
+ break;
672
+ case "stream":
673
+ emptyData = response.body;
674
+ break;
675
+ case "json":
676
+ default:
677
+ emptyData = {};
678
+ break;
679
+ }
680
+ return opts.responseStyle === "data" ? emptyData : {
681
+ data: emptyData,
682
+ ...result
683
+ };
684
+ }
685
+ let data;
686
+ switch (parseAs) {
687
+ case "arrayBuffer":
688
+ case "blob":
689
+ case "formData":
690
+ case "text":
691
+ data = await response[parseAs]();
692
+ break;
693
+ case "json": {
694
+ const text = await response.text();
695
+ data = text ? JSON.parse(text) : {};
696
+ break;
697
+ }
698
+ case "stream":
699
+ return opts.responseStyle === "data" ? response.body : {
700
+ data: response.body,
701
+ ...result
702
+ };
703
+ }
704
+ if (parseAs === "json") {
705
+ if (opts.responseValidator) {
706
+ await opts.responseValidator(data);
707
+ }
708
+ if (opts.responseTransformer) {
709
+ data = await opts.responseTransformer(data);
710
+ }
711
+ }
712
+ return opts.responseStyle === "data" ? data : {
713
+ data,
714
+ ...result
715
+ };
716
+ }
717
+ const textError = await response.text();
718
+ let jsonError;
719
+ try {
720
+ jsonError = JSON.parse(textError);
721
+ } catch {
722
+ }
723
+ throw jsonError ?? textError;
724
+ } catch (error) {
725
+ let finalError = error;
726
+ for (const fn of interceptors.error.fns) {
727
+ if (fn) {
728
+ finalError = await fn(finalError, response, request2, options);
729
+ }
730
+ }
731
+ finalError = finalError || {};
732
+ if (throwOnError) {
733
+ throw finalError;
734
+ }
735
+ return responseStyle === "data" ? void 0 : {
736
+ error: finalError,
737
+ request: request2,
738
+ response
739
+ };
740
+ }
741
+ };
742
+ const makeMethodFn = (method) => (options) => request({ ...options, method });
743
+ const makeSseFn = (method) => async (options) => {
744
+ const { opts, url } = await beforeRequest(options);
745
+ return createSseClient({
746
+ ...opts,
747
+ body: opts.body,
748
+ method,
749
+ onRequest: async (url2, init) => {
750
+ let request2 = new Request(url2, init);
751
+ for (const fn of interceptors.request.fns) {
752
+ if (fn) {
753
+ request2 = await fn(request2, opts);
754
+ }
755
+ }
756
+ return request2;
757
+ },
758
+ serializedBody: getValidRequestBody(opts),
759
+ url
760
+ });
761
+ };
762
+ const _buildUrl = (options) => buildUrl({ ..._config, ...options });
763
+ return {
764
+ buildUrl: _buildUrl,
765
+ connect: makeMethodFn("CONNECT"),
766
+ delete: makeMethodFn("DELETE"),
767
+ get: makeMethodFn("GET"),
768
+ getConfig: getConfig2,
769
+ head: makeMethodFn("HEAD"),
770
+ interceptors,
771
+ options: makeMethodFn("OPTIONS"),
772
+ patch: makeMethodFn("PATCH"),
773
+ post: makeMethodFn("POST"),
774
+ put: makeMethodFn("PUT"),
775
+ request,
776
+ setConfig,
777
+ sse: {
778
+ connect: makeSseFn("CONNECT"),
779
+ delete: makeSseFn("DELETE"),
780
+ get: makeSseFn("GET"),
781
+ head: makeSseFn("HEAD"),
782
+ options: makeSseFn("OPTIONS"),
783
+ patch: makeSseFn("PATCH"),
784
+ post: makeSseFn("POST"),
785
+ put: makeSseFn("PUT"),
786
+ trace: makeSseFn("TRACE")
787
+ },
788
+ trace: makeMethodFn("TRACE")
789
+ };
790
+ };
791
+
792
+ // src/generated/client.gen.ts
793
+ var client = createClient(createConfig({ baseUrl: "https://shipeasy.ai" }));
794
+
795
+ // src/generated/sdk.gen.ts
796
+ var listGates = (options) => (options?.client ?? client).get({
797
+ security: [{ scheme: "bearer", type: "http" }],
798
+ url: "/api/admin/gates",
799
+ ...options
800
+ });
801
+ var listExperiments = (options) => (options?.client ?? client).get({
802
+ security: [{ scheme: "bearer", type: "http" }],
803
+ url: "/api/admin/experiments",
804
+ ...options
805
+ });
806
+ var listConfigs = (options) => (options?.client ?? client).get({
807
+ security: [{ scheme: "bearer", type: "http" }],
808
+ url: "/api/admin/configs",
809
+ ...options
810
+ });
811
+ var getConfig = (options) => (options.client ?? client).get({
812
+ security: [{ scheme: "bearer", type: "http" }],
813
+ url: "/api/admin/configs/{id}",
814
+ ...options
815
+ });
816
+ var listUniverses = (options) => (options?.client ?? client).get({
817
+ security: [{ scheme: "bearer", type: "http" }],
818
+ url: "/api/admin/universes",
819
+ ...options
820
+ });
821
+ var listOpsItems = (options) => (options?.client ?? client).get({
822
+ security: [{ scheme: "bearer", type: "http" }],
823
+ url: "/api/admin/ops",
824
+ ...options
825
+ });
826
+ var createOpsItem = (options) => (options.client ?? client).post({
827
+ security: [{ scheme: "bearer", type: "http" }],
828
+ url: "/api/admin/ops",
829
+ ...options,
830
+ headers: {
831
+ "Content-Type": "application/json",
832
+ ...options.headers
833
+ }
834
+ });
835
+ var getOpsItem = (options) => (options.client ?? client).get({
836
+ security: [{ scheme: "bearer", type: "http" }],
837
+ url: "/api/admin/ops/{handle}",
838
+ ...options
839
+ });
840
+ var updateOpsItem = (options) => (options.client ?? client).patch({
841
+ security: [{ scheme: "bearer", type: "http" }],
842
+ url: "/api/admin/ops/{handle}",
843
+ ...options,
844
+ headers: {
845
+ "Content-Type": "application/json",
846
+ ...options.headers
847
+ }
848
+ });
849
+ var listI18nProfiles = (options) => (options?.client ?? client).get({
850
+ security: [{ scheme: "bearer", type: "http" }],
851
+ url: "/api/admin/i18n/profiles",
852
+ ...options
853
+ });
854
+ var listI18nKeys = (options) => (options?.client ?? client).get({
855
+ security: [{ scheme: "bearer", type: "http" }],
856
+ url: "/api/admin/i18n/keys",
857
+ ...options
858
+ });
859
+ var updateI18nKey = (options) => (options.client ?? client).put({
860
+ security: [{ scheme: "bearer", type: "http" }],
861
+ url: "/api/admin/i18n/keys/{id}",
862
+ ...options,
863
+ headers: {
864
+ "Content-Type": "application/json",
865
+ ...options.headers
866
+ }
867
+ });
868
+ var listI18nDrafts = (options) => (options?.client ?? client).get({
869
+ security: [{ scheme: "bearer", type: "http" }],
870
+ url: "/api/admin/i18n/drafts",
871
+ ...options
872
+ });
873
+ var createI18nDraft = (options) => (options.client ?? client).post({
874
+ security: [{ scheme: "bearer", type: "http" }],
875
+ url: "/api/admin/i18n/drafts",
876
+ ...options,
877
+ headers: {
878
+ "Content-Type": "application/json",
879
+ ...options.headers
880
+ }
881
+ });
882
+
883
+ // src/api.ts
884
+ var AuthError = class extends Error {
885
+ status = 401;
886
+ constructor(message) {
887
+ super(message);
888
+ this.name = "AuthError";
889
+ }
890
+ };
891
+ var DevtoolsClient = class {
892
+ // Per-instance memo cache keyed by method/args. List endpoints rarely change
893
+ // between tab switches, so caching the promise lets users flip between
894
+ // panels without refetching. Mutations scrub their own keys.
895
+ cache = /* @__PURE__ */ new Map();
896
+ adminBaseUrl;
897
+ /** Raw admin bearer key for this session. Public so overlays can compare it
898
+ * against their stored session and rebuild the client on session swap. */
899
+ token;
900
+ projectId;
901
+ onUnauthed;
902
+ fetchImpl;
903
+ /** Session-configured generated client — every spec-covered call goes
904
+ * through it (bearer auth, base URL, injectable fetch). */
905
+ api;
906
+ constructor(opts) {
907
+ this.adminBaseUrl = (opts.adminBaseUrl ?? DEFAULT_ADMIN_BASE_URL).replace(/\/$/, "");
908
+ this.token = opts.token;
909
+ this.projectId = opts.projectId;
910
+ this.onUnauthed = opts.onUnauthed;
911
+ this.fetchImpl = opts.fetch ?? ((...args) => fetch(...args));
912
+ this.api = createClient(
913
+ createConfig({
914
+ baseUrl: this.adminBaseUrl,
915
+ // Generated ops declare `security: [{ scheme: "bearer" }]`; the client
916
+ // resolves the token through this callback per request.
917
+ auth: () => this.token,
918
+ // Never send cookies: the client authenticates with its minted admin
919
+ // SDK key, NOT the dashboard session. Dogfooded same-origin, a session
920
+ // cookie would otherwise ride along and `authenticateAdmin` prefers
921
+ // the session, resolving the WRONG project → 403. RN ignores this.
922
+ credentials: "omit",
923
+ fetch: opts.fetch,
924
+ throwOnError: false
925
+ })
926
+ );
927
+ }
928
+ /** Admin origin this client talks to. Overlays build dashboard deep links
929
+ * ("Open in dashboard ↗") off it. */
930
+ get adminUrl() {
931
+ return this.adminBaseUrl;
932
+ }
933
+ /** Run `fn` once per `key` and cache the resulting promise. Rejections are
934
+ * evicted so the next call retries instead of replaying the failure. */
935
+ memo(key, fn) {
936
+ const hit = this.cache.get(key);
937
+ if (hit) return hit;
938
+ const p = fn();
939
+ this.cache.set(key, p);
940
+ p.catch(() => {
941
+ if (this.cache.get(key) === p) this.cache.delete(key);
942
+ });
943
+ return p;
944
+ }
945
+ /** Drop all cached responses (pull-to-refresh). */
946
+ invalidate() {
947
+ this.cache.clear();
948
+ }
949
+ /** Build + throw the error for a failed request. 401 produces an `AuthError`
950
+ * and fires `onUnauthed` so the host can drop the stale session instead of
951
+ * leaving the user staring at a failed-load message. */
952
+ fail(path, error, response) {
953
+ const status = response?.status ?? 0;
954
+ let detail = "";
955
+ if (error && typeof error === "object") {
956
+ const e = error;
957
+ detail = e.detail ?? e.error ?? "";
958
+ } else if (typeof error === "string") {
959
+ detail = error.slice(0, 200);
960
+ }
961
+ const message = `${path} \u2192 HTTP ${status}${detail ? ` \u2014 ${detail}` : ""}`;
962
+ if (status === 401) {
963
+ try {
964
+ this.onUnauthed?.();
965
+ } catch {
966
+ }
967
+ throw new AuthError(message);
968
+ }
969
+ throw new Error(message);
970
+ }
971
+ /** Resolve a generated op result to its body, or throw the normalized
972
+ * error. Write ops with empty bodies pass on any 2xx. */
973
+ unwrap(path, r) {
974
+ if (r.error !== void 0 || r.data === void 0 && !(r.response?.ok ?? false)) {
975
+ this.fail(path, r.error, r.response);
976
+ }
977
+ return r.data;
978
+ }
979
+ /** Some list endpoints return a bare array, others a `{ data }` envelope
980
+ * (the spec is the contract, but be tolerant like the overlays always
981
+ * were). */
982
+ asList(body) {
983
+ return Array.isArray(body) ? body : body.data ?? [];
984
+ }
985
+ /**
986
+ * Drain a paginated `{ data, next_cursor }` list op by walking cursors.
987
+ * Devtools shows the entire list at once; without this, large projects would
988
+ * silently truncate at the first page (limit=100 default).
989
+ */
990
+ async drain(path, page, limit = 500) {
991
+ const out = [];
992
+ let cursor;
993
+ do {
994
+ const body = this.unwrap(path, await page({ limit, ...cursor ? { cursor } : {} }));
995
+ if (Array.isArray(body)) return body;
996
+ out.push(...body.data);
997
+ cursor = body.next_cursor ?? void 0;
998
+ } while (cursor);
999
+ return out;
1000
+ }
1001
+ // ── SPEC-GAP raw transport ──────────────────────────────────────────────
1002
+ // A few admin routes the overlays use aren't in the OpenAPI contract yet
1003
+ // (spec-alignment candidates). Until they land there, these two helpers
1004
+ // carry them with the same auth/error semantics as the generated client.
1005
+ rawInit(extra) {
1006
+ return {
1007
+ credentials: "omit",
1008
+ // see the createConfig note above
1009
+ headers: { Authorization: `Bearer ${this.token}` },
1010
+ ...extra
1011
+ };
1012
+ }
1013
+ async rawGet(path) {
1014
+ const res = await this.fetchImpl(`${this.adminBaseUrl}${path}`, this.rawInit());
1015
+ if (!res.ok) this.fail(path, await this.readErrorDetail(res), res);
1016
+ const body = await res.json();
1017
+ return Array.isArray(body) ? body : body.data ?? body;
1018
+ }
1019
+ async rawSend(method, path, body) {
1020
+ const res = await this.fetchImpl(
1021
+ `${this.adminBaseUrl}${path}`,
1022
+ this.rawInit({
1023
+ method,
1024
+ headers: {
1025
+ Authorization: `Bearer ${this.token}`,
1026
+ "Content-Type": "application/json"
1027
+ },
1028
+ body: JSON.stringify(body)
1029
+ })
1030
+ );
1031
+ if (!res.ok) this.fail(path, await this.readErrorDetail(res), res);
1032
+ return await res.json();
1033
+ }
1034
+ /** Read the response error body for diagnostics. Best-effort; never throws. */
1035
+ async readErrorDetail(res) {
1036
+ try {
1037
+ return await res.json();
1038
+ } catch {
1039
+ try {
1040
+ return (await res.text()).slice(0, 200);
1041
+ } catch {
1042
+ return "";
1043
+ }
1044
+ }
1045
+ }
1046
+ // ── project ─────────────────────────────────────────────────────────────
1047
+ project() {
1048
+ return this.memo("project", async () => {
1049
+ const raw = await this.rawGet(`/api/admin/projects/${encodeURIComponent(this.projectId)}`);
1050
+ const b = (v) => v === void 0 || v === true || v === 1;
1051
+ return {
1052
+ id: raw.id,
1053
+ name: raw.name,
1054
+ domain: raw.domain,
1055
+ modules: {
1056
+ translations: b(raw.moduleTranslations),
1057
+ configs: b(raw.moduleConfigs),
1058
+ gates: b(raw.moduleGates),
1059
+ experiments: b(raw.moduleExperiments),
1060
+ feedback: b(raw.moduleFeedback),
1061
+ user: b(raw.moduleUser),
1062
+ events: b(raw.moduleEvents)
1063
+ }
1064
+ };
1065
+ });
1066
+ }
1067
+ // ── release surface ─────────────────────────────────────────────────────
1068
+ gates() {
1069
+ return this.memo(
1070
+ "gates",
1071
+ () => (
1072
+ // The wire returns more fields than the generated list item declares
1073
+ // (killswitch, rolloutPct — spec-alignment candidates); the projection
1074
+ // widens to the overlay contract.
1075
+ this.drain(
1076
+ "/api/admin/gates",
1077
+ (query) => listGates({ client: this.api, query })
1078
+ )
1079
+ )
1080
+ );
1081
+ }
1082
+ configs() {
1083
+ return this.memo("configs", async () => {
1084
+ const list = await this.drain("/api/admin/configs", (query) => listConfigs({ client: this.api, query }));
1085
+ const env = "prod";
1086
+ return Promise.all(
1087
+ list.map(async (c) => {
1088
+ try {
1089
+ const detail = this.unwrap(
1090
+ `/api/admin/configs/${c.id}`,
1091
+ await getConfig({ client: this.api, path: { id: c.id } })
1092
+ );
1093
+ const valueJson = detail.valueJson !== void 0 ? detail.valueJson : detail.values?.[env] ?? {};
1094
+ return {
1095
+ id: c.id,
1096
+ name: c.name,
1097
+ updatedAt: c.updatedAt,
1098
+ valueJson,
1099
+ schema: detail.schema ?? c.schema ?? PERMISSIVE_CONFIG_SCHEMA
1100
+ };
1101
+ } catch {
1102
+ return {
1103
+ id: c.id,
1104
+ name: c.name,
1105
+ updatedAt: c.updatedAt,
1106
+ valueJson: {},
1107
+ schema: c.schema ?? PERMISSIVE_CONFIG_SCHEMA
1108
+ };
1109
+ }
1110
+ })
1111
+ );
1112
+ });
1113
+ }
1114
+ /** Experiments list. The endpoint returns the non-archived set (running /
1115
+ * draft / stopped) by default; pass `{ archived: true }` for the archive tab
1116
+ * (a separate memo bucket so the overlay's Archived section loads on demand). */
1117
+ experiments(opts) {
1118
+ const archived = opts?.archived === true;
1119
+ return this.memo(
1120
+ archived ? "experiments:archived" : "experiments",
1121
+ () => this.drain(
1122
+ "/api/admin/experiments",
1123
+ (query) => listExperiments({
1124
+ client: this.api,
1125
+ query: archived ? { ...query, status: "archived" } : query
1126
+ })
1127
+ )
1128
+ );
1129
+ }
1130
+ universes() {
1131
+ return this.memo(
1132
+ "universes",
1133
+ () => this.drain(
1134
+ "/api/admin/universes",
1135
+ (query) => listUniverses({ client: this.api, query })
1136
+ )
1137
+ );
1138
+ }
1139
+ // ── i18n ────────────────────────────────────────────────────────────────
1140
+ profiles() {
1141
+ return this.memo(
1142
+ "profiles",
1143
+ async () => this.asList(
1144
+ this.unwrap("/api/admin/i18n/profiles", await listI18nProfiles({ client: this.api }))
1145
+ )
1146
+ );
1147
+ }
1148
+ drafts() {
1149
+ return this.memo(
1150
+ "drafts",
1151
+ async () => this.asList(
1152
+ this.unwrap("/api/admin/i18n/drafts", await listI18nDrafts({ client: this.api }))
1153
+ )
1154
+ );
1155
+ }
1156
+ keys(profileId) {
1157
+ return this.memo(`keys:${profileId ?? ""}`, async () => {
1158
+ const PAGE = 500;
1159
+ const fetchPage = async (offset) => {
1160
+ const body = this.unwrap(
1161
+ "/api/admin/i18n/keys",
1162
+ await listI18nKeys({
1163
+ client: this.api,
1164
+ query: {
1165
+ ...profileId ? { profile_id: profileId } : {},
1166
+ limit: PAGE,
1167
+ offset
1168
+ }
1169
+ })
1170
+ );
1171
+ if (Array.isArray(body)) return { keys: body, total: body.length };
1172
+ const keys = body.keys ?? [];
1173
+ const total = body.total ?? keys.length;
1174
+ return { keys, total };
1175
+ };
1176
+ const first = await fetchPage(0);
1177
+ const all = first.keys.slice();
1178
+ while (all.length < first.total && first.keys.length > 0) {
1179
+ const next = await fetchPage(all.length);
1180
+ if (next.keys.length === 0) break;
1181
+ all.push(...next.keys);
1182
+ }
1183
+ return all;
1184
+ });
1185
+ }
1186
+ async createDraft(input) {
1187
+ const r = this.unwrap(
1188
+ "/api/admin/i18n/drafts",
1189
+ await createI18nDraft({
1190
+ client: this.api,
1191
+ body: { profile_id: input.profileId, name: input.name }
1192
+ })
1193
+ );
1194
+ this.cache.delete("drafts");
1195
+ return r;
1196
+ }
1197
+ async upsertDraftKey(draftId, key, value) {
1198
+ await this.rawSend("POST", `/api/admin/i18n/drafts/${encodeURIComponent(draftId)}/keys`, {
1199
+ key,
1200
+ value
1201
+ });
1202
+ this.invalidateKeysCache();
1203
+ }
1204
+ async updateKeyById(id, value) {
1205
+ this.unwrap(
1206
+ `/api/admin/i18n/keys/${id}`,
1207
+ await updateI18nKey({ client: this.api, path: { id }, body: { value } })
1208
+ );
1209
+ this.invalidateKeysCache();
1210
+ }
1211
+ async upsertKeys(profileId, keys, chunk = "default") {
1212
+ await this.rawSend("PUT", `/api/admin/i18n/keys`, { profile_id: profileId, chunk, keys });
1213
+ this.invalidateKeysCache();
1214
+ }
1215
+ /** Drop every cached `keys(profileId?)` response. We don't track which
1216
+ * profile a single key write affects, so be safe and clear them all. */
1217
+ invalidateKeysCache() {
1218
+ for (const k of Array.from(this.cache.keys())) {
1219
+ if (k.startsWith("keys:")) this.cache.delete(k);
1220
+ }
1221
+ }
1222
+ // ── feedback (bugs + feature requests) ──────────────────────────────────
1223
+ bugs() {
1224
+ return this.memo(
1225
+ "bugs",
1226
+ async () => this.asList(
1227
+ this.unwrap(
1228
+ "/api/admin/ops?type=bug",
1229
+ await listOpsItems({ client: this.api, query: { type: "bug" } })
1230
+ )
1231
+ )
1232
+ );
1233
+ }
1234
+ bug(id) {
1235
+ return this.memo(
1236
+ `bug:${id}`,
1237
+ async () => this.unwrap(
1238
+ `/api/admin/ops/${id}`,
1239
+ await getOpsItem({ client: this.api, path: { handle: id } })
1240
+ )
1241
+ );
1242
+ }
1243
+ /** Auto-filed error tickets. Same list endpoint, `type=error`. */
1244
+ errors() {
1245
+ return this.memo(
1246
+ "errors",
1247
+ async () => this.asList(
1248
+ this.unwrap(
1249
+ "/api/admin/ops?type=error",
1250
+ await listOpsItems({ client: this.api, query: { type: "error" } })
1251
+ )
1252
+ )
1253
+ );
1254
+ }
1255
+ /** Auto-filed alert tickets. Same list endpoint, `type=alert`. */
1256
+ alerts() {
1257
+ return this.memo(
1258
+ "alerts",
1259
+ async () => this.asList(
1260
+ this.unwrap(
1261
+ "/api/admin/ops?type=alert",
1262
+ await listOpsItems({ client: this.api, query: { type: "alert" } })
1263
+ )
1264
+ )
1265
+ );
1266
+ }
1267
+ /** Generic ops-item detail — works for any type (bug/feature/error/alert). */
1268
+ opsItem(id) {
1269
+ return this.memo(
1270
+ `ops:${id}`,
1271
+ async () => this.unwrap(
1272
+ `/api/admin/ops/${id}`,
1273
+ await getOpsItem({ client: this.api, path: { handle: id } })
1274
+ )
1275
+ );
1276
+ }
1277
+ /** Triage-only update (status / priority) that works for ANY item type —
1278
+ * the only editable shape for auto-filed error/alert tickets. Scrubs every
1279
+ * list bucket + the item's detail so the panels refetch. */
1280
+ async updateOps(id, patch) {
1281
+ this.unwrap(
1282
+ `/api/admin/ops/${id}`,
1283
+ await updateOpsItem({
1284
+ client: this.api,
1285
+ path: { handle: id },
1286
+ body: patch
1287
+ })
1288
+ );
1289
+ for (const k of ["bugs", "featureRequests", "errors", "alerts"]) this.cache.delete(k);
1290
+ this.cache.delete(`ops:${id}`);
1291
+ this.cache.delete(`bug:${id}`);
1292
+ this.cache.delete(`featureRequest:${id}`);
1293
+ }
1294
+ async createBug(input) {
1295
+ const r = this.unwrap(
1296
+ "/api/admin/ops",
1297
+ await createOpsItem({
1298
+ client: this.api,
1299
+ body: { ...input, type: "bug" }
1300
+ })
1301
+ );
1302
+ this.cache.delete("bugs");
1303
+ return r;
1304
+ }
1305
+ async updateBug(id, patch) {
1306
+ this.unwrap(
1307
+ `/api/admin/ops/${id}`,
1308
+ await updateOpsItem({
1309
+ client: this.api,
1310
+ path: { handle: id },
1311
+ body: patch
1312
+ })
1313
+ );
1314
+ this.cache.delete("bugs");
1315
+ this.cache.delete(`bug:${id}`);
1316
+ }
1317
+ featureRequests() {
1318
+ return this.memo(
1319
+ "featureRequests",
1320
+ async () => this.asList(
1321
+ this.unwrap(
1322
+ "/api/admin/ops?type=feature_request",
1323
+ await listOpsItems({ client: this.api, query: { type: "feature_request" } })
1324
+ )
1325
+ )
1326
+ );
1327
+ }
1328
+ featureRequest(id) {
1329
+ return this.memo(
1330
+ `featureRequest:${id}`,
1331
+ async () => this.unwrap(
1332
+ `/api/admin/ops/${id}`,
1333
+ await getOpsItem({ client: this.api, path: { handle: id } })
1334
+ )
1335
+ );
1336
+ }
1337
+ async createFeatureRequest(input) {
1338
+ const r = this.unwrap(
1339
+ "/api/admin/ops",
1340
+ await createOpsItem({
1341
+ client: this.api,
1342
+ body: { ...input, type: "feature_request" }
1343
+ })
1344
+ );
1345
+ this.cache.delete("featureRequests");
1346
+ return r;
1347
+ }
1348
+ async updateFeatureRequest(id, patch) {
1349
+ this.unwrap(
1350
+ `/api/admin/ops/${id}`,
1351
+ await updateOpsItem({
1352
+ client: this.api,
1353
+ path: { handle: id },
1354
+ body: patch
1355
+ })
1356
+ );
1357
+ this.cache.delete("featureRequests");
1358
+ this.cache.delete(`featureRequest:${id}`);
1359
+ }
1360
+ // ── attachments ─────────────────────────────────────────────────────────
1361
+ // SPEC GAP: the report-attachment routes aren't in the OpenAPI contract.
1362
+ /** Fetch an attachment file as a Blob via the authenticated stream route
1363
+ * (an <Image>/<img> can't send the Authorization header itself). */
1364
+ async attachmentBlob(id) {
1365
+ const path = `/api/admin/reports/attachments/${encodeURIComponent(id)}`;
1366
+ const res = await this.fetchImpl(`${this.adminBaseUrl}${path}`, this.rawInit());
1367
+ if (!res.ok) this.fail(path, await this.readErrorDetail(res), res);
1368
+ return res.blob();
1369
+ }
1370
+ async uploadAttachment(args) {
1371
+ const fd = new FormData();
1372
+ fd.append("reportKind", args.reportKind);
1373
+ fd.append("reportId", args.reportId);
1374
+ fd.append("kind", args.kind);
1375
+ fd.append("filename", args.filename);
1376
+ fd.append(
1377
+ "file",
1378
+ args.blob,
1379
+ args.filename
1380
+ );
1381
+ const path = "/api/admin/reports/attachments";
1382
+ const res = await this.fetchImpl(
1383
+ `${this.adminBaseUrl}${path}`,
1384
+ this.rawInit({ method: "POST", body: fd })
1385
+ );
1386
+ if (!res.ok) this.fail(path, await this.readErrorDetail(res), res);
1387
+ return await res.json();
1388
+ }
1389
+ };
1390
+
1391
+ export {
1392
+ AuthError,
1393
+ DevtoolsClient
1394
+ };