@nospt/backstage-plugin-apigee-backend 1.1.0-rc2
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 +118 -0
- package/config.d.ts +85 -0
- package/dist/index.cjs.js +22 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +511 -0
- package/dist/lib/api-hub-client.cjs.js +118 -0
- package/dist/lib/api-hub-client.cjs.js.map +1 -0
- package/dist/lib/apigee-client.cjs.js +40 -0
- package/dist/lib/apigee-client.cjs.js.map +1 -0
- package/dist/lib/apigee-stitching-processor.cjs.js +104 -0
- package/dist/lib/apigee-stitching-processor.cjs.js.map +1 -0
- package/dist/lib/entity-builder.cjs.js +347 -0
- package/dist/lib/entity-builder.cjs.js.map +1 -0
- package/dist/lib/entity-provider.cjs.js +502 -0
- package/dist/lib/entity-provider.cjs.js.map +1 -0
- package/dist/lib/fetch-utils.cjs.js +49 -0
- package/dist/lib/fetch-utils.cjs.js.map +1 -0
- package/dist/lib/sharedflow-client.cjs.js +21 -0
- package/dist/lib/sharedflow-client.cjs.js.map +1 -0
- package/dist/module.cjs.js +93 -0
- package/dist/module.cjs.js.map +1 -0
- package/dist/plugin.cjs.js +28 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/router.cjs.js +26 -0
- package/dist/router.cjs.js.map +1 -0
- package/package.json +78 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
|
+
import { SchedulerServiceTaskRunner, LoggerService } from '@backstage/backend-plugin-api';
|
|
3
|
+
import { GoogleAuth } from 'google-auth-library';
|
|
4
|
+
import { EntityProvider, EntityProviderConnection, CatalogProcessor, LocationSpec, CatalogProcessorEmit } from '@backstage/plugin-catalog-node';
|
|
5
|
+
import { Entity } from '@backstage/catalog-model';
|
|
6
|
+
|
|
7
|
+
declare const apigeePlugin: _backstage_backend_plugin_api.BackendFeature;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Backstage catalog backend module for Apigee API discovery.
|
|
11
|
+
* Plugs into the catalog backend and registers Apigee proxies + API Hub
|
|
12
|
+
* definitions as Backstage API entities.
|
|
13
|
+
*/
|
|
14
|
+
declare const catalogModuleApigee: _backstage_backend_plugin_api.BackendFeature;
|
|
15
|
+
|
|
16
|
+
/** Represents a single Apigee API proxy entry from GET /organizations/{org}/apis */
|
|
17
|
+
interface ApigeeProxy {
|
|
18
|
+
/** Proxy name as registered in Apigee (may contain dots/underscores — slugify before use as metadata.name) */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Array of revision numbers (strings), most recent last */
|
|
21
|
+
revision?: string[];
|
|
22
|
+
/** ISO 8601 creation timestamp */
|
|
23
|
+
createdAt?: string;
|
|
24
|
+
/** ISO 8601 last-modified timestamp */
|
|
25
|
+
lastModifiedAt?: string;
|
|
26
|
+
}
|
|
27
|
+
/** Represents one environment deployment from GET /organizations/{org}/apis/{proxy}/deployments */
|
|
28
|
+
interface ApigeeDeployment {
|
|
29
|
+
/** Environment name (e.g. "prod", "staging") */
|
|
30
|
+
environment: string;
|
|
31
|
+
/** Deployed API proxy revision (string, e.g. "1") — field name matches the Apigee API. */
|
|
32
|
+
revision?: string;
|
|
33
|
+
/** Deployment state: READY | PROGRESSING | ERROR (omitted by the list endpoint). */
|
|
34
|
+
state?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Represents a single proxy revision detail from
|
|
38
|
+
* GET /organizations/{org}/apis/{proxy}/revisions/{rev}
|
|
39
|
+
*/
|
|
40
|
+
interface ApigeeProxyRevision {
|
|
41
|
+
name: string;
|
|
42
|
+
revision?: string;
|
|
43
|
+
/** Base paths declared in the proxy bundle (used for apigee.com/base-path annotation) */
|
|
44
|
+
basepaths?: string[];
|
|
45
|
+
description?: string;
|
|
46
|
+
/** Resource files attached to this revision */
|
|
47
|
+
resourceFiles?: {
|
|
48
|
+
resourceFile?: Array<{
|
|
49
|
+
name: string;
|
|
50
|
+
type: string;
|
|
51
|
+
}>;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Read-only HTTP client for the Apigee Management API v1.
|
|
56
|
+
*
|
|
57
|
+
* Auth: uses GCP Application Default Credentials via the injected GoogleAuth
|
|
58
|
+
* instance. All calls are GET-only (AUTH-02: read-only IAM roles).
|
|
59
|
+
*
|
|
60
|
+
* Usage:
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const auth = new GoogleAuth({ scopes: ['https://www.googleapis.com/auth/cloud-platform'] });
|
|
63
|
+
* const client = new ApigeeClient(auth);
|
|
64
|
+
* const proxies = await client.listProxies('my-apigee-org');
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
declare class ApigeeClient {
|
|
68
|
+
private readonly auth;
|
|
69
|
+
constructor(auth: GoogleAuth);
|
|
70
|
+
/**
|
|
71
|
+
* Lists all API proxies in the given Apigee organisation.
|
|
72
|
+
* Calls: GET /organizations/{org}/apis
|
|
73
|
+
*/
|
|
74
|
+
listProxies(org: string): Promise<ApigeeProxy[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Lists all environment deployments for a given proxy.
|
|
77
|
+
* Calls: GET /organizations/{org}/apis/{proxy}/deployments
|
|
78
|
+
*/
|
|
79
|
+
listDeployments(org: string, proxy: string): Promise<ApigeeDeployment[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Fetches full detail for a specific proxy revision.
|
|
82
|
+
* Calls: GET /organizations/{org}/apis/{proxy}/revisions/{rev}
|
|
83
|
+
*/
|
|
84
|
+
getProxyRevision(org: string, proxy: string, rev: string): Promise<ApigeeProxyRevision>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** One allowed value of a Cloud API Hub enum attribute (AllowedValue). */
|
|
88
|
+
interface ApiHubAllowedValue {
|
|
89
|
+
id?: string;
|
|
90
|
+
displayName?: string;
|
|
91
|
+
description?: string;
|
|
92
|
+
immutable?: boolean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The value(s) assigned to a single attribute on an API resource
|
|
96
|
+
* (Cloud API Hub AttributeValues). Exactly one of the *Values fields is set,
|
|
97
|
+
* depending on the attribute's data type.
|
|
98
|
+
*/
|
|
99
|
+
interface ApiHubAttributeValues {
|
|
100
|
+
/** Attribute resource name these values belong to (output-only). */
|
|
101
|
+
attribute?: string;
|
|
102
|
+
enumValues?: {
|
|
103
|
+
values?: ApiHubAllowedValue[];
|
|
104
|
+
};
|
|
105
|
+
stringValues?: {
|
|
106
|
+
values?: string[];
|
|
107
|
+
};
|
|
108
|
+
jsonValues?: {
|
|
109
|
+
values?: string[];
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Represents a single API definition from Cloud API Hub.
|
|
114
|
+
* GET /projects/{p}/locations/{l}/apis
|
|
115
|
+
*/
|
|
116
|
+
interface ApiHubApi {
|
|
117
|
+
/** Full resource name: projects/{p}/locations/{l}/apis/{id} */
|
|
118
|
+
name: string;
|
|
119
|
+
/** Human-readable display name */
|
|
120
|
+
displayName?: string;
|
|
121
|
+
/** Description from API Hub */
|
|
122
|
+
description?: string;
|
|
123
|
+
/**
|
|
124
|
+
* System-defined "Business unit" attribute. In Cloud API Hub this is a
|
|
125
|
+
* TOP-LEVEL field on the Api resource (an AttributeValues object), NOT an
|
|
126
|
+
* entry inside `attributes`. Feeds the entity `department` label (see
|
|
127
|
+
* departmentLabel in entity-builder).
|
|
128
|
+
*/
|
|
129
|
+
businessUnit?: ApiHubAttributeValues;
|
|
130
|
+
/**
|
|
131
|
+
* User-defined attributes keyed by attribute resource name. System-defined
|
|
132
|
+
* attributes (business unit, team, …) are top-level fields, not entries here;
|
|
133
|
+
* scanned only as a fallback for a user-modelled business unit.
|
|
134
|
+
*/
|
|
135
|
+
attributes?: Record<string, ApiHubAttributeValues>;
|
|
136
|
+
/** Owner team name (maps to spec.owner in Backstage) */
|
|
137
|
+
owner?: {
|
|
138
|
+
displayName?: string;
|
|
139
|
+
email?: string;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* GitHub / source repo URI from API Hub metadata.
|
|
143
|
+
* Maps to metadata.links in Backstage (FR17 / META-10).
|
|
144
|
+
*/
|
|
145
|
+
sourceMetadata?: Array<{
|
|
146
|
+
sourceType?: string;
|
|
147
|
+
value?: string;
|
|
148
|
+
}>;
|
|
149
|
+
/**
|
|
150
|
+
* Externally hosted documentation URI — the API Hub "Documentation" field.
|
|
151
|
+
* NOS records the proxy's GitHub repository URL here, so it is the
|
|
152
|
+
* authoritative source for the entity's "Source repository" link (META-10).
|
|
153
|
+
*/
|
|
154
|
+
documentation?: {
|
|
155
|
+
externalUri?: string;
|
|
156
|
+
};
|
|
157
|
+
/** Array of category/tag IDs applied to this API in API Hub (META-09) */
|
|
158
|
+
categories?: string[];
|
|
159
|
+
/** API Hub catalog type (e.g. "openapi", "mcp") — maps to spec.type in Backstage */
|
|
160
|
+
apiType?: string;
|
|
161
|
+
/** ISO 8601 create/update timestamps */
|
|
162
|
+
createTime?: string;
|
|
163
|
+
updateTime?: string;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Represents one version of an API Hub API.
|
|
167
|
+
* GET /projects/{p}/locations/{l}/apis/{apiId}/versions
|
|
168
|
+
*/
|
|
169
|
+
interface ApiHubVersion {
|
|
170
|
+
/** Full resource name: .../apis/{id}/versions/{vid} */
|
|
171
|
+
name: string;
|
|
172
|
+
displayName?: string;
|
|
173
|
+
description?: string;
|
|
174
|
+
/** Version state: DRAFT | ACTIVE | DEPRECATED | ARCHIVED */
|
|
175
|
+
lifecycle?: {
|
|
176
|
+
stage?: string;
|
|
177
|
+
};
|
|
178
|
+
/** Spec IDs attached to this version */
|
|
179
|
+
specIds?: string[];
|
|
180
|
+
createTime?: string;
|
|
181
|
+
updateTime?: string;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Represents a spec (OpenAPI document) attached to a version.
|
|
185
|
+
* GET /projects/{p}/locations/{l}/apis/{apiId}/versions/{versionId}/specs/{specId}
|
|
186
|
+
*/
|
|
187
|
+
interface ApiHubSpec {
|
|
188
|
+
/** Full resource name */
|
|
189
|
+
name: string;
|
|
190
|
+
/** MIME type (e.g. "application/json", "application/yaml") */
|
|
191
|
+
mimeType?: string;
|
|
192
|
+
/** File name (e.g. "openapi.yaml") */
|
|
193
|
+
filename?: string;
|
|
194
|
+
/** Hash of spec contents for deduplication */
|
|
195
|
+
hash?: string;
|
|
196
|
+
/** Size in bytes — used by spec size guard in Phase 3 */
|
|
197
|
+
sizeBytes?: number;
|
|
198
|
+
createTime?: string;
|
|
199
|
+
updateTime?: string;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Raw spec contents response from the :contents sub-resource.
|
|
203
|
+
* `contents` is base64-encoded (Cloud API Hub ApiSpecContents resource).
|
|
204
|
+
*/
|
|
205
|
+
interface ApiHubSpecContents {
|
|
206
|
+
mimeType?: string;
|
|
207
|
+
/** Base64-encoded spec file content */
|
|
208
|
+
contents?: string;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Read-only HTTP client for the Cloud API Hub REST v1 API.
|
|
212
|
+
*
|
|
213
|
+
* Mirrors ApigeeClient exactly (D-04): same constructor pattern, same
|
|
214
|
+
* fetchWithRetry delegation, same encodeURIComponent usage.
|
|
215
|
+
*
|
|
216
|
+
* Auth: uses the same GCP Application Default Credentials GoogleAuth instance
|
|
217
|
+
* that is shared with ApigeeClient (D-03).
|
|
218
|
+
*
|
|
219
|
+
* Usage:
|
|
220
|
+
* ```typescript
|
|
221
|
+
* const auth = new GoogleAuth({ scopes: ['https://www.googleapis.com/auth/cloud-platform'] });
|
|
222
|
+
* const client = new ApiHubClient(auth, 'my-gcp-project');
|
|
223
|
+
* const apis = await client.listApis();
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
declare class ApiHubClient {
|
|
227
|
+
private readonly auth;
|
|
228
|
+
private readonly projectId;
|
|
229
|
+
private readonly location;
|
|
230
|
+
/**
|
|
231
|
+
* @param auth - Shared GoogleAuth instance (D-03: injected by module.ts, not created here)
|
|
232
|
+
* @param projectId - GCP project that hosts the API Hub instance
|
|
233
|
+
* @param location - API Hub location (default: "global" — override if regional instance)
|
|
234
|
+
*/
|
|
235
|
+
constructor(auth: GoogleAuth, projectId?: string, location?: string);
|
|
236
|
+
/** Base path prefix shared by all API Hub resource URLs for this instance. */
|
|
237
|
+
private resourcePrefix;
|
|
238
|
+
/**
|
|
239
|
+
* Fetches every page of a paginated API Hub list endpoint, following
|
|
240
|
+
* `nextPageToken` until the collection is exhausted, and returns the
|
|
241
|
+
* concatenated items. API Hub caps list responses (commonly at 50 entries per
|
|
242
|
+
* page), so callers MUST page or risk silently truncating the catalogue.
|
|
243
|
+
*/
|
|
244
|
+
private fetchAllPages;
|
|
245
|
+
/**
|
|
246
|
+
* Lists all API definitions in this API Hub instance.
|
|
247
|
+
* Calls: GET /projects/{p}/locations/{l}/apis
|
|
248
|
+
*
|
|
249
|
+
* @param projectId - Optional override for the GCP project ID. When provided,
|
|
250
|
+
* overrides the instance-level projectId from the constructor. Allows a single
|
|
251
|
+
* ApiHubClient instance to serve multiple GCP projects (multi-org setups).
|
|
252
|
+
*
|
|
253
|
+
* Pages through all results, following nextPageToken until exhausted (API Hub
|
|
254
|
+
* caps list responses, commonly at 50 entries per page).
|
|
255
|
+
*/
|
|
256
|
+
listApis(projectId?: string): Promise<ApiHubApi[]>;
|
|
257
|
+
/**
|
|
258
|
+
* Lists all versions for a given API.
|
|
259
|
+
* Calls: GET /projects/{p}/locations/{l}/apis/{apiId}/versions
|
|
260
|
+
*
|
|
261
|
+
* @param apiId - Short API ID (last segment of the full resource name)
|
|
262
|
+
* @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.
|
|
263
|
+
*/
|
|
264
|
+
listVersions(apiId: string, projectId?: string): Promise<ApiHubVersion[]>;
|
|
265
|
+
/**
|
|
266
|
+
* Lists all specs attached to a given API version.
|
|
267
|
+
* Calls: GET /projects/{p}/locations/{l}/apis/{apiId}/versions/{versionId}/specs
|
|
268
|
+
*
|
|
269
|
+
* This is the authoritative source of spec IDs for a version. The versions
|
|
270
|
+
* list endpoint frequently returns version.specIds: null even when specs
|
|
271
|
+
* exist, so callers must enumerate specs here rather than trusting specIds.
|
|
272
|
+
*
|
|
273
|
+
* @param apiId - Short API ID (last segment of the full resource name)
|
|
274
|
+
* @param versionId - Short version ID (last segment of the version resource name)
|
|
275
|
+
* @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.
|
|
276
|
+
*/
|
|
277
|
+
listSpecs(apiId: string, versionId: string, projectId?: string): Promise<ApiHubSpec[]>;
|
|
278
|
+
/**
|
|
279
|
+
* Fetches the spec metadata for a given API version and spec ID.
|
|
280
|
+
* Calls: GET /projects/{p}/locations/{l}/apis/{apiId}/versions/{versionId}/specs/{specId}
|
|
281
|
+
*
|
|
282
|
+
* To retrieve the actual spec file content (base64), call getSpecContents() instead.
|
|
283
|
+
*
|
|
284
|
+
* @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.
|
|
285
|
+
*/
|
|
286
|
+
getSpec(apiId: string, versionId: string, specId: string, projectId?: string): Promise<ApiHubSpec>;
|
|
287
|
+
/**
|
|
288
|
+
* Fetches the raw spec file content for a given spec resource.
|
|
289
|
+
* Calls: GET .../specs/{specId}:contents
|
|
290
|
+
* Returns base64-encoded content in the `contents` field — caller must decode
|
|
291
|
+
* with Buffer.from(contents, 'base64').toString('utf-8').
|
|
292
|
+
*
|
|
293
|
+
* Used in Phase 3 for spec.definition population (with 500 KB size guard).
|
|
294
|
+
*
|
|
295
|
+
* @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.
|
|
296
|
+
*/
|
|
297
|
+
getSpecContents(apiId: string, versionId: string, specId: string, projectId?: string): Promise<ApiHubSpecContents>;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Minimal representation of an Apigee sharedflow as returned by
|
|
302
|
+
* GET /v1/organizations/{org}/sharedflows
|
|
303
|
+
*/
|
|
304
|
+
interface ApigeeSharedflow {
|
|
305
|
+
name: string;
|
|
306
|
+
latestRevisionId?: string;
|
|
307
|
+
metaData?: {
|
|
308
|
+
createdAt?: string;
|
|
309
|
+
lastModifiedAt?: string;
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Client for the Apigee Management API's sharedflow endpoints.
|
|
314
|
+
*/
|
|
315
|
+
declare class SharedflowClient {
|
|
316
|
+
private readonly auth;
|
|
317
|
+
constructor(auth: GoogleAuth);
|
|
318
|
+
/**
|
|
319
|
+
* Lists all sharedflows in the given Apigee organisation.
|
|
320
|
+
*/
|
|
321
|
+
listSharedflows(org: string): Promise<ApigeeSharedflow[]>;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Per-organisation data gathered during a sync run.
|
|
326
|
+
* Populated by ApigeeEntityProvider.run() before calling proxyToEntities().
|
|
327
|
+
*/
|
|
328
|
+
/** One environment this proxy is deployed to, with the revision deployed there. */
|
|
329
|
+
interface EnvDeployment {
|
|
330
|
+
/** Environment name (e.g. "dev-1") */
|
|
331
|
+
name: string;
|
|
332
|
+
/** Deployed API proxy revision in this environment (e.g. "1") */
|
|
333
|
+
revision: string;
|
|
334
|
+
}
|
|
335
|
+
interface OrgSyncData {
|
|
336
|
+
/** Slugified org identifier (used as annotation key segment) */
|
|
337
|
+
slug: string;
|
|
338
|
+
/** Raw org name from config (e.g. "my-apigee-org") */
|
|
339
|
+
name: string;
|
|
340
|
+
/** GCP project ID owning this Apigee organisation */
|
|
341
|
+
projectId: string;
|
|
342
|
+
/** Environments this proxy is deployed to in this org, each with its revision */
|
|
343
|
+
environments: EnvDeployment[];
|
|
344
|
+
/** Resolved Backstage lifecycle for this org: production | experimental | deprecated */
|
|
345
|
+
lifecycle: string;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* A proxy that has been deduplicated across organisations (D-03).
|
|
349
|
+
* Multiple orgs may share the same proxy name — they are merged into one entity.
|
|
350
|
+
*/
|
|
351
|
+
interface MergedProxy {
|
|
352
|
+
/** Proxy data from the first org that declared this proxy */
|
|
353
|
+
proxy: ApigeeProxy;
|
|
354
|
+
/** Union of all base paths declared across all org revisions */
|
|
355
|
+
basePaths: string[];
|
|
356
|
+
/** All orgs that contain this proxy */
|
|
357
|
+
orgs: OrgSyncData[];
|
|
358
|
+
}
|
|
359
|
+
/** Options forwarded from plugin configuration. */
|
|
360
|
+
interface EntityBuildOptions {
|
|
361
|
+
/** Default spec.owner when not provided by API Hub. Must be a Backstage entity ref. */
|
|
362
|
+
defaultOwner?: string;
|
|
363
|
+
/** Inline spec size limit in bytes; default 512000 applied by the provider (D-03). */
|
|
364
|
+
specMaxBytes?: number;
|
|
365
|
+
/**
|
|
366
|
+
* Optional map of Apigee environment name → Backstage lifecycle
|
|
367
|
+
* (production | experimental | deprecated). When set, a proxy's lifecycle is
|
|
368
|
+
* derived from the environments it is deployed to (highest-ranked wins).
|
|
369
|
+
*/
|
|
370
|
+
environmentLifecycle?: Record<string, string>;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Org configuration shape (matches config.d.ts apigee.orgs[n]) */
|
|
374
|
+
interface OrgConfig {
|
|
375
|
+
name: string;
|
|
376
|
+
projectId: string;
|
|
377
|
+
environments?: string[];
|
|
378
|
+
githubOrgSlug?: string;
|
|
379
|
+
/** proxyName → API Hub apiId override (D-01); set by module.ts from config. */
|
|
380
|
+
apiHubMappings?: Record<string, string>;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Entity provider that discovers Apigee API proxies and sharedflows across
|
|
384
|
+
* one or more organisations and registers them as Backstage entities.
|
|
385
|
+
*
|
|
386
|
+
* Registered with the catalog via `catalogProcessingExtensionPoint.addEntityProvider`.
|
|
387
|
+
* Scheduled via the `SchedulerServiceTaskRunner` injected at construction time.
|
|
388
|
+
*/
|
|
389
|
+
declare class ApigeeEntityProvider implements EntityProvider {
|
|
390
|
+
private readonly taskRunner;
|
|
391
|
+
private readonly logger;
|
|
392
|
+
private readonly orgsConfig;
|
|
393
|
+
private readonly apigeeClient;
|
|
394
|
+
private readonly apiHubClient;
|
|
395
|
+
private readonly sharedflowClient;
|
|
396
|
+
private readonly options;
|
|
397
|
+
private connection?;
|
|
398
|
+
/**
|
|
399
|
+
* Last successfully-fetched data per org name. Reused when a later sync of
|
|
400
|
+
* that org fails, so a `type: 'full'` mutation never deletes a transiently
|
|
401
|
+
* unavailable org's entities (they survive until that org syncs again).
|
|
402
|
+
*/
|
|
403
|
+
private readonly lastGoodOrgData;
|
|
404
|
+
/**
|
|
405
|
+
* Last successfully-resolved spec per `${projectId}::${apiId}`. Reused when a
|
|
406
|
+
* later live spec resolution throws (e.g. API Hub is down while an org is
|
|
407
|
+
* served from {@link lastGoodOrgData}), so the entity keeps its embedded spec
|
|
408
|
+
* instead of blinking to `has-spec: false`. Bounded by an LRU policy
|
|
409
|
+
* (MAX_SPEC_CACHE_ENTRIES / MAX_SPEC_CACHE_BYTES).
|
|
410
|
+
*/
|
|
411
|
+
private readonly lastGoodSpecs;
|
|
412
|
+
/** Running total of bytes retained in {@link lastGoodSpecs} (for LRU eviction). */
|
|
413
|
+
private specCacheBytes;
|
|
414
|
+
constructor(taskRunner: SchedulerServiceTaskRunner, logger: LoggerService, orgsConfig: OrgConfig[], apigeeClient: ApigeeClient, apiHubClient: ApiHubClient, sharedflowClient: SharedflowClient, options: EntityBuildOptions);
|
|
415
|
+
/** Stable provider name — MUST NOT change; it links entities to this provider. */
|
|
416
|
+
getProviderName(): string;
|
|
417
|
+
/**
|
|
418
|
+
* Called once by the catalog on startup. Stores the connection and registers
|
|
419
|
+
* the recurring sync task with the scheduler.
|
|
420
|
+
*/
|
|
421
|
+
connect(connection: EntityProviderConnection): Promise<void>;
|
|
422
|
+
/**
|
|
423
|
+
* Executes one full catalog sync:
|
|
424
|
+
* 1. Fetch proxy / sharedflow / API-Hub data for all orgs in parallel.
|
|
425
|
+
* 2. Reuse each failed org's last-known-good data so a `full` mutation
|
|
426
|
+
* never deletes its entities; skip the run entirely if no org succeeds.
|
|
427
|
+
* 3. Deduplicate proxies shared across multiple orgs (D-03).
|
|
428
|
+
* 4. Build entities and call applyMutation exactly once.
|
|
429
|
+
*/
|
|
430
|
+
run(): Promise<void>;
|
|
431
|
+
/**
|
|
432
|
+
* Maps `items` through `fn` with at most `limit` invocations in flight at
|
|
433
|
+
* once, preserving input order in the returned array. Used to parallelise
|
|
434
|
+
* per-API spec resolution without overwhelming the API Hub backend (WR-03).
|
|
435
|
+
*/
|
|
436
|
+
private mapWithConcurrency;
|
|
437
|
+
/**
|
|
438
|
+
* Fetches a spec's contents subject to the size guard (D-03).
|
|
439
|
+
*
|
|
440
|
+
* Two-stage guard:
|
|
441
|
+
* 1. Pre-fetch: if the spec metadata reports `sizeBytes > specMaxBytes`, skip
|
|
442
|
+
* the download entirely and return a `specUrl` link. NOTE: the API Hub spec
|
|
443
|
+
* metadata endpoint frequently reports `sizeBytes: 0`/undefined, so this
|
|
444
|
+
* stage often cannot fire and the contents are downloaded anyway.
|
|
445
|
+
* 2. Post-decode: enforce `specMaxBytes` against the actual decoded byte
|
|
446
|
+
* length — this is the guard that reliably bounds the *embedded* size.
|
|
447
|
+
*
|
|
448
|
+
* Because stage 1 is unreliable, `specMaxBytes` bounds the size of the spec
|
|
449
|
+
* that gets embedded, not necessarily the size downloaded into memory.
|
|
450
|
+
*/
|
|
451
|
+
private fetchSpecEmbed;
|
|
452
|
+
/**
|
|
453
|
+
* Resolves the spec for an API: lists versions, deterministically selects one
|
|
454
|
+
* (D-02), enumerates that version's specs (the versions endpoint does not
|
|
455
|
+
* reliably populate specIds), picks the lexicographically smallest spec ID,
|
|
456
|
+
* then fetches it with the size guard.
|
|
457
|
+
*
|
|
458
|
+
* Every successful resolution is cached in {@link lastGoodSpecs}. If a live
|
|
459
|
+
* resolution throws (e.g. API Hub is down while the owning org is served from
|
|
460
|
+
* cache), the last-known-good spec is returned so the entity keeps its
|
|
461
|
+
* embedded spec; with no prior spec, `undefined` is returned. Either way one
|
|
462
|
+
* bad spec never aborts the whole sync.
|
|
463
|
+
*/
|
|
464
|
+
private resolveSpecForApi;
|
|
465
|
+
/** Records a successfully-resolved spec (bounded LRU) and returns it unchanged. */
|
|
466
|
+
private rememberSpec;
|
|
467
|
+
/**
|
|
468
|
+
* Fetches all data for one org: proxies, their deployments + revisions,
|
|
469
|
+
* API Hub type map, and sharedflows.
|
|
470
|
+
*/
|
|
471
|
+
private fetchOrgData;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* A Backstage {@link CatalogProcessor} that links a backend `Component` to its
|
|
476
|
+
* proxy `API` entity by emitting the native `providesApi` / `apiProvidedBy`
|
|
477
|
+
* relation pair, surfacing the standard "Provided APIs" / "Providers" cards
|
|
478
|
+
* with zero custom UI (STITCH-01).
|
|
479
|
+
*
|
|
480
|
+
* Resolution dispatches through a strategy seam: a TargetServer-first path
|
|
481
|
+
* (deferred — STITCH-02, see 04-CONTEXT D-01) followed by the active
|
|
482
|
+
* direct-mapping path. The first strategy to return a target ref wins.
|
|
483
|
+
*/
|
|
484
|
+
declare class ApigeeStitchingProcessor implements CatalogProcessor {
|
|
485
|
+
private readonly logger;
|
|
486
|
+
constructor(logger: LoggerService);
|
|
487
|
+
getProcessorName(): string;
|
|
488
|
+
postProcessEntity(entity: Entity, _location: LocationSpec, emit: CatalogProcessorEmit): Promise<Entity>;
|
|
489
|
+
/**
|
|
490
|
+
* Strategy seam. Tries the TargetServer-preferred path first (deferred this
|
|
491
|
+
* phase), then falls back to the active direct-mapping path. First strategy
|
|
492
|
+
* to return a ref wins.
|
|
493
|
+
*/
|
|
494
|
+
private resolveTarget;
|
|
495
|
+
/**
|
|
496
|
+
* Deferred: TargetServer-preferred path (STITCH-02). See 04-CONTEXT D-01.
|
|
497
|
+
* No TargetServer data exists in the codebase yet, so this is the clean
|
|
498
|
+
* insertion point for the future branch and intentionally returns undefined.
|
|
499
|
+
*/
|
|
500
|
+
private resolveViaTargetServer;
|
|
501
|
+
/**
|
|
502
|
+
* Direct-mapping path. Requires BOTH the api-name and project-id annotations.
|
|
503
|
+
* The proxy API entity ref is deterministic — `api:default/<slugify(api-name)>` —
|
|
504
|
+
* so no live catalog lookup is needed (D-01). projectId is validated for
|
|
505
|
+
* presence to confirm intent but is not part of the deterministic ref.
|
|
506
|
+
*/
|
|
507
|
+
private resolveViaDirect;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export { ApiHubClient, ApigeeClient, ApigeeEntityProvider, ApigeeStitchingProcessor, SharedflowClient, catalogModuleApigee, apigeePlugin as default };
|
|
511
|
+
export type { ApiHubApi, ApiHubSpec, ApiHubSpecContents, ApiHubVersion, ApigeeDeployment, ApigeeProxy, ApigeeProxyRevision, ApigeeSharedflow, EntityBuildOptions, MergedProxy, OrgSyncData };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fetchUtils = require('./fetch-utils.cjs.js');
|
|
4
|
+
|
|
5
|
+
const APIHUB_BASE_URL = "https://apihub.googleapis.com/v1";
|
|
6
|
+
class ApiHubClient {
|
|
7
|
+
/**
|
|
8
|
+
* @param auth - Shared GoogleAuth instance (D-03: injected by module.ts, not created here)
|
|
9
|
+
* @param projectId - GCP project that hosts the API Hub instance
|
|
10
|
+
* @param location - API Hub location (default: "global" — override if regional instance)
|
|
11
|
+
*/
|
|
12
|
+
constructor(auth, projectId = "", location = "global") {
|
|
13
|
+
this.auth = auth;
|
|
14
|
+
this.projectId = projectId;
|
|
15
|
+
this.location = location;
|
|
16
|
+
}
|
|
17
|
+
auth;
|
|
18
|
+
projectId;
|
|
19
|
+
location;
|
|
20
|
+
/** Base path prefix shared by all API Hub resource URLs for this instance. */
|
|
21
|
+
resourcePrefix(projectId) {
|
|
22
|
+
const proj = projectId ?? this.projectId;
|
|
23
|
+
return `${APIHUB_BASE_URL}/projects/${encodeURIComponent(proj)}/locations/${encodeURIComponent(this.location)}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Fetches every page of a paginated API Hub list endpoint, following
|
|
27
|
+
* `nextPageToken` until the collection is exhausted, and returns the
|
|
28
|
+
* concatenated items. API Hub caps list responses (commonly at 50 entries per
|
|
29
|
+
* page), so callers MUST page or risk silently truncating the catalogue.
|
|
30
|
+
*/
|
|
31
|
+
async fetchAllPages(baseUrl, collection) {
|
|
32
|
+
const items = [];
|
|
33
|
+
let pageToken;
|
|
34
|
+
do {
|
|
35
|
+
const sep = baseUrl.includes("?") ? "&" : "?";
|
|
36
|
+
const url = pageToken ? `${baseUrl}${sep}pageToken=${encodeURIComponent(pageToken)}` : baseUrl;
|
|
37
|
+
const body = await fetchUtils.fetchWithRetry(
|
|
38
|
+
url,
|
|
39
|
+
() => this.auth.getAccessToken()
|
|
40
|
+
);
|
|
41
|
+
const page = body[collection] ?? [];
|
|
42
|
+
items.push(...page);
|
|
43
|
+
pageToken = typeof body.nextPageToken === "string" && body.nextPageToken.length > 0 ? body.nextPageToken : void 0;
|
|
44
|
+
} while (pageToken);
|
|
45
|
+
return items;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Lists all API definitions in this API Hub instance.
|
|
49
|
+
* Calls: GET /projects/{p}/locations/{l}/apis
|
|
50
|
+
*
|
|
51
|
+
* @param projectId - Optional override for the GCP project ID. When provided,
|
|
52
|
+
* overrides the instance-level projectId from the constructor. Allows a single
|
|
53
|
+
* ApiHubClient instance to serve multiple GCP projects (multi-org setups).
|
|
54
|
+
*
|
|
55
|
+
* Pages through all results, following nextPageToken until exhausted (API Hub
|
|
56
|
+
* caps list responses, commonly at 50 entries per page).
|
|
57
|
+
*/
|
|
58
|
+
async listApis(projectId) {
|
|
59
|
+
const url = `${this.resourcePrefix(projectId)}/apis`;
|
|
60
|
+
return this.fetchAllPages(url, "apis");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Lists all versions for a given API.
|
|
64
|
+
* Calls: GET /projects/{p}/locations/{l}/apis/{apiId}/versions
|
|
65
|
+
*
|
|
66
|
+
* @param apiId - Short API ID (last segment of the full resource name)
|
|
67
|
+
* @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.
|
|
68
|
+
*/
|
|
69
|
+
async listVersions(apiId, projectId) {
|
|
70
|
+
const url = `${this.resourcePrefix(projectId)}/apis/${encodeURIComponent(apiId)}/versions`;
|
|
71
|
+
return this.fetchAllPages(url, "versions");
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Lists all specs attached to a given API version.
|
|
75
|
+
* Calls: GET /projects/{p}/locations/{l}/apis/{apiId}/versions/{versionId}/specs
|
|
76
|
+
*
|
|
77
|
+
* This is the authoritative source of spec IDs for a version. The versions
|
|
78
|
+
* list endpoint frequently returns version.specIds: null even when specs
|
|
79
|
+
* exist, so callers must enumerate specs here rather than trusting specIds.
|
|
80
|
+
*
|
|
81
|
+
* @param apiId - Short API ID (last segment of the full resource name)
|
|
82
|
+
* @param versionId - Short version ID (last segment of the version resource name)
|
|
83
|
+
* @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.
|
|
84
|
+
*/
|
|
85
|
+
async listSpecs(apiId, versionId, projectId) {
|
|
86
|
+
const url = `${this.resourcePrefix(projectId)}/apis/${encodeURIComponent(apiId)}/versions/${encodeURIComponent(versionId)}/specs`;
|
|
87
|
+
return this.fetchAllPages(url, "specs");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Fetches the spec metadata for a given API version and spec ID.
|
|
91
|
+
* Calls: GET /projects/{p}/locations/{l}/apis/{apiId}/versions/{versionId}/specs/{specId}
|
|
92
|
+
*
|
|
93
|
+
* To retrieve the actual spec file content (base64), call getSpecContents() instead.
|
|
94
|
+
*
|
|
95
|
+
* @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.
|
|
96
|
+
*/
|
|
97
|
+
async getSpec(apiId, versionId, specId, projectId) {
|
|
98
|
+
const url = `${this.resourcePrefix(projectId)}/apis/${encodeURIComponent(apiId)}/versions/${encodeURIComponent(versionId)}/specs/${encodeURIComponent(specId)}`;
|
|
99
|
+
return await fetchUtils.fetchWithRetry(url, () => this.auth.getAccessToken());
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Fetches the raw spec file content for a given spec resource.
|
|
103
|
+
* Calls: GET .../specs/{specId}:contents
|
|
104
|
+
* Returns base64-encoded content in the `contents` field — caller must decode
|
|
105
|
+
* with Buffer.from(contents, 'base64').toString('utf-8').
|
|
106
|
+
*
|
|
107
|
+
* Used in Phase 3 for spec.definition population (with 500 KB size guard).
|
|
108
|
+
*
|
|
109
|
+
* @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.
|
|
110
|
+
*/
|
|
111
|
+
async getSpecContents(apiId, versionId, specId, projectId) {
|
|
112
|
+
const url = `${this.resourcePrefix(projectId)}/apis/${encodeURIComponent(apiId)}/versions/${encodeURIComponent(versionId)}/specs/${encodeURIComponent(specId)}:contents`;
|
|
113
|
+
return await fetchUtils.fetchWithRetry(url, () => this.auth.getAccessToken());
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
exports.ApiHubClient = ApiHubClient;
|
|
118
|
+
//# sourceMappingURL=api-hub-client.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-hub-client.cjs.js","sources":["../../src/lib/api-hub-client.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\r\nimport { GoogleAuth } from 'google-auth-library';\r\nimport { fetchWithRetry } from './fetch-utils';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Minimal response types — only the fields consumed in Phases 1-3.\r\n// Full Cloud API Hub REST v1 reference:\r\n// https://cloud.google.com/apigee/docs/apihub/reference/rest/v1/projects.locations.apis\r\n// ---------------------------------------------------------------------------\r\n\r\n/** One allowed value of a Cloud API Hub enum attribute (AllowedValue). */\r\nexport interface ApiHubAllowedValue {\r\n id?: string;\r\n displayName?: string;\r\n description?: string;\r\n immutable?: boolean;\r\n}\r\n\r\n/**\r\n * The value(s) assigned to a single attribute on an API resource\r\n * (Cloud API Hub AttributeValues). Exactly one of the *Values fields is set,\r\n * depending on the attribute's data type.\r\n */\r\nexport interface ApiHubAttributeValues {\r\n /** Attribute resource name these values belong to (output-only). */\r\n attribute?: string;\r\n enumValues?: { values?: ApiHubAllowedValue[] };\r\n stringValues?: { values?: string[] };\r\n jsonValues?: { values?: string[] };\r\n}\r\n\r\n/**\r\n * Represents a single API definition from Cloud API Hub.\r\n * GET /projects/{p}/locations/{l}/apis\r\n */\r\nexport interface ApiHubApi {\r\n /** Full resource name: projects/{p}/locations/{l}/apis/{id} */\r\n name: string;\r\n /** Human-readable display name */\r\n displayName?: string;\r\n /** Description from API Hub */\r\n description?: string;\r\n /**\r\n * System-defined \"Business unit\" attribute. In Cloud API Hub this is a\r\n * TOP-LEVEL field on the Api resource (an AttributeValues object), NOT an\r\n * entry inside `attributes`. Feeds the entity `department` label (see\r\n * departmentLabel in entity-builder).\r\n */\r\n businessUnit?: ApiHubAttributeValues;\r\n /**\r\n * User-defined attributes keyed by attribute resource name. System-defined\r\n * attributes (business unit, team, …) are top-level fields, not entries here;\r\n * scanned only as a fallback for a user-modelled business unit.\r\n */\r\n attributes?: Record<string, ApiHubAttributeValues>;\r\n /** Owner team name (maps to spec.owner in Backstage) */\r\n owner?: { displayName?: string; email?: string };\r\n /**\r\n * GitHub / source repo URI from API Hub metadata.\r\n * Maps to metadata.links in Backstage (FR17 / META-10).\r\n */\r\n sourceMetadata?: Array<{ sourceType?: string; value?: string }>;\r\n /**\r\n * Externally hosted documentation URI — the API Hub \"Documentation\" field.\r\n * NOS records the proxy's GitHub repository URL here, so it is the\r\n * authoritative source for the entity's \"Source repository\" link (META-10).\r\n */\r\n documentation?: { externalUri?: string };\r\n /** Array of category/tag IDs applied to this API in API Hub (META-09) */\r\n categories?: string[];\r\n /** API Hub catalog type (e.g. \"openapi\", \"mcp\") — maps to spec.type in Backstage */\r\n apiType?: string;\r\n /** ISO 8601 create/update timestamps */\r\n createTime?: string;\r\n updateTime?: string;\r\n}\r\n\r\n/**\r\n * Represents one version of an API Hub API.\r\n * GET /projects/{p}/locations/{l}/apis/{apiId}/versions\r\n */\r\nexport interface ApiHubVersion {\r\n /** Full resource name: .../apis/{id}/versions/{vid} */\r\n name: string;\r\n displayName?: string;\r\n description?: string;\r\n /** Version state: DRAFT | ACTIVE | DEPRECATED | ARCHIVED */\r\n lifecycle?: { stage?: string };\r\n /** Spec IDs attached to this version */\r\n specIds?: string[];\r\n createTime?: string;\r\n updateTime?: string;\r\n}\r\n\r\n/**\r\n * Represents a spec (OpenAPI document) attached to a version.\r\n * GET /projects/{p}/locations/{l}/apis/{apiId}/versions/{versionId}/specs/{specId}\r\n */\r\nexport interface ApiHubSpec {\r\n /** Full resource name */\r\n name: string;\r\n /** MIME type (e.g. \"application/json\", \"application/yaml\") */\r\n mimeType?: string;\r\n /** File name (e.g. \"openapi.yaml\") */\r\n filename?: string;\r\n /** Hash of spec contents for deduplication */\r\n hash?: string;\r\n /** Size in bytes — used by spec size guard in Phase 3 */\r\n sizeBytes?: number;\r\n createTime?: string;\r\n updateTime?: string;\r\n}\r\n\r\n/**\r\n * Raw spec contents response from the :contents sub-resource.\r\n * `contents` is base64-encoded (Cloud API Hub ApiSpecContents resource).\r\n */\r\nexport interface ApiHubSpecContents {\r\n mimeType?: string;\r\n /** Base64-encoded spec file content */\r\n contents?: string;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Client\r\n// ---------------------------------------------------------------------------\r\n\r\nconst APIHUB_BASE_URL = 'https://apihub.googleapis.com/v1';\r\n\r\n/**\r\n * Read-only HTTP client for the Cloud API Hub REST v1 API.\r\n *\r\n * Mirrors ApigeeClient exactly (D-04): same constructor pattern, same\r\n * fetchWithRetry delegation, same encodeURIComponent usage.\r\n *\r\n * Auth: uses the same GCP Application Default Credentials GoogleAuth instance\r\n * that is shared with ApigeeClient (D-03).\r\n *\r\n * Usage:\r\n * ```typescript\r\n * const auth = new GoogleAuth({ scopes: ['https://www.googleapis.com/auth/cloud-platform'] });\r\n * const client = new ApiHubClient(auth, 'my-gcp-project');\r\n * const apis = await client.listApis();\r\n * ```\r\n */\r\nexport class ApiHubClient {\r\n /**\r\n * @param auth - Shared GoogleAuth instance (D-03: injected by module.ts, not created here)\r\n * @param projectId - GCP project that hosts the API Hub instance\r\n * @param location - API Hub location (default: \"global\" — override if regional instance)\r\n */\r\n constructor(\r\n private readonly auth: GoogleAuth,\r\n private readonly projectId: string = '',\r\n private readonly location: string = 'global',\r\n ) {}\r\n\r\n /** Base path prefix shared by all API Hub resource URLs for this instance. */\r\n private resourcePrefix(projectId?: string): string {\r\n const proj = projectId ?? this.projectId;\r\n return (\r\n `${APIHUB_BASE_URL}/projects/${encodeURIComponent(proj)}` +\r\n `/locations/${encodeURIComponent(this.location)}`\r\n );\r\n }\r\n\r\n /**\r\n * Fetches every page of a paginated API Hub list endpoint, following\r\n * `nextPageToken` until the collection is exhausted, and returns the\r\n * concatenated items. API Hub caps list responses (commonly at 50 entries per\r\n * page), so callers MUST page or risk silently truncating the catalogue.\r\n */\r\n private async fetchAllPages<T>(\r\n baseUrl: string,\r\n collection: 'apis' | 'versions' | 'specs',\r\n ): Promise<T[]> {\r\n const items: T[] = [];\r\n let pageToken: string | undefined;\r\n do {\r\n const sep = baseUrl.includes('?') ? '&' : '?';\r\n const url = pageToken\r\n ? `${baseUrl}${sep}pageToken=${encodeURIComponent(pageToken)}`\r\n : baseUrl;\r\n const body = (await fetchWithRetry(url, () =>\r\n this.auth.getAccessToken(),\r\n )) as Record<string, unknown>;\r\n const page = (body[collection] as T[] | undefined) ?? [];\r\n items.push(...page);\r\n pageToken =\r\n typeof body.nextPageToken === 'string' && body.nextPageToken.length > 0\r\n ? body.nextPageToken\r\n : undefined;\r\n } while (pageToken);\r\n return items;\r\n }\r\n\r\n /**\r\n * Lists all API definitions in this API Hub instance.\r\n * Calls: GET /projects/{p}/locations/{l}/apis\r\n *\r\n * @param projectId - Optional override for the GCP project ID. When provided,\r\n * overrides the instance-level projectId from the constructor. Allows a single\r\n * ApiHubClient instance to serve multiple GCP projects (multi-org setups).\r\n *\r\n * Pages through all results, following nextPageToken until exhausted (API Hub\r\n * caps list responses, commonly at 50 entries per page).\r\n */\r\n async listApis(projectId?: string): Promise<ApiHubApi[]> {\r\n const url = `${this.resourcePrefix(projectId)}/apis`;\r\n return this.fetchAllPages<ApiHubApi>(url, 'apis');\r\n }\r\n\r\n /**\r\n * Lists all versions for a given API.\r\n * Calls: GET /projects/{p}/locations/{l}/apis/{apiId}/versions\r\n *\r\n * @param apiId - Short API ID (last segment of the full resource name)\r\n * @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.\r\n */\r\n async listVersions(apiId: string, projectId?: string): Promise<ApiHubVersion[]> {\r\n const url = `${this.resourcePrefix(projectId)}/apis/${encodeURIComponent(apiId)}/versions`;\r\n return this.fetchAllPages<ApiHubVersion>(url, 'versions');\r\n }\r\n\r\n /**\r\n * Lists all specs attached to a given API version.\r\n * Calls: GET /projects/{p}/locations/{l}/apis/{apiId}/versions/{versionId}/specs\r\n *\r\n * This is the authoritative source of spec IDs for a version. The versions\r\n * list endpoint frequently returns version.specIds: null even when specs\r\n * exist, so callers must enumerate specs here rather than trusting specIds.\r\n *\r\n * @param apiId - Short API ID (last segment of the full resource name)\r\n * @param versionId - Short version ID (last segment of the version resource name)\r\n * @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.\r\n */\r\n async listSpecs(\r\n apiId: string,\r\n versionId: string,\r\n projectId?: string,\r\n ): Promise<ApiHubSpec[]> {\r\n const url =\r\n `${this.resourcePrefix(projectId)}/apis/${encodeURIComponent(apiId)}` +\r\n `/versions/${encodeURIComponent(versionId)}/specs`;\r\n return this.fetchAllPages<ApiHubSpec>(url, 'specs');\r\n }\r\n\r\n /**\r\n * Fetches the spec metadata for a given API version and spec ID.\r\n * Calls: GET /projects/{p}/locations/{l}/apis/{apiId}/versions/{versionId}/specs/{specId}\r\n *\r\n * To retrieve the actual spec file content (base64), call getSpecContents() instead.\r\n *\r\n * @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.\r\n */\r\n async getSpec(\r\n apiId: string,\r\n versionId: string,\r\n specId: string,\r\n projectId?: string,\r\n ): Promise<ApiHubSpec> {\r\n const url =\r\n `${this.resourcePrefix(projectId)}/apis/${encodeURIComponent(apiId)}` +\r\n `/versions/${encodeURIComponent(versionId)}` +\r\n `/specs/${encodeURIComponent(specId)}`;\r\n return (await fetchWithRetry(url, () => this.auth.getAccessToken())) as ApiHubSpec;\r\n }\r\n\r\n /**\r\n * Fetches the raw spec file content for a given spec resource.\r\n * Calls: GET .../specs/{specId}:contents\r\n * Returns base64-encoded content in the `contents` field — caller must decode\r\n * with Buffer.from(contents, 'base64').toString('utf-8').\r\n *\r\n * Used in Phase 3 for spec.definition population (with 500 KB size guard).\r\n *\r\n * @param projectId - Optional GCP project override for multi-org setups; defaults to the instance projectId.\r\n */\r\n async getSpecContents(\r\n apiId: string,\r\n versionId: string,\r\n specId: string,\r\n projectId?: string,\r\n ): Promise<ApiHubSpecContents> {\r\n const url =\r\n `${this.resourcePrefix(projectId)}/apis/${encodeURIComponent(apiId)}` +\r\n `/versions/${encodeURIComponent(versionId)}` +\r\n `/specs/${encodeURIComponent(specId)}:contents`;\r\n return (await fetchWithRetry(url, () => this.auth.getAccessToken())) as ApiHubSpecContents;\r\n }\r\n}\r\n"],"names":["fetchWithRetry"],"mappings":"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"}
|