@agentxm/registry-client 0.28.4-bootstrap.0
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/LICENSE +110 -0
- package/README.md +9 -0
- package/dist/src/__generated__/registry-client.d.ts +6501 -0
- package/dist/src/__generated__/registry-client.js +2270 -0
- package/dist/src/admin-client.d.ts +181 -0
- package/dist/src/admin-client.js +165 -0
- package/dist/src/archive-cache.d.ts +46 -0
- package/dist/src/archive-cache.js +179 -0
- package/dist/src/atomic-write.d.ts +49 -0
- package/dist/src/atomic-write.js +60 -0
- package/dist/src/axm-package-meta.d.ts +31 -0
- package/dist/src/axm-package-meta.js +31 -0
- package/dist/src/cache-root.d.ts +11 -0
- package/dist/src/cache-root.js +43 -0
- package/dist/src/client.d.ts +270 -0
- package/dist/src/client.js +43 -0
- package/dist/src/deprecation-warning.d.ts +3 -0
- package/dist/src/deprecation-warning.js +16 -0
- package/dist/src/error-mapping.d.ts +82 -0
- package/dist/src/error-mapping.js +186 -0
- package/dist/src/errors.d.ts +107 -0
- package/dist/src/errors.js +98 -0
- package/dist/src/failure-mapping.d.ts +14 -0
- package/dist/src/failure-mapping.js +100 -0
- package/dist/src/fs-helpers.d.ts +12 -0
- package/dist/src/fs-helpers.js +13 -0
- package/dist/src/index.d.ts +33 -0
- package/dist/src/index.js +37 -0
- package/dist/src/integrity.d.ts +13 -0
- package/dist/src/integrity.js +17 -0
- package/dist/src/local-client.d.ts +24 -0
- package/dist/src/local-client.js +815 -0
- package/dist/src/network.d.ts +6 -0
- package/dist/src/network.js +6 -0
- package/dist/src/path-safety.d.ts +18 -0
- package/dist/src/path-safety.js +26 -0
- package/dist/src/purl-match.d.ts +28 -0
- package/dist/src/purl-match.js +35 -0
- package/dist/src/registry-url.d.ts +14 -0
- package/dist/src/registry-url.js +12 -0
- package/dist/src/remote-client.d.ts +27 -0
- package/dist/src/remote-client.js +725 -0
- package/dist/src/request-policy.d.ts +29 -0
- package/dist/src/request-policy.js +190 -0
- package/dist/src/response-body.d.ts +11 -0
- package/dist/src/response-body.js +32 -0
- package/dist/src/retry-after.d.ts +9 -0
- package/dist/src/retry-after.js +28 -0
- package/dist/src/translate.d.ts +20 -0
- package/dist/src/translate.js +170 -0
- package/dist/src/utils.d.ts +60 -0
- package/dist/src/utils.js +187 -0
- package/package.json +55 -0
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
// @effect-diagnostics anyUnknownInErrorContext:off — generated HTTP response errors are translated to typed registry failures by this registry adapter
|
|
2
|
+
/**
|
|
3
|
+
* Remote HTTPS registry client.
|
|
4
|
+
*
|
|
5
|
+
* Implements `RegistryClient` by delegating HTTP transport to the generated
|
|
6
|
+
* registry client and mapping all errors to the typed registry failure
|
|
7
|
+
* vocabulary with per-operation categories.
|
|
8
|
+
*
|
|
9
|
+
* @experimental This API is unstable and may change without notice.
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
13
|
+
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
14
|
+
import * as Effect from "effect/Effect";
|
|
15
|
+
import * as Option from "effect/Option";
|
|
16
|
+
import * as Schema from "effect/Schema";
|
|
17
|
+
import { sanitizeSuggestedAction, } from "@agentxm/registry-protocol/unstable/suggested-action";
|
|
18
|
+
import { parseExtensionFqnParts, toExtensionTypePlural, } from "@agentxm/extension-model/unstable/extensions/common";
|
|
19
|
+
import { decodeExtensionNameSync, toAuthor, ExtensionTypeSchema, } from "@agentxm/extension-model/unstable/extensions";
|
|
20
|
+
import { decodeHandleSync } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
21
|
+
import { CompanionPackageSchema } from "@agentxm/extension-model/unstable/package-urls";
|
|
22
|
+
import { PackageUrlSchema, } from "@agentxm/extension-model/unstable/packaging/package-url";
|
|
23
|
+
import { packagesToPackageUrlParts, ExtensionIndexSchema, } from "@agentxm/registry-protocol/unstable/registry/schema";
|
|
24
|
+
import { DiscoverPackagesResponseSchema } from "@agentxm/registry-protocol/unstable/registry/discover-schema";
|
|
25
|
+
import { decodeVersionSync } from "@agentxm/extension-model/unstable/version-constraints";
|
|
26
|
+
import { extensionLifecycleWarnings, pluralizeType, resolveVersionEntry } from "./utils.js";
|
|
27
|
+
import { isRegistryClientError, hasTagSuffix, getTag } from "./error-mapping.js";
|
|
28
|
+
import { RegistryOperationFailed, RegistryRequestFailed, withRegistrySemantics, } from "./errors.js";
|
|
29
|
+
import { captureRegistryErrorResponseBodies, mapRegistryFailure } from "./failure-mapping.js";
|
|
30
|
+
import { executeRegistryRequest, PUBLISH_REGISTRY_REQUEST_POLICY, } from "./request-policy.js";
|
|
31
|
+
import * as GeneratedRegistryClient from "./__generated__/registry-client.js";
|
|
32
|
+
// Reuse the established four-request cap from publish transport. Named and
|
|
33
|
+
// list discovery share the same registry service and must not multiply it via
|
|
34
|
+
// nested traversals.
|
|
35
|
+
const REGISTRY_READ_CONCURRENCY = 4;
|
|
36
|
+
const SETTLEMENT_READ_REQUEST_POLICY = {
|
|
37
|
+
requestTimeout: "10 seconds",
|
|
38
|
+
totalDeadline: "10 seconds",
|
|
39
|
+
maxAttempts: 1,
|
|
40
|
+
initialBackoff: "200 millis",
|
|
41
|
+
maxBackoff: "2 seconds",
|
|
42
|
+
};
|
|
43
|
+
import { PreviewPublicationSetResponseSchema, validatePublicationDescriptors, validatePublicationSetResponse, } from "@agentxm/registry-protocol/unstable/registry/publication-set";
|
|
44
|
+
// -----------------------------------------------------------------------------
|
|
45
|
+
// Type Mapping Helpers
|
|
46
|
+
// -----------------------------------------------------------------------------
|
|
47
|
+
const decodeExtensionType = Schema.decodeUnknownSync(ExtensionTypeSchema);
|
|
48
|
+
const decodeExtensionIndex = Schema.decodeUnknownSync(Schema.toType(ExtensionIndexSchema));
|
|
49
|
+
const decodeCompanionPackages = Schema.decodeUnknownSync(Schema.Array(CompanionPackageSchema));
|
|
50
|
+
const encodePackageUrl = Schema.encodeSync(PackageUrlSchema);
|
|
51
|
+
const normalizeRegistrySuggestedAction = (suggestion) => sanitizeSuggestedAction({
|
|
52
|
+
description: suggestion.description,
|
|
53
|
+
...(suggestion.cmd === undefined || suggestion.cmd === null ? {} : { cmd: suggestion.cmd }),
|
|
54
|
+
...(suggestion.url === undefined || suggestion.url === null ? {} : { url: suggestion.url }),
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Narrow a string to ExtensionType via Schema validation.
|
|
58
|
+
* The generated client uses a wider union than our domain type;
|
|
59
|
+
* this validates the value is within our supported subset.
|
|
60
|
+
*/
|
|
61
|
+
const narrowExtensionType = (type) => decodeExtensionType(type);
|
|
62
|
+
const decodePublicationSetResponse = Schema.decodeUnknownSync(PreviewPublicationSetResponseSchema);
|
|
63
|
+
const encodePublicationSetRequest = (args) => ({
|
|
64
|
+
contract: args.contract,
|
|
65
|
+
candidates: args.candidates.map((descriptor) => ({
|
|
66
|
+
target: descriptor.target,
|
|
67
|
+
participation: descriptor.participation,
|
|
68
|
+
...(descriptor.archiveSha256Hex === undefined
|
|
69
|
+
? {}
|
|
70
|
+
: { archiveSha256Hex: descriptor.archiveSha256Hex }),
|
|
71
|
+
visibility: descriptor.visibility,
|
|
72
|
+
...(descriptor.pack === undefined ? {} : { pack: descriptor.pack }),
|
|
73
|
+
})),
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Map the generated ExtensionsGet200 response to our domain ExtensionIndex type.
|
|
77
|
+
*
|
|
78
|
+
* Validates against the type side of ExtensionIndexSchema, so timestamps the
|
|
79
|
+
* generated client already decoded to DateTime.Utc flow through without an
|
|
80
|
+
* encode/re-decode round-trip. Companion packages are the one remaining
|
|
81
|
+
* wire-form field (versionRange strings), so they decode via the wire schema.
|
|
82
|
+
*/
|
|
83
|
+
const mapToExtensionIndex = (response) => decodeExtensionIndex({
|
|
84
|
+
name: response.name,
|
|
85
|
+
owner: response.owner,
|
|
86
|
+
type: narrowExtensionType(response.type),
|
|
87
|
+
publisherBindingId: response.publisher_binding_id,
|
|
88
|
+
description: response.description ?? undefined,
|
|
89
|
+
repository: response.repository ?? undefined,
|
|
90
|
+
license: response.license ?? undefined,
|
|
91
|
+
authors: response.authors === null || response.authors === undefined
|
|
92
|
+
? undefined
|
|
93
|
+
: response.authors.map((a) => ({
|
|
94
|
+
name: a.name ?? "",
|
|
95
|
+
email: a.email ?? undefined,
|
|
96
|
+
url: a.url ?? undefined,
|
|
97
|
+
})),
|
|
98
|
+
visibility: response.visibility ?? undefined,
|
|
99
|
+
deprecation: response.deprecation === null
|
|
100
|
+
? null
|
|
101
|
+
: response.deprecation.message !== undefined && response.deprecation.message !== null
|
|
102
|
+
? {
|
|
103
|
+
deprecatedAt: response.deprecation.deprecatedAt,
|
|
104
|
+
message: response.deprecation.message,
|
|
105
|
+
...(response.deprecation.replacement === undefined ||
|
|
106
|
+
response.deprecation.replacement === null
|
|
107
|
+
? {}
|
|
108
|
+
: {
|
|
109
|
+
replacement: response.deprecation.replacement.status === "available"
|
|
110
|
+
? response.deprecation.replacement
|
|
111
|
+
: {
|
|
112
|
+
status: "unavailable",
|
|
113
|
+
...(response.deprecation.replacement.fqn === undefined ||
|
|
114
|
+
response.deprecation.replacement.fqn === null
|
|
115
|
+
? {}
|
|
116
|
+
: { fqn: response.deprecation.replacement.fqn }),
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
}
|
|
120
|
+
: {
|
|
121
|
+
deprecatedAt: response.deprecation.deprecatedAt,
|
|
122
|
+
replacement: response.deprecation.replacement?.status === "available"
|
|
123
|
+
? response.deprecation.replacement
|
|
124
|
+
: {
|
|
125
|
+
status: "unavailable",
|
|
126
|
+
...(response.deprecation.replacement?.fqn === undefined ||
|
|
127
|
+
response.deprecation.replacement.fqn === null
|
|
128
|
+
? {}
|
|
129
|
+
: { fqn: response.deprecation.replacement.fqn }),
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
versions: response.versions.map((v) => ({
|
|
133
|
+
version: v.version,
|
|
134
|
+
published: v.published,
|
|
135
|
+
integrity: v.integrity,
|
|
136
|
+
dependencies: v.dependencies === null ? undefined : v.dependencies,
|
|
137
|
+
packages: v.packages === null || v.packages === undefined
|
|
138
|
+
? undefined
|
|
139
|
+
: decodeCompanionPackages(v.packages),
|
|
140
|
+
yankedAt: v.yanked_at ?? undefined,
|
|
141
|
+
yankCategory: v.yank_category ?? undefined,
|
|
142
|
+
yankNotice: v.yank_notice ?? undefined,
|
|
143
|
+
})),
|
|
144
|
+
});
|
|
145
|
+
/**
|
|
146
|
+
* Convert an ExtensionIndex + version constraint to a RegistryExtensionManifest.
|
|
147
|
+
*/
|
|
148
|
+
const toRegistryManifest = (index, versionRange) => {
|
|
149
|
+
const selected = resolveVersionEntry(index.versions, versionRange);
|
|
150
|
+
if (Option.isNone(selected))
|
|
151
|
+
return Option.none();
|
|
152
|
+
const latest = selected.value;
|
|
153
|
+
const lifecycleWarnings = extensionLifecycleWarnings(index, latest);
|
|
154
|
+
return Option.some({
|
|
155
|
+
owner: index.owner,
|
|
156
|
+
type: index.type,
|
|
157
|
+
name: index.name,
|
|
158
|
+
publisherBindingId: index.publisherBindingId,
|
|
159
|
+
description: Option.fromUndefinedOr(index.description),
|
|
160
|
+
repository: Option.fromUndefinedOr(index.repository),
|
|
161
|
+
bugs: Option.fromUndefinedOr(index.bugs),
|
|
162
|
+
license: Option.fromUndefinedOr(index.license),
|
|
163
|
+
authors: index.authors === undefined ? [] : index.authors.map((author) => toAuthor(author)),
|
|
164
|
+
dependencies: latest.dependencies ?? {},
|
|
165
|
+
version: latest.version,
|
|
166
|
+
integrity: latest.integrity,
|
|
167
|
+
packages: packagesToPackageUrlParts(latest.packages),
|
|
168
|
+
...(index.deprecation === null ? {} : { deprecation: index.deprecation }),
|
|
169
|
+
...(lifecycleWarnings.length === 0 ? {} : { lifecycleWarnings }),
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
// -----------------------------------------------------------------------------
|
|
173
|
+
// Remote Registry Client
|
|
174
|
+
// -----------------------------------------------------------------------------
|
|
175
|
+
const remoteDiscoveryTypes = [
|
|
176
|
+
"skill",
|
|
177
|
+
"mcp-server",
|
|
178
|
+
"subagent",
|
|
179
|
+
"pack",
|
|
180
|
+
];
|
|
181
|
+
const packageIdentity = (parts) => ({
|
|
182
|
+
type: parts.type,
|
|
183
|
+
name: parts.name,
|
|
184
|
+
...(parts.namespace === undefined ? {} : { namespace: parts.namespace }),
|
|
185
|
+
...(parts.qualifiers === undefined ? {} : { qualifiers: parts.qualifiers }),
|
|
186
|
+
...(parts.subpath === undefined ? {} : { subpath: parts.subpath }),
|
|
187
|
+
});
|
|
188
|
+
const extensionDeclarationToDiscoveryRef = (value) => {
|
|
189
|
+
const parts = parseExtensionFqnParts(value.ref);
|
|
190
|
+
if (parts === undefined) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
ref: `${parts.owner}/${toExtensionTypePlural(parts.type)}/${parts.name}`,
|
|
195
|
+
...(value.versionRange === undefined || value.versionRange === null
|
|
196
|
+
? {}
|
|
197
|
+
: { versionRange: value.versionRange }),
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
const registryRequestMetadata = (method, url) => ({
|
|
201
|
+
service: "registry",
|
|
202
|
+
method,
|
|
203
|
+
url,
|
|
204
|
+
});
|
|
205
|
+
/**
|
|
206
|
+
* Creates a remote HTTPS registry client.
|
|
207
|
+
*
|
|
208
|
+
* Uses the generated registry client for all HTTP transport and maps
|
|
209
|
+
* generated errors to typed registry failure categories per-operation.
|
|
210
|
+
*
|
|
211
|
+
* @param baseUrl - Base URL of the remote registry (e.g. `https://registry.example.com`)
|
|
212
|
+
* @param httpClient - Effect HttpClient instance for making HTTP requests
|
|
213
|
+
*
|
|
214
|
+
* @experimental This API is unstable and may change without notice.
|
|
215
|
+
*/
|
|
216
|
+
export const createRemoteRegistryClient = (baseUrl, httpClient, archiveCache, requestPolicy) => {
|
|
217
|
+
const remoteHttpClient = captureRegistryErrorResponseBodies(httpClient.pipe(HttpClient.mapRequest(HttpClientRequest.prependUrl(baseUrl))));
|
|
218
|
+
const client = GeneratedRegistryClient.make(remoteHttpClient);
|
|
219
|
+
const verificationArchiveClient = GeneratedRegistryClient.make(remoteHttpClient, {
|
|
220
|
+
transformClient: (baseClient) => Effect.succeed(baseClient.pipe(HttpClient.mapRequest(HttpClientRequest.setHeader("x-agentxm-usage-purpose", "verification")))),
|
|
221
|
+
});
|
|
222
|
+
const executeRemoteRequest = (effect, args) => executeRegistryRequest(effect, {
|
|
223
|
+
operation: args.operation,
|
|
224
|
+
request: registryRequestMetadata(args.method, new URL(args.path, baseUrl).href),
|
|
225
|
+
replaySafety: args.replaySafety,
|
|
226
|
+
mapError: args.mapError,
|
|
227
|
+
...(requestPolicy === undefined && args.policy === undefined
|
|
228
|
+
? {}
|
|
229
|
+
: { policy: requestPolicy ?? args.policy }),
|
|
230
|
+
});
|
|
231
|
+
const safe = { kind: "safe" };
|
|
232
|
+
const mutation = { kind: "mutation" };
|
|
233
|
+
const mapFailure = (error, context) => mapRegistryFailure(error, {
|
|
234
|
+
baseUrl,
|
|
235
|
+
requestConstructionDetail: "Could not construct the Registry request.",
|
|
236
|
+
...context,
|
|
237
|
+
});
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// getExtensionIndex
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
const getExtensionIndex = (args) => executeRemoteRequest(client.ExtensionsGet(args.owner, pluralizeType(args.type), args.name, undefined).pipe(
|
|
242
|
+
// Resolve the HTTP outcome first (404 → absent), then decode the index in
|
|
243
|
+
// the effect so a schema-drift SchemaError is mapped through the error
|
|
244
|
+
// channel (mapDiscoveryError's isSchemaError branch) instead of throwing
|
|
245
|
+
// an uncatchable defect out of Effect.map.
|
|
246
|
+
Effect.map((response) => Option.some(response)), Effect.catch((e) => isRegistryClientError("ExtensionsGet404")(e)
|
|
247
|
+
? Effect.succeed(Option.none())
|
|
248
|
+
: Effect.fail(e))), {
|
|
249
|
+
operation: "get extension index",
|
|
250
|
+
method: "GET",
|
|
251
|
+
path: `/v1/extensions/${args.owner}/${pluralizeType(args.type)}/${args.name}`,
|
|
252
|
+
replaySafety: safe,
|
|
253
|
+
mapError: (error) => mapDiscoveryError(error, "REGISTRY_REMOTE_DISCOVERY"),
|
|
254
|
+
}).pipe(Effect.flatMap((response) => {
|
|
255
|
+
if (Option.isNone(response)) {
|
|
256
|
+
return Effect.succeed(Option.none());
|
|
257
|
+
}
|
|
258
|
+
const responseValue = response.value;
|
|
259
|
+
if (responseValue === undefined) {
|
|
260
|
+
return Effect.fail(new RegistryRequestFailed({
|
|
261
|
+
category: "internal",
|
|
262
|
+
detail: "Remote Registry returned an extension index without a body",
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
return Effect.try({
|
|
266
|
+
try: () => Option.some(mapToExtensionIndex(responseValue)),
|
|
267
|
+
catch: (cause) => mapDiscoveryError(cause, "REGISTRY_REMOTE_DISCOVERY"),
|
|
268
|
+
});
|
|
269
|
+
}));
|
|
270
|
+
const getExactExtensionVersion = (args) => {
|
|
271
|
+
const exactClient = GeneratedRegistryClient.make(remoteHttpClient, {
|
|
272
|
+
transformClient: (baseClient) => Effect.succeed(args.accessToken === undefined
|
|
273
|
+
? baseClient
|
|
274
|
+
: baseClient.pipe(HttpClient.mapRequest(HttpClientRequest.bearerToken(args.accessToken)))),
|
|
275
|
+
});
|
|
276
|
+
return executeRemoteRequest(exactClient
|
|
277
|
+
.ExtensionsGetVersion(args.owner, pluralizeType(args.type), args.name, args.version, undefined)
|
|
278
|
+
.pipe(Effect.map((response) => Option.some({
|
|
279
|
+
owner: decodeHandleSync(response.owner),
|
|
280
|
+
type: narrowExtensionType(response.type),
|
|
281
|
+
name: decodeExtensionNameSync(response.name),
|
|
282
|
+
version: decodeVersionSync(response.version),
|
|
283
|
+
integrity: response.integrity,
|
|
284
|
+
status: response.status,
|
|
285
|
+
})), Effect.catch((error) => isRegistryClientError("ExtensionsGetVersion404")(error)
|
|
286
|
+
? Effect.succeed(Option.none())
|
|
287
|
+
: Effect.fail(error))), {
|
|
288
|
+
operation: "get exact extension version",
|
|
289
|
+
method: "GET",
|
|
290
|
+
path: `/v1/extensions/${args.owner}/${pluralizeType(args.type)}/${args.name}/${args.version}`,
|
|
291
|
+
replaySafety: safe,
|
|
292
|
+
mapError: (error) => mapDiscoveryError(error, "REGISTRY_REMOTE_DISCOVERY"),
|
|
293
|
+
policy: SETTLEMENT_READ_REQUEST_POLICY,
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* Map all discovery/read errors to typed registry failures.
|
|
298
|
+
*/
|
|
299
|
+
const mapDiscoveryError = (e, _prefix) => {
|
|
300
|
+
return mapFailure(e, {
|
|
301
|
+
networkDetail: "Failed to connect to remote registry discovery endpoint",
|
|
302
|
+
incompatibleDetail: "Remote discovery response does not match expected schema",
|
|
303
|
+
fallbackDetail: "Remote discovery failed",
|
|
304
|
+
});
|
|
305
|
+
};
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
307
|
+
// getExtensionsByScope
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
const getExtensionsByScope = (args) => Effect.gen(function* () {
|
|
310
|
+
let allExtensions;
|
|
311
|
+
const owner = args.owner;
|
|
312
|
+
if (args.names.length === 0 || owner === "*") {
|
|
313
|
+
// List mode: fetch owner listing, then fan-out to get full indexes
|
|
314
|
+
allExtensions = yield* getListModeExtensions(args);
|
|
315
|
+
if (args.names.length > 0) {
|
|
316
|
+
const nameSet = new Set(args.names);
|
|
317
|
+
const requestedTypes = new Set(args.types);
|
|
318
|
+
allExtensions = allExtensions.filter((entry) => nameSet.has(entry.name) &&
|
|
319
|
+
(requestedTypes.size === 0 || requestedTypes.has(entry.type)));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
// Named mode: fetch each name+type combination
|
|
324
|
+
const requestedTypes = args.types.length > 0 ? args.types : remoteDiscoveryTypes;
|
|
325
|
+
const requests = args.names.flatMap((name) => requestedTypes.map((type) => ({ name, type })));
|
|
326
|
+
const maybeEntries = yield* Effect.forEach(requests, ({ name, type }) => Effect.sync(() => {
|
|
327
|
+
try {
|
|
328
|
+
return decodeExtensionNameSync(name);
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
}).pipe(Effect.flatMap((decodedName) => decodedName === undefined
|
|
334
|
+
? Effect.succeed(Option.none())
|
|
335
|
+
: getExtensionIndex({ owner, type, name: decodedName }))), { concurrency: REGISTRY_READ_CONCURRENCY });
|
|
336
|
+
allExtensions = maybeEntries.flatMap((entry) => Option.match(entry, {
|
|
337
|
+
onNone: () => [],
|
|
338
|
+
onSome: (value) => Option.match(toRegistryManifest(value, Option.none()), {
|
|
339
|
+
onNone: () => [],
|
|
340
|
+
onSome: (manifest) => [manifest],
|
|
341
|
+
}),
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
const total = allExtensions.length;
|
|
345
|
+
const sliced = allExtensions.slice(args.offset);
|
|
346
|
+
const extensions = Option.match(args.limit, {
|
|
347
|
+
onNone: () => sliced,
|
|
348
|
+
onSome: (limit) => sliced.slice(0, limit),
|
|
349
|
+
});
|
|
350
|
+
return { extensions, total };
|
|
351
|
+
});
|
|
352
|
+
const getListModeExtensions = (args) => Effect.gen(function* () {
|
|
353
|
+
// Fetch extension lists by type
|
|
354
|
+
const listResults = args.types.length === 0
|
|
355
|
+
? [yield* fetchExtensionList(args.owner)]
|
|
356
|
+
: yield* Effect.forEach(args.types, (type) => fetchExtensionListByType(args.owner, type), { concurrency: REGISTRY_READ_CONCURRENCY });
|
|
357
|
+
const summaries = listResults.flat();
|
|
358
|
+
// Fetch full indexes for each extension
|
|
359
|
+
const maybeEntries = yield* Effect.forEach(summaries, (summary) => getExtensionIndex({
|
|
360
|
+
owner: decodeHandleSync(summary.owner),
|
|
361
|
+
type: narrowExtensionType(summary.type),
|
|
362
|
+
name: decodeExtensionNameSync(summary.name),
|
|
363
|
+
}), { concurrency: REGISTRY_READ_CONCURRENCY });
|
|
364
|
+
const allExtensions = maybeEntries.flatMap((entry) => Option.match(entry, {
|
|
365
|
+
onNone: () => [],
|
|
366
|
+
onSome: (value) => Option.match(toRegistryManifest(value, Option.none()), {
|
|
367
|
+
onNone: () => [],
|
|
368
|
+
onSome: (manifest) => [manifest],
|
|
369
|
+
}),
|
|
370
|
+
}));
|
|
371
|
+
const sorted = [...allExtensions].sort((a, b) => {
|
|
372
|
+
if (a.owner !== b.owner)
|
|
373
|
+
return a.owner.localeCompare(b.owner);
|
|
374
|
+
if (a.name !== b.name)
|
|
375
|
+
return a.name.localeCompare(b.name);
|
|
376
|
+
return a.type.localeCompare(b.type);
|
|
377
|
+
});
|
|
378
|
+
return sorted;
|
|
379
|
+
});
|
|
380
|
+
const fetchExtensionList = (owner) => executeRemoteRequest(client.ExtensionsListByOwner(owner, undefined), {
|
|
381
|
+
operation: "list owner extensions",
|
|
382
|
+
method: "GET",
|
|
383
|
+
path: `/v1/extensions/${owner}`,
|
|
384
|
+
replaySafety: safe,
|
|
385
|
+
mapError: (error) => mapDiscoveryError(error, "REGISTRY_REMOTE_DISCOVERY"),
|
|
386
|
+
}).pipe(Effect.map((response) => response.extensions));
|
|
387
|
+
const fetchExtensionListByType = (owner, type) => executeRemoteRequest(client.ExtensionsListByType(owner, pluralizeType(type), undefined), {
|
|
388
|
+
operation: "list owner extensions by type",
|
|
389
|
+
method: "GET",
|
|
390
|
+
path: `/v1/extensions/${owner}/${pluralizeType(type)}`,
|
|
391
|
+
replaySafety: safe,
|
|
392
|
+
mapError: (error) => mapDiscoveryError(error, "REGISTRY_REMOTE_DISCOVERY"),
|
|
393
|
+
}).pipe(Effect.map((response) => response.extensions));
|
|
394
|
+
// ---------------------------------------------------------------------------
|
|
395
|
+
// ownerExists
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
const ownerExists = (owner) => executeRemoteRequest(client.OwnersGetOwner(owner, undefined).pipe(Effect.as({ exists: true }), Effect.catch((e) => {
|
|
398
|
+
// 404 → not found
|
|
399
|
+
if (hasTagSuffix(e, "404")) {
|
|
400
|
+
return Effect.succeed({ exists: false });
|
|
401
|
+
}
|
|
402
|
+
return Effect.fail(e);
|
|
403
|
+
})), {
|
|
404
|
+
operation: "get owner",
|
|
405
|
+
method: "GET",
|
|
406
|
+
path: `/v1/owners/${owner}`,
|
|
407
|
+
replaySafety: safe,
|
|
408
|
+
mapError: mapOwnerExistsError,
|
|
409
|
+
});
|
|
410
|
+
/**
|
|
411
|
+
* Map ownerExists errors to typed registry failures.
|
|
412
|
+
*/
|
|
413
|
+
const mapOwnerExistsError = (e) => {
|
|
414
|
+
return mapFailure(e, {
|
|
415
|
+
networkDetail: "Failed to connect to remote registry owner endpoint",
|
|
416
|
+
incompatibleDetail: "Remote owner endpoint response does not match expected schema",
|
|
417
|
+
fallbackDetail: "Remote owner check failed",
|
|
418
|
+
});
|
|
419
|
+
};
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
// getExtensionPackage
|
|
422
|
+
// ---------------------------------------------------------------------------
|
|
423
|
+
const getExtensionPackage = (args) => Effect.gen(function* () {
|
|
424
|
+
// Step 1: Fetch extension index
|
|
425
|
+
const indexResult = yield* executeRemoteRequest(client.ExtensionsGet(args.owner, pluralizeType(args.type), args.name, undefined), {
|
|
426
|
+
operation: "get package index",
|
|
427
|
+
method: "GET",
|
|
428
|
+
path: `/v1/extensions/${args.owner}/${pluralizeType(args.type)}/${args.name}`,
|
|
429
|
+
replaySafety: safe,
|
|
430
|
+
mapError: mapPackageFetchError,
|
|
431
|
+
});
|
|
432
|
+
if (indexResult === undefined) {
|
|
433
|
+
return yield* new RegistryRequestFailed({
|
|
434
|
+
category: "internal",
|
|
435
|
+
detail: "Remote Registry returned a package index without a body",
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
const index = yield* Effect.try({
|
|
439
|
+
try: () => mapToExtensionIndex(indexResult),
|
|
440
|
+
catch: (cause) => mapDiscoveryError(cause, "REGISTRY_REMOTE_DISCOVERY"),
|
|
441
|
+
});
|
|
442
|
+
// Step 2: Resolve version
|
|
443
|
+
const resolvedEntry = resolveVersionEntry(index.versions, args.version);
|
|
444
|
+
if (Option.isNone(resolvedEntry)) {
|
|
445
|
+
return yield* new RegistryOperationFailed({
|
|
446
|
+
category: "not_found",
|
|
447
|
+
detail: "Requested package version is not available in remote index",
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (archiveCache !== undefined) {
|
|
451
|
+
const cached = yield* archiveCache.read(resolvedEntry.value.integrity);
|
|
452
|
+
if (Option.isSome(cached)) {
|
|
453
|
+
const warnings = extensionLifecycleWarnings(index, resolvedEntry.value);
|
|
454
|
+
return {
|
|
455
|
+
archive: cached.value,
|
|
456
|
+
...(warnings.length === 0 ? {} : { warnings }),
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
// Step 3: Download archive
|
|
461
|
+
const archiveClient = args.usagePurpose === "verification" ? verificationArchiveClient : client;
|
|
462
|
+
const archive = yield* executeRemoteRequest(archiveClient.ExtensionsDownloadArchive(args.owner, pluralizeType(args.type), args.name, resolvedEntry.value.version, undefined), {
|
|
463
|
+
operation: "download package archive",
|
|
464
|
+
method: "GET",
|
|
465
|
+
path: `/v1/extensions/${args.owner}/${pluralizeType(args.type)}/${args.name}/${resolvedEntry.value.version}/archive`,
|
|
466
|
+
replaySafety: safe,
|
|
467
|
+
mapError: mapArchiveFetchError,
|
|
468
|
+
});
|
|
469
|
+
if (archiveCache !== undefined) {
|
|
470
|
+
yield* archiveCache.write(resolvedEntry.value.integrity, archive);
|
|
471
|
+
}
|
|
472
|
+
const warnings = extensionLifecycleWarnings(index, resolvedEntry.value);
|
|
473
|
+
return {
|
|
474
|
+
archive,
|
|
475
|
+
...(warnings.length === 0 ? {} : { warnings }),
|
|
476
|
+
};
|
|
477
|
+
});
|
|
478
|
+
/**
|
|
479
|
+
* Map errors from the index fetch step of getExtensionPackage.
|
|
480
|
+
*/
|
|
481
|
+
const mapPackageFetchError = (e) => {
|
|
482
|
+
const mapped = mapFailure(e, {
|
|
483
|
+
networkDetail: "Failed to connect to remote registry package endpoint",
|
|
484
|
+
incompatibleDetail: "Remote package index response does not match expected schema",
|
|
485
|
+
fallbackDetail: "Remote package index request failed",
|
|
486
|
+
});
|
|
487
|
+
return mapped.metadata?.response?.status === 404
|
|
488
|
+
? withRegistrySemantics(mapped, {
|
|
489
|
+
category: "not_found",
|
|
490
|
+
detail: "Remote package index was not found",
|
|
491
|
+
})
|
|
492
|
+
: mapped;
|
|
493
|
+
};
|
|
494
|
+
/**
|
|
495
|
+
* Map errors from the archive download step of getExtensionPackage.
|
|
496
|
+
*/
|
|
497
|
+
const mapArchiveFetchError = (e) => {
|
|
498
|
+
const mapped = mapFailure(e, {
|
|
499
|
+
networkDetail: "Failed to connect to remote registry package archive endpoint",
|
|
500
|
+
incompatibleDetail: "Failed to read remote package archive response",
|
|
501
|
+
fallbackDetail: "Remote package archive request failed",
|
|
502
|
+
});
|
|
503
|
+
return mapped.metadata?.response?.status === 404
|
|
504
|
+
? withRegistrySemantics(mapped, {
|
|
505
|
+
category: "not_found",
|
|
506
|
+
detail: "Remote package archive was not found",
|
|
507
|
+
})
|
|
508
|
+
: mapped;
|
|
509
|
+
};
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
// extensionExists
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
const extensionExists = (args) => executeRemoteRequest(client.ExtensionsHead(args.owner, pluralizeType(args.type), args.name, undefined).pipe(Effect.map(() => ({ exists: true })), Effect.catch((e) => {
|
|
514
|
+
if (getTag(e) === "404") {
|
|
515
|
+
return Effect.succeed({ exists: false });
|
|
516
|
+
}
|
|
517
|
+
return Effect.fail(e);
|
|
518
|
+
})), {
|
|
519
|
+
operation: "check extension",
|
|
520
|
+
method: "HEAD",
|
|
521
|
+
path: `/v1/extensions/${args.owner}/${pluralizeType(args.type)}/${args.name}`,
|
|
522
|
+
replaySafety: safe,
|
|
523
|
+
mapError: mapExtensionExistsError,
|
|
524
|
+
});
|
|
525
|
+
/**
|
|
526
|
+
* Map extensionExists errors to typed registry failures.
|
|
527
|
+
*/
|
|
528
|
+
const mapExtensionExistsError = (e) => {
|
|
529
|
+
return mapFailure(e, {
|
|
530
|
+
networkDetail: "Failed to connect to remote registry extension check endpoint",
|
|
531
|
+
incompatibleDetail: "Remote extension check response does not match expected schema",
|
|
532
|
+
fallbackDetail: "Remote extension check failed",
|
|
533
|
+
});
|
|
534
|
+
};
|
|
535
|
+
// ---------------------------------------------------------------------------
|
|
536
|
+
// publishExtension
|
|
537
|
+
// ---------------------------------------------------------------------------
|
|
538
|
+
const publishExtension = (args) => {
|
|
539
|
+
if (args.condition === undefined ||
|
|
540
|
+
args.publicationSetDigest === undefined ||
|
|
541
|
+
args.publicationDescriptorDigest === undefined) {
|
|
542
|
+
return Effect.fail(new RegistryOperationFailed({
|
|
543
|
+
category: "validation",
|
|
544
|
+
detail: "Remote publish requires an admitted publication-set preview.",
|
|
545
|
+
suggestions: [{ description: "Preview the complete publication set before uploading." }],
|
|
546
|
+
}));
|
|
547
|
+
}
|
|
548
|
+
const contentDigest = args.metadata.integrity.startsWith("sha512-")
|
|
549
|
+
? `sha-512=:${args.metadata.integrity.slice("sha512-".length)}:`
|
|
550
|
+
: args.metadata.integrity;
|
|
551
|
+
const publishClient = GeneratedRegistryClient.make(remoteHttpClient, {
|
|
552
|
+
transformClient: (baseClient) => Effect.succeed(baseClient.pipe(HttpClient.mapRequest((request) => {
|
|
553
|
+
const archiveRequest = request.pipe(HttpClientRequest.bodyUint8Array(args.archive, "application/zip"), HttpClientRequest.setHeaders({
|
|
554
|
+
"content-digest": contentDigest,
|
|
555
|
+
"content-length": String(args.archive.byteLength),
|
|
556
|
+
}));
|
|
557
|
+
return args.accessToken === undefined
|
|
558
|
+
? archiveRequest
|
|
559
|
+
: HttpClientRequest.bearerToken(archiveRequest, args.accessToken);
|
|
560
|
+
}))),
|
|
561
|
+
});
|
|
562
|
+
return executeRemoteRequest(publishClient
|
|
563
|
+
.ExtensionsPublishVersion(args.owner, pluralizeType(args.type), args.name, args.version, {
|
|
564
|
+
params: {
|
|
565
|
+
"if-match": args.condition,
|
|
566
|
+
"x-axm-publication-set-digest": args.publicationSetDigest,
|
|
567
|
+
"x-axm-publication-descriptor-digest": args.publicationDescriptorDigest,
|
|
568
|
+
"x-axm-visibility-input": JSON.stringify(args.visibilityInput),
|
|
569
|
+
...(args.visibility === undefined ? {} : { visibility: args.visibility.value }),
|
|
570
|
+
},
|
|
571
|
+
})
|
|
572
|
+
.pipe(Effect.map((response) => ({
|
|
573
|
+
published: true,
|
|
574
|
+
owner: decodeHandleSync(response.owner),
|
|
575
|
+
type: narrowExtensionType(response.type),
|
|
576
|
+
name: decodeExtensionNameSync(response.name),
|
|
577
|
+
version: decodeVersionSync(response.version),
|
|
578
|
+
integrity: response.integrity,
|
|
579
|
+
status: response.publish_status,
|
|
580
|
+
visibility: response.visibility,
|
|
581
|
+
links: response.links,
|
|
582
|
+
warnings: response.warnings.map((warning) => ({
|
|
583
|
+
ruleId: warning.ruleId,
|
|
584
|
+
severity: "warning",
|
|
585
|
+
message: warning.message,
|
|
586
|
+
suggestions: warning.suggestions.map(normalizeRegistrySuggestedAction),
|
|
587
|
+
})),
|
|
588
|
+
}))), {
|
|
589
|
+
operation: "publish extension version",
|
|
590
|
+
method: "PUT",
|
|
591
|
+
path: `/v1/extensions/${args.owner}/${pluralizeType(args.type)}/${args.name}/${args.version}`,
|
|
592
|
+
replaySafety: mutation,
|
|
593
|
+
mapError: mapPublishError,
|
|
594
|
+
policy: PUBLISH_REGISTRY_REQUEST_POLICY,
|
|
595
|
+
});
|
|
596
|
+
};
|
|
597
|
+
const previewExtensionPublishes = (args) => executeRemoteRequest(client
|
|
598
|
+
.PublishPreviewsPreviewExtensionPublishes({
|
|
599
|
+
payload: encodePublicationSetRequest(args),
|
|
600
|
+
config: undefined,
|
|
601
|
+
})
|
|
602
|
+
.pipe(Effect.flatMap((response) => Effect.try({
|
|
603
|
+
try: () => validatePublicationSetResponse(validatePublicationDescriptors(args.candidates), decodePublicationSetResponse(response)),
|
|
604
|
+
catch: (cause) => new RegistryRequestFailed({
|
|
605
|
+
category: "internal",
|
|
606
|
+
detail: "The registry returned an incompatible publication-set preview.",
|
|
607
|
+
cause,
|
|
608
|
+
}),
|
|
609
|
+
}))), {
|
|
610
|
+
operation: "preview extension publishes",
|
|
611
|
+
method: "POST",
|
|
612
|
+
path: "/v1/publish-previews",
|
|
613
|
+
replaySafety: safe,
|
|
614
|
+
mapError: (error) => mapFailure(error, {
|
|
615
|
+
networkDetail: "Failed to connect to the publish preview endpoint",
|
|
616
|
+
incompatibleDetail: "The registry returned an incompatible authoritative publish preview.",
|
|
617
|
+
fallbackDetail: "Publish preview failed",
|
|
618
|
+
}),
|
|
619
|
+
});
|
|
620
|
+
const updateExtensionVisibility = (args) => {
|
|
621
|
+
const target = parseExtensionFqnParts(args.target);
|
|
622
|
+
if (target === undefined) {
|
|
623
|
+
return Effect.fail(new RegistryOperationFailed({
|
|
624
|
+
category: "validation",
|
|
625
|
+
detail: `Invalid extension target: ${args.target}`,
|
|
626
|
+
}));
|
|
627
|
+
}
|
|
628
|
+
const payload = {
|
|
629
|
+
target: args.target,
|
|
630
|
+
visibility: args.visibility,
|
|
631
|
+
authority: args.authority,
|
|
632
|
+
revision: args.revision,
|
|
633
|
+
...(args.verification === undefined ? {} : { verification: args.verification }),
|
|
634
|
+
};
|
|
635
|
+
return executeRemoteRequest(client.ExtensionsUpdateVisibility(target.owner, pluralizeType(target.type), target.name, {
|
|
636
|
+
params: {
|
|
637
|
+
"if-match": args.revision,
|
|
638
|
+
...(args.verification === undefined
|
|
639
|
+
? {}
|
|
640
|
+
: { "x-axm-step-up-request": args.verification }),
|
|
641
|
+
},
|
|
642
|
+
payload,
|
|
643
|
+
config: undefined,
|
|
644
|
+
}), {
|
|
645
|
+
operation: "update extension visibility",
|
|
646
|
+
method: "PATCH",
|
|
647
|
+
path: `/v1/extensions/${target.owner}/${pluralizeType(target.type)}/${target.name}`,
|
|
648
|
+
replaySafety: mutation,
|
|
649
|
+
mapError: (error) => mapFailure(error, {
|
|
650
|
+
networkDetail: "Failed to connect to extension visibility endpoint",
|
|
651
|
+
incompatibleDetail: "Extension visibility response does not match the expected schema.",
|
|
652
|
+
fallbackDetail: "Remote extension visibility update failed",
|
|
653
|
+
}),
|
|
654
|
+
});
|
|
655
|
+
};
|
|
656
|
+
const getExtensionVisibility = (args) => executeRemoteRequest(client.ExtensionsGetVisibility(args.owner, pluralizeType(args.type), args.name, {
|
|
657
|
+
...(args.intent === null
|
|
658
|
+
? {}
|
|
659
|
+
: {
|
|
660
|
+
params: {
|
|
661
|
+
intent_visibility: args.intent.value,
|
|
662
|
+
intent_source: args.intent.source,
|
|
663
|
+
intent_fingerprint: args.intent.fingerprint,
|
|
664
|
+
},
|
|
665
|
+
}),
|
|
666
|
+
config: undefined,
|
|
667
|
+
}), {
|
|
668
|
+
operation: "get extension visibility",
|
|
669
|
+
method: "GET",
|
|
670
|
+
path: `/v1/extensions/${args.owner}/${pluralizeType(args.type)}/${args.name}/visibility`,
|
|
671
|
+
replaySafety: safe,
|
|
672
|
+
mapError: (error) => mapFailure(error, {
|
|
673
|
+
networkDetail: "Failed to connect to extension visibility endpoint",
|
|
674
|
+
incompatibleDetail: "Extension visibility response does not match the expected schema.",
|
|
675
|
+
fallbackDetail: "Remote extension visibility evaluation failed",
|
|
676
|
+
}),
|
|
677
|
+
});
|
|
678
|
+
/**
|
|
679
|
+
* Map all publish error types through the shared Registry boundary.
|
|
680
|
+
*/
|
|
681
|
+
const mapPublishError = (e) => mapFailure(e, {
|
|
682
|
+
networkDetail: "Remote registry is unreachable",
|
|
683
|
+
incompatibleDetail: "The registry returned a response the CLI could not parse.",
|
|
684
|
+
fallbackDetail: "Publish failed",
|
|
685
|
+
});
|
|
686
|
+
// ---------------------------------------------------------------------------
|
|
687
|
+
// discoverPackages
|
|
688
|
+
// ---------------------------------------------------------------------------
|
|
689
|
+
const discoverPackages = (args) => {
|
|
690
|
+
const payload = {
|
|
691
|
+
client: { axmVersion: "0.0.0" },
|
|
692
|
+
packages: args.packages.map((pkg) => ({
|
|
693
|
+
purl: encodePackageUrl(packageIdentity(pkg.purl)),
|
|
694
|
+
version: pkg.version,
|
|
695
|
+
declaredExtensions: pkg.declaredExtensions.flatMap((entry) => {
|
|
696
|
+
const ref = extensionDeclarationToDiscoveryRef(entry);
|
|
697
|
+
return ref === undefined ? [] : [ref];
|
|
698
|
+
}),
|
|
699
|
+
})),
|
|
700
|
+
};
|
|
701
|
+
return executeRemoteRequest(client
|
|
702
|
+
.DiscoveryPostDiscovery({ payload, config: undefined })
|
|
703
|
+
.pipe(Effect.flatMap(Schema.decodeUnknownEffect(DiscoverPackagesResponseSchema))), {
|
|
704
|
+
operation: "discover packages",
|
|
705
|
+
method: "POST",
|
|
706
|
+
path: "/v1/discovery",
|
|
707
|
+
replaySafety: safe,
|
|
708
|
+
mapError: (error) => mapDiscoveryError(error, "REGISTRY_REMOTE_DISCOVERY"),
|
|
709
|
+
});
|
|
710
|
+
};
|
|
711
|
+
return {
|
|
712
|
+
getExtensionIndex,
|
|
713
|
+
getExactExtensionVersion,
|
|
714
|
+
getExtensionsByScope,
|
|
715
|
+
ownerExists,
|
|
716
|
+
getExtensionPackage,
|
|
717
|
+
publishExtension,
|
|
718
|
+
previewExtensionPublishes,
|
|
719
|
+
getExtensionVisibility,
|
|
720
|
+
updateExtensionVisibility,
|
|
721
|
+
extensionExists,
|
|
722
|
+
discoverPackages,
|
|
723
|
+
};
|
|
724
|
+
};
|
|
725
|
+
//# sourceMappingURL=remote-client.js.map
|