{"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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"}