diff --git a/scripts/lib/openapi-docs.mjs b/scripts/lib/openapi-docs.mjs index 7ac3046..a8bc19a 100644 --- a/scripts/lib/openapi-docs.mjs +++ b/scripts/lib/openapi-docs.mjs @@ -221,8 +221,22 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { const resp = rawResp.$ref ? resolveRef(rawResp.$ref) : rawResp; const media = jsonBody(resp); - // An example authored on the response wins outright — it shows the whole - // envelope, including any `meta`, exactly as the API returns it. + // Examples authored on the response win outright — they show the whole + // envelope, including any `meta`, exactly as the API returns it. Where a + // spec authors several, each is one the others cannot stand in for: a + // configured resource beside its unset default, a full page beside an + // empty one. They render in spec order, since `default` is only a key + // here and does not mean "the representative one". + const named = media?.examples; + if (named && typeof named === "object") { + const authoredAll = Object.entries(named).filter( + ([, entry]) => entry && "value" in entry + ); + if (authoredAll.length > 1) { + return exampleBlocks(successCode, authoredAll); + } + } + const authored = mediaTypeExample(media); if (authored !== undefined) { return exampleBlock(successCode, authored); @@ -266,6 +280,19 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { return `\n**Example response \`${code}\`**\n\n\`\`\`json\n${JSON.stringify(body, null, 2)}\n\`\`\`\n`; } + // Several authored examples under one heading, each captioned with its + // summary so the reader can see which case it is. + function exampleBlocks(code, entries) { + const blocks = entries + .map(([key, entry]) => { + const label = escapeMarkdown(entry.summary ?? key); + const body = JSON.stringify(entry.value, null, 2); + return `\n_${label}_\n\n\`\`\`json\n${body}\n\`\`\`\n`; + }) + .join(""); + return `\n**Example responses \`${code}\`**\n${blocks}`; + } + // ------------------------------------------------------------------------- // Types and constraints // ------------------------------------------------------------------------- diff --git a/site/dolt/src/content/products/hosted/api/v1/README.md b/site/dolt/src/content/products/hosted/api/v1/README.md index 12fbaba..e6de340 100644 --- a/site/dolt/src/content/products/hosted/api/v1/README.md +++ b/site/dolt/src/content/products/hosted/api/v1/README.md @@ -53,6 +53,7 @@ See [Authentication](/products/hosted/api/v1/authentication) for how to create a | **PATCH** | `/api/v1/deployments/{owner}/{deployment}/config` | [Change some of a deployment's configuration overrides](/products/hosted/api/v1/deployment#patchDeploymentConfig) | | **GET** | `/api/v1/deployments/{owner}/{deployment}/logs` | [Read a deployment's logs](/products/hosted/api/v1/deployment#getDeploymentLogs) | | **PATCH** | `/api/v1/deployments/{owner}/{deployment}/expose` | [Expose or stop exposing the remotesapi or MCP endpoint](/products/hosted/api/v1/deployment#exposeDeploymentService) | +| **GET** | `/api/v1/deployments/{owner}/{deployment}/service-windows` | [List a deployment's service windows](/products/hosted/api/v1/deployment#listDeploymentServiceWindows) | | **GET** | `/api/v1/deployments/{owner}/{deployment}/metrics` | [List a deployment's metrics](/products/hosted/api/v1/deployment#listDeploymentMetrics) | | **GET** | `/api/v1/deployments/{owner}/{deployment}/metrics/{metric}` | [Read one of a deployment's metrics](/products/hosted/api/v1/deployment#getDeploymentMetric) | | **GET** | `/api/v1/deployments/{owner}/{deployment}/backups` | [List a deployment's backups](/products/hosted/api/v1/deployment#listDeploymentBackups) | @@ -88,7 +89,7 @@ List endpoints put the pagination cursor in `meta`: When `meta.next_page_token` is present, pass it back as the `page_token` query parameter to fetch the next page. On the last page `meta` is omitted entirely, so checking whether the token is present is all a client needs — it is never returned present but empty. Page size is fixed and not caller-controlled, so a full page is not itself a sign that another one follows. -Two kinds of list depart from that. A pull request's [comments](/products/hosted/api/v1/pull-request#listDeploymentPullComments) and its [activity log](/products/hosted/api/v1/pull-request#listDeploymentPullLogs), and a deployment's [metrics catalogue](/products/hosted/api/v1/deployment#listDeploymentMetrics), are small enough by nature to be returned whole, so they take no `page_token` at all. And [log retrieval](/products/hosted/api/v1/deployment#getDeploymentLogs) walks a window of history rather than a finite list: it is the only endpoint that pages in both directions, and the only one whose page size you set (`lines`). There `meta.next_page_token` reads further back, `meta.prev_page_token` reads toward the present, both can be present at once, and either can come back on a page with no lines — so stop when a page comes back empty, not when a token is missing. +Two kinds of list depart from that. A pull request's [comments](/products/hosted/api/v1/pull-request#listDeploymentPullComments) and its [activity log](/products/hosted/api/v1/pull-request#listDeploymentPullLogs), and a deployment's [metrics catalogue](/products/hosted/api/v1/deployment#listDeploymentMetrics) and [service windows](/products/hosted/api/v1/deployment#listDeploymentServiceWindows), are small enough by nature to be returned whole, so they take no `page_token` at all. And [log retrieval](/products/hosted/api/v1/deployment#getDeploymentLogs) walks a window of history rather than a finite list: it is the only endpoint that pages in both directions, and the only one whose page size you set (`lines`). There `meta.next_page_token` reads further back, `meta.prev_page_token` reads toward the present, both can be present at once, and either can come back on a page with no lines — so stop when a page comes back empty, not when a token is missing. Each endpoint's parameters say which of the three it is. diff --git a/site/dolt/src/content/products/hosted/api/v1/deployment.md b/site/dolt/src/content/products/hosted/api/v1/deployment.md index af847b1..7bb98de 100644 --- a/site/dolt/src/content/products/hosted/api/v1/deployment.md +++ b/site/dolt/src/content/products/hosted/api/v1/deployment.md @@ -753,7 +753,9 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/l | `500` | An unexpected server error occurred. | [`Problem`](/products/hosted/api/v1/models#model-problem) | | `503` | The service is temporarily unavailable. | [`Problem`](/products/hosted/api/v1/models#model-problem) | -**Example response `200`** +**Example responses `200`** + +_Two lines with more history available._ ```json { @@ -773,6 +775,14 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/l } ``` +_A window with nothing in it._ + +```json +{ + "data": [] +} +``` + --- ## Expose or stop exposing a deployment's remotesapi or MCP endpoint {#exposeDeploymentService} @@ -850,6 +860,80 @@ _Stop serving the remotesapi endpoint._ --- +## List a deployment's service windows {#listDeploymentServiceWindows} +GET /api/v1/deployments/{owner}/{deployment}/service-windows + +Returns the weekly windows in which Hosted may restart the deployment's instances to apply maintenance. + +A window covers whole hours in UTC on one day of the week. `start_hour_utc` is inclusive and `end_hour_utc` is exclusive, so 7 and 8 mean the hour beginning 07:00 UTC. + +Every deployment has at least one. Until one is set, the list holds a single window with `is_default` set: Sunday 07:00 to 08:00 UTC. + + +**Parameters** + +| Name | In | Type | Required | Description | +|------|----|------|----------|-------------| +| `owner` | path | string | yes | The user or organization that owns the deployment. 3–32 characters of letters, digits, hyphens, and underscores. | +| `deployment` | path | string | yes | The deployment name, unique within the owner. 3–32 characters of letters, digits, hyphens, and underscores. | + +**Example request** + +```sh +curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/service-windows' \ + -H 'Authorization: Bearer YOUR_TOKEN' +``` + +**Responses** + +| Status | Description | Schema | +|--------|-------------|--------| +| `200` | The deployment's service windows. | [`ServiceWindow[]`](/products/hosted/api/v1/models#model-servicewindow) | +| `400` | The request was malformed or failed input validation. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `401` | Authentication credentials were missing or invalid. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `403` | Authenticated, but not permitted to perform this action. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `404` | The requested resource does not exist. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `405` | The HTTP method is not supported for this resource. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `422` | The request was well-formed but semantically invalid. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `500` | An unexpected server error occurred. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `503` | The service is temporarily unavailable. | [`Problem`](/products/hosted/api/v1/models#model-problem) | + +**Example responses `200`** + +_A window set for early Tuesday morning UTC._ + +```json +{ + "data": [ + { + "id": "7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d", + "day_of_week": "tuesday", + "start_hour_utc": 3, + "end_hour_utc": 5, + "is_default": false + } + ] +} +``` + +_A deployment with no window configured._ + +```json +{ + "data": [ + { + "id": "00000000-0000-0000-0000-000000000000", + "day_of_week": "sunday", + "start_hour_utc": 7, + "end_hour_utc": 8, + "is_default": true + } + ] +} +``` + +--- + ## Disable a deployment {#disableDeployment} POST /api/v1/deployments/{owner}/{deployment}/disable diff --git a/site/dolt/src/content/products/hosted/api/v1/models.md b/site/dolt/src/content/products/hosted/api/v1/models.md index 8b547e2..186ecb5 100644 --- a/site/dolt/src/content/products/hosted/api/v1/models.md +++ b/site/dolt/src/content/products/hosted/api/v1/models.md @@ -251,6 +251,36 @@ One metric a deployment collects. Read it with `GET /api/v1/deployments/{owner}/ --- +## DayOfWeek {#model-dayofweek} +The day of the week a service window falls on, in UTC. + +**Enum values** + +| Value | +|-------| +| `sunday` | +| `monday` | +| `tuesday` | +| `wednesday` | +| `thursday` | +| `friday` | +| `saturday` | + +--- + +## ServiceWindow {#model-servicewindow} +One weekly window in which Hosted may restart the deployment's instances for maintenance. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | `string` | yes | The window's identifier, unique within the deployment. A default window has no identifier of its own and reports the nil UUID, `00000000-0000-0000-0000-000000000000`. | +| `day_of_week` | [`DayOfWeek`](/products/hosted/api/v1/models#model-dayofweek) | yes | The day of the week a service window falls on, in UTC. | +| `start_hour_utc` | `integer` | yes | The first hour of the window, in UTC. Inclusive. | +| `end_hour_utc` | `integer` | yes | The hour the window ends, in UTC. Exclusive, so a window of 3 to 5 covers 03:00 until 05:00. | +| `is_default` | `boolean` | yes | Whether this is the default window Hosted falls back to rather than one that was configured. `true` means no maintenance window has been set for this deployment. | + +--- + ## DeploymentInstance {#model-deploymentinstance} One instance backing a deployment. A deployment has a primary and, when it has read replicas, one instance per replica. diff --git a/specs/hosted-v1.yaml b/specs/hosted-v1.yaml index f2dc57c..0a96a30 100644 --- a/specs/hosted-v1.yaml +++ b/specs/hosted-v1.yaml @@ -1328,6 +1328,103 @@ paths: "503": $ref: "#/components/responses/ServiceUnavailable" + /api/v1/deployments/{owner}/{deployment}/service-windows: + get: + operationId: listDeploymentServiceWindows + summary: List a deployment's service windows. + description: >- + Returns the weekly windows in which Hosted may restart the deployment's instances to + apply maintenance. + + + A window covers whole hours in UTC on one day of the week. `start_hour_utc` is + inclusive and `end_hour_utc` is exclusive, so 7 and 8 mean the hour beginning + 07:00 UTC. + + + Every deployment has at least one. Until one is set, the list holds a single window + with `is_default` set: Sunday 07:00 to 08:00 UTC. + tags: + - Deployment + security: + - apiToken: [] + parameters: + - name: owner + in: path + required: true + description: >- + The user or organization that owns the deployment. 3–32 characters of letters, + digits, hyphens, and underscores. + schema: + type: string + pattern: "^[-a-zA-Z0-9_]{3,32}$" + example: acme + - name: deployment + in: path + required: true + description: >- + The deployment name, unique within the owner. 3–32 characters of letters, + digits, hyphens, and underscores. + schema: + type: string + pattern: "^[-a-zA-Z0-9_]{3,32}$" + example: analytics + responses: + "200": + description: The deployment's service windows. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Envelope" + - type: object + required: + - data + properties: + data: + type: array + description: The deployment's service windows. + items: + $ref: "#/components/schemas/ServiceWindow" + examples: + configured: + summary: A window set for early Tuesday morning UTC. + value: + data: + - id: 7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d + day_of_week: tuesday + start_hour_utc: 3 + end_hour_utc: 5 + is_default: false + default: + summary: A deployment with no window configured. + value: + data: + - id: 00000000-0000-0000-0000-000000000000 + day_of_week: sunday + start_hour_utc: 7 + end_hour_utc: 8 + is_default: true + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + "405": + $ref: "#/components/responses/MethodNotAllowed" + "422": + $ref: "#/components/responses/UnprocessableEntity" + "500": + $ref: "#/components/responses/InternalServerError" + "503": + $ref: "#/components/responses/ServiceUnavailable" + /api/v1/deployments/{owner}/{deployment}/disable: post: operationId: disableDeployment @@ -2882,6 +2979,77 @@ components: - id: cpu display_name: CPU Utilization + DayOfWeek: + type: string + title: DayOfWeek + description: The day of the week a service window falls on, in UTC. + enum: + - sunday + - monday + - tuesday + - wednesday + - thursday + - friday + - saturday + examples: + - tuesday + + ServiceWindow: + type: object + title: ServiceWindow + description: >- + One weekly window in which Hosted may restart the deployment's instances for + maintenance. + required: + - id + - day_of_week + - start_hour_utc + - end_hour_utc + - is_default + properties: + id: + type: string + format: uuid + description: >- + The window's identifier, unique within the deployment. A default window has no + identifier of its own and reports the nil UUID, + `00000000-0000-0000-0000-000000000000`. + examples: + - 7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d + day_of_week: + $ref: "#/components/schemas/DayOfWeek" + start_hour_utc: + type: integer + format: int32 + minimum: 0 + maximum: 23 + description: The first hour of the window, in UTC. Inclusive. + examples: + - 3 + end_hour_utc: + type: integer + format: int32 + minimum: 0 + maximum: 23 + description: >- + The hour the window ends, in UTC. Exclusive, so a window of 3 to 5 covers + 03:00 until 05:00. + examples: + - 5 + is_default: + type: boolean + description: >- + Whether this is the default window Hosted falls back to rather than one that was + configured. `true` means no maintenance window has been set for this deployment. + examples: + - false + examples: + - id: 7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d + day_of_week: tuesday + start_hour_utc: 3 + end_hour_utc: 5 + is_default: false + DeploymentInstance: type: object title: DeploymentInstance