UNPKG

verdaccio

Version:

A lightweight private npm proxy registry

1 lines • 15.3 kB
{"version":3,"file":"legacy-storage-adapter.mjs","names":["buildDebug"],"sources":["../../../src/lib/legacy-storage-adapter.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { Writable } from 'node:stream';\n\nimport { Logger } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:legacy-adapter');\n\n/**\n * Compatibility shim that lets a LEGACY callback/stream based storage plugin\n * (the `@verdaccio/local-storage-legacy` contract used by Verdaccio < 8 and by\n * many third-party plugins) be driven by the promise-based `@verdaccio/store`.\n *\n * `@verdaccio/store` awaits every storage method:\n * await plugin.get() / add(name) / remove(name) / search(query)\n * const h = plugin.getPackageStorage(name)\n * await h.readPackage(name) / savePackage(name, v) / createPackage(...) /\n * deletePackage(file) / removePackage() / hasPackage() /\n * hasTarball(file) / writeTarball(file, {signal}) /\n * readTarball(file, {signal}) / updatePackage(name, handleUpdate)\n *\n * The legacy contract is callback/stream based instead, so we promisify it.\n * New (promise) plugins are detected and returned untouched — zero overhead on\n * the default path.\n */\n\ntype AnyFn = (...args: any[]) => any;\n\n/** Resolve an error-first callback method into a promise. */\nfunction fromCallback<T = any>(invoke: (cb: (err: any, data?: T) => void) => void): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n invoke((err, data) => (err ? reject(err) : resolve(data as T)));\n });\n}\n\n/**\n * Detect a legacy callback-based storage plugin. The new contract's `get()`\n * takes no arguments (returns a promise); the legacy `get(cb)` declares a\n * trailing callback, and `add(name, cb)` takes two. Arity is a stable signal\n * here because both contracts are frozen.\n */\nexport function isLegacyStoragePlugin(plugin: any): boolean {\n if (!plugin || typeof plugin.get !== 'function') {\n return false;\n }\n const getIsCallback = plugin.get.length >= 1;\n const addIsCallback = typeof plugin.add === 'function' && plugin.add.length >= 2;\n return getIsCallback || addIsCallback;\n}\n\n/** Synthesize the new `hasPackage()` from legacy `readPackage(name, cb)`. */\nfunction hasPackage(handler: any, pkgName: string): Promise<boolean> {\n return new Promise((resolve) => {\n handler.readPackage(pkgName, (err: any) => resolve(!err));\n });\n}\n\n/**\n * Synthesize the new `hasTarball(file)` from a legacy `readTarball` stream:\n * open => exists, 404/ENOENT error => missing. Mirrors the old\n * `Storage.hasLocalTarball` probe.\n */\nfunction hasTarball(handler: any, fileName: string): Promise<boolean> {\n return new Promise((resolve, reject) => {\n let stream: any = handler.readTarball(fileName);\n let isOpen = false;\n stream.on('error', (err: any) => {\n const notFound = err?.code === 'ENOENT' || err?.status === 404;\n if (isOpen || !notFound) {\n // a real read error after the file was already opened\n if (!isOpen) {\n resolve(false);\n } else {\n reject(err);\n }\n } else {\n resolve(false);\n }\n stream?.abort?.();\n stream = null;\n });\n stream.on('open', () => {\n isOpen = true;\n stream.abort?.();\n stream = null;\n resolve(true);\n });\n });\n}\n\n/**\n * Bridge legacy `readTarball(name) => ReadTarball` to the promise contract.\n * The legacy object is already a Readable that emits `content-length`/`open`/\n * `error`, so it can be handed back as-is; we only wire abort to the signal.\n */\nfunction readTarball(handler: any, fileName: string, options: { signal?: AbortSignal } = {}) {\n const stream = handler.readTarball(fileName);\n const { signal } = options;\n if (signal) {\n if (signal.aborted) {\n stream.abort?.();\n } else {\n signal.addEventListener('abort', () => stream.abort?.(), { once: true });\n }\n }\n return Promise.resolve(stream);\n}\n\n/**\n * Bridge legacy `writeTarball(name) => UploadTarball` to a Node Writable that\n * `@verdaccio/store` can use in `pipeline()`:\n * - the legacy stream finalizes only when `.done()` is called (ends temp file,\n * renames it into place, then emits `success`); store instead ends the\n * stream and waits for `close`.\n * - so we forward writes to the legacy stream, call `.done()` on `_final`, and\n * only complete (emit `close`) once the legacy stream emits `success`.\n */\nfunction writeTarball(handler: any, fileName: string, options: { signal?: AbortSignal } = {}) {\n const upload = handler.writeTarball(fileName);\n const { signal } = options;\n\n const bridge = new Writable({\n write(chunk, _enc, cb): void {\n if (upload.write(chunk)) {\n cb();\n } else {\n upload.once('drain', cb);\n }\n },\n final(cb): void {\n upload.once('success', () => cb());\n upload.once('error', (err: any) => cb(err));\n // `done()` waits for the legacy stream's `end`, so end it first.\n upload.end();\n upload.done();\n },\n });\n\n upload.on('open', () => bridge.emit('open'));\n upload.on('error', (err: any) => bridge.destroy(err));\n\n if (signal) {\n const onAbort = (): void => {\n upload.abort?.();\n bridge.destroy(new Error('tarball upload aborted'));\n };\n if (signal.aborted) {\n onAbort();\n } else {\n signal.addEventListener('abort', onAbort, { once: true });\n }\n }\n return Promise.resolve(bridge);\n}\n\n/**\n * Bridge legacy 5-arg `updatePackage(name, updateHandler, onWrite,\n * transformPackage, onEnd)` to the new 2-arg `updatePackage(name, handleUpdate)`\n * where `handleUpdate(manifest) => Promise<manifest>`. The store persists the\n * result itself (via `writePackage`/`savePackage`), so `onWrite` is a no-op:\n * we only read-lock, run the handler, and unlock.\n */\nfunction updatePackage(handler: any, name: string, handleUpdate: AnyFn): Promise<any> {\n return new Promise((resolve, reject) => {\n let updated: any;\n handler.updatePackage(\n name,\n // updateHandler(json, cb): run the promise handler, capture the result\n (json: any, cb: (err?: any) => void) => {\n Promise.resolve(handleUpdate(json))\n .then((result) => {\n updated = result;\n cb();\n })\n .catch(cb);\n },\n // onWrite(name, data, cb): store persists separately, so just unlock\n (_name: string, _data: any, cb: (err?: any) => void) => cb(),\n // transformPackage(json): return the updated manifest\n (json: any) => updated ?? json,\n // onEnd(err)\n (err: any) => (err ? reject(err) : resolve(updated))\n );\n });\n}\n\n/** Wrap a legacy per-package storage handler in the promise contract. */\nfunction wrapLegacyHandler(handler: any): any {\n if (!handler) {\n return handler;\n }\n return {\n readPackage: (name: string) => fromCallback((cb) => handler.readPackage(name, cb)),\n createPackage: (name: string, manifest: any) =>\n fromCallback((cb) => handler.createPackage(name, manifest, cb)),\n savePackage: (name: string, value: any) =>\n fromCallback((cb) => handler.savePackage(name, value, cb)),\n deletePackage: (fileName: string) => fromCallback((cb) => handler.deletePackage(fileName, cb)),\n removePackage: () => fromCallback((cb) => handler.removePackage(cb)),\n hasPackage: (name?: string) => hasPackage(handler, name as string),\n hasTarball: (fileName: string) => hasTarball(handler, fileName),\n readTarball: (fileName: string, options?: { signal?: AbortSignal }) =>\n readTarball(handler, fileName, options),\n writeTarball: (fileName: string, options?: { signal?: AbortSignal }) =>\n writeTarball(handler, fileName, options),\n updatePackage: (name: string, handleUpdate: AnyFn) =>\n updatePackage(handler, name, handleUpdate),\n };\n}\n\n/**\n * The legacy contract emits the package itself, while `@verdaccio/store` reads\n * `item.package.name` and `item.score`. Wrap whatever is not already in that shape,\n * otherwise a plugin following the documented contract makes `/-/v1/search` fail.\n */\nfunction toSearchItem(item: any): any {\n if (item !== null && typeof item === 'object' && 'package' in item) {\n return item;\n }\n\n return {\n package: item,\n score: { final: 1, detail: { quality: 1, popularity: 1, maintenance: 1 } },\n };\n}\n\n/**\n * Collect a legacy streaming `search(onPackage, onEnd, validate)` into a list.\n *\n * A legacy plugin has no way to receive the query, so the filtering happens here, on\n * the emitted name. The predicate stays permissive on purpose: a plugin calls it with\n * the basename (`pkg` for `@scope/pkg`), so any rejection there could drop a package\n * whose full name does match.\n */\nfunction search(plugin: any, query: any): Promise<any[]> {\n const text = typeof query?.text === 'string' ? query.text.toLowerCase() : undefined;\n const matches = (name: unknown): boolean =>\n text === undefined || (typeof name === 'string' && name.toLowerCase().includes(text));\n\n return new Promise((resolve, reject) => {\n const items: any[] = [];\n try {\n plugin.search(\n (item: any, done: () => void) => {\n const searchItem = toSearchItem(item);\n if (matches(searchItem?.package?.name)) {\n items.push(searchItem);\n }\n done();\n },\n (err: any) => (err ? reject(err) : resolve(items)),\n () => true\n );\n } catch (err) {\n reject(err);\n }\n });\n}\n\n/**\n * Wrap a legacy callback storage plugin in the promise contract expected by\n * `@verdaccio/store`. Token methods are exposed only when the legacy plugin\n * implements them (store already guards their absence with SERVICE_UNAVAILABLE).\n */\nexport function wrapLegacyStoragePlugin(plugin: any, logger: Logger): any {\n debug('wrapping legacy callback storage plugin');\n logger.warn(\n 'a legacy callback-based storage plugin was detected and wrapped for compatibility; ' +\n 'consider upgrading it to the promise-based storage API'\n );\n\n const wrapped: Record<string, AnyFn> = {\n init: () =>\n typeof plugin.init === 'function'\n ? Promise.resolve(plugin.init())\n : Promise.resolve(undefined),\n getSecret: () => Promise.resolve(plugin.getSecret()),\n setSecret: (secret: string) => Promise.resolve(plugin.setSecret(secret)),\n get: () => fromCallback((cb) => plugin.get(cb)),\n add: (name: string) => fromCallback((cb) => plugin.add(name, cb)),\n remove: (name: string) => fromCallback((cb) => plugin.remove(name, cb)),\n search: (query: any) => search(plugin, query),\n getPackageStorage: (packageName: string) =>\n wrapLegacyHandler(plugin.getPackageStorage(packageName)),\n };\n\n for (const tokenMethod of ['saveToken', 'deleteToken', 'readTokens'] as const) {\n if (typeof plugin[tokenMethod] === 'function') {\n wrapped[tokenMethod] = (...args: any[]) => Promise.resolve(plugin[tokenMethod](...args));\n }\n }\n\n // preserve any extra plugin surface (e.g. logger, config) by prototype-less merge\n return Object.assign(Object.create(plugin), wrapped);\n}\n"],"mappings":"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"}