Skip to content

Commit

Permalink
feat: add searchPackages
Browse files Browse the repository at this point in the history
  • Loading branch information
velut committed Apr 17, 2024
1 parent 1d234a4 commit 0a6b737
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 0 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"keyid",
"keytype",
"nistp",
"npmjs",
"packument",
"packuments",
"pathnames",
Expand Down
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"dependencies": {
"isomorphic-unfetch": "^4.0.2",
"make-error": "^1.3.6",
"query-string": "^9.0.0",
"tiny-lru": "^11.2.5",
"url-join": "^5.0.0",
"validate-npm-package-name": "^5.0.0",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export { RegistryDownloads, getRegistryDownloads } from "./get-registry-download
export { RegistryMetadata, getRegistryMetadata } from "./get-registry-metadata";
export { RegistrySigningKeys, getRegistrySigningKeys } from "./get-registry-signing-keys";
export { npmRegistryDownloadsApiUrl, npmRegistryUrl } from "./npm-registry";
export { SearchCriteria, SearchResults, searchPackages } from "./search-packages";
174 changes: 174 additions & 0 deletions src/search-packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import queryString from "query-string";
import urlJoin from "url-join";
import { z } from "zod";
import { PackageJson } from "zod-package-json";
import { fetchData } from "./fetch-data";
import { npmRegistryUrl } from "./npm-registry";

export const SearchCriteria = z.object({
/**
Query text.
@remarks
The following special text attributes can be used to refine results:
- `author:<name>`: show packages from the given author (e.g., `author:someone`)
- `maintainer:<name>`: show packages with the given maintainer (e.g., `maintainer:someone`)
- `keywords:<keyword list>`: show packages matching the given keyword(s);
separators `,`, `+` and `,-` act respectively as `OR`, `AND` and `NOT`
(e.g., use `keywords:foo,bar+baz,-quux` to include keywords `foo` OR (`bar` AND `baz`) but NOT `quux`)
- `not:unstable`: exclude unstable packages (semver version `<1.0.0`)
- `not:insecure`: exclude insecure packages
- `is:unstable`: include only unstable packages (semver version `<1.0.0`)
- `is:insecure`: include only insecure packages
- `boost-exact:<true/false>`: boost packages with exact name match (default: `true`)
*/
text: z.string().optional(),

/** Number of results to return (the npm registry accepts a maximum of `250` with a default of `20`). */
size: z.number().optional(),

/** Return results from this offset. */
from: z.number().optional(),

/** Package quality weight (from `0.0` to `1.0`). */
quality: z.number().optional(),

/** Package popularity weight (from `0.0` to `1.0`). */
popularity: z.number().optional(),

/** Package maintenance weight (from `0.0` to `1.0`). */
maintenance: z.number().optional(),
});

/**
`SearchCriteria` describes the available search criteria for searching packages.
@see {@link https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#get-v1search}
*/
export type SearchCriteria = z.infer<typeof SearchCriteria>;

const SearchResult = z.object({
/** Package metadata. */
package: PackageJson.pick({
name: true,
version: true,
description: true,
keywords: true,
}).extend({
/**
Either `unscoped` for unscoped packages (e.g., `foo` -> `unscoped`) or
the package's scope for scoped packages (e.g., `@foo/bar` -> `foo`).
*/
scope: z.string(),

/**
Timestamp of when the `latest` version of the package was published
in ISO 8601 format (e.g., `2021-11-23T19:12:24.006Z`).
*/
date: z.string(),

/** Author of the package. */
author: z
.object({
username: z.string().optional(),
name: z.string().optional(),
email: z.string().optional(),
url: z.string().optional(),
})
.optional(),

/** User who published the `latest` version of the package. */
publisher: z.object({
username: z.string(),
email: z.string(),
}),

/** Maintainers of the `latest` version of the package. */
maintainers: z.array(
z.object({
username: z.string(),
email: z.string(),
}),
),

/** Links to resources associated to the package. */
links: z.object({
/** Page for the package on npmjs.com. */
npm: z.string().optional(),

/** Homepage for the package. */
homepage: z.string().optional(),

/** Repository for the package. */
repository: z.string().optional(),

/** Issue tracker for the package. */
bugs: z.string().optional(),
}),
}),

/** Final and detailed search score values. */
score: z.object({
/** Final search score value (from `0.0` to `1.0`), computed from the detailed scores. */
final: z.number(),

/** Detailed search score values. */
detail: z.object({
/** Quality search score value (from `0.0` to `1.0`). */
quality: z.number(),

/** Popularity search score value (from `0.0` to `1.0`). */
popularity: z.number(),

/** Maintenance search score value (from `0.0` to `1.0`). */
maintenance: z.number(),
}),
}),

/** Search score value; may be different from `score.final`. */
searchScore: z.number(),

/** Flag attributes for the package. */
flags: z
.object({
/** True if the package semver version number is `<1.0.0`. */
unstable: z.coerce.boolean().optional(),

/** True if the package is insecure or has vulnerable dependencies. */
insecure: z.coerce.boolean().optional(),
})
.optional(),
});

export const SearchResults = z.object({
objects: z.array(SearchResult),

/**
Total number of corresponding search results available;
may be higher than the number of `objects` returned.
*/
total: z.number(),

/** Date at which the search happened. */
time: z.string(),
});

/**
`SearchResults` describes the results returned by the registry for a search query.
@see {@link https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#get-v1search}
*/
export type SearchResults = z.infer<typeof SearchResults>;

/**
`searchPackages` returns the packages corresponding to a given query.
@param criteria - one or more search criteria
@param registry - URL of the registry (default: npm registry)
@see {@link SearchCriteria}
@see {@link SearchResults}
*/
export const searchPackages = async (
criteria: SearchCriteria,
registry = npmRegistryUrl,
): Promise<SearchResults> =>
fetchData(SearchResults, urlJoin(registry, "-/v1/search", `?${queryString.stringify(criteria)}`));

0 comments on commit 0a6b737

Please sign in to comment.