Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {Dependencies} from './resources/dependencies.js'
import {DeployLock} from './resources/deploy-lock.js'
import {Status} from './resources/status.js'
import {StatusPages} from './resources/status-pages.js'
import {MaintenanceWindows} from './resources/maintenance-windows.js'

/**
* DevHelm API client.
Expand Down Expand Up @@ -50,6 +51,7 @@ export class Devhelm {
readonly deployLock: DeployLock
readonly status: Status
readonly statusPages: StatusPages
readonly maintenanceWindows: MaintenanceWindows

constructor(config: DevhelmConfig) {
const client = buildClient(config)
Expand All @@ -68,5 +70,6 @@ export class Devhelm {
this.deployLock = new DeployLock(client)
this.status = new Status(client)
this.statusPages = new StatusPages(client)
this.maintenanceWindows = new MaintenanceWindows(client)
}
}
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export type {
DeployLockDto,
AssertionTestResultDto,
MonitorTestResultDto,
MaintenanceWindowDto,
IncidentTimelineDto,
CheckTraceDto,
PolicySnapshotDto,
Expand All @@ -60,6 +61,8 @@ export type {
UpdateWebhookEndpointRequest,
CreateApiKeyRequest,
AcquireDeployLockRequest,
CreateMaintenanceWindowRequest,
UpdateMaintenanceWindowRequest,
StatusPageDto,
StatusPageComponentDto,
StatusPageComponentGroupDto,
Expand Down Expand Up @@ -109,3 +112,5 @@ export {Dependencies} from './resources/dependencies.js'
export {DeployLock} from './resources/deploy-lock.js'
export {Status} from './resources/status.js'
export {StatusPages} from './resources/status-pages.js'
export {MaintenanceWindows} from './resources/maintenance-windows.js'
export type {MaintenanceWindowFilters} from './resources/maintenance-windows.js'
157 changes: 157 additions & 0 deletions src/resources/maintenance-windows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import type {ApiClient} from '../http.js'
import type {
MaintenanceWindowDto,
CreateMaintenanceWindowRequest,
UpdateMaintenanceWindowRequest,
Page,
} from '../types.js'
import {
MaintenanceWindowDtoSchema,
CreateMaintenanceWindowRequestSchema,
UpdateMaintenanceWindowRequestSchema,
} from '../schemas.js'
import {apiGet, fetchSingle, fetchVoid} from '../http.js'
import {parsePage, validateRequest} from '../validation.js'

/**
* Filters for listing maintenance windows.
*
* - `monitorId` scopes results to a single monitor.
* - `filter` is a server-side status bucket: `"active"` for windows currently
* in progress, `"upcoming"` for windows whose `startsAt` is in the future.
* Omit to return every window in the org regardless of status.
*/
export interface MaintenanceWindowFilters {
monitorId?: string
filter?: 'active' | 'upcoming'
}

/**
* Schedule planned downtime so DevHelm suppresses alerts during the window.
*
* Maintenance windows are scoped to a single monitor (set `monitorId`) or
* org-wide (leave `monitorId` unset / `null`). Recurring windows are
* expressed via an iCal RRULE in `repeatRule`.
*/
export class MaintenanceWindows {
constructor(private readonly client: ApiClient) {}

/**
* List maintenance windows for the calling org. Auto-paginates through
* every page so callers receive the full set in a single array.
*
* @example
* ```ts
* const windows = await client.maintenanceWindows.list({filter: 'upcoming'})
* ```
*/
async list(filters: MaintenanceWindowFilters = {}): Promise<MaintenanceWindowDto[]> {
const path = '/api/v1/maintenance-windows'
const baseQuery: Record<string, unknown> = {}
if (filters.monitorId !== undefined) baseQuery['monitorId'] = filters.monitorId
if (filters.filter !== undefined) baseQuery['filter'] = filters.filter

const all: MaintenanceWindowDto[] = []
let page = 0
const size = 200
while (true) {
const raw = await apiGet(this.client, path, {...baseQuery, page, size})
const validated = parsePage(MaintenanceWindowDtoSchema, raw, path)
all.push(...validated.data)
if (!validated.hasNext) break
page++
}
return all
}

/**
* List maintenance windows with manual page control. Useful for callers
* that render large windows in a paginated UI.
*
* @example
* ```ts
* const page = await client.maintenanceWindows.listPage(0, 50)
* ```
*/
async listPage(
page: number,
size: number,
filters: MaintenanceWindowFilters = {},
): Promise<Page<MaintenanceWindowDto>> {
const path = '/api/v1/maintenance-windows'
const query: Record<string, unknown> = {page, size}
if (filters.monitorId !== undefined) query['monitorId'] = filters.monitorId
if (filters.filter !== undefined) query['filter'] = filters.filter
const raw = await apiGet(this.client, path, query)
const validated = parsePage(MaintenanceWindowDtoSchema, raw, path)
return {
data: validated.data,
hasNext: validated.hasNext,
hasPrev: validated.hasPrev,
totalElements: validated.totalElements ?? null,
totalPages: validated.totalPages ?? null,
}
}

/**
* Fetch a single maintenance window by UUID.
*
* @example
* ```ts
* const window = await client.maintenanceWindows.get(id)
* ```
*/
async get(id: string): Promise<MaintenanceWindowDto> {
return fetchSingle(this.client, 'GET', `/api/v1/maintenance-windows/${id}`, MaintenanceWindowDtoSchema)
}

/**
* Schedule a new maintenance window. Set `monitorId` to `null` (or omit it)
* to create an org-wide window that suppresses every monitor's alerts.
*
* @example
* ```ts
* const window = await client.maintenanceWindows.create({
* monitorId: '6f1a...',
* startsAt: '2026-06-01T03:00:00Z',
* endsAt: '2026-06-01T04:00:00Z',
* reason: 'Quarterly db migration',
* })
* ```
*/
async create(body: CreateMaintenanceWindowRequest): Promise<MaintenanceWindowDto> {
validateRequest(CreateMaintenanceWindowRequestSchema, body, 'maintenanceWindows.create')
return fetchSingle(this.client, 'POST', '/api/v1/maintenance-windows', MaintenanceWindowDtoSchema, body)
}

/**
* Update an existing maintenance window — adjust the start/end, swap the
* monitor it covers, or change the recurrence rule.
*
* @example
* ```ts
* await client.maintenanceWindows.update(id, {
* startsAt: '2026-06-01T04:00:00Z',
* endsAt: '2026-06-01T05:00:00Z',
* })
* ```
*/
async update(id: string, body: UpdateMaintenanceWindowRequest): Promise<MaintenanceWindowDto> {
validateRequest(UpdateMaintenanceWindowRequestSchema, body, 'maintenanceWindows.update')
return fetchSingle(this.client, 'PUT', `/api/v1/maintenance-windows/${id}`, MaintenanceWindowDtoSchema, body)
}

/**
* Cancel (delete) a maintenance window. After this call, alerts will fire
* normally for the affected monitor — even if the window's `endsAt` is
* still in the future.
*
* @example
* ```ts
* await client.maintenanceWindows.cancel(id)
* ```
*/
async cancel(id: string): Promise<void> {
return fetchVoid(this.client, `/api/v1/maintenance-windows/${id}`)
}
}
5 changes: 5 additions & 0 deletions src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const DashboardOverviewDtoSchema = schemas.DashboardOverviewDto
export const DeployLockDtoSchema = schemas.DeployLockDto
export const AssertionTestResultDtoSchema = schemas.AssertionTestResultDto
export const MonitorTestResultDtoSchema = schemas.MonitorTestResultDto
export const MaintenanceWindowDtoSchema = schemas.MaintenanceWindowDto

// ── Forensic DTO schemas ────────────────────────────────────────────

Expand Down Expand Up @@ -75,6 +76,8 @@ export const CreateWebhookEndpointRequestSchema = schemas.CreateWebhookEndpointR
export const UpdateWebhookEndpointRequestSchema = schemas.UpdateWebhookEndpointRequest
export const CreateApiKeyRequestSchema = schemas.CreateApiKeyRequest
export const AcquireDeployLockRequestSchema = schemas.AcquireDeployLockRequest
export const CreateMaintenanceWindowRequestSchema = schemas.CreateMaintenanceWindowRequest
export const UpdateMaintenanceWindowRequestSchema = schemas.UpdateMaintenanceWindowRequest

// ── Status Page Request schemas ─────────────────────────────────────

Expand Down Expand Up @@ -124,6 +127,7 @@ export const SingleValueResponseServiceSubscriptionDtoSchema = schemas.SingleVal
export const SingleValueResponseDashboardOverviewDtoSchema = schemas.SingleValueResponseDashboardOverviewDto
export const SingleValueResponseDeployLockDtoSchema = schemas.SingleValueResponseDeployLockDto
export const SingleValueResponseMonitorTestResultDtoSchema = schemas.SingleValueResponseMonitorTestResultDto
export const SingleValueResponseMaintenanceWindowDtoSchema = schemas.SingleValueResponseMaintenanceWindowDto
export const SingleValueResponseStatusPageDtoSchema = schemas.SingleValueResponseStatusPageDto
export const SingleValueResponseStatusPageComponentDtoSchema = schemas.SingleValueResponseStatusPageComponentDto
export const SingleValueResponseStatusPageComponentGroupDtoSchema = schemas.SingleValueResponseStatusPageComponentGroupDto
Expand All @@ -147,6 +151,7 @@ export const TableValueResultWebhookEndpointDtoSchema = schemas.TableValueResult
export const TableValueResultApiKeyDtoSchema = schemas.TableValueResultApiKeyDto
export const TableValueResultServiceSubscriptionDtoSchema = schemas.TableValueResultServiceSubscriptionDto
export const TableValueResultMonitorVersionDtoSchema = schemas.TableValueResultMonitorVersionDto
export const TableValueResultMaintenanceWindowDtoSchema = schemas.TableValueResultMaintenanceWindowDto
export const TableValueResultStatusPageDtoSchema = schemas.TableValueResultStatusPageDto
export const TableValueResultStatusPageComponentDtoSchema = schemas.TableValueResultStatusPageComponentDto
export const TableValueResultStatusPageComponentGroupDtoSchema = schemas.TableValueResultStatusPageComponentGroupDto
Expand Down
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type DashboardOverviewDto = z.infer<typeof S.DashboardOverviewDtoSchema>
export type DeployLockDto = z.infer<typeof S.DeployLockDtoSchema>
export type AssertionTestResultDto = z.infer<typeof S.AssertionTestResultDtoSchema>
export type MonitorTestResultDto = z.infer<typeof S.MonitorTestResultDtoSchema>
export type MaintenanceWindowDto = z.infer<typeof S.MaintenanceWindowDtoSchema>

// ── Forensic DTOs ─────────────────────────────────────────────────────

Expand Down Expand Up @@ -86,6 +87,8 @@ export type CreateWebhookEndpointRequest = z.infer<typeof S.CreateWebhookEndpoin
export type UpdateWebhookEndpointRequest = z.infer<typeof S.UpdateWebhookEndpointRequestSchema>
export type CreateApiKeyRequest = z.infer<typeof S.CreateApiKeyRequestSchema>
export type AcquireDeployLockRequest = z.infer<typeof S.AcquireDeployLockRequestSchema>
export type CreateMaintenanceWindowRequest = z.infer<typeof S.CreateMaintenanceWindowRequestSchema>
export type UpdateMaintenanceWindowRequest = z.infer<typeof S.UpdateMaintenanceWindowRequestSchema>

// ── Status Page Request types ─────────────────────────────────────────

Expand Down
1 change: 1 addition & 0 deletions test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('Devhelm client', () => {
expect(client.deployLock).toBeDefined()
expect(client.status).toBeDefined()
expect(client.statusPages).toBeDefined()
expect(client.maintenanceWindows).toBeDefined()
})

it('resource modules have expected CRUD methods', () => {
Expand Down
Loading
Loading