@shipeasy/openapi 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +24 -0
- package/dist/index.js +1924 -3
- package/dist/node-context.d.ts +37 -0
- package/dist/node-context.js +56 -0
- package/dist/operations/attributes.d.ts +33 -0
- package/dist/operations/attributes.js +16 -0
- package/dist/operations/cli-adapter.d.ts +35 -0
- package/dist/operations/cli-adapter.js +91 -0
- package/dist/operations/coerce.d.ts +32 -0
- package/dist/operations/coerce.js +53 -0
- package/dist/operations/configs.d.ts +28 -0
- package/dist/operations/configs.js +119 -0
- package/dist/operations/docs.d.ts +37 -0
- package/dist/operations/docs.js +39 -0
- package/dist/operations/events.d.ts +28 -0
- package/dist/operations/events.js +121 -0
- package/dist/operations/experiments.d.ts +28 -0
- package/dist/operations/experiments.js +280 -0
- package/dist/operations/gates.d.ts +28 -0
- package/dist/operations/gates.js +132 -0
- package/dist/operations/i18n.d.ts +28 -0
- package/dist/operations/i18n.js +121 -0
- package/dist/operations/index.d.ts +63 -0
- package/dist/operations/index.js +1751 -0
- package/dist/operations/killswitches.d.ts +28 -0
- package/dist/operations/killswitches.js +126 -0
- package/dist/operations/mcp-adapter.d.ts +35 -0
- package/dist/operations/mcp-adapter.js +94 -0
- package/dist/operations/metrics.d.ts +30 -0
- package/dist/operations/metrics.js +114 -0
- package/dist/operations/ops.d.ts +28 -0
- package/dist/operations/ops.js +313 -0
- package/dist/operations/projects.d.ts +28 -0
- package/dist/operations/projects.js +36 -0
- package/dist/operations/sdk-docs.d.ts +28 -0
- package/dist/operations/sdk-docs.js +160 -0
- package/dist/operations/types.d.ts +138 -0
- package/dist/operations/types.js +9 -0
- package/dist/operations/universes.d.ts +28 -0
- package/dist/operations/universes.js +93 -0
- package/dist/operations/util.d.ts +33 -0
- package/dist/operations/util.js +9 -0
- package/dist/resources/alert-rules.d.ts +3 -1
- package/dist/resources/alert-rules.js +11 -2
- package/dist/resources/attributes.d.ts +19 -0
- package/dist/resources/attributes.js +10 -0
- package/dist/resources/events.d.ts +61 -0
- package/dist/resources/events.js +42 -0
- package/dist/resources/i18n.d.ts +84 -0
- package/dist/resources/i18n.js +60 -0
- package/dist/resources/index.d.ts +22 -4
- package/dist/resources/index.js +184 -3
- package/dist/resources/metrics.d.ts +54 -0
- package/dist/resources/metrics.js +40 -0
- package/dist/resources/ops.d.ts +88 -0
- package/dist/resources/ops.js +36 -0
- package/dist/resources/projects.d.ts +54 -0
- package/dist/resources/projects.js +14 -0
- package/dist/schemas/alert-rules.d.ts +23 -1
- package/dist/schemas/alert-rules.js +13 -3
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +12 -2
- package/package.json +10 -1
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import { Transport } from '../transport.js';
|
|
2
|
-
import '../schemas/pagination.js';
|
|
3
1
|
import * as zod_v4_core from 'zod/v4/core';
|
|
4
2
|
import * as zod from 'zod';
|
|
3
|
+
import { AttributesClient } from './attributes.js';
|
|
4
|
+
export { attributesClient } from './attributes.js';
|
|
5
|
+
import { I18nClient } from './i18n.js';
|
|
6
|
+
export { i18nClient } from './i18n.js';
|
|
7
|
+
import { ProjectsClient } from './projects.js';
|
|
8
|
+
export { projectsClient } from './projects.js';
|
|
9
|
+
import { OpsClient } from './ops.js';
|
|
10
|
+
export { opsClient } from './ops.js';
|
|
11
|
+
import { EventsClient } from './events.js';
|
|
12
|
+
export { eventsClient } from './events.js';
|
|
13
|
+
import { MetricsClient } from './metrics.js';
|
|
14
|
+
export { metricsClient } from './metrics.js';
|
|
5
15
|
import { AlertRulesClient } from './alert-rules.js';
|
|
6
16
|
export { alertRulesClient } from './alert-rules.js';
|
|
7
17
|
import { KillswitchesClient } from './killswitches.js';
|
|
@@ -14,12 +24,14 @@ import { ExperimentsClient } from './experiments.js';
|
|
|
14
24
|
export { experimentsClient, experimentsResource } from './experiments.js';
|
|
15
25
|
import { GatesClient } from './gates.js';
|
|
16
26
|
export { gatesClient, gatesResource } from './gates.js';
|
|
17
|
-
import '../
|
|
18
|
-
import '../schemas/universes.js';
|
|
27
|
+
import { Transport } from '../transport.js';
|
|
19
28
|
import '../schemas/alert-rules.js';
|
|
20
29
|
import '../schemas/killswitches.js';
|
|
30
|
+
import '../schemas/pagination.js';
|
|
31
|
+
import '../schemas/universes.js';
|
|
21
32
|
import '../schemas/configs.js';
|
|
22
33
|
import '../schemas/experiments.js';
|
|
34
|
+
import '../schemas/gates.js';
|
|
23
35
|
|
|
24
36
|
/**
|
|
25
37
|
* Aggregate admin client. Each resource lives in its own file under
|
|
@@ -36,6 +48,12 @@ declare function createAdminClient(transport: Transport): {
|
|
|
36
48
|
universes: UniversesClient;
|
|
37
49
|
killswitches: KillswitchesClient;
|
|
38
50
|
alertRules: AlertRulesClient;
|
|
51
|
+
metrics: MetricsClient;
|
|
52
|
+
events: EventsClient;
|
|
53
|
+
ops: OpsClient;
|
|
54
|
+
projects: ProjectsClient;
|
|
55
|
+
i18n: I18nClient;
|
|
56
|
+
attributes: AttributesClient;
|
|
39
57
|
};
|
|
40
58
|
type AdminClient = ReturnType<typeof createAdminClient>;
|
|
41
59
|
/**
|
package/dist/resources/index.js
CHANGED
|
@@ -2296,6 +2296,13 @@ var killswitchesResource = {
|
|
|
2296
2296
|
import { z as z13 } from "zod";
|
|
2297
2297
|
var ALERT_COMPARATORS = ["gt", "gte", "lt", "lte"];
|
|
2298
2298
|
var ALERT_SEVERITIES = ["danger", "warn", "info"];
|
|
2299
|
+
var notificationTargetSchema = z13.object({
|
|
2300
|
+
slackChannel: z13.object({
|
|
2301
|
+
id: z13.string().min(1).describe("Slack channel id (e.g. C0123ABCD)."),
|
|
2302
|
+
name: z13.string().min(1).describe("Slack channel name (without the leading #).")
|
|
2303
|
+
}).nullish().describe("Slack channel to post this rule's alert to. Requires a Slack connector."),
|
|
2304
|
+
email: z13.string().email().nullish().describe("Email address to notify for this rule (overrides the default recipient).")
|
|
2305
|
+
}).strict();
|
|
2299
2306
|
var alertRuleCreateSchema = z13.object({
|
|
2300
2307
|
name: z13.string().min(1).max(120).describe("Human label for the rule, shown on the alert and the rules list."),
|
|
2301
2308
|
metricId: z13.string().min(1).describe("Id of the metric to evaluate."),
|
|
@@ -2303,7 +2310,8 @@ var alertRuleCreateSchema = z13.object({
|
|
|
2303
2310
|
threshold: z13.number().finite().describe("Threshold the metric value is compared against."),
|
|
2304
2311
|
windowHours: z13.number().int().min(1).max(720).default(24).describe("Lookback window (hours) the metric is aggregated over. 1\u2013720."),
|
|
2305
2312
|
severity: z13.enum(ALERT_SEVERITIES).default("warn").describe("Severity of the raised alert."),
|
|
2306
|
-
enabled: z13.boolean().default(true).describe("Whether the rule is evaluated by the cron.")
|
|
2313
|
+
enabled: z13.boolean().default(true).describe("Whether the rule is evaluated by the cron."),
|
|
2314
|
+
notify: notificationTargetSchema.nullish().describe("Where to deliver this rule's alert (Slack channel and/or email target).")
|
|
2307
2315
|
}).strict();
|
|
2308
2316
|
var alertRuleUpdateSchema = z13.object({
|
|
2309
2317
|
name: z13.string().min(1).max(120),
|
|
@@ -2311,7 +2319,8 @@ var alertRuleUpdateSchema = z13.object({
|
|
|
2311
2319
|
threshold: z13.number().finite(),
|
|
2312
2320
|
windowHours: z13.number().int().min(1).max(720),
|
|
2313
2321
|
severity: z13.enum(ALERT_SEVERITIES),
|
|
2314
|
-
enabled: z13.boolean()
|
|
2322
|
+
enabled: z13.boolean(),
|
|
2323
|
+
notify: notificationTargetSchema.nullish()
|
|
2315
2324
|
}).partial().strict();
|
|
2316
2325
|
|
|
2317
2326
|
// src/resources/alert-rules.ts
|
|
@@ -2346,6 +2355,166 @@ function alertRulesClient(t) {
|
|
|
2346
2355
|
};
|
|
2347
2356
|
}
|
|
2348
2357
|
|
|
2358
|
+
// src/resources/metrics.ts
|
|
2359
|
+
var BASE7 = "/api/admin/metrics";
|
|
2360
|
+
function metricsClient(t) {
|
|
2361
|
+
async function list() {
|
|
2362
|
+
return t.request("GET", BASE7);
|
|
2363
|
+
}
|
|
2364
|
+
async function resolve(idOrName) {
|
|
2365
|
+
const all = await list();
|
|
2366
|
+
const byId = all.find((m) => m.id === idOrName);
|
|
2367
|
+
if (byId) return byId;
|
|
2368
|
+
const byPrefix = all.filter((m) => m.id.startsWith(idOrName));
|
|
2369
|
+
if (byPrefix.length === 1) return byPrefix[0];
|
|
2370
|
+
if (byPrefix.length > 1) throw new ApiError(`Metric id prefix '${idOrName}' is ambiguous`, 400);
|
|
2371
|
+
const byName = all.filter((m) => m.name === idOrName);
|
|
2372
|
+
if (byName.length === 1) return byName[0];
|
|
2373
|
+
if (byName.length > 1)
|
|
2374
|
+
throw new ApiError(`Metric name '${idOrName}' is ambiguous \u2014 pass an id`, 400);
|
|
2375
|
+
throw new ApiError(`Metric '${idOrName}' not found`, 404);
|
|
2376
|
+
}
|
|
2377
|
+
return {
|
|
2378
|
+
list,
|
|
2379
|
+
resolve,
|
|
2380
|
+
get: (id) => t.request("GET", `${BASE7}/${id}`),
|
|
2381
|
+
create: (input) => t.request("POST", BASE7, input),
|
|
2382
|
+
delete: (id) => t.request("DELETE", `${BASE7}/${id}`)
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
// src/resources/events.ts
|
|
2387
|
+
var BASE8 = "/api/admin/events";
|
|
2388
|
+
function eventsClient(t) {
|
|
2389
|
+
async function list() {
|
|
2390
|
+
return t.request("GET", BASE8);
|
|
2391
|
+
}
|
|
2392
|
+
async function resolve(idOrName) {
|
|
2393
|
+
const all = await list();
|
|
2394
|
+
const byId = all.find((e) => e.id === idOrName);
|
|
2395
|
+
if (byId) return byId;
|
|
2396
|
+
const byPrefix = all.filter((e) => e.id.startsWith(idOrName));
|
|
2397
|
+
if (byPrefix.length === 1) return byPrefix[0];
|
|
2398
|
+
if (byPrefix.length > 1) throw new ApiError(`Event id prefix '${idOrName}' is ambiguous`, 400);
|
|
2399
|
+
const byName = all.filter((e) => e.name === idOrName);
|
|
2400
|
+
if (byName.length === 1) return byName[0];
|
|
2401
|
+
if (byName.length > 1)
|
|
2402
|
+
throw new ApiError(`Event name '${idOrName}' is ambiguous \u2014 pass an id`, 400);
|
|
2403
|
+
throw new ApiError(`Event '${idOrName}' not found`, 404);
|
|
2404
|
+
}
|
|
2405
|
+
return {
|
|
2406
|
+
list,
|
|
2407
|
+
resolve,
|
|
2408
|
+
get: (id) => t.request("GET", `${BASE8}/${id}`),
|
|
2409
|
+
create: (input) => t.request("POST", BASE8, input),
|
|
2410
|
+
update: (id, input) => t.request("PATCH", `${BASE8}/${id}`, input),
|
|
2411
|
+
approve: (id, input = {}) => t.request("POST", `${BASE8}/${id}/approve`, input),
|
|
2412
|
+
delete: (id) => t.request("DELETE", `${BASE8}/${id}`)
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// src/resources/ops.ts
|
|
2417
|
+
var FEEDBACK = "/api/admin/feedback";
|
|
2418
|
+
var NOTIFY = "/api/admin/notifications";
|
|
2419
|
+
var CREATE_PATH = {
|
|
2420
|
+
bug: "/api/admin/bugs",
|
|
2421
|
+
feature_request: "/api/admin/feature-requests"
|
|
2422
|
+
};
|
|
2423
|
+
function itemPath(handle) {
|
|
2424
|
+
return `${FEEDBACK}/${encodeURIComponent(handle)}`;
|
|
2425
|
+
}
|
|
2426
|
+
function opsClient(t) {
|
|
2427
|
+
return {
|
|
2428
|
+
list: (query = {}) => {
|
|
2429
|
+
const q = {};
|
|
2430
|
+
if (query.type) q.type = query.type;
|
|
2431
|
+
if (query.status) q.status = query.status;
|
|
2432
|
+
if (query.limit !== void 0) q.limit = String(query.limit);
|
|
2433
|
+
return t.request("GET", FEEDBACK, void 0, q);
|
|
2434
|
+
},
|
|
2435
|
+
get: (handle) => t.request("GET", itemPath(handle)),
|
|
2436
|
+
create: ({ type, ...body }) => t.request("POST", CREATE_PATH[type], body),
|
|
2437
|
+
update: (handle, input) => t.request("PATCH", itemPath(handle), input),
|
|
2438
|
+
linkPr: (handle, input) => t.request("POST", `${itemPath(handle)}/link-pr`, input),
|
|
2439
|
+
notify: (input) => t.request("POST", NOTIFY, {
|
|
2440
|
+
title: input.title,
|
|
2441
|
+
summary: input.summary,
|
|
2442
|
+
steps: input.steps ?? [],
|
|
2443
|
+
...input.href ? { href: input.href } : {},
|
|
2444
|
+
...input.dedupeKey ? { dedupeKey: input.dedupeKey } : {}
|
|
2445
|
+
}),
|
|
2446
|
+
channels: () => t.request("GET", "/api/admin/slack/channels")
|
|
2447
|
+
};
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
// src/resources/projects.ts
|
|
2451
|
+
var BASE9 = "/api/admin/projects";
|
|
2452
|
+
function projectsClient(t) {
|
|
2453
|
+
return {
|
|
2454
|
+
upsert: (input) => t.request("POST", `${BASE9}/upsert`, {
|
|
2455
|
+
domain: input.domain,
|
|
2456
|
+
...input.name ? { name: input.name } : {}
|
|
2457
|
+
}),
|
|
2458
|
+
current: () => t.request("GET", `${BASE9}/current`)
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// src/resources/i18n.ts
|
|
2463
|
+
var PROFILES = "/api/admin/i18n/profiles";
|
|
2464
|
+
var KEYS = "/api/admin/i18n/keys";
|
|
2465
|
+
var DRAFTS = "/api/admin/i18n/drafts";
|
|
2466
|
+
function i18nClient(t) {
|
|
2467
|
+
async function listProfiles() {
|
|
2468
|
+
return t.request("GET", PROFILES);
|
|
2469
|
+
}
|
|
2470
|
+
async function resolveProfile(name) {
|
|
2471
|
+
const all = await listProfiles();
|
|
2472
|
+
const found = all.find((p) => p.name === name);
|
|
2473
|
+
if (!found)
|
|
2474
|
+
throw new ApiError(
|
|
2475
|
+
`Profile '${name}' not found. Existing: ${all.map((p) => p.name).join(", ") || "(none)"}`,
|
|
2476
|
+
404
|
|
2477
|
+
);
|
|
2478
|
+
return found;
|
|
2479
|
+
}
|
|
2480
|
+
async function listKeys(query = {}) {
|
|
2481
|
+
const q = {};
|
|
2482
|
+
if (query.profile_id) q.profile_id = query.profile_id;
|
|
2483
|
+
if (query.prefix) q.prefix = query.prefix;
|
|
2484
|
+
if (query.limit !== void 0) q.limit = String(query.limit);
|
|
2485
|
+
const res = await t.request("GET", KEYS, void 0, q);
|
|
2486
|
+
return Array.isArray(res) ? res : res.keys;
|
|
2487
|
+
}
|
|
2488
|
+
function updateKey(id, input) {
|
|
2489
|
+
return t.request("PUT", `${KEYS}/${id}`, input);
|
|
2490
|
+
}
|
|
2491
|
+
return {
|
|
2492
|
+
listProfiles,
|
|
2493
|
+
resolveProfile,
|
|
2494
|
+
listKeys,
|
|
2495
|
+
updateKey,
|
|
2496
|
+
createProfile: (input) => t.request("POST", PROFILES, input),
|
|
2497
|
+
pushKeys: (input) => t.request("POST", KEYS, input),
|
|
2498
|
+
updateKeyByName: async (profileId, key, input) => {
|
|
2499
|
+
const rows = await listKeys({ profile_id: profileId, prefix: key, limit: 500 });
|
|
2500
|
+
const match = rows.find((k) => k.key === key);
|
|
2501
|
+
if (!match)
|
|
2502
|
+
throw new ApiError(`Key '${key}' not found in profile. Add it first with i18n push.`, 404);
|
|
2503
|
+
return updateKey(match.id, input);
|
|
2504
|
+
},
|
|
2505
|
+
listDrafts: () => t.request("GET", DRAFTS),
|
|
2506
|
+
publish: (profileId, input) => t.request("POST", `${PROFILES}/${profileId}/publish`, { chunk: input.chunk ?? "default" })
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
// src/resources/attributes.ts
|
|
2511
|
+
var BASE10 = "/api/admin/attributes";
|
|
2512
|
+
function attributesClient(t) {
|
|
2513
|
+
return {
|
|
2514
|
+
list: () => t.request("GET", BASE10)
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2349
2518
|
// src/resources/index.ts
|
|
2350
2519
|
function createAdminClient(transport) {
|
|
2351
2520
|
return {
|
|
@@ -2355,7 +2524,13 @@ function createAdminClient(transport) {
|
|
|
2355
2524
|
configs: configsClient(transport),
|
|
2356
2525
|
universes: universesClient(transport),
|
|
2357
2526
|
killswitches: killswitchesClient(transport),
|
|
2358
|
-
alertRules: alertRulesClient(transport)
|
|
2527
|
+
alertRules: alertRulesClient(transport),
|
|
2528
|
+
metrics: metricsClient(transport),
|
|
2529
|
+
events: eventsClient(transport),
|
|
2530
|
+
ops: opsClient(transport),
|
|
2531
|
+
projects: projectsClient(transport),
|
|
2532
|
+
i18n: i18nClient(transport),
|
|
2533
|
+
attributes: attributesClient(transport)
|
|
2359
2534
|
};
|
|
2360
2535
|
}
|
|
2361
2536
|
var RESOURCE_REGISTRY = [
|
|
@@ -2368,15 +2543,21 @@ var RESOURCE_REGISTRY = [
|
|
|
2368
2543
|
export {
|
|
2369
2544
|
RESOURCE_REGISTRY,
|
|
2370
2545
|
alertRulesClient,
|
|
2546
|
+
attributesClient,
|
|
2371
2547
|
configsClient,
|
|
2372
2548
|
configsResource,
|
|
2373
2549
|
createAdminClient,
|
|
2550
|
+
eventsClient,
|
|
2374
2551
|
experimentsClient,
|
|
2375
2552
|
experimentsResource,
|
|
2376
2553
|
gatesClient,
|
|
2377
2554
|
gatesResource,
|
|
2555
|
+
i18nClient,
|
|
2378
2556
|
killswitchesClient,
|
|
2379
2557
|
killswitchesResource,
|
|
2558
|
+
metricsClient,
|
|
2559
|
+
opsClient,
|
|
2560
|
+
projectsClient,
|
|
2380
2561
|
universesClient,
|
|
2381
2562
|
universesResource
|
|
2382
2563
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Transport } from '../transport.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Metric definitions — the event-backed queries that power tracking dashboards
|
|
5
|
+
* and experiment success/guardrail metrics. A metric pins one event, one
|
|
6
|
+
* aggregation, and (for sum/avg) a value path, expressed as the metric query
|
|
7
|
+
* DSL or its typed IR form.
|
|
8
|
+
*
|
|
9
|
+
* The admin endpoint accepts either `query` (the DSL string) or `query_ir`
|
|
10
|
+
* (the typed IR). The CLI's vendored DSL parser turns `--query` into IR
|
|
11
|
+
* client-side for an early error; the server validates either form, so the
|
|
12
|
+
* registry op can pass `query` straight through and stay worker-safe (no
|
|
13
|
+
* parser bundled into the shared registry).
|
|
14
|
+
*/
|
|
15
|
+
interface Metric {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
folder: string | null;
|
|
19
|
+
eventName: string;
|
|
20
|
+
aggregation: string;
|
|
21
|
+
valuePath: string | null;
|
|
22
|
+
queryIr?: unknown;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
interface MetricCreateInput {
|
|
26
|
+
name: string;
|
|
27
|
+
event_name: string;
|
|
28
|
+
folder?: string | null;
|
|
29
|
+
winsorize_pct?: number;
|
|
30
|
+
min_detectable_effect?: number | null;
|
|
31
|
+
/** Metric query DSL string, e.g. `sum(purchase, amount)`. */
|
|
32
|
+
query?: string;
|
|
33
|
+
/** Typed IR form (alternative to `query`). */
|
|
34
|
+
query_ir?: unknown;
|
|
35
|
+
}
|
|
36
|
+
interface MetricsClient {
|
|
37
|
+
/** Every metric in the project (the list endpoint is not paginated). */
|
|
38
|
+
list(): Promise<Metric[]>;
|
|
39
|
+
/** Fetch one metric by its full id. */
|
|
40
|
+
get(id: string): Promise<Metric>;
|
|
41
|
+
/** Resolve by exact id, unique id-prefix, or exact (unique) name. */
|
|
42
|
+
resolve(idOrName: string): Promise<Metric>;
|
|
43
|
+
create(input: MetricCreateInput): Promise<{
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
}>;
|
|
47
|
+
/** Soft-delete (the user-facing verb is `archive`). */
|
|
48
|
+
delete(id: string): Promise<{
|
|
49
|
+
ok: true;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
declare function metricsClient(t: Transport): MetricsClient;
|
|
53
|
+
|
|
54
|
+
export { type Metric, type MetricCreateInput, type MetricsClient, metricsClient };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/transport.ts
|
|
2
|
+
var ApiError = class extends Error {
|
|
3
|
+
constructor(message, status) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.name = "ApiError";
|
|
7
|
+
}
|
|
8
|
+
status;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/resources/metrics.ts
|
|
12
|
+
var BASE = "/api/admin/metrics";
|
|
13
|
+
function metricsClient(t) {
|
|
14
|
+
async function list() {
|
|
15
|
+
return t.request("GET", BASE);
|
|
16
|
+
}
|
|
17
|
+
async function resolve(idOrName) {
|
|
18
|
+
const all = await list();
|
|
19
|
+
const byId = all.find((m) => m.id === idOrName);
|
|
20
|
+
if (byId) return byId;
|
|
21
|
+
const byPrefix = all.filter((m) => m.id.startsWith(idOrName));
|
|
22
|
+
if (byPrefix.length === 1) return byPrefix[0];
|
|
23
|
+
if (byPrefix.length > 1) throw new ApiError(`Metric id prefix '${idOrName}' is ambiguous`, 400);
|
|
24
|
+
const byName = all.filter((m) => m.name === idOrName);
|
|
25
|
+
if (byName.length === 1) return byName[0];
|
|
26
|
+
if (byName.length > 1)
|
|
27
|
+
throw new ApiError(`Metric name '${idOrName}' is ambiguous \u2014 pass an id`, 400);
|
|
28
|
+
throw new ApiError(`Metric '${idOrName}' not found`, 404);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
list,
|
|
32
|
+
resolve,
|
|
33
|
+
get: (id) => t.request("GET", `${BASE}/${id}`),
|
|
34
|
+
create: (input) => t.request("POST", BASE, input),
|
|
35
|
+
delete: (id) => t.request("DELETE", `${BASE}/${id}`)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
metricsClient
|
|
40
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Transport } from '../transport.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Operational queue — the unified feedback table of bug reports, feature
|
|
5
|
+
* requests, and auto-filed error/alert tickets, over `/api/admin/feedback`.
|
|
6
|
+
* One resource covers list/get/create/update/link-pr plus the `notify`
|
|
7
|
+
* escalation bell (`/api/admin/notifications`) and the Slack channel reader
|
|
8
|
+
* (`/api/admin/slack/channels`) used to resolve alert-rule targets.
|
|
9
|
+
*
|
|
10
|
+
* Item handles are either the per-project number (`7`) or the full id; the
|
|
11
|
+
* admin API resolves either, so callers pass the handle straight through.
|
|
12
|
+
*/
|
|
13
|
+
type OpsType = "bug" | "feature_request" | "error" | "alert";
|
|
14
|
+
type OpsStatus = "open" | "triaged" | "in_progress" | "ready_for_qa" | "resolved" | "wont_fix";
|
|
15
|
+
type OpsPriority = "nice_to_have" | "medium" | "high" | "critical";
|
|
16
|
+
interface OpsItem {
|
|
17
|
+
id: string;
|
|
18
|
+
number: number | null;
|
|
19
|
+
type: string;
|
|
20
|
+
title: string;
|
|
21
|
+
status: string;
|
|
22
|
+
priority: string | null;
|
|
23
|
+
sourceRef?: string | null;
|
|
24
|
+
createdAt: string;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
interface OpsListQuery {
|
|
28
|
+
type?: OpsType | "all";
|
|
29
|
+
status?: OpsStatus | "all";
|
|
30
|
+
limit?: number;
|
|
31
|
+
}
|
|
32
|
+
interface OpsCreateInput {
|
|
33
|
+
/** Only `bug` / `feature_request` are user-fileable; error/alert are auto-filed. */
|
|
34
|
+
type: "bug" | "feature_request";
|
|
35
|
+
title: string;
|
|
36
|
+
body?: string;
|
|
37
|
+
priority?: OpsPriority;
|
|
38
|
+
stepsToReproduce?: string;
|
|
39
|
+
pageUrl?: string;
|
|
40
|
+
}
|
|
41
|
+
interface OpsUpdateInput {
|
|
42
|
+
status?: OpsStatus;
|
|
43
|
+
priority?: OpsPriority;
|
|
44
|
+
}
|
|
45
|
+
interface OpsNotifyInput {
|
|
46
|
+
title: string;
|
|
47
|
+
summary: string;
|
|
48
|
+
steps?: string[];
|
|
49
|
+
href?: string;
|
|
50
|
+
dedupeKey?: string;
|
|
51
|
+
}
|
|
52
|
+
interface SlackChannel {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
isPrivate?: boolean;
|
|
56
|
+
}
|
|
57
|
+
interface SlackChannelsResponse {
|
|
58
|
+
connected: boolean;
|
|
59
|
+
channels: SlackChannel[];
|
|
60
|
+
}
|
|
61
|
+
interface OpsClient {
|
|
62
|
+
list(query?: OpsListQuery): Promise<OpsItem[]>;
|
|
63
|
+
get(handle: string): Promise<OpsItem>;
|
|
64
|
+
/** File a bug or feature request. Fires project connectors (GitHub/Slack). */
|
|
65
|
+
create(input: OpsCreateInput): Promise<{
|
|
66
|
+
id: string;
|
|
67
|
+
number?: number | null;
|
|
68
|
+
}>;
|
|
69
|
+
update(handle: string, input: OpsUpdateInput): Promise<{
|
|
70
|
+
id: string;
|
|
71
|
+
}>;
|
|
72
|
+
linkPr(handle: string, input: {
|
|
73
|
+
prNumber: number | null;
|
|
74
|
+
prUrl?: string;
|
|
75
|
+
}): Promise<{
|
|
76
|
+
id: string;
|
|
77
|
+
}>;
|
|
78
|
+
/** Raise a 'needs your attention' bell notification (create-only, idempotent on dedupeKey). */
|
|
79
|
+
notify(input: OpsNotifyInput): Promise<{
|
|
80
|
+
dedupeKey: string;
|
|
81
|
+
dispatched: boolean;
|
|
82
|
+
}>;
|
|
83
|
+
/** The project's Slack channels — used to resolve alert-rule `--slack-channel`. */
|
|
84
|
+
channels(): Promise<SlackChannelsResponse>;
|
|
85
|
+
}
|
|
86
|
+
declare function opsClient(t: Transport): OpsClient;
|
|
87
|
+
|
|
88
|
+
export { type OpsClient, type OpsCreateInput, type OpsItem, type OpsListQuery, type OpsNotifyInput, type OpsPriority, type OpsStatus, type OpsType, type OpsUpdateInput, type SlackChannel, type SlackChannelsResponse, opsClient };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/resources/ops.ts
|
|
2
|
+
var FEEDBACK = "/api/admin/feedback";
|
|
3
|
+
var NOTIFY = "/api/admin/notifications";
|
|
4
|
+
var CREATE_PATH = {
|
|
5
|
+
bug: "/api/admin/bugs",
|
|
6
|
+
feature_request: "/api/admin/feature-requests"
|
|
7
|
+
};
|
|
8
|
+
function itemPath(handle) {
|
|
9
|
+
return `${FEEDBACK}/${encodeURIComponent(handle)}`;
|
|
10
|
+
}
|
|
11
|
+
function opsClient(t) {
|
|
12
|
+
return {
|
|
13
|
+
list: (query = {}) => {
|
|
14
|
+
const q = {};
|
|
15
|
+
if (query.type) q.type = query.type;
|
|
16
|
+
if (query.status) q.status = query.status;
|
|
17
|
+
if (query.limit !== void 0) q.limit = String(query.limit);
|
|
18
|
+
return t.request("GET", FEEDBACK, void 0, q);
|
|
19
|
+
},
|
|
20
|
+
get: (handle) => t.request("GET", itemPath(handle)),
|
|
21
|
+
create: ({ type, ...body }) => t.request("POST", CREATE_PATH[type], body),
|
|
22
|
+
update: (handle, input) => t.request("PATCH", itemPath(handle), input),
|
|
23
|
+
linkPr: (handle, input) => t.request("POST", `${itemPath(handle)}/link-pr`, input),
|
|
24
|
+
notify: (input) => t.request("POST", NOTIFY, {
|
|
25
|
+
title: input.title,
|
|
26
|
+
summary: input.summary,
|
|
27
|
+
steps: input.steps ?? [],
|
|
28
|
+
...input.href ? { href: input.href } : {},
|
|
29
|
+
...input.dedupeKey ? { dedupeKey: input.dedupeKey } : {}
|
|
30
|
+
}),
|
|
31
|
+
channels: () => t.request("GET", "/api/admin/slack/channels")
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
opsClient
|
|
36
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Transport } from '../transport.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Projects — find-or-create by domain (`upsert`) and the auth-resolved current
|
|
5
|
+
* project (`current`). The `.shipeasy` write that `bind`/`upsert --bind`
|
|
6
|
+
* performs is a CONSUMER side-effect layered on top of these calls (it touches
|
|
7
|
+
* the filesystem); it never lives in the resource, so this stays worker-safe.
|
|
8
|
+
*/
|
|
9
|
+
interface UpsertResult {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
domain: string | null;
|
|
13
|
+
owner_email: string;
|
|
14
|
+
created: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The project the caller's auth header resolves to. The server reads the
|
|
18
|
+
* project from the credential — there is no id parameter — so `current` works
|
|
19
|
+
* with nothing but the session, which is what makes a registry-driven `whoami`
|
|
20
|
+
* possible.
|
|
21
|
+
*/
|
|
22
|
+
interface CurrentProject {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
domain: string | null;
|
|
26
|
+
ownerEmail: string;
|
|
27
|
+
plan: "free" | "paid";
|
|
28
|
+
status: "active" | "inactive";
|
|
29
|
+
subscriptionStatus: string;
|
|
30
|
+
billingInterval: "monthly" | "annual";
|
|
31
|
+
currentPeriodEnd: string | null;
|
|
32
|
+
trialEndsAt: string | null;
|
|
33
|
+
cancelAtPeriodEnd: number;
|
|
34
|
+
moduleTranslations: boolean | number;
|
|
35
|
+
moduleConfigs: boolean | number;
|
|
36
|
+
moduleGates: boolean | number;
|
|
37
|
+
moduleExperiments: boolean | number;
|
|
38
|
+
moduleFeedback: boolean | number;
|
|
39
|
+
createdAt: string;
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
}
|
|
43
|
+
interface ProjectsClient {
|
|
44
|
+
/** Find-or-create a project by domain (idempotent). */
|
|
45
|
+
upsert(input: {
|
|
46
|
+
domain: string;
|
|
47
|
+
name?: string;
|
|
48
|
+
}): Promise<UpsertResult>;
|
|
49
|
+
/** The project the auth header resolves to (no id needed). */
|
|
50
|
+
current(): Promise<CurrentProject>;
|
|
51
|
+
}
|
|
52
|
+
declare function projectsClient(t: Transport): ProjectsClient;
|
|
53
|
+
|
|
54
|
+
export { type CurrentProject, type ProjectsClient, type UpsertResult, projectsClient };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/resources/projects.ts
|
|
2
|
+
var BASE = "/api/admin/projects";
|
|
3
|
+
function projectsClient(t) {
|
|
4
|
+
return {
|
|
5
|
+
upsert: (input) => t.request("POST", `${BASE}/upsert`, {
|
|
6
|
+
domain: input.domain,
|
|
7
|
+
...input.name ? { name: input.name } : {}
|
|
8
|
+
}),
|
|
9
|
+
current: () => t.request("GET", `${BASE}/current`)
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
projectsClient
|
|
14
|
+
};
|
|
@@ -2,6 +2,14 @@ import { z } from 'zod';
|
|
|
2
2
|
|
|
3
3
|
declare const ALERT_COMPARATORS: readonly ["gt", "gte", "lt", "lte"];
|
|
4
4
|
declare const ALERT_SEVERITIES: readonly ["danger", "warn", "info"];
|
|
5
|
+
declare const notificationTargetSchema: z.ZodObject<{
|
|
6
|
+
slackChannel: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
name: z.ZodString;
|
|
9
|
+
}, z.core.$strip>>>;
|
|
10
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
type NotificationTarget = z.infer<typeof notificationTargetSchema>;
|
|
5
13
|
declare const alertRuleCreateSchema: z.ZodObject<{
|
|
6
14
|
name: z.ZodString;
|
|
7
15
|
metricId: z.ZodString;
|
|
@@ -19,6 +27,13 @@ declare const alertRuleCreateSchema: z.ZodObject<{
|
|
|
19
27
|
info: "info";
|
|
20
28
|
}>>;
|
|
21
29
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
30
|
+
notify: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
31
|
+
slackChannel: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
32
|
+
id: z.ZodString;
|
|
33
|
+
name: z.ZodString;
|
|
34
|
+
}, z.core.$strip>>>;
|
|
35
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
36
|
+
}, z.core.$strict>>>;
|
|
22
37
|
}, z.core.$strict>;
|
|
23
38
|
declare const alertRuleUpdateSchema: z.ZodObject<{
|
|
24
39
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -36,8 +51,15 @@ declare const alertRuleUpdateSchema: z.ZodObject<{
|
|
|
36
51
|
info: "info";
|
|
37
52
|
}>>;
|
|
38
53
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
54
|
+
notify: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
55
|
+
slackChannel: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
56
|
+
id: z.ZodString;
|
|
57
|
+
name: z.ZodString;
|
|
58
|
+
}, z.core.$strip>>>;
|
|
59
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
60
|
+
}, z.core.$strict>>>>;
|
|
39
61
|
}, z.core.$strict>;
|
|
40
62
|
type AlertRuleCreate = z.infer<typeof alertRuleCreateSchema>;
|
|
41
63
|
type AlertRuleUpdate = z.infer<typeof alertRuleUpdateSchema>;
|
|
42
64
|
|
|
43
|
-
export { ALERT_COMPARATORS, ALERT_SEVERITIES, type AlertRuleCreate, type AlertRuleUpdate, alertRuleCreateSchema, alertRuleUpdateSchema };
|
|
65
|
+
export { ALERT_COMPARATORS, ALERT_SEVERITIES, type AlertRuleCreate, type AlertRuleUpdate, type NotificationTarget, alertRuleCreateSchema, alertRuleUpdateSchema, notificationTargetSchema };
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
var ALERT_COMPARATORS = ["gt", "gte", "lt", "lte"];
|
|
4
4
|
var ALERT_SEVERITIES = ["danger", "warn", "info"];
|
|
5
|
+
var notificationTargetSchema = z.object({
|
|
6
|
+
slackChannel: z.object({
|
|
7
|
+
id: z.string().min(1).describe("Slack channel id (e.g. C0123ABCD)."),
|
|
8
|
+
name: z.string().min(1).describe("Slack channel name (without the leading #).")
|
|
9
|
+
}).nullish().describe("Slack channel to post this rule's alert to. Requires a Slack connector."),
|
|
10
|
+
email: z.string().email().nullish().describe("Email address to notify for this rule (overrides the default recipient).")
|
|
11
|
+
}).strict();
|
|
5
12
|
var alertRuleCreateSchema = z.object({
|
|
6
13
|
name: z.string().min(1).max(120).describe("Human label for the rule, shown on the alert and the rules list."),
|
|
7
14
|
metricId: z.string().min(1).describe("Id of the metric to evaluate."),
|
|
@@ -9,7 +16,8 @@ var alertRuleCreateSchema = z.object({
|
|
|
9
16
|
threshold: z.number().finite().describe("Threshold the metric value is compared against."),
|
|
10
17
|
windowHours: z.number().int().min(1).max(720).default(24).describe("Lookback window (hours) the metric is aggregated over. 1\u2013720."),
|
|
11
18
|
severity: z.enum(ALERT_SEVERITIES).default("warn").describe("Severity of the raised alert."),
|
|
12
|
-
enabled: z.boolean().default(true).describe("Whether the rule is evaluated by the cron.")
|
|
19
|
+
enabled: z.boolean().default(true).describe("Whether the rule is evaluated by the cron."),
|
|
20
|
+
notify: notificationTargetSchema.nullish().describe("Where to deliver this rule's alert (Slack channel and/or email target).")
|
|
13
21
|
}).strict();
|
|
14
22
|
var alertRuleUpdateSchema = z.object({
|
|
15
23
|
name: z.string().min(1).max(120),
|
|
@@ -17,11 +25,13 @@ var alertRuleUpdateSchema = z.object({
|
|
|
17
25
|
threshold: z.number().finite(),
|
|
18
26
|
windowHours: z.number().int().min(1).max(720),
|
|
19
27
|
severity: z.enum(ALERT_SEVERITIES),
|
|
20
|
-
enabled: z.boolean()
|
|
28
|
+
enabled: z.boolean(),
|
|
29
|
+
notify: notificationTargetSchema.nullish()
|
|
21
30
|
}).partial().strict();
|
|
22
31
|
export {
|
|
23
32
|
ALERT_COMPARATORS,
|
|
24
33
|
ALERT_SEVERITIES,
|
|
25
34
|
alertRuleCreateSchema,
|
|
26
|
-
alertRuleUpdateSchema
|
|
35
|
+
alertRuleUpdateSchema,
|
|
36
|
+
notificationTargetSchema
|
|
27
37
|
};
|
package/dist/schemas/index.d.ts
CHANGED
|
@@ -7,5 +7,5 @@ export { GateCreateInput, GateUpdateInput, StackedGateEntryInput, gateCreateResp
|
|
|
7
7
|
export { KillswitchCreateInput, KillswitchSwitchSetInput, KillswitchSwitchUnsetInput, KillswitchUpdateInput, KillswitchValue, KillswitchValueSetInput, SWITCH_KEY_RE, killswitchCreateResponseSchema, killswitchCreateSchema, killswitchDeleteResponseSchema, killswitchResponseSchema, killswitchSwitchSetResponseSchema, killswitchSwitchSetSchema, killswitchSwitchUnsetResponseSchema, killswitchSwitchUnsetSchema, killswitchUpdateResponseSchema, killswitchUpdateSchema, killswitchValueSchema, killswitchValueSetSchema, switchKeySchema } from './killswitches.js';
|
|
8
8
|
export { UniverseCreateInput, UniverseUpdateInput, holdoutRangeSchema, universeCreateResponseSchema, universeCreateSchema, universeDeleteResponseSchema, universeNameSchema, universeResponseSchema, universeUpdateResponseSchema, universeUpdateSchema } from './universes.js';
|
|
9
9
|
export { ExperimentCreateInput, ExperimentInlineMetric, ExperimentUpdateInput, bucketBySchema, experimentCreateResponseSchema, experimentCreateSchema, experimentDeleteResponseSchema, experimentGroupSchema, experimentInlineMetricSchema, experimentMetricsResponseSchema, experimentMetricsUpdateSchema, experimentNameSchema, experimentReanalyzeResponseSchema, experimentResponseSchema, experimentResultsResponseSchema, experimentStatusResponseSchema, experimentStatusUpdateSchema, experimentTimeseriesResponseSchema, experimentUpdateResponseSchema, experimentUpdateSchema, paramSchemaSchema } from './experiments.js';
|
|
10
|
-
export { ALERT_COMPARATORS, ALERT_SEVERITIES, AlertRuleCreate, AlertRuleUpdate, alertRuleCreateSchema, alertRuleUpdateSchema } from './alert-rules.js';
|
|
10
|
+
export { ALERT_COMPARATORS, ALERT_SEVERITIES, AlertRuleCreate, AlertRuleUpdate, NotificationTarget, alertRuleCreateSchema, alertRuleUpdateSchema, notificationTargetSchema } from './alert-rules.js';
|
|
11
11
|
import 'zod';
|