977 lines
28 KiB
TypeScript
977 lines
28 KiB
TypeScript
import { config } from './config.js';
|
|
|
|
export interface PlatformClientOptions {
|
|
/** Bearer token for the upstream request */
|
|
token?: string;
|
|
/** x-request-id to propagate */
|
|
requestId?: string;
|
|
/** x-product-id to forward */
|
|
productId?: string;
|
|
}
|
|
|
|
export type RunKind = 'job' | 'agent';
|
|
export type RunStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled';
|
|
export type RunStepStatus =
|
|
| 'pending'
|
|
| 'running'
|
|
| 'succeeded'
|
|
| 'failed'
|
|
| 'skipped'
|
|
| 'cancelled';
|
|
|
|
export async function platformFetch<T>(
|
|
path: string,
|
|
init: RequestInit,
|
|
opts: PlatformClientOptions
|
|
): Promise<T> {
|
|
const url = `${config.PLATFORM_SERVICE_URL}${path}`;
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
|
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
|
...(opts.productId ? { 'x-product-id': opts.productId } : {}),
|
|
};
|
|
const res = await fetch(url, {
|
|
...init,
|
|
headers: { ...headers, ...(init.headers ?? {}) },
|
|
signal: AbortSignal.timeout(10_000),
|
|
});
|
|
if (!res.ok) {
|
|
const body = await res.text().catch(() => '');
|
|
throw new Error(`platform-service ${init.method ?? 'GET'} ${path} → ${res.status}: ${body}`);
|
|
}
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
// ── Telemetry ─────────────────────────────────────────────────────────────────
|
|
|
|
export interface TelemetryQueryResult {
|
|
events: unknown[];
|
|
total: number;
|
|
continuationToken?: string;
|
|
}
|
|
|
|
export async function telemetryQuery(
|
|
params: {
|
|
productId: string;
|
|
eventType?: string;
|
|
from?: string;
|
|
to?: string;
|
|
limit?: number;
|
|
continuationToken?: string;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<TelemetryQueryResult> {
|
|
const qs = new URLSearchParams();
|
|
qs.set('productId', params.productId);
|
|
if (params.eventType) qs.set('eventType', params.eventType);
|
|
if (params.from) qs.set('from', params.from);
|
|
if (params.to) qs.set('to', params.to);
|
|
qs.set(
|
|
'limit',
|
|
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
|
|
);
|
|
if (params.continuationToken) qs.set('continuationToken', params.continuationToken);
|
|
return platformFetch<TelemetryQueryResult>(`/api/telemetry/query?${qs}`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
export interface TelemetryCluster {
|
|
/** Cluster doc ID: ${fingerprint}:${yyyyMM} */
|
|
id: string;
|
|
/** Partition key: ${productId}:${platform}:${module} */
|
|
pk: string;
|
|
fingerprint: string;
|
|
severity: 'warn' | 'error' | 'fatal';
|
|
totalCount: number;
|
|
lastSeenAt: string;
|
|
firstSeenAt: string;
|
|
platform: string;
|
|
module: string;
|
|
eventName: string;
|
|
sampleMessage?: string;
|
|
status: 'open' | 'resolved' | 'ignored';
|
|
resolvedBy?: string;
|
|
resolvedAt?: string;
|
|
}
|
|
|
|
export async function telemetryClusters(
|
|
params: { from?: string; to?: string; platform?: string; module?: string },
|
|
opts: PlatformClientOptions
|
|
): Promise<{ clusters: TelemetryCluster[]; total: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.from) qs.set('from', params.from);
|
|
if (params.to) qs.set('to', params.to);
|
|
if (params.platform) qs.set('platform', params.platform);
|
|
if (params.module) qs.set('module', params.module);
|
|
return platformFetch<{ clusters: TelemetryCluster[]; total: number }>(
|
|
`/api/telemetry/clusters?${qs}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function telemetryMetrics(opts: PlatformClientOptions): Promise<unknown> {
|
|
return platformFetch<unknown>('/api/telemetry/metrics', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export async function telemetryListPolicies(opts: PlatformClientOptions): Promise<unknown> {
|
|
return platformFetch<unknown>('/api/telemetry/policies', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export async function telemetryPreviewPolicy(
|
|
body: { targeting?: Record<string, unknown> },
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
'/api/telemetry/policies/preview',
|
|
{ method: 'POST', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function telemetryCreatePolicy(
|
|
body: Record<string, unknown>,
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
'/api/telemetry/policies',
|
|
{ method: 'POST', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function telemetryUpdatePolicy(
|
|
policyId: string,
|
|
body: Record<string, unknown>,
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
`/api/telemetry/policies/${encodeURIComponent(policyId)}`,
|
|
{ method: 'PUT', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function telemetryDeletePolicy(
|
|
policyId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ success: boolean }> {
|
|
return platformFetch<{ success: boolean }>(
|
|
`/api/telemetry/policies/${encodeURIComponent(policyId)}`,
|
|
{ method: 'DELETE' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function telemetryUpdateCluster(
|
|
clusterId: string,
|
|
pk: string,
|
|
status: 'open' | 'resolved' | 'ignored',
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
const qs = new URLSearchParams({ pk });
|
|
return platformFetch<unknown>(
|
|
`/api/telemetry/clusters/${encodeURIComponent(clusterId)}?${qs}`,
|
|
{ method: 'PATCH', body: JSON.stringify({ status }) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Runs ──────────────────────────────────────────────────────────────────────
|
|
|
|
export async function runsCreate(
|
|
body: {
|
|
id: string;
|
|
kind: RunKind;
|
|
name: string;
|
|
source: string;
|
|
triggeredBy?: string;
|
|
parentRunId?: string;
|
|
queueName?: string;
|
|
queueJobId?: string;
|
|
input?: Record<string, unknown>;
|
|
metadata?: Record<string, unknown>;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch('/api/runs', { method: 'POST', body: JSON.stringify(body) }, opts);
|
|
}
|
|
|
|
export async function runsUpdate(
|
|
runId: string,
|
|
body: {
|
|
status: RunStatus;
|
|
output?: Record<string, unknown>;
|
|
error?: string;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch(
|
|
`/api/runs/${encodeURIComponent(runId)}`,
|
|
{ method: 'PATCH', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function runStepsCreate(
|
|
runId: string,
|
|
body: {
|
|
stepName: string;
|
|
order: number;
|
|
input?: Record<string, unknown>;
|
|
metadata?: Record<string, unknown>;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch(
|
|
`/api/runs/${encodeURIComponent(runId)}/steps`,
|
|
{ method: 'POST', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function runStepsUpdate(
|
|
runId: string,
|
|
stepName: string,
|
|
body: {
|
|
status: RunStepStatus;
|
|
output?: Record<string, unknown>;
|
|
error?: string;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch(
|
|
`/api/runs/${encodeURIComponent(runId)}/steps/${encodeURIComponent(stepName)}`,
|
|
{ method: 'PATCH', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── AI Budgets ───────────────────────────────────────────────────────────────
|
|
|
|
export async function aiBudgetsRecordSpend(
|
|
body: {
|
|
scopeType: 'product' | 'agent';
|
|
scopeId: string;
|
|
policyId?: string;
|
|
agentId?: string;
|
|
agentVersionId?: string;
|
|
runId?: string;
|
|
evaluationRunId?: string;
|
|
model?: string;
|
|
tokensUsed?: number;
|
|
costUsd: number;
|
|
source?: string;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch(
|
|
'/api/ai-budgets/spend',
|
|
{ method: 'POST', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Support Cases ────────────────────────────────────────────────────────────
|
|
|
|
export async function supportCasesCreate(
|
|
body: {
|
|
orgId?: string;
|
|
workspaceId?: string;
|
|
requesterUserId?: string;
|
|
assignedTo?: string;
|
|
title: string;
|
|
description?: string;
|
|
priority?: 'critical' | 'high' | 'medium' | 'low';
|
|
source?: 'manual' | 'agent' | 'telemetry' | 'customer';
|
|
runId?: string;
|
|
reviewId?: string;
|
|
knowledgeBaseId?: string;
|
|
tags?: string[];
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch('/api/support/cases', { method: 'POST', body: JSON.stringify(body) }, opts);
|
|
}
|
|
|
|
// ── Diagnostics ───────────────────────────────────────────────────────────────
|
|
|
|
export interface DebugSession {
|
|
id: string;
|
|
productId: string;
|
|
status: string;
|
|
collectionLevel: string;
|
|
captureLogs: boolean;
|
|
captureNetwork: boolean;
|
|
captureScreenshots: boolean;
|
|
maxDurationMinutes: number;
|
|
logCount: number;
|
|
traceCount: number;
|
|
createdAt: string;
|
|
expiresAt: string;
|
|
targetUserId?: string;
|
|
targetAnonymousId?: string;
|
|
}
|
|
|
|
export async function diagnosticsListSessions(
|
|
params: {
|
|
productId?: string;
|
|
status?: string;
|
|
limit?: number;
|
|
offset?: number;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<{ sessions: DebugSession[]; total: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.productId) qs.set('productId', params.productId);
|
|
if (params.status) qs.set('status', params.status);
|
|
qs.set(
|
|
'limit',
|
|
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
|
|
);
|
|
if (params.offset) qs.set('offset', String(params.offset));
|
|
return platformFetch<{ sessions: DebugSession[]; total: number }>(
|
|
`/api/diagnostics/sessions?${qs}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Reviews ──────────────────────────────────────────────────────────────────
|
|
|
|
export async function reviewsCreate(
|
|
body: {
|
|
title: string;
|
|
description: string;
|
|
category: string;
|
|
priority?: 'low' | 'normal' | 'high' | 'urgent';
|
|
scope?: 'org' | 'workspace';
|
|
orgId: string;
|
|
workspaceId?: string;
|
|
assignedTo?: string;
|
|
runId?: string;
|
|
source: string;
|
|
actionType: string;
|
|
metadata?: Record<string, unknown>;
|
|
dueAt?: string;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch('/api/reviews', { method: 'POST', body: JSON.stringify(body) }, opts);
|
|
}
|
|
|
|
export async function diagnosticsCreateSession(
|
|
body: {
|
|
productId: string;
|
|
targetUserId?: string;
|
|
targetAnonymousId?: string;
|
|
collectionLevel?: 'standard' | 'debug' | 'trace';
|
|
captureLogs?: boolean;
|
|
captureNetwork?: boolean;
|
|
maxDurationMinutes?: number;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<DebugSession> {
|
|
return platformFetch<DebugSession>(
|
|
'/api/diagnostics/sessions',
|
|
{ method: 'POST', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function diagnosticsGetSession(
|
|
sessionId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<DebugSession> {
|
|
return platformFetch<DebugSession>(
|
|
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function diagnosticsUpdateSession(
|
|
sessionId: string,
|
|
body: { status?: string; collectionLevel?: string; maxDurationMinutes?: number },
|
|
opts: PlatformClientOptions
|
|
): Promise<DebugSession> {
|
|
return platformFetch<DebugSession>(
|
|
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}`,
|
|
{ method: 'PATCH', body: JSON.stringify(body) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function diagnosticsGetLogs(
|
|
sessionId: string,
|
|
params: { level?: string; from?: string; to?: string; limit?: number },
|
|
opts: PlatformClientOptions
|
|
): Promise<{ logs: unknown[]; continuationToken?: string }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.level) qs.set('level', params.level);
|
|
if (params.from) qs.set('from', params.from);
|
|
if (params.to) qs.set('to', params.to);
|
|
qs.set(
|
|
'limit',
|
|
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
|
|
);
|
|
return platformFetch<{ logs: unknown[]; continuationToken?: string }>(
|
|
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}/logs?${qs}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function diagnosticsGetTraces(
|
|
sessionId: string,
|
|
params: { limit?: number; continuationToken?: string },
|
|
opts: PlatformClientOptions
|
|
): Promise<{ traces: unknown[]; continuationToken?: string }> {
|
|
const qs = new URLSearchParams();
|
|
qs.set(
|
|
'limit',
|
|
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
|
|
);
|
|
if (params.continuationToken) qs.set('continuationToken', params.continuationToken);
|
|
return platformFetch<{ traces: unknown[]; continuationToken?: string }>(
|
|
`/api/diagnostics/sessions/${encodeURIComponent(sessionId)}/traces?${qs}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Tracker — items ───────────────────────────────────────────────────────────
|
|
|
|
export interface TrackerItemDoc {
|
|
id: string;
|
|
productId: string;
|
|
type: string;
|
|
status: string;
|
|
priority: string;
|
|
title: string;
|
|
description?: string;
|
|
labels?: string[];
|
|
assignee?: string | null;
|
|
reportedBy?: string;
|
|
source?: string;
|
|
visibility?: string;
|
|
voteCount: number;
|
|
commentCount: number;
|
|
targetRelease?: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export function trackerItemsList(
|
|
params: {
|
|
productId?: string;
|
|
type?: string;
|
|
status?: string;
|
|
priority?: string;
|
|
q?: string;
|
|
labels?: string;
|
|
visibility?: string;
|
|
sortBy?: string;
|
|
sortOrder?: string;
|
|
limit?: number;
|
|
offset?: number;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<{ items: TrackerItemDoc[]; total: number; limit: number; offset: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.productId) qs.set('productId', params.productId);
|
|
if (params.type) qs.set('type', params.type);
|
|
if (params.status) qs.set('status', params.status);
|
|
if (params.priority) qs.set('priority', params.priority);
|
|
if (params.q) qs.set('q', params.q);
|
|
if (params.labels) qs.set('labels', params.labels);
|
|
if (params.visibility) qs.set('visibility', params.visibility);
|
|
if (params.sortBy) qs.set('sortBy', params.sortBy);
|
|
if (params.sortOrder) qs.set('sortOrder', params.sortOrder);
|
|
qs.set(
|
|
'limit',
|
|
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
|
|
);
|
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
return platformFetch<{ items: TrackerItemDoc[]; total: number; limit: number; offset: number }>(
|
|
`/api/items?${qs}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function trackerItemsStats(opts: PlatformClientOptions): Promise<{
|
|
total: number;
|
|
byType: Record<string, number>;
|
|
byStatus: Record<string, number>;
|
|
byPriority: Record<string, number>;
|
|
}> {
|
|
return platformFetch<{
|
|
total: number;
|
|
byType: Record<string, number>;
|
|
byStatus: Record<string, number>;
|
|
byPriority: Record<string, number>;
|
|
}>('/api/items/stats', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function trackerItemsGet(
|
|
itemId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<TrackerItemDoc> {
|
|
return platformFetch<TrackerItemDoc>(
|
|
`/api/items/${encodeURIComponent(itemId)}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function trackerItemsCreate(
|
|
input: {
|
|
productId?: string;
|
|
type: string;
|
|
priority: string;
|
|
title: string;
|
|
description?: string;
|
|
labels?: string[];
|
|
assignee?: string;
|
|
source?: string;
|
|
visibility?: string;
|
|
targetRelease?: string;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<TrackerItemDoc> {
|
|
return platformFetch<TrackerItemDoc>(
|
|
'/api/items',
|
|
{ method: 'POST', body: JSON.stringify(input) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function trackerItemsUpdateStatus(
|
|
itemId: string,
|
|
status: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<TrackerItemDoc> {
|
|
return platformFetch<TrackerItemDoc>(
|
|
`/api/items/${encodeURIComponent(itemId)}/status`,
|
|
{ method: 'PATCH', body: JSON.stringify({ status }) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function trackerItemsDelete(
|
|
itemId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ success: boolean }> {
|
|
return platformFetch<{ success: boolean }>(
|
|
`/api/items/${encodeURIComponent(itemId)}`,
|
|
{ method: 'DELETE' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Tracker — votes ───────────────────────────────────────────────────────────
|
|
|
|
export function trackerVotesToggle(
|
|
itemId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ voted: boolean; voteCount: number }> {
|
|
return platformFetch<{ voted: boolean; voteCount: number }>(
|
|
`/api/items/${encodeURIComponent(itemId)}/vote`,
|
|
{ method: 'POST', body: '{}' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function trackerVotesList(
|
|
itemId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ votes: unknown[]; count: number }> {
|
|
return platformFetch<{ votes: unknown[]; count: number }>(
|
|
`/api/items/${encodeURIComponent(itemId)}/votes`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Tracker — comments ────────────────────────────────────────────────────────
|
|
|
|
export function trackerCommentsList(
|
|
itemId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ comments: unknown[]; count: number }> {
|
|
return platformFetch<{ comments: unknown[]; count: number }>(
|
|
`/api/items/${encodeURIComponent(itemId)}/comments`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function trackerCommentsAdd(
|
|
itemId: string,
|
|
body: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
`/api/items/${encodeURIComponent(itemId)}/comments`,
|
|
{ method: 'POST', body: JSON.stringify({ body }) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function trackerCommentsDelete(
|
|
itemId: string,
|
|
commentId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ success: boolean }> {
|
|
return platformFetch<{ success: boolean }>(
|
|
`/api/items/${encodeURIComponent(itemId)}/comments/${encodeURIComponent(commentId)}`,
|
|
{ method: 'DELETE' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Tracker — public roadmap ──────────────────────────────────────────────────
|
|
|
|
export function trackerPublicRoadmap(
|
|
params: {
|
|
productId?: string;
|
|
type?: string;
|
|
status?: string;
|
|
q?: string;
|
|
sortBy?: string;
|
|
sortOrder?: string;
|
|
limit?: number;
|
|
offset?: number;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<{ items: TrackerItemDoc[]; total: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.productId) qs.set('productId', params.productId);
|
|
if (params.type) qs.set('type', params.type);
|
|
if (params.status) qs.set('status', params.status);
|
|
if (params.q) qs.set('q', params.q);
|
|
if (params.sortBy) qs.set('sortBy', params.sortBy);
|
|
if (params.sortOrder) qs.set('sortOrder', params.sortOrder);
|
|
qs.set(
|
|
'limit',
|
|
String(Math.min(params.limit ?? config.QUERY_DEFAULT_LIMIT, config.QUERY_MAX_LIMIT))
|
|
);
|
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
return platformFetch<{ items: TrackerItemDoc[]; total: number }>(
|
|
`/api/public/roadmap?${qs}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function trackerPublicStats(opts: PlatformClientOptions): Promise<{
|
|
total: number;
|
|
byStatus: Record<string, number>;
|
|
byType: Record<string, number>;
|
|
totalVotes: number;
|
|
}> {
|
|
return platformFetch<{
|
|
total: number;
|
|
byStatus: Record<string, number>;
|
|
byType: Record<string, number>;
|
|
totalVotes: number;
|
|
}>('/api/public/roadmap/stats', { method: 'GET' }, opts);
|
|
}
|
|
|
|
// ── Flags ─────────────────────────────────────────────────────────────────────
|
|
|
|
export interface FeatureFlagDoc {
|
|
id: string;
|
|
productId: string;
|
|
key: string;
|
|
enabled: boolean;
|
|
percentage: number;
|
|
description?: string;
|
|
platforms: string[];
|
|
regions?: string[];
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export function flagsList(opts: PlatformClientOptions): Promise<{ flags: FeatureFlagDoc[] }> {
|
|
return platformFetch<{ flags: FeatureFlagDoc[] }>('/api/flags', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function flagsGet(key: string, opts: PlatformClientOptions): Promise<FeatureFlagDoc> {
|
|
return platformFetch<FeatureFlagDoc>(
|
|
`/api/flags/${encodeURIComponent(key)}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export async function flagsUpsert(
|
|
key: string,
|
|
input: {
|
|
enabled: boolean;
|
|
percentage?: number;
|
|
description?: string;
|
|
platforms?: string[];
|
|
regions?: string[];
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<FeatureFlagDoc> {
|
|
// Try update first; fall back to create if not found
|
|
try {
|
|
return await platformFetch<FeatureFlagDoc>(
|
|
`/api/flags/${encodeURIComponent(key)}`,
|
|
{ method: 'PUT', body: JSON.stringify(input) },
|
|
opts
|
|
);
|
|
} catch (err) {
|
|
if (err instanceof Error && err.message.includes('404')) {
|
|
return platformFetch<FeatureFlagDoc>(
|
|
'/api/flags',
|
|
{ method: 'POST', body: JSON.stringify({ key, ...input }) },
|
|
opts
|
|
);
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
export function flagsDelete(
|
|
key: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ success: boolean }> {
|
|
return platformFetch<{ success: boolean }>(
|
|
`/api/flags/${encodeURIComponent(key)}`,
|
|
{ method: 'DELETE' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function flagsKillSwitch(
|
|
input: { platform?: string; keys?: string[] },
|
|
opts: PlatformClientOptions
|
|
): Promise<{ disabled: string[]; count: number }> {
|
|
return platformFetch<{ disabled: string[]; count: number }>(
|
|
'/api/flags/kill',
|
|
{ method: 'POST', body: JSON.stringify(input) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Jobs ──────────────────────────────────────────────────────────────────────
|
|
|
|
export function jobsList(opts: PlatformClientOptions): Promise<unknown[]> {
|
|
return platformFetch<unknown[]>('/api/jobs', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function jobsGet(id: string, opts: PlatformClientOptions): Promise<unknown> {
|
|
return platformFetch<unknown>(`/api/jobs/${encodeURIComponent(id)}`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function jobsTrigger(jobName: string, opts: PlatformClientOptions): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
'/api/jobs/trigger',
|
|
{ method: 'POST', body: JSON.stringify({ jobName }) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function jobsListRuns(
|
|
name: string,
|
|
limit: number,
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
`/api/jobs/${encodeURIComponent(name)}/runs?limit=${limit}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Maintenance ───────────────────────────────────────────────────────────────
|
|
|
|
export function maintenanceGetCurrent(opts: PlatformClientOptions): Promise<unknown> {
|
|
return platformFetch<unknown>('/api/settings/maintenance/full', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function maintenanceSet(
|
|
input: {
|
|
mode: 'none' | 'scheduled' | 'active' | 'emergency';
|
|
message?: string;
|
|
affectedServices?: string[];
|
|
scheduledStart?: string;
|
|
scheduledEnd?: string;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
'/api/settings/maintenance',
|
|
{ method: 'PUT', body: JSON.stringify(input) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function maintenanceScheduleWindow(
|
|
input: {
|
|
title: string;
|
|
message: string;
|
|
mode: string;
|
|
scheduledStart: string;
|
|
scheduledEnd: string;
|
|
affectedServices?: string[];
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
'/api/settings/maintenance/schedule',
|
|
{ method: 'POST', body: JSON.stringify(input) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function maintenanceGetSchedule(opts: PlatformClientOptions): Promise<unknown[]> {
|
|
return platformFetch<unknown[]>('/api/settings/maintenance/schedule', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function maintenanceDeleteWindow(
|
|
windowId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ success: boolean }> {
|
|
return platformFetch<{ success: boolean }>(
|
|
`/api/settings/maintenance/schedule/${encodeURIComponent(windowId)}`,
|
|
{ method: 'DELETE' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Settings ──────────────────────────────────────────────────────────────────────────────────
|
|
|
|
export function settingsGet(opts: PlatformClientOptions): Promise<unknown> {
|
|
return platformFetch<unknown>('/api/settings', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function settingsUpdate(
|
|
settings: Record<string, unknown>,
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
'/api/settings',
|
|
{ method: 'PUT', body: JSON.stringify({ settings }) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function settingsCheckKillSwitch(
|
|
productId: string,
|
|
opts: Pick<PlatformClientOptions, 'requestId'>
|
|
): Promise<{ enabled: boolean; disabled: boolean; message: string }> {
|
|
return platformFetch<{ enabled: boolean; disabled: boolean; message: string }>(
|
|
`/api/settings/kill-switch?productId=${encodeURIComponent(productId)}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Webhooks ──────────────────────────────────────────────────────────────────
|
|
|
|
export function webhooksListSubscriptions(
|
|
productId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown[]> {
|
|
return platformFetch<unknown[]>(
|
|
`/api/webhooks/subscriptions?productId=${encodeURIComponent(productId)}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function webhooksCreate(
|
|
input: {
|
|
productId: string;
|
|
url: string;
|
|
events: string[];
|
|
description?: string;
|
|
enabled?: boolean;
|
|
},
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
'/api/webhooks/subscriptions',
|
|
{ method: 'POST', body: JSON.stringify(input) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function webhooksGet(
|
|
id: string,
|
|
productId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
`/api/webhooks/subscriptions/${encodeURIComponent(id)}?productId=${encodeURIComponent(productId)}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function webhooksUpdate(
|
|
id: string,
|
|
productId: string,
|
|
input: { url?: string; events?: string[]; enabled?: boolean; description?: string },
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
`/api/webhooks/subscriptions/${encodeURIComponent(id)}?productId=${encodeURIComponent(productId)}`,
|
|
{ method: 'PATCH', body: JSON.stringify(input) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function webhooksDelete(
|
|
id: string,
|
|
productId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ success: boolean }> {
|
|
return platformFetch<{ success: boolean }>(
|
|
`/api/webhooks/subscriptions/${encodeURIComponent(id)}?productId=${encodeURIComponent(productId)}`,
|
|
{ method: 'DELETE' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function webhooksListDeliveries(
|
|
id: string,
|
|
limit: number,
|
|
opts: PlatformClientOptions
|
|
): Promise<unknown> {
|
|
return platformFetch<unknown>(
|
|
`/api/webhooks/subscriptions/${encodeURIComponent(id)}/deliveries?limit=${limit}`,
|
|
{ method: 'GET' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function webhooksTest(
|
|
id: string,
|
|
productId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ success: boolean; message: string }> {
|
|
return platformFetch<{ success: boolean; message: string }>(
|
|
`/api/webhooks/subscriptions/${encodeURIComponent(id)}/test?productId=${encodeURIComponent(productId)}`,
|
|
{ method: 'POST', body: '{}' },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function webhooksRotateSecret(
|
|
id: string,
|
|
productId: string,
|
|
opts: PlatformClientOptions
|
|
): Promise<{ secret: string; message: string }> {
|
|
return platformFetch<{ secret: string; message: string }>(
|
|
`/api/webhooks/subscriptions/${encodeURIComponent(id)}/rotate-secret?productId=${encodeURIComponent(productId)}`,
|
|
{ method: 'POST', body: '{}' },
|
|
opts
|
|
);
|
|
}
|