@confighub/api 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -31,6 +31,60 @@ const { data, error } = await api.GET('/space/{space_id}/unit', {
31
31
  request is not retried automatically.
32
32
  - `fetch?` — override the fetch implementation.
33
33
 
34
+ ## Configuration data
35
+
36
+ A Unit's configuration is not a field of the Unit. `Unit`, `Revision` and `Release` carry
37
+ `DataHash` and `DataSize`; the document itself lives on its own endpoints, which serve it
38
+ as `application/octet-stream`. `openapi-fetch` parses every response as JSON and
39
+ serializes every body with `JSON.stringify`, and both are wrong for a document — so use
40
+ these helpers rather than calling those paths directly:
41
+
42
+ ```ts
43
+ import { getUnitData, putUnitData, getUnitMutationSources } from '@confighub/api';
44
+
45
+ const { data: config, dataHash, notModified } = await getUnitData(api, { spaceId, unitId });
46
+
47
+ const { data: result } = await putUnitData(api, { spaceId, unitId }, config + '\n', {
48
+ ifMatch: dataHash, // 409 rather than clobbering a concurrent write
49
+ lastChangeDescription: 'note',
50
+ include: 'ConfigData', // the configuration the write produced
51
+ });
52
+ const unit = result?.Unit; // a write answers with the result, not the entity
53
+ ```
54
+
55
+ - `getUnitData` / `getRevisionData` / `getReleaseData` read text, and return the `DataHash`
56
+ from the ETag. Pass it back as `ifNoneMatch` to get `notModified: true` instead of a body
57
+ when nothing changed.
58
+ - `putUnitData` writes it. **Every parameter describing how the configuration should land
59
+ belongs here**, not on a metadata update that precedes it — `mergeExternalSource`,
60
+ `mergeBase`, `mergeEnableSubtraction`, `protect`, `clearance`, `tag`, `subgroup`,
61
+ `changeSetId`, `lastChangeDescription` and `dryRun`. A metadata call changes no
62
+ configuration, so anything sent there is silently dropped.
63
+ - An **empty configuration is a configuration** — emptying a Unit is how its resources are
64
+ withdrawn — so never guard the write with `if (config)`.
65
+ - `getUnitMutationSources` / `getRevisionMutationSources` read what set each value.
66
+
67
+ For a list, read many at once rather than one request per Unit. Those are ordinary JSON,
68
+ so the client handles them directly:
69
+
70
+ ```ts
71
+ const { data } = await api.GET('/unit_data', {
72
+ params: { query: { where: `SpaceID = '${spaceId}'` } },
73
+ });
74
+ ```
75
+
76
+ `/revision_data`, `/unit_mutation_sources` and `/revision_mutation_sources` are the same
77
+ shape. All four are organization-scoped and take a `where` clause.
78
+
79
+ ## Writes answer with the operation's result
80
+
81
+ `POST /space/{id}/unit`, `PUT` and `PATCH` on one, the data write, and the bulk forms all
82
+ return `UnitCreateOrUpdateResponse` rather than a `Unit` — unwrap `.Unit`. Naming
83
+ `ConfigData` or `MutationSources` in `include` adds the configuration the operation
84
+ produced and what set each value in it; for a `dryRun` that is the only place the result
85
+ exists, because nothing was stored. An `include` naming neither those nor an expandable
86
+ field is a 400.
87
+
34
88
  ## Types
35
89
 
36
90
  The full generated surface is exported for reuse:
@@ -40,4 +94,8 @@ import type { paths, components } from '@confighub/api';
40
94
  type Unit = components['schemas']['Unit'];
41
95
  ```
42
96
 
97
+ `Data` and `MutationSources` are not fields of any entity, so naming either in a `select`
98
+ is a 400 rather than a silently absent field. There is one content hash, `DataHash`;
99
+ `ContentHash` and `RevisionHash` no longer exist.
100
+
43
101
  Types track the ConfigHub server version pinned in the SDK repo's `.spec-version`.
package/dist/index.cjs CHANGED
@@ -40,6 +40,101 @@ function createConfigHubClient(options) {
40
40
  return client;
41
41
  }
42
42
 
43
+ // src/data.ts
44
+ var etagToHash = (response) => {
45
+ const etag = response.headers.get("ETag");
46
+ if (!etag) return void 0;
47
+ return etag.replace(/^W\//, "").replace(/^"|"$/g, "");
48
+ };
49
+ var readData = async (call) => {
50
+ const { data, error, response } = await call;
51
+ if (response.status === 304) {
52
+ return { notModified: true, dataHash: etagToHash(response), response };
53
+ }
54
+ if (!response.ok) {
55
+ return { notModified: false, error, response };
56
+ }
57
+ return { data: data ?? "", dataHash: etagToHash(response), notModified: false, response };
58
+ };
59
+ function getUnitData(client, ref, options = {}) {
60
+ return readData(
61
+ client.GET("/space/{space_id}/unit/{unit_id}/data", {
62
+ params: { path: { space_id: ref.spaceId, unit_id: ref.unitId } },
63
+ headers: options.ifNoneMatch ? { "If-None-Match": `"${options.ifNoneMatch}"` } : void 0,
64
+ signal: options.signal,
65
+ parseAs: "text"
66
+ })
67
+ );
68
+ }
69
+ function getRevisionData(client, ref, options = {}) {
70
+ return readData(
71
+ client.GET("/space/{space_id}/unit/{unit_id}/revision/{revision_id}/data", {
72
+ params: {
73
+ path: { space_id: ref.spaceId, unit_id: ref.unitId, revision_id: ref.revisionId }
74
+ },
75
+ headers: options.ifNoneMatch ? { "If-None-Match": `"${options.ifNoneMatch}"` } : void 0,
76
+ signal: options.signal,
77
+ parseAs: "text"
78
+ })
79
+ );
80
+ }
81
+ function getReleaseData(client, ref, options = {}) {
82
+ return readData(
83
+ client.GET("/space/{space_id}/release/{release_id}/data", {
84
+ params: { path: { space_id: ref.spaceId, release_id: ref.releaseId } },
85
+ headers: options.ifNoneMatch ? { "If-None-Match": `"${options.ifNoneMatch}"` } : void 0,
86
+ signal: options.signal,
87
+ parseAs: "text"
88
+ })
89
+ );
90
+ }
91
+ function putUnitData(client, ref, data, options = {}) {
92
+ const headers = { "Content-Type": "application/octet-stream" };
93
+ if (options.ifMatch) headers["If-Match"] = `"${options.ifMatch}"`;
94
+ return client.PUT("/space/{space_id}/unit/{unit_id}/data", {
95
+ params: {
96
+ path: { space_id: ref.spaceId, unit_id: ref.unitId },
97
+ query: {
98
+ last_change_description: options.lastChangeDescription,
99
+ include: options.include,
100
+ dry_run: options.dryRun,
101
+ protect: options.protect,
102
+ clearance: options.clearance,
103
+ merge_base: options.mergeBase,
104
+ merge_external_source: options.mergeExternalSource,
105
+ merge_enable_subtraction: options.mergeEnableSubtraction,
106
+ tag: options.tag,
107
+ change_set_id: options.changeSetId,
108
+ subgroup: options.subgroup
109
+ }
110
+ },
111
+ headers,
112
+ signal: options.signal,
113
+ body: data,
114
+ // The body is the configuration. The default serializer would JSON.stringify it and
115
+ // upload a quoted string.
116
+ bodySerializer: (body) => body ?? ""
117
+ });
118
+ }
119
+ function getUnitMutationSources(client, ref) {
120
+ return client.GET("/space/{space_id}/unit/{unit_id}/mutation_sources", {
121
+ params: { path: { space_id: ref.spaceId, unit_id: ref.unitId } }
122
+ });
123
+ }
124
+ function getRevisionMutationSources(client, ref) {
125
+ return client.GET("/space/{space_id}/unit/{unit_id}/revision/{revision_id}/mutation_sources", {
126
+ params: {
127
+ path: { space_id: ref.spaceId, unit_id: ref.unitId, revision_id: ref.revisionId }
128
+ }
129
+ });
130
+ }
131
+
43
132
  exports.createConfigHubClient = createConfigHubClient;
133
+ exports.getReleaseData = getReleaseData;
134
+ exports.getRevisionData = getRevisionData;
135
+ exports.getRevisionMutationSources = getRevisionMutationSources;
136
+ exports.getUnitData = getUnitData;
137
+ exports.getUnitMutationSources = getUnitMutationSources;
138
+ exports.putUnitData = putUnitData;
44
139
  //# sourceMappingURL=index.cjs.map
45
140
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"names":["createClient"],"mappings":";;;;;;;;;AAqCA,IAAM,oBAAoB,CAAC,CAAA,KAAsB,CAAA,CAAE,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAIrE,IAAM,UAAA,GAAa,CAAC,OAAA,KAA4B;AAC9C,EAAA,MAAM,OAAA,GAAU,kBAAkB,OAAO,CAAA;AACzC,EAAA,OAAO,OAAA,CAAQ,QAAA,CAAS,MAAM,CAAA,GAAI,UAAU,OAAA,GAAU,MAAA;AACxD,CAAA;AAaO,SAAS,sBAAsB,OAAA,EAAkD;AACtF,EAAA,MAAM,EAAE,OAAA,EAAS,QAAA,EAAU,cAAA,EAAgB,KAAA,EAAO,WAAU,GAAI,OAAA;AAEhE,EAAA,MAAM,SAASA,6BAAA,CAAoB;AAAA,IACjC,OAAA,EAAS,WAAW,OAAO,CAAA;AAAA,IAC3B,KAAA,EAAO;AAAA,GACR,CAAA;AAED,EAAA,MAAM,UAAA,GAAyB;AAAA,IAC7B,MAAM,SAAA,CAAU,EAAE,OAAA,EAAQ,EAAG;AAC3B,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,MAAM,KAAA,GAAQ,MAAM,QAAA,EAAS;AAC7B,QAAA,IAAI,OAAO,OAAA,CAAQ,OAAA,CAAQ,IAAI,eAAA,EAAiB,CAAA,OAAA,EAAU,KAAK,CAAA,CAAE,CAAA;AAAA,MACnE;AAIA,MAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,OAAA,IAAW,OAAA,CAAQ,QAAQ,IAAA,EAAM;AACtD,QAAA,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,cAAA,EAAgB,8BAA8B,CAAA;AAAA,MACpE;AACA,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAM,UAAA,CAAW,EAAE,QAAA,EAAS,EAAG;AAC7B,MAAA,IAAI,QAAA,CAAS,MAAA,KAAW,GAAA,IAAO,cAAA,EAAgB;AAC7C,QAAA,MAAM,cAAA,EAAe;AAAA,MACvB;AACA,MAAA,OAAO,QAAA;AAAA,IACT;AAAA,GACF;AAEA,EAAA,MAAA,CAAO,IAAI,UAAU,CAAA;AACrB,EAAA,OAAO,MAAA;AACT","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"]}
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"]}