@confighub/api 0.2.0 → 0.4.1

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.cjs CHANGED
@@ -129,6 +129,38 @@ function getRevisionMutationSources(client, ref) {
129
129
  });
130
130
  }
131
131
 
132
+ // src/validation.ts
133
+ var SLUG_PATTERN = /^[A-Za-z0-9]([\-_.A-Za-z0-9]*[A-Za-z0-9])?$/;
134
+ var SLUG_MAX_LENGTH = 128;
135
+ var SLUG_PATTERN_MESSAGE = "Must start and end with a letter or number. May contain letters, numbers, hyphens (-), underscores (_), and periods (.)";
136
+ var LABEL_KEY_PATTERN = /^[A-Za-z0-9]([\-_\./A-Za-z0-9]*[A-Za-z0-9])?$/;
137
+ var LABEL_KEY_MAX_LENGTH = 128;
138
+ var LABEL_KEY_PATTERN_MESSAGE = "Must start and end with a letter or number. May contain letters, numbers, hyphens (-), underscores (_), periods (.), and forward slashes (/)";
139
+ var LABEL_VALUE_PATTERN = /^[\-_@/#$%&~+=!?.,:;(){}\[\]<>|A-Za-z0-9](([\-_@/#$%&~+=!?.,:;(){}\[\]<>|A-Za-z0-9]| )*[\-_@/#$%&~+=!?.,:;(){}\[\]<>|A-Za-z0-9])?$/;
140
+ var LABEL_VALUE_MAX_LENGTH = 128;
141
+ var LABEL_VALUE_PATTERN_MESSAGE = "Must start and end with a printable character (no leading/trailing spaces). Quotes, backslashes, and wildcards are not allowed";
142
+ var ANNOTATION_KEY_PATTERN = /^[A-Za-z0-9]([\-_\./A-Za-z0-9]*[A-Za-z0-9])?$/;
143
+ var ANNOTATION_KEY_MAX_LENGTH = 128;
144
+ var ANNOTATION_KEY_PATTERN_MESSAGE = "Must start and end with a letter or number. May contain letters, numbers, hyphens (-), underscores (_), periods (.), and forward slashes (/)";
145
+ var ANNOTATION_VALUE_PATTERN = /^(.|\n)*$/;
146
+ var ANNOTATION_VALUE_MAX_LENGTH = 1024;
147
+ var ANNOTATION_VALUE_PATTERN_MESSAGE = "Any content is allowed, including multiline text";
148
+
149
+ exports.ANNOTATION_KEY_MAX_LENGTH = ANNOTATION_KEY_MAX_LENGTH;
150
+ exports.ANNOTATION_KEY_PATTERN = ANNOTATION_KEY_PATTERN;
151
+ exports.ANNOTATION_KEY_PATTERN_MESSAGE = ANNOTATION_KEY_PATTERN_MESSAGE;
152
+ exports.ANNOTATION_VALUE_MAX_LENGTH = ANNOTATION_VALUE_MAX_LENGTH;
153
+ exports.ANNOTATION_VALUE_PATTERN = ANNOTATION_VALUE_PATTERN;
154
+ exports.ANNOTATION_VALUE_PATTERN_MESSAGE = ANNOTATION_VALUE_PATTERN_MESSAGE;
155
+ exports.LABEL_KEY_MAX_LENGTH = LABEL_KEY_MAX_LENGTH;
156
+ exports.LABEL_KEY_PATTERN = LABEL_KEY_PATTERN;
157
+ exports.LABEL_KEY_PATTERN_MESSAGE = LABEL_KEY_PATTERN_MESSAGE;
158
+ exports.LABEL_VALUE_MAX_LENGTH = LABEL_VALUE_MAX_LENGTH;
159
+ exports.LABEL_VALUE_PATTERN = LABEL_VALUE_PATTERN;
160
+ exports.LABEL_VALUE_PATTERN_MESSAGE = LABEL_VALUE_PATTERN_MESSAGE;
161
+ exports.SLUG_MAX_LENGTH = SLUG_MAX_LENGTH;
162
+ exports.SLUG_PATTERN = SLUG_PATTERN;
163
+ exports.SLUG_PATTERN_MESSAGE = SLUG_PATTERN_MESSAGE;
132
164
  exports.createConfigHubClient = createConfigHubClient;
133
165
  exports.getReleaseData = getReleaseData;
134
166
  exports.getRevisionData = getRevisionData;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/data.ts"],"names":["createClient"],"mappings":"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","file":"index.cjs","sourcesContent":["// Copyright (C) ConfigHub, Inc.\n// SPDX-License-Identifier: MIT\n\nimport createClient, { type Client, type Middleware } from 'openapi-fetch';\nimport type { paths } from './schema';\n\nexport interface ConfigHubClientOptions {\n /**\n * Base URL of the ConfigHub instance, e.g. `https://hub.confighub.com` — the same\n * value passed to the auth provider. The ConfigHub API is mounted under `/api`\n * (the spec's paths are relative to it), so the client targets `{baseUrl}/api`.\n * Passing a URL that already ends in `/api` is accepted as-is.\n */\n baseUrl: string;\n\n /**\n * Returns the current bearer token, or undefined when unauthenticated. May be\n * async so the caller can await a refresh. Wired as an openapi-fetch middleware\n * that sets `Authorization: Bearer <token>` per request. The client never stores\n * or refreshes tokens itself — that is the auth layer's job (see\n * `@confighub/react-auth`).\n */\n getToken?: () => string | undefined | Promise<string | undefined>;\n\n /**\n * Called when a request returns 401. A library must not redirect on its own, so\n * this hands control back to the caller — typically to trigger a token refresh\n * or re-login. The originating request is not retried automatically.\n */\n onUnauthorized?: () => void | Promise<void>;\n\n /** Override the fetch implementation (tests, non-browser runtimes). */\n fetch?: typeof globalThis.fetch;\n}\n\nexport type ConfigHubClient = Client<paths>;\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\n\n// The ConfigHub API lives under /api and the OpenAPI paths are relative to it. Accept\n// either the instance origin or a URL that already includes /api.\nconst apiBaseUrl = (baseUrl: string): string => {\n const trimmed = trimTrailingSlash(baseUrl);\n return trimmed.endsWith('/api') ? trimmed : trimmed + '/api';\n};\n\n/**\n * Create a typed ConfigHub API client. Every path, param, and response is derived\n * from the pinned OpenAPI spec (`src/schema.d.ts`), so it stays in lockstep with\n * the server.\n *\n * ```ts\n * const api = createConfigHubClient({ baseUrl, getToken: () => session.accessToken });\n * const { data, error } = await api.GET('/me');\n * const units = await api.GET('/space/{space_id}/unit', { params: { path: { space_id } } });\n * ```\n */\nexport function createConfigHubClient(options: ConfigHubClientOptions): ConfigHubClient {\n const { baseUrl, getToken, onUnauthorized, fetch: fetchImpl } = options;\n\n const client = createClient<paths>({\n baseUrl: apiBaseUrl(baseUrl),\n fetch: fetchImpl,\n });\n\n const middleware: Middleware = {\n async onRequest({ request }) {\n if (getToken) {\n const token = await getToken();\n if (token) request.headers.set('Authorization', `Bearer ${token}`);\n }\n // ConfigHub's PATCH endpoints expect RFC 7386 merge-patch semantics. This\n // mirrors the first-party UI, which sets the same content type for its\n // patch/bulk-patch operations.\n if (request.method === 'PATCH' && request.body != null) {\n request.headers.set('Content-Type', 'application/merge-patch+json');\n }\n return request;\n },\n async onResponse({ response }) {\n if (response.status === 401 && onUnauthorized) {\n await onUnauthorized();\n }\n return response;\n },\n };\n\n client.use(middleware);\n return client;\n}\n","// Copyright (C) ConfigHub, Inc.\n// SPDX-License-Identifier: MIT\n\nimport type { ConfigHubClient } from './client';\nimport type { components } from './schema';\n\n/**\n * Configuration data is not a field of a Unit, a Revision, or a Release. It is read from\n * and written to its own endpoints, which serve the document itself as\n * `application/octet-stream` rather than a JSON envelope around it.\n *\n * These helpers exist because the two defaults of the underlying fetch client are both\n * wrong for that shape, and neither failure is visible to the type checker:\n *\n * - a read is parsed with `response.json()`, which throws on YAML, and\n * - a write is serialized with `JSON.stringify`, which would upload a quoted string.\n *\n * Everything else in the API is ordinary JSON and needs no wrapper — call the client\n * directly. In particular the bulk reads (`/unit_data`, `/revision_data`,\n * `/unit_mutation_sources`, `/revision_mutation_sources`) return JSON arrays, and a list\n * view should use those rather than one request per Unit.\n */\n\nexport type UnitCreateOrUpdateResponse = components['schemas']['UnitCreateOrUpdateResponse'];\nexport type MutationSourcesResponse = components['schemas']['MutationSourcesResponse'];\n\n/** Identifies a Unit. Both ids are required: the data endpoints are Space-scoped. */\nexport interface UnitRef {\n spaceId: string;\n unitId: string;\n}\n\n/** Identifies one Revision of a Unit. */\nexport interface RevisionRef extends UnitRef {\n revisionId: string;\n}\n\n/** Identifies a Release. */\nexport interface ReleaseRef {\n spaceId: string;\n releaseId: string;\n}\n\nexport interface ReadDataOptions {\n /**\n * A `DataHash` a previous read served, sent as `If-None-Match`. The server answers 304\n * when the configuration has not changed, which surfaces as `notModified: true` and no\n * `data` — keep what you already had.\n */\n ifNoneMatch?: string;\n signal?: AbortSignal;\n}\n\nexport interface ReadDataResult {\n /** The configuration, as text. Undefined on a 304 or an error. */\n data?: string;\n /** The `DataHash`, taken from the ETag. Pass it back as `ifNoneMatch` or `ifMatch`. */\n dataHash?: string;\n /** True when the server answered 304 because `ifNoneMatch` still matched. */\n notModified: boolean;\n /** The parsed error body, when the request failed. */\n error?: unknown;\n response: Response;\n}\n\n/**\n * Everything describing *how* a configuration should land belongs on the write that\n * carries it, not on a metadata update that precedes it. A metadata call changes no\n * configuration, so each of these has nothing to act on there and is silently dropped —\n * `mergeExternalSource` degrades to a plain overwrite that ignores protected paths, and\n * `dryRun` leaves the metadata call a dry run while this one really writes.\n */\nexport interface WriteDataOptions {\n /** Description recorded on the Revision this write creates. */\n lastChangeDescription?: string;\n /**\n * Extra parts of the result to return: `'ConfigData'`, `'MutationSources'`, or both,\n * comma-separated. For a `dryRun` nothing is stored, so this is the only way to see what\n * the operation produced. A name that is neither of these nor an expandable field is a 400.\n */\n include?: string;\n /** Compute the result and report it without storing anything. */\n dryRun?: boolean;\n /** Record the paths this write sets as protected local overrides. */\n protect?: boolean;\n /** The classes of guarded reason this write is cleared for, as a JSON Clearance. */\n clearance?: string;\n /** Revision providing the base configuration for a merge. */\n mergeBase?: string;\n /** Merge the body against the Unit as an external source rather than replacing it. */\n mergeExternalSource?: string;\n /** Also subtract the target's local differences from the source patch. */\n mergeEnableSubtraction?: boolean;\n /** Tag id to attach to the head Revision. */\n tag?: string;\n /** ChangeSet the write belongs to. */\n changeSetId?: string;\n /** User-defined category for the Mutation. */\n subgroup?: string;\n /**\n * A `DataHash` a read served, sent as `If-Match`, so the write fails rather than\n * clobbering a configuration somebody else changed in the meantime.\n */\n ifMatch?: string;\n signal?: AbortSignal;\n}\n\nexport interface WriteDataResult {\n /**\n * The operation's result. The Unit is in its `Unit` field — a write answers with what\n * it did, not with the entity.\n */\n data?: UnitCreateOrUpdateResponse;\n error?: unknown;\n response: Response;\n}\n\n// An ETag is the quoted DataHash, optionally weak. Give callers back the hash itself, so\n// it can go straight into If-Match / If-None-Match or be compared with Unit.DataHash.\nconst etagToHash = (response: Response): string | undefined => {\n const etag = response.headers.get('ETag');\n if (!etag) return undefined;\n return etag.replace(/^W\\//, '').replace(/^\"|\"$/g, '');\n};\n\nconst readData = async (\n call: Promise<{ data?: unknown; error?: unknown; response: Response }>,\n): Promise<ReadDataResult> => {\n const { data, error, response } = await call;\n if (response.status === 304) {\n return { notModified: true, dataHash: etagToHash(response), response };\n }\n if (!response.ok) {\n return { notModified: false, error, response };\n }\n // parseAs: 'text' below makes this a string; an empty body is an empty configuration,\n // which is a configuration, so it is not normalized away.\n return { data: (data as string | undefined) ?? '', dataHash: etagToHash(response), notModified: false, response };\n};\n\n/** A Unit's configuration, as text. */\nexport function getUnitData(\n client: ConfigHubClient,\n ref: UnitRef,\n options: ReadDataOptions = {},\n): Promise<ReadDataResult> {\n return readData(\n client.GET('/space/{space_id}/unit/{unit_id}/data', {\n params: { path: { space_id: ref.spaceId, unit_id: ref.unitId } },\n headers: options.ifNoneMatch ? { 'If-None-Match': `\"${options.ifNoneMatch}\"` } : undefined,\n signal: options.signal,\n parseAs: 'text',\n }),\n );\n}\n\n/** One Revision's configuration, as text. */\nexport function getRevisionData(\n client: ConfigHubClient,\n ref: RevisionRef,\n options: ReadDataOptions = {},\n): Promise<ReadDataResult> {\n return readData(\n client.GET('/space/{space_id}/unit/{unit_id}/revision/{revision_id}/data', {\n params: {\n path: { space_id: ref.spaceId, unit_id: ref.unitId, revision_id: ref.revisionId },\n },\n headers: options.ifNoneMatch ? { 'If-None-Match': `\"${options.ifNoneMatch}\"` } : undefined,\n signal: options.signal,\n parseAs: 'text',\n }),\n );\n}\n\n/** A Release's bundle. Its ETag is the Release digest rather than a DataHash. */\nexport function getReleaseData(\n client: ConfigHubClient,\n ref: ReleaseRef,\n options: ReadDataOptions = {},\n): Promise<ReadDataResult> {\n return readData(\n client.GET('/space/{space_id}/release/{release_id}/data', {\n params: { path: { space_id: ref.spaceId, release_id: ref.releaseId } },\n headers: options.ifNoneMatch ? { 'If-None-Match': `\"${options.ifNoneMatch}\"` } : undefined,\n signal: options.signal,\n parseAs: 'text',\n }),\n );\n}\n\n/**\n * Replace a Unit's configuration. This is the only way configuration reaches a Unit other\n * than a clone, which copies it server-side: a metadata write has nowhere to put one, and\n * therefore nowhere to lose one.\n *\n * `data` is the document. An empty string is a real configuration — emptying a Unit is how\n * its resources are withdrawn — so never guard this call on the string being non-empty.\n * Track whether a configuration was *supplied* separately from what it contains.\n */\nexport function putUnitData(\n client: ConfigHubClient,\n ref: UnitRef,\n data: string,\n options: WriteDataOptions = {},\n): Promise<WriteDataResult> {\n const headers: Record<string, string> = { 'Content-Type': 'application/octet-stream' };\n if (options.ifMatch) headers['If-Match'] = `\"${options.ifMatch}\"`;\n\n return client.PUT('/space/{space_id}/unit/{unit_id}/data', {\n params: {\n path: { space_id: ref.spaceId, unit_id: ref.unitId },\n query: {\n last_change_description: options.lastChangeDescription,\n include: options.include,\n dry_run: options.dryRun,\n protect: options.protect,\n clearance: options.clearance,\n merge_base: options.mergeBase,\n merge_external_source: options.mergeExternalSource,\n merge_enable_subtraction: options.mergeEnableSubtraction,\n tag: options.tag,\n change_set_id: options.changeSetId,\n subgroup: options.subgroup,\n },\n },\n headers,\n signal: options.signal,\n body: data,\n // The body is the configuration. The default serializer would JSON.stringify it and\n // upload a quoted string.\n bodySerializer: (body: string | undefined) => body ?? '',\n });\n}\n\n/** What set each value in a Unit's configuration. Ordinary JSON. */\nexport function getUnitMutationSources(client: ConfigHubClient, ref: UnitRef) {\n return client.GET('/space/{space_id}/unit/{unit_id}/mutation_sources', {\n params: { path: { space_id: ref.spaceId, unit_id: ref.unitId } },\n });\n}\n\n/** The Revision counterpart of {@link getUnitMutationSources}. */\nexport function getRevisionMutationSources(client: ConfigHubClient, ref: RevisionRef) {\n return client.GET('/space/{space_id}/unit/{unit_id}/revision/{revision_id}/mutation_sources', {\n params: {\n path: { space_id: ref.spaceId, unit_id: ref.unitId, revision_id: ref.revisionId },\n },\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/data.ts","../src/validation.ts"],"names":["createClient"],"mappings":"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","file":"index.cjs","sourcesContent":["// Copyright (C) ConfigHub, Inc.\n// SPDX-License-Identifier: MIT\n\nimport createClient, { type Client, type Middleware } from 'openapi-fetch';\nimport type { paths } from './schema';\n\nexport interface ConfigHubClientOptions {\n /**\n * Base URL of the ConfigHub instance, e.g. `https://hub.confighub.com` — the same\n * value passed to the auth provider. The ConfigHub API is mounted under `/api`\n * (the spec's paths are relative to it), so the client targets `{baseUrl}/api`.\n * Passing a URL that already ends in `/api` is accepted as-is.\n */\n baseUrl: string;\n\n /**\n * Returns the current bearer token, or undefined when unauthenticated. May be\n * async so the caller can await a refresh. Wired as an openapi-fetch middleware\n * that sets `Authorization: Bearer <token>` per request. The client never stores\n * or refreshes tokens itself — that is the auth layer's job (see\n * `@confighub/react-auth`).\n */\n getToken?: () => string | undefined | Promise<string | undefined>;\n\n /**\n * Called when a request returns 401. A library must not redirect on its own, so\n * this hands control back to the caller — typically to trigger a token refresh\n * or re-login. The originating request is not retried automatically.\n */\n onUnauthorized?: () => void | Promise<void>;\n\n /** Override the fetch implementation (tests, non-browser runtimes). */\n fetch?: typeof globalThis.fetch;\n}\n\nexport type ConfigHubClient = Client<paths>;\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\n\n// The ConfigHub API lives under /api and the OpenAPI paths are relative to it. Accept\n// either the instance origin or a URL that already includes /api.\nconst apiBaseUrl = (baseUrl: string): string => {\n const trimmed = trimTrailingSlash(baseUrl);\n return trimmed.endsWith('/api') ? trimmed : trimmed + '/api';\n};\n\n/**\n * Create a typed ConfigHub API client. Every path, param, and response is derived\n * from the pinned OpenAPI spec (`src/schema.d.ts`), so it stays in lockstep with\n * the server.\n *\n * ```ts\n * const api = createConfigHubClient({ baseUrl, getToken: () => session.accessToken });\n * const { data, error } = await api.GET('/me');\n * const units = await api.GET('/space/{space_id}/unit', { params: { path: { space_id } } });\n * ```\n */\nexport function createConfigHubClient(options: ConfigHubClientOptions): ConfigHubClient {\n const { baseUrl, getToken, onUnauthorized, fetch: fetchImpl } = options;\n\n const client = createClient<paths>({\n baseUrl: apiBaseUrl(baseUrl),\n fetch: fetchImpl,\n });\n\n const middleware: Middleware = {\n async onRequest({ request }) {\n if (getToken) {\n const token = await getToken();\n if (token) request.headers.set('Authorization', `Bearer ${token}`);\n }\n // ConfigHub's PATCH endpoints expect RFC 7386 merge-patch semantics. This\n // mirrors the first-party UI, which sets the same content type for its\n // patch/bulk-patch operations.\n if (request.method === 'PATCH' && request.body != null) {\n request.headers.set('Content-Type', 'application/merge-patch+json');\n }\n return request;\n },\n async onResponse({ response }) {\n if (response.status === 401 && onUnauthorized) {\n await onUnauthorized();\n }\n return response;\n },\n };\n\n client.use(middleware);\n return client;\n}\n","// Copyright (C) ConfigHub, Inc.\n// SPDX-License-Identifier: MIT\n\nimport type { ConfigHubClient } from './client';\nimport type { components } from './schema';\n\n/**\n * Configuration data is not a field of a Unit, a Revision, or a Release. It is read from\n * and written to its own endpoints, which serve the document itself as\n * `application/octet-stream` rather than a JSON envelope around it.\n *\n * These helpers exist because the two defaults of the underlying fetch client are both\n * wrong for that shape, and neither failure is visible to the type checker:\n *\n * - a read is parsed with `response.json()`, which throws on YAML, and\n * - a write is serialized with `JSON.stringify`, which would upload a quoted string.\n *\n * Everything else in the API is ordinary JSON and needs no wrapper — call the client\n * directly. In particular the bulk reads (`/unit_data`, `/revision_data`,\n * `/unit_mutation_sources`, `/revision_mutation_sources`) return JSON arrays, and a list\n * view should use those rather than one request per Unit.\n */\n\nexport type UnitCreateOrUpdateResponse = components['schemas']['UnitCreateOrUpdateResponse'];\nexport type MutationSourcesResponse = components['schemas']['MutationSourcesResponse'];\n\n/** Identifies a Unit. Both ids are required: the data endpoints are Space-scoped. */\nexport interface UnitRef {\n spaceId: string;\n unitId: string;\n}\n\n/** Identifies one Revision of a Unit. */\nexport interface RevisionRef extends UnitRef {\n revisionId: string;\n}\n\n/** Identifies a Release. */\nexport interface ReleaseRef {\n spaceId: string;\n releaseId: string;\n}\n\nexport interface ReadDataOptions {\n /**\n * A `DataHash` a previous read served, sent as `If-None-Match`. The server answers 304\n * when the configuration has not changed, which surfaces as `notModified: true` and no\n * `data` — keep what you already had.\n */\n ifNoneMatch?: string;\n signal?: AbortSignal;\n}\n\nexport interface ReadDataResult {\n /** The configuration, as text. Undefined on a 304 or an error. */\n data?: string;\n /** The `DataHash`, taken from the ETag. Pass it back as `ifNoneMatch` or `ifMatch`. */\n dataHash?: string;\n /** True when the server answered 304 because `ifNoneMatch` still matched. */\n notModified: boolean;\n /** The parsed error body, when the request failed. */\n error?: unknown;\n response: Response;\n}\n\n/**\n * Everything describing *how* a configuration should land belongs on the write that\n * carries it, not on a metadata update that precedes it. A metadata call changes no\n * configuration, so each of these has nothing to act on there and is silently dropped —\n * `mergeExternalSource` degrades to a plain overwrite that ignores protected paths, and\n * `dryRun` leaves the metadata call a dry run while this one really writes.\n */\nexport interface WriteDataOptions {\n /** Description recorded on the Revision this write creates. */\n lastChangeDescription?: string;\n /**\n * Extra parts of the result to return: `'ConfigData'`, `'MutationSources'`, or both,\n * comma-separated. For a `dryRun` nothing is stored, so this is the only way to see what\n * the operation produced. A name that is neither of these nor an expandable field is a 400.\n */\n include?: string;\n /** Compute the result and report it without storing anything. */\n dryRun?: boolean;\n /** Record the paths this write sets as protected local overrides. */\n protect?: boolean;\n /** The classes of guarded reason this write is cleared for, as a JSON Clearance. */\n clearance?: string;\n /** Revision providing the base configuration for a merge. */\n mergeBase?: string;\n /** Merge the body against the Unit as an external source rather than replacing it. */\n mergeExternalSource?: string;\n /** Also subtract the target's local differences from the source patch. */\n mergeEnableSubtraction?: boolean;\n /** Tag id to attach to the head Revision. */\n tag?: string;\n /** ChangeSet the write belongs to. */\n changeSetId?: string;\n /** User-defined category for the Mutation. */\n subgroup?: string;\n /**\n * A `DataHash` a read served, sent as `If-Match`, so the write fails rather than\n * clobbering a configuration somebody else changed in the meantime.\n */\n ifMatch?: string;\n signal?: AbortSignal;\n}\n\nexport interface WriteDataResult {\n /**\n * The operation's result. The Unit is in its `Unit` field — a write answers with what\n * it did, not with the entity.\n */\n data?: UnitCreateOrUpdateResponse;\n error?: unknown;\n response: Response;\n}\n\n// An ETag is the quoted DataHash, optionally weak. Give callers back the hash itself, so\n// it can go straight into If-Match / If-None-Match or be compared with Unit.DataHash.\nconst etagToHash = (response: Response): string | undefined => {\n const etag = response.headers.get('ETag');\n if (!etag) return undefined;\n return etag.replace(/^W\\//, '').replace(/^\"|\"$/g, '');\n};\n\nconst readData = async (\n call: Promise<{ data?: unknown; error?: unknown; response: Response }>,\n): Promise<ReadDataResult> => {\n const { data, error, response } = await call;\n if (response.status === 304) {\n return { notModified: true, dataHash: etagToHash(response), response };\n }\n if (!response.ok) {\n return { notModified: false, error, response };\n }\n // parseAs: 'text' below makes this a string; an empty body is an empty configuration,\n // which is a configuration, so it is not normalized away.\n return { data: (data as string | undefined) ?? '', dataHash: etagToHash(response), notModified: false, response };\n};\n\n/** A Unit's configuration, as text. */\nexport function getUnitData(\n client: ConfigHubClient,\n ref: UnitRef,\n options: ReadDataOptions = {},\n): Promise<ReadDataResult> {\n return readData(\n client.GET('/space/{space_id}/unit/{unit_id}/data', {\n params: { path: { space_id: ref.spaceId, unit_id: ref.unitId } },\n headers: options.ifNoneMatch ? { 'If-None-Match': `\"${options.ifNoneMatch}\"` } : undefined,\n signal: options.signal,\n parseAs: 'text',\n }),\n );\n}\n\n/** One Revision's configuration, as text. */\nexport function getRevisionData(\n client: ConfigHubClient,\n ref: RevisionRef,\n options: ReadDataOptions = {},\n): Promise<ReadDataResult> {\n return readData(\n client.GET('/space/{space_id}/unit/{unit_id}/revision/{revision_id}/data', {\n params: {\n path: { space_id: ref.spaceId, unit_id: ref.unitId, revision_id: ref.revisionId },\n },\n headers: options.ifNoneMatch ? { 'If-None-Match': `\"${options.ifNoneMatch}\"` } : undefined,\n signal: options.signal,\n parseAs: 'text',\n }),\n );\n}\n\n/** A Release's bundle. Its ETag is the Release digest rather than a DataHash. */\nexport function getReleaseData(\n client: ConfigHubClient,\n ref: ReleaseRef,\n options: ReadDataOptions = {},\n): Promise<ReadDataResult> {\n return readData(\n client.GET('/space/{space_id}/release/{release_id}/data', {\n params: { path: { space_id: ref.spaceId, release_id: ref.releaseId } },\n headers: options.ifNoneMatch ? { 'If-None-Match': `\"${options.ifNoneMatch}\"` } : undefined,\n signal: options.signal,\n parseAs: 'text',\n }),\n );\n}\n\n/**\n * Replace a Unit's configuration. This is the only way configuration reaches a Unit other\n * than a clone, which copies it server-side: a metadata write has nowhere to put one, and\n * therefore nowhere to lose one.\n *\n * `data` is the document. An empty string is a real configuration — emptying a Unit is how\n * its resources are withdrawn — so never guard this call on the string being non-empty.\n * Track whether a configuration was *supplied* separately from what it contains.\n */\nexport function putUnitData(\n client: ConfigHubClient,\n ref: UnitRef,\n data: string,\n options: WriteDataOptions = {},\n): Promise<WriteDataResult> {\n const headers: Record<string, string> = { 'Content-Type': 'application/octet-stream' };\n if (options.ifMatch) headers['If-Match'] = `\"${options.ifMatch}\"`;\n\n return client.PUT('/space/{space_id}/unit/{unit_id}/data', {\n params: {\n path: { space_id: ref.spaceId, unit_id: ref.unitId },\n query: {\n last_change_description: options.lastChangeDescription,\n include: options.include,\n dry_run: options.dryRun,\n protect: options.protect,\n clearance: options.clearance,\n merge_base: options.mergeBase,\n merge_external_source: options.mergeExternalSource,\n merge_enable_subtraction: options.mergeEnableSubtraction,\n tag: options.tag,\n change_set_id: options.changeSetId,\n subgroup: options.subgroup,\n },\n },\n headers,\n signal: options.signal,\n body: data,\n // The body is the configuration. The default serializer would JSON.stringify it and\n // upload a quoted string.\n bodySerializer: (body: string | undefined) => body ?? '',\n });\n}\n\n/** What set each value in a Unit's configuration. Ordinary JSON. */\nexport function getUnitMutationSources(client: ConfigHubClient, ref: UnitRef) {\n return client.GET('/space/{space_id}/unit/{unit_id}/mutation_sources', {\n params: { path: { space_id: ref.spaceId, unit_id: ref.unitId } },\n });\n}\n\n/** The Revision counterpart of {@link getUnitMutationSources}. */\nexport function getRevisionMutationSources(client: ConfigHubClient, ref: RevisionRef) {\n return client.GET('/space/{space_id}/unit/{unit_id}/revision/{revision_id}/mutation_sources', {\n params: {\n path: { space_id: ref.spaceId, unit_id: ref.unitId, revision_id: ref.revisionId },\n },\n });\n}\n","// Copyright (C) ConfigHub, Inc.\n// SPDX-License-Identifier: MIT\n//\n// Generated by the ConfigHub server from its own validation rules; synced by\n// scripts/sync-spec.mjs at the pinned version. Do not edit.\n\nexport const SLUG_PATTERN = /^[A-Za-z0-9]([\\-_.A-Za-z0-9]*[A-Za-z0-9])?$/;\nexport const SLUG_MAX_LENGTH = 128;\nexport const SLUG_PATTERN_MESSAGE =\n 'Must start and end with a letter or number. May contain letters, numbers, hyphens (-), underscores (_), and periods (.)';\n\nexport const LABEL_KEY_PATTERN = /^[A-Za-z0-9]([\\-_\\./A-Za-z0-9]*[A-Za-z0-9])?$/;\nexport const LABEL_KEY_MAX_LENGTH = 128;\nexport const LABEL_KEY_PATTERN_MESSAGE =\n 'Must start and end with a letter or number. May contain letters, numbers, hyphens (-), underscores (_), periods (.), and forward slashes (/)';\n\nexport const LABEL_VALUE_PATTERN = /^[\\-_@/#$%&~+=!?.,:;(){}\\[\\]<>|A-Za-z0-9](([\\-_@/#$%&~+=!?.,:;(){}\\[\\]<>|A-Za-z0-9]| )*[\\-_@/#$%&~+=!?.,:;(){}\\[\\]<>|A-Za-z0-9])?$/;\nexport const LABEL_VALUE_MAX_LENGTH = 128;\nexport const LABEL_VALUE_PATTERN_MESSAGE =\n 'Must start and end with a printable character (no leading/trailing spaces). Quotes, backslashes, and wildcards are not allowed';\n\nexport const ANNOTATION_KEY_PATTERN = /^[A-Za-z0-9]([\\-_\\./A-Za-z0-9]*[A-Za-z0-9])?$/;\nexport const ANNOTATION_KEY_MAX_LENGTH = 128;\nexport const ANNOTATION_KEY_PATTERN_MESSAGE =\n 'Must start and end with a letter or number. May contain letters, numbers, hyphens (-), underscores (_), periods (.), and forward slashes (/)';\n\nexport const ANNOTATION_VALUE_PATTERN = /^(.|\\n)*$/;\nexport const ANNOTATION_VALUE_MAX_LENGTH = 1024;\nexport const ANNOTATION_VALUE_PATTERN_MESSAGE =\n 'Any content is allowed, including multiline text';\n\n"]}