{"version":3,"file":"scan.mjs","sources":["../../../src/plugins/scan.ts"],"sourcesContent":["import {\n  combineAbortSignals,\n  compact,\n  concurrently,\n  False,\n  isDefined,\n  isTypeOfString,\n  some,\n  True,\n  tryCreateTimeoutSignal\n} from '@neodx/std';\nimport { join } from 'pathe';\nimport type { VfsDirent } from '../backend/shared.ts';\nimport type { BaseVfs } from '../core/types.ts';\nimport { createVfsPlugin } from '../create-vfs-plugin.ts';\n\nexport interface ScanPluginApi {\n  scan(path?: string, params?: ScanVfsParams): Promise<string[]>;\n  scan(params?: ScanVfsParams): Promise<string[]>;\n  scan(path: string, params: ScanVfsParams & { withFileTypes: true }): Promise<ScannedItem[]>;\n  scan(params: ScanVfsParams & { withFileTypes: true }): Promise<ScannedItem[]>;\n}\n\nexport interface ScanVfsParams {\n  /** Path to scan. */\n  path?: string;\n  /**\n   * Should return false if the scanning for the current directory should be stopped.\n   * @default False\n   */\n  barrier?: ScanVfsDirentChecker;\n  /**\n   * Should return true if the item should be included in the result.\n   * @default True\n   */\n  filter?: ScanVfsDirentChecker;\n  /** Custom abort signal. */\n  signal?: AbortSignal;\n  /** Timeout in milliseconds. */\n  timeout?: number;\n  /** Maximum depth to scan. */\n  maxDepth?: number;\n\n  /** If true, the result will contain information about the path, depth, relativePath and dirent. */\n  withFileTypes?: boolean;\n  /** Optional cache for optimizing multiple scans under relatively static conditions. */\n  cache?: ScanVfsCache;\n}\n\nexport interface ScannedItem {\n  /** Current scanning depth. */\n  depth: number;\n  /** Relative path to file or directory */\n  relativePath: string;\n  dirent: VfsDirent;\n}\n\nexport type ScanVfsDirentChecker = (item: ScannedItem) => boolean;\nexport type ScanVfsCache = ReturnType<typeof createScanVfsCache>;\n\nexport function scan() {\n  return createVfsPlugin<ScanPluginApi>('scan', vfs => {\n    async function scanImpl(pathOrParams?: string | ScanVfsParams, params?: ScanVfsParams) {\n      return await scanVfs(\n        vfs,\n        isTypeOfString(pathOrParams) ? { ...params, path: pathOrParams } : pathOrParams\n      );\n    }\n\n    vfs.scan = scanImpl as ScanPluginApi['scan'];\n    return vfs;\n  });\n}\n\nexport async function scanVfs(\n  vfs: BaseVfs,\n  params: ScanVfsParams & { withFileTypes: true }\n): Promise<ScannedItem[]>;\nexport async function scanVfs(vfs: BaseVfs, params?: ScanVfsParams): Promise<string[]>;\nexport async function scanVfs(\n  vfs: BaseVfs,\n  {\n    path = '.',\n    cache = createScanVfsCache(),\n    filter = True,\n    signal: manualSignal,\n    barrier: manualBarrier = False,\n    timeout,\n    maxDepth,\n    withFileTypes\n  }: ScanVfsParams = {}\n) {\n  const signal = combineAbortSignals([manualSignal, tryCreateTimeoutSignal(timeout)]);\n  const barrier = some(\n    ...compact([\n      manualBarrier,\n      isDefined(maxDepth) && (({ depth }: ScannedItem) => depth >= maxDepth)\n    ])\n  );\n  const result: ScannedItem[] = [];\n\n  async function iterate(params: Pick<ScannedItem, 'relativePath' | 'depth'>) {\n    signal.throwIfAborted();\n    Object.freeze(params);\n    const resolved = vfs.resolve(path, params.relativePath);\n    const children = await (cache.visited[resolved] ??= vfs.readDir(resolved, {\n      withFileTypes: true\n    }));\n\n    await concurrently(\n      children,\n      async dirent => {\n        signal.throwIfAborted();\n        const scanned = {\n          relativePath: join(params.relativePath, dirent.name),\n          dirent,\n          depth: params.depth + 1\n        };\n\n        if (filter(scanned)) result.push(scanned);\n        if (dirent.isDirectory() && !barrier(scanned)) await iterate(scanned);\n      },\n      10\n    );\n  }\n\n  await iterate({ relativePath: '.', depth: 0 });\n  return withFileTypes ? result : result.map(item => item.relativePath);\n}\n\nexport const createScanVfsCache = () => {\n  const visited = {} as Record<string, Promise<VfsDirent[]>>;\n\n  return {\n    visited,\n    clear() {\n      for (const key of Object.keys(visited)) {\n        delete visited[key];\n      }\n    }\n  };\n};\n"],"names":["scan","createVfsPlugin","vfs","scanImpl","pathOrParams","params","scanVfs","isTypeOfString","path","cache","createScanVfsCache","filter","True","signal","manualSignal","barrier","manualBarrier","False","timeout","maxDepth","withFileTypes","combineAbortSignals","tryCreateTimeoutSignal","some","compact","isDefined","depth","result","iterate","throwIfAborted","Object","freeze","resolved","resolve","relativePath","children","visited","readDir","concurrently","dirent","scanned","join","name","push","isDirectory","map","item","clear","key","keys"],"mappings":"2QA4DO,SAASA,IACd,OAAOC,EAA+B,OAAQC,AAAAA,IAC5C,eAAeC,EAASC,CAAqC,CAAEC,CAAsB,EACnF,OAAO,MAAMC,EACXJ,EACAK,EAAeH,GAAgB,CAAE,GAAGC,CAAM,CAAEG,KAAMJ,CAAiBA,EAAAA,EAEvE,CAGA,OADAF,EAAIF,IAAI,CAAGG,EACJD,CACT,EACF,CAOO,eAAeI,EACpBJ,CAAY,CACZ,CACEM,KAAAA,EAAO,GAAG,CACVC,MAAAA,EAAQC,GAAoB,CAC5BC,OAAAA,EAASC,CAAI,CACbC,OAAQC,CAAY,CACpBC,QAASC,EAAgBC,CAAK,CAC9BC,QAAAA,CAAO,CACPC,SAAAA,CAAQ,CACRC,cAAAA,CAAa,CACC,CAAG,EAAE,EAErB,IAAMP,EAASQ,EAAoB,CAACP,EAAcQ,EAAuBJ,GAAS,EAC5EH,EAAUQ,KACXC,EAAQ,CACTR,EACAS,EAAUN,IAAc,CAAA,CAAC,CAAEO,MAAAA,CAAK,CAAe,GAAKA,GAASP,GAC9D,GAEGQ,EAAwB,EAAE,CAEhC,eAAeC,EAAQvB,CAAmD,EACxEQ,EAAOgB,cAAc,GACrBC,OAAOC,MAAM,CAAC1B,GACd,IAAM2B,EAAW9B,EAAI+B,OAAO,CAACzB,EAAMH,EAAO6B,YAAY,EAChDC,EAAW,MAAO1B,CAAAA,EAAM2B,OAAO,CAACJ,EAAS,GAAK9B,EAAImC,OAAO,CAACL,EAAU,CACxEZ,cAAe,CAAA,CAChB,EAAA,CAED,OAAMkB,EACJH,EACA,MAAMI,IACJ1B,EAAOgB,cAAc,GACrB,IAAMW,EAAU,CACdN,aAAcO,EAAKpC,EAAO6B,YAAY,CAAEK,EAAOG,IAAI,EACnDH,OAAAA,EACAb,MAAOrB,EAAOqB,KAAK,CAAG,CACxB,EAEIf,EAAO6B,IAAUb,EAAOgB,IAAI,CAACH,GAC7BD,EAAOK,WAAW,IAAM,CAAC7B,EAAQyB,IAAU,MAAMZ,EAAQY,EAE/D,EAAA,GAEJ,CAGA,OADA,MAAMZ,EAAQ,CAAEM,aAAc,IAAKR,MAAO,CAAE,GACrCN,EAAgBO,EAASA,EAAOkB,GAAG,CAACC,AAAAA,GAAQA,EAAKZ,YAAY,CACtE,KAEaxB,EAAqB,KAChC,IAAM0B,EAAU,CAAA,EAEhB,MAAO,CACLA,QAAAA,EACAW,QACE,IAAK,IAAMC,KAAOlB,OAAOmB,IAAI,CAACb,GAC5B,OAAOA,CAAO,CAACY,EAAI,AAEvB,CACF,CACF"}