Skip to content

Commit 4fa17ec

Browse files
committed
[WIP] Additional types
1 parent 4b632bb commit 4fa17ec

40 files changed

+2637
-126
lines changed

lib/condaRepoAccess.d.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// (c) Copyright 2025, SAP SE and ClearlyDefined contributors. Licensed under the MIT license.
2+
// SPDX-License-Identifier: MIT
3+
4+
import { ICache } from '../providers/caching'
5+
6+
/** Configuration mapping of Conda channel names to their base URLs */
7+
export interface CondaChannels {
8+
'anaconda-main': string
9+
'anaconda-r': string
10+
'conda-forge': string
11+
[key: string]: string
12+
}
13+
14+
/** Package information from Conda channel data */
15+
export interface CondaPackageInfo {
16+
/** Package name */
17+
name?: string
18+
/** Package version */
19+
version?: string
20+
/** Build string */
21+
build?: string
22+
/** Available subdirectories for this package */
23+
subdirs?: string[]
24+
}
25+
26+
/** Channel data structure returned by Conda API */
27+
export interface CondaChannelData {
28+
/** Map of package names to their information */
29+
packages: Record<string, CondaPackageInfo>
30+
/** Available subdirectories in this channel */
31+
subdirs: string[]
32+
}
33+
34+
/** Repository data structure for a specific platform */
35+
export interface CondaRepoData {
36+
/** Standard packages */
37+
packages?: Record<string, CondaPackageInfo>
38+
/** Conda format packages */
39+
'packages.conda'?: Record<string, CondaPackageInfo>
40+
[key: string]: any
41+
}
42+
43+
/** Package search result */
44+
export interface CondaPackageMatch {
45+
/** Package identifier */
46+
id: string
47+
}
48+
49+
/** Main class for accessing Conda repository data */
50+
declare class CondaRepoAccess {
51+
/** Cache instance for storing fetched data */
52+
private cache: ICache
53+
54+
/**
55+
* Creates a new CondaRepoAccess instance
56+
*
57+
* @param cache - Cache instance to use for storing data
58+
*/
59+
constructor(cache?: ICache)
60+
61+
/**
62+
* Validates if a channel is recognized and supported
63+
*
64+
* @param channel - Channel name to validate
65+
* @throws {Error} When channel is not recognized
66+
*/
67+
checkIfValidChannel(channel: string): void
68+
69+
/**
70+
* Fetches channel data from cache or network
71+
*
72+
* @param channel - Channel name
73+
* @returns Promise resolving to channel data
74+
* @throws {Error} When channel is invalid or fetch fails
75+
*/
76+
fetchChannelData(channel: string): Promise<CondaChannelData>
77+
78+
/**
79+
* Fetches repository data for a specific channel and subdirectory
80+
*
81+
* @param channel - Channel name
82+
* @param subdir - Subdirectory name (platform)
83+
* @returns Promise resolving to repository data
84+
* @throws {Error} When channel is invalid or fetch fails
85+
*/
86+
fetchRepoData(channel: string, subdir: string): Promise<CondaRepoData>
87+
88+
/**
89+
* Gets all available revisions for a package
90+
*
91+
* @example
92+
* ```javascript
93+
* const revisions = await condaAccess.getRevisions('conda-forge', 'linux-64', 'numpy')
94+
* // Returns: ['linux-64:1.21.0-py39h0']
95+
* ```
96+
*
97+
* @param channel - Channel name
98+
* @param subdir - Subdirectory name or '-' for all subdirs
99+
* @param name - Package name
100+
* @returns Promise resolving to array of revision strings in format "subdir:version-build"
101+
* @throws {Error} When package not found or subdir doesn't exist
102+
*/
103+
getRevisions(channel: string, subdir: string, name: string): Promise<string[]>
104+
105+
/**
106+
* Searches for packages by name pattern
107+
*
108+
* @example
109+
* ```javascript
110+
* const packages = await condaAccess.getPackages('conda-forge', 'numpy')
111+
* // Returns: [{ id: 'numpy' }, { id: 'numpy-base' }]
112+
* ```
113+
*
114+
* @param channel - Channel name
115+
* @param name - Package name pattern to search for
116+
* @returns Promise resolving to array of matching packages
117+
* @throws {Error} When channel is invalid or fetch fails
118+
*/
119+
getPackages(channel: string, name: string): Promise<CondaPackageMatch[]>
120+
}
121+
122+
/**
123+
* Factory function that creates a new CondaRepoAccess instance
124+
*
125+
* @param cache - Optional cache instance
126+
* @returns New CondaRepoAccess instance
127+
*/
128+
declare function createCondaRepoAccess(cache?: ICache): CondaRepoAccess
129+
130+
export = createCondaRepoAccess

lib/condaRepoAccess.js

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,66 @@
33

44
const { callFetch: requestPromise } = require('./fetch')
55
const { uniq } = require('lodash')
6-
const Cache = require('../providers/caching/memory')
6+
const createCache = require('../providers/caching/memory')
77

8+
/**
9+
* @typedef {import('./condaRepoAccess').CondaChannels} CondaChannels
10+
*
11+
* @typedef {import('./condaRepoAccess').CondaChannelData} CondaChannelData
12+
*
13+
* @typedef {import('./condaRepoAccess').CondaRepoData} CondaRepoData
14+
*
15+
* @typedef {import('./condaRepoAccess').CondaPackageMatch} CondaPackageMatch
16+
*
17+
* @typedef {import('../providers/caching').ICache} ICache
18+
*/
19+
20+
/**
21+
* Configuration mapping of Conda channel names to their base URLs
22+
*
23+
* @type {CondaChannels}
24+
*/
825
const condaChannels = {
926
'anaconda-main': 'https://repo.anaconda.com/pkgs/main',
1027
'anaconda-r': 'https://repo.anaconda.com/pkgs/r',
1128
'conda-forge': 'https://conda.anaconda.org/conda-forge'
1229
}
1330

31+
/**
32+
* Main class for accessing Conda repository data. Provides methods to fetch channel data, repository data, and search
33+
* for packages.
34+
*/
1435
class CondaRepoAccess {
36+
/**
37+
* Creates a new CondaRepoAccess instance
38+
*
39+
* @param {ICache} [cache] - Cache instance to use for storing data. Defaults to memory cache with 8 hour TTL if not
40+
* provided.
41+
*/
1542
constructor(cache) {
16-
this.cache = cache || Cache({ defaultTtlSeconds: 8 * 60 * 60 }) // 8 hours
43+
this.cache = cache || createCache({ defaultTtlSeconds: 8 * 60 * 60 }) // 8 hours
1744
}
1845

46+
/**
47+
* Validates if a channel is recognized and supported
48+
*
49+
* @param {string} channel - Channel name to validate
50+
* @throws {Error} When channel is not recognized
51+
*/
1952
checkIfValidChannel(channel) {
2053
if (!condaChannels[channel]) {
2154
throw new Error(`Unrecognized Conda channel ${channel}`)
2255
}
2356
}
2457

58+
/**
59+
* Fetches channel data from cache or network. Channel data contains information about all packages available in a
60+
* channel.
61+
*
62+
* @param {string} channel - Channel name
63+
* @returns {Promise<CondaChannelData>} Promise resolving to channel data
64+
* @throws {Error} When channel is invalid or fetch fails
65+
*/
2566
async fetchChannelData(channel) {
2667
const key = `${channel}-channelData`
2768
let channelData = this.cache.get(key)
@@ -33,6 +74,15 @@ class CondaRepoAccess {
3374
return channelData
3475
}
3576

77+
/**
78+
* Fetches repository data for a specific channel and subdirectory. Repository data contains detailed package
79+
* information for a specific platform.
80+
*
81+
* @param {string} channel - Channel name
82+
* @param {string} subdir - Subdirectory name (platform like 'linux-64', 'win-64')
83+
* @returns {Promise<CondaRepoData>} Promise resolving to repository data
84+
* @throws {Error} When channel is invalid or fetch fails
85+
*/
3686
async fetchRepoData(channel, subdir) {
3787
const key = `${channel}-${subdir}-repoData`
3888
let repoData = this.cache.get(key)
@@ -44,6 +94,16 @@ class CondaRepoAccess {
4494
return repoData
4595
}
4696

97+
/**
98+
* Gets all available revisions for a package across specified subdirectories. Each revision represents a specific
99+
* build of a package version.
100+
*
101+
* @param {string} channel - Channel name
102+
* @param {string} subdir - Subdirectory name or '-' to search all available subdirs
103+
* @param {string} name - Package name
104+
* @returns {Promise<string[]>} Promise resolving to array of revision strings in format "subdir:version-build"
105+
* @throws {Error} When package not found or subdir doesn't exist
106+
*/
47107
async getRevisions(channel, subdir, name) {
48108
channel = encodeURIComponent(channel)
49109
name = encodeURIComponent(name)
@@ -56,6 +116,7 @@ class CondaRepoAccess {
56116
if (subdir !== '-' && !channelData.subdirs.find(x => x === subdir)) {
57117
throw new Error(`Subdir ${subdir} is non-existent in channel ${channel}, subdirs: ${channelData.subdirs}`)
58118
}
119+
/** @type {string[]} */
59120
let revisions = []
60121
const subdirs = subdir === '-' ? channelData.packages[name].subdirs : [subdir]
61122
for (let subdir of subdirs) {
@@ -72,6 +133,15 @@ class CondaRepoAccess {
72133
return uniq(revisions)
73134
}
74135

136+
/**
137+
* Searches for packages by name pattern in the specified channel. Returns packages whose names contain the search
138+
* term.
139+
*
140+
* @param {string} channel - Channel name
141+
* @param {string} name - Package name pattern to search for
142+
* @returns {Promise<CondaPackageMatch[]>} Promise resolving to array of matching packages
143+
* @throws {Error} When channel is invalid or fetch fails
144+
*/
75145
async getPackages(channel, name) {
76146
channel = encodeURIComponent(channel)
77147
name = encodeURIComponent(name)
@@ -84,4 +154,10 @@ class CondaRepoAccess {
84154
}
85155
}
86156

157+
/**
158+
* Factory function that creates a new CondaRepoAccess instance
159+
*
160+
* @param {ICache} [cache] - Optional cache instance
161+
* @returns {CondaRepoAccess} New CondaRepoAccess instance
162+
*/
87163
module.exports = cache => new CondaRepoAccess(cache)

lib/entityCoordinates.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface EntityCoordinatesSpec {
1111
}
1212

1313
/** Represents entity coordinates for a software component */
14-
declare class EntityCoordinates {
14+
export declare class EntityCoordinates {
1515
/** The type of the entity (e.g., 'npm', 'maven', 'git') */
1616
type?: string
1717

@@ -85,3 +85,4 @@ declare class EntityCoordinates {
8585
}
8686

8787
export default EntityCoordinates
88+
export = EntityCoordinates

lib/entityCoordinates.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Microsoft Corporation and others. Licensed under the MIT license.
22
// SPDX-License-Identifier: MIT
33

4-
/** @typedef {import('./entityCoordinates')} EntityCoordinates */
54
/** @typedef {import('./entityCoordinates').EntityCoordinatesSpec} EntityCoordinatesSpec */
65

76
/** Property flag for namespace normalization */
@@ -42,11 +41,6 @@ class EntityCoordinates {
4241
*
4342
* @param {EntityCoordinatesSpec | EntityCoordinates | null | undefined} spec - The specification object or existing
4443
* EntityCoordinates instance
45-
* @param {string} [spec.type] - The type of the entity
46-
* @param {string} [spec.provider] - The provider of the entity
47-
* @param {string} [spec.namespace] - The namespace of the entity
48-
* @param {string} [spec.name] - The name of the entity
49-
* @param {string} [spec.revision] - The revision/version of the entity
5044
* @returns {EntityCoordinates | null} New EntityCoordinates instance or null if spec is falsy
5145
*/
5246
static fromObject(spec) {
@@ -78,7 +72,7 @@ class EntityCoordinates {
7872
static fromUrn(urn) {
7973
if (!urn) return null
8074
// eslint-disable-next-line no-unused-vars
81-
const [scheme, type, provider, namespace, name, revToken, revision] = urn.split(':')
75+
const [, type, provider, namespace, name, , revision] = urn.split(':')
8276
return new EntityCoordinates(type, provider, namespace, name, revision)
8377
}
8478

lib/fetch.d.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// (c) Copyright 2024, SAP SE and ClearlyDefined contributors. Licensed under the MIT license.
2+
// SPDX-License-Identifier: MIT
3+
4+
import { AxiosInstance, AxiosResponse } from 'axios'
5+
6+
/** Default headers used for HTTP requests. */
7+
export declare const defaultHeaders: Readonly<{ 'User-Agent': string }>
8+
9+
/** Request options for HTTP calls. */
10+
export interface FetchRequestOptions {
11+
/** The HTTP method to use. */
12+
method?: string
13+
/** The URL to request (alternative to `uri`). */
14+
url?: string
15+
/** The URI to request (alternative to `url`). */
16+
uri?: string
17+
/** Whether to parse response as JSON. */
18+
json?: boolean
19+
/** Text encoding for the response. Set to `null` for binary/stream responses. */
20+
encoding?: string | null
21+
/** HTTP headers to include in the request. */
22+
headers?: Record<string, string>
23+
/** Request body data. */
24+
body?: any
25+
/** Whether to include credentials in cross-origin requests. */
26+
withCredentials?: boolean
27+
/** Whether to throw an error for HTTP error status codes. Defaults to `true`. */
28+
simple?: boolean
29+
/** Whether to return the full response object instead of just the data. */
30+
resolveWithFullResponse?: boolean
31+
}
32+
33+
/** Extended response object returned when `resolveWithFullResponse` is true. */
34+
export interface FetchResponse<T = any> extends AxiosResponse<T> {
35+
/** HTTP status code (alias for `status`). */
36+
statusCode: number
37+
/** HTTP status message (alias for `statusText`). */
38+
statusMessage: string
39+
/** Response configuration used for the request. */
40+
config: any
41+
}
42+
43+
/** HTTP error with status code information. */
44+
export interface FetchError extends Error {
45+
/** HTTP status code of the error response. */
46+
statusCode?: number
47+
/** The original response object if available. */
48+
response?: AxiosResponse
49+
}
50+
51+
/** Options for creating a fetch instance with default settings. */
52+
export interface WithDefaultsOptions {
53+
/** Default headers to include in all requests. */
54+
headers?: Record<string, string>
55+
/** Other axios configuration options. */
56+
[key: string]: any
57+
}
58+
59+
/** Function signature for making HTTP requests with default options applied. */
60+
export type FetchFunction = (request: FetchRequestOptions) => Promise<any>
61+
62+
/**
63+
* Makes an HTTP request using axios with the specified options.
64+
*
65+
* @param request - The request configuration options
66+
* @param axiosInstance - Optional axios instance to use for the request
67+
* @returns Promise that resolves to the response data or full response object
68+
* @throws {FetchError} When the request fails or returns an error status code
69+
*/
70+
export declare function callFetch<T = any>(
71+
request: FetchRequestOptions,
72+
axiosInstance?: AxiosInstance
73+
): Promise<T | FetchResponse<T>>
74+
75+
/**
76+
* Creates a new fetch function with default options applied.
77+
*
78+
* @param opts - Default options to apply to all requests made with the returned function
79+
* @returns A function that makes HTTP requests with the default options applied
80+
*/
81+
export declare function withDefaults(opts: WithDefaultsOptions): FetchFunction

0 commit comments

Comments
 (0)