Repository: nextapps-de/flexsearch Branch: master Commit: defb38b083f0 Files: 434 Total size: 3.6 MB Directory structure: gitextract_4puswicl/ ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ └── node.js.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── demo/ │ ├── autocomplete.html │ └── data/ │ ├── gulliver.js │ └── movies.js ├── dist/ │ ├── db/ │ │ ├── clickhouse/ │ │ │ └── index.cjs │ │ ├── indexeddb/ │ │ │ └── index.cjs │ │ ├── mongodb/ │ │ │ └── index.cjs │ │ ├── postgres/ │ │ │ └── index.cjs │ │ ├── redis/ │ │ │ └── index.cjs │ │ └── sqlite/ │ │ └── index.cjs │ ├── flexsearch.bundle.debug.js │ ├── flexsearch.bundle.module.debug.js │ ├── flexsearch.bundle.module.debug.mjs │ ├── flexsearch.bundle.module.min.mjs │ ├── flexsearch.compact.debug.js │ ├── flexsearch.compact.module.debug.js │ ├── flexsearch.es5.debug.js │ ├── flexsearch.light.debug.js │ ├── flexsearch.light.module.debug.js │ ├── module/ │ │ ├── async.js │ │ ├── bundle.js │ │ ├── cache.js │ │ ├── charset/ │ │ │ ├── cjk.js │ │ │ ├── exact.js │ │ │ ├── latin/ │ │ │ │ ├── advanced.js │ │ │ │ ├── balance.js │ │ │ │ ├── extra.js │ │ │ │ └── soundex.js │ │ │ ├── normalize.js │ │ │ └── polyfill.js │ │ ├── charset.js │ │ ├── common.js │ │ ├── compress.js │ │ ├── db/ │ │ │ ├── clickhouse/ │ │ │ │ └── index.js │ │ │ ├── indexeddb/ │ │ │ │ └── index.js │ │ │ ├── interface.js │ │ │ ├── mongodb/ │ │ │ │ └── index.js │ │ │ ├── postgres/ │ │ │ │ └── index.js │ │ │ ├── redis/ │ │ │ │ └── index.js │ │ │ └── sqlite/ │ │ │ └── index.js │ │ ├── document/ │ │ │ ├── add.js │ │ │ ├── highlight.js │ │ │ └── search.js │ │ ├── document.js │ │ ├── encoder.js │ │ ├── index/ │ │ │ ├── add.js │ │ │ ├── remove.js │ │ │ └── search.js │ │ ├── index.js │ │ ├── intersect.js │ │ ├── keystore.js │ │ ├── lang/ │ │ │ ├── de.js │ │ │ ├── en.js │ │ │ └── fr.js │ │ ├── preset.js │ │ ├── profiler.js │ │ ├── resolve/ │ │ │ ├── and.js │ │ │ ├── default.js │ │ │ ├── handler.js │ │ │ ├── not.js │ │ │ ├── or.js │ │ │ └── xor.js │ │ ├── resolver.js │ │ ├── serialize.js │ │ ├── type.js │ │ ├── worker/ │ │ │ ├── handler.js │ │ │ ├── node.js │ │ │ └── worker.js │ │ └── worker.js │ ├── module-debug/ │ │ ├── async.js │ │ ├── bundle.js │ │ ├── cache.js │ │ ├── charset/ │ │ │ ├── cjk.js │ │ │ ├── exact.js │ │ │ ├── latin/ │ │ │ │ ├── advanced.js │ │ │ │ ├── balance.js │ │ │ │ ├── extra.js │ │ │ │ └── soundex.js │ │ │ ├── normalize.js │ │ │ └── polyfill.js │ │ ├── charset.js │ │ ├── common.js │ │ ├── compress.js │ │ ├── db/ │ │ │ ├── clickhouse/ │ │ │ │ └── index.js │ │ │ ├── indexeddb/ │ │ │ │ └── index.js │ │ │ ├── interface.js │ │ │ ├── mongodb/ │ │ │ │ └── index.js │ │ │ ├── postgres/ │ │ │ │ └── index.js │ │ │ ├── redis/ │ │ │ │ └── index.js │ │ │ └── sqlite/ │ │ │ └── index.js │ │ ├── document/ │ │ │ ├── add.js │ │ │ ├── highlight.js │ │ │ └── search.js │ │ ├── document.js │ │ ├── encoder.js │ │ ├── index/ │ │ │ ├── add.js │ │ │ ├── remove.js │ │ │ └── search.js │ │ ├── index.js │ │ ├── intersect.js │ │ ├── keystore.js │ │ ├── lang/ │ │ │ ├── de.js │ │ │ ├── en.js │ │ │ └── fr.js │ │ ├── preset.js │ │ ├── profiler.js │ │ ├── resolve/ │ │ │ ├── and.js │ │ │ ├── default.js │ │ │ ├── handler.js │ │ │ ├── not.js │ │ │ ├── or.js │ │ │ └── xor.js │ │ ├── resolver.js │ │ ├── serialize.js │ │ ├── type.js │ │ ├── worker/ │ │ │ ├── handler.js │ │ │ ├── node.js │ │ │ └── worker.js │ │ └── worker.js │ └── module-min/ │ ├── async.js │ ├── bundle.js │ ├── cache.js │ ├── charset/ │ │ ├── cjk.js │ │ ├── exact.js │ │ ├── latin/ │ │ │ ├── advanced.js │ │ │ ├── balance.js │ │ │ ├── extra.js │ │ │ └── soundex.js │ │ ├── normalize.js │ │ └── polyfill.js │ ├── charset.js │ ├── common.js │ ├── compress.js │ ├── db/ │ │ ├── clickhouse/ │ │ │ └── index.js │ │ ├── indexeddb/ │ │ │ └── index.js │ │ ├── interface.js │ │ ├── mongodb/ │ │ │ └── index.js │ │ ├── postgres/ │ │ │ └── index.js │ │ ├── redis/ │ │ │ └── index.js │ │ └── sqlite/ │ │ └── index.js │ ├── document/ │ │ ├── add.js │ │ ├── highlight.js │ │ └── search.js │ ├── document.js │ ├── encoder.js │ ├── index/ │ │ ├── add.js │ │ ├── remove.js │ │ └── search.js │ ├── index.js │ ├── intersect.js │ ├── keystore.js │ ├── lang/ │ │ ├── de.js │ │ ├── en.js │ │ └── fr.js │ ├── preset.js │ ├── profiler.js │ ├── resolve/ │ │ ├── and.js │ │ ├── default.js │ │ ├── handler.js │ │ ├── not.js │ │ ├── or.js │ │ └── xor.js │ ├── resolver.js │ ├── serialize.js │ ├── type.js │ ├── worker/ │ │ ├── handler.js │ │ ├── node.js │ │ └── worker.js │ └── worker.js ├── doc/ │ ├── async.md │ ├── custom-builds.md │ ├── customization.md │ ├── document-search.md │ ├── encoder.md │ ├── export-import.md │ ├── keystore.md │ ├── persistent-clickhouse.md │ ├── persistent-indexeddb.md │ ├── persistent-mongodb.md │ ├── persistent-postgres.md │ ├── persistent-redis.md │ ├── persistent-sqlite.md │ ├── persistent.md │ ├── resolver.md │ ├── result-highlighting.md │ └── worker.md ├── docker-compose.yml ├── example/ │ ├── browser-legacy/ │ │ ├── basic/ │ │ │ └── index.html │ │ ├── basic-persistent/ │ │ │ └── index.html │ │ ├── basic-resolver/ │ │ │ └── index.html │ │ ├── basic-suggestion/ │ │ │ └── index.html │ │ ├── basic-worker/ │ │ │ └── index.html │ │ ├── document/ │ │ │ └── index.html │ │ ├── document-highlighting/ │ │ │ └── index.html │ │ ├── document-persistent/ │ │ │ └── index.html │ │ ├── document-resolver/ │ │ │ └── index.html │ │ ├── document-worker/ │ │ │ └── index.html │ │ └── language-pack/ │ │ └── index.html │ ├── browser-module/ │ │ ├── basic/ │ │ │ └── index.html │ │ ├── basic-persistent/ │ │ │ └── index.html │ │ ├── basic-resolver/ │ │ │ └── index.html │ │ ├── basic-suggestion/ │ │ │ └── index.html │ │ ├── basic-worker/ │ │ │ └── index.html │ │ ├── basic-worker-extern-config/ │ │ │ ├── config.js │ │ │ └── index.html │ │ ├── document/ │ │ │ └── index.html │ │ ├── document-highlighting/ │ │ │ └── index.html │ │ ├── document-persistent/ │ │ │ └── index.html │ │ ├── document-resolver/ │ │ │ └── index.html │ │ ├── document-worker/ │ │ │ └── index.html │ │ ├── document-worker-extern-config/ │ │ │ ├── config.originalTitle.js │ │ │ ├── config.primaryTitle.js │ │ │ └── index.html │ │ └── language-pack/ │ │ └── index.html │ ├── nodejs-commonjs/ │ │ ├── .document-worker-persistent/ │ │ │ ├── config.originalTitle.js │ │ │ ├── config.primaryTitle.js │ │ │ ├── data.json │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── basic/ │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── basic-export-import/ │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── basic-persistent/ │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── basic-resolver/ │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── basic-suggestion/ │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── basic-worker/ │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── basic-worker-export-import/ │ │ │ ├── config.js │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── basic-worker-extern-config/ │ │ │ ├── config.js │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── document/ │ │ │ ├── README.md │ │ │ ├── data.json │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── document-export-import/ │ │ │ ├── README.md │ │ │ ├── data.json │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── document-persistent/ │ │ │ ├── README.md │ │ │ ├── data.json │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── document-resolver/ │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── document-worker/ │ │ │ ├── README.md │ │ │ ├── data.json │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── document-worker-export-import/ │ │ │ ├── README.md │ │ │ ├── config.originalTitle.js │ │ │ ├── config.primaryTitle.js │ │ │ ├── data.json │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── document-worker-extern-config/ │ │ │ ├── README.md │ │ │ ├── config.originalTitle.js │ │ │ ├── config.primaryTitle.js │ │ │ ├── data.json │ │ │ ├── index.js │ │ │ └── package.json │ │ └── language-pack/ │ │ ├── README.md │ │ ├── index.js │ │ └── package.json │ └── nodejs-esm/ │ ├── basic/ │ │ ├── README.md │ │ ├── index.js │ │ └── package.json │ ├── basic-export-import/ │ │ ├── README.md │ │ ├── index.js │ │ └── package.json │ ├── basic-persistent/ │ │ ├── README.md │ │ ├── index.js │ │ └── package.json │ ├── basic-resolver/ │ │ ├── README.md │ │ ├── index.js │ │ └── package.json │ ├── basic-suggestion/ │ │ ├── README.md │ │ ├── index.js │ │ └── package.json │ ├── basic-worker/ │ │ ├── README.md │ │ ├── index.js │ │ └── package.json │ ├── basic-worker-export-import/ │ │ ├── config.js │ │ ├── index.js │ │ └── package.json │ ├── basic-worker-extern-config/ │ │ ├── README.md │ │ ├── config.js │ │ ├── index.js │ │ └── package.json │ ├── document/ │ │ ├── README.md │ │ ├── data.json │ │ ├── index.js │ │ └── package.json │ ├── document-export-import/ │ │ ├── README.md │ │ ├── data.json │ │ ├── index.js │ │ └── package.json │ ├── document-persistent/ │ │ ├── README.md │ │ ├── data.json │ │ ├── index.js │ │ └── package.json │ ├── document-resolver/ │ │ ├── README.md │ │ ├── index.js │ │ └── package.json │ ├── document-worker/ │ │ ├── README.md │ │ ├── data.json │ │ ├── index.js │ │ └── package.json │ ├── document-worker-export-import/ │ │ ├── README.md │ │ ├── config.originalTitle.js │ │ ├── config.primaryTitle.js │ │ ├── data.json │ │ ├── index.js │ │ └── package.json │ ├── document-worker-extern-config/ │ │ ├── README.md │ │ ├── config.originalTitle.js │ │ ├── config.primaryTitle.js │ │ ├── data.json │ │ ├── index.js │ │ └── package.json │ └── language-pack/ │ ├── README.md │ ├── index.js │ └── package.json ├── index.d.ts ├── package.json ├── src/ │ ├── async.js │ ├── bundle.js │ ├── cache.js │ ├── charset/ │ │ ├── cjk.js │ │ ├── exact.js │ │ ├── latin/ │ │ │ ├── advanced.js │ │ │ ├── balance.js │ │ │ ├── extra.js │ │ │ └── soundex.js │ │ ├── normalize.js │ │ └── polyfill.js │ ├── charset.js │ ├── common.js │ ├── compress.js │ ├── config.js │ ├── db/ │ │ ├── clickhouse/ │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── indexeddb/ │ │ │ └── index.js │ │ ├── interface.js │ │ ├── mongodb/ │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── postgres/ │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── redis/ │ │ │ ├── index.js │ │ │ └── package.json │ │ └── sqlite/ │ │ ├── index.js │ │ └── package.json │ ├── document/ │ │ ├── add.js │ │ ├── highlight.js │ │ └── search.js │ ├── document.js │ ├── encoder.js │ ├── index/ │ │ ├── add.js │ │ ├── remove.js │ │ └── search.js │ ├── index.js │ ├── intersect.js │ ├── keystore.js │ ├── lang/ │ │ ├── de.js │ │ ├── en.js │ │ └── fr.js │ ├── preset.js │ ├── profiler.js │ ├── resolve/ │ │ ├── and.js │ │ ├── default.js │ │ ├── handler.js │ │ ├── not.js │ │ ├── or.js │ │ └── xor.js │ ├── resolver.js │ ├── serialize.js │ ├── type.js │ ├── worker/ │ │ ├── handler.js │ │ ├── node.js │ │ ├── node.mjs │ │ └── worker.js │ └── worker.js ├── task/ │ ├── babel.bundle.json │ ├── babel.debug.json │ ├── babel.js │ ├── babel.min.json │ └── build.js └── test/ ├── .c8rc.json ├── async.js ├── basic.js ├── cache.js ├── context.js ├── debug.js ├── document.js ├── document.tag.js ├── encoder.js ├── highlight.js ├── issues.js ├── keystore.js ├── misc/ │ ├── reporter.js │ └── runner.js ├── package.json ├── persistent.clickhouse.js ├── persistent.js ├── persistent.mongo.js ├── persistent.postgres.js ├── persistent.redis.js ├── persistent.sqlite.js ├── resolver.js ├── scoring.js ├── serialize.js ├── tokenize.js ├── types.ts └── worker.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ index.html -linguist-detectable bench/** -linguist-detectable dist/** -linguist-detectable doc/** -linguist-detectable test/** -linguist-detectable demo/** -linguist-detectable task/** -linguist-detectable ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [ts-thomas] open_collective: flexsearch liberapay: ts-thomas patreon: user?u=96245532 custom: ["https://www.paypal.com/donate/?hosted_button_id=GEVR88FC9BWRW", "https://salt.bountysource.com/teams/ts-thomas"] ================================================ FILE: .github/workflows/node.js.yml ================================================ # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs name: Node.js CI permissions: contents: read pull-requests: write on: push: branches: [ "master" ] pull_request: branches: [ "master" ] jobs: # Label of the container job build: # Containers must run in Linux based operating systems runs-on: ubuntu-latest timeout-minutes: 10 # # Docker Hub image that `container-job` executes in # container: node:20-bookworm-slim # # # Service containers to run with `container-job` # services: # # Label used to access the service container # postgres: # # Docker Hub image # image: postgres # # Provide the password for postgres # env: # POSTGRES_USER: postgres # POSTGRES_PASSWORD: postgres # POSTGRES_DATABASE: postgres # POSTGRES_HOST: postgres # POSTGRES_PORT: 5432 # # # Set health checks to wait until postgres has started # options: >- # --health-cmd pg_isready # --health-interval 10s # --health-timeout 5s # --health-retries 5 strategy: matrix: node-version: [20.x, 22.x, 24.x] # 18.x # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' - uses: actions/setup-java@v1 with: java-version: 21 - run: npm install - run: npm run build:compact - run: npm run build:module:compact - run: npm run build:light - run: npm run build:module:light - run: npm install working-directory: test - run: npm run test:github working-directory: test ================================================ FILE: .gitignore ================================================ .idea .nyc_output .db !*.keep coverage perf node_modules server tmp log issue node ================================================ FILE: CHANGELOG.md ================================================ # Changelog ### Current Version - Calling `index.clear()` on a persistent Index does not stack to the task queue by default (which executes on commit), instead it will execute immediately and return a Promise - Added new tokenizer `tolerant`, inherits from `strict` but also matches simple typos like missing letters and swapped letters - Improved Redis Cleanup - Resolver: Support Result Highlighting ### v0.8.2 - Config-Serialized Query Caches, Improved caching strategy for Document indexes and Resolver - Resolver Async Processing Workflow (including Queuing) - Extended Resolver Support: Worker, Persistent, Cache - Extended Result Highlighting: Boundaries, Ellipsis, Alignment - Improved TypeScript Typings - Improved Stemmer Handling - Improved Result Highlighting - Use multi-language charset normalization as the default `Encoder` - Simplified charset support for multi-language content - Charset renamed `LatinExact` => `Exact`, `LatinDefault` => `Default` and `LatinSimple` => `Normalize`, these are universal charset presets for any languages - Charset `ArabicDefault` and `CyrillicDefault` was removed, they are fully covered by the default universal charset presets - Charset `Charset.CjkDefault` was renamed to `Charset.CJK` ### v0.8.1 - Resolver Support for Documents - Asynchronous Runtime Balancer, new option `priority` - Export/Import Worker Indexes + Document Worker, new extern config options `export` and `import` - Improved interoperability of the different build packages, including source folder - Support custom `filter` function for encoder (stop-word filter) ### v0.8.0 - Persistent indexes support for: `IndexedDB` (Browser), `Redis`, `SQLite`, `Postgres`, `MongoDB`, `Clickhouse` - Enhanced language customization via the new `Encoder` class - Result Highlighting - Query performance achieve results up to 4.5 times faster compared to the previous generation v0.7.x by also improving the quality of results - Enhanced support for larger indexes or larger result sets - Improved offset and limit processing achieve up to 100 times faster traversal performance through large datasets - Support for larger In-Memory index with extended key size (the defaults maximum keystore limit is: 2^24) - Greatly enhanced performance of the whole text encoding pipeline - Improved indexing of numeric content (Triplets) - Intermediate result sets and `Resolver` - Basic Resolver: `and`, `or`, `xor`, `not`, `limit`, `offset`, `boost`, `resolve` - Improved charset collection - New charset preset `soundex` which further reduces memory consumption by also increasing "fuzziness" - Performance gain when polling tasks to the index by using "Event-Loop-Caches" - Up to 100 times faster deletion/replacement when not using the additional "fastupdate" register - Regex Pre-Compilation (transforms hundreds of regex rules into just a few) - Extended support for multiple tags (DocumentIndex) - Custom Fields ("Virtual Fields") - Custom Filter - Custom Score Function - Added French language preset (stop-word filter, stemmer) - Enhanced Worker Support - Export / Import index in chunks - Improved Build System + Bundler (Supported: CommonJS, ESM, Global Namespace), also the import of language packs are now supported for Node.js - Full covering index.d.ts type definitions - Fast-Boot Serialization optimized for Server-Side-Rendering (PHP, Python, Ruby, Rust, Java, Go, Node.js, ...) ### v0.7.0 - Bidirectional Context (the order of words can now vary, does not increase memory when using bidirectional context) - New memory-friendly strategy for indexes (switchable, saves up to 50% of memory for each index, slightly decrease performance) - Better scoring calculation (one of the biggest concerns of the old implementation was that the order of arrays processed in the intersection has affected the order of relevance in the final result) - Fix resolution (the resolution in the old implementation was not fully stretched through the whole range in some cases) - Skip words (optionally, automatically skip words from the context chain which are too short) - Hugely improves performance of long queries (up to 450x faster!) and also memory allocation (up to 250x less memory) - New fast-update strategy (optionally, hugely improves performance of all updates and removals of indexed contents up to 2850x) - Improved auto-balanced cache (keep and expire cache by popularity) - Append contents to already existing entries (already indexed documents or contents) - New method "contain" to check if an ID was already indexed - Access documents directly from internal store (read/write) - Suggestions are hugely improved, falls back from context search all the way down to single term match - Document descriptor has now array support (optionally adds array entries via the new `append` under the hood to provide a unique relevance context for each entry) - Document storage handler gets improved - Results from document index now grouped by field (this is one of the few bigger breaking changes which needs migrations of your old code) - Boolean search has a new concept (use in combination of the new result structure) - Node.js Worker Threads - Improved default latin encoders - New parallelization model and workload distribution - Improved Export/Import - Tag Search - Offset pagination - Enhanced Field Search - Improved sorting by relevance (score) - Added Context Scoring (context index has its own resolution) - Enhanced charset normalization - Improved bundler (support for inline WebWorker) These features have been removed: - Where-Clause - Index Information `index.info()` - Paging Cursor (was replaced by `offset`) #### Migration Quick Overview > The "async" options was removed, instead you can call each method in its async version, e.g. `index.addAsync` or `index.searchAsync`. > Define document fields as object keys is not longer supported due to the unification of all option payloads. A full configuration example for a context-based index: ```js var index = new Index({ tokenize: "strict", resolution: 9, minlength: 3, optimize: true, fastupdate: true, cache: 100, context: { depth: 1, resolution: 3, bidirectional: true } }); ``` The `resolution` could be set also for the contextual index. A full configuration example for a document based index: ```js const index = new Document({ tokenize: "forward", optimize: true, resolution: 9, cache: 100, worker: true, document: { id: "id", tag: "tag", store: [ "title", "content" ], index: [{ field: "title", tokenize: "forward", optimize: true, resolution: 9 },{ field: "content", tokenize: "strict", optimize: true, resolution: 9, minlength: 3, context: { depth: 1, resolution: 3 } }] } }); ``` A full configuration example for a document search: ```js index.search({ enrich: true, bool: "and", tag: ["cat", "dog"], index: [{ field: "title", query: "some query", limit: 100, suggest: true },{ field: "content", query: "same or other query", limit: 100, suggest: true }] }); ``` #### Where Clause Replacement Old Syntax: ```js const result = index.where({ cat: "comedy", year: "2018" }); ``` Equivalent Syntax (0.7.x): ```js const data = Object.values(index.store); ``` The line above retrieves data from the document store (just useful when not already available in your runtime). ```js const result = data.filter(function(item){ return item.cat === "comedy" && item.year === "2018"; }); ``` Also considering using the Tag-Search feature, which partially replaces the Where-Clause with a huge performance boost. ### v0.6.0 - Pagination ### v0.5.3 - Logical Operator ### v0.5.2 - Intersect Partial Results ### v0.5.1 - Customizable Scoring Resolution ### v0.5.0 - Where / Find Documents - Document Tags - Custom Sort ### v0.4.0 - Index Documents (Field-Search) ### v0.3.6 - Right-To-Left Support - CJK Word Splitting Support ### v0.3.5 - Promise Support ### v0.3.4 - Export / Import Indexes (Serialize) ### v0.3.0 - Profiler Support ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ Getting instant help by the DeepWiki AI assistant: [Ask DeepWiki.com](https://deepwiki.com/nextapps-de/flexsearch)

FlexSearch.js: Next-Generation full-text search library for Browser and Node.js

Next-Generation full-text search library for Browser and Node.js

[Basic Start](#load-library)  •  [API Reference](#api-overview)  •  [Encoder](doc/encoder.md)  •  [Document Search](doc/document-search.md)  •  [Persistent Indexes](doc/persistent.md)  •  [Using Worker](doc/worker.md)  •  [Tag Search](doc/document-search.md#tag-search)  •  [Highlighting](doc/result-highlighting.md)  •  [Resolver](doc/resolver.md)  •  [Changelog](CHANGELOG.md) ## Please Support this Project FlexSearch has been helping developers around the world build powerful, efficient search functionalities for years. Maintaining and improving the library requires significant time and resources. If you’ve found this project valuable and you're interested in supporting the project, please consider donating. Thanks a lot for your continued support! Donate using Open Collective Donate using Github Sponsors Donate using Liberapay Donate using Patreon Donate using PayPal ### FlexSearch Sponsors
Donate using Open Collective
Antithesis Operations LLC

FlexSearch performs queries up to 1,000,000 times faster compared to other libraries by also providing powerful search capabilities like multi-field search (document search), phonetic transformations, partial matching, tag-search, result highlighting or suggestions. Bigger workloads are scalable through workers to perform any updates or queries to the index in parallel through dedicated balanced threads. The latest generation v0.8 introduce [Persistent Indexes](doc/persistent.md), well optimized for scaling of large datasets and running in parallel. All available features was natively ported right into the database engine of your choice. FlexSearch was nominated by the GitNation for the "Best Technology of the Year". Supported Platforms: - Browser - Node.js Supported Database: - InMemory (Default) - [IndexedDB (Browser)](doc/persistent-indexeddb.md) - [Redis](doc/persistent-redis.md) - [SQLite](doc/persistent-sqlite.md) - [Postgres](doc/persistent-postgres.md) - [MongoDB](doc/persistent-mongodb.md) - [Clickhouse](doc/persistent-clickhouse.md) Supported Charsets: - Latin - Chinese, Korean, Japanese (CJK) - Hindi - Arabic - Cyrillic - Greek and Coptic - Hebrew Common Code Examples: - Node.js: [Module (ESM)](example/nodejs-esm) - Node.js: [CommonJS](example/nodejs-commonjs) - Browser: [Module (ESM)](example/browser-module) - Browser: [Legacy Script](example/browser-legacy) Demos: - Auto-Complete Benchmarks: - Performance Benchmark - Matching Benchmark
Latest Benchmark Results
The benchmark was measured in terms per seconds, higher values are better (except the test "Memory"). The memory value refers to the amount of memory which was additionally allocated during search.
Library Memory Query: Single Query: Multi Query: Large Query: Not Found
flexsearch 16 50955718 11912730 13981110 51706499
jsii 2188 13847 949559 1635959 3730307
wade 980 60473 443214 419152 1239372
js-search 237 22982 383775 426609 994803
minisearch 4777 30589 191657 5849 304233
orama 5355 29445 170231 4454 225491
elasticlunr 3073 14326 48558 101206 95840
lunr 2443 11527 51476 88858 103386
ufuzzy 13754 2799 7788 58544 9557
bm25 33963 3903 4777 12657 12471
fuzzysearch 300147 148 229 455 276
fuse 247107 422 321 337 329
Run Comparison: Performance Benchmark "Gulliver's Travels"
Extern Projects & Plugins: - React: https://github.com/angeloashmore/react-use-flexsearch - Vue: https://github.com/Noction/vue-use-flexsearch - Gatsby: https://www.gatsbyjs.org/packages/gatsby-plugin-flexsearch/ - Nikola: https://plugins.getnikola.com/v8/flexsearch_plugin/ ## Table of contents > [!TIP] > Understanding those 3 elementary things about FlexSearch will improve your results significantly: [Tokenizer](#tokenizer-partial-match), [Encoder](doc/encoder.md) and [Suggestions](#suggestions) - [Load Library (Node.js, ESM, Legacy Browser)](#load-library) - [Non-Module Bundles (ES5 Legacy)](#non-module-bundles-es5-legacy) - [Module (ESM)](#module-esm) - [Node.js](#nodejs) - [Basic Usage and Variants](#basic-usage-and-variants) - [Index Options](#index-options) - [Search Options](#search-options) - [Common Code Examples (Browser, Node.js)](#common-code-examples) - [API Overview](#api-overview) - [Presets](#presets) - [Context Search](#context-search) - [Context Options](#context-options) - [Fast-Update Mode](#fast-update-mode) - [Suggestions](#suggestions) - [Document Search (Multi-Field Search)](doc/document-search.md) - [Document Index Options](doc/document-search.md#document-options) - [Document Descriptor](doc/document-search.md#the-document-descriptor) - [Document Search Options](doc/document-search.md#document-search-options) - [Multi-Tag Search](doc/document-search.md) - [Result Highlighting](doc/result-highlighting.md) - [Highlighting Options](doc/result-highlighting.md#highlighting-options) - [Boundary Options](doc/result-highlighting.md#highlighting-boundary-options) - [Ellipsis Options](doc/result-highlighting.md#highlighting-ellipsis-options) - [Phonetic Search (Fuzzy Search)](#fuzzy-search) - [Tokenizer (Partial Search)](#tokenizer-partial-match) - [Charset Collection](#charset-collection) - [Encoder](doc/encoder.md) - [Encoder Options](doc/encoder.md#encoder-options) - [Universal Charset Collection](doc/encoder.md) - [Latin Charset Encoder Presets](doc/encoder.md) - [Language Specific Preset](doc/encoder.md) - [Custom Encoder](doc/encoder.md#custom-encoder) - [Async Non-Blocking Runtime Balancer](doc/async.md) - [Worker Indexes](doc/worker.md) - [Worker Index Options](doc/worker.md#worker-index-options) - [Resolver (Complex Queries)](doc/resolver.md) - [Resolver Options](doc/resolver.md) - [Boolean Operations (and, or, xor, not)](doc/resolver.md) - [Boost](doc/resolver.md) - [Limit / Offset](doc/resolver.md) - [Resolve](doc/resolver.md) - [Auto-Balanced Cache by Popularity/Last Query](#auto-balanced-cache-by-popularity) - [Export / Import Indexes](doc/export-import.md) - [Fast-Boot Serialization](doc/export-import.md#fast-boot-serialization-for-server-side-rendering-php-python-ruby-rust-java-go-nodejs-) - [Persistent Indexes](doc/persistent.md) - [Persistent Index Options](doc/persistent.md) - [IndexedDB (Browser)](doc/persistent-indexeddb.md) - [Postgres](doc/persistent-postgres.md) - [Redis](doc/persistent-redis.md) - [MongoDB](doc/persistent-mongodb.md) - [SQLite](doc/persistent-sqlite.md) - [Clickhouse](doc/persistent-clickhouse.md) - [Custom Score Function](doc/customization.md) - [Custom Builds](doc/custom-builds.md) - [Extended Keystores (In-Memory Index)](doc/keystore.md) - [Best Practices](#best-practices) - [Page-Load / Fast-Boot](#page-load--fast-boot) - [Prefer numeric typed IDs](#use-numeric-ids) ## Load Library (Node.js, ESM, Legacy Browser) ```bash npm install flexsearch ``` The **_dist_** folder is located in: `node_modules/flexsearch/dist/` > It is not recommended to use the `/src/` folder of this repository as it requires some kind of conditional compilation to resolve the build flags. The `/dist/` folder contains every version you might need including unminified ES6 modules. When none of the `/dist/` folder versions works for you please open an issue. Alternatively you can read more about [Custom Builds](doc/custom-builds.md).
Download Builds
****
Build File CDN
flexsearch.bundle.min.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.bundle.min.js
flexsearch.bundle.debug.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.bundle.debug.js
flexsearch.bundle.module.min.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.bundle.module.min.js
flexsearch.bundle.module.debug.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.bundle.module.debug.js
flexsearch.compact.min.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.compact.min.js
flexsearch.compact.debug.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.compact.debug.js
flexsearch.compact.module.min.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.compact.module.min.js
flexsearch.compact.module.debug.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.compact.module.debug.js
flexsearch.light.min.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.light.min.js
flexsearch.light.debug.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.light.debug.js
flexsearch.light.module.min.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.light.module.min.js
flexsearch.light.module.debug.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.light.module.debug.js
flexsearch.es5.min.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.es5.min.js
flexsearch.es5.debug.js Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.es5.debug.js
Javascript Modules (ESM) Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/module/
Javascript Modules Minified (ESM) Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/module-min/
Javascript Modules Debug (ESM) Download https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/module-debug/
flexsearch.custom.js Read more about "Custom Build"
Compare Bundles: Light, Compact, Bundle
> The Node.js package includes all features.
Feature flexsearch.bundle.js flexsearch.compact.js flexsearch.light.js
Presets
Async Processing -
Workers (Web + Node.js) - -
Context Search
Document Search -
Document Datastore -
Partial Matching
Auto-Balanced Cache by Popularity/Last Queries -
Tag Search -
Suggestions
Phonetic Search (Fuzzy Search) -
Encoder
Export / Import Indexes -
Resolver - -
Result Highlighting -
Persistent Index (IndexedDB) - -
File Size (gzip) 16.3 kb 11.4 kb 4.5 kb
> [!TIP] > All debug versions are providing debug information through the console and gives you helpful advices on certain situations. Do not use them in production, since they are special builds containing extra debugging processes which noticeably reduce performance. The abbreviations used at the end of the filenames indicates: - `bundle` All features included, FlexSearch is available on `window.FlexSearch` - `light` Only basic features are included, FlexSearch is available on `window.FlexSearch` - `es5` bundle has support for EcmaScript5, FlexSearch is available on `window.FlexSearch` - `module` indicates that this bundle is a Javascript module (ESM), FlexSearch members are available by `import { Index, Document, Worker, Encoder, Charset } from "./flexsearch.bundle.module.min.js"` or alternatively using the default export `import FlexSearch from "./flexsearch.bundle.module.min.js"` - `min` bundle is minified - `debug` bundle has enabled debug mode and contains additional code just for debugging purposes (do not use for production) ## Load Library ### Non-Module Bundles (ES5 Legacy) > Non-Module Bundles export all their features to the public namespace "FlexSearch" e.g. `window.FlexSearch.Index` or `window.FlexSearch.Document`. Load the bundle by a script tag: ```html ``` FlexSearch Members are accessible on: ```js var Index = window.FlexSearch.Index; var Document = window.FlexSearch.Document; var Encoder = window.FlexSearch.Encoder; var Charset = window.FlexSearch.Charset; var Resolver = window.FlexSearch.Resolver; var Worker = window.FlexSearch.Worker; var IdxDB = window.FlexSearch.IndexedDB; // only exported by non-module builds: var Language = window.FlexSearch.Language; ``` Load language packs: ```html ``` ### Module (ESM) When using modules you can choose from 2 variants: `flexsearch.xxx.module.min.js` has all features bundled ready for production, whereas the folder `/dist/module/` export all the features in the same structure as the source code but here compiler flags was resolved. Also, for each variant there exist: 1. A debug version for the development 2. A pre-compiled minified version for production Use the bundled version exported as a module (default export): ```html ``` Or import FlexSearch members separately by: ```html ``` Use bundled style on non-bundled modules: ```html ``` Use non-bundled modules by file default exports: ```html ``` Language packs are accessible via: ```js import EnglishEncoderPreset from "./dist/module/lang/en.js"; import GermanEncoderPreset from "./dist/module/lang/de.js"; import FrenchEncoderPreset from "./dist/module/lang/fr.js"; ``` Also, pre-compiled non-bundled production-ready modules are located in `dist/module-min/`, whereas the debug version is located at `dist/module-debug/`. You can also load modules via CDN: ```html ``` ### Node.js Install FlexSearch via NPM: ```npm npm install flexsearch ``` Use the default export: ```js const FlexSearch = require("flexsearch"); const index = new FlexSearch.Index(/* ... */); ``` Or require FlexSearch members separately by: ```js const { Index, Document, Encoder, Charset, Resolver, Worker } = require("flexsearch"); const index = new Index(/* ... */); ``` When using ESM instead of CommonJS: ```js import { Index, Document, Encoder, Charset, Resolver, Worker } from "flexsearch"; const index = new Index(/* ... */); ``` Language packs are accessible via: ```js const EnglishEncoderPreset = require("flexsearch/lang/en"); const GermanEncoderPreset = require("flexsearch/lang/de"); const FrenchEncoderPreset = require("flexsearch/lang/fr"); ``` Persistent Connectors are accessible via: ```js const Postgres = require("flexsearch/db/postgres"); const Sqlite = require("flexsearch/db/sqlite"); const MongoDB = require("flexsearch/db/mongodb"); const Redis = require("flexsearch/db/redis"); const Clickhouse = require("flexsearch/db/clickhouse"); ``` ## Basic Usage and Variants There are 3 types of indexes: 1. `Index` is a flat high performance index which stores id-content-pairs. 2. `Worker` / `WorkerIndex` is also a flat index which stores id-content-pairs but runs in background as a dedicated worker thread. 3. `Document` is multi-field index which can store complex JSON documents (could also exist of worker indexes). The most of you probably need just one of them according to your scenario. Any of these 3 index type are upgradable to persistent indexes. The `worker` instance inherits from type `Index` and basically works like a standard FlexSearch Index. A document index is a complex register automatically operating on several of those standard indexes in parallel. Worker-Support in documents needs to be enabled by just passing the appropriate option during creation e.g. `{ worker: true }`. ```js index.add(id, text); const result = index.search(text, options); ``` ```js document.add(doc); const result = document.search(text, options); ``` ```js await worker.add(id, text); const result = await worker.search(text, options); ``` > Every method called on a `Worker` index is treated as async. You will get back a `Promise` or you can provide a callback function as the last parameter alternatively. ### Common Code Examples The documentation will refer to several examples. A list of all examples:
Examples Node.js (CommonJS)
- [basic](example/nodejs-commonjs/basic) - [basic-suggestion](example/nodejs-commonjs/basic-suggestion) - [basic-persistent](example/nodejs-commonjs/basic-persistent) - [basic-resolver](example/nodejs-commonjs/basic-resolver) - [basic-worker](example/nodejs-commonjs/basic-worker) - [basic-worker-extern-config](example/nodejs-commonjs/basic-worker-extern-config) - [basic-worker-export-import](example/nodejs-commonjs/basic-worker-export-import) - [basic-export-import](example/nodejs-commonjs/basic-export-import) - [document](example/nodejs-commonjs/document) - [document-persistent](example/nodejs-commonjs/document-persistent) - [document-resolver](example/nodejs-commonjs/document-resolver) - [document-worker](example/nodejs-commonjs/document-worker) - [document-worker-extern-config](example/nodejs-commonjs/document-worker-extern-config) - [document-export-import](example/nodejs-commonjs/document-export-import) - [document-worker-export-import](example/nodejs-commonjs/document-worker-export-import) - [language-pack](example/nodejs-commonjs/language-pack)
Examples Node.js (ESM/Module)
- [basic](example/nodejs-esm/basic) - [basic-suggestion](example/nodejs-esm/basic-suggestion) - [basic-persistent](example/nodejs-esm/basic-persistent) - [basic-resolver](example/nodejs-esm/basic-resolver) - [basic-worker](example/nodejs-esm/basic-worker) - [basic-worker-extern-config](example/nodejs-esm/basic-worker-extern-config) - [basic-worker-export-import](example/nodejs-esm/basic-worker-export-import) - [basic-export-import](example/nodejs-esm/basic-export-import) - [document](example/nodejs-esm/document) - [document-persistent](example/nodejs-esm/document-persistent) - [document-resolver](example/nodejs-esm/document-resolver) - [document-worker](example/nodejs-esm/document-worker) - [document-worker-extern-config](example/nodejs-esm/document-worker-extern-config) - [document-export-import](example/nodejs-esm/document-export-import) - [document-worker-export-import](example/nodejs-esm/document-worker-export-import) - [language-pack](example/nodejs-esm/language-pack)s
Examples Browser (Legacy)
- [basic](example/browser-legacy/basic) - [basic-suggestion](example/browser-legacy/basic-suggestion) - [basic-persistent](example/browser-legacy/basic-persistent) - [basic-resolver](example/browser-legacy/basic-resolver) - [basic-worker](example/browser-legacy/basic-worker) - [document](example/browser-legacy/document) - [document-highlighting](example/browser-legacy/document-highlighting) - [document-persistent](example/browser-legacy/document-persistent) - [document-resolver](example/browser-legacy/document-resolver) - [document-worker](example/browser-legacy/document-worker) - [language-pack](example/browser-legacy/language-pack)
Examples Browser (ESM/Module)
- [basic](example/browser-module/basic) - [basic-suggestion](example/browser-module/basic-suggestion) - [basic-persistent](example/browser-module/basic-persistent) - [basic-resolver](example/browser-module/basic-resolver) - [basic-worker](example/browser-module/basic-worker) - [basic-worker-extern-config](example/browser-module/basic-worker-extern-config) - [document](example/browser-module/document) - [document-highlighting](example/browser-module/document-highlighting) - [document-persistent](example/browser-module/document-persistent) - [document-resolver](example/browser-module/document-resolver) - [document-worker](example/browser-module/document-worker) - [document-worker-extern-config](example/browser-module/document-worker-extern-config) - [language-pack](example/browser-module/language-pack)
## API Overview Constructors: - new [**Index**](#basic-usage)(\) : _index_ - new [**Document**](doc/document-search.md)(options) : _document_ - new [**Worker**](doc/worker.md)(\) : _worker_ - new [**Encoder**](doc/encoder.md)(\, \, ...) : _encoder_ - new [**Resolver**](doc/resolver.md)(\) : _resolver_ - new [**IndexedDB**](doc/persistent-indexeddb.md)(\) : _indexeddb_ --- Global Members: - [**Charset**](#charset-collection) - [**Language**](doc/encoder.md#built-in-language-packs) (Legacy Browser Only) --- `Index` / `Worker`-Index Methods: - index.[**add**](#add-text-item-to-an-index)(id, string) - index.[**append**]()(id, string) - index.[**update**](#update-item-from-an-index)(id, string) - index.[**remove**](#remove-item-from-an-index)(id) - index.[**search**](#search-items)(string, \, \) - index.[**search**](#search-items)(options) - index.[**searchCache**](#auto-balanced-cache-by-popularity)(...) - index.[**contain**](#check-existence-of-already-indexed-ids)(id) - index.[**clear**](#clear-all-items-from-an-index)() - index.[**cleanup**](#fast-update-mode)() ## - _async_ index.[**export**](doc/export-import.md)(handler) - _async_ index.[**import**](doc/export-import.md)(key, data) - _async_ index.[**serialize**](doc/export-import.md#fast-boot-serialization-for-server-side-rendering-php-python-ruby-rust-java-go-nodejs-)(boolean) ## - _async_ index.[**mount**](doc/persistent.md)(db) - _async_ index.[**commit**](doc/persistent.md)() - _async_ index.[**destroy**](doc/persistent.md#delete-store--migration)() --- `Document` Methods: - document.[**add**](doc/document-search.md#addupdateremove-documents)(\, document) - document.[**append**]()(\, document) - document.[**update**](doc/document-search.md#addupdateremove-documents)(\, document) - document.[**remove**](doc/document-search.md#addupdateremove-documents)(id) - document.[**remove**](doc/document-search.md#addupdateremove-documents)(document) - document.[**search**](doc/document-search.md#document-search-field-search)(string, \, \) - document.[**search**](doc/document-search.md#document-search-field-search)(options) - document.[**searchCache**](#auto-balanced-cache-by-popularity)(...) - document.[**contain**](doc/document-search.md)(id) - document.[**clear**](doc/document-search.md)() - document.[**cleanup**](#fast-update-mode)() - document.[**get**](doc/document-search.md#document-store)(id) - document.[**set**](doc/document-search.md#document-store)(\, document) ## - _async_ document.[**export**](doc/export-import.md)(handler) - _async_ document.[**import**](doc/export-import.md)(key, data) ## - _async_ document.[**mount**](doc/persistent.md)(db) - _async_ document.[**commit**](doc/persistent.md)() - _async_ document.[**destroy**](doc/persistent.md#delete-store--migration)() `Document` Properties: - document.[**store**](doc/document-search.md#document-store) --- Async Equivalents (Non-Blocking Balanced): - _async_ [**.addAsync**](doc/async.md)( ... , \) - _async_ [**.appendAsync**](doc/async.md)( ... , \) - _async_ [**.updateAsync**](doc/async.md)( ... , \) - _async_ [**.removeAsync**](doc/async.md)( ... , \) - _async_ [**.searchAsync**](doc/async.md)( ... , \) - _async_ [**.searchCacheAsync**](doc/async.md)( ... , \) Async methods will return a `Promise`, additionally you can pass a callback function as the last parameter. Methods `.export()` and also `.import()` are always async as well as every method you call on a `Worker`-based or `Persistent` Index. --- `Encoder` Methods: - encoder.[**encode**](doc/encoder.md)(string) - encoder.[**assign**](doc/encoder.md)(options, \, ...) - encoder.[**addFilter**](doc/encoder.md#add-language-specific-stemmer-andor-filter)(string) - encoder.[**addStemmer**](doc/encoder.md#add-language-specific-stemmer-andor-filter)(string => boolean) - encoder.[**addMapper**](doc/encoder.md)(char, char) - encoder.[**addMatcher**](doc/encoder.md)(string, string) - encoder.[**addReplacer**](doc/encoder.md)(regex, string) --- `Resolver` Methods: - resolver.[**and**](doc/resolver.md)(options) - resolver.[**or**](doc/resolver.md)(options) - resolver.[**xor**](doc/resolver.md)(options) - resolver.[**not**](doc/resolver.md)(options) - resolver.[**boost**](doc/resolver.md)(number) - resolver.[**limit**](doc/resolver.md)(number) - resolver.[**offset**](doc/resolver.md)(number) - resolver.[**resolve**](doc/resolver.md)(\) `Resolver` Properties: - resolver.[**result**](doc/resolver.md) - resolver.[**await**](doc/resolver.md) (Async) --- `StorageInterface` Methods: - _async_ db.[**mount**](doc/persistent.md)(index, \) - _async_ db.[**open**](doc/persistent.md)() - _async_ db.[**close**](doc/persistent.md)() - _async_ db.[**destroy**](doc/persistent.md)() - _async_ db.[**clear**](doc/persistent.md)() - _async_ db.[**commit**](doc/persistent.md)(index) --- `Charset` Universal Encoder Preset: - Charset.[**Exact**](#charset-collection) - Charset.[**Default**](#charset-collection) - Charset.[**Normalize**](#charset-collection) `Charset` Latin-specific Encoder Preset: - Charset.[**LatinBalance**](#charset-collection) - Charset.[**LatinAdvanced**](#charset-collection) - Charset.[**LatinExtra**](#charset-collection) - Charset.[**LatinSoundex**](#charset-collection) `Charset` Chinese, Japanese, Korean Encoder Preset: - Charset.[**CJK**](#charset-collection) --- `Language` Encoder Preset: - [**en**](doc/encoder.md#built-in-language-packs) - [**de**](doc/encoder.md#built-in-language-packs) - [**fr**](doc/encoder.md#built-in-language-packs) ## Basic Usage #### Create a new index ```js const index = new Index(); ``` Create a new index and choosing one of the [Presets](#presets): ```js const index = new Index("match"); ``` Create a new index with custom options: ```js const index = new Index({ tokenize: "forward" }); ``` Create a new index and extend a preset with custom options: ```js var index = new FlexSearch({ preset: "memory", tokenize: "forward", resolution: 5 }); ``` Create a new index and assign an [Encoder](doc/encoder.md): ```js import { Charset } from "flexsearch"; const index = new Index({ tokenize: "forward", encoder: Charset.LatinBalance }); ``` Related Topics: [Index Options](#index-options)  •  [Resolution](#resolution)  •  [Charset Collection](#charset-collection)  •  [Tokenizer](#tokenizer-partial-match) #### Add text item to an index Every content which should be added to the index needs an ID. When your content has no ID, then you need to create one by passing an index or count or something else as an ID (a value from type `number` is highly recommended). Those IDs are unique references to a given content. This is important when you update or adding over content through existing IDs. When referencing is not a concern, you can simply use something simple like `count++`. > Index.__add(id, string)__ ```js index.add(0, "John Doe"); ``` #### Search items > Index.__search(string | options, \, \)__ ```js index.search("John"); ``` Limit the result: ```js index.search("John", 10); ``` #### Check existence of already indexed IDs You can check if an ID was already indexed by: ```js if(index.contain(1)){ console.log("ID was found in index"); } ``` #### Update item from an index > Index.__update(id, string)__ ```js index.update(0, "Max Miller"); ``` #### Remove item from an index > Index.__remove(id)__ ```js index.remove(0); ``` #### Clear all items from an index > Index.__clear()__ ```js index.clear(); ``` ### Chaining Simply chain methods like: ```js const index = new Index().addMatcher({'â': 'a'}).add(0, 'foo').add(1, 'bar'); ``` ```js index.remove(0).update(1, 'foo').add(2, 'foobar'); ``` ## Index Options
Option Values Description Default
preset "memory"
"performance"
"match"
"score"
"default"
The configuration profile as a shortcut or as a base for your custom settings.
"default"
tokenize "strict" / "exact"
"tolerant"
"forward"
"reverse" / "bidirectional
"full"
Indicates how terms should be indexed by tokenization. "strict"
resolution Number Sets the scoring resolution 9
encoder new Encoder(options)
Charset.Exact
Charset.Default
Charset.Normalize
Charset.LatinBalance
Charset.LatinAdvanced
Charset.LatinExtra
Charset.LatinSoundex
Charset.CJK
false
Choose one of the built-in encoder
Read more about Encoder
"default"
encode function(string) => string[] Pass a custom encoding function
Read more about Encoder
"default"
context Boolean
Context Options
Enable/Disable context index. When passing "true" as a value will use the defaults for the context. false
cache Boolean
Number
Enable/Disable and/or set capacity of cached entries.

The cache automatically balance stored entries related to their popularity.
false
fastupdate Boolean Additionally add a fastupdate index which boost any replace/update/remove task to a high performance level by also increasing index size by ~30%. false
priority Number Sets the task execution priority (1 low priority - 9 high priority) when using the async methods 4
score function(string) => number Use a custom score function
keystore Number Increase available size for In-Memory-Index by additionally using uniform balanced registers (Keystore). You can apply values from 1 to 64. false
Persistent Options:
db StorageInterface Pass an instance of a persistent adapter
commit Boolean When disabled any changes won't commit, instead it needs calling index.commit() manually to make modifications to the index (add, update, remove) persistent. true
## Search Options
Option Values Description Default
limit number Sets the limit of results 100
offset number Apply offset (skip items) 0
resolution number Limit the resolution (score) of the results
suggest Boolean Enables Suggestions in results false
cache Boolean Use a Query Cache false
resolve Boolean When set to false, an instance of a Resolver is returned to apply further operations true
## Suggestions Any query on each of the index types is supporting the option `suggest: true`. Also within some of the `Resolver` stages (and, not, xor) you can add this option for the same purpose. When suggestions is enabled, it allows results which does not perfectly match to the given query e.g. when one term was not included. Suggestion-Search will keep track of the scoring, therefore the first result entry is the closest one to a perfect match. ```js const index = new Index().add(1, "cat dog bird"); const result = index.search("cat fish"); // result => [] ``` Same query with suggestion enabled: ```js const result = index.search("cat fish", { suggest: true }); // result => [ 1 ] ``` At least one match (or partial match) has to be found to get back any result: ```js const result = index.search("horse fish", { suggest: true }); // result => [] ``` ## Resolution The resolution refers to the maximum count of scoring slots on which the content is divided into. > A formula to determine a well-balanced value for the `resolution` is: $2*floor(\sqrt{content.length})$ where content is the largest value pushed by `index.add()`. This formula does not apply to the `context` resolution. A resolution of 1 will disable scoring, when `context` was not enabled. A suggested minimum meaningful resolution is 3, because the first and last slot are reserved when available. ### Context Resolution When `context` was enabled the minimum valuable resolution is 1. You can adjust the resolution of the context index independently. Giving both a value of 1 will disable scoring by term position related to the document root. Instead, the scoring refers to the distance between each term within the query. Although using a resolution > 1 can further improve context scoring. The default resolution still matters when context chain breaks and falls back to default index internally. A context resolution higher than 50% of the default resolution is probably too much. ## Tokenizer (Partial Match) The tokenizer is one of the most important options and heavily influence: 1. required memory / storage 2. capabilities of partial matches > [!TIP] > If you want getting back results of an indexed term "flexsearch" when just typing "flex" or "search" then this is done by choosing a tokenizer. Try to choose the most upper of these tokenizer which covers your requirements:
Option Description Example Memory Factor (n = length of term)
"strict"
"exact"
"default"
index the full term foobar 1
"forward" index term in forward direction (supports right-to-left by Index option rtl: true) foobar
foobar
n
"reverse"
"bidirectional"
index term in both directions foobar
foobar
foobar
foobar
2n - 1
"tolerant" index the full term by also being tolerant against typos like swapped letters and missing letters foobra
foboar
foobr
fooba
2(n - 2) + 2
"full" index every consecutive partial foobar
foobar
n * (n - 1)
## Charset Collection Encoding is one of the most important task and heavily influence: 1. required memory / storage 2. capabilities of phonetic matches (Fuzzy-Search)
Option Description Charset Type Compression Ratio
Exact Bypass encoding and take exact input Universal (multi-lang) 0%
Normalize
Default
Case in-sensitive encoding
Charset normalization
Letter deduplication
Universal (multi-lang) ~ 7%
LatinBalance Case in-sensitive encoding
Charset normalization
Letter deduplication
Phonetic basic transformation
Latin ~ 30%
LatinAdvanced Case in-sensitive encoding
Charset normalization
Letter deduplication
Phonetic advanced transformation
Latin ~ 45%
LatinExtra Case in-sensitive encoding
Charset normalization
Letter deduplication
Soundex-like transformation
Latin ~ 60%
LatinSoundex Full Soundex transformation Latin ~ 70%
function(str) => [str] Pass a custom encoding function to the Encoder
## Fuzzy-Search FlexSearch provides several methods to achieve fuzziness to make queries more tolerant: 1. Use a tokenizer: `tolerant`, `forward`, `reverse` or `full` 2. Consider using any of the builtin encoder `normalize` > `balance` > `advanced` > `extra` > `soundex` (sorted by fuzziness) 3. Use one of the language-specific presets e.g. `/lang/en.js` for en-US specific content 4. Enable suggestions by passing the search option `suggest: true` Additionally, you can apply custom `Mapper`, `Replacer`, `Stemmer`, `Filter` or by assigning a custom `normalize(str)`, `prepare(str)` or `finalize(arr)` function to the Encoder. ### Compare Built-In Encoder Preset Original term which was indexed: "Struldbrugs"
Encoder: Exact Normalize (Default) LatinBalance LatinAdvanced LatinExtra LatinSoundex
Index Size 3.1 Mb 1.9 Mb 1.7 Mb 1.6 Mb 1.1 Mb 0.7 Mb
Struldbrugs
strũlldbrųĝgs
strultbrooks
shtruhldbrohkz
zdroltbrykz
struhlbrogger
The index size was measured after indexing the book "Gulliver's Travels". ## Fast-Update Mode The default mode is highly optimized for search performance and adding contents to the index. Whenever you need to `update` or `remove` existing contents of an index you can enable an additional register that boosts those tasks also to a high-performance level. This register will take an extra amount of memory (~30% increase of index size). ```js const index = new Index({ fastupdate: true }); ``` ```js const index = new Document({ fastupdate: true }); ``` > `Persistent`-Index does not support the `fastupdate` option, because of its nature. When using `fastupdate: true`, the index won't fully clear up, when removing items. A barely rest of structure will still remain. It's not a memory issue, because this rest will take less than 1% of the index size. But instead the internal performance of key lookups will lose efficiency, because of not used (empty) keys in the index. In most cases this is not an issue. But you can trigger a `index.cleanup()` task, which will find those empty index slots and remove them: ```js index.cleanup(); ``` > The `cleanup` method has no effect when not using `fastupdate: true`. ## Context Search The basic idea of this concept is to limit relevance by its context instead of calculating relevance through the whole distance of its corresponding document. The context acts like a bidirectional moving window of 2 pointers (terms) which can initially have a maximum distance of the value passed via option setting `depth` and dynamically growth on search when the query did not match any results.


FlexSearch Context-based Scoring (Full-text Search)

### Enable Context-Search Create an index and use the default context: ```js var index = new FlexSearch({ tokenize: "strict", context: true }); ``` Create an index and apply custom options for the context: ```js var index = new FlexSearch({ tokenize: "strict", context: { resolution: 5, depth: 3, bidirectional: true } }); ``` > Only the tokenizer `strict` is actually supported by the context index. > The context index requires additional amount of memory depending on passed property `depth`. ### Compare Context Search Pay attention to the numbers "1", "2" and "3": ```js const index = new Index(); index.add(1, "1 A B C D 2 E F G H I 3 J K L"); index.add(2, "A B C D E F G H I J 1 2 3 K L"); const result = index.search("1 2 3"); // --> [1, 2] ``` Same example with context enabled: ```js const index = new Index({ context: true }); index.add(1, "1 A B C D 2 E F G H I 3 J K L"); index.add(2, "A B C D E F G H I J 1 2 3 K L"); const result = index.search("1 2 3"); // --> [2, 1] ``` The first index returns ID 1 in the first slot for the best pick, because matched terms are closer to the document root. The 2nd index has context enabled and returns the ID 2 in the first slot, because of the shorter distance between terms. ### Context Options
Option Values Description Default
resolution Number Sets the scoring resolution for the context. 3
depth

false
Number
Enable/Disable context index and also sets the maximum initial distance of related terms. 1
bidirectional Boolean If enabled the context direction (aka "context chain") can move bidirectional. You should ony disable this options when you need a more exact match with fewer results. true
## Auto-Balanced Cache (By Popularity) You need to initialize the cache and its limit of available cache slots during the creation of the index: ```js const index = new Index({ cache: 100 }); ``` > The method `.searchCache(query)` is available for each type of index. ```js const results = index.searchCache(query); ``` > The cache automatically balance stored entries related to their popularity. The cache also stores latest queries. A common scenario is an autocomplete or instant search when typing. ## Index Memory Allocation The book "Gulliver's Travels" (Swift Jonathan 1726) was indexed for this test. by default a lexical index is very small:
`depth: 0, bidirectional: 0, resolution: 3, minlength: 0` => 2.1 Mb a higher resolution will increase the memory allocation:
`depth: 0, bidirectional: 0, resolution: 9, minlength: 0` => 2.9 Mb using the contextual index will increase the memory allocation:
`depth: 1, bidirectional: 0, resolution: 9, minlength: 0` => 12.5 Mb a higher contextual depth will increase the memory allocation:
`depth: 2, bidirectional: 0, resolution: 9, minlength: 0` => 21.5 Mb a higher minlength will decrease memory allocation:
`depth: 2, bidirectional: 0, resolution: 9, minlength: 3` => 19.0 Mb using bidirectional will decrease memory allocation:
`depth: 2, bidirectional: 1, resolution: 9, minlength: 3` => 17.9 Mb enable the option "fastupdate" will increase memory allocation:
`depth: 2, bidirectional: 1, resolution: 9, minlength: 3` => 6.3 Mb ## Presets 1. `memory` primarily optimized for a small memory footprint 2. `performance` primarily optimized for high performance 3. `match` primarily optimized for matching capabilities 4. `score` primarily optimized for scoring capabilities (order of results) 5. `default` the default balanced profile These profiles are covering standard use cases. It is recommended to apply custom configuration instead of using profiles to get the best out. Every profile could be optimized further to its specific task, e.g. extreme performance optimized configuration or extreme memory and so on. You can pass a preset during creation/initialization of the index. ## Best Practices ### Page-Load / Fast-Boot There are several options to optimize either the page load or when booting up or populate an index on server-side: - Using [Fast-Boot Serialization](doc/export-import.md#fast-boot-serialization-for-server-side-rendering-php-python-ruby-rust-java-go-nodejs-) for small and simple indexes - Using [Non-Blocking Runtime Balancer (Async)](doc/async.md) for populating larger amounts of contents while doing other processes in parallel - Using [Worker Indexes](doc/worker.md) will distribute the workload to dedicated balanced threads - Using [Persistent Indexes](doc/persistent.md) when targeting a zero-latency boot-up ### Use numeric IDs It is recommended to use id values from type `number` as reference when adding content to the index. The reserved byte length of passed ids influences the memory consumption significantly. When stringified numeric IDs are included in your datasets consider replacing these by `parseInt(...)` before pushing to the index. --- Copyright 2018-2025 Thomas Wilkerling, Hosted by Nextapps GmbH
Released under the Apache 2.0 License
================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions | Version | Supported | | ------- | ------------------ | | 0.8.x | :white_check_mark: | | 0.7.x | :x: | | < 0.7 | :x: | ## Reporting a Vulnerability Just create an issue or pull request on Github. > The vulnerability has to be a part of the basic installment e.g. when install by `npm install flexsearch`. > Every vulnerability which is included by any of the `devDependencies` probably won't be fixed. ================================================ FILE: demo/autocomplete.html ================================================ Demo: Auto-Complete
================================================ FILE: demo/data/gulliver.js ================================================ export const text_queries = "gulliver;great;country;time;people;little;master;took;feet;houyhnhnms".split(";"); export const text_queries_multi = "italians homunceletino;theodorus vangrult;virtuous houyhnhnms;creature discovered;lord high chancellor".split(";"); export const text_data = "LIBRARY OF THE FUTURE (R) First Edition Ver. 4.02 Gulliver's Travels Swift Jonathan 1726 GULLIVER'S TRAVELS by Jonathan Swift Electronically Enhanced Text (c) Copyright 1991, World Library, Inc. LETTER_TO_SYMPSON A LETTER FROM CAPTAIN GULLIVER TO HIS COUSIN SYMPSON - I hope you will be ready to own publicly, whenever you shall be called to it, that by your great and frequent urgency you prevailed on me to publish a very loose and uncorrect account of my travels; with direction to hire some young gentlemen of either university to put them in order, and correct the style, as my cousin Dampier did by my advice, in his book called A Voyage round the World. But I do not remember I gave you power to consent that any thing should be omitted, and much less that any thing should be inserted: therefore, as to the latter, I do here renounce every thing of that kind; particularly a paragraph about her Majesty the late Queen Anne, of most pious and glorious memory; although I did reverence and esteem her more than any of human species. But you, or your interpolator, ought to have considered, that as it was not my inclination, so was it not decent to praise any animal of our composition before my master Houyhnhnm: and besides the fact was altogether false; for to my knowledge, being in England during some part of her Majesty's reign, she did govern by a chief minister; nay, even by two successively; the first whereof was the Lord of Godolphin, and the second the Lord of Oxford; so that you have made me say the thing that was not. Likewise, in the account of the Academy of Projectors, and several passages of my discourse to my master Houyhnhnm, you have either omitted some material circumstances, or minced or changed them in such a manner, that I do hardly know my own work. When I formerly hinted to you something of this in a letter, you were pleased to answer that you were afraid of giving offense; that people in power were very watchful over the press, and apt not only to interpret, but to punish every thing which looked like an innuendo (as I think you called it). But pray, how could that which I spoke so many years ago, and at about five thousand leagues distance, in another reign, be applied to any of the Yahoos who now are said to govern the herd; especially at a time when I little thought on or feared the unhappiness of living under them? Have not I the most reason to complain, when I see these very Yahoos carried by Houyhnhnms in a vehicle, as if these were brutes, and those the rational creatures? And indeed, to avoid so monstrous and detestable a sight was one principal motive of my retirement hither. Thus much I thought proper to tell you in relation to yourself, and to the trust I reposed in you. I do in the next place complain of my own great want of judgement, in being prevailed upon by the entreaties and false reasonings of you and some others, very much against my own opinion, to suffer my travels to be published. Pray bring to your mind how often I desired you to consider, when you insisted on the motive of public good; that the Yahoos were a species of animals utterly incapable of amendment by precepts or examples: and so it hath proved; for instead of seeing a full stop put to all abuses and corruptions, at least in this little island, as I had reason to expect: behold, after above six months warning, I cannot learn that my book hath produced one single effect according to my intentions: I desired you would let me know by a letter, when party and faction were extinguished; judges learned and upright; pleaders honest and modest, with some tincture of common sense; and Smithfield blazing with pyramids of lawbooks; the young nobility's education entirely changed; the physicians banished; the female Yahoos abounding in virtue, honour, truth and good sense; courts and levees of great ministers thoroughly weeded and swept; wit, merit and learning rewarded; all disgracers of the press in prose and verse condemned to eat nothing but their own cotton, and quench their thirst with their own ink. These and a thousand other reformations, I firmly counted upon by your encouragement; as indeed they were plainly deducible from the precepts delivered in my book. And it must be owned that seven months were a sufficient time to correct every vice and folly to which Yahoos are subject, if their natures had been capable of the least disposition to virtue or wisdom: yet so far have you been from answering my expectation in any of your letters, that on the contrary you are loading our carrier every week with libels, and keys, and reflections, and memoirs, and second parts; wherein I see myself accused of reflecting upon great states-folk, of degrading human nature (for so they have still the confidence to style it), and of abusing the female sex. I find likewise that the writers of those bundles are not agreed among themselves; for some of them will not allow me to be author of my own travels; and others make me author of books to which I am wholly a stranger. I find likewise that your printer hath been so careless as to confound the times, and mistake the dates of my several voyages and returns; neither assigning the true year, or the true month, or day of the month: and I hear the original manuscript is all destroyed since the publication of my book. Neither have I any copy left: however I have sent you some corrections, which you may insert, if ever there should be a second edition: and yet I cannot stand to them, but shall leave that matter to my judicious and candid readers, to adjust it as they please. I hear some of our sea-Yahoos find fault with my sea-language, as not proper in many parts, nor now in use. I cannot help it. In my first voyages, while I was young, I was instructed by the oldest mariners, and learned to speak as they did. But I have since found that the sea-Yahoos are apt, like the land ones, to become new-fangled in their words, which the latter change every year, insomuch as I remember upon each return to my own country their old dialect was so altered that I could hardly understand the new. And I observe, when any Yahoo comes from London out of curiosity visit me at my own house, we neither of us are able to deliver our conceptions in a manner intelligible to the other. {LETTER_TO_SYMPSON ^paragraph 5} If the censure of Yahoos could any way affect me, I should have great reason to complain that some of them are so bold as to think my book of travels a mere fiction out of my own brain, and have gone so far as to drop hints that the Houyhnhnms and Yahoos have no more existence than the inhabitants of Utopia. Indeed I must confess, that as to the people of Lilliput, Brobdingrag (for so the word should have been spelt, and not erroneously Brobdingnag), and Laputa, I have never yet heard of any Yahoo so presumptuous as to dispute their being, or the facts I have related concerning them; because the truth immediately strikes every reader with conviction. And is there less probability in my account of the Houyhnhnms or Yahoos, when it is manifest as to the latter, there are so many thousands even in this city, who only differ from their brother brutes in Houyhnhnm-land, because they use a sort of a jabber, and do not go naked? I wrote for their amendment, and not their approbation. The united praise of the whole race would be of less consequence to me than the neighing of those two degenerate Houyhnhnms I keep in my stable; because from these, degenerate as they are, I still improve in some virtues, without any mixture of vice. Do these miserable animals presume to think that I am so far degenerated as to defend my veracity? Yahoo as I am, it is well known through all Houyhnhnm-land, that by the instructions and example of my illustrious master I was able in the compass of two years (although I confess with the utmost difficulty) to remove that infernal habit of lying, shuffling, deceiving, and equivocating, so deeply rooted in the very souls of all my species, especially the Europeans. I have other complaints to make upon this vexatious occasion; but I forbear troubling myself or you any further. I must freely confess, that since my last return some corruptions of my Yahoo nature have revived in me by conversing with a few of your species, and particularly those of my own family, by an unavoidable necessity; else I should never have attempted so absurd a project as that of reforming the Yahoo race in this kingdom; but I have now done with all visionary schemes for ever. - - April 2, 1727. PUBLISHERS_LETTER THE PUBLISHER TO THE READER - The author of these Travels, Mr. Lemuel Gulliver, is my ancient and intimate friend; there is likewise some relation between us by the mother's side. About three years ago Mr. Gulliver, growing weary of the concourse of curious people coming to him at his house in Redriff, made a small purchase of land, with a convenient house, near Newark in Nottinghamshire, his native country; where he now lives retired, yet in good esteem among his neighbors. Although Mr. Gulliver was born in Nottinghamshire, where his father dwelt, yet I have heard him say his family came from Oxfordshire; to confirm which, I have observed in the churchyard at Banbury, in that county, several tombs and monuments of the Gullivers. Before he quitted Redriff, he left the custody of the following papers in my hands, with the liberty to dispose of them as I should think fit. I have carefully perused them three times: the style is very plain and simple; and the only fault I find is, that the author, after the manner of travelers, is a little too circumstantial. There is an air of truth apparent through the whole; and indeed the author was so distinguished for his veracity, that it became a sort of proverb among his neighbors at Redriff, when any one affirmed a thing, to say it was as true as if Mr. Gulliver had spoke it. By the advice of several worthy persons, to whom, with the author's permission, I communicated these papers, I now venture to send them into the world, hoping they may be at least, for some time, a better entertainment to our young noblemen than the common scribbles of politics and party. This volume would have been at least twice as large, if I had not made bold to strike out innumerable passages relating to the winds and tides, as well as to the variations and bearings in the several voyages; together with the minute descriptions of the management of the ship in storms, in the style of sailors: likewise the account of the longitudes and latitudes; wherein I have reason to apprehend that Mr. Gulliver may be a little dissatisfied: but I was resolved to fit the work as much as possible to the general capacity of readers. However, if my own ignorance in sea-affairs shall have led me to commit some mistakes, I alone am answerable for them: and if any traveler hath a curiosity to see the whole work at large, as it came from the hand of the author, I shall be ready to gratify him. {PUBLISHERS_LETTER ^paragraph 5} As for any further particulars relating to the author, the reader will receive satisfaction from the first pages of the book. - Richard Sympson. PART I A VOYAGE TO LILLIPUT (SEE PLATE 1) P_1|CH_1 CHAPTER I - My father had a small estate in Nottinghamshire; I was the third of five sons. He sent me to Emanuel College in Cambridge at fourteen years old, where I resided three years, and applied myself close to my studies: but the charge of maintaining me (although I had a very scanty allowance) being too great for a narrow fortune, I was bound apprentice to Mr. James Bates, an eminent surgeon in London, with whom I continued four years; and my father now and then sending me small sums of money, I laid them out in learning navigation, and other parts of the mathematics, useful to those who intend to travel, as I always believed it would be some time or other my fortune to do. When I left Mr. Bates, I went down to my father; where, by the assistance of him and my uncle John, and some other relations, I got forty pounds, and a promise of thirty pounds a year to maintain me at Leyden: there I studied physic two years and seven months, knowing it would be useful in long voyages. Soon after my return from Leyden, I was recommended, by my good master Mr. Bates, to be surgeon to the Swallow, Captain Abraham Pannell commander; with whom I continued three years and a half, making a voyage or two into the Levant, and some other parts. When I came back, I resolved to settle in London, to which Mr. Bates, my master, encouraged me, and by him I was recommended to several patients. I took part of a small house in the Old Jury; and being advised to alter my condition, I married Mrs. Mary Burton, second daughter to Mr. Edmund Burton, hosier, in Newgate-street, with whom I received four hundred pounds for a portion. But, my good master Bates dying in two years after, and I having few friends, my business began to fail; for my conscience would not suffer me to imitate the bad practice of too many among my brethren. Having therefore consulted with my wife, and some of my acquaintance, I determined to go again to sea. I was surgeon successively in two ships, and made several voyages, for six years, to the East and West Indies, by which I got some addition to my fortune. My hours of leisure I spent in reading the best authors, ancient and modern, being always provided with a good number of books; and when I was ashore, in observing the manners and dispositions of the people, well as learning their language, wherein I had a great facility by the strength of my memory. The last of these voyages not proving very fortunate, I grew weary of the sea, and intended to stay at home with my wife and family. I removed from the Old jury to Fetter-Lane, and from thence to Wapping hoping to get business among the sailors; but it would not turn to account. After three years expectation that things would mend, I accepted an advantageous offer from Captain William Prichard, master of the Antelope, who was making a voyage to the South-Sea. We set sail from Bristol May 4, 1699, and our voyage at first was very prosperous. It would not be proper, for some reasons, to trouble the reader with the particulars of our adventures in those seas: let it suffice to inform him, that in our passage from thence to the East Indies, we were driven by a violent storm to the northwest of Van Diemen's Land. By an observation, we found ourselves in the latitude of 30 degrees 2 minutes south. Twelve of our crew were dead by immoderate labor and ill food, the rest were in a very weak condition. On the fifth of November, which was the beginning of summer in those parts, the weather being very hazy, the seamen spied a rock, within half a cable's length of the ship; but the wind was so strong, that we were driven directly upon it, and immediately split. Six of the crew, of whom I was one, having let down the boat into the sea, made a shift to get clear of the ship, and the rock. We rowed by my computation about three leagues, till we were able to work no longer, being already spent with labor while we were in the ship. We therefore trusted ourselves to the mercy of the waves, and in about half an hour the boat was overset by a sudden flurry from the north. What became of my companions in the boat, as well as of those who escaped on the rock, or were left in the vessel, I cannot tell; but conclude they were all lost. For my own part, I swam as fortune directed me, and was pushed forward by wind and tide. I often let my legs drop, and could feel no bottom: but when I was almost gone, and able to struggle no longer, I found myself within my depth; and by this time the storm was much abated. The declivity was so small, that I walked near a mile before I got to the shore, which I conjectured was about eight o'clock in the evening. I then advanced forward near half a mile, but could not discover any sign of houses or inhabitants; at least I was in so weak a condition, that I did not observe them. I was extremely tired, and with that, and the heat of the weather, and about half a pint of brandy that I drank as I left the ship, I found myself much inclined to sleep. I lay down on the grass, which was very short and soft, where I slept sounder than ever I remember to have done in my life, and, as I reckoned, above nine hours; for when I awakened, it was just daylight. I attempted to rise, but was not able to stir for, as I happened to he on my back, I found my arms and legs were strongly fastened on each side to the ground; and my hair, which was long and thick, tied down in the same manner. I likewise felt several slender ligatures across my body, from my armpits to my thighs. I could only look upwards; the sun began to grow hot, and the light offended my eyes. I heard a confused noise about me, but in the posture I lay, could see nothing except the sky. In a little time I felt something alive moving on my left leg, which advancing gently forward over my breast, came almost up to my chin; when bending my eyes downwards as much as I could, I perceived it to be a human creature not six inches high, with a bow and arrow in his hands, and a quiver at his back. In the meantime, I felt at least forty more of the same kind (as I conjectured) following the first. I was in the utmost astonishment, and roared so loud, that they all ran back in a fright; and some of them, as I was afterwards told, were hurt with the falls they got by leaping from my sides upon the ground. However, they soon returned, and one of them, who ventured so far as to get a full sight of my face, lifting up his hands and eyes by way of admiration, cried out in a shrill but distinct voice, Hekinah degul: the others repeated the same words several times, but I then knew not what they meant. I lay all this while, as the reader may believe, in great uneasiness: at length, struggling to get loose, I had the fortune to break the strings, and wrench out the pegs that fastened my left arm to the ground; for, by lifting it up to my face, I discovered the methods they had taken to bind me, and at the same time, with a violent pull, which gave me excessive pain, I a little loosened the strings that tied down my hair on the left side, so that I was just able to turn my head about two inches. But the creatures ran off a second time, before I could seize them; whereupon there was a great shout in a very shrill accent, and after it ceased, I heard one of them cry aloud, Tolgo phonac; when in an instant I felt above a hundred arrows discharged on my left hand, which pricked me like so many needles; and besides they shot another flight into the air, as we do bombs in Europe, whereof many, I suppose, fell on my body (though I felt them not) and some on my face, which I immediately covered with my left hand. When this shower of arrows was over, I fell a groaning with grief and pain, and then striving again to get loose, they discharged another volley larger than the first, and some of them attempted with spears to stick me in the sides; but, by good luck, I had on me a buff jerkin, which they could not pierce. I thought it the most prudent method to lie still, and my design was to continue so till night, when, my left hand being already loose, I could easily free myself: and as for the inhabitants, I had reason to believe I might be a match for the greatest armies they could bring against me, if they were all of the same size with him that I saw. But fortune disposed otherwise of me. When the people observed I was quiet, they discharged no more arrows; but, by the noise I heard, I knew their numbers increased; and about four yards from me, over against my right ear, I heard a knocking for above an hour, like that of people at work; when turning my head that way, as well as the pegs and strings would permit me, I saw a stage erected, about a foot and a half from the ground, capable of holding four of the inhabitants, with two or three ladders to mount it: from whence one of them, who seemed to be a person of quality, made me a long speech, whereof I understood not one syllable. But I should have mentioned, that before the principal person began his oration, he cried out three times, Langro dehul san (these words and the former were afterwards repeated and explained to me). Whereupon immediately about fifty of the inhabitants came, and cut the strings that fastened the left side of my head, which gave me the liberty of turning it to the right, and of observing the person and gesture of him that was to speak. He appeared to be of a middle age, and taller than any of the other three who attended him, whereof one was a page that held up his train, and seemed to be somewhat longer than my middle finger; the other two stood one on each side to support him. He acted every part of an orator, and I could observe many periods of threatenings, and others of promises, pity, and kindness. I answered in a few words, but in the most submissive manner, lifting up my left hand and both my eyes to the sun, as calling him for a witness; and being almost famished with hunger, having not eaten a morsel for some hours before I left the ship, I found the demands of nature so strong upon me, that I could not forbear showing my impatience (perhaps against the strict rules of decency) by putting my finger frequently on my mouth, to signify that I wanted food. The Hurgo (for so they call a great lord, as I afterwards learned) understood me very well. He descended from the stage, and commanded that several ladders should be applied to my sides, on which above a hundred of the inhabitants mounted, and walked towards my mouth, laden with baskets full of meat, which had been provided, and sent thither by the King's orders, upon the first intelligence he received of me. I observed there was the flesh of several animals, but could not distinguish them by the taste. There were shoulders, legs, and loins, shaped like those of mutton, and very well dressed, but smaller than the wings of a lark. I ate them by two or three at a mouthful, and took three loaves at a time, about the bigness of musket bullets. They supplied me as they could, showing a thousand marks of wonder and astonishment at my bulk and appetite. I then made another sign that I wanted drink. They found by my eating that a small quantity would not suffice me, and being a most ingenious people, they slung up with great dexterity one of their largest hogsheads, then rolled it toward my hand, and beat out the top; I drank it off at a draught, which I might well do, for it did not hold half a pint, and tasted like a small wine of Burgundy, but much more delicious. They brought me a second hogshead, which I drank in the same manner, and made signs for more, but they had none to give me. When I had performed these wonders, they shouted for joy, and danced upon my breast, repeating several times as they did at first, Hekinah degul. They made me a sign that I should throw down the two hogsheads, but first warning the people below to stand out of the way, crying aloud, Borach mivola, and when they saw the vessels in the air, there was a universal shout of Hekinah degul. I confess I was often tempted, while they were passing backwards and forwards on my body, to seize forty or fifty of the first that came in my reach, and dash them against the ground. But the remembrance of what I had felt, which probably might not be the worst they could do, and the promise of honor I made them, for so I interpreted my submissive behavior, soon drove out these imaginations. Besides, I now considered myself as bound by the laws of hospitality to a people who had treated me with so much expense and magnificence. However, in my thoughts I could not sufficiently wonder at the intrepidity of these diminutive mortals, who dare venture to mount and walk upon my body, while one of my hands was at liberty, without trembling at the very sight of so prodigious a creature as I must appear to them. After some time, when they observed that I made no more demands for meat, there appeared before me a person of high rank from his Imperial Majesty. His Excellency, having mounted on the small of my right leg, advanced forwards up to my face, with about a dozen of his retinue. And producing his credentials under the Signet Royal, which he applied close to my eyes, spoke about ten minutes, without any signs of anger, but with a kind of determinate resolution; often pointing forwards, which, as I afterwards found, was towards the capital city, about half a mile distant, whither it was agreed by his Majesty in council that I must be conveyed. I answered in few words, but to no purpose, and made a sign with my hand that was loose, putting it to the other (but over his Excellency's head, for fear of hurting him or his train) and then to my own head and body, to signify that I desired my liberty. It appeared that he understood me well enough, for he shook his head by way of disapprobation, and held his hand in a posture to show that I must be carried as a prisoner. However, he made other signs to let me understand that I should have meat and drink enough, and very good treatment. Whereupon I once more thought of attempting to break my bonds, but again, when I felt the smart of their arrows upon my face and hands, which were all in blisters, and many of the darts still sticking in them, and observing likewise that the number of my enemies increased, I gave tokens to let them know that they might do with me what they pleased. Upon this the Hurgo and his train withdrew with much civility and cheerful countenances. Soon after I heard a general shout, with frequent repetitions of the words, Peplom selan, and I felt great numbers of the people on my left side relaxing the cords to such a degree, that I was able to turn upon my right, and to ease myself with making water; which I very plentifully did, to the great astonishment of the who conjecturing by my motions what I was going to do, immediately opened to the right and left on that side, to avoid the torrent which fell with such noise and violence from me. But before this, they had daubed my face and both my hands with a sort of ointment very pleasant to the smell, which in a few minutes removed all the smart of their arrows. These circumstances, added to the refreshment I had received by their victuals and drink, which were very nourishing, disposed me to sleep. I slept about eight hours, as I was afterwards assured; and it was no wonder, for the physicians, by the Emperor's order, had mingled a sleepy potion in the hogsheads of wine. {P_1|CH_1 ^paragraph 5} It seems that upon the first moment I was discovered sleeping on the ground after my landing, the Emperor had early notice of it by an express; and determined in council that I should be tied in the manner I have related (which was done in the night while I slept), that plenty of meat and drink should be sent me, and a machine prepared to carry me to the capital city. This resolution perhaps may appear very bold and dangerous, and I am confident would not be imitated by any prince in Europe on the like occasion; however, in my opinion, it was extremely prudent, as well as generous. For supposing these people had endeavored to kill me with their spears and arrows while I was asleep, I should certainly have awakened with the first sense of smart, which might so far have roused my rage and strength, as to have enabled me to break the strings wherewith I was tied; after which, as they were not able to make resistance, so they could expect no mercy. These people are most excellent mathematicians, and arrived to great perfection in mechanics by the countenance and encouragement of the Emperor, who is a renowned patron of learning. This prince has several machines fixed on wheels for the carriage of trees and other great weights. He often builds his largest men of war, whereof some are nine feet long, in the woods where the timber grows, and has them carried on these engines three or four hundred yards to the sea. Five hundred carpenters and engineers were immediately set at work to prepare the greatest engine they had. It was a frame of wood raised three inches from the ground, about seven feet long and four wide, moving upon twenty-two wheels. The shout I heard was upon the arrival of this engine, which it seems set out in four hours after my landing. It was brought parallel to me as I lay. But the principal difficulty was to raise and place me in this vehicle. Eighty poles, each of one foot high, were erected for this purpose, and very strong cords of the bigness of packthread were fastened by hooks to many bandages, which the workmen had girt round my neck, my hands, my body, and my legs. Nine hundred of the strongest men were employed to draw up these cords by many pulleys fastened on the poles, and thus, in less than three hours, I was raised and slung into the engine, and there tied fast. All this I was told, for while the whole operation was performing, I lay in a profound sleep, by the force of that soporiferous medicine infused into my liquor. Fifteen hundred of the Emperor's largest horses, each about four inches and a half high, were employed to draw me towards the metropolis, which, as I said, was half a mile distant. About four hours after we began our journey, I awaked by a very ridiculous accident; for the carriage being stopped a while to adjust something that was out of order, two or three of the young natives had the curiosity to see how I looked when I was asleep; they climbed up into the engine, and advancing very softly to my face, one of them, an officer in the Guards, put the sharp end of his half-pike a good way up into my left nostril, which tickled my nose like a straw, and made me sneeze violently: whereupon they stole off unperceived, and it was three weeks before I knew the cause of my awaking so suddenly. We made a long march the remaining part of that day, and rested at night with five hundred guards on each side of me, half with torches, and half with bows and arrows, ready to shoot me if I should offer to stir. The next morning at sunrise we continued our march, and arrived within two hundred yards of the city gates about noon. The Emperor, and all his court, came out to meet us; but his great officers would by no means suffer his Majesty to endanger his person by mounting on my body. At the place where the carriage stopped, there stood an ancient temple, esteemed to be the largest in the whole kingdom, which having been polluted some years before by an unnatural murder, was, according to the zeal of those people, looked on as profane, and therefore had been applied to common uses, and all the ornaments and furniture carried away. In this edifice it was determined I should lodge. The great gate fronting to the north was about four feet high, and almost two feet wide, through which I could easily creep. On each side of the gate was a small window not above six inches from the ground: into that on the left side, the King's smiths conveyed fourscore and eleven chains, like those that hang to a lady's watch in Europe, and almost as large, which were locked to my left leg with six and thirty padlocks. Over against this temple, on the other side of the great highway, at twenty feet distance, there was a turret at least five feet high. Here the Emperor ascended with many principal lords of his court, to have an opportunity of viewing me, as I was told, for I could not see them. It was reckoned that above a hundred thousand inhabitants came out of the town upon the same errand; and in spite of my guards, I believe there could not be fewer than ten thousand, at several times, who mounted upon my body by the help of ladders. But a proclamation was soon issued to forbid it upon pain of death. When the workmen found it was impossible for me to break loose, they cut all the strings that bound me; whereupon I rose up with as melancholy a disposition as ever I had in my life. But the noise and astonishment of the people at seeing me rise and walk, are not to be expressed. The chains that held my left leg were about two yards long, and gave me not only the liberty of walking backwards and forwards in a semi-circle; but, being fixed within four inches of the gate, allowed me to creep in, and lie at my full length in the temple. P_1|CH_2 CHAPTER II - When I found myself on my feet, I looked about me, and must confess I never beheld a more entertaining prospect. The country round appeared like a continued garden, and the inclosed fields, which were generally forty feet square, resembled so many beds of flowers. These fields were intermingled with woods of half a sting, and the tallest trees, as I could judge, appeared to be seven feet high. I viewed the town on my left hand, which looked like the painted scene of a city in a theatre. I had been for some hours extremely pressed by the necessities of nature; which was no wonder, it being almost two days since I had last unburdened myself. I was under great difficulties between urgency and shame. The best expedient I could think of, was to creep into my house, which I accordingly did; and shutting the gate after me, I went as far as the length of my chain would suffer, and discharged my body of that uneasy load. But this was the only time I was ever guilty of so uncleanly an action; for which I cannot but hope the candid reader will give some allowance, after he has maturely and impartially considered my case, and the distress I was in. From this time my constant practice was, as soon as I rose, to perform that business in open air, at the full extent of my chain, and due care was taken every morning before company came, that the offensive matter should be carried off in wheelbarrows, by two servants appointed for that purpose. I would not have dwelt so long upon a circumstance, that perhaps at first sight may appear not very momentous, if I had not thought it necessary to justify my character in point of cleanliness to the world; which I am told some of my maligners have been pleased, upon this and other occasions, to call in question. When this adventure was at an end, I came back out of my house, having occasion for fresh air. The Emperor was already descended from the tower, and advancing on horseback towards me, which had like to have cost him dear; for the beast, though very well trained, yet wholly unused to such a sight, which appeared as if a mountain moved before him, he reared up on his hinder feet: but that prince, who is an excellent horseman, kept his seat, till his attendants ran in, and held the bridle, while his Majesty had time to dismount. When he alighted, he surveyed me round with great admiration, but kept without the length of my chain. He ordered his cooks and butlers, who were already prepared, to give me victuals and drink, which they pushed forward in a sort of vehicle upon wheels till I could reach them. I took these vehicles, and soon emptied them all; twenty of them were filled with meat, and ten with liquor; each of the former afforded me two or three good mouthfuls, and I emptied the liquor of ten vessels, which was contained in earthen vials, into one vehicle, drinking it off at a draught; and so I did with the rest. The Empress, and young Princes of the blood, of both sexes, attended by many ladies, sat at some distance in their chairs; but upon the accident that happened to the Emperor's horse, they alighted, and came near his person, which I am now going to describe. He is taller by almost the breadth of my nail than any of his court, which alone is enough to strike an awe into the beholders. His features are strong and masculine, with an Austrian lip and arched nose, his complexion olive, his countenance erect, his body and limbs well proportioned, all his motions graceful, and his deportment majestic. He was then past his prime, being twenty-eight years and three quarters old, of which he had reigned about seven, in great felicity, and generally victorious. For the better convenience of beholding him, I lay on my side, so that my face was parallel to his, and he stood but three yards off: however, I have had him since many times in my hand, and therefore cannot be deceived in the description. His dress was very plain and simple, and the fashion of it between the Asiatic and the European; but he had on his head a light helmet of gold, adorned with jewels, and a plume on the crest. He held his sword drawn in his hand, to defend himself, if I should happen to break loose; it was almost three inches long, the hilt and scabbard were gold enriched with diamonds. His voice was shrill, but very clear and articulate, and I could distinctly hear it when I stood up. The ladies and courtiers were all most magnificently clad, so that the spot they stood upon seemed to resemble a petticoat spread on the ground, embroidered with figures of gold and silver. His Imperial Majesty spoke often to me, and I returned answers, but neither of us could understand a syllable. There were several of his priests and lawyers present (as I conjectured by their habits) who were commanded to address themselves to me, and I spoke to them in as many languages as I had the least smattering of, which were High and Low Dutch, Latin, French, Spanish, Italian, and Lingua Franca; but all to no purpose. After about two hours the court retired, and I was left with a strong guard, to prevent the impertinence, and probably the malice of the rabble, who were very impatient to crowd about me as near as they dare, and some of them had the impudence to shoot their arrows at me as I sat on the ground by the door of my house, whereof one very narrowly missed my left eye. But the colonel ordered six of the ringleaders to be seized, and thought no punishment so proper as to deliver them bound into my hands, which some of his soldiers accordingly did, pushing them forwards with the butt-ends of their pikes into my reach; I took them all in my right hand, put five of them into my coat pocket, and as to the sixth, I made a countenance as if I would eat him alive. The poor man squalled terribly, and the colonel and his officers were in much pain, especially when they saw me take out my penknife: but I soon put them out of fear; for, looking mildly, and immediately cutting the strings he was bound with, I set him gently on the ground, and away he ran. I treated the rest in the same manner, taking them one by one out of my pocket, and I observed both the soldiers and people were highly obliged at this mark of my clemency, which was represented very much to my advantage at court. Towards night I with some difficulty got into my house, where I lay on the ground, and continued to do so about a fortnight; during which time the Emperor gave orders to have a bed prepared for me. Six hundred beds of the common measure were brought in carriages, and worked up in my house; a hundred and fifty of their beds sewn together made up the breadth and length, and these were four double, which however kept me but very indifferently from the hardness of the floor, that was of smooth stone. By the same computation they provided me with sheets, blankets, and coverlets, tolerable enough for one who had been so long inured to hardships as I. As the news of my arrival spread through the kingdom, it brought prodigious numbers of rich, idle, and curious people to see me; so that the villages were almost emptied, and great neglect of tillage and household affairs must have ensued, if his Imperial Majesty had not provided, by several proclamations and orders of state, against this inconveniency. He directed that those who had already beheld me should return home, and not presume to come within fifty yards of my house without license from court; whereby the secretaries of state got considerable fees. {P_1|CH_2 ^paragraph 5} In the meantime, the Emperor held frequent councils to debate what course should be taken with me; and I was afterwards assured by a particular friend, a person of great quality, who was looked upon to be as much in the secret as any, that the court was under many difficulties concerning me. They apprehended my breaking loose, that my diet would be very expensive, and might cause a famine. Sometimes they determined to starve me, or at least to shoot me in the face and hands with poisoned arrows, which would soon dispatch me: but again they considered, that the stench of so large a carcass might produce a plague in the metropolis, and probably spread through the whole kingdom. In the midst of these consultations, several officers of the army went to the door of the great council chamber; and two of them being admitted, gave an account of my behavior to the six criminals above mentioned, which made so favorable an impression in the breast of his Majesty and the whole board in my behalf, that an Imperial Commission was issued out, obliging all the villages nine hundred yards round the city, to deliver in every morning six beeves, forty sheep, and other victuals for my sustenance; together with a proportionable quantity of bread, and wine, and other liquors for the due payment of which his Majesty gave assignments upon his treasury. For this prince lives chiefly upon his own demesnes, seldom, except upon great occasions, raising any subsidies upon his subjects, who are bound to attend him in his wars at their own expense. An establishment was-also made of six hundred persons to be my domestics, who had board-wages allowed for their maintenance, and tents built for them very conveniently on each side of my door. It was likewise ordered, that three hundred tailors should make me a suit of clothes after the fashion of the country: that six of his Majesty's greatest scholars should be employed to instruct me in their language: and, lastly, that the Emperor's horses, and those of the nobility, and troops of guards, should be frequently exercised in my sight, to accustom themselves to me. All these orders were duly put in execution, and in about three weeks I made a great progress in learning their language; during which time the Emperor frequently honored me with his visits, and was pleased to assist my masters in teaching me. We began already to converse together in some sort; and the first words I learned were to express my desire that he would please give me my liberty, which I every day repeated on my knees. His answer, as I could apprehend it, was, that this must be a work of time, not to be thought on without the advice of his council, and that first I must Lumos kelmin pesso desmar lon Emposo; that is, swear a peace with him and his kingdom. However, that I should be used with all kindness; and he advised me to acquire, by my patience and discreet behavior, the good opinion of himself and his subjects. He desired I would not take it ill, if he gave orders to certain proper officers to search me; for probably I might carry about me several weapons, which must needs be dangerous things, if they answered the bulk of so prodigious a person. I said, his Majesty should be satisfied, for I was ready to strip myself, and turn out my pockets before him. This I delivered part in words, and part in signs. He replied, that by the laws of the kingdom I must be searched by two of his officers; that he knew this could not be done without my consent and assistance; that he had so good an opinion of my generosity and justice, as to trust their persons in my hands: that whatever they took from me should be returned when I left the country, or paid for at the rate which I would set upon them. I took up the two officers in my hands, put them first into my coat-pockets, and then into every other pocket about me, except my two fobs, and another secret pocket I had no mind should be searched, wherein I had some little necessaries that were of no consequence to any but myself. In one of my fobs there was a silver watch, and in the other a small quantity of gold in a purse. These gentlemen, having pen, ink, and paper about them, made an exact inventory of everything they saw; and when they were through, desired I would set them down, that they might deliver it to the Emperor. This inventory I afterwards translated that into English, and is word for word as follows. - Imprimis, In the right coat pocket of the Great Man Mountain (for so I interpret the words Quinbus Flestrin) after the strictest search, we found only one great piece of coarse cloth, large enough to be a foot cloth for your Majesty's chief room of state. In the left pocket we saw a huge silver chest, with a cover of the same metal, which we the searchers were not able to lift. We desired it should be opened, and one of us stepping into it, found himself up to the mid leg in a sort of dust, some part whereof flying up to our faces, set us both sneezing for several times together. In his right waistcoat pocket we found a prodigious bundle of white thin substances, folded one over another, about the bigness of three men, tied with a strong cable, and marked with black figures; which we humbly conceive to be writings, every letter almost half as large as the palm of our hands. In the left there was a sort of engine, from the back of which were extended twenty long poles, resembling the palisades before your Majesty's court; wherewith we conjecture the Man-Mountain combs his head, for we did not always trouble him with questions, because we found it a great difficulty to make him understand us. In the large pocket on the right side of his middle cover (so I translate the word ranfu-lo, by which they meant my breeches) we saw a hollow pillar of iron, about the length of a man, fastened to a strong piece of timber, larger than the pillar; and upon one side of the pillar were huge pieces of iron sticking out, cut into strange figures, which we know not what to make of. In the left pocket, another engine of the same kind. In the smaller pocket on the right side, were several round flat pieces of white and red metal, of different bulk; some of the white, which seemed to be silver, were so large and heavy, that my comrade and I could hardly lift them. In the left pocket were two black pillars irregularly shaped: we could not, without difficulty, reach the top of them as we stood at the bottom of his pocket. One of them was covered, and seemed all of a piece: but at the upper end of the other, there appeared a white round substance, about twice the bigness of our heads. Within each of these was enclosed a prodigious plate of steel; which, by our orders, we obliged him to show us, because we apprehended they might be dangerous engines. He took them out of their cases, and told us, that in his own country his practice was to shave his beard with one of these, and to cut his meat with the other. There were two pockets which we could not enter: these he called his fobs; they were two large slits cut into the top of his middle cover, but squeezed close by the pressure of his belly. Out of the right fob hung a great silver chain, with a wonderful kind of engine at the bottom. We directed him to draw out whatever was fastened to that chain; which appeared to be a globe, half silver, and half of some transparent metal: for on the transparent side we saw certain strange figures circularly drawn, and thought we could touch them, till we found our fingers stopped by that lucid substance. He put this engine to our ears, which made an incessant noise like that of a watermill: and we conjecture it is either some unknown animal, or the god that he worships; but we are more inclined to the latter opinion, because he assured us (if we understood him right, for he expressed himself very imperfectly) that he seldom did anything without consulting it: he called it his oracle, and said it pointed out the time for every action of his life. From the left fob he took out a net almost large enough for a fisherman, but contrived to open and shut like a purse, and serve him for the same use: we found therein several massy pieces of yellow metal, which, if they be real gold, must be of immense value. Having thus, in obedience to your Majesty's commands, diligently searched all his pockets, we observed a girdle about his waist made of the hide of some prodigious animal; from which, on the left side, hung a sword of the length of five men; and on the right, a bag or pouch divided into two cells, each cell capable of holding three of your Majesty's subjects. In one of these cells were several globes or balls of a most ponderous metal, about the bigness of our heads, and requiring a strong hand to lift them: the other cell contained a heap of certain black grains, but of no great bulk or weight, for we could hold above fifty of them in the palms of our hands. This is an exact inventory of what we found about the body of the Man-Mountain, who used us with great civility, and due respect to your Majesty's commission. Signed and sealed on the fourth day of the eighty-ninth moon of your Majesty's auspicious reign. {P_1|CH_2 ^paragraph 10} Clefren Frelock, Marsi Frelock. - - When this inventory was read over to the Emperor, he directed me, although in very gentle terms, to deliver up the several particulars. He first called for my scimitar, which I took out, scabbard and all. In the meantime he ordered three thousand of his choicest troops (who then attended him) to surround me at a distance, with their bows and arrows just ready to discharge: but I did not observe it, for my eyes were wholly fixed upon his Majesty. He then desired me to draw my scimitar, which, although it had got some rust by the sea water, was in most parts exceeding bright. I did so, and immediately all the troops gave a shout between terror and surprise; for the sun shone clear, and the reflection dazzled their eyes as I waved the scimitar to and fro in my hand. His Majesty, who is a most magnanimous prince, was less daunted than I could expect; he ordered me to return it into the scabbard, and cast it on the ground as gently as I could, about six foot from the end of my chain. The next thing he demanded was one of the hollow iron pillars, by which he meant my pocket-pistols. I drew it out, and at his desire, as well as I could, expressed to him the use of it; and charging it only with powder, which by the closeness of my pouch happened to escape wetting in the sea (an inconvenience against which all prudent mariners take special care to provide) I first cautioned the Emperor not to be afraid, and then I let it off in the air. The astonishment here was much greater than at the sight of my scimitar. Hundreds fell down as if they had been struck dead; and even the Emperor, although he stood his ground, could not recover himself in some time. I delivered up both my pistols in the same manner as I had done my scimitar, and then my pouch of powder and bullets; begging him that the former might be kept from the fire, for it would kindle with the smallest spark, and blow up his imperial palace into the air. I likewise delivered up my watch, which the Emperor was very curious to see, and commanded two of his tallest yeomen of the guards to bear it on a pole upon their shoulders, as draymen in England do a barrel of ale. He was amazed at the continual noise it made, and the motion of the minute-hand, which he could easily discern; for their sight is much more acute than ours; and asked the opinions of his learned men about him, which were various and remote, as the reader may well imagine without my repeating; although indeed I could not very perfectly understand them. I then gave up my silver and copper money, my purse with nine large pieces of gold, and some smaller ones; my knife and razor, my comb and silver snuff-box, my handkerchief and journal-book. My scimitar, pistols, and pouch, were conveyed in carriages to his Majesty's stores; but the rest of my goods were returned me. I had, as I before observed, one private pocket which escaped their search, wherein there was a pair of spectacles (which I sometimes use for the weakness of my eyes), a pocket perspective, and several other little conveniences; which, being of no consequence to the Emperor, I did not think myself bound in honor to discover, and I apprehended they might be lost or spoiled if I ventured them out of my possession. P_1|CH_3 CHAPTER III - My gentleness and good behavior had gained so far on the Emperor and his court, and indeed upon the army and people in general, that I began to conceive hopes of getting my liberty in a short time. I took all possible methods to cultivate this favorable disposition. The natives came by degrees to be less apprehensive of any danger from me. I would sometimes lie down, and let five or six of them dance on my hand. And last the boys and girls would venture to come and play at hide and seek in my hair. I had now made good progress in understanding and speaking their language. The Emperor had a mind one day to entertain me with several of the country shows, wherein they exceeded all nations I have known, both for dexterity and magnificence. I was diverted with none so much as that of the rope-dancers, performed upon a slender white thread, extended about two feet, and twelve inches from the ground. Upon which I shall desire liberty, with the reader's patience, to enlarge a little. This diversion is only practiced by those persons who are candidates for great employments and high favors at court. They are trained in this art from their youth, and are not always of noble birth, or liberal education. When a great office is vacant either by death or disgrace (which often happens) five or six of those candidates petition the Emperor to entertain his Majesty and the court with a dance on the rope, and whoever jumps the highest without falling, succeeds in the office. Very often the chief ministers themselves are commanded to show their skill, and to convince the Emperor that they have not lost their faculty. Flimnap, the Treasurer, is allowed to cut a caper on the straight rope, at least an inch higher than any other lord in the whole empire. I have seen him do the summerset several times together upon a trencher fixed on the rope, which is no thicker than a common packthread in England. My friend Reldresal, principal Secretary for Private Affairs, is, in my opinion, if I am not partial, the second after the Treasurer; the rest of the great officers are much upon a par. These diversions are often attended with fatal accidents, whereof great numbers are on record. I myself have seen two or three candidates break a limb. But the danger is much greater when the ministers themselves are commanded to show their dexterity; for by contending to excell themselves and their fellows, they strain so far, that there is hardly one of them who has not received a fall, and some of them two or three. I was assured that a year or two before my arrival, Flimnap would have infallibly broken his neck, if one of the King's cushions, that accidentally lay on the ground, had not weakened the force of his fall. There is likewise another diversion, which is only shown before the Emperor and Empress, and first minister, upon particular occasions. The Emperor lays on the table three fine silken threads of six inches long. One is blue, the other red, and the third green. These threads are proposed as prizes for those persons whom the Emperor has a mind to distinguish by a peculiar mark of his favor. The ceremony is performed in his Majesty's great chamber of state, where the candidates are to undergo a trial of dexterity very different from the former, and such as I have not observed the least resemblance of in any other country of the old or the new world. The Emperor holds a stick in his hands, both ends parallel to the horizon, while the candidates, advancing one by one, sometimes leap over the stick, sometimes creep under it backwards and forwards several times, according as the stick is advanced or depressed. Sometimes the Emperor holds one end of the stick, and his first minister the other; sometimes the minister has it entirely to himself. Whoever performs his part with most agility, and holds out the longest in leaping and creeping, is rewarded with the blue-colored silk; the red is given to the next, and the green to the third, which they all wear girt twice round about the middle; and you see few great persons about this court who are not adorned with one of these girdles. The horses of the army, and those of the royal stables, having been daily led before me, were no longer shy, but would come up to my very feet without starting. The riders would leap them over my hand as I held it on the ground, and one of the Emperor's huntsmen, upon a large courser, took my foot, shoe and all; which was indeed a prodigious leap. I had the good fortune to divert the Emperor one day after a very extraordinary manner. I desired he would order several sticks two feet high, and the thickness of an ordinary cane, to be brought me; whereupon his Majesty commanded the master of his woods to give directions accordingly; and the next morning six woodmen arrived with as many carriages, drawn by eight horses to each. I took nine of these sticks, and fixing them firmly in the ground in a quadrangular figure, two feet and a half square, I took four other sticks, and tied them parallel at each corner, about two feet from the ground; then I fastened my handkerchief to the nine sticks that stood erect, and extended it on all sides till it was as tight as the top of a drum; and the four parallel sticks rising about five inches higher than the handkerchief served as ledges on each side. When I had finished my work, I desired the Emperor to let a troop of his best horse, twentyfour in number, come and exercise upon this plain. His Majesty approved of the proposal, and I took them up one by one in my hands, ready mounted and armed, with the proper officers to exercise them. As soon as they got into order, they divided into two parties, performed mock skirmishes, discharged blunt arrows, drew their swords, fled and pursued, attacked and retired, and in short discovered the best military discipline I ever beheld. The parallel sticks secured them and their horses from falling over the stage; and the Emperor was so much delighted, that he ordered this entertainment to be repeated several days, and once was pleased to be lifted up and give the word of command; and, with great difficulty, persuaded even the Empress herself to let me hold her in her close chair within two yards of the stage, from whence she was able to take a full view of the whole performance. It was my good fortune that no ill accident happened in these entertainments, only once a fiery horse that belonged to one of the captains pawing with his hoof struck a hole in my handkerchief, and his foot slipping, he overthrew his rider and himself; but I immediately relieved them both, and covering the hole with one hand, I set down the troop with the other, in the same manner as I took them up. The horse that fell was strained in the left shoulder, but the rider got no hurt, and I repaired my handkerchief as well as I could: however I would not trust to the strength of it any more in such dangerous enterprises. {P_1|CH_3 ^paragraph 5} About two or three days before I was set at liberty, as I was entertaining the court with these kind of feats, there arrived an express to inform his Majesty that some of his subjects riding near the place where I was first taken up, had seen a great black substance lying on the ground, very oddly shaped, extending its edges round as wide as his Majesty's bedchamber, and rising up in the middle as high as a man; that it was no living creature, as they at first apprehended, for it lay on the grass without motion, and some of them had walked round it several tunes: that by mounting upon each other's shoulders, they had got to the top, which was flat and even, and stamping upon it they found it was hollow within; that they humbly conceived it might be something belonging to the Man-Mountain, and if his Majesty pleased, they would undertake to bring it with only five horses. I presently knew what they meant, and was glad at heart to receive this intelligence. It seems upon my first reaching the shore after our shipwreck, I was in such confusion, that before I came to the place where I went to sleep, my hat, which I had fastened with a string to my head while I was rowing, and had stuck on all the time I was swimming, fell off after I came to land; the string, as I conjecture, breaking by some accident which I never observed, but thought my hat had been lost at sea. I entreated his Imperial Majesty to give orders it might be brought to me as soon as possible, describing to him the use and the nature of it: and the next day the wagoners arrived with it, but not in a very good condition; they had bored two holes in the brim, within an inch and a half of the edge, and fastened two hooks in the holes; these hooks were tied by a long cord to the harness, and thus my hat was dragged along for above half an English mile: but the ground in that country being extremely smooth and level, it received less damage than I expected. Two days after this adventure, the Emperor having ordered that part of his army which quarters in and about his metropolis to be in a readiness, took a fancy of diverting himself in a very singular manner. He desired I would stand like a Colossus, with my legs as far asunder as I conveniently could. He then commanded his General (who was an old experienced leader, and a great patron of mine) to draw up the troops in close order, and march them under me, the foot by twentyfour in a breast, and the horse by sixteen, with drums beating, colors flying, and pikes advanced. This body consisted of three thousand foot, and a thousand horse. His Majesty gave orders, upon pain of death, that every soldier in his march should observe the strictest decency with regard to my person; which, however, could not prevent some of the younger officers from turning up their eyes as they passed under me. And, to confess the truth, my breeches were at that time in so ill a condition, that they afforded some opportunities for laughter and admiration. I had sent so many memorials and petitions for my liberty, that his Majesty at length mentioned the matter, first in the cabinet, and then in a full council; where it was opposed by none, except Skyresh Bolgolam, who was pleased, without any provocation, to be my mortal enemy. But it was carried against him by the whole board, and confirmed by the Emperor. That minister was Galbet, or Admiral of the Realm, very much in his master's confidence, and a person well versed in affairs, but of a morose and sour complexion. However, he was at length persuaded to comply; but prevailed that the articles and conditions upon which I should be set free, and to which I must swear, should be drawn up by himself. These articles were brought to me by Skyresh Bolgolam in person, attended by two under-secretaries, and several persons of distinction. After they were read, I was demanded to swear to the performance of them; first in the manner of my own country, and afterwards in the method prescribed by their laws; which was to hold my right foot in my left hand, to place the middle finger of my right hand on the crown of my head, and my thumb on the tip of my right ear. But because the reader may perhaps be curious to have some idea of the style and manner of expression peculiar to that people, as well as to know the articles upon which I recovered my liberty, I have made a translation of the whole instrument word for word, as near as I was able, which I here offer to the public. - GOLBASTO MOMAREN EVLAME GURDILO SHEFIN MULLY ULLY GUE, most mighty Emperor of Lilliput, delight and terror of the universe, whose dominions extend five thousand blustrugs (about twelve miles in circumference) to the extremities of the globe; monarch of all monarchs, taller than the sons of men; whose feet press down to the center, and whose head strikes against the sun; at whose nod the princes of the earth shake their knees; pleasant as the spring, comfortable as the summer, fruitful as autumn, dreadful as winter. His most sublime Majesty proposes to the Man-Mountain, lately arrived to our celestial dominions, the following articles, which by a solemn oath he shall be obliged to perform. {P_1|CH_3 ^paragraph 10} First, The Man-Mountain shall not depart from our dominions, without our license under our great seal. 2nd, He shall not presume to come into our metropolis, without our express order; at which time the inhabitants shall have two hours warning to keep within their doors. 3rd, The said Man-Mountain shall confine his walks to our principal high roads, and not offer to walk or lie down in a meadow or field of corn. 4th, As he walks the said roads, he shall take the utmost care not to trample upon the bodies of any of our loving subjects, their horses, or carriages, nor take any of our said subjects into his hands, without their own consent. 5th, If an express requires extraordinary dispatch, the Man-Mountain shall be obliged to carry in his pocket the messenger and horse a six days journey once in every moon, and return the said messenger back (if so required) safe to our Imperial Presence. {P_1|CH_3 ^paragraph 15} 6th, He shall be our ally against our enemies in the Island of Blefuscu, and do his utmost to destroy their fleet, which is now preparing to invade us. 7th, That the said Man-Mountain shall, at his times of leisure, be aiding and assisting to our workmen, in helping to raise certain great stones, towards covering the wall of the principal park, and other of our royal buildings. 8th, That the said Man-Mountain shall, in two moons' time, deliver in an exact survey of the circumference of our dominions by a computation of his own paces round the coast. Lastly, That upon his solemn oath to observe all the above articles, the said Man-Mountain shall have a daily allowance of meat and drink sufficient for the support of 1,728 of our subjects, with free access to our Royal Person, and other marks of our favor. Given at our Palace at Belfaborac the twelfth day of the ninety-first moon of our reign. - {P_1|CH_3 ^paragraph 20} I swore and subscribed to these articles with great cheerfulness and content, although some of them were not so honorable as I could have wished; which proceeded wholly from the malice of Skyresh Bolgolam the High Admiral: whereupon my chains were immediately unlocked, and I was at full liberty; the Emperor himself in person did me the honor to be by at the whole ceremony. I made my acknowledgments by prostrating myself at his Majesty's feet: but he commanded me to rise; and after many gracious expressions, which, to avoid the censure of vanity, I shall not repeat, he added, that he hoped I should prove a useful servant, and well deserve all the favors he had already conferred upon me, or might do for the future. The reader may please to observe, that in the last article for the recovery of my liberty the Emperor stipulates to allow me a quantity of meat and drink sufficient for the support of 1,728 Lilliputians. Some time after, asking a friend at court how they came to fix on that determinate number, he told me that his Majesty's mathematicians, having taken the height of my body by the help of a quadrant, and finding it to exceed theirs in the proportion of twelve to one, they concluded from the similarity of their bodies, that mine must contain at least 1,728 of theirs, and consequently would require as much food as was necessary to support that number of Lilliputians. By which the reader may conceive an idea of the ingenuity of that people, as well as the prudent and exact economy of so great a prince. P_1|CH_4 CHAPTER IV - The first request I made after I had obtained my liberty, was, that I might have license to see Mildendo, the metropolis, which the Emperor easily granted me, but with a special charge to do no hurt either to the inhabitants or their houses. The people had notice by proclamation of my design to visit the town. The wall which encompassed it is two feet and a half high, and at least eleven inches broad, so that a coach and horses may be driven very safely round it; and it is flanked with strong towers at ten feet distance. I stepped over the great Gate, and passed very gently, and sidering through the two principal streets, only in my short waistcoat, for fear of damaging the roofs and eaves of the houses with the skirts of my coat. I walked with the utmost circumspection, to avoid treading on any stragglers, that might remain in the streets, although the orders were very strict, that all people should keep in their houses at their own peril. The garret windows and tops of houses were so crowded with spectators, that I thought in all my travels I had not seen a more populous place. The city is an exact square, each side of the wall being five hundred feet long. The two great streets, which run cross and divide it into four quarters, are five feet wide. The lanes and alleys, which I could not enter, but only viewed them as I passed, are from twelve to eighteen inches. The town is capable of holding five hundred thousand souls. The houses are from three to five stories. The shops and markets well provided. The Emperor's palace is in the centre of the city, where the two great streets meet. It is enclosed by a wall of two feet high, and twenty feet distant from the buildings. I had his Majesty's permission to step over this wall; and the space being so wide between that and the palace, I could easily view it on every side. The outward court is a square of forty feet, and includes two other courts: in the inmost are the royal apartments, which I was very desirous to see, but found it extremely difficult; for the great gates, from one square into another, were but eighteen inches high and seven inches wide. Now the buildings of the outer court were at least five feet high, and it was impossible for me to stride over them without infinite damage to the pile, though the walls were strongly built of hewn stone, and four inches thick. At the same time the Emperor had a great desire that I should see the magnificence of his palace; but this I was not able to do till three days after, which I spent in cutting down with my knife some of the largest trees in the royal park, about a hundred yards distant from the city. Of these trees I made two stools, each about three feet high, and strong enough to bear my weight. The people having received notice a second time, I went again through the city to the palace, with my two stools in my hands. When I came to the side of the outer court, I stood upon one stool, and took the other in my hand: this I lifted over the roof, and gently set it down on the space between the first and second court, which was eight feet wide. I then stepped over the buildings very conveniently from one stool to the other, and drew up the first after me with a hooked stick. By this contrivance I got into the inmost court; and lying down upon my side, I applied my face to the windows of the middle stories, which were left open on purpose, and discovered the most splendid apartments that can be imagined. There I saw the Empress and the young Princes, in their several lodgings, with their chief attendants about them. Her Imperial Majesty was pleased to smile very graciously upon me, and gave me out of the window her hand to kiss. But I shall not anticipate the reader with farther descriptions of this kind, because I reserve them for a greater work, which is now almost ready for the press, containing a general description of this empire, from its first erection, through a long series of princes, with a particular account of their wars and politics, laws, learning, and religion: their plants and animals, their peculiar manners and customs, with other matters very curious and useful; my chief design at present being only to relate such events and transactions as happened to the public, or to myself, during a residence of about nine months in that empire. One morning, about a fortnight after I had obtained my liberty, Reldresal, principal Secretary (as they style him) of Private Affairs, came to my house attended only by one servant. He ordered his coach to wait at a distance, and desired I would give him an hour's audience; which I readily consented to, on account of his quality and personal merits, as well as the many good offices he had done me during my solicitations at court. I offered to lie down, that he might the more conveniently reach my ear; but he chose rather to let me hold him in my hand during our conversation. He began with compliments on my liberty; said he might pretend to some merit in it: but, however, added, that if it had not been for the present situation of things at court, perhaps I might not have obtained it so soon. For, said he, as flourishing a condition as we may appear to be in to foreigners, we labor under two mighty evils; a violent faction at home, and the danger of an invasion by a most potent enemy from abroad. As to the first, you are to understand, that for above seventy moons past there have been two struggling parties in this empire, under the names of Tramecksan and Slamecksan, from the high and low heels on their shoes, by which they distinguish themselves. It is alleged indeed, that the high heels are most agreeable to our ancient constitution: but however this be, his Majesty has determined to make use of only low heels in the administration of the government, and all offices in the gift of the Crown, as you cannot but observe; and particularly, that his Majesty's Imperial heels are lower at least by a drurr than any of his court; (drurr is a measure about the fourteenth part of an inch). The animosities between these two parties run so high, that they will neither eat nor drink, nor talk with each other. We compute the Tramecksan, or High-Heels, to exceed us in number; but the power is wholly on our side. We apprehend his Imperial Highness, the Heir to the Crown, to have some tendency towards the High-Heels; at least we can plainly discover one of his heels higher than the other, which gives him a hobble in his gait. Now, in the midst of these intestine disquiets, we are threatened with an invasion from the Island of Blefuscu, which is the other great empire of the universe, almost as large and powerful as this of his Majesty. For as to what we have heard you affirm, that there are other kingdoms and states in the world inhabited by human creatures as large as yourself, our philosophers are in much doubt, and would rather conjecture that you dropped from the moon, or one of the stars; because it is certain, that a hundred mortals of your bulk would, in a short time, destroy all the fruits and cattle of his Majesty's dominions. Besides, our histories of six thousand moons make no mention of any other regions, than the two great empires of Lilliput and Blefuscu. Which two mighty powers have, as I was going to tell you, been engaged in a most obstinate war for six and thirty moons past. It began upon the following occasion. It is allowed on all hands, that the primitive way of breaking eggs, before we eat them, was upon the larger end: but his present Majesty's grandfather, while he was a boy, going to eat an egg, and breaking it according to the ancient practice, happened to cut one of his fingers. Whereupon the Emperor his father published an edict, commanding all his subjects, upon great penalties, to break the smaller end of their eggs. The people so highly resented this law, that our histories tell us there have been six rebellions raised on that account; wherein one Emperor lost his life, and another his crown. These civil commotions were constantly fomented by the monarchs of Blefuscu; and when they were quelled, the exiles always fled for refuge to that empire. It is computed, that eleven thousand persons have, at several times, suffered death, rather than submit to break their eggs at the smaller end. Many hundred large volumes have been published upon this controversy: but the books of the Big-Endians have been long forbidden, and the whole party rendered incapable by law of holding employments. During the course of these troubles, the Emperors of Blefuscu did frequently expostulate by their ambassadors, accusing us of making a schism in religion, by offending against a fundamental doctrine of our great prophet Lustrog, in the fifty-fourth chapter of the Blundecral (which is their however, is thought to be a mere strain upon the text: for the words are these; That all true believers shall break their eggs at the convenient end: and which is the convenient end, seems, in my humble opinion, to be left to every man's conscience, or at least in the power of the chief magistrate to determine. Now the Big-Endian exiles have found so much credit in the Emperor of Blefuscu's court, and so much private assistance and encouragement from their party here at home, that a bloody war has been carried on between the two empires for six and thirty moons with various success; during which time we have lost forty capital ships, and a much greater number of smaller vessels, together with thirty thousand of our best seamen and soldiers; and the damage received by the enemy is reckoned to be somewhat greater than ours. However, they have now equipped a numerous fleet, and are just preparing to make a descent upon us; and his Imperial Majesty, placing great confidence in your valor and strength, has commanded me to lay this account of his affairs before you. I desired the Secretary to present my humble duty to the Emperor, and to let him know, that I thought it would not become me, who was a foreigner, to interfere with parties; but I was ready, with the hazard of my life, to defend his person and state against all invaders. P_1|CH_5 CHAPTER V - The Empire of Blefuscu is an island situated to the north-northeast side of Lilliput, from whence it is parted only by a channel of eight hundred yards wide. I had not yet seen it, and upon this notice of an intended invasion, I avoided appearing on that side of the coast, for fear of being discovered by some of the enemy's ships, who had received no intelligence of me, all intercourse between the two empires having been strictly forbidden during the war, upon pain of death, and an embargo laid by our Emperor upon all vessels whatsoever. I communicated to his Majesty a project I had formed of seizing the enemy's whole fleet: which, as our scouts assured us, lay at anchor in the harbor ready to sail with the first fair wind. I consulted the most experienced seamen, upon the depth of the channel, which they had often plumbed, who told me, that in the middle at high-water it was seventy glumgluffs deep, which is about six feet of European measure; and the rest of it fifty glumgluffs at most. I walked towards the northeast coast over against Blefuscu; and lying down behind a hillock, took out my small pocket perspective glass, and viewed the enemy's fleet at anchor, consisting of about fifty men of war, and a great number of transports; I then came back to my house, and gave order (for which I had a warrant) for a great quantity of the strongest cable and bars of iron. The cable was about as thick as packthread, and the bars of the length and size of a knitting needle. I trebled the cable to make it stronger, and for the same reason I twisted three of the iron bars together, binding the extremities into a hook. Having thus fixed fifty hooks to as many cables, I went back to the northeast coast, and putting off my coat, shoes, and stockings, walked into the sea in my leather jerkin, about half an hour before high water. I waded with what haste I could, and swam in the middle about thirty yards till I felt ground; I arrived at the fleet in less than half an hour. The enemy was so frighted when they saw me, that they leaped out of their ships, and swam to shore, where there could not be fewer than thirty thousand souls. I then took my tackling, and fastening a hook to a hole at the prow of each, I tied all the cords together at the end. While I was thus employed, the enemy discharged several thousand arrows, many of which stuck in my hands and face; and besides the excessive smart, gave me much disturbance in my work. My greatest apprehension was for my eyes, which I should have infallibly lost, if I had not suddenly thought of an expedient. I kept among other little necessaries a pair of spectacles in a private pocket, which, as I observed before, had escaped the Emperor's searchers. These I took out and fastened as strongly as I could upon my nose, and thus armed went on boldly with my work in spite of the enemy's arrows, many of which struck against the glasses of my spectacles, but without any other effect, further than a little to discompose them. I now fastened all the hooks, and taking the knot in my hand, began to pull; but not a ship would stir, for they were all too fast held by their anchors, so that the boldest part of my enterprise remained. I therefore let go the cord, and leaving the hooks fixed to the ships, I resolutely cut with my knife the cables that fastened the anchors, receiving above two hundred shots in my face and hands; then I took up the knotted end of the cables to which my hooks were tied, and with great ease drew fifty of the enemy's men-of-war after me. The Blefuscudians, who had not the least imagination of what I intended, were at first confounded with astonishment. They had seen me cut the cables, and thought my design was only to let the ships run adrift or fall foul on each other: but when they perceived the whole fleet moving in order, and saw me pulling at the end, they set up such a scream of grief and despair, that it is almost impossible to describe or conceive. When I had got out of danger, I stopped awhile to pick out the arrows that stuck in my hands and face, and rubbed on some of the same ointment that was given me at my first arrival, as I have formerly mentioned. I then took off my spectacles, and waiting about an hour, till the tide was a little fallen, I waded through the middle with my cargo, and arrived safe at the royal port of Lilliput The Emperor and his whole court stood on the shore expecting the issue of this great adventure. They saw the ships move forward in a large half-moon, but could not discern me, who was up to my breast in water. When I advanced to the middle of the channel, they were yet in more pain, because I was under water to my neck. The Emperor concluded me to be drowned, and that the enemy's fleet was approaching in a hostile manner: but he was soon eased of his fears, for the channel growing shallower every step I made, I came in a short time within hearing, and holding up the end of the cable by which the fleet was fastened, I cried in a loud voice, Long live the most puissant Emperor of Lilliput! This great prince received me at my landing with all possible encomiums, and created me a Nardac upon the spot, which is the highest title of honor among them. His Majesty desired I would take some other opportunity of bringing all the rest of his enemy's ships into his ports. And so unmeasurable is the ambition of princes, that he seemed to think of nothing less than reducing the whole empire of Blefuscu into a province, and governing it by a Viceroy; of destroying the Big-Endian exiles, and compelling that people to break the smaller end of their eggs, by which he would remain the sole monarch of the whole world. But I endeavored to divert him from this design, by many arguments drawn from the topics of policy as well as justice; and I plainly protested, that I would never be an instrument of bringing a free and brave people into slavery. And when the matter was debated in council, the wisest part of the ministry were of my opinion. This open bold declaration of mine was so opposite to the schemes and politics of his Imperial Majesty, that he could never forgive it; he mentioned it in a very artful manner at council, where I was told that some of the wisest appeared, at least by their silence, to be of my opinion; but others, who were my secret enemies, could not forbear some expressions, which by a side-wind reflected on me. And from this time began an intrigue between his Majesty and a junto of ministers maliciously bent against me, which broke out in less than two months, and had like to have ended in my utter destruction. Of so little weight are the greatest services to princes, when put into the balance with a refusal to gratify their passions. {P_1|CH_5 ^paragraph 5} About three weeks after this exploit, there arrived a solemn embassy from Blefuscu, with humble offers of a peace; which was soon concluded upon conditions very advantageous to our Emperor, wherewith I shall not trouble the reader. There were six ambassadors, with a train of about five hundred persons, and their entry was very magnificent, suitable to the grandeur of their master, and the importance of their business. When their treaty was finished, wherein I did them several good offices by the credit I now had, or at least appeared to have at court, their Excellencies, who were privately told how much I had been their friend, made me a visit in form. They began with many compliments upon my valor and generosity, invited me to that kingdom in the Emperor their master's name, and desired me to show them some proofs of my prodigious strength, of which they had heard so many wonders; wherein I readily obliged them, but shall not trouble the reader with the particulars. When I had for some time entertained their Excellencies, to their infinite satisfaction and surprise, I desired they would do me the honor to present my most humble respects to the Emperor their master, the renown of whose had so justly filled the whole world with admiration, and whose royal person I resolved to attend before I returned to my own country: accordingly, the next time I had the honor to see our Emperor, I desired his general license to wait on the Blefuscudian monarch, which he was pleased to grant me, as I could plainly perceive, in a very cold manner; but could not guess the reason, till I had a whisper from a certain person, that Flimnap and Bolgolam had represented my intercourse with those ambassadors as a mark of disaffection, from which I am sure my heart was wholly free. And this was the first time I began to conceive some imperfect idea of courts and ministers. It is to be observed, that these ambassadors spoke to me by an interpreter, the languages of both empires differing as much from each other as any two in Europe, and each nation priding itself upon the antiquity, beauty, and energy of their own tongues, with an avowed contempt for that of their neighbor; yet our Emperor, standing upon the advantage he had got by the seizure of their fleet, obliged them to deliver their credentials, and make their speech in the Lilliputian tongue. And it must be confessed, that from the great intercourse of trade and commerce between both realms, from the continual reception of exiles, which is mutual among them, and from the custom in each empire to send their young nobility and richer gentry to the other, in order to polish themselves by seeing the world and understanding men and manners; there are few persons of distinction, or merchants, or seamen, who dwell in the maritime parts, but what can hold conversation both tongues; as I found some weeks after, when I went to pay my respects to the Emperor of Blefuscu, which in the midst of great misfortunes, through the malice of my enemies, proved a very happy adventure to me, as I shall relate in its proper place. The reader may remember, that when I signed those articles upon which I recovered my liberty, there were some which I disliked upon account of their being too servile, neither could anything but an extreme necessity have forced me to submit. But being now a Nardac, of the highest rank in that empire, such offices were looked upon as below my dignity, and the Emperor (to do him justice) never once mentioned them to me. However, it was not long before I had an opportunity of doing his Majesty, at least, as I then thought, a most signal service. I was alarmed at midnight with the cries of many hundred people at my door; by which being suddenly awaked, I was in some kind of terror. I heard the word burglum repeated incessantly: several of the Emperor's court, making their way through the crowd, entreated me to come immediately to the Palace, where her Imperial Majesty's apartment was on fire, by the carelessness of a maid of honor, who fell asleep while she was reading a romance. I got up in an instant; and orders being given to clear the way before me, and it being likewise a moonshine night, I made a shift to get to the Palace without trampling on any of the people. I found they had already applied ladders to the walls of the apartment, and were well provided with buckets, but the water was at some distance. These buckets were about the size of a large thimble, and the poor people supplied me with them as fast as they could; but the flame was so violent that they did little good. I might easily have stifled it with my coat, which I unfortunately left behind me for haste, and came away only in my leathern jerkin. The case seemed wholly desperate and deplorable; and this magnificent palace would have infallibly been burned down to the ground, if, by a presence of mind, unusual to me, I had not suddenly thought of an expedient. I had the evening before drunk plentifully of a most delicious wine, called glimigrim (the Blefuscudians call it flunec, but ours is esteemed the better sort), which is very diuretic. By the luckiest chance in the world, I had not discharged myself of any part of it. The heat I had contracted by coming very near the flames, and by laboring to quench them, made the wine begin to operate my urine; which I voided in such a quantity, and applied so well to the proper places, that in three minutes the fire was wholly extinguished, and the rest of that noble pile, which had cost so many ages in erecting, preserved from destruction. It was now daylight, and I returned to my house without waiting to congratulate with the Emperor: because, although I had done a very eminent piece of service, yet I could not tell how his Majesty might resent the manner by which I had performed it: for, by the fundamental laws of the realm, it is capital in any person, of what quality soever, to make water within the precincts of the palace. But I was a little comforted by a message from his Majesty, that he would give orders to the Grand Justiciary for passing my pardon in form; which, however, I could not obtain. And I was privately assured, that the Empress, conceiving the greatest abhorrence of what I had done, removed to the most distant side of the court, firmly resolved that those buildings should never be repaired for her use: and, in the presence of her chief confidants could not forbear vowing revenge. P_1|CH_6 CHAPTER VI - Although I intend to leave the description of this empire to a particular treatise, yet in the meantime I am content to gratify the curious reader with some general ideas. As the common size of the natives is somewhat under six inches high, so there is an exact proportion in all other animals, as well as plants and trees: for instance, the tallest horses and oxen are between four and five inches in height, the sheep an inch and a half, more or less: their geese about the bigness of a sparrow, and so the several gradations downwards till you come to the smallest, which, to my sight, were almost invisible; but nature had adapted the eyes of the Lilliputians to all objects proper for their view: they see with great exactness, but at no great distance. And to show the sharpness of their sight towards objects that are near, I have been much pleased with observing a cook pulling a lark, which was not so large as a common fly; and a young girl threading an invisible needle with invisible silk. Their tallest trees are about seven feet high; I mean some of those in the great royal park, the tops whereof I could but just reach with my fist clenched. The other vegetables are in the same proportion; but this I leave to the reader's imagination. I shall say but little at present of their learning, which for many ages had flourished in all its branches among them; but their manner of writing is very peculiar, being neither from the left to the right, like the Europeans; nor from the right to the left, like the Arabians; nor from up to down, like the Chinese; nor from down to up, like the Cascagians; but aslant from one corner of the paper to the other, like ladies in England. They bury their dead with their heads directly downwards, because they hold an opinion, that in eleven thousand moons they are all to rise again, in which period the earth (which they conceive to be flat) will turn upside down, and by this means they shall, at their resurrection, be found ready standing on their feet. The learned among them confess the absurdity of this doctrine, but the practice still continues, in compliance to the vulgar. There are some laws and customs in this empire very peculiar; and if they were not so directly contrary to those of my own dear country, I should be tempted to say a little in their justification. It is only to be wished that they were as well executed. The first I shall mention relates to informers. All crimes against the state are punished here with the utmost severity; but if the person accused makes his innocence plainly to appear upon his trial, the accuser is immediately put to an ignominious death; and out of his goods or lands, the innocent person is quadruply recompensed for the loss of his time, for the danger he underwent, for the hardship of his imprisonment, and for all the charges he had been at in making his defense. Or, if that fund be deficient, it is largely supplied by the Crown. The Emperor does also confer on him some public mark of his favor, and proclamation is made of his innocence through the whole city. They look upon fraud as a greater crime than theft, and therefore seldom fail to punish it with death; for they allege, that care and vigilance, with a very common understanding, may preserve a man's goods from thieves, but honesty has no fence against superior cunning; and since it is necessary that there should be a perpetual intercourse of buying and selling, and dealing upon credit, where fraud is permitted and connived at, or has no law to punish it, the honest dealer is always undone, and the knave gets the advantage. remember when I was once interceding with the King for a criminal who had wronged his master of a great sum of money, which he had received by order, and ran away with; and happening to tell his Majesty, by way of extenuation, that it was only a breach of trust; the Emperor thought it monstrous in me to offer, as a defense, the greatest aggravation of the crime: and truly I had little to say in return, farther than the common answer, that different nations had different customs; for, I confess, I was heartily ashamed. {P_1|CH_6 ^paragraph 5} Although we usually call reward and punishment the two hinges upon which all government turns, yet I could never observe this maxim to be put in practice by any nation except that of Lilliput. Whoever can there bring sufficient proof that he has strictly observed the laws of his country for seventy-three moons, has a claim to certain privileges, according to his quality and condition of life, with a proportionable sum of money out of a fund appropriated for that use: he likewise acquires the title of Snilpall, or Legal, which is added to his name, but does not descend to his posterity. And these people thought it a prodigious defect of policy among us, when I told them that our laws were enforced only by penalties without any mention of reward. It is upon this account that the image of justice, in their courts of judicature, is formed with six eyes, two before, as many behind, and on each side one, to signify circumspection; with a bag of gold open in her right hand, and a sword sheathed in her left, to show she is more disposed to reward than to punish. In choosing persons for all employments, they have more regard to good morals than to great abilities; for, since government is necessary to mankind, they believe-that the common size of human understandings is fitted to some station or other, and that Providence never intended to make the management of public affairs a mystery, to be comprehended only by a few persons of sublime genius, of which there seldom are three born in an age: but they suppose truth, justice, temperance, and the like, to be in every man's power; the practice of which virtues, assisted by experience and a good intention, would qualify any man for the service of his country, except where a course of study is required. But they thought the want of moral virtues was so far from being supplied by superior endowments of the mind, that employments could never be put into such dangerous hands as those of persons so qualified; and at least, that the mistakes committed by ignorance in a virtuous disposition, would never be of such fatal consequence to the public weal, as the practices of a man whose inclinations led him to be corrupt, and had great abilities to manage, and multiply, and defend his corruptions. In like manner, the disbelief of a Divine Providence renders a man incapable of holding any public station; for, since kings avow themselves to be the deputies of Providence, the Lilliputians think nothing can be more absurd than for a prince to employ such men as disown the authority under which he acts. In relating these and the following laws, I would only be understood to mean the original institutions, and not the most scandalous corruptions into which these people are fallen by the degenerate nature of man. For as to that infamous practice of acquiring great employments by dancing on the ropes, or badges of favor and distinction by leaping over sticks and creeping under them, the reader is to observe, that they were first introduced by the grandfather of the Emperor now reigning, and grew to the present height by the gradual increase of party and faction. Ingratitude is among them a capital crime, as we read it to have been in some other countries; for they reason thus, that whoever makes ill returns to his benefactor, must needs be a common enemy to the rest of mankind, from whom he has received no obligation, and therefore such a man is not fit to live. {P_1|CH_6 ^paragraph 10} Their notions relating to the duties of parents and children differ extremely from ours. For since the conjunction of male and female is founded upon the great law of nature, in order to propagate and continue the species, the Lilliputians will needs have it, that men and women are joined together like other animals, by the motives of concupiscence; and that their tenderness towards their young proceeds from the like natural principle: for which reason they will never allow, that a child is under any obligation to his father for begetting him, or his mother for bringing him into the world; which, considering the miseries of human life, was neither a benefit itself, nor intended so by his parents, whose thoughts in their love-encounters were otherwise employed. Upon these, and the like reasonings, their opinion is, that parents are the last of all others to be trusted with the education of their own children: and therefore they have in every town public nurseries, where all parents, except cottagers and laborers, are obliged to send their infants of both sexes to be reared and educated when they come to the age of twenty moons, at which time they are supposed to have some rudiments of docility. These schools are of several kinds, suited to different qualities, and to both sexes. They have certain professors well skilled in preparing children for such a condition of life as befits the rank of their parents, and their own capacities as well as inclinations. I shall say something of the male nurseries, and then of the female. The nurseries for males of noble or eminent birth are provided with grave and learned professors, and their several deputies. The clothes and food of the children are plain and simple. They are bred up in the principles of honor, justice, courage, modesty, clemency, religion, and love of their country; they are always employed in some business, except in the times of eating and sleeping, which are very short, and two hours for diversions, consisting of bodily exercises. They are dressed by men till four years of age, and then are obliged to dress themselves, although their quality be ever so great; and the women attendants, who are aged proportionably to ours at fifty, perform only the most menial offices. They are never suffered to converse with servants, but go together in small or greater numbers to take their diversions, and always in the presence of a professor, or one of his deputies; whereby they avoid those early bad impressions of folly and vice to which our children are subject. Their parents are suffered to see them only twice a year; the visit is to last but an hour. They are allowed to kiss the child at meeting and parting; but a professor, who always stands by on those occasions, will not suffer them to whisper, or use any fondling expressions, or bring any presents of toys, sweetmeats, and the like. The pension from each family for the education and entertainment of a child, upon failure of due payment, is levied by the Emperor's officers. The nurseries for children of ordinary gentlemen, merchants, traders, and handicrafts, are managed proportionably after the same manner; only those designed for trades are put out apprentices at eleven years old, whereas those of persons of quality continue in their exercises till fifteen, which answers to one and twenty with us: but the confinement is gradually lessened for the last three years. In the female nurseries, the young girls of quality are educated much like the males, only they are dressed by orderly servants of their own sex; but always in the presence of a professor or deputy, till they come to dress themselves, which is at five years old. And if it be found that these nurses ever presume to entertain the girls with frightful or foolish stories, or the common follies practiced by chambermaids among us, they are publicly whipped thrice about the city, imprisoned for a year and banished for life to the most desolate part of the country. Thus the young ladies there are as much ashamed of being cowards and fools as the men, and despise all personal ornaments beyond decency and cleanliness: neither did I perceive any difference in their education, made by their difference of sex, only that the exercises of the females were not altogether so robust; and that some rules were given them relating to domestic life, and a smaller compass of learning was enjoined them: for their maxim is, that among people of quality a wife should be always a reasonable and agreeable companion, because she cannot always be young. When the girls are twelve years old, which among them is the marriageable age, their parents or guardians take them home, with great expressions of gratitude to the professors, and seldom without tears of the young lady and her companions. {P_1|CH_6 ^paragraph 15} In the nurseries of females of the meaner sort, the children are instructed in all kinds of works proper for their sex, and their several degrees: those intended for apprentices are dismissed at nine years old, the rest are to thirteen. The meaner families who have children at these nurseries, are obliged, besides their annual pension, which is as low as possible, to return to the steward of the nursery a small monthly share of their gettings, to be a portion for the child; and therefore all parents are limited in their expenses by the law. For the Lilliputians think nothing can be more unjust, than for people, in subservience to their own appetites, to bring children into the world and leave the burden of supporting them on the public. As to persons of quality, they give security to appropriate a certain sum for each child, suitable to their condition; and these funds are always managed with good husbandry, and the most exact justice. The cottagers and laborers keep their children at home, their business being only to till and cultivate the earth, and therefore their education is of little consequence to the public; but the old and discased among them are supported by hospitals: for begging is a trade unknown in this kingdom. And here it may perhaps divert the curious reader to give some account of my domestics, and my manner of living in this country, during a residence of nine months and thirteen days. Having a head mechanically turned, and being likewise forced by necessity, I had made for myself a table and chair convenient enough, out of the largest trees in the royal park. Two hundred seamstresses were employed to make me shirts, and linen for my bed and table, all of the strongest and coarsest kind they could get; which, however, they were forced to quilt together in several folds, for the thickest was some degrees finer than lawn. Their linen is usually three inches wide, and three feet make a piece. The seamstresses took my measure as I lay on the ground, one standing at my neck, and another at my mid-leg, with a strong cord extended, that each held by the end, while the third measured the length of the cord with a rule an inch long. Then they measured my right thumb, and desired no more; for by a mathematical computation, that twice round the thumb is once round the wrist, and so on to the neck and the waist, and by the help of my old shirt, which I displayed on the ground before them for a pattern, they fitted me exactly. Three hundred tailors were employed in the same manner to make me clothes; but they had another contrivance for taking my measure. I kneeled down, and they raised a ladder from the ground to my neck; upon this ladder one of them mounted, and let fall a plumb-line from my collar to the floor, which just answered the length of my coat; but my waist and arms I measured myself. When my clothes finished, which was done in my house (for the largest of theirs would not have been able to hold them) they looked like the patch-work made by the ladies in England, only that mine were all of a color. I had three hundred cooks to dress my victuals, in little convenient huts built about my house, where they and their families lived, and prepared me two dishes apiece. I took up twenty waiters in my hand, and placed them on the table; a hundred more attended below on the ground, some with dishes of meat, and some with barrels of wine, and other liquors, slung on their shoulders; all which the waiters above drew up as I wanted, in a very ingenious manner, by certain cords, as we draw the bucket up a well in Europe. A dish of their meat was a good mouthful, and a barrel of their liquor a reasonable draught. Their mutton yields to ours, but their beef is excellent. I have had a sirloin so large, that I have been forced to make three bits of it; but this is rare. My servants were astonished to see me eat it bones and all, as in our country we do the leg of a lark. Their geese and turkeys I usually ate at a mouthful, and I must confess they far exceed ours. Of their smaller fowl I could take up twenty or thirty at the end of my knife. {P_1|CH_6 ^paragraph 20} One day his Imperial Majesty, being informed of my way of living, desired that himself and his Royal Consort, with the young Princes of the blood of both sexes, might have the happiness (as he was pleased to call it) of dining with me. They came accordingly, and I placed them upon chairs of state on my table, just over against me, with their guards about them. Flimnap, the Lord High Treasurer, attended there likewise with his white staff; and I observed he often looked on me with a sour countenance, which I would not seem to regard, but ate more than usual, in honor to my dear country, as well as to fill the court with admiration. I have some private reasons to believe, that this visit from his Majesty gave Flimnap an opportunity of doing me ill offices to his master. That minister had always been my secret enemy, though he outwardly caressed me more than was usual to the moroseness of his nature. He represented to the Emperor the low condition of his treasury; that he was forced to take up money at great discount; that exchequer bills would not circulate under nine per cent below par; that in short I had cost his Majesty above a million and a half of sprugs (their greatest gold coin, about the bigness of a spangle) and upon the whole, that it would be advisable in the Emperor to take the first fair occasion of dismissing me. I am here obliged to vindicate the reputation of an excellent lady, who was an innocent sufferer upon my account. The Treasurer took a fancy to be jealous of his wife, from the malice of some evil tongues, who informed him that her Grace had taken a violent affection for my person; and the court-scandal ran for some time, that she once came privately to my lodging. This I solemnly declare to be a most infamous falsehood, without any grounds, farther than that her Grace was pleased to treat me with all innocent marks of freedom and friendship. I own she came often to my house, but always publicly, nor ever without three more in the coach, who were usually her sister and young daughter, and some particular acquaintance; but this was common to many other ladies of the court. And I still appeal to my servants round, whether they at any time saw a coach at my door without knowing what persons were in it. On those occasions, when a servant had given me notice, my custom was to go immediately to the door; and, after paying my respects, to take up the coach and two horses very carefully in my hands (for if there were six horses, the postillion always unharnessed four) and place them on a table, where I had fixed a moveable rim quite round, of five inches high, to prevent accidents. And I have often had four coaches and horses at once on my table full of company, while I sat in my chair leaning my face towards them; and when I was engaged with one set, the coachmen would gently drive the others round my table. I have passed many an afternoon very agreeably in these conversations. But I defy the Treasurer, or his two informers (I will name them, and let them make their best of it) Clustril and Drunlo, to prove that any person ever came to me incognito, except the secretary Reldresal, who was sent by express command of his Imperial Majesty, as I have before related. I should not have dwelt so long upon this particular, it had not been a point wherein the reputation of a great lady is so nearly concerned, to say nothing of my own; though I then had the honor to be a Nardac, which the Treasurer himself is not; for all the world knows he is only a Glumglum, a title inferior by one degree, as that of a Marquis is to a Duke in England, although I allow he preceded me in right of his post. These false informations, which I afterwards came to the knowledge of, by an accident not proper to mention, made Flimnap the Treasurer show his lady for some time an ill countenance, and me a worse; and although he were at last undeceived and reconciled to her, yet I lost all credit with him, and found my interest decline very fast with the Emperor himself, who was indeed too much governed by that favorite. P_1|CH_7 CHAPTER VII - Before I proceed to give an account of my leaving this kingdom, it may be proper to inform the reader of a private intrigue which had been for two months forming against me. I had been hitherto all my life a stranger to courts, for which I was unqualified by the meanness of my condition. I had indeed heard and read enough of the dispositions of great princes and ministers; but never expected to have found such terrible effects of them in so remote a country, governed, as I thought, by very different maxims from those in Europe. When I was just preparing to pay my attendance on the Emperor of Blefuscu, a considerable person at court (to whom I had been very serviceable at a time when he lay under the highest displeasure of his Imperial Majesty) came to my house very privately at night in a close chair, and without sending his name, desired admittance. The chairmen were dismissed; I put the chair, with his Lordship in it, into my coat-pocket: and giving orders to a trusty servant to say I was indisposed and gone to sleep, I fastened the door of my house, placed the chair on the table, according to my usual custom, and sat down by it. After the common salutations were over, observing his Lordship's countenance full of concern, and enquiring into the reason, he desired I would hear him with patience in a matter that highly concerned my honor and my life. His speech was to the following effect, for I took notes of it as soon as he left me. You are to know, said he, that several Committees of Council have been lately called in the most private manner on your account; and it is but two days since his Majesty came to a full resolution. You are very sensible that Skyresh Bolgolam (Galbet, or High Admiral) has been your mortal enemy almost ever since your arrival. His original reasons I know not, but his hatred is much increased since your great success against Blefuscu, by which his glory as Admiral is obscured. This Lord, in conjunction with Flimnap the High Treasurer, whose enmity against you is notorious on account of his lady, Limtoc the General, Lalcon the Chamberlain, and Balmuff the Grand Justiciary, have prepared articles of impeachment against you, for treason, and other capital crimes. {P_1|CH_7 ^paragraph 5} This preface made me so impatient, being conscious of my own merits and innocence, that I was going to interrupt; when he entreated me to be silent, and thus proceeded. Out of gratitude for the favors you have done me, I procured information of the whole proceedings, and a copy of the articles, wherein I venture my head for your service. - Articles of Impeachment against Quinbus Flestrin (the Man-Mountain) {P_1|CH_7 ^paragraph 10} - ARTICLE I Whereas, by a statute made in the reign of his Imperial Majesty Calin Deffar Plune, it is enacted, that whoever shall make water within the precincts of the royal palace, should be liable to the pains and penalties of high treason; notwithstanding, the said Quinbus Flestrin, in open breach of the said law, under color of extinguishing the fire kindled in the apartment of his Majesty's most dear Imperial Consort, did maliciously, traitorously, and devilishly, by discharge of his urine, put out the said fire kindled in the said apartment, lying and being within the precincts of the said royal palace, against the statute in that case provided, etc., against the duty, etc. - ARTICLE II. {P_1|CH_7 ^paragraph 15} That the said Quinbus Flestrin having brought the imperial fleet of Blefuscu into the royal port, and being afterwards commanded by his Imperial Majesty to seize all the other ships of the said empire of Blefuscu, and reduce that empire to a province, to be governed by a Viceroy from hence, and to destroy and put to death not only all the Big-Endian exiles, but likewise all the people of that empire, who would not immediately forsake the Big-Endian heresy: He, the said Flestrin, like a false traitor against his most Auspicious, Serene, Imperial Majesty, did petition to be excused from the said service upon pretense of unwillingness to force the consciences, or destroy the liberties and lives of an innocent people. - ARTICLE III. That, whereas certain ambassadors from the court of Blefuscu, to sue for peace in his Majesty's court: He, the said Flestrin, did, like a false traitor, aid, abet, comfort, and divert the said ambassadors, although he knew them to be servants to a Prince who was lately an open enemy to his Imperial Majesty, and in open war against his said Majesty. - {P_1|CH_7 ^paragraph 20} ARTICLE IV. That the said Quinbus Flestrin, contrary to the duty of a faithful subject, is now preparing to make a voyage to the court and empire of Blefuscu, for which he had received only verbal license from his Imperial Majesty; and under color of the said license, doth falsely and traitorously intend to take the said voyage, and hereby to aid, comfort, and abet the Emperor of Blefuscu, so late an enemy, and in open war with his Imperial Majesty aforesaid. - There are some other articles, but these are the most important, of which I have read you an abstract. In the several debates upon this impeachment, it must be confessed that his Majesty gave many marks of his great lenity, often urging the services you had done him, and endeavoring to extenuate your crimes. The Treasurer and Admiral insisted that you should be put to the most painful and ignominious death, by setting fire on your house at night, and the General was to attend with twenty thousand men armed with poisoned arrows to shoot you on the face and hands. Some of your servants were to have private orders to strew a poisonous juice on your shirts, which would soon make you tear your own flesh, and die in the utmost torture. The General came into the same opinion, so that for a long time there was a majority against you. But his Majesty resolving, if possible, to spare your life, at last brought off the Chamberlain. {P_1|CH_7 ^paragraph 25} Upon this incident, Reldresal, principal Secretary for Private Affairs, who always approved himself your true friend, was commanded by the Emperor to deliver his opinion, which he accordingly did; and therein justified the good thoughts you have of him. He allowed your crimes to be great, but that still there was room for mercy, the most commendable virtue in a prince, and for which his Majesty was so justly celebrated. He said, the friendship between you and him was so well known to the world, that perhaps the most honorable board might think him partial: however, in obedience to the command he had received, he would freely offer his sentiments. That if his Majesty, in consideration of your services, and pursuant to his own merciful disposition, would please to spare your life, and only give order to put out both your eyes, he humbly conceived that by this expedient justice might in some measure be satisfied, and all the world would applaud the lenity of the Emperor, as well as the fair and generous proceedings of those who have the honor to be his counsellors. That the loss of your eyes would be no impediment to your bodily strength, by which you might still be useful to his Majesty. That blindness is an addition to courage, by concealing dangers from us; that the fear you had for your eyes was the greatest difficulty in bringing over the enemy's fleet, and it would be sufficient for you to see by the eyes of the ministers, since the greatest princes do no more. This proposal was received with the utmost disapprobation by the whole board. Bolgolam, the Admiral, could not preserve his temper, but rising up in fury said he wondered how the Secretary dared presume to give his opinion for preserving the life of a traitor: that the services you had performed, were, by all true reasons of state, the great aggravation of your crimes; that you, who were able to extinguish the fire, by discharge of urine in her Majesty's apartment (which he mentioned with horror), might at another time, raise an inundation by the same means, to drown the whole palace; and the same strength which enabled you to bring over the enemy's fleet, might serve, upon the first discontent, to carry it back: that he had good reasons to think you were a Big-Endian in your heart; and as treason begins in the heart, before it appears in overt acts, so he accused you as a traitor on that account, and therefore insisted you should be put to death. The Treasurer was of the same opinion; he showed to what straits his Majesty's revenue was reduced by the charge of maintaining you, which would soon grow insupportable: that the Secretary's expedient of putting out your eyes was so far from being a remedy against this evil, it would probably increase it, as it is manifest from the common practice of blinding some kind of fowl, after which they fed the faster, and grew sooner fat: that his sacred Majesty and the Council, who are your judges, were in their own consciences fully convinced of your guilt, which was a sufficient argument to condemn you to death, without the formal proofs required by the strict letter of the law. But his Imperial Majesty, fully determined against capital punishment, was graciously pleased to say, that since the Council thought the loss of your eyes too easy a censure, some other may be inflicted hereafter. And your friend the Secretary humbly desiring to be heard again, in answer to what the Treasurer had objected concerning the great charge his Majesty was at in maintaining you, said that his Excellency, who had the sole disposal of the Emperor's revenue, might easily provide against that evil, by gradually lessening your establishment; by which, for want of sufficient food, you would grow weak and faint, and lose your appetite, and consequently decay and consume in a few months; neither would the stench of your carcass be then so dangerous, when it should become more than half diminished; and immediately upon your death, five or six thousand of his Majesty's subjects might, in two or three days, cut your flesh from your bones, take it away by cartloads, and bury it in distant parts to prevent infection, leaving the skeleton as a monument of admiration to posterity. Thus by the great friendship of the Secretary, the whole affair was compromised. It was strictly enjoined, that the project of starving you by degrees should be kept a secret, but the sentence of putting out your eyes was entered on the books; none dissenting except Bolgolam the Admiral, who, being a creature of the Empress, was perpetually instigated by her Majesty to insist upon your death, she having borne perpetual malice against you, on account of that infamous and illegal method you took to extinguish the fire in her apartment. {P_1|CH_7 ^paragraph 30} In three days your friend the Secretary will be directed to come to your house, and read before you the articles of impeachment; and then to signify the great lenity and favor of his Majesty and Council, whereby you are only condemned to the loss of your eyes, which his Majesty does not question you will gratefully and humbly submit to; and twenty of his Majesty's surgeons will attend, in order to see the operation well performed, by discharging very sharp-pointed arrows into the balls of your eyes, as you lie on the ground. I leave to your prudence what measures you will take; and to avoid suspicion, I must immediately return in as private a manner as I came. His Lordship did so, and I remained alone, under many doubts and perplexities of mind. It was a custom introduced by this prince and his ministry (very different, as I have been assured, from the practices of former times) that after the court had decreed any cruel execution, either to gratify the monarch's resentment, or the malice of a favorite, the Emperor always made a speech to his whole Council, expressing his great lenity and tenderness, as qualities known and confessed by all the world. This speech was immediately published through the kingdom; nor did anything terrify the people so much as those encomiums on his Majesty's mercy; because it was observed, that the more these praises were enlarged and insisted on, the more inhuman was the punishment, and the sufferer more innocent. And as to myself, I must confess, having never been designed for a courtier either by my birth or education, I was so ill a judge of things, that I could not discover the lenity and favor of this sentence, but conceived it (perhaps erroneously) rather to be rigorous than gentle. I sometimes thought of standing my trial, for although I could not deny the facts alleged in the several articles, yet I hoped they would admit of some extenuations. But having in my life perused many state trials, which I ever observed to terminate as the judges thought fit to direct, I dared not rely on so dangerous a decision, in so critical a juncture, and against such powerful enemies. Once I was strongly bent upon resistance, for while I had liberty, the whole strength of that empire could hardly subdue me, and I might easily with stones pelt the metropolis to pieces; but I soon rejected that project with horror, by remembering the oath I had made to the Emperor, the favors I received from him, and the high title of Nardac he conferred upon me. Neither had I so soon learned the gratitude of courtiers, to persuade myself that his Majesty's present severities quitted me of all past obligations. At last I fixed upon a resolution, for which it is probable I may incur some censure, and not unjustly; for I confess I owe the preserving of my eyes, and consequently my liberty, to my own great rashness and want of experience: because if I had then known the nature of princes and ministers, which I have since observed in many other courts, and their methods of treating criminals less obnoxious than myself, I should with great alacrity and readiness have submitted to so easy a punishment. But hurried on by the precipitancy of youth, and having his Imperial Majesty's license to pay my attendance upon the Emperor of Blefuscu, I took this opportunity, before the three days were elapsed, to send a letter to my friend the Secretary, signifying my resolution of setting out that morning Blefuscu pursuant to the leave I had got; and without waiting for an answer, I went to that side of the island where our fleet lay. I seized a large man of war, tied a cable to the prow, and, lifting up the anchors, I stripped myself, put my clothes (together with my coverlet, which I brought under my arm) into the vessel, and drawing it after me between wading and swimming, arrived at the royal port of Blefuscu, where the people had long expected me; they lent me two guides to direct me to the capital city, which is of the same name. I held them in my hands till I came within two hundred yards of the gate, and desired them to signify my arrival to one of the secretaries, and let him know, I there waited his Majesty's commands. I had an answer in about an hour, that his Majesty, attended by the Royal Family, and great officers of the court, was coming out to receive me. I advanced a hundred yards. The Emperor and his train alighted from their horses, the Empress and ladies from their coaches, and I did not perceive they were in any fright or concern. I lay on the ground to kiss his Majesty's and the Empress's hand. I told his Majesty that I had come according to my promise, and with the license of the Emperor, my master, to have the honor of seeing so mighty a monarch, and to offer him any service in my power, consistent with my duty to my own prince; not mentioning a word of my disgrace, because I had hitherto no regular information of it, and might suppose myself wholly ignorant of any such design; neither could I reasonably conceive that the Emperor would discover the secret while I was out of his power: wherein, however, it soon appeared I was deceived. {P_1|CH_7 ^paragraph 35} I shall not trouble the reader with the particular account of my reception at this court, which was suitable to the generosity of so great a prince; nor of the difficulties I was in for want of a house and bed, being forced to lie on the ground, wrapped up in my coverlet. P_1|CH_8 CHAPTER VIII - Three days after my arrival, walking out of curiosity to the northeast coast of the island, I observed, about half a league off, in the sea, something that looked like a boat overturned. I pulled off my shoes and stockings, and wading two or three hundred yards, I found the object to approach nearer by force of the tide; and then plainly saw it to be a real boat, which I supposed might, by some tempest, have been driven from a ship; whereupon I returned immediately towards the city, and desired his Imperial Majesty to lend me twenty of the tallest vessels he had left after the loss of his fleet, and three thousand seamen under the command of his Vice-Admiral. This fleet sailed round, while I went back the shortest way to the coast where I first discovered the boat; I found the tide had driven it still nearer. The seamen were all provided with cordage, which I had beforehand twisted to a sufficient strength. When the ships came up, I stripped myself, and waded till I came within a hundred yards of the boat, after which I was forced to swim till I got up to it. The seamen threw me the end of the cord, which I fastened to a hole in the forepart of the boat, and the other end to a man of war; but I found all my labor to little purpose; for being out of my depth, I was not able to work. In this necessity, I was forced to swim behind, and push the boat forwards as often as I could, with one of my hands; and the tide favoring me, I advanced so far, that I could just hold up my chin and feel the ground. I rested two or three minutes, and then gave the boat another shove, and so on till the sea was no higher than my arm-pits; and now the most laborious part being over, I took out my other cables, which were stowed in one of the ships, and fastening them first to the boat, and then to nine of the vessels which attended me; the wind being favorable, the seamen towed, and I shoved till we arrived within forty yards of the shore; and waiting till the tide was out, I got dry to the boat, and by the assistance of two thousand men, with ropes and engines, I made a shift to turn it on its bottom, and found it was but little damaged. I shall not trouble the reader with the difficulties I was under by the help of certain paddles, which cost me ten days making, to get my boat to the royal port of Blefuscu, where a mighty concourse of people appeared upon my arrival, full of wonder at the sight of so prodigious a vessel. I told the Emperor that my good fortune had thrown this boat in my way, to carry me to some place from whence I might return into my native country, and begged his Majesty's orders for getting materials to fit it up, together with his license to depart; which, after some kind expostulations, he was pleased to grant. I did very much wonder, in all this time, not to have heard of any express relating to me from our Emperor to the court of Blefuscu. But I was afterwards given privately to understand, that his Imperial Majesty, never imagining I had the least notice of his designs, believed I was only gone to Blefuscu in performance of my promise, according to the license he had given me, which was well known at our court, and would return in a few days when that ceremony was ended. But he was at last in pain at my long absence; and after consulting with the Treasurer, and the rest of that cabal, a person of quality was dispatched with the copy of the articles against me. This envoy had instructions to represent to the monarch of Blefuscu the great lenity of his master, who was content to punish me no farther than with the loss of my eyes; that I had fled from justice, and if I did not return in two hours, I should be deprived of my title of Nardac, and declared a traitor. The envoy further added, that in order to maintain the peace and amity between both empires, his master expected, that his brother of Blefuscu would give orders to have me sent back to Lilliput, bound hand and foot, to be punished as a traitor. The Emperor of Blefuscu having taken three days to consult, returned an answer consisting of many civilities and excuses. He said, that as for sending me bound, his brother knew it was impossible; that although I had deprived him of his fleet, yet he owed great obligations to me for many good offices I had done him in making the peace. That however both their Majesties would soon be made easy; for I had found a prodigious vessel on the shore, able to carry me on the sea, which he had given order to fit up with my own assistance and direction; and he hoped in a few weeks both empires would be freed from so insupportable an incumbrance. With this answer the envoy returned to Lilliput, and the monarch of Blefuscu related to me all that had past, offering me at the same time (but under the strictest confidence) his gracious protection, if I would continue in his service; wherein although I believed him sincere, yet I resolved never more to put any confidence in princes or ministers, where I could possibly avoid it; and therefore, with all due acknowledgements for his favorable intentions, I humbly begged to be excused. I told him that since fortune, whether good or evil, had thrown a vessel in my way, I was resolved to venture myself in the ocean, rather than be an occasion of difference between two such mighty monarchs. Neither did I find the Emperor at all displeased; and I discovered by a certain accident, that he was very glad of my resolution, and so were most of his ministers. {P_1|CH_8 ^paragraph 5} These considerations moved me to hasten my departure somewhat sooner than I intended; to which the court, impatient to have me gone, very readily contributed. Five hundred workmen were employed to make two sails to my boat, according to my directions, by quilting thirteen fold of their strongest linen together. I was at the pains of making ropes and cables, by twisting ten, twenty or thirty of the thickest and strongest of theirs. A great stone that I happened to find, after a long search, by the sea-shore, served me for an anchor. I had the tallow of three hundred cows for greasing my boat, and other uses. I was at incredible pains in cutting down some of the largest timber-trees for oars and masts, wherein I was, however, much assisted by his Majesty's ship carpenters, who helped me in smoothing them, after I had done the rough work. In about a month, when all was prepared, I sent to receive his Majesty's commands, and to take my leave. The Emperor and Royal Family came out of the palace; I lay down on my face to kiss his hand, which he very graciously gave me: so did the Empress and young Princes of the blood. His Majesty presented me with fifty purses of two hundred sprugs apiece, together with his picture at full length, which I put immediately into one of my gloves, to keep it from being hurt. The ceremonies at my departure were too many to trouble the reader with at this time. I stored the boat with the carcases of a hundred oxen, and three hundred sheep, with bread and drink proportionable, and as much meat ready dressed as four hundred cooks could provide. I took with me six cows and two bulls alive, with as many ewes and rams, intending to carry them into my own country, and propagate the breed. And to feed them on board, I had a good bundle of hay, and a bag of corn. I would gladly have taken a dozen of the natives, but this was a thing the Emperor would by no means permit; and besides a diligent search into my pockets, his Majesty engaged my honor not to carry away any of his subjects, although with their own consent and desire. Having thus prepared all things as well as I was able, I set sail on the twenty-fourth day of September, 1701, at six in the morning; and when I had gone about four leagues to the northward, the wind being at southeast, at six in the evening I descried a small island about half a league to the northwest. I advanced forward, and cast anchor on the leeside of the island, which seemed to be uninhabited. I then took some refreshment, and went to my rest. I slept well, and I conjecture at least six hours, for I found the day broke in two hours after I awaked. It was a clear night. I ate my breakfast before the sun was up; and heaving anchor, the wind being favorable, I steered the same course that I had done the day before, wherein I was directed by my pocket compass. My intention was to reach, if possible, one of those islands, which I had reason to believe lay to the northeast of Van Diemen's Land. I discovered nothing all that day; but upon the next, about three in the afternoon, when I had by my computation made twenty-four leagues from Blefuscu, I descried a sail steering to the southeast; my course was due east. I hailed her, but could get no answer; yet I found I gained upon her, for the wind slackened. I made all the sail I could, and in half an hour she spied me, then hung out her ancient, and discharged a gun. It is not easy to express the joy I was in upon the unexpected hope of once more seeing my beloved country, and the dear pledges I had left in it. The ship slackened her sails, and I came up with her between five and six in the evening, September 26; but my heart leaped within me to see her English colors. I put my cows and sheep into my coat pockets, and got on board with all my little cargo of provisions. The vessel was an English merchantman, returning from Japan by the North and South Seas; the Captain, Mr. John Biddle of Deptford, a very civil man, and an excellent sailor. We were now in the latitude of 30 degrees south; there were about fifty men in the ship; and here I met an old comrade of mine, one Peter Williams, who gave me a good character to the Captain. This gentleman treated me with kindness, and desired I would let know what place I came from last, and whither I was bound; which I did in few words, but he thought I was raving, and that the dangers I underwent had disturbed my head; whereupon I took my black cattle and sheep out of my pocket, which, after great astonishment, clearly convinced him of my veracity. I then showed him the gold given me by the Emperor of Blefuscu, together with his Majesty's picture at full length, and some other rarities of that country. I gave him two purses of two hundred sprugs each, and promised, when we arrived in England, to make him a present of a cow and a sheep big with young. I shall not trouble the reader with a particular account of this voyage, which was very prosperous for the most part. We arrived in the Downs on the 13th of April, 1702. I had only one misfortune, that the rats on board carried away one of my sheep; I found her bones in a hole, picked clean from the flesh. The rest of my cattle I got safe on shore, and set them grazing in a bowling-green at Greenwich, where the fineness of the grass made them feed very heartily, though I had always feared the contrary: neither could I possibly have preserved them in so long a voyage, if the Captain had not allowed me some of his best biscuit, which, rubbed to powder, and mingled with water, was their constant food. The short time I continued in England, I made considerable profit by showing my cattle to many persons of quality, and others: and before I began my second voyage, I sold them for six hundred pounds. Since my last return, I find the breed is considerably increased, especially the sheep; which I hope will prove much to the advantage of the woollen manufacture, by the fineness of the fleeces. {P_1|CH_8 ^paragraph 10} I stayed but two months with my wife and family; for my insatiable desire of seeing foreign countries would suffer me to continue no longer. I left fifteen hundred pounds with my wife, and fixed her in a good house at Redriff. My remaining stock I carried with me, part in money, and part in goods, in hopes to improve my fortunes. My eldest uncle John had left me an estate in land, near Epping, of about thirty pounds a year; and I had a long lease of the Black Bull in Fetter Lane, which yielded me as much more; so that I was not in any danger of leaving my family upon the parish. My son Johnny, named so after his uncle, was at the Grammar School, and a towardly child. My daughter Betty (who is now well married, and has children) was then at her needlework. I took leave of my wife, and boy and girl, with tears on both sides, and went on board the Adventure, a merchantship of three hundred tons, bound for Surat, Captain John Nicholas of Liverpool, Commander. But my account of this voyage must be referred to the second part of my Travels. - THE END OF THE FIRST PART PART II A VOYAGE TO BROBDINGNAG (SEE PLATE 2) P_2|CH_1 CHAPTER I - Having been condemned by nature and fortune to an active and restless life, in two months after my return I again left my native country, and took shipping in the Downs on the 20th day of June, 1702, in the Adventure, Captain John Nicholas, a Cornishman, Commander, bound for Surat. We had a very prosperous gale till we arrived at the Cape of Good Hope, where we landed for fresh water, but discovering a leak we unshipped our goods and wintered there; for the Captain falling sick of an ague, we could not leave the Cape till the end of March. We then set sail, and had a good voyage till we passed the Straits of Madagascar; but having got northward of that island, and to about five degrees south latitude, the winds, which in those seas are observed to blow a constant equal gale between the north and west from the beginning of December to the beginning of May, on the 19th of April began to blow with much greater violence, and more westerly than usual, continuing so for twenty days together, during which time we were driven a little to the east of the Molucca Islands, and about three degrees northward of the Line, as our Captain found by an observation he took the 2nd of May, at which time the wind ceased, and it was a perfect calm, whereat I was not a little rejoiced. But he, being a man well experienced in the navigation of those seas, bid us all prepare against a storm, which accordingly happened the day following: for a southern wind, called the southern monsoon, began to set in. Finding it was likely to overblow, we took in our spritsail, and stood by to hand the foresail; but making foul weather, we looked the guns were all fast, and handed the mizzen. The ship lay very broad off, so we thought it better spooning before the sea, than trying or hulling. We reefed the foresail and set him, we hauled aft the foresheet; the helm was hard aweather. The ship wore bravely. We belayed the fore-down-haul; but the sail was split, and we hauled down the yard, and got the sail into the ship, and unbound all the things clear of it. It was a very fierce storm; the sea broke strange and dangerous. We hauled off upon the lanyard of the whipstaff, and helped the man at helm. We would not get down our topmast, but let all stand, because she scudded before the sea very well, and we knew that the topmast being aloft, the ship was the wholesomer, and made better way through the sea, seeing we had sea room. When the storm was over, we set foresail and mainsail, and brought the ship to: then we set the mizzen, main-topsail, and the fore-topsail. Our course was east northeast, the wind was at southwest. We got the starboard tacks aboard; we cast off our weather-braces and lifts; we set in the leebraces, and hauled forward by the weatherbowlings, and hauled them tight, and belayed them, and hauled over the mizzen tack to windward, and kept her full and by as near as she would lie. During this storm, which was followed by a strong wind west southwest, we were carried by my computation about five hundred leagues to the east, so that the oldest sailor on board could not tell in what part of the world we were. Our provisions held out well, our ship was staunch, and our crew all in good health; but we lay in the utmost distress for water. We thought it best to hold on the same course, rather than turn more northerly, which might have brought us to the northwest parts of Great Tartary, and into the frozen sea. On the 16th day of June, 1703, a boy on the topmost discovered land. On the 17th we came in full view of a great island or continent (for we knew not which) on the south side whereof was a small neck of land jutting out into the sea, and a creek too shallow to hold a ship of above one hundred tons. We cast anchor within a league of this creek, and our Captain sent a dozen of his men well armed in the longboat, with vessels for water if any could be found. I desired his leave to go with them, that I might see the country, and make what discoveries I could. When we came to land we saw no river or spring, nor any sign of inhabitants. Our men therefore wandered on the shore to find out some fresh water near the sea, and I walked alone about a mile on the other side, where I observed the country all barren and rocky. I now began to be weary, and seeing nothing to entertain my curiosity, I returned gently down towards the creek; and the sea being full in my view, I saw our men already got into the boat, and rowing for life to the ship. I was going to halloo after them, although it had been to little purpose, when I observed a huge creature walking after them in the sea, as fast as he could: he waded not much deeper than his knees, and took prodigious strides: but our men had the start of him half a league, and the sea thereabouts being full of sharp-pointed rocks, the monster was not able to overtake the boat. This I was afterwards told, for I dared not stay to see the issue of that adventure; but ran as fast as I could the way I first went, and then climbed up a steep hill, which gave me some prospect of the country. I found it fully cultivated; but that which first surprised me was the length of the grass, which in those grounds that seemed to be kept for hay, was about twenty feet high. I fell into a high road, for so I took it to be, though it served to the inhabitants only as a footpath through a field of barley. Here I walked on for some time, but could see little on either side, it being now near harvest, and the corn rising at least forty feet. I was an hour walking to the end of this field, which was fenced in with a hedge of at least one hundred and twenty feet high, and the trees so lofty that I could make no computation of their altitude. There was a stile to pass from this field into the next. It had four steps, and a stone to cross over when you came to the uppermost. It was impossible for me to climb this stile, because every step was six feet high, and the upper stone above twenty. I was endeavoring to find some gap in the hedge, when I discovered one of the inhabitants in the next field, advancing towards the stile, of the same size with him whom I saw in the sea pursuing our boat. He appeared as tall as an ordinary spire steeple, and took about ten yards at every stride, as near as I could guess. I was struck with the utmost fear and astonishment, and ran to hide myself in the corn, from whence I saw him at the top of the stile, looking back into the next field on the right hand, and heard him call in a voice many degrees louder than a speaking trumpet: but the noise was so high in the air, that at first I certainly thought it was thunder. Whereupon seven monsters like himself came towards him with reaping hooks in their hands, each hook about the size of six scythes. These people were not so well clad as the first, whose servants or laborers they seemed to be. For upon some words he spoke, they went to reap the corn in the field where I lay. I kept from them at as great a distance as I could, but was forced to move with extreme difficulty, for the stalks of the corn were sometimes not above a foot distant, so that I could hardly squeeze my body between them. However, I made a shift to go forward till I came to a part of the field where the corn had been laid by the rain and wind. Here it was impossible for me to advance a step; for the stalks were so interwoven that I could not creep through, and the beards of the fallen ears so strong and pointed that they pierced through my clothes into my flesh. At the same time I heard the reapers not above a hundred yards behind me. Being quite dispirited with toil, and wholly overcome by grief and despair, I lay down between two ridges, and heartily wished I might there end my days. I bemoaned my desolate widow, and fatherless children. I lamented my own folly and willfulness in attempting a second voyage against the advice of all my friends and relations. In this terrible agitation of mind I could not forbear thinking of Lilliput, whose inhabitants looked upon me as the greatest prodigy that ever appeared in the world; where I was able to draw an Imperial Fleet in my hand, and perform those other actions which will be recorded forever in the chronicles of that empire, while posterity shall hardly believe them, although attested by millions. I reflected what a mortification it must prove to me to appear as inconsiderable in this nation as one single Lilliputian would be among us. But this I conceived was to be the least of my misfortunes: for as human creatures are observed to be more savage and cruel in proportion to their bulk, what could I expect but to be a morsel in the mouth of the first among these enormous barbarians that should happen to seize me? Undoubtedly philosophers are in the right when they tell us, that nothing is great or little otherwise than by comparison. It might have pleased fortune to let the Lilliputians find some nation, where the people were as diminutive with respect to them, as they were to me. And who knows but that even this prodigious race of mortals might be equally overmatched in some distant part of the world, whereof we have yet no discovery? {P_2|CH_1 ^paragraph 5} Scared and confounded as I was, I could not forbear going on with these reflections, when one of the reapers approaching within ten yards of the ridge where I lay, made me apprehend that with the next step I should be squashed to death under his foot, or cut in two with his reaping hook. And therefore when he was again about to move, I screamed as loud as fear could make me. Whereupon the huge creature trod short, and looking round about under him for some time, at last espied me as I lay on the ground. He considered a while with the caution of one who endeavors to lay hold on a small dangerous animal in such a manner that it shall not be able either to scratch or to bite him, as I myself have sometimes done with a weasel in England. At length he ventured to take me up behind by the middle between his forefinger and thumb, and brought me within three yards of his eyes, that he might behold my shape more perfectly. I guessed his meaning, and my good fortune gave me so much presence of mind, that I resolved not to struggle in the least as he held me in the air about sixty feet from the ground, although he grievously pinched my sides, for fear I should slip through his fingers. All I ventured was to raise my eyes towards the sun, and place my hands together in a supplicating posture, and to speak some words in a humble melancholy tone, suitable to the condition I then was in. For I apprehended every moment that he would dash me against the ground, as we usually do any little hateful animal which we have a mind to destroy. But my good star would have it, that he appeared pleased with my voice and gestures, and began to look upon me as a curiosity, much wondering to hear me pronounce articulate words, although he could not understand them. In the meantime I was not able to forbear groaning and shedding tears, and turning my head towards my sides; letting him know, as well as I could, how cruelly I was hurt by the pressure of his thumb and finger. He seemed to apprehend my meaning; for, lifting up the lappet of his coat, he put me gently into it, and immediately ran along with me to his master, who was a substantial farmer, and the same person I had first seen in the field. The farmer having (as I supposed by their talk) received such an account of me as his servant could give him, took a piece of a small straw, about the size of a walking staff, and therewith lifted up the lappets of my coat; which it seems he thought to be some kind of covering that nature had given me. He blew my hair aside to take a better view of my face. He called his hinds about him, and asked them (as I afterwards learned) whether they had ever seen in the fields any little creature that resembled me. He then placed me softly on the ground upon all four, but I got immediately up, and walked slowly backwards and forwards, to let those people see I had no intent to run away. They all sat down in a circle about me, the better to observe my motions. I pulled off my hat, and made a low bow towards the farmer. I fell on my knees, and lifted up my hands and eyes, and spoke several words as loud as I could: I took a purse of gold out of my pocket, and humbly presented it to him. He received it on the palm of his hand, then applied it close to his eye, to see what it was, and afterwards turned it several times with the point of a pin (which he took out of his sleeve), but could make nothing of it. Whereupon I made a sign that he should place his hand on the ground. I took the purse, and opening it, poured all the gold into his palm. There were six Spanish pieces of four pistoles each, beside twenty or thirty smaller coins. I saw him wet the tip of his little finger upon his tongue, and take up one of my largest pieces, and then another, but he seemed to be wholly ignorant what they were. He made me a sign to put them again into my purse, and the purse again into my pocket, which after offering to him several times, I thought it best to do. The farmer by this time was convinced I must be a rational creature. He spoke often to me, but the sound of his voice pierced my ears like that of a water mill, yet his words were articulate enough. I answered as loud as I could, in several languages, and he often laid his car within two yards of me, but all in vain, for we were wholly unintelligible to each other. He then sent his servants to their work, and taking his handkerchief out of his pocket, he doubled and spread it on his left hand, which he placed flat on the ground, with the palm upwards, making me a sign to step into it, as I could easily do, for it was not above a foot in thickness. I thought it my part to obey, and for fear of falling, laid myself at length upon the handkerchief, with the remainder of which he lapped me up to the head for further security, and in this manner carried me home to his house. There he called his wife, and showed me to her; but she screamed and ran back, as women in England do at the sight of a toad or a spider. However, when she had a while seen my behavior, and how well I observed the signs her husband made, she was soon reconciled, and by degrees grew extremely tender of me. It was about twelve at noon, and a servant brought in dinner. It was only one substantial dish of meat (fit for the plain condition of an husbandman) in a dish of about twenty-four feet in diameter. The company were the farmer and his wife, three children, and an old grandmother. When they sat down, the farmer placed me at some distance from him on the table, which was thirty feet high from the floor. I was in a terrible fright, and kept as far as I could from the edge for fear of falling. The wife minced a bit of meat, then crumbled some bread on a trencher, and placed it before me. I made her a low bow, took out my knife and fork, and fell to eating, which gave them exceeding delight. The mistress sent her maid for a small dram cup, which held about three gallons, and filled it with drink; I took up the vessel with much difficulty in both hands, and in a most respectful manner drank to her ladyship's health, expressing the words as loud as I could in English, which made the company laugh so heartily, that I was almost deafened with the noise. This liquor tasted like a small cider, and was not unpleasant. Then the master made me a sign to come to his trencher side; but as I walked on the table, being in great surprise all the time, as the indulgent reader will easily conceive and excuse, I happened to stumble against a crust, and fell flat on my face, but received no hurt. I got up immediately, and observing the good people to be in much concern, I took my hat (which I held under my arm out of good manners) and waving it over my head, made three huzzas, to show I had gotten no mischief by my fall. But advancing forwards toward my master (as I shall henceforth call him), his youngest son who sat next him, an arch boy of about ten years old, took me up by the legs, and held me so high in the air, that I trembled every limb; but his father snatched me from him, and at the same time gave him such a box on the left ear, as would have felled an European troop of horse to the earth, ordering him to be taken from the table. But being afraid the boy might owe me a spite, and well remembering how mischievous all children among us naturally are to sparrows, rabbits, young kittens, and puppy dogs, I fell on my knees, and pointing to the boy, made my master to understand, as well as I could, that I desired his son might be pardoned. The father complied, and the lad took his seat again; whereupon I went to him and kissed his hand, which my master took, and made him stroke me gently with it. In the midst of dinner, my mistress' favorite cat leaped into her lap. I heard a noise behind me like that of a dozen stocking-weavers at work; and turning my head, I found it proceeded from the purring of this animal, who seemed to be three times larger than an ox, as I computed by the view of her head, and one of her paws, while her mistress was feeding and stroking her. The fierceness of this creature's countenance altogether discomposed me; though I stood at the farther end of the table, above fifty feet off and although my mistress held her fast for fear she might give a spring, and seize me in her talons. But it happened there was no danger; for the cat took not the least notice of me when my master placed me within three yards of her. And as I have been always told, and found true by experience in my travels, that flying, or discovering fear way to make it pursue or attack you, so I resolved in this dangerous juncture to show no manner of concern. I walked with intrepidity five or six times before the very head of the cat, and came within half a yard of her; whereupon she drew herself back, as if she were more afraid of me: I had less apprehension concerning the dogs, whereof three or four came into the room, as it is usual in farmers' houses; one of which was a mastiff, equal in bulk to four elephants, and a greyhound, somewhat taller than the mastiff, but not so large. {P_2|CH_1 ^paragraph 10} When dinner was almost done, the nurse came in with a Child of a year old in her arms, who immediately spied me, and began a squall that you might have heard from London Bridge to Chelsea, after the usual oratory of infants, to get me for a plaything. The mother out of pure indulgence took me up, and put me towards the child, who presently seized me by the middle, and got my head in his mouth, where I roared so loud that the urchin was frightened, and let me drop; and I should infallibly have broken my neck if the mother had not held her apron under me. The nurse to quiet her babe made use of a rattle, which was a kind of hollow vessel filled with great stones, and fastened by a cable to the childs waist: but all in vain, so that she was forced to apply the last remedy by giving it suck. I must confess no object ever disgusted me so much as the sight of her monstrous breast, which I cannot tell what to compare with, so as to give the curious reader an idea of its bulk, shape and color. It stood prominent six feet, and could not be less than sixteen in circumference. The nipple was about half the size of my head, and the hue both of that and the dug so varified with spots, pimples and freckles, that nothing could appear more nauseous: for I had a near sight of her, she sitting down the more conveniently to give suck, and I standing on the table. This made me reflect upon the fair skins of our English ladies, who appear so beautiful to us, only because they are of our own size, and their defects not to be seen but through a magnifying glass, where we find by experiment that the smoothest and whitest skins look rough and coarse, and ill colored. I remember when I was at Lilliput, the complexion of those diminutive people appeared to me the fairest in the world; and talking upon this subject with a person of learning there, who was an intimate friend of mine, he said that my face appeared much fairer and smoother when he looked on me from the ground, than it did upon a nearer view when I took him up in my hand and brought him close, which he confessed was at first a very shocking sight. He said he could discover great holes in my skin; that the stumps of my beard were ten times stronger than the bristles of a boar, and my complexion made up of several colors altogether disagreeable: although I must beg leave to say for myself, that I am as fair as most of my sex and country, and very little sunburned by all my travels. On the other side, discoursing of the ladies in that Emperor's court, he used to tell me, one had freckles, another too wide a mouth, a third too large a nose, nothing of which I was able to distinguish. I confess this reflection was obvious enough; which however I could not forbear, lest the reader might think those vast creatures were actually deformed: for I must do them justice to say they are a comely race of people; and particularly the features of my master's countenance, although he were but a farmer, when I beheld him from the height of sixty feet, appeared very well proportioned. When dinner was done, my master went out to his laborers, and as I could discover by his voice and gesture, gave his wife a strict charge to take care of me. I was very much tired, and disposed to sleep, which my mistress perceiving, she put me on her own bed, and covered me with a clean white handkerchief, larger and coarser than the mainsail of a man of war. I slept about two hours, and dreamed I was at home with my wife and children, which aggravated my sorrows when I awakened and found myself alone in a vast room, between two and three hundred feet wide, and above two hundred high, lying in a bed twenty yards wide. My mistress was gone about her household affairs, and had locked me in. The bed was eight yards from the floor. Some natural necessities required me to get down; I dare not presume to call, and if I had, it would have been in vain, with such a voice as mine, at so great a distance from the room where I lay to the kitchen where the family kept. While I was under these circumstances, two rats crept up the curtains, and ran smelling backwards and forwards on the bed. One of them came up almost to my face, whereupon I rose in a fright, and drew out my hanger to defend myself. These horrible animals had the boldness to attack me on both sides, and one of them held his fore-feet at my collar; but I had the good fortune to rip up his belly before he could do me any mischief. He fell down at my feet, and the other, seeing the fate of his comrade, made his escape, but not without one good wound on the back, which I gave him as he fled, and made the blood run trickling from him. After this exploit, I walked gently to and fro on the bed, to recover my breath and loss of spirits. These creatures were of the size of a large mastiff, but infinitely more nimble and fierce, so that if I had taken off my belt before I went to sleep, I must have infallibly been torn to pieces and devoured. I measured the tail of the dead rat, and found it to be two yards long, wanting an inch; but it went against my stomach to drag the carcass off the bed, where it lay still bleeding; I observed it had yet some life, but with a strong slash cross the neck, I thoroughly dispatched it. Soon after my mistress came into the room, who seeing me all bloody, ran and took me up in her hand. I pointed to the dead rat, smiling and making other signs to show I was not hurt, whereat she was extremely rejoiced, calling the maid to take up the dead rat with a pair of tongs, and throw it out of the window. Then she set me on a table, where I showed her my hanger all bloody, and wiping it on the lappet of my coat, returned it to the scabbard. I was pressed to do more than one thing, which another could not do for me, and therefore endeavored to make my mistress understand that I desired to be set down on the floor; which after she had done, my bashfulness would not suffer me to express myself farther than by pointing to the door, and bowing several times. The good woman with much difficulty at last perceived what I would be at, and taking me up again in her hand, walked into the garden, where she set me down. I went on one side about two hundred yards, and beckoning to her not to look or to follow me, I hid myself between two leaves of sorrel and there discharged the necessities of nature. {P_2|CH_1 ^paragraph 15} I hope the gentle reader will excuse me for dwelling on these and the like particulars, which however insignificant they may appear to grovelling vulgar minds, yet will certainly help a philosopher to enlarge his thoughts and imagination, and apply them to the benefit of public as well as private life, which was my sole design in presenting this and other accounts of my travels to the world; wherein I have been chiefly studious of truth, without affecting any ornaments of learning or of style. But the whole scene of this voyage made so strong an impression on my mind, and is so deeply fixed in my memory, that in committing it to paper I did not omit one material circumstance: however, upon a strict review, I blotted out several passages of less moment which were in my first copy, for fear of being censured as tedious and trifling, whereof travelers are often, perhaps not without justice, accused. P_2|CH_2 CHAPTER II - My mistress had a daughter of nine years old, a child of forward parts for her age, very dextrous at her needle, and skillful in dressing her baby. Her mother and she contrived to fit up the baby's cradle for me against night: the cradle was put into a small drawer of a cabinet, and the drawer placed upon a hanging shelf for fear of the rats. This was my bed all the time I stayed with those people, though made more convenient by degrees, as I began to learn their language, and make my wants known. This young girl was so handy, that after I had once or twice pulled off my clothes before her, she was able to dress and undress me, though I never gave her that trouble when she would let me do either myself. She made me seven shirts, and some other linen, of as fine cloth as could be got, which indeed was coarser than sackcloth; and these she constantly washed for me with her own hands. She was likewise my school mistress to teach me the language: when I pointed to anything, she told me the name of it in her own tongue, so that in a few days I was able to call for whatever I had a mind to. She was very good-natured, and not above forty feet high, being little for her age. She gave me the name of Grildrig, which the family took up, and afterwards the whole kingdom. The word imports what the Latins call nanunculus the Italians homunceletino, and the English mannikin. To her I chiefly owe my preservation in that country: we never parted while I was there; I called her my Glumdalclitch, or little nurse: and I should be guilty of great ingratitude if I omitted this honorable mention of her care and affection towards me, which I heartily wish it lay in my power to requite as she deserves, instead of being the innocent but unhappy instrument of her disgrace, as I have too much reason to fear. It now began to be known and talked of in the neighborhood, that my master had found a strange animal in the field, about the bigness of a splacknuck, but exactly shaped in every part like a human creature; which it likewise imitated in all its actions; seemed to speak in a little language of its own, had already learned several words of theirs, went erect upon two legs, was tame and gentle, would come when it was called, do whatever it was bid, had the finest limbs in the world, and a complexion fairer than a nobleman's daughter of three years old. Another farmer who lived hard by, and was a particular friend of my master, came on a visit on purpose to inquire into the truth of this story. I was immediately produced, and placed upon a table, where I walked as I was commanded, drew my hanger, put it up again, made my reverence to my master's guest, asked him in his own language how he did, and told him he was welcome, just as my little nurse had instructed me. This man, who was old and dim-sighted, put on his spectacles to behold me better, at which I could not forbear laughing very heartily, for his eyes appeared like the full moon shining into a chamber at two windows. Our people, who discovered the cause of my mirth, bore me company in laughing, at which the old fellow was fool enough to be angry and out of countenance. He had the character of a great miser, and to my misfortune he well deserved it, by the cursed advice he gave my master to show me as a sight upon a market day in the next town, which was half an hour's riding, about twenty-two miles from our house. I guessed there was some mischief contriving, when I observed my master and his friend whispering long together, sometimes pointing at me; and my fears made me fancy that I overheard and understood some of their words. But the next morning Glumdalclitch, my little nurse, told me the whole matter, which she had cunningly picked out from her mother. The poor girl laid me on her bosom, and fell weeping with shame and grief. She apprehended some mischief would happen to me from rude vulgar folks, who might squeeze me to death, or break one of my limbs by taking me in their hands. She had also observed how modest I was in my nature, how nicely I regarded my honor, and what an indignity I should conceive it to be exposed for money as a public spectacle to the meanest of the people. She said, her papa and mamma had promised that Grildrig should be hers, but now she found they meant to serve her as they did last year, when they pretended to give her a lamb, and yet, as soon as it was fat, sold it to a butcher. For my own part, I may truly affirm that I was less concerned than my nurse. I had a strong hope which never left me, that I should one day recover my liberty; and as to the ignominy of being carried about for a monster, I considered myself to be a perfect stranger in the country, and that such a misfortune could never be charged upon me as a reproach, if ever I should return to England; since the King of Great Britain himself, in my condition, must have undergone the same distress. My master, pursuant to the advice of his friend, carried me in a box the next market day to the neighboring town, and took along with him his little daughter, my nurse, upon a pillion behind him. The box was close on every side, with a little door for me to go in and out, and a few gimlet holes to let in air. The girl had been so careful to put the quilt of her baby's bed into it, for me to lie down on. However, I was terribly shaken and discomposed in this journey, though it were but of half an hour. For the horse went about forty feet at every step, and trotted so high, that the agitation was equal to the rising and falling of a ship in a great storm, but much more frequent. Our journey was somewhat further than from London to St. Albans. My master alighted at an inn which he used to frequent; and after consulting a while with the inn-keeper, and making some necessary preparations, he hired the Grultrud, or crier, to give notice through the town of a strange creature to be seen at the Sign of the Green Eagle, not so big as a splacknuck (an animal in that country very finely shaped, about six foot long) and in every part of the body resembling a human creature, could speak several words, and perform a hundred diverting tricks. I was placed upon a table in the largest room of the inn, which might be near three hundred feet square. My little nurse stood on a low stool close to the table, to take care of me, and direct what I should do. My master, to avoid a crowd, would suffer only thirty people at a time to see me. I walked about on the table as the girl commanded: she asked me questions as far as she knew my understanding of the language reached, and I answered them as loud as I could. I turned about several times to the company, paid my humble respects, said they were welcome, and used some other speeches I had been taught. I took up a thimble filled with liquor, which Glumdalclitch had given me for a cup, and drank their health. I drew out my hanger, and flourished it after the manner of fencers in England. My nurse gave me part of a straw, which I exercised as a pike, having learned the art in my youth. I was that day shown to twelve sets of company, and as often forced to go over again with the same fopperies, till I was half dead with weariness and vexation. For those who had seen me made such wonderful reports, that the people were ready to break down the doors to come in. My master for his own interest would not suffer any one to touch me except my nurse; and, to prevent danger, benches were set around the table at such a distance as put me out of everybody's reach. However, an unlucky schoolboy aimed a hazel nut directly at my head, which very narrowly missed me; otherwise, it came with so much violence, that it would have infallibly knocked out my brains, for it was almost as large as a small pumpion: but I had the satisfaction to see the young rogue well beaten, and turned out of the room. My master gave public notice that he would show me again the next market day, and in the meantime he prepared a more convenient vehicle for me, which he had reason enough to do; for I was so tired with my first journey, and with entertaining company for eight hours together, that I could hardly stand upon my legs or speak a word. It was at least three days before I recovered my strength; and that I might have no rest at home, all the neighboring gentlemen from a hundred miles around, hearing of my fame, came to see me at my master's own house. There could not be fewer than thirty persons with their wives and children (for the country is very populous); and my master demanded the rate of a full room whenever he showed me at home, although it were only to a single family; so that for some time I had but little ease every day of the week (except Wednesday, which is their Sabbath) although I were not carried to the town. {P_2|CH_2 ^paragraph 5} My master, finding how profitable I was likely to be, resolved to carry me to the most considerable cities of the kingdom. Having therefore provided himself with all things necessary for a long journey, and settled his affairs at home, he took leave of his wife, and upon the 17th of August, 1703, about two months after my arrival, we set out for the metropolis, situated near the middle of that empire, and about three thousand miles distance from our house. My master made his daughter Glumdalclitch ride behind him. She carried me on her lap in a box tied about her waist. The girl had lined it on all sides with the softest cloth she could get, well quilted underneath, furnished it with her baby's bed, provided me with linen and other necessaries, and made everything as convenient as she could. We had no other company but a boy of the house, who rode after us with the luggage. My master's design was to show me in all the towns by the way, and to step out of the road for fifty or a hundred miles, to any village or person of quality's house where he might expect custom. We made easy journeys of not above seven or eight score miles a day: for Glumdalclitch, on purpose to spare me, complained she was tired with the trotting of the horse. She often took me out of my box at my own desire, to give me air and show me the country, but always held me fast by a leading string. We passed over five or six rivers many degrees broader and deeper than the Nile or the Ganges; and there was hardly a rivulet so small as the Thames at London Bridge. We were ten weeks in our journey, and I was shown in eighteen large towns besides many villages and private families. On the 26th day of October, we arrived at the metropolis, called in their language Lorbrulgrud, or Pride of the Universe. My master took a lodging in the principal street of the city, not far from the royal palace, and put out bills in the usual form, containing an exact description of my person and parts. He hired a large room between three and four hundred feet wide. He provided a table sixty feet in diameter, upon which I was to act my part, and palisadoed it around three feet from the edge, and as many high, to prevent my falling over. I was shown ten times a day to the wonder and satisfaction of all people. I could now speak the language tolerably well, and perfectly understood every word that was spoken to me. Besides, I had learned their alphabet, and could make a shift to explain a sentence here and there; for Glumdalclitch had been my instructor while we were at home, and at leisure hours during our journey. She carried a little book in her pocket, not much larger than a Sanson's Atlas; it was a common treatise for the use of young girls, giving a short account of their religion: out of this she taught me my letters, and interpreted the words. P_2|CH_3 CHAPTER III - The frequent labors I underwent every day made in a few weeks a very considerable change in my health: the more my master got by me, the more unsatiable he grew. I had quite lost my stomach, and was almost reduced to a skeleton. The farmer observed it, and concluding I soon must die, resolved to make as good a hand of me as he could. While he was thus reasoning and resolving with himself, a Slardral, or Gentleman Usher, came from court, commanding my master to carry me immediately thither for the diversion of the Queen and her ladies. Some of the latter had already been to see me, and reported strange things of my beauty, behavior, and good sense. Her Majesty and those who attended her were beyond measure delighted with my demeanor. I fell on my knees, and begged the honor of kissing her Imperial foot; but this gracious princess held out her little finger towards me (after I was set on a table) which I embraced in both my arms, and put the tip of it with the utmost respect to my lip. She made me some general questions about my country and my travels, which I answered as distinctly and in as few words as I could. She asked whether I would be content to live at court. I bowed down to the board of the table, and humbly answered, that I was my master's slave, but if I were at my own disposal, I should be proud to devote my life to her Majesty's service. She then asked my master whether he were willing to sell me at a good price. He, who apprehended I could not live a month, was ready enough to part with me, and demanded a thousand pieces of gold, which were ordered him on the spot, each piece being about the bigness of eight hundred moidores; but, allowing for the proportion of all things between that country and Europe, and the high price of gold among them, was hardly so great a sum as a thousand guineas would be in England. I then said to the Queen, since I was now her Majesty's most humble creature and vassal, I must beg the favor, that Glumdalclitch, who had always tended me with so much care and kindness, and understood to do it so well, might be admitted into her service, and continue to be my nurse and instructor. Her Majesty agreed to my petition, and easily got the farmer's consent, who was glad enough to have his daughter preferred at court: and the poor girl herself was not able to hide her joy. My late master withdrew, bidding me farewell, and saying he had left me in a good service; to which I replied not a word, only making him a slight bow. The Queen observed my coldness, and when the farmer was gone out of the apartment, asked me the reason. I made bold to tell her Majesty that I owed no other obligation to my late master, than his not dashing out the brains of a poor harmless creature found by chance in his field; which obligation was amply recompensed by the gain he had made in showing me through half the kingdom, and the price he had now sold me for. That the life I had since led was laborious enough to kill an animal of ten times my strength. That my health was much impaired by the continual drudgery of entertaining the rabble every hour of the day, and that if my master had not thought my life in danger, her Majesty perhaps would not have got so cheap a bargain. But as I was out of all fear of being ill treated under the protection of so great and good an Empress, the Ornament of Nature, the Darling of the World, the Delight of her Subjects, the Phoenix of the Creation; so I hoped my late master's apprehensions would appear to be groundless, for I already found my spirits to revive by the influence of her most august presence. This was the sum of my speech, delivered with great improprieties and hesitation; the latter part was altogether framed in the style peculiar to that people, whereof I learned some phrases from Glumdalclitch, while she was carrying me to court. The Queen giving great allowance for my defectiveness in speaking, was however surprised at so much wit and good sense in so diminutive an animal. She took me in her own hand, and carried me to the King, who was then retired to his cabinet. His Majesty, a prince of much gravity, and austere countenance, not well observing my shape at first view, asked the Queen after a cold manner, how long it was since she grew fond of a splacknuck; for such it seems he took me to be, as I lay upon my breast in her Majesty's right hand. But this princess, who has an infinite deal of wit and humor, set me gently on my feet upon the scrutore, and commanded me to give his Majesty an account of myself, which I did in a very few words; and Glumdalclitch, who attended at the cabinet door, and could not endure I should be out of her sight, being admitted, confirmed all that had passed from my arrival at her father's house. The King, although he be as learned a person as any in his dominions, and had been educated in the study of philosophy, and particularly mathematics; yet when he observed my shape exactly, and saw me walk erect, before I began to speak, conceived I might be a piece of clockwork (which is in that country arrived to a very great perfection), contrived by some ingenious artist. But when he heard my voice, and found what I delivered to be regular and rational, he could not conceal his astonishment. He was by no means satisfied with the relation I gave him of the manner I came into his kingdom, but thought it a story concerted between Glumdalclitch and her father, who had taught me a set of words to make me sell at a higher price. Upon this imagination he put several other questions to me, and still received rational answers, no otherwise defective than by a foreign accent, and an imperfect knowledge in the language, with some rustic phrases which I had learned at the farmer's house, and did not suit the polite style of a court. {P_2|CH_3 ^paragraph 5} His Majesty sent for three great scholars who were then in their weekly waiting, according to the custom in that country. These gentlemen, after they had awhile examined my shape with much nicety, were of different opinions concerning me. They all agreed that I could not be produced according to the regular laws of nature, because I was not framed with a capacity of preserving my life, either by swiftness, or climbing of trees, or digging holes in the earth. They observed by my teeth, which they viewed with great exactness, that I was a carnivorous animal; yet most quadrupeds being an overmatch for me, and field mice, with some others, too nimble, they could not imagine how I should be able to support myself, unless I fed upon snails and other insects, which they offered, by many learned arguments, to evince that I could not possibly do. One of these virtuosi seemed to think that I might be an embryo, or abortive birth. But this opinion was rejected by the other two, who observed my limbs to be perfect and finished, and that I had lived several years, as it was manifested from my beard, the stumps whereof they plainly discovered through a magnifying glass. They would not allow me to be a dwarf, because my littleness was beyond all degrees of comparison; for the Queen's favorite dwarf, the smallest ever known in that kingdom, was nearly thirty feet high. After much debate, they concluded unanimously that I was only relplum scalcath, which is interpreted literally, lusus naturae; a determination exactly agreeable to the modern philosophy of Europe, whose professors, disdaining the old evasion of occult causes, whereby the followers of Artistotle endeavor in vain to disguise their ignorance, have invented this wonderful solution of all difficulties, to the unspeakable advancement of human knowledge. After this decisive conclusion, I entreated to be heard a word or two. I applied myself to the King, and assured his Majesty, that I came from a country which abounded with several millions of both sexes, and of my own stature; where the animals, trees, and houses were all in proportion, and where by consequence I might be as able to defend myself, and to find sustenance, as any of his Majesty's subjects could do here; which I took for a full answer to those gentlemen's arguments. To this they only replied with a smile of contempt, saying that the farmer had instructed me very well in my lesson. The King, who had a much better understanding, dismissing his learned men, sent for the farmer, who by good fortune was not yet gone out of town. Having therefore first examined him privately, and then confronted him with me and the young girl, his Majesty began to think that what we told him might possibly be true. He desired the Queen to order that a particular care should be taken of me, and was of opinion that Glumdalclitch should still continue in her office of tending me, because he observed we had a great affection for each other. A convenient apartment was provided for her at court; she had a sort of governess appointed to take care of her education, a maid to dress her, and two other servants for menial offices; but the care of me was wholly appropriated to herself. The Queen commanded her own cabinet maker to contrive a box that might serve me for a bedchamber, after the model that Glumdalclitch and I should agree upon. This man was a most ingenious artist, and according to my directions, in three weeks finished for me a wooden chamber of sixteen feet square, and twelve high, with sash windows, a door, and two closets, like a London bedchamber. The board that made the ceiling was to be lifted up and down by two hinges, to put in a bed ready furnished by her Majesty's upholsterer, which Glumdalclitch took out every day to air, made it with her own hands, and letting it down at night, locked up the roof over me. A nice workman, who was famous for little curiosities, undertook to make me two chairs, with backs and frames, of a substance not unlike ivory, and two tables, with a cabinet to put my things in. The room was quilted on all sides, as well as the floor and the ceiling, to prevent any accident from the carelessness of those who carried me, and to break the force of a jolt when I went in a coach. I desired a lock for my door, to prevent rats and mice from coming in: the smith, after several attempts, made the smallest that ever was seen among them, for I have known a larger at the gate of a gentleman's house in England. I made a shift to keep the key in a pocket of my own, fearing Glumdalclitch might lose it. The Queen likewise ordered the thinnest silks that could be gotten, to make me clothes, not much thicker than an English blanket, very cumbersome till I was accustomed to them. They were after the fashion of the kingdom, partly resembling the Persian, and partly the Chinese, and are a very grave and decent habit. The Queen became so fond of my company, that she could not dine without me. I had a table placed upon the same at which her Majesty ate, just at her left elbow, and a chair to sit on. Glumdalclitch stood upon a stool on the floor, near my table, to assist and take care of me. I had an entire set of silver dishes and plates, and other necessaries, which, in proportion to those of the Queen, were not much bigger than what I have seen of the same kind in a London toy shop, for the furniture of a babyhouse: these my little nurse kept in her pocket in a silver box, and gave me at meals as I wanted them, always cleaning them herself. No person dined with the Queen but the two Princesses Royal, the elder sixteen years old, and the younger at that time thirteen and a month. Her Majesty used to put a bit of meat upon one of my dishes, out of which I carved for myself, and her diversion was to see me eat in miniature. For the Queen (who had indeed but a weak stomach) took up at one mouthful as much as a dozen English farmers could eat at a meal, which to me was for some time a very nauseous sight. She would crunch the wing of a lark, bones and all, between her teeth, although it were nine times as large as that of a full grown turkey; and put a bit of bread into her mouth, as big as two twelve-penny loaves. She drank out of a golden cup, above a hogshead at a draught. Her knives were twice as long as a scythe set straight upon the handle. The spoons, forks, and other instruments were all in the same proportion. I remember when Glumdalclitch carried me out of curiosity to see some of the tables at court, where ten or a dozen of these enormous knives and forks were lifted up together, I thought I had never till then beheld so terrible a sight. It is the custom that every Wednesday (which, as I have before observed, was their Sabbath) the King and Queen, with the royal issue of both sexes, dine together in the apartment of his Majesty, to whom I was now become a great favorite; and at these times my little chair and table were placed at his left hand, before one of the salt cellars. This prince took a pleasure in conversing with me, inquiring into the manners, religion, laws, government, and learning of Europe; wherein I gave him the best account I was able. His apprehension was so clear, and his judgment so exact, that he made very wise reflections and observations upon all I said. But, I confess, that after I had been a little too copious in talking of my own beloved country, of our trade, and wars by sea and land, of our schisms in religion, and parties in the state, the prejudices of his education prevailed so far, that he could not forbear taking me up in his right hand, and stroking me gently with the other, after an hearty fit of laughing, asked me whether I were a Whig or a Tory. Then turning to his first minister, who waited behind him with a white staff, near as tall as the mainmast of the Royal Sovereign, he observed how contemptible a thing was human grandeur, which could be mimicked by such diminutive insects as I: and yet, said he, I dare engage, these creatures have their titles and distinctions of honor, they contrive little nests and burrows, that they call houses and cities; they make a figure in dress and equipage; they love, they fight, they dispute, they cheat, they betray. And thus he continued on, while my color came and went several times with indignation to hear our noble country, the mistress of arts and arms, the scourge of France, the arbitress of Europe, the seat of virtue, piety, honor and truth, the pride and envy of the world, contemptuously treated. But as I was not in a condition to resent injuries, so, upon mature thoughts, I began to doubt whether I were injured or not. For, after having been accustomed several months to the sight and converse of this people, and observed every object upon which I cast my eyes to be of proportionable magnitude, the horror I had first conceived from their bulk and aspect was so far worn off, that if I had then beheld a company of English lords and ladies in their finery and best day clothes, acting their several parts in the most courtly manner, of strutting, and bowing, and prating, to say the truth, I should have been strongly tempted to laugh as much at them as the King and his grandees did at me. Neither indeed could I forbear smiling at myself, when the Queen used to place me upon her hand towards a looking glass, by which both our persons appeared before me in full view together! and there could be nothing more ridiculous than the comparison; so that I really began to imagine myself dwindled many degrees below my usual size. {P_2|CH_3 ^paragraph 10} Nothing angered and mortified me so much as the Queen's dwarf, who being of the lowest stature that was ever that country (for I verily think he was not thirty feet high) became insolent at seeing a creature so much beneath him, that he would always affect to swagger and look big as he passed by me in the Queen's antechamber, while I was standing on some table talking with the lords or ladies of the court, and he seldom failed of a smart word or two upon my littleness; against which I could only revenge myself by calling him brother, challenging him to wrestle, and such repartees as are usual in the mouths of court pages. One day at dinner this malicious little cub was so nettled with something I had said to him, that raising himself upon the frame of her Majesty's chair, he took me up by the middle, as I was sitting down, not thinking any harm, and let me drop into a large silver bowl of cream, and then ran away as fast as he could. I fell over head and ears, and if I had not been a good swimmer, it might have gone very hard with me; for Glumdalclitch in that instant happened to be at the other end of the room, and the Queen was in such a fright that she wanted presence of mind to assist me. But my little nurse ran to my relief, and took me out, after I had swallowed above a quart of cream. I was put to bed; however, I received no other damage than the loss of a suit of clothes, which was utterly spoiled. The dwarf was soundly whipped, and as a farther punishment, forced to drink up the bowl of cream, into which he had thrown me; neither was he ever restored to favor; for soon after the Queen bestowed him to a lady of high quality, so that I saw him no more, to my very great satisfaction; for I could not tell to what extremity such a malicious urchin might have carried his resentment. He had before served me a scurvy trick, which set the Queen a laughing, although at the same time she was heartily vexed, and would have immediately cashiered him, if I had not been so generous as to intercede. Her Majesty had taken a marrow bone upon her plate, and after knocking out the marrow, placed the bone again in the dish erect as it stood before; the dwarf watching his opportunity, while Glumdalclitch was gone to the sideboard, mounted upon the stool she stood on to take care of me at meals, took me up in both hands, and squeezing my legs together, wedged them into the marrow bone above my waist, where I stuck for some time, and made a very ridiculous figure. I believe it was near a minute before any one knew what was become of me, for I thought it below me to cry out. But, as princes seldom get their meat hot, my legs were not scalded, only my stockings and breeches in a sad condition. The dwarf at my entreaty had no other punishment than a sound whipping. I was frequently rallied by the Queen upon account of my fearfulness, and she used to ask me whether the people of ray country were as great cowards as myself. The occasion was this. The kingdom is much pestered with flies in summer; and these odious insects, each of them as big as a Dunstable lark, hardly gave me any rest while I sat at dinner, with their continual humming and buzzing about my ears. They would sometimes alight upon my victuals; and leave their loathsome excrement or spawn behind, which to me was very visible, though not to the natives of that country, whose large optics were not so acute as mine in viewing smaller objects. Sometimes they would fix upon my nose or forehead, where they stung me to the quick, smelling very offensively, and I could easily trace that viscous matter, which our naturalists tell us enables those creatures to walk with their feet upwards upon a ceiling. I had much ado to defend myself against these detestable animals, and could not forbear starting when they came on my face. It was the common practice of the dwarf to catch a number of these insects in his hand, as schoolboys do among us, and let them out suddenly under my nose, on purpose to frighten me, and divert the Queen. My remedy was to cut them in pieces with my knife as they flew in the air, wherein my dexterity was much admired. I remember one morning when Glumdalclitch had set me in my box upon a window, as she usually did in fair days to give me air (for I dared not venture to let the box be hung on a nail out of the window, as we do with cages in England) after I had lifted up one of my sashes, and sat down at my table to eat a piece of sweet cake for my breakfast, above twenty wasps, allured by the smell, came flying into the room, humming louder than the drones of as many bagpipes. Some of them seized my cake, and carried it piecemeal away, others flew about my head and face, confounding me with the noise, and putting me in the utmost terror of their stings. However I had the courage to rise and draw my hanger, and attack them in the air. I dispatched four of them, but the rest got away, and I presently shut my window. These insects were as large as partridges: I took out their stings, found them an inch and a half long, and as sharp as needles. I carefully preserved them all, and having since shown them with some other curiosities in several parts of Europe, upon my return to England I gave three of them to Gresham College, and kept the fourth for myself. P_2|CH_4 CHAPTER IV - I now intend to give the reader a short description of this country, as far as I traveled in it, which was not above two thousand miles round Lorbrulgrud, the metropolis. For the Queen, whom I always attended, never went further when she accompanied the King in his progresses, and there stayed until his Majesty returned from viewing his frontiers. The whole extent of this prince's dominions reaches about six thousand miles in length, and from three to five in breadth. From whence I cannot but conclude that our geographers of Europe are in a great error, by supposing nothing but sea between Japan and California; for it was ever my opinion, that there must be a balance of earth to counterpoise the great continent of Tartary; and therefore they ought to correct their maps and charts, by joining this vast tract of land to the northwest parts of America, wherein I shall be ready to lend them my assistance. The kingdom is a peninsula, terminated to the northeast by a ridge of mountains thirty miles high, which are altogether impassable by reason of the volcanoes upon the tops. Neither do the most learned know what sort of mortals inhabit beyond those mountains, or whether they be inhabited at all. On the three other sides it is bounded by the ocean. There is not one seaport in the whole kingdom, and those parts of the coasts into which the rivers issue are so full of pointed rocks, and the sea generally so rough, that there is no venturing with the smallest of their boats, so that these people are wholly excluded from any commerce with the rest of the world. But the large rivers are full of vessels, and abound with excellent fish, for they seldom get any from the sea because the sea fish are of the same size with those in Europe, and consequently not worth catching; whereby it is manifest, that nature, in the production of plants and animals of so extraordinary a bulk, is wholly confined to this continent, of which I leave the reasons to be determined by philosophers. However, now and then they take a whale that happens to be dashed against the rocks, which the common people feed on heartily. These whales I have known so large that a man could hardly carry one upon his shoulders; and sometimes for curiosity they are brought in hampers to Lorbrulgrud: I saw one of them in a dish at the King's table, which passed for a rarity, but I did not observe he was fond of it; for I think indeed the bigness disgusted him, although I have seen one somewhat larger in Greenland. The country is well inhabited, for it contains fifty-one cities, near a hundred walled towns, and a great number of villages. To satisfy my curious reader, it may be sufficient to describe Lorbrulgrud. This city stands upon almost two equal parts on each side the river that passes through. It contains above eighty thousand houses, and about six hundred thousand inhabitants. It is in length three glonglungs (which make about fifty-four English miles) and two and a half in breadth, as I measured it myself in the royal map made by the King's order, which was laid on the ground on purpose for me, and extended a hundred feet; I paced the diameter and circumference several times barefoot, and computing by the scale, measured it pretty exactly. The King's palace is no regular edifice, but a heap of building about seven miles around: the chief rooms are generally two hundred and forty feet high, and broad and long proportion. A coach was allowed to Glumdalclitch and me, wherein her governess frequently took her out to see the town, or go among the shops; and I was always of the party, carried in my box; although the girl at my own desire would often take me out, and hold me in her hand, that I might more conveniently view the houses and the people, as we passed along the streets. I reckoned our coach to be about a square of Westminster Hall, but not altogether so high; however, I cannot be very exact. One day the governess ordered our coachman to stop at several shops, where the beggars, watching their opportunity, crowded to the sides of the coach, and gave me the most horrible spectacles that ever an English eye beheld. There was a woman with a cancer in her breast, swelled to a monstrous size, full of holes, in two or three of which I could have easily crept, and covered my whole body. There was a fellow with a wen in his neck, larger than five wool-packs, and another with a couple of wooden legs, each about twenty feet high. But the most hateful sight of all was the lice crawling on their clothes. I could see distinctly the limbs of these vermin with my naked eye, much better than those of an European louse through a microscope, and their snouts with which they rooted like swine. They were the first I had ever beheld, and I should have been curious enough to dissect one of them, if I had proper instruments (which I unluckily left behind me in the shop) although indeed the sight was so nauseous, that it perfectly turned my stomach. Besides the large box in which I was usually carried, the Queen ordered a smaller one to be made for me, of about twelve feet square, and ten high, for the convenience of traveling, because the other was somewhat too large for Glumdalclitch's lap, and cumbersome in the coach; it was made by the same artist, whom I directed in the whole contrivance. This traveling closet was an exact square with a window in the middle of three of the squares, and each window was latticed with iron wire on the outside, to prevent accidents in long journeys. On the fourth side, which had no window, two strong staples were fixed, through which the person that carried me, when I had a mind to be on horseback, put in a leathern belt, and buckled it about his waist. This was always the office of some grave trusty servant in whom I could confide, whether I attended the King and Queen in their progresses, or were disposed to see the gardens, or pay a visit to some great lady or minister of state in the court, when Glumdalclitch happened to be out of order: for I soon began to be known and esteemed among the greatest officers, I suppose more upon account of their Majesties' favor, than any merit of my own. In journeys, when I was weary of the coach, a servant on horseback would buckle my box, and place it on a cushion before him; and there I had a full prospect of the country on three sides from my three windows. I had in this closet a field bed and a hung from the ceiling, two chairs and a table, neatly screwed to the floor, to prevent being tossed about by the agitation of the horse or the coach. And having been long used to sea voyages, those motions, although sometimes very violent, did not much discompose me. {P_2|CH_4 ^paragraph 5} Whenever I had a mind to see the town, it was always in my traveling closet, which Glumdalclitch held in her lap in a kind of open sedan, after the fashion of the country, borne by four men, and attended by two others in the Queen's livery. The people who had often heard of me, were very curious to crowd about the sedan, and the girl was complaisant enough to make the bearers stop, and to take me in her hand that I might be more conveniently seen. I was very desirous to see the chief temple, and particularly the tower belonging to it, which is reckoned the highest in the kingdom. Accordingly, one day my nurse carried me thither, but I may truly say I came back disappointed; for height is not above three thousand feet, reckoning from the ground to the highest pinnacle top; which allowing for the difference between the size of those people and us in Europe, is no great matter for admiration, nor at all equal in proportion (if I rightly remember) to Salisbury steeple. But, not to detract from a nation to which during my life I shall acknowledge myself extremely obliged, it must be allowed that whatever this famous tower wants in height is amply made up in beauty and strength. For the walls are near a hundred feet thick, built of hewn stone, whereof each is about forty feet square, and adorned on all sides with statues of gods and emperors cut in marble larger than the life, placed in their several niches. I measured a little finger which had fallen down from one of these statues, and lay unperceived among some rubbish, and found it exactly four feet and an inch in length. Glumdalclitch wrapped it up in a handkerchief, and carried it home in her pocket to keep among other trinkets, of which the girl was very fond, as children at her age usually are. The King's kitchen is indeed a noble building, vaulted at top, and about six hundred feet high. The great oven is not so wide by ten yards as the cupola at St. Paul's; for I measured the latter on purpose after my return. But if I should describe the kitchen grate, the prodigious pots and kettles, the joints of meat turning on the spits, with many other particulars, perhaps I should be hardly believed; at least a severe critic would be apt to think I enlarged a little, as travelers are often suspected to do. To avoid which censure, I fear I have run too much into the other extreme; and that if this treatise should happen to be translated into the language of Brobdingnag (which is the general name of that kingdom) and transmitted thither, the King and his people would have reason to complain that I had done them an injury by a false and diminutive representation. His Majesty seldom keeps above six hundred horses in his stables: they are generally from fifty-four to sixty feet high. But when he goes abroad on solemn days, he is attended for state by a militia guard of five hundred horse, which indeed I thought was the most splendid sight that could be ever beheld, till I saw part of his army in battalia, whereof I shall find another occasion to speak. P_2|CH_5 CHAPTER V - I should have lived happy enough in that country, if my littleness had not exposed me to several ridiculous and troublesome accidents, some of which I shall venture to relate. Glumdalclitch often carried me into the gardens of the court in my smaller box, and would sometimes take me out of it and hold me in her hand, or set me down to walk. I remember, before the dwarf left the Queen, he followed us one day into those gardens, and my nurse having set me down, he and I being close together, near some dwarf apple trees, I must needs show my wit by a silly allusion between him and the trees, which happens to hold in their language as it does in ours. Whereupon, the malicious rogue watching his opportunity, when I was walking under one of them, shook it directly over my head, by which a dozen apples, each of them near as large as a Bristol barrel, came tumbling about my ears; one of them hit me on the back as I chanced to stoop, and knocked me down flat on my face, but I received no other hurt, and the dwarf was pardoned at my desire, because I had given the provocation. Another day Glumdalclitch left me on a smooth grass plot to divert myself while she walked at some distance with her governess. In the meantime there suddenly fell such a violent shower of hail, that I was immediately by the force of it struck to the ground: and when I was down, the hailstones gave me such cruel bangs all over the body, as if I had been pelted with tennis balls; however, I made a shift to creep on all fours, and shelter myself by lying flat on my face on the lee side of a border of lemon thyme, but so bruised from head to foot that I could not go abroad in ten days. Neither is this at all to be wondered at, because nature in that country observing the same proportion through all her operations, a hailstone is near eighteen hundred times as large as one in Europe, which I can assert upon experience, having been so curious to weigh and measure them. But a more dangerous accident happened to me in the same garden, when my little nurse believing she had put me in a secure place, which I often entreated her to do, that might enjoy my own thoughts, and having left my box at home to avoid the trouble of carrying it, went to another part of the garden with her governess and some ladies of her acquaintance. While she was absent and out of hearing, a small white spaniel belonging to one of the chief gardeners, having got by accident into the garden, happened to range near the place where I lay. The dog following the scent, came directly up, and taking me in his mouth, ran straight to his master, wagging his tail, and set me gently on the ground. By good fortune he had been so well taught, that I was carried between his teeth without the least hurt, or even tearing my clothes. But the poor gardener, who knew me well, and had a great kindness for me, was in a terrible fright. He gently took me up in both his hands, and asked me how I did; but I was so amazed and out of breath, that I could not speak a word. In a few minutes I came to myself, and he carried me safe to my little nurse, who by this time had returned to the place where she left me, and was in cruel agonies when I did not appear, nor answer when she called: she severely reprimanded the gardener on account of his dog. But the thing was hushed up, and never known at court; for the girl was afraid of the Queen's anger, and truly as to myself, I thought it would not be for my reputation that such a story should go about. This accident absolutely determined Glumdalclitch never to trust me abroad for the future out of her sight. I had been long afraid of this resolution, and therefore concealed from her some little unlucky adventures that happened in those times when I was left by myself. Once a kite hovering over the garden made a swoop at me, and if I had not resolutely drawn my hanger, and run under a thick espalier, he would have certainly carried me away in his talons. Another time walking to the top of a fresh molehill, I fell to my neck in the hole through which that animal had cast up the earth, and coined some lie, not worth remembering, to excuse myself for spoiling my clothes. I likewise broke my right shin against the shell of a snail, which I happened to stumble over, as I was walking alone, and thinking on poor England. I cannot tell whether I were more pleased or mortified, to observe in those solitary walks that the smaller birds did not appear to be at all afraid of me, but would hop about within a yard's distance, looking for worms and other food with as much indifference and security as if no creature at all were near them. I remember a thrush had the confidence to snatch out of my hand with his bill a piece of cake that Glumdalclitch had just given me for my breakfast. When I attempted to catch any of these birds, they would boldly turn against me, endeavoring to pick my fingers, which I dared not venture within their reach; and then they would hop back unconcerned to hunt for worms or snails, as they did before. But one day I took a thick cudgel, and threw it with all my strength so luckily at a linnet that I knocked him down, and seizing him by the neck with both my hands, ran with him in triumph to my nurse. However, the bird, who had only been stunned, recovering himself, gave me so many boxes with his wings on both sides of my head and body, though I held him at arm's length, and was out of the reach of his claws, that I was twenty times thinking to let him go. But I was soon relieved by one of our servants, who wrung off the bird's neck, and I had him next day for dinner, by the Queen's command. This as near as I can remember, to be somewhat larger than an English swan. {P_2|CH_5 ^paragraph 5} The Maids of Honor often invited Glumdalclitch to their apartments, and desired she would bring me along with her, on purpose to have the pleasure of seeing and touching me. They would often strip me naked from top to toe, and lay me at full length in their bosoms; wherewith I was much disgusted; because, to say the truth, a very offensive smell came from their skins; which I do not mention or intend to the disadvantage of those excellent ladies, for whom I have all manner of respect; but I conceive that my sense was more acute in proportion to my littleness, and that those illustrious persons were no more disagreeable to their lovers, or to each other, than people of the same quality are with us in England. And, after all, I found their natural smell was much more supportable than when they used perfumes, under which I immediately swooned away. I cannot forget that an intimate friend of mine in Lilliput took the freedom in a warm day, when I had used a good deal of exercise, to complain of a strong smell about me, although I am as little faulty that way as most of my sex: but I suppose his faculty of smelling was as nice with regard to me, as mine was to that of this people. Upon this point, I cannot forbear doing justice to the Queen my mistress, and Glumdalclitch my nurse, whose persons were as sweet as those of any lady in England. That which gave me most uneasiness among these Maids of Honor, when my nurse carried me to visit them, was to see them use me without any manner of ceremony, like a creature who had no sort of consequence. For they would strip themselves to the skin, and put on their smocks in my presence, while I was placed on their toilet directly before their naked bodies, which, I am sure, to me was very far from being a tempting sight, or from giving me any other emotions than those of horror and disgust. Their skins appeared so coarse and uneven, so variously colored, when I saw them near, with a mole here and there as broad as a trencher, and hairs hanging from it thicker than packthreads, to say nothing further concerning the rest of their persons. Neither did they at all scruple, while I was by, to discharge what they had drunk, to the quantity of at least two hogsheads, in a vessel that held above three tons. The handsomest among these Maids of Honor, a pleasant frolicsome girl of sixteen, would sometimes set me astride upon one of her nipples, with many other tricks, wherein the reader will excuse me for not being over particular. But I was so much displeased, that I entreated Glumdalclitch to contrive some excuse for not seeing that young lady any more. One day a young gentleman, who was a nephew to my nurse's governess, came and pressed them both to see an execution. It was of a man who had murdered one of that gentleman's intimate acquaintance. Glumdalclitch was prevailed on to be of the company, very much against her inclination, for she was naturally tender-hearted; and as for myself, although I abhorred such kind of spectacles, yet my curiosity tempted me to see something that I thought must be extraordinary. The malefactor was fixed in a chair upon a scaffold erected for the purpose, and his head cut off at a blow with a sword of about forty foot long. The veins and arteries spouted up such a prodigious quantity of blood, and so high in the air, that the great jet d'eau at Versailles was not equal for the time it lasted; and the head, when it fell on the scaffold floor, gave such a bounce, as made me start, although I were at least half an English mile distant. The Queen, who often used to hear me talk of my sea voyages, and took all occasions to divert me when I was melancholy, asked me whether I understood how to handle a sail or an oar, and whether a little exercise of rowing might not be convenient for my health. I answered that I understood both very well. For, although my proper employment had been to be surgeon or doctor to the ship, yet upon a pinch, I was forced to work like a common mariner. But I could not see how this could be done in their country, where the smallest wherry was equal to a first-rate man of war among us, and such a boat as I could manage would never live in any of their rivers. Her Majesty said, if I would contrive a boat, her own joiner should make it, and she would provide a place for me to sail in. The fellow was an ingenious workman, and by my instructions in ten days finished a pleasure boat with all its tackling, able conveniently to hold eight Europeans. When it was finished, the Queen was so delighted, that she ran with it in her lap to the King, who ordered it to be put in a cistern full of water, with me in it, by way of trial; where I could not manage my two sculls, or little oars, for want of room. But the Queen had before contrived another project. She ordered the joiner to make a wooden trough of three hundred feet long, fifty broad, and eight deep; which being well pitched to prevent leaking, was placed on the floor along the wall, in an outer room of the palace. It had a cock near the bottom to let out the water when it began to grow stale, and two servants could easily fill it in half an hour. Here I often used to row for my own diversion, as well as that of the Queen and her ladies, who thought themselves well entertained with my skill and agility. Sometimes I would put up my sail, and then my business was only to steer, while the ladies gave me a gale with their fans; and when they were weary, some of the pages would blow my sail forward with their breath, while I showed my art steering starboard or larboard as I pleased. When I had done, Glumdalclitch always carried my boat into her closet, and hung it on a nail to dry. In this exercise I once met an accident which had like to have cost me my life. For one of the pages having put my boat into the trough, the governess who attended Glumdalclitch very officiously lifted me up to place me in the boat, but I happened to slip through her fingers, and should have infallibly fallen down forty feet upon the floor, if by the luckiest chance in the world, I had not been stopped by a corking-pin that stuck in the good gentlewoman's stomacher; the head of the pin passed between my shirt and the waistband of my breeches, and thus I was held by the middle in the air till Glumdalclitch ran to my relief. {P_2|CH_5 ^paragraph 10} Another time, one of the servants, whose office it was to fill my trough every third day with fresh water, was so careless to let a huge frog (not perceiving it) slip out of his pail. The frog lay concealed till I was put into my boat, but then seeking a resting place, climbed up, and made it lean so much on one side, that I was forced to balance it with all my weight on the other, to prevent overturning. When the frog got in, it hopped at once half the length of the boat, and then over my head, backwards and forwards, daubing my face and clothes with its odious slime. The largeness of its features made it appear the most deformed animal that can be conceived. However, I desired Glumdaclitch to let me deal with it alone. I banged it a good while with one of my sculls, and at last forced it to leap out of the boat. But the greatest danger I ever underwent in that kingdom was from a monkey, who belonged to one of the clerks of the kitchen. Glumdalclitch had locked me up in her closet, while she went somewhere upon business or a visit. The weather being very warm, the closet window was left open, as well as the windows and the door of my bigger box, in which I usually lived, because of its largeness and conveniency. As I sat quietly meditating at my table, I heard something bounce in at the closet window, and skip about from one side to the other; whereat, although I was much alarmed, yet I ventured to look out, but stirred not from my seat; and then I saw this frolicsome animal, frisking and leaping up and down, till at last he came to my box, which he seemed to view with great pleasure and curiosity, peeping in at the door and every window. I retreated to the farther corner of my room, or box, but the monkey looking in at every side, put me into such a fright, that I wanted presence of mind to conceal myself under the bed, as I might easily have done. After some time spent in peeping, grinning, and chattering, he at last espied me, and reaching one of his paws in at the door, as a cat does when she plays with a mouse, although I often shifted place to avoid him, he at length seized the lappet of my coat (which being made of that country cloth, was very thick and strong) and dragged me out. He took me up in his right forefoot, and held me as a nurse does a child she is going to suckle, just as I have seen the same sort of creature do with a kitten in Europe: and when I offered to struggle, he squeezed me so hard, that I thought it more prudent to submit. I have good to believe that he took me for a young one of his own species, by his often stroking my face very gently with his other paw. In these diversions he was interrupted by a noise at the closet door, as if somebody were opening it; whereupon he suddenly leaped up to the window at which he had come in, and thence upon the leads and gutters, walking upon three legs, and holding me in the fourth, till he clambered up to a roof that was next to ours. I heard Glumdalclitch give a shriek at the moment he was carrying me out. The poor girl was almost distracted: that quarter of the palace was all in an uproar; the servants ran for ladders; the monkey was seen by hundreds in the court, sitting upon the ridge of a building, holding me like a baby in one of his fore-paws, and feeding me with the other, by cramming into my mouth some victuals he had squeezed out of the bag on one side of his chaps, and patting me when I would not eat; whereat many of the rabble below could not forbear laughing; neither do I think they justly ought to be blamed, for without question the sight was ridiculous enough to everybody but myself. Some of the people threw up stones, hoping to drive the monkey down; but this was strictly forbidden, or else very probably my brains had been dashed out. The ladders were now applied, and mounted by several men, which the monkey observing, and finding himself almost encompassed, not being able to make speed enough with his three legs, let me drop on a ridge tile, and made his escape. Here I sat for some time three hundred yards from the ground, expecting every moment to be blown down by the wind, or to fall by my own giddiness, and come tumbling over and over from the ridge to the eaves; but an honest lad, one of my nurse's footmen, climbed up, and putting me into his breeches pocket, brought me down safe. I was almost choked with the filthy stuff the monkey had crammed down my throat: but my dear little nurse picked it out of my mouth with a small needle, and then I fell to vomiting, which gave me great relief. Yet I was so weak and bruised in the sides with the squeezes given me by this odious animal, that I was forced to keep my bed a fortnight. The King, Queen, and all the court, sent every day to inquire after my health, and her Majesty made me several visits during my sickness. The monkey was killed, and an order made that no such animal should be kept about the palace. When I attended the King after my recovery, to return him thanks for his favors, he was pleased to rally me a good deal upon this adventure. He asked me what my thoughts and speculations were while I lay in the monkey's paw, how I liked the victuals he gave me, his manner of feeding, and whether the fresh air on the roof had sharpened my stomach. He desired to know what I would have done upon such an occasion my own country. I told his Majesty that in Europe we had no monkeys, except such as were brought for curiosities from other places, and so small that I could deal with a dozen of them together, if they presumed to attack me. And as for that monstrous animal with whom I was so lately engaged (it was indeed as large as an elephant), if my fears had suffered me to think so far as to make use of my hanger (looking fiercely and clapping my hand upon the hilt as I spoke) when he poked his paw into my chamber, perhaps I should have given him such a wound, as would have made him glad to withdraw it with more haste than he put it in. This I delivered in a firm tone, like a person who was jealous lest his courage should be called in question. However, my speech produced nothing else besides a loud laughter, which all the respect due to his Majesty from those about him could not make them contain. This made me reflect how vain an attempt it is for a man to endeavor doing himself honor among those who are out of all degree of equality or comparison with him. And yet I have seen the moral of my own behavior very frequent in England since my return, where a little contemptible varlet, without the least title to birth, person, wit, or common sense, shall presume to look with importance, and put himself upon a foot with the greatest persons of the kingdom. {P_2|CH_5 ^paragraph 15} I was every day furnishing the court with some ridiculous story; and Glumdalclitch, although she loved me to excess, yet was arch enough to inform the Queen, whenever I committed any folly that she thought would be diverting to her Majesty. The girl, who had been out of order, was carried by her governess to take the air about an hour's distance, or thirty miles from town. They alighted out of the coach near a small footpath in a field, and Glumdalclitch setting down my traveling box, I went out of it to walk. There was a cow dung in the path, and I must needs try my activity by attempting to leap over it. I took a run, but unfortunately jumped short, and found myself just in the middle up to my knees. I waded through with some difficulty, and one of the footmen wiped me as clean as he could with his handkerchief; for I was filthily bemired, and my nurse confined me to my box till we returned home; where the Queen was soon informed of what had passed, and the footmen spread it about the court, so that all the mirth, for some days, was at my expense. P_2|CH_6 CHAPTER VI - I used to attend the King's levee once or twice a week, and had often seen him under the barber's hand, which indeed was at first very terrible to behold; for the razor was almost twice as long as an ordinary scythe. His Majesty, according to the custom of the country, was only shaved twice a week. I once prevailed on the barber to give me some of the suds or lather, out of which I picked forty or fifty of the strongest stumps of hair. I then took a piece of fine wood, and cut it like the back of a comb, making several holes in it at equal distance with as small a needle as I could get from Glumdalclitch. I fixed in the stumps so artificially, scraping and sloping them with my knife toward the points, that I made a very tolerable comb; which was a seasonable supply, my own being so much broken in the teeth, that it was almost useless: neither did I know any artist in that country so nice and exact, as would undertake to make me another. And this puts me in mind of an amusement wherein I spent many of my leisure hours. I desired the Queen's woman to save for me the combings of her Majesty's hair, whereof in time I got a good quantity, and consulting with my friend the cabinet-maker, who had received general orders to do little jobs for me, I directed him to make two chair frames, no larger than those I had in my box, and then to bore little holes with a fine awl round those parts where I designed the backs and seats; through these holes I wove the strongest hairs I could pick out, just after the manner of cane chairs in England. When they were finished, I made a present of them to her Majesty, who kept them in her cabinet, and used to show them for curiosities, as indeed they were the wonder of every one that beheld them. The Queen would have had me sit upon one of these chairs, but I absolutely refused to obey her, protesting I would rather die a thousand deaths than place a dishonorable part of my body on those precious hairs that once adorned her Majesty's head. Of these hairs (as I had always a mechanical genius) I likewise made a neat little purse about five feet long, with her Majesty's name deciphered in gold letters, which I gave to Glumdalclitch, by the Queen's consent. To say the truth, it was more for show than use, being not of strength to bear the weight of the larger coins, and therefore she kept nothing in it but some little toys that girls are fond of. The King, who delighted in music, had frequent concerts at court, to which I was sometimes carried, and set in my box on a table to hear them; but the noise was so great, that I could hardly distinguish the tunes. I am confident that all the drums and trumpets of a royal army, beating and sounding together just at your ears, could not equal it. My practice was to have my box removed from the places where the performers sat, as far as I could, then to shut the doors and windows of it, and draw the window curtains; after which I found their music not disagreeable. I had learned in my youth to play a little upon the spinet Glumdaclitch kept one in her chamber, and a master attended twice a week to teach her: I call it a spinet, because it somewhat resembled that instrument. and was played upon in the same manner. A fancy came into my head that I would entertain the King and Queen with an English tune upon this instrument. But this appeared extremely difficult; for the spinet was near sixty feet long, each key being almost a foot wide, so that, with my arms extended, I could not reach to above five keys, and to press them down required a good smart stroke with my fist, which would be too great a labor, and to no purpose. The method I contrived was this. I prepared two round sticks about the bigness of common cudgels; they were thicker at one end than the other, and I covered the thicker ends with a piece of a mouse's skin, that by rapping on them I might neither damage the tops of the keys, nor interrupt the sound. Before the spinet a bench was placed, about four feet below the keys, and I was put upon the bench. I ran sideling upon it that way and this, as fast as I could, banging the proper keys with my two sticks, and made a shift to play a jig, to the great satisfaction of both their Majesties: but it was the most violent exercise I ever underwent, and yet I could not strike above sixteen keys, nor, consequently, play the bass and treble together, as other artists do; which was a great disadvantage to my performance. The King, who, as I before observed, was a prince of excellent understanding, would frequently order that I should be brought in my box, and set upon the table in his closet. He would then command me to bring one of my chairs out of the box, and sit down within three yards distance upon the top of the cabinet, which brought me almost to a level with his face. In this manner I had several conversations with him. I one day took the freedom to tell his Majesty, that the contempt he discovered towards Europe, and the rest of the world, did not seem answerable to those excellent qualities of the mind he was master of. That reason did not extend itself with the bulk of the body: on the contrary, we observed in our country that the tallest persons were usually least provided with it. That among other animals, bees and ants had the reputation of more industry, art and sagacity, than many of the larger kinds. And that, as inconsiderable as he took me to be, I hoped I might live to do his Majesty some signal service. The King heard me with attention, and began to conceive a much better opinion of me than he had ever before. He desired I would give him as exact an account of the government of England as I possibly could; because, as fond as princes commonly are of their own customs (for so he conjectured of other monarchs, by my former discourses), he should be glad to hear of anything that might deserve imitation. {P_2|CH_6 ^paragraph 5} Imagine with thyself, courteous reader, how often I then wished for the tongue of Demosthenes or Cicero, that might have enabled me to celebrate the praise of my own dear native country in a style equal to its merits and felicity. I began my discourse by informing his Majesty that our dominions consisted of two islands, which composed three mighty kingdoms under one sovereign, beside our plantations in America. I dwelt long upon the fertility of our soil, and the temperature of our climate. I then spoke at large upon the constitution of an English Parliament, partly made up of an illustrious body called the House of Peers, persons of the noblest blood, and of the most ancient and ample patrimonies. I described that extraordinary care always taken of their education in arts and arms, to qualify them for being counselors born to the king and kingdom, to have a share in the legislature, to be members of the highest Court of Judicature, from whence there could be no appeal, and to be champions always ready for the defense of their prince and country, by their valor, conduct, and fidelity. That these were the ornament and bulwark of the kingdom, worthy followers of their most renowned ancestors, whose honor had been the reward of their virtue, from which their posterity were never once known to degenerate. To these we joined several holy persons, as part of that assembly, under the title of Bishops, whose peculiar business it is to take care of religion, and of those who instruct the people therein. These were searched and sought out through the whole nation, by the prince and his wisest counselors, among such of the priesthood as were most deservedly distinguished by the sanctity of their lives, and the depth of their erudition; who were indeed the spiritual fathers of the clergy and the people. That the other part of the Parliament consisted of an assembly called the House of Commons, who were all principal gentlemen, freely picked and culled out by the people themselves, for their great abilities and love of their country, to represent the wisdom of the whole nation. And these two bodies make up the most august assembly in Europe, to whom, in conjunction with the prince, the whole legislature is committed. I then descended to the Courts of justice, over which the judges, those venerable sages and interpreters of the law, presided, for determining the disputed rights and properties of men, as well as for the punishment of vice, and protection of innocence. I mentioned the prudent management of our treasury; the valor and achievements of our forces by sea and land. I computed the number of our people, by reckoning how many millions there might be of each religious sect, or political party among us. I did not omit even our sports and pastimes, or any other particular which I thought might redound to the honor of my country. And I finished all with a brief historical account of affairs and events in England for about a hundred years past. This conversation was not ended under five audiences, each of several hours, and the King heard the whole with great attention, frequently taking notes of what I spoke, as well as memorandums of several questions he intended to ask me. {P_2|CH_6 ^paragraph 10} When I had put an end to these long discourses, his Majesty in a sixth audience, consulting his notes, proposed many doubts, queries, and objections, upon every article. He asked what methods were used to cultivate the minds and bodies of our young nobility, and in what kind of business they commonly spent the first and teachable part of their lives. What course was taken to supply that assembly when any noble family became extinct. What qualifications were in those who were to be created new lords. Whether the humor of the prince, a sum of money to a court lady, or a prime minister, or a design of strengthening a party opposite to the public interest, ever happened to be motives in those advancements. What share of knowledge these lords had in the laws of their country, and how they came by it, so as to enable them to decide the properties of their fellow-subjects in the last resort. Whether they were always so free from avarice, partialities, or want, that a bribe, or some other sinister view, could have no place among them. Whether those holy lords I spoke of were always promoted to that rank upon account of their knowledge in religious matters, and the sanctity of their lives, had never been compliers with the times while they were common priests, or slavish prostitute chaplains to some nobleman, whose opinions they continued servilely to follow after they were admitted into that assembly. He then desired to know what arts were practiced in electing those whom I commoners: whether a stranger with a strong purse might not influence the vulgar voters to choose him before their own landlord, or the most considerable gentleman in the neighborhood. How it came to pass, that people were so violently bent upon getting into this assembly, which I allowed to be a great trouble and expense, often to the ruin of their families, without any salary or pension: because this appeared such an exalted strain of virtue and public spirit, that his Majesty seemed to doubt it might possibly not be always sincere: and he desired to know whether such zealous gentlemen could have any views of refunding themselves for the charges and trouble they were at, by sacrificing the public good to the designs of a weak and vicious prince in conjunction with a corrupted ministry. He multiplied his questions and sifted me thoroughly upon every part of this head, proposing numberless inquiries and objections, which I think it not prudent or convenient to repeat. Upon what I said in relation to our Courts of Justice, his Majesty desired to be satisfied in several points: and this I was the better able to do, having been formerly almost ruined by a long suit in chancery, which was decreed for me with costs. He asked, what time was usually spent in determining between right and wrong, and what degree of expense. Whether advocates and orators had liberty to plead in causes manifestly known to be unjust, vexatious, or oppressive. Whether party in religion or politics were observed to be of any weight in the scale of justice. Whether those pleading orators were persons educated in the general knowledge of equity, or only in provincial, national, and other local customs. Whether they or their judges had any part in penning those laws which they assumed the liberty of interpreting and glossing upon at their pleasure. Whether they had ever at different times pleaded for and against the same cause, and cited precedents to prove contrary opinions. Whether they were a rich or a poor corporation. Whether they received any pecuniary reward for pleading or delivering their opinions. And particularly whether they were ever admitted as members in the lower senate. He fell next upon the management of our treasury; and said he thought my memory had failed me, because I computed our taxes at about five or six millions a year, and when I came to mention the issues, he found they sometimes amounted to more than double; for the notes he had taken were very particular in this point, because he hoped, as he told me, that the knowledge of our conduct might be useful to him, and he could not be deceived in his calculations. But, if what I told him were true, he was still at a loss how a kingdom could run out of its estate like a private person. He asked me, who were our creditors; and where we should find money to pay them. He wondered to hear me talk of such chargeable and extensive wars; that certainly we must be a quarrelsome people, or live among very bad neighbors, and that our generals must needs be richer than our kings. He asked what business we had out of our own islands, unless upon the score of trade or treaty, or to defend the coasts with our fleet. About all, he was amazed to hear me talk of a mercenary standing army in the midst of peace, and among a free people. He said, if we were governed by our own consent in the persons of our representatives, he could not imagine of whom we were afraid, or against whom we were to fight; and would hear my opinion, whether a private man's house might not better be defended by himself, his children, and family, than by half a dozen rascals picked up at a venture in the streets, for small wages, who might get a hundred times more by cutting their throats. He laughed at my odd kind of arithmetic (as he was pleased to call it) in reckoning the numbers of our people by a computation drawn from the several sects among us in religion and politics. He said, he knew no reason, why those who entertain opinions prejudicial to the public, should be obliged to change, or should not be obliged to conceal them. And as it was tyranny in any government to require the first, so it was weakness not to enforce the second: for a man may be allowed to keep poisons in his closet, but not to vend them about for cordials. {P_2|CH_6 ^paragraph 15} He observed that among the diversions of our nobility and gentry I had mentioned gaming. He desired to know at what age this entertainment was usually taken up, and when it was laid down; how much of their time it employed; whether it ever went so high as to affect their fortunes; whether mean vicious people, by their dexterity in that art, might not arrive at great riches, and sometimes keep our very nobles in dependence, as well as habituate them to vile companions, wholly take them from the improvement of their minds, and force them, by the losses they have received, to learn and practice that infamous dexterity upon others. He was perfectly astonished with the historical account I gave him of our affairs during the last century, protesting it was only a heap of conspiracies, rebellions, murders, massacres, revolutions, banishments, the very worst effects that avarice, faction, hypocrisy, perfidiousness, cruelty, rage, madness, hatred, envy, lust, malice, or ambition could produce. His Majesty in another audience was at the pains to recapitulate the sum of all I had spoken, compared the questions he made with the answers I had given, then taking me into his hands, and stroking me gently, delivered himself in these words, which I shall never forget nor the manner he spoke them in: My little friend Grildrig, you have made a most admirable panegyric upon your country; you have clearly proved that ignorance, idleness, and vice, may be sometimes the only ingredients for qualifying a legislator; that laws are best explained, interpreted, and applied by those whose interest and abilities lie in perverting, confounding, and eluding them. I observe among you some lines of an institution, which in its original might have been tolerable, but these half erased, and the rest wholly blurred and blotted by corruptions. It does not appear from all you have said, how any one virtue is required towards the procurement of any one station among you; much less that men are ennobled on account of their virtue, that priests are advanced for their piety or learning, soldiers for their conduct or valor, judges for their integrity, senators for the love of their country, or counsellors for their wisdom. As for yourself (continued the King) who have spent the greatest part of your life in traveling, I am well disposed to hope you may hitherto have escaped many vices of your country. But by what I have gathered from your own relation, and the answers I have with much pains wringed and extorted from you, I cannot but conclude the bulk of your natives to be the most pernicious race of little odious vermin that nature ever suffered to crawl upon the surface of the earth. P_2|CH_7 CHAPTER VII - Nothing but an extreme love of truth could have hindered me from concealing this part of my story. It was in vain to discover my resentments, which were always turned into ridicule; and I was forced to rest with patience while my noble and most beloved country was so injuriously treated. I am heartily sorry as any of my readers can possibly be, that such an occasion was given: but this prince happened to be so curious and inquisitive upon every particular, that it could not consist either with gratitude or good manners to refuse giving him what satisfaction I was able. Yet thus much I may be allowed to say in my own vindication, that I artfully eluded many of his questions, and gave to every point a more favorable turn by many degrees than the strictness of truth would allow. For I have always borne that laudable partiality to my own country, which Dionysius Halicarnassensis with so much justice recommends to a historian. I would hide the frailties and deformities of my political mother, and place her virtues and beauties in the most advantageous light. This was my sincere endeavor in those many discourses I had with that mighty monarch, although it unfortunately failed of success. But great allowances should be given to a King who lives wholly secluded from the rest of the world, and must therefore be altogether unacquainted with the manners and customs that most prevail in other nations; the want of which knowledge will ever produce many prejudices, and a certain narrowness of thinking, from which we and the politer countries of Europe are wholly exempted. And it would be hard indeed, if so remote a prince's notions of virtue and vice were to be offered as a standard for all mankind. To confirm what I have now said, and further, to show the miserable effects of a confined education, I shall here insert a passage which will hardly obtain belief. In hopes to ingratiate myself farther into his Majesty's favor, I told him of an invention discovered between three and four hundred years ago, to make a certain powder, into a heap of which the smallest spark of fire falling, would kindle the whole in a moment, although it were as big as a mountain, and make it all fly up in the air together, with a noise and agitation greater than thunder. That a proper quantity of this powder rammed into a hollow tube of brass or iron, according to its bigness, would drive a ball of iron or lead with such violence and speed, as nothing was able to sustain its force. the largest balls thus discharged, would not only destroy whole ranks of an army at once, but batter the strongest walls to the ground, sink down ships, with a thousand men in each, to the bottom of the sea; and, when linked together by a chain, would cut through masts and rigging, divide hundreds of bodies in the middle, and lay all waste before them. That we often put this powder into large hollow balls of iron, and discharged them by an engine into some city we were besieging, which would rip up the pavements, tear the houses to pieces, burst and throw splinters on every side, dashing out the brains of all who came near. That I knew the ingredients very well, which were cheap, and common; I understood the manner of compounding them, and could direct his workmen how to make those tubes of a size proportionable to all other things in his Majesty's kingdom, and the largest need not be above a hundred feet long; twenty or thirty of which tubes, charged with the proper quantity of powder and balls, would batter down the walls of the strongest town in his dominions in a few hours, or destroy the whole metropolis, if ever it should pretend to dispute his absolute commands. This I humbly offered to his Majesty, as a small tribute of acknowledgment in return of so many marks that I had received of his royal favor and protection. The King was struck with horror at the description I had given of those terrible engines, and the proposal I had made. He was amazed how so impotent and grovelling an insect as I (these were his expressions) could entertain such inhuman ideas, and in so familiar a manner as to appear wholly unmoved at all the scenes of blood and desolation, which I had painted as the common effects of those destructive machines, whereof he said some evil genius, enemy to mankind, must have been the first contriver. As for himself, he protested that although few things delighted him so much as new discoveries in art or in nature, yet he would rather lose half his kingdom than be privy to such a secret, which he commanded me, as I valued my life, never to mention any more. A strange effect of narrow principles and short views that a prince possessed of every quality which procures veneration, love, and esteem; of strong parts, great wisdom, and profound learning, endued with admirable talents for government, and almost adored by his subjects, should from a nice unnecessary scruple, whereof in Europe we can have no conception, let slip an opportunity to put into his hands, that would have made him absolute master of the lives, the liberties, and the fortunes of his people. Neither do I say this with the least intention to detract from the many virtues of that excellent King, whose character I am sensible will on this account be very much lessened in the opinion of an English reader: but I take this defect among them to have risen from their ignorance, they not having hitherto reduced politics into a science, as the more acute wits of Europe have done. For I remember very well, in a discourse one day with the King, when I happened to say there were several thousand books among us written upon the art of government, it gave him (directly contrary to my intention) a very mean opinion of our understandings. He professed both to abominate and despise all mystery, refinement, and intrigue, either in a prince or a minister. He could not tell what I meant secrets of state, where an enemy or some rival nation were not in the case. He confined the knowledge of governing within very narrow bounds; to common sense and reason, to justice and lenity, to the speedy determination of civil and criminal causes; with some other obvious topics, which are not worth considering. And he gave it for his opinion, that whoever could make two ears of corn or two blades of grass to grow upon a spot of ground where only one grew before, would deserve better of mankind, and do more essential service to his country than the whole race of politicians put together. {P_2|CH_7 ^paragraph 5} The learning of this people is very defective, consisting only in morality, history, poetry, and mathematics, wherein they must be allowed to excel. But the last of these is wholly applied to what may be useful in life, to the improvement of agriculture, and all mechanical arts; so that among us it would be little esteemed. And as to ideas, entities, abstractions, and transcendentals, I could never drive the least conception into their heads. No law of that country must exceed in words the number of letters in their alphabet, which consists only of twenty-two. But indeed few of them extend even to that length. They are expressed in the most plain and simple terms, wherein those people are not mercurial enough to discover above one interpretation; and to write a comment upon any law is a capital crime. As to the decision of civil causes, or proceedings against criminals, their precedents are so few, that they have little reason to boast of any extraordinary skill in either. They have had the art of printing, as well as the Chinese, time out of mind. But their libraries are not very large; for that of the King's which is reckoned the biggest, does not amount to above a thousand volumes, placed in a gallery twelve hundred feet long, from which I had liberty to borrow what books I pleased. The Queen's joiner had contrived in one of Glumdalclitch's rooms a kind of wooden machine twenty-five feet high, formed like a standing ladder; the steps were each fifty feet long. It was indeed a moveable pair of stairs, the lowest end placed at ten feet distance from the wall of the chamber. The book I had a mind to read was put up leaning against the wall. I first mounted to the upper step of the ladder, and turning my face towards the book, began at the top of the page, and so walking to the right and left about eight or ten yards, according to the length of the lines, till I had gotten a little below the level of my eyes, and then descending gradually till I came to the bottom; after which I mounted again and began the other page in the same manner, and so turned over the leaf, which I could easily do with both my hands, for it was as thick and stiff as pasteboard, and in the largest folios not above eighteen or twenty feet long. Their style is clear, masculine, and smooth, but not florid, for they avoid nothing more than multiplying unnecessary words, or using various expressions. I have perused many of their books, especially those in history and morality. Among the rest, I was much diverted with a little old treatise, which always lay in Glumdalclitch's bed chamber, and belonged to her governess, a grave elderly gentlewoman, who dealt in writings of morality and devotion. The book treats of the weakness of human kind, and is in little esteem, except among the women and the vulgar. However, I was curious to see what an author of that country could say upon such a subject. This writer went through all the usual topics of European showing how diminutive, contemptible, and helpless an animal was man in his own nature; how unable to defend himself from the inclemencies of the air, or the fury of wild beasts; how much he was excelled by one creature in strength, by another in speed, by a third in foresight, by a fourth in industry. He added, that nature was degenerated in these latter declining ages of the world, and could now produce only small abortive births in comparison of those in ancient times. He said, it was very reasonable to think, not only that the species of men were originally much larger, but also, that there must have been giants in former ages, which, as it is asserted by history and tradition, so it has been confirmed by huge bones and skulls casually dug up in several parts of the Kingdom, far exceeding the common dwindled race of man in our days. He argued, that the very laws of nature absolutely required we should have been made in the beginning, of a size more large and robust, not so liable to destruction from every little accident of a tile falling from a house, or a stone cast from the hand of a boy, or of being drowned in a little brook. From this way of reasoning the author drew several moral applications useful in the conduct of life, but needless here to repeat. For my own part, I could not avoid reflecting how universally this talent was spread, of drawing lectures in morality, or indeed rather matter of discontent and repining, from the quarrels we raise with nature. And I believe, upon a strict inquiry, those quarrels might be shown as ill-grounded among us as they are among that people. As to their military affairs, they boast that the King's army consists of a hundred and seventy-six thousand foot, and thirty-two thousand horse: if that may be called an army which is made up of tradesmen in the several cities, and farmers in the country, whose commanders are only the nobility and gentry, without pay or reward. They are indeed perfect enough in their exercises, and under very good discipline, wherein I saw no great merit; for how should it be otherwise, where every farmer is under the command of his own landlord, and every citizen under that of the principal men in his own city, chosen after the manner of Venice by ballot? {P_2|CH_7 ^paragraph 10} I have often seen the militia of Lorbrulgrud drawn out to exercise in a great field near the city of twenty miles square. They were in all not above twenty-five thousand foot, and six thousand horse; but it was impossible for me to compute their number, considering the space of ground they took up. A cavalier mounted on a large steed, might be about a hundred feet high. I have seen this whole body of horse, upon a word of command, draw their swords at once, and brandish them in the air. Imagination can figure nothing so grand, so surprising, and so astonishing. It looked as if ten thousand flashes of lightning were darting at the same time from every quarter of the sky. I was curious to know how this prince, to whose dominions there is no access from any other country, came to think of armies, or to teach his people the practice of military discipline. But I was soon informed, both by conversation and reading their histories. For in the course of many ages they have been troubled with the same disease to which the whole race of mankind is subject; the nobility often contending for power, the people for liberty, and the King for absolute dominion. All which, however happily tempered by the laws of the kingdom, have been sometimes violated by each of the three parties, and have once or more occasioned civil wars, the last whereof was happily put an end to by this prince's grandfather by a general composition; and the militia, then settled with common consent, has been ever since kept in the strictest duty. P_2|CH_8 CHAPTER VIII - I had always a strong impulse that I should some time recover my liberty, though it was impossible to conjecture by what means, or to form any project with the least hope of succeeding. The ship in which I sailed was the first ever known to be driven within sight of that coast, and the King had given strict orders, that if at any time another appeared, it should be taken ashore, and with all its crew and passengers brought in a tumbril to Lorbrulgrud. He was strongly bent to get me a woman of my own size, by whom I might propagate the breed: but I think I should rather have died than undergone the disgrace of leaving a posterity to be kept in cages like tame canary birds, and perhaps, in time, sold about the kingdom to persons of quality for curiosities. I was, indeed, treated with much kindness; I was the favorite of a great King and Queen, and the delight of the whole court, but it was upon such a foot as ill became the dignity of human kind. I could never forget those domestic pledges I had left behind me. I wanted to be among people with whom I could converse upon even terms, and walk about the streets and fields without fear of being trod to death like a frog or a young puppy. But my deliverance came sooner than I expected, and in a manner not very common; the whole story and circumstances of which I shall faithfully relate. I had now been two years in this country; and about the beginning of the third, Glumdalclitch and I attended the King and Queen in a progress to the coast of the kingdom. I was carried, as usual, in my traveling box, which, as I have already described, was a very convenient closet of twelve feet wide. And I had ordered a hammock to be fixed by silken ropes from the four corners at the top, to break the jolts, when a servant carried me before him on horseback, as I sometimes desired, and would often sleep in my hammock while we were upon the road. On the roof of my closet, just over the middle of the hammock, I ordered the joiner to cut out a hole a foot square, to give me air in hot weather as I slept, which hole I shut at pleasure with a board that drew backwards and forwards through a groove. When we came to our journey's end, the King thought proper to pass a few days at a palace he has near Flanflasnic, a city within eighteen English miles of the seaside. Glumdalclitch and I were much fatigued; I had gotten a small cold, but the poor girl was so ill as to be confined to her chamber. I longed to see the ocean, which must be the only scene of my escape, if ever it should happen. I pretended to be worse than I really was, and desired leave to take the fresh air of the sea, with a page whom I was very fond of, and who had sometimes been trusted with me. I shall never forget with what unwillingness Glumdalclitch consented, nor the strict charge she gave the page to be careful of me, bursting at the same time into a flood of tears, as if she had some foreboding of what was to happen. The boy took me out in my box about half an hour's walk from the palace, towards the rocks on the seashore. I ordered him to set me down, and lifting up one of my sashes, cast many a wistful melancholy look towards the sea. I found myself not very well, and told the page that I had a mind to take a nap in my hammock, which I hoped would do me good. I got in, and the boy shut the window close down to keep out the cold. I soon fell asleep, and all I can conjecture is, that while I slept, the page, thinking no danger could happen, went among the rocks to look for birds' eggs, having before observed him from my window searching about, and picking up one or two in the clefts. Be that as it will, I found myself suddenly awaked with a violent pull upon the ring which was fastened at the top of my box for the conveniency of carriage. I felt my box raised very high in the air, and then borne forward with prodigious speed. The first jolt had like to have shaken me out of my hammock, but afterwards the motion was easy enough. I called out several times as loud as I could raise my voice, but all to no purpose. I looked towards my windows and could see nothing but the clouds and sky. I heard a noise just over my head like the clapping of wings, and then began to perceive the woeful condition I was in; that some eagle had got the ring of my box in his beak, with an intent to let it fall on a rock like a tortoise in a shell, and then pick out my body, and devour it. For the sagacity and smell of this bird enable him to discover his quarry at a great distance, though better concealed than I could be within a two-inch board. In a little time I observed the noise of flutter of wings to increase very fast, and my box was tossed up and down, like a sign post on a windy day. I heard several bangs or buffets, as I thought, given to the eagle (for such I am certain it must have been that held the ring of my box in his beak), and then all of a sudden felt myself falling perpendicularly down for above a minute, but with such incredible swiftness that I almost lost my breath. My fall was stopped by a terrible squash, that sounded louder to my ears than the cataract of Niagara; after which I was quite in the dark for another minute, and then my box began to rise so high that I could see light from the tops of my windows. I now perceived that I had fallen into the sea. My box, by the weight of my body, the goods that were in, and the broad plates of iron fixed for strength at the four corners of the top and bottom, floated five feet deep in water. I did then, and do now, suppose that the eagle which flew away with my box was pursued by two or three others, and forced to let me drop while he was defending himself against the rest, who hoped to share in the prey. The plates of iron fastened at the bottom of the box (for those were the strongest) preserved the balance while it fell, and hindered it from being broken on the surface of the water. Every joint of it was well grooved, and the door did not move on hinges, but up and down like a sash, which kept my closet so tight that very little water came in. I got with much difficulty out of my hammock, having first ventured to draw back the slipboard on the roof already mentioned, contrived on purpose to let in air, for want of which I found myself almost stifled. How often did I then wish myself with my dear Glumdalclitch, from whom one single hour had so far divided me! And I may say with truth, that in the midst of my own misfortunes I could not forbear lamenting my poor nurse, the grief she would suffer for my loss, the displeasure of the Queen, and the ruin of her fortune. Perhaps many travelers have not been under greater difficulties and distress than I was at this juncture, expecting every moment to see my box dashed in pieces, or at least overset by the first violent blast, or a rising wave. A breach in one single pane of glass would have been immediate death: nor could anything have preserved the windows, but the strong lattice wires placed on the outside against accidents in traveling. I saw the water ooze in at several crannies, although the leaks were not considerable, and I endeavored to stop them as well as I could. I was not able to lift up the roof of my closet, which otherwise I certainly should have done, and sat on the top of it, where I might at least preserve myself some hours longer than by being shut up, as I may call it, in the hold. Or, if I escaped these dangers for a day or two, what could I expect but a miserable death of cold and hunger! I was four hours under these circumstances, expecting and indeed wishing every moment to be my last. {P_2|CH_8 ^paragraph 5} I have already told the reader that there were two strong staples fixed upon that side of my box which had no window, and into which the servant who used to carry me on horseback would put a leathern belt, and buckle it about his waist. Being in this disconsolate state, I heard or at least thought I beard some kind of grating noise on that side of my box where the staples were fixed, and soon after I began to fancy that the box was pulled or towed along in the sea; for I now and then felt a sort of tugging, which made the waves rise near the tops of my windows, leaving me almost in the dark. This gave me some faint hopes of relief, although I was not able to imagine how it could be brought about. I ventured to unscrew one of my chairs, which were always fastened to the floor; and having made a hard shift to screw it down again directly under the slipping-board that I had lately opened, I mounted on the chair, and putting my mouth as near as I could to the hole, I called for help in a loud voice, and in all the languages I understood. I then fastened my handkerchief to a stick I usually carried, and thrusting it up the hole, waved it several times in the air, that if any boat or ship were near, the seamen might conjecture some unhappy mortal to be shut up in the box. I found no effect from all I could do, but plainly perceived my closet to be moved along; and in the space of an hour, or better, that side of the box where the staples were, and had no window, struck against something that was hard. I apprehended it to be a rock, and found myself tossed more than ever. I plainly heard a noise upon the cover of my closet, like that of a cable, and the grating of it as it passed through the ring. I then found myself hoisted up by degrees at least three feet higher than I was before. Whereupon I again thrust up my stick and handkerchief, calling for help till I was almost hoarse. In return to which, I heard a great shout repeated three times, giving me such transports of joy, as are not to be conceived but by those who feel them. I now heard a trampling over my head, and somebody calling through the hole with a loud voice in the English tongue: If there be anybody below, let them speak. I answered, I was an Englishman, drawn by ill fortune into the greatest calamity that ever any creature underwent, and begged, by all that is moving, to be delivered out of the dungeon I was in. The voice replied, I was safe, for my box was fastened to their ship; and the carpenter should immediately come and saw an hole in the cover, large enough to pull me out. I answered, that was needless, and would take up too much time, for there was no more to be done, but let one of the crew put his finger into the ring, and take the box out of the sea into the ship, and so into the captain's cabin. Some of them upon hearing me talk so wildly thought I was mad; others laughed; for indeed it never came into my head that I was now among people of my own stature and strength. The carpenter came, and in a few minutes sawed a passage about four feet square, then let down a small ladder, upon which I mounted, and from thence was taken into the ship in a very weak condition. The sailors were all in amazement, and asked me a thousand questions, which I had no inclination to answer. I was equally confounded at the sight of so many pigmies, for such I took them to be, after having so long accustomed my eyes to the monstrous objects I had left. But the Captain, Mr. Thomas Wilcocks, an honest worthy Shropshire man, observing I was ready to faint, took me into his cabin, gave me a cordial to comfort me, and made me turn in upon his own bed, advising me to take a little rest, of which I had great need. Before I went to sleep I gave him to understand that I had some valuable furniture in my box, too good to be lost, a fine hammock, a handsome field bed, two chairs, a table, and a cabinet; that my closet was hung on all sides, or rather quilted, with silk and cotton; that if he would let one of the crew bring my closet into his cabin, I would open it there before him, and show him my goods. The Captain hearing me utter these absurdities, concluded I was raving: however (I suppose to pacify me), he promised to give order as I desired, and going upon deck sent some of his men down into my closet, from whence (as I afterwards found) they drew up all my goods, and stripped off the quilting; but the chairs, cabinet, and bedstead, being screwed to the floor, were much damaged by the ignorance of the seamen, who tore them up by force. Then they knocked off some of the boards for the use of the ship, and when they had got all they had a mind for, let the hull drop into the sea, which by reason of many breaches made in the bottom and sides, sunk to rights. And indeed I was glad not to have been a spectator of the havoc they made; because I am confident it would have sensibly touched me, by bringing former passages into my mind, which I had rather forget. I slept some hours, but perpetually disturbed with dreams of the place I had left, and the dangers I had escaped. However, upon waking I found myself much recovered. It was now about eight o'clock at night, and the Captain ordered supper immediately, thinking I had already fasted too long. He entertained me with great kindness, observing me not to look wildly, or talk inconsistently: and when we were left alone, desired I would give him a relation of my travels, and by what accident I came to be set adrift in that monstrous wooden chest. He said, that about twelve o'clock at noon, as he was looking through his glass, he spied it at a distance, and thought it was a sail, which he had a mind to make, being not much out of his course, in hopes of buying some biscuit, his own beginning to fall short. That upon coming nearer, and finding his error, he sent out his longboat to discover what I was; that his men came back in a fright, swearing they had seen a swimming house. That he laughed at their folly, and went himself in the boat, ordering his men to take a strong cable along with them. That the weather being calm, he rowed round me several times, observed my windows, and the wire lattices that defended them. That he discovered two staples upon one side, which was all of boards, without any passage for light. He then commanded his men to row up to that side, and fastening a cable to one of the staples, ordered them to tow my chest (as he called it) towards the ship. When it was there, he gave directions to fasten another cable to the ring fixed in the cover, and to raise up my chest with pulley, which all the sailors were not able to do above two or three feet. He said they saw my stick and handkerchief thrust out of the hole, and concluded that some unhappy men must be shut up in the cavity. I asked whether he or the crew had seen any prodigious birds in the air about the time he first discovered me. To which he answered, that discoursing this matter with the sailors while I was asleep, one of them said he had observed three eagles flying towards the north, but remarked nothing of their being larger than the usual size, which I suppose must be imputed to the great height they were at; and he could not guess the reason of my question. I then asked the Captain how far he reckoned we might be from land; he said, by the best computation he could make, we were at least a hundred leagues. I assured him, that he must be mistaken by almost half, for I had not left the country from where I came above two hours before I dropped into the sea. Whereupon he began again to think that my brain was disturbed, of which he gave me a hint, and advised me to go to bed in a cabin he had provided. I assured him I was well refreshed with his good entertainment and company, and as much in my senses as ever I was in my life. He then grew serious, and desired to ask me freely whether I were not troubled in mind by the consciousness of some enormous crime, for which I was punished at the command of some prince, by exposing me in that chest, as great criminals in other countries have been forced to sea in a leaky vessel without provisions; for although he should be sorry to have taken so ill a man into his ship, yet he would engage his word to set me safe on shore in the first port where we arrived. He added, that his suspicions were much increased by some very absurd speeches I had delivered at first to the sailors, and afterwards to himself, in relation to my closet or chest, as well as by my odd looks and behavior while I was at supper. I begged his patience to hear me tell my story, which I faithfully did from the last time I left England to the moment he first discovered me. And as truth always forces its way into rational minds, so this honest worthy gentleman, who had some tincture of learning, and very good sense, was immediately convinced of my candor and veracity. But further to confirm all I had said, I entreated him to give order that my cabinet should be brought, of which I had the key in my pocket (for he had already informed me how the seamen disposed of my closet), I opened it in his presence and showed him the small collection of rarities I made in the country from where I had been so strangely delivered. There was a comb I had contrived out of the stumps of the King's beard, and another of the same materials, but fixed into a paring of her Majesty's thumb-nail, which served for the back. There was a collection of needles and pins from a foot to half a yard long; four wasp-stings, like joiners' tacks; some combings of the Queen's hair; a gold ring which one day she made me a present of in a most obliging manner, taking it from her little finger, and throwing it over my head like a collar. I desired the Captain would please to accept this ring in return of his civilities, which he absolutely refused. I showed him a corn that I had cut off with my own hand, from a maid of honor's toe; it was about the bigness of a Kentish pippin, and grown so hard that when I returned to England, I got it hollowed into a cup, and set in silver. Lastly, I desired him to see the breeches I had then on, which were made of a mouse's skin. {P_2|CH_8 ^paragraph 10} I could force nothing on him but a footman's tooth, which I observed him to examine with great curiosity, and found he had a fancy for it. He received it with abundance of thanks, more than such a trifle could deserve. It was drawn by an unskillful surgeon, in a mistake, from one of Glumdalclitch's men, who was afflicted with the toothache, but it was as sound as any in his head. I got it cleaned, and put it into my cabinet. It was about a foot long, and four inches in diameter. The Captain was very well satisfied with plain relation I had given him, and said he hoped when we returned to England I would oblige the world by putting it in paper and making it public. My answer was that I thought we were already overstocked with books of travels; that nothing could now pass which was not extraordinary; wherein I doubted some authors less consulted truth than their own vanity, or interest, or the diversion of ignorant readers. That my story could contain little besides common events, without those ornamental descriptions of strange plants, trees, birds, and other animals, or of the barbarous customs and idolatry of savage people, with which most writers abound. However, I thanked him for his good opinion, and promised to take the matter into my thoughts. He said he wondered at one thing very much, which was, to bear me speak so loud, asking me whether the King or Queen of that country were thick of hearing. I told him it was what I had been used to for above two years past, and that I admired as much at the voices of him and his men, who seemed to me only to whisper, and yet I could hear them well enough. But when I spoke in that country, it was like a man talking in the street to another looking out from the top of a steeple, unless when I was placed on a table, or held in any person's hand, I told him, I had likewise observed another thing, that when I first got into the ship, and the sailors stood all about me, I thought they were the most little contemptible creatures I had ever beheld. For indeed while I was in that prince's country, I could never endure to look in a glass after my eyes had been accustomed to such prodigious objects, because the comparison gave me so despicable a conceit of myself. The Captain said that while we were at supper he observed me look at everything with a sort of wonder, and that I often seemed hardly able to contain my laughter, which he knew not well how to take, but imputed it to some disorder in my brain. I answered, it was very true; and I wondered how I could forbear, when I saw his dishes of the size of a silver three-pence, a leg of pork hardly a mouthful, a cup not so big as a nut-shell; and so I went on, describing the rest of his household stuff and provisions after the same manner. For, although the Queen had ordered a little equipage of all things necessary for me while I was in her service, yet my ideas were wholly taken up with what I saw on every side of me, and I winked at my own littleness as people do at their own faults. The Captain understood my raillery very well, and merrily replied with the old English proverb, that he doubted my eyes were bigger than my belly, for he did not observe my stomach so good, although I had fasted all day; and continuing in his mirth, protested he would have gladly given a hundred pounds to have seen my closet in the eagle's bill, and afterwards in its fall from so great a height into the sea; which would certainly have been a most astonishing object, worthy to have the description of it transmitted to future ages: and the comparison of Phaeton was so obvious, that he could not forbear applying it, although I did not much admire the conceit. The Captain having been at Tonquin, was in his return to England driven northeastward to the latitude of 44 degrees, and of longitude 143. But meeting a trade wind two days after I came on board him, we sailed southward a long time, and coasting New Holland kept our course west-southwest, and then south-southwest till we doubled the Cape of Good Hope. Our voyage was very prosperous, but I shall not trouble the reader with a journal of it. The Captain called in at one or two ports, and sent in his long-boat for provisions and fresh water, but I never went out of the ship till we came into the Downs, which was on the third day of June, 1706, about nine months after my escape. I offered to leave my goods in security for payment of my freight; but the Captain protested he would not receive one farthing. We took kind leave of each other, and I made him promise he would come to see me at my house in Redriff. I hired a horse and guide for five shillings, which I borrowed of the Captain. As I was on the road, observing the littleness of the houses, the trees, the cattle, and the people, I began to think myself in Lilliput. I was afraid of trampling on every traveler I met, and often called aloud to have them stand out of the way, so that I had like to have gotten one or two broken heads for my impertinence. {P_2|CH_8 ^paragraph 15} When I came to my own house, for which I was forced to enquire, one of the servants opening the door, I bent down to go in (like a goose under a gate) for fear of striking my head. My wife ran out to embrace me, but I stooped lower than her knees, thinking she could otherwise never be able to reach my mouth. My daughter kneeled to ask my blessing, but I could not see her till she arose, having been so long used to stand with my head and eyes erect to above sixty feet; and then I went to take her up with one hand, by the waist. I looked down upon the servants and one or two friends who were in the house, as if they had been pigmies, and I a giant. I told my wife, she had been too thrifty, for I found she had starved herself and her daughter to nothing. In short, I behaved myself so unaccountably, that they were all of the Captain's opinion when he first saw me, and concluded I had lost my wits. This I mention as an instance of the great power of habit and prejudice. In a little time I and my family and friends came to a right understanding: but my wife protested I should never go to sea any more; although my evil destiny so ordered that she had not power to hinder me, as the reader may know hereafter. In the mean time I here conclude the second part of my unfortunate voyages. - THE END OF THE SECOND PART PART III A VOYAGE TO LAPUTA, BALNIBARBI, GLUBBDUBDRIB, LUGGNAGG AND JAPAN (SEE PLATE 3) P_3|CH_1 CHAPTER I - I had not been at home above ten days, when Captain William Robinson, a Cornish man, Commander of the Hope-well, a stout ship of three hundred tons, came to my house. I had formerly been surgeon of another ship where he was master, and a fourth part owner, in a voyage to the Levant; he had always treated me more like a brother than an inferior officer, and hearing of my arrival made me a visit, as I apprehended only out of friendship, for nothing passed more than what is usual after long absences. But repeating his visits often, expressing his joy to find me in good health, asking whether I were now settled for life, adding that he intended a voyage to the East Indies in two months; at last he plainly invited me, though with some apologies, to be surgeon of the ship; that I should have another surgeon under me besides our two mates; that my salary should be double to the usual pay; and that having experienced my knowledge in sea affairs to be at least equal to his, he would enter into any engagement to follow my advice, as much as if I had share in the command. He said so many other obliging things, and I knew him to be so honest a man, that I could not reject his proposal; the thirst I had of seeing the world, notwithstanding my past misfortunes, continuing as violent as ever. The only difficulty that remained, was to persuade my wife, whose consent however I at last obtained by the prospect of advantage she proposed to her children. We set out the 5th of August, 1706, and arrived at Fort St. George the 11th of April 1707. We stayed there three weeks to refresh our crew, many of whom were sick. From there we went to Tonquin, where the Captain resolved to continue some time, because many of the goods he intended to buy were not ready, nor could he expect to be dispatched in some months. Therefore in hopes to defray some of the charges he must be at, he bought a sloop, loaded it with several sorts of goods, wherewith the Tonquinese usually trade to the neighboring islands, and putting fourteen men on board, whereof three were of the country, he appointed me master of the sloop, and gave me power to traffic for two months, while he transacted his affairs at Tonquin. We had not sailed more than three days, when a great storm arising, we were driven five days to the north-northeast, and then to the east; after which we had fair weather, but still with a pretty strong gale from the west. Upon the tenth day we were chased by two pirates, who soon overtook us, for my sloop was so deep loaden, that she sailed very slow, neither were we in a condition to defend ourselves. We were boarded about the same time by both the pirates, who entered furiously at the head of their men, but finding us all prostrate upon our faces (for so I gave order) they pinioned us with strong ropes, and setting a guard upon us, went to search the sloop. {P_3|CH_1 ^paragraph 5} I observed among them a Dutchman, who seemed to be of some authority, though he was not commander of either ship. He knew us by our countenances to be Englishmen, and jabbering to us in his own language, swore we should be tied back to back, and thrown into the sea. I spoke Dutch tolerably well; I told him who we were, and begged him in consideration of our being Christians and Protestants, of neighboring countries, in strict alliance, that he would move the Captains to take some pity on us. This inflamed his rage; he repeated his threatenings, and turning to his companions, spoke with great vehemence, in the Japanese language, as I suppose, often using the word Christianos. The largest of the two pirate ships was commanded by a Japanese Captain, who spoke a little Dutch, but very imperfectly. He came up to me, and after several questions, which I answered in great humility, he said we should not die. I made the Captain a very low bow, and then turning to the Dutchman, said, I was sorry to find more mercy in a heathen, than in a brother Christian. But I had soon reason to repent those foolish words; for that malicious reprobate, having often endeavored in vain to persuade both the Captains that I might be thrown into the sea (which they would not yield to after the promise made me, that I should not die), however prevailed so far as to have a punishment inflicted on me, worse in all human appearance than death itself. My men were sent by an equal division into both the pirate ships, and my sloop new manned. As to myself, it was determined that I should be set adrift in a small canoe, with paddles and a sail, and four days' provisions, which last the Japanese Captain was so kind to double out of his own stores, and would permit no man to search me. I got down into the canoe, while the Dutchman standing upon the deck, loaded me with all the curses and injurious terms his language could afford. About an hour before we saw the pirates, I had taken an observation, and found we were in the latitude of 46 N. and of longitude 183. When I was at some distance from the pirates, I discovered by my pocket glass several islands to the southeast. I set up my sail, the wind being fair, with a design to reach the nearest of those islands, which I made a shift to do in about three hours. It was all rocky; however I got many birds' eggs, and striking fire, I kindled some heath and dry seaweed, by which I roasted my eggs. I ate no other supper, being resolved to spare my provisions as much as I could. I passed the night under the shelter of a rock, strowing some heath under me, and slept pretty well. The next day I sailed to another island, and then to a third and fourth, sometimes using my sail, and sometimes my paddles. But not to trouble the reader with a particular account of my distresses, let it suffice that on the fifth day I arrived at the last island in my sight, which lay south-southeast to the former. This island was at a greater distance than I expected, and I did not reach it in less than five hours. I encompassed it almost around before I could find a convenient place to land in, which was a small creek about three times the wideness of my canoe. I found the island to be all rocky, only a little intermingled with tufts of grass and sweet smelling herbs. I took out my small provisions, and after having refreshed myself, I secured the remainder in a cave, whereof there were great numbers. I gathered plenty of eggs upon the rocks, and got a quantity of dry seaweed and parched grass, which I designed to kindle the next day, and roast my eggs as well as I could. (For I had about me my flint, steel, match, and burning glass.) I lay all night in the cave where I had lodged my provisions. My bed was the same dry grass and seaweed which I intended for fuel. I slept very little, for the disquiets of my mind prevailed over my weariness, and kept me awake. I considered how impossible it was to preserve my life in so desolate a place, and how miserable my end must be. Yet I found myself so listless and desponding that I had not the heart to rise, and before I could get spirits enough to creep out of my cave the day was far advanced. I walked a while among the rocks; the sky was perfectly clear, and the sun so hot that I was forced to turn my face from it; when all of a sudden it became obscured, as I thought, in a manner very different from what happens by the interposition of a cloud. I turned back, and perceived a vast opaque body between me and the sun, moving forwards towards the island: it seemed to be about two miles high, and hid the sun six or seven minutes, but I did not observe the air to be much colder, or the sky more darkened, than if I had stood under the shade of a mountain. As it approached nearer over the place where I was, it appeared to be a firm substance, the bottom flat, smooth, and shining very bright from the reflection of the sea below. I stood upon a height about two hundred yards from the shore, and saw this vast body descending almost to a parallel with me, at less than an English mile distance. I took out my pocket perspective, and could plainly discover numbers of people moving up and down the sides of it, which appeared to be sloping, but what those people were doing, I was not able to distinguish. {P_3|CH_1 ^paragraph 10} The natural love of life gave me some inward motions of joy, and I was ready to entertain a hope that this adventure might some way or other help to deliver me from the desolate place and condition I was in. But at the same time the reader can hardly conceive my astonishment, to behold an island in the air, inhabited by men, who were able (as it should seem) to raise or sink, or put it into a progressive motion, as they pleased. But not being at that time in a disposition to philosophise upon this phenomenon, I rather chose to observe what course the island would take, because it seemed for a while to stand still. Yet soon after it advanced nearer, and I could see the sides of it, encompassed with several gradations of galleries, and stairs at certain intervals, to descend from one to the other. In the lowest gallery I beheld some people fishing with long angling rods, and others looking on. I waved my cap (for my hat was long since worn out) and my handkerchief towards the island; and upon its nearer approach, I called and shouted with the utmost strength of my voice; and then looking circumspectly, I beheld a crowd gather to that side which was most in my view. I found by their pointing towards me and to each other, that they plainly discovered me, although they made no return to my shouting. But I could see four or five men running in great haste up the stairs to the top of the island, who then disappeared. I happened rightly to conjecture, that these were sent for orders to some person in authority upon this occasion. The number of people increased, and in less than half an hour the island was moved and raised in such a manner, that the lowest gallery appeared in a parallel of less than a hundred yards distance from the height where I stood. I then put myself into the most supplicating postures, and spoke in the humblest accent, but received no answer. Those who stood nearest over against me seemed to be persons of distinction, as I supposed by their habit. They conferred earnestly with each other, looking often upon me. At length one of them called out in a clear, polite, smooth dialect, not unlike in sound to the Italian; and therefore I returned an answer in that language, hoping at least that the cadence might be more agreeable to his ears. Although neither of us understood the other, yet my meaning was easily known, for the people saw the distress I was in. They made signs for me to come down from the rock and go towards the shore, which I accordingly did; and the flying island being raised to a convenient height, the verge directly over me, a chain was let down from the lowest gallery, with a seat fastened to the bottom, to which I fixed myself, and was drawn up by pulleys. P_3|CH_2 CHAPTER II - At my alighting I was surrounded by a crowd of people, but those who stood nearest seemed to be of better quality. They beheld me with all the marks and circumstances of wonder; neither indeed was I much in their debt, having never till then seen a race of mortals so singular in their shapes, habits, and countenances. Their heads were all reclined either to the right or the left; one of their eyes turned inward, and the other directly up to the zenith. Their outward garments were adorned with the figures of suns, moons, and stars, interwoven with those of fiddles, flutes, harps, trumpets, guitars, harpsichords, and many other instruments of music, unknown to us in Europe. I observed here and there many in the habit of servants, with a blown bladder fastened like a flail to the end of a short stick, which they carried in their hands. In each bladder was a small quantity of dried pease, or little pebbles (as I was afterwards informed). With these bladders they now and then flapped the mouths and ears of those who stood near them, of which practice I could not then conceive the meaning; it seems the minds of these people are so taken up with intense speculations, that they neither can speak, nor attend to the discourses of others, without being roused by some external taction upon the organs of speech and hearing; for which reason those persons who are able to afford it always keep a flapper (the original is climenole) in their family, as one of their domestics, nor ever walk abroad or make visits without him. And the business of this officer is, when two or more persons are in company, gently to strike with his bladder the mouth of him who is to speak, and the right ear of him or them to whom the speaker addresses himself. This flapper is likewise employed diligently to attend his master in his walks, and upon occasion to give him a soft flap on his eyes, because he is always so wrapped up in cogitation, that he is in manifest danger of falling down every precipice, and bouncing his head against every post, and in the streets, of jostling others, or being jostled himself into the kennel. It was necessary to give the reader this information, without which he would be at the same loss with me, to understand the proceedings of these people, as they conducted me the stairs, to the top of the island, and from there to the royal palace. While we were ascending, they forgot several times what they were about, and left me to myself, till their memories were again roused by their flappers; for they appeared altogether unmoved by the sight of my foreign habit and countenance, and by the shouts of the vulgar, whose thoughts and minds were more disengaged. At last we entered the palace, and proceeded into the chamber of presence, where I saw the King seated on his throne, attended on each side by persons of prime quality. Before the throne was a large table filled with globes and spheres, and mathematical instruments of all kinds. His Majesty took not the least notice of us, although our entrance was not without sufficient noise, by the concourse of all persons belonging to the court. But he was then deep in a problem, and we attended at least an hour, before he could solve it. There stood by him on each side a young page, with flaps in their hands, and when they saw he was at leisure, one of them gently struck his mouth, and the other his right ear; at which he started like one awakened on the sudden, and looking towards me and the company I was in, recollected the occasion of our coming, whereof he had been informed before. He spoke some words, whereupon immediately a young man with a flap came up to my side, and flapped me gently on the right ear; but I made signs, as well as I could, that I had no occasion for such an instrument; which, as I afterwards found, gave his Majesty and the whole court a very mean opinion of my understanding. The King, as far as I could conjecture, asked me several questions, and I addressed myself to him in all the languages I had. When it was found that I could neither understand nor be understood, I was conducted by the King's order to an apartment in his palace (this prince being distinguished above all his predecessors for his hospitality to strangers), where two servants were appointed to attend me. My dinner was brought, and four persons of quality, whom I remembered to have seen very near the King's person, did me the honor to dine with me. We had two courses of three dishes each. In the first course there was a shoulder of mutton, cut into an equilateral triangle, a piece of beef into a rhomboides, and a pudding into a cycloid. The second course was two ducks, trussed up into the form of fiddles; sausages and puddings resembling flutes and hautboys, and a breast of veal in the shape of a harp. The servants cut our bread into cones, cylinders, parallelograms, and several other mathematical figures. While we were at dinner, I made bold to ask the names of several things in their language; and those noble persons, by the assistance of their flappers, delighted to give me answers, hoping to raise my admiration of their great abilities, if I could be brought to converse with them. I was soon able to call for bread and drink, or whatever else I wanted. After dinner my company withdrew, and a person was sent to me by the King's order, attended by a flapper. He brought with him pen, ink, and paper, and three or four books, giving me to understand by signs, that he was sent to teach me the language. We sat together four hours, in which time I wrote down a great number of words in columns, with the translations over against them. I likewise made a shift to learn several short sentences. For my tutor would order one of my servants to fetch something, to turn about, to make a bow, to sit, or stand, or walk, and the like. Then I took down the sentence in writing. He showed me also in one of his books the figures of the sun, moon, and stars, the zodiac, the tropics, and polar circles, together with the denominations of many figures of planes and solids. He gave me the names and descriptions of all the musical instruments, and the general terms of art in playing on each of them. After he had left me, I placed all my words with their interpretations in alphabetical order. And thus in a few days, by the help of a very faithful memory, I got some insight into their language. {P_3|CH_2 ^paragraph 5} The word, which I interpret the Flying or Floating Island, is in the original Laputa, whereof I could never learn the true etymology. Lap in the old obsolete language signifies high, and untuh, a governor, from which they say by corruption was derived Laputa, from Lapuntuh. But I do not approve of this derivation, which seems to be a little strained. I ventured to offer to the learned among them a conjecture of my own, that Laputa was quasi lap outed; lap signifying properly the dancing of the sunbeams in the sea, and outed, a wing, which however I shall not obtrude, but submit to the judicious reader. Those to whom the King had entrusted me, observing how ill I was clad, ordered a tailor to come next morning, and take my measure for a suit of clothes. This operator did his office after a different manner from those of his trade in Europe. He first took my height by a quadrant, and then with a rule and compasses described the dimensions and outlines of my whole body, all which he entered upon paper, and in six days brought my clothes very ill made, and quite out of shape, by happening to mistake a figure in the calculation. But my comfort was, that I observed such accidents very frequent, and little regarded. During my confinement for want of clothes, and by an indisposition that held me some days longer, I much enlarged my dictionary; and when I went next to court, was able to understand many things the King spoke, and to return him some kind of answers. His Majesty had given orders that the island should move northeast and by east, to the vertical point over Lagado, the metropolis of the whole kingdom below upon the firm earth. It was about ninety leagues distant, and our voyage lasted four days and an half. I was not in the least sensible of the progressive motion made in the air by the island. On the second morning about eleven o'clock, the King himself in person, attended by his nobility, courtiers, and officers, having prepared all their musical instruments, played on them for three hours without intermission, so that I was quite stunned with the noise; neither could I possibly guess the meaning, till my tutor informed me. He said that the people of their island had their ears adapted to hear the music of the spheres, which always played at certain periods, and the court was now prepared to bear their part in whatever instrument they most excelled. In our journey towards Lagado, the capital city, his Majesty ordered that the island should stop over certain towns and villages, from whence he might receive the petitions of his subjects. And to this purpose several packthreads were let down with small weights at the bottom. On these packthreads the people strung their petitions, which mounted up directly like the scraps of paper fastened by school boys at the end of the string that holds their kite. Sometimes we received wine and victuals from below, which were drawn up by pulleys. The knowledge I had in mathematics gave me great assistance in acquiring their phraseology, which depended much upon that science and music; and in the latter I was not unskilled. Their ideas are perpetually conversant in lines and figures. If they would, for example, praise the beauty of a woman, or any other animal, they describe it by rhombs, circles, parallelograms, ellipses, and other geometrical terms, or by words of art drawn from music, needless here to repeat. I observed in the King's kitchen all sorts of mathematical and musical instruments, after the figures of which they cut up the joints that were served to his Majesty's table. {P_3|CH_2 ^paragraph 10} Their houses are very ill built, the walls bevel without one right angle in any apartment, and this defect arises from the contempt they bear to practical geometry, which they despise as vulgar and mechanic, those instructions they give being too refined for the intellectuals of their workmen, which occasions perpetual mistakes. And although they are dexterous enough upon a piece of paper in the management of the rule, the pencil, and the divider, yet in the common actions and behavior of life, I have not seen a more clumsy, awkward, and unhandy people, nor so slow and perplexed in their conceptions upon all other subjects, except those of mathematics and music. They are very bad reasoners, and vehemently given to opposition, unless when they happen to be of the right opinion, which is seldom their case. Imagination, fancy, and invention, they are wholly strangers to, nor have any words in their language by which those ideas can be expressed; the whole compass of their thoughts and mind being shut up within the two forementioned sciences. Most of them, and especially those who deal in the astronomical part, have great faith in judicial astrology, although they are ashamed to own it publicly. But what I chiefly admired, and thought altogether unaccountable, was the strong disposition I observed in them towards news and politics, perpetually enquiring into public affairs, giving their judgments in matters of state, and passionately disputing every inch of a party opinion. I have indeed observed the same disposition among most of the mathematicians I have known in Europe, although I could never discover the least analogy between the two sciences; unless those people suppose, that because the smallest circle hath as many degrees as the largest, therefore the regulation and management of the world require no more abilities than the handling and turning of a globe. But I rather take this quality to spring from a very common infirmity of human nature, inclining us to be more curious and conceited in matters where we have least concern, and for which we are least adapted either by study or nature. These people are under continual disquietudes, never enjoying a minute's peace of mind; and their disturbances proceed from causes which very little affect the rest of mortals. Their apprehensions arise from several changes they dread in the celestial bodies. For instance, that the earth, by the continual approaches of the sun towards it, must in course of time be absorbed or swallowed up. That the face of the sun will by degrees be encrusted with its own effluvia, and give no more light to the world. That the earth very narrowly escaped a brush from the tail of the last comet, which would have infallibly reduced it to ashes; and that the next, which they have calculated for thirty-one years hence, will probably destroy us. For if in its perihelion it should approach within a certain degree of the sun (as by their calculations they have reason to dread) it will conceive a degree of heat ten thousand times more intense than that of red-hot glowing iron; and in its absence from the sun, carry a blazing tail ten hundred thousand and fourteen miles long; through which if the earth should pass at the distance of one hundred thousand miles from the nucleus or main body of the comet, it must in its passage be set on fire, and reduced to ashes. That the sun daily spending its rays without any nutriment to supply them, will at last be wholly consumed and annihilated; which must be attended with the destruction of this earth, and of all the planets that receive their light from it. They are so perpetually alarmed with the apprehensions of these and the like impending dangers, that they can neither sleep quietly in their beds, nor have any relish for the common pleasures or amusements of life. When they meet an acquaintance in the morning, the first question is about the sun's health, how he looked at his setting and rising, and what hopes they have to avoid the stroke of the approaching comet. This conversation they are apt to run into with the same temper that boys discover, in delighting to hear terrible stories of sprites and hobgoblins, which they greedily listen to, and dare not go to bed for fear. The women of the island have abundance of vivacity: they contemn their husbands, and are exceedingly fond of strangers, whereof there is always a considerable number from the continent below, attending at court, either upon affairs of the several towns and corporations, or their own particular occasions, but are much despised, because they want the same endowments. Among these the ladies choose their gallants: but the vexation is, that they act with too much ease and security, for the husband is always so rapt in speculation, that the mistress and lover may proceed to the greatest familiarities before his face, if he be but provided with paper and implements, and without his flapper at his side. {P_3|CH_2 ^paragraph 15} The wives and daughters lament their confinement to the island, although I think it the most delicious spot of ground in the world; and although they live here in the greatest plenty and magnificence, and are allowed to do whatever they please, they long to see the world, and take the diversions of the metropolis, which they are not allowed to do without a particular license from the King; and this is not easy to be obtained, because the people of quality have found by frequent experience how hard it is to persuade their women to return from below. I was told that a great court lady, who had several children, is married to the prime minister, the richest subject in the kingdom, a very graceful person, extremely fond of her, and lives in the finest palace of the island, went down to Lagado, on the pretense of health, there hid herself for several months, till the King sent a warrant to search for her, and she was found in an obscure eatinghouse all in rags, having pawned her clothes to maintain an old deformed footman, who beat her every day, and in whose company she was taken much against her will. And although her husband received her with all possible kindness, and without the least reproach, she soon after contrived to steal down again with all her jewels, to the same gallant, and has not been heard of since. This may perhaps pass with the reader rather for an European or English story, than for one of a country so remote. But he may please to consider, that the caprices of are not limited by any climate or nation, and that they are much more uniform than can be easily imagined. In about a month's time I had made a tolerable proficiency in their language, and was able to answer most of the King's questions, when I had the honor to attend him. His Majesty discovered not the least curiosity to inquire into the laws, government, history, religion, or manners of the countries where I had been, but confined his questions to the state of mathematics, and received the account I gave him with great contempt and indifference, though often roused by his flapper on each side. P_3|CH_3 CHAPTER III - I desired leave of this prince to see the curiosities of the island, which he was graciously pleased to grant, and ordered my tutor to attend me. I chiefly wanted to know to what cause in art or in nature it owed its several motions, whereof I will now give a philosophical account to the reader. The Flying or Floating Island is exactly circular, its diameter 7837 yards, or about four miles and a half, and consequently contains ten thousand acres. It is three hundred yards thick. The bottom or under surface, which appears to those who view it from below, is one even regular plate of adamant, shooting up to the height of about two hundred yards. Above it lie the several minerals in their usual order, and over all is a coat of rich mold, ten or twelve feet deep. The declivity of the upper surface, from the circumference to the center, is the natural cause why all the dews and rains which fall upon the island, are conveyed in small rivulets toward the middle, where they are emptied into four large basins, each of about half a mile in circuit, and two hundred yards distant from the center. From these basins the water is continually exhaled by the sun in the daytime, which effectually prevents their overflowing. Besides, as it is in the power of the monarch to raise the island above the region of clouds and vapors, he can prevent the falling of dews and rains whenever he pleases. For the highest clouds cannot rise above two miles, as naturalists agree, at least they were never known to do so in that country. At the centre of the island there is a chasm about fifty yards in diameter, from whence the astronomers descend into a large dome, which is therefore called Flandona Gagnole, or the Astronomer's Cave, situated at the depth of a hundred yards beneath the upper surface of the adamant. In this cave are twenty lamps continually burning, which from the reflection of the adamant cast a strong light into every part. The place is stored with great variety of sextants, quadrants, telescopes, astrolabes, and other astronomical instruments. But the greatest curiosity, upon which the fate of the island depends, is a loadstone of a prodigious size, in shape resembling a weaver's shuttle. It is in length six yards, and in the thickest part at least three yards over. This magnet is sustained by a very strong axle of adamant passing through its middle, upon which it plays, and is poised so exactly that the weakest hand can turn it. It is hooped round with a hollow cylinder of adamant, four feet deep, as many thick, and twelve yards in diameter, placed horizontally, and supported by eight adamantine feet, each six yards high. In the middle of the concave side there is a groove twelve inches deep, in which the extremities of the axle are lodged, and turned round as there is occasion. The stone cannot be moved from its place by any force, because the hoop and its feet are one continued piece with that body of adamant which constitutes the bottom of the island. By means of this loadstone, the island is made to rise and fall, and move from one place to another. For with respect to that part of the earth over which the monarch presides, the stone is endued at one of its sides with an attractive power, and at the other with a repulsive. Upon placing the magnet erect with its attracting end towards the earth, the island descends; but when the repelling extremity points downwards, the island mounts directly upwards. When the position of the stone is oblique, the motion of the island is so too. For in this magnet the forces always act in lines parallel to its direction. {P_3|CH_3 ^paragraph 5} (SEE PLATE 4) By this oblique motion the island is conveyed to different parts of the monarch's dominions. To explain the manner of its progress, let AB represent a line drawn cross the dominions of Balnibarbi, let the line cd represent the loadstone, of which let d be the repelling end, and c the attracting end, the island being over C; let the stone be placed in the position cd, with its repelling end downwards; then the island will be driven upwards obliquely towards D. When it is arrived at D, let the stone be turned upon its axle, till its attracting end points towards E, and then the island will be carried obliquely towards E; where if the stone be again turned upon its axle till it stands in the position EF, with its repelling point downwards, the island will rise obliquely towards F, where by directing the attracting end towards G, the island may be carried to G, and from G to H, by turning the stone, so as to make its repelling extremity point directly downwards. And thus by changing the situation of the stone as often as there is occasion, the island is made to rise and fall by turns in an oblique direction, and by those alternate risings and fallings (the obliquity being not considerable) is conveyed from one part of the dominions to the other. But it must be observed that this island cannot move beyond the extent of the dominions below, nor can it rise above the height of four miles. For which the astronomers (who have written large systems concerning the stone) assign the following reason: that the magnetic virtue does not extend beyond the distance of four Miles, and that the mineral which acts upon the stone in the bowels of the earth, and in the sea about six leagues distant from the shore, is not diffused through the whole globe, but terminated with the limits of the King's dominions; and it was easy from the great advantage of such a superior situation, for a prince to bring under his obedience whatever country lay within the attraction of that magnet. When the stone is put parallel to the plane of the horizon, the island stands still; for in that case the extremities of it being at equal distance from the earth, act with equal force, the one in drawing downwards, the other in pushing upwards, and consequently no motion can ensue. This loadstone is under the care of certain astronomers, who from time to time give it such positions as the monarch directs. They spend the greatest part of their lives in observing the celestial bodies, which they do by the assistance of glasses far excelling ours in goodness. For although their largest telescopes do not exceed three feet, they magnify much more than those of a hundred yards among us, and at the same time show the stars with greater clearness. This advantage has enabled them to extend their discoveries much farther than our astronomers in Europe; for they have made a catalogue of ten thousand fixed stars, whereas the largest of ours do not contain above one third part of that number. They have likewise discovered two lesser stars, or satellites, which revolve about Mars, whereof the innermost is distant from the center of the primary planet exactly three of his diameters, and the outermost five; the former revolves in the space of ten hours, and the latter in twenty-one and a half; so that the squares of their periodical times are very near in the same proportion with the cubes of their distance from the center of Mars, which evidently shows them to be governed by the same law of gravitation that influences the other heavenly bodies. {P_3|CH_3 ^paragraph 10} They have observed ninety-three different comets, and settled their periods with great exactness. If this be true (and they affirm it with great confidence), it is much to be wished that their observations were made public, whereby the theory of comets, which at present is very lame and defective, might be brought to the same perfection with other parts of astronomy. The King would be the most absolute prince in the universe, if he could but prevail on a ministry to join with him; but these having their estates below on the continent, and considering that the office of a favorite has a very uncertain tenure, would never consent to the enslaving their country. If any town should engage in rebellion or mutiny, fall into violent factions, or refuse to pay the usual tribute, the King has two methods of reducing them to obedience. The first and the mildest course by keeping the island hovering over such a town, and the lands about it, whereby he can deprive them of the benefit of the sun and the rain, and consequently afflict the inhabitants with dearth and diseases. And if the crime deserve it, they are at the same time pelted from above with great stones, against which they have no defense but by creeping into cellars or caves, while the roofs of their houses are beaten to pieces. But if they still continue obstinate, or offer to raise insurrections, he proceeds to the last remedy, by letting the island drop directly upon their heads, which makes a universal destruction both of houses and men. However, this is an extremity to which the prince is seldom driven, neither indeed is he willing to put it in execution, nor dare his ministers advise him to an action, which as it would render them odious to the people, so it would be a great damage to their own estates, which lie all below, for the island is the King's demesne. But there is still indeed a more weighty reason, why the kings of this country have been always averse from executing so terrible an action, unless upon the utmost necessity. For if the town intended to be destroyed should have in it any tall rocks, as it generally falls out in the larger cities, a situation probably chosen at first with a view to prevent such a catastrophe; or if it abound in high spires, or pillars of stone, a sudden fall might endanger the bottom or under surface of the island, which, although it consist, as I have said, of one entire adamant two hundred yards thick, might happen to crack by too great a shock, or burst by approaching too near the fires from the houses below, as the backs both of iron and stone will often do in our chimneys. Of all this the people are well apprised, and understand how far to carry their obstinacy, where their liberty or property is concerned. And the King, when he is highest provoked, and most determined to press a city to rubbish, orders the island to descend with great gentleness, out of a pretense of tenderness to his people, but indeed for fear of breaking the adamantine bottom; in which case it is the opinion of all their philosophers that the loadstone could no longer hold it up, and the whole mass would fall to the ground. About three years before my arrival among them, while the King was in his progress over his dominions, there happened an extraordinary accident which had like to have put a period to the fate of that monarchy, at least as it is now instituted. Lindalino, the second city in the kingdom, was the first his Majesty visited in his progress. Three days after his departure the inhabitants, who had often complained of great oppressions, shut the town gates, seized on the governor, and with incredible speed and labor erected four large towers, one at every corner of the city (which is an exact square), equal in height to a strong pointed rock that stands directly in the center of the city. Upon the top of each tower, as well as upon the rock, they fixed a great loadstone, and in case their design should fail, they had provided a vast quantity of the most combustible fuel, hoping to burst therewith the adamantine bottom of the island, if the loadstone project should miscarry. {P_3|CH_3 ^paragraph 15} It was eight months before the King had perfect notice that the Lindalinians were in rebellion. He then commanded that the island should be wafted over the city. The people were unanimous, and had laid in stores of provisions, and a great river runs through the middle of the town. The King hovered over them several days to deprive them of the sun and the rain. He ordered many packthreads to be let down, yet not a person offered to send up a petition, but instead thereof very bold demands, the redress of all their grievances, great immunities, the choice of their own governor, and other like exorbitances. Upon which his Majesty commanded all the inhabitants of the island to cast great stones from the lower gallery into the town; but the citizens had provided against this mischief by conveying their persons and effects into the four towers, and other strong buildings, and vaults underground. The King being now determined to reduce this proud people, ordered that the island should descend gently within forty yards of the top of the towers and rock. This was accordingly done; but the officers employed in that work found the descent much speedier than usual, and by turning the loadstone could not without great difficulty keep it in a firm position, but found the island inclining to fall. They sent the King immediate intelligence of this astonishing event, and begged his Majesty's permission to raise the island higher; the King consented, a general council was called, and the officers of the loadstone ordered to attend. One of the oldest and most expert among them obtained leave to try an experiment. He took a strong line of a hundred yards, and the island being raised over the town above the attracting power they had felt, he fastened a piece of adamant to the end of his line, which had in it a mixture of iron mineral, of the same nature with that whereof the bottom or lower surface of the island is composed, and from the lower gallery let it down slowly towards the top of the towers. The adamant was not descended four yards, before the officer felt it drawn so strongly downward that he could hardly pull it back. He then threw down several small pieces of adamant, and observed that they were all violently attracted by the top of the tower. The same experiment was made on the other three towers, and on the rock with the same effect. This incident broke entirely the King's measures, and (to dwell no longer on other circumstances) he was forced to give the town their own conditions. I was assured by a great minister that if the island had descended so near the town as not to be able to raise itself, the citizens were determined to fix it for ever, to kill the King and all his servants, and entirely change the government. By a fundamental law of this realm, neither the king, nor either of his two elder sons, are permitted to leave the island; nor the queen, till she is past child-bearing. P_3|CH_4 CHAPTER IV - Although I cannot say that I was ill treated in this island, yet I must confess I thought myself too much neglected, not without some degree of contempt. For neither prince nor people appeared to be curious in any part of knowledge, except mathematics and music, wherein I was far their inferior, and upon that account very little regarded. On the other side, after having seen all the curiosities of the island, I was very desirous to leave it, being heartily weary of those people. They were indeed excellent in two sciences for which I have great esteem, and wherein I am not unversed; but at the same time so abstracted and involved in speculation, that I never met with such disagreeable companions. I conversed only with women, tradesmen, flappers, and court pages, during two months of my abode there, by which at last I rendered myself extremely contemptible; yet these were the only people from whom I could ever receive a reasonable answer. I had obtained by hard study a good degree of knowledge in their language; I was weary of being confined to an island where I received so little countenance, and resolved to leave it with the first opportunity. There was a great lord at court, nearly related to the King, and for that reason alone used with respect. He was universally reckoned the most ignorant and stupid person among them. He had performed many eminent services for the crown, had great natural and acquired parts, adorned with integrity and honor, but so ill an ear for music, that his detractors reported he had been often known to beat time in the wrong place; neither could his tutors without extreme difficulty teach him to demonstrate the most easy proposition in the mathematics. He was pleased to show me many marks of favor, often did me the honor of a visit, desired to be informed in the affairs of Europe, the laws and customs, the manners and learning of the several countries where I had traveled. He listened to me with great attention, and made very wise observations on all I spoke. He had two flappers attending him for state, but never made use of them except at court, and in visits of ceremony, and would always command them to withdraw when we were alone together. I entreated this illustrious person to intercede in my behalf with his Majesty for leave to depart, which he accordingly did, as he was pleased to tell me, with regret: for indeed he had made me several offers very advantageous, which however I refused with expressions of the highest acknowledgment. {P_3|CH_4 ^paragraph 5} On the 16th day of February I took leave of his Majesty and the court. The King made me a present to the value of about two hundred pounds English, and my protector his kinsman as much more, together with a letter of recommendation to a friend of his in Lagado, the metropolis. The island being then hovering over a mountain about two miles from it, I was let down from the lowest gallery, in the same manner as I had been taken up. The continent, as far as it is subject to the monarch of the Flying Island, passes under the general name of Balnibarbi, and the metropolis, as I said before, is called Lagado. I felt some little satisfaction in finding myself on firm ground. I walked to the city without any concern, being clad like one of the natives, and sufficiently instructed to converse with them. I soon found out the person's house to whom I was ended, presented my letter from his friend the grandee in the island, and was received with much kindness. This great lord, whose name was Munodi, ordered me an apartment in his own house, where I continued during my stay, and was entertained in a most hospitable manner. The next morning after my arrival, he took me in his chariot to see the town, which is about half the size of London, but the houses very strangely built, and most of them out of repair. The people in the streets walked fast, looked wild, their eyes fixed, and were generally in rags. We passed through one of the town gates, and went about three miles into the country, where I saw many laborers working with several sorts of tools in the ground, but was not able to conjecture what they were about; neither did I observe any expectation either of corn or grass, although the soil appeared to be excellent. I could not forbear admiring at these odd appearances both in town and country, and I made bold to desire my conductor, that he would be pleased to explain to me what could be meant by so many busy heads, hands, and faces, both in the streets and the fields, because I did not discover any good effects they produced; but on the contrary, I never knew a soil so unhappily cultivated, houses so ill contrived and so ruinous, or a people whose countenances and habit expressed so much misery and want. This Lord Munodi was a person of the first rank, and had been some years Governor of Lagado, but by a cabal of ministers was discharged for insufficiency. However, the King treated him with tenderness, as a well-meaning man, but of a low contemptible understanding. When I gave that free censure of the country and its inhabitants, he made no further answer than by telling me that I had not been long enough among them to form a judgment, and that the different nations of the world had different customs, with other common topics to the same purpose. But when we returned to his palace, he asked me how I liked the building, what absurdities I observed, and what quarrel I had with the dress or looks of his domestics. This he might safely do, because every thing about him was magnificent, regular, and polite. I answered that his Excellency's prudence, quality, and fortune, had exempted him from those defects which folly and beggary had produced in others. He said if I would go with him to his country house, about twenty miles distant, where his estate lay, there would be more leisure for this kind of conversation. I told his Excellency that I was entirely at his disposal, and accordingly we set out next morning. {P_3|CH_4 ^paragraph 10} During our journey he made me observe the several methods used by farmers in managing their lands, which to me were wholly unaccountable; for except in some very few places I could not discover one ear of corn or blade of grass. But in three hours' traveling the scene was wholly altered; we came into a most beautiful country; farmers' houses at small distances, neatly built; the fields enclosed, containing vineyards, corn grounds, and meadows. Neither do I remember to have seen a more delightful prospect. His Excellency observed my countenance to clear up; he told me with a sigh that there his estate began, and would continue the same till we should come to his house. That his countrymen ridiculed and despised him for managing his affairs no better, and for setting so ill an example to the kingdom, which however was followed by very few, such as were old, and willful, and weak like himself. We came at length to the house, which was indeed a noble structure, built according to the best rules of ancient architecture. The fountains, gardens, walks, avenues, and groves were all disposed with exact judgment and taste. I gave due praises to every thing I saw, whereof his Excellency took not the least notice till after supper, when, there being no third companion, he told me with a very melancholy air that he doubted he must thrown down his houses in town and country, to rebuild them after the present mode, destroy all his plantations, and cast others into such a form as modern usage required, and give the same directions to all his tenants, unless he would submit to incur the censure of pride, singularity, affectation, ignorance, caprice, and perhaps increase his Majesty's displeasure. That the admiration I appeared to be under would cease or diminish when he had informed me of some particulars, which probably I never heard of at court, the people there being too much taken up in their own speculations, to have regard to what passed here below. The sum of his discourse was to this effect. That about forty years ago certain persons went up to Laputa, either upon business or diversion, and after five months continuance came back with a very little smattering in mathematics, but full of volatile spirits acquired in that airy region. That these persons upon their return began to dislike the management of every thing below, and fell into schemes of putting all arts, sciences, languages, and mechanics upon a new foot. To this end they procured a royal patent for erecting an Academy of Projectors in Lagado; and the humor prevailed so strongly among the people, that there is not a town of any consequence in the kingdom without such an academy. In these colleges the professors contrive new rules and methods of agriculture and building, and new instruments and tools for all trades and manufactures, whereby, as they undertake, one man shall do the work of ten; a palace may be built in a week, of materials so durable as to last forever without repairing. All the fruits of the earth shall come to maturity at whatever season we think fit to choose, and increase a hundred fold more than they do at present, with innumerable other happy proposals. The only inconvenience is, that none of these projects are yet brought to perfection, and in the meantime, the whole country lies miserably waste, the houses in ruins, and the people without food or clothes. By all which, instead of being discouraged, they are fifty times more violently bent upon prosecuting their schemes, driven equally on by hope and despair; that as for himself, being not of an enterprising spirit, he was content to go on in the old forms, to live in the houses his ancestors had built, and act as they did in every part of life without innovation. That some few other persons of quality and gentry had done the same, but were looked on with an eye of contempt and ill-will, as enemies to art, ignorant, and ill commonwealth's-men, preferring their own ease and sloth before the general improvement of their country. His Lordship added that he would not by any further particulars prevent the pleasure I should certainly take in viewing the grand Academy, whither he was resolved I should go. He only desired me to observe a ruined building upon the side of a mountain about three miles distant, of which he gave me this account. That he had a very convenient mill within half a mile of his house, turned by a current from a large river, and sufficient for his own family as well as a great number of his tenants. That about seven years ago a club of those projectors came to him with proposals to destroy this mill, and build another on the side of that mountain, on the long ridge whereof a long canal must be cut for a repository of water, to be conveyed up by pipes and engines to supply the mill; because the wind and air upon a height agitated the water, and thereby made it fitter for motion; and because the water descending down a declivity would turn the mill with half the current of a river whose course is more upon a level. He said, that being then not very well with the court, and pressed by many of his friends, he complied with the proposal; and after employing an hundred men for two years, the work miscarried, the projectors went off, laying the blame entirely. upon him, railing at him ever since, and putting others upon the same experiment, with equal assurance of success, as well as equal disappointment. {P_3|CH_4 ^paragraph 15} In a few days we came back to town, and his Excellency, considering the bad character he had in the Academy, would not go with me himself, but recommended me to a friend of his to bear me company thither. My lord was pleased to represent me as a great admirer of projects, and a person of much curiosity and easy belief; which indeed was not without truth, for I had myself been a sort of projector in my younger days. P_3|CH_5 CHAPTER V - This Academy is not an entire single building, but a continuation of several houses on both sides of a street, which growing waste was purchased and applied to that use. I was received very kindly by the Warden, and went for many days to the Academy. Every room has in it one or more projectors, and I believe I could not be in fewer than five hundred rooms. The first man I saw was of a meager aspect, with sooty hands and face, his hair and beard long, ragged and singed in several places. His clothes, shirt, and skin were all of the same color. He had been eight years upon a project for extracting sunbeams out of cucumbers, which were to be put into vials hermetically sealed, and let out to warm the air in raw inclement summers. He told me he did not doubt in eight years more he should be able to supply the Governor's gardens with sunshine at a reasonable rate; but he complained that his stock was low, and entreated me to give him something as an encouragement to ingenuity, especially since this had been a very dear season for cucumbers. I made him a small present, for my lord had furnished me with money on purpose, because he knew their practice of begging from all who go to see them. I went into another chamber, but was ready to hasten back, being almost overcome with a horrible stink. My conductor pressed me forward, conjuring me in a whisper to give no offense, which would be highly resented, and therefore I dare not so much as stop my nose. The projector of this cell was the most ancient student of the Academy; his face and beard were of a pale yellow; his hands and clothes daubed over with filth. When I was presented to him, he gave me a close embrace (a compliment I could well have excused). His employment from his first coming into the Academy, was an operation to reduce human excrement to its original food, by separating the several parts, removing the tincture which it receives from the gall, making the odor exhale, and off the saliva. He had a weekly allowance from the society, of a vessel filled with human ordure about the size of a Bristol barrel. I saw another at work to calcine ice into gunpowder, who likewise showed me a treatise he had written concerning the malleability of fire, which he intended to publish. {P_3|CH_5 ^paragraph 5} There was a most ingenious architect who had contrived a new method for building houses, by beginning at the roof, and working downwards to the foundation, which he justified to me by the like practice of those two prudent insects, the bee and the spider. There was a man born blind, who had several apprentices in his own condition; their employment was to mix colors for painters, which their master taught them to distinguish by feeling and smelling. It was indeed my misfortune to find them at that time not very perfect in their lessons, and the professor himself happened to be generally mistaken; this artist is much encouraged and esteemed by the whole fraternity. In another apartment I was highly pleased with a projector, who had found a device of ploughing the ground with hogs, to save the charges of plows, cattle, and labor. The method in this: in an acre of ground you bury, at six inches distance and eight deep, a quantity of acorns, dates, chestnuts, and other mast or vegetables whereof these animals are fondest; then you drive six hundred or more of them into the field, where in a few days they will root up the whole ground in search of their food, and make it fit for sowing, at the same time manuring it with their dung. It is true, upon experiment they found the charge and trouble very great, and they had little or no crop. However, it is not doubted that this invention may be capable of great improvement. I went into another room, where the walls and ceiling were all hung round with cobwebs, except a narrow passage for the artist to go in and out. At my entrance he called aloud to me not to disturb his webs. He lamented the fatal mistake the world had been so long in of using silk worms, while we had such plenty of domestic insects, who infinitely excelled the former, because they understood how to weave as well as spin. And he proposed farther that by employing spiders the charge of dyeing silks should be wholly saved, whereof I was fully convinced when he showed me a vast number of flies most beautifully colored, wherewith he fed his spiders, assuring us that the webs would take a tincture from them; and as he had them of all hues, he hoped to fit everybody's fancy, as soon as he could find proper food for the flies, of certain gums, oils, and other glutinous matter to give a strength and consistence to the threads. There was an astronomer who had undertaken to place a sundial upon the great weathercock on the townhouse, by adjusting the annual and diurnal motions of the earth and sun, so as to answer and coincide with all accidental turnings by the wind. {P_3|CH_5 ^paragraph 10} I was complaining of a small fit of the colic, upon which my conductor led me into a room, where a great physician resided, who was famous for curing that disease by contrary operations from the same instrument. He had a large pair of bellows with a long slender muzzle of ivory. This he conveyed eight inches up the anus, and drawing in the wind, he affirmed he could make the guts as lank as a dried bladder. But when the disease was more stubborn and violent, he let in the muzzle While the bellows were full of wind, which he discharged into the body of the patient, then withdrew the instrument to replenish it, clapping his thumb strongly against the orifice of the fundament; and this being repeated three or four times, the adventitious wind would rush out, bringing the noxious along with it (like water put into a pump), and the patient recover. I saw him try both experiments upon a dog, but could not discern any effect from the former. After the latter, the animal was ready to burst, and made so violent a discharge, as was very offensive to me and my companions. The dog died on the spot, and we left the doctor endeavoring to recover him by the same operation. I visited many other apartments, but shall not trouble my reader with all the curiosities I observed, being studious of brevity. I had hitherto seen only one side of the Academy, the other being appropriated to the advancers of speculative learning, of which I shall say something when I have mentioned one illustrious person more, who is called among them the universal artist. He told us he had been thirty years employing his thoughts for the improvement of human life. He had two large rooms full of wonderful curiosities, and fifty men at work. Some were condensing air into a dry tangible substance, by extracting the nitre, and letting the aqueous or fluid particles percolate; others softening marbles for pillows and pin-cushions; others petrifying the hoofs of a living horse to preserve them from foundering. The artist himself was at that time busy upon two great designs; the first, to sow land with chaff, wherein he affirmed the true seminal virtue to be contained, as he demonstrated by several experiments which I was not skillful enough to comprehend. The other was, by a certain composition of gums, minerals, and vegetables outwardly applied, to prevent the growth of wool upon two young lambs; and he hoped in a reasonable time to propagate the breed of naked sheep all over the kingdom. We crossed a walk to the other part of the Academy, where, as I have already said, the projectors in speculative learning resided. The first professor I saw was in a very large room, with forty pupils about him. After salutation, observing me to look earnestly upon a frame, which took up the greatest part of both the length and breadth of the room, he said perhaps I might wonder to see him employed in a project for improving speculative knowledge by practical and mechanical operations. But the world would soon be sensible of its usefulness, and he flattered himself that a more noble exalted thought never sprang in any other man's head. Everyone knew how laborious the usual method is of attaining to arts and sciences; whereas by his contrivance the most ignorant person at a reasonable charge, and with a little bodily labor, may write books in philosophy, poetry, politics, law, mathematics, and theology, without the least assistance from genius or study. He then led me to the frame, about the sides whereof all his pupils stood in ranks. It was twenty feet square, placed in the middle of the room. The superficies was composed of several bits of wood, about the bigness of a die, but some larger than others. They were all linked together by slender wires. These bits of wood were covered on every square with paper pasted on them, and on these papers were written all the words of their language, in their several moods, tenses, and declensions, but without any order. The professor then desired me to observe, for he was going to set his engine at work. The pupils at his command took each of them hold of an iron handle, whereof there were forty fixed round the edges of the frame, and giving them a sudden turn, the whole disposition of the words was entirely changed. He then commanded thirty-six of the lads to read the several lines softly as they appeared upon the frame; and where they found three or four words together that might make part of a sentence, they dictated to the four remaining boys who were scribes. This work was repeated three or four times, and at every turn the engine was so contrived that the words shifted into new places, as the square bits of wood moved upside down. {P_3|CH_5 ^paragraph 15} Six hours a day the young students were employed in this labor, and the professor showed me several volumes in large folio already collected, of broken sentences, which he intended to piece together, and out of those rich materials to give the world a complete body of all arts and sciences; which however might be still improved, and much expedited, if the public would raise a fund for making and employing five hundred such frames in Lagado, and oblige the managers to contribute in common their several collections. (SEE PLATE 5) He assured me, that this invention had employed all his thoughts from his youth, that he had emptied the whole vocabulary into his frame, and made the strictest computation of the general proportion there is in books between the numbers of particles, nouns, and verbs, and other parts of speech. I made my humblest acknowledgement to this illustrious person for his great communicativeness, and promised if ever I had the good fortune to return to my native country, that I would do him justice, as the sole inventor of this wonderful machine; the form and contrivance of which I desired leave to delineate upon paper, as in the figure here annexed. I told him, although it were the custom of our learned in Europe to steal inventions from each other, who had thereby at least this advantage, that it became a controversy which was the right owner, yet I would take such caution, that he should have the honor entire without a rival. We next went to the school of languages, where three professors sat in consultation upon improving that of their own country. {P_3|CH_5 ^paragraph 20} The first project was to shorten discourse by cutting polysyllables into one, and leaving out verbs and participles, because in reality all things imaginable are but nouns. The other project was a scheme for entirely abolishing all words whatsoever; and this was urged as a great advantage in point of health as well as brevity. For it is plain that every word we speak is in some degree a diminution of our lungs by corrosion, and consequently contributes to the shortening of our lives. An expedient was therefore offered, that since words are only names for things, it would be more convenient for all men to carry about them such things as were necessary to express the particular business they are to discourse on. And this invention would certainly have taken place, to the great ease as well as health of the subject, if the women, in conjunction with the vulgar and illiterate, had not threatened to raise a rebellion, unless they might be allowed the liberty to speak with their tongues, after the manner of their ancestors; such constant irreconcilable enemies to science are the common people. However, many of the most learned and wise adhere to the new scheme of expressing themselves by things, which has only this inconvenience attending it, that if a man's business be very great, and of various kinds, he must be obliged in proportion to carry a greater bundle of things upon his back, unless he can afford one or two strong servants to attend him. I have often beheld two of those sages almost sinking under the weight of their packs, like pedlars among us; who, when they met in the streets, would lay down their loads, open their sacks, and hold conversation for an hour together; then put up their implements, help each other to resume their burdens, and take their leave. But for short conversations a man may carry implements in his pockets and under his arms, enough to supply him, and in his house he cannot be at a loss. Therefore the room where company meet who practise this art, is full of all things ready at hand, requisite to furnish matter for this kind of artificial converse. Another great advantage proposed by this invention was that it would serve as a universal language to be understood in all civilized nations, whose goods and utensils are generally of the same kind, or nearly resembling, so that their uses might easily be comprehended. And thus ambassadors would be qualified to treat with foreign princes or ministers of state, to whose tongues they were utter strangers. I was at the mathematical school, where the master taught his pupils after a method scarce imaginable to us in Europe. The proposition and demonstration were fairly written on a thin wafer, with ink composed of a cephalic tincture. This the student was to swallow upon a fasting stomach, and for three days following eat nothing but bread and water. As the wafer digested, the tincture mounted to his brain, bearing the proposition along with it. But the success has not hitherto been answerable, partly by some error in the quantum or composition, and partly by the perverseness of lads, to whom this bolus is so nauseous, that they generally steal aside, and discharge it upwards before it can operate; neither have they been yet persuaded to use so long an abstinence as the prescription required. P_3|CH_6 CHAPTER VI - In the school of political projectors I was but ill entertained, the professors appearing in my judgment wholly out of their senses, which is a scene that never fails to make me melancholy. These unhappy people were proposing schemes for persuading monarchs to choose favorites upon the score of their wisdom, capacity, and virtue; of teaching ministers to consult the public good; of rewarding merit, great abilities, eminent services; of instructing princes to know their true interest by placing it on the same foundation with that of their people; of choosing for employments persons qualified to exercise them; with many other wild impossible chimeras, that never entered before into the heart of man to conceive, and confirmed in me the old observation, that there is nothing so extravagant and irrational which some philosophers have not maintained for truth. But however I shall so far do justice to this part of the Academy, as to acknowledge that all of them were not so visionary. There was a most ingenious doctor who seemed to be perfectly versed in the whole nature and system of government. This illustrious person had very usefully employed his studies in finding out effectual remedies for all diseases and corruptions, to which the several kinds of public administration are subject by the vices or infirmities of those who govern, as well as by the licentiousness of those who are to obey. For instance, whereas all writers and reasoners have agreed, that there is a strict universal resemblance between the natural and the political body; can there be anything more evident, than that the health of both must be preserved, and the cured by the same prescriptions? It is allowed that senates and great councils are often troubled with redundant, ebullient, and other peccant humors, with many diseases of the head, and more of the heart; with strong convulsions, with grievous contractions of the nerves and sinews in both hands, but especially the right; with spleen, flatus, vertigos, and deliriums; with scrofulous tumors full of fetid purulent matter; with sour frothy ructations, with canine appetites and crudeness of digestion, besides many others needless to mention. This doctor therefore proposed, that upon the meeting of a senate, certain physicians should attend at the three first days of their sitting, and at the close of each day's debate, feel the pulses of every senator; after which, having maturely considered, and consulted upon the nature of the several maladies, and the methods of cure, they should on the fourth day return to the senate house, attended by their apothecaries stored with proper medicines; and before the members sat, administer to each of them lenitives, aperitives, abstersives, corrosives, restringents, palliatives, laxatives, cephalalgics, icterics, apophlegmatics, acoustics, as their several cases required; and according as these medicines should operate, repeat, alter, or omit them at the next meeting. This project could not be of any great expense to the public, and would, in my poor opinion, be of much use for the dispatch of business in those countries where senates have any share in the legislative power; beget unanimity, shorten debates, open a few mouths which are now closed, and close many more which are now open; curb the petulancy of the young, and correct the positiveness of the old; rouse the stupid, and damp the pert. Again, because it is a general complaint, that the favorites of princes are troubled with short and weak memories, the same doctor proposed, that whoever attended a first minister, after having told his business with the utmost brevity and in the plainest words, should at his departure give the said minister a tweak by the nose, or a kick in the belly, or tread on his corns, or lug him thrice by both ears, or pin into his breech, or pinch his arm black and blue, to prevent forgetfulness; and at every levee day repeat the same operation, till the business were done or absolutely refused. He likewise directed, that every senator in the great council of a nation, after he had delivered his opinion, and argued in the defense of it, should be obliged to give his vote directly contrary; because if that were done, the result would infallibly terminate in the good of the public. {P_3|CH_6 ^paragraph 5} When parties in a state are violent, he offered a wonderful contrivance to reconcile them. The method is this. You take a hundred leaders of each party, you dispose them into couples of such whose heads are nearest of a size; then let two nice operators saw off the occiput of each couple at the same time, in such a manner that the brain may be equally divided. Let the occiputs thus cut off be interchanged, applying each to the head of his opposite party-man. It seems indeed to be a work that requires some exactness, but the professor assured us that if it were dexterously performed the cure would be infallible. For he argued thus; that the two half brains being left to debate the matter between themselves within the space of one skull, would soon come to a good understanding, and produce that moderation, as well as regularity of thinking, so much to be wished for in the heads of those who imagine they come into the world only to watch and govern its motion: and as to the difference of brains in quantity or quality among those who are directors in faction, the doctor assured us from his own knowledge that it was a perfect trifle. I heard a very warm debate between two professors, about the most commodious and effectual ways and means of raising money without grieving the subject. The first affirmed the most just method would be to lay a certain tax upon vices and folly, and the sum fixed upon every man to be rated after the fairest manner by a jury of his neighbors. The second was of an opinion directly contrary, to tax those qualities of body and mind for which men chiefly value themselves, the rate to be more or less according to the degrees of excelling, the decision whereof should be left entirely to their own breast. The highest tax was upon men who are the greatest favorites of the other sex, and the assessments according to the number and natures of the favors they have received; for which they are allowed to be their own vouchers. Wit, valor, and politeness were likewise proposed to be largely taxed, and collected in the same manner, by every person's giving his own word for the quantum of what he possessed. But as to honor, justice, wisdom, and learning, they should not be taxed at all, because they are qualifications of so singular a kind, that no man will either allow them in his neighbor, or value them in himself. The women were proposed to be taxed according to their beauty and skill in dressing, wherein they had the same privilege with the men, to be determined by their own judgment. But constancy, chastity, good sense, and good nature were not rated, because they would not bear the charge of collecting. To keep senators in the interest of the crown, it was proposed that the members should raffle for employments, every man first taking an oath, and giving security that he would vote for the court, whether he won or not; after which the losers had in their turn the liberty of raising upon the next vacancy. Thus hope and expectation would be kept alive, none would complain of broken promises, but impute their disappointments wholly to fortune, whose shoulders are broader and stronger than those of a ministry. Another professor showed me a large paper of instructions for discovering plots and conspiracies against the government. He advised great statesmen to examine into the diet of all suspected persons; their times of eating; upon which side they lay in bed; with which hand they wiped their posteriors; to take a strict view of their excrements, and, from the color, the odor, the taste, the consistence, the crudeness of maturity of digestion, form a judgment of their thoughts and designs. Because men are never so serious, thoughtful, and intent, as when they are at stool, which he found by frequent experiment; for in such conjunctures, when he used merely as a trial to consider which was the best way of murdering the king, his ordure would have a tincture of green, but quite different when he thought only of raising an insurrection or burning the metropolis. {P_3|CH_6 ^paragraph 10} The whole discourse was written with great acuteness, containing many observations both curious and useful for politicians, but as I conceived not altogether complete. This I ventured to tell the author, and offered if he pleased to supply him with some additions. He received my proposition with more compliance than is usual among writers, especially those of the projecting species, professing he would be glad to receive further information. I told him that in the kingdom of Tribnia, by the natives called Langden, where I had sojourned some time in my travels, the bulk of the people consist in a manner wholly of discoverers, witnesses, informers, accusers, prosecutors, evidences, swearers, together with their several subservient and subaltern instruments, all under the colors and conduct of ministers of state and their deputies. The plots in that kingdom are usually the workmanship of those persons who desire to raise their own characters of profound politicians, to restore new vigor to a crazy administration, to stifle or divert general discontents, to fill their pockets with forfeitures, and raise or sink the opinion of public credit, as either shall best answer their private advantage. It is first agreed and settled among them, what suspected persons shall be accused of a plot; then, effectual care is taken to secure all their letters and papers, and put the criminals in chains. These papers are delivered to a set of artists, very dexterous in finding out the mysterious meanings of words, syllables, and letters. For instance, they can discover a close-stool to signify a privy council; a flock of geese, a senate; a lame dog, an invader; a codshead, a ---; the plague, a standing army; a buzzard, a prime minister; the gout, a high priest; a gibbet, a secretary of state; a chamber-pot, a committee of grandees; a sieve, a court lady; a broom, a revolution; a mousetrap, an employment; a bottomless pit, the treasury; a sink, the court; a cap-and bells, a favorite; a broken reed, a court of justice; an empty tun, a general; a running sore, the administration. When this method fails, they have two others more effectual, which the learned among them call acrostics and anagrams. First they can decipher all initial letters into political meanings. Thus, N. shall signify a plot; B. a regiment of horse; L. a fleet at sea; or secondly by transposing the letters of the alphabet in any suspected paper, they can discover the deepest designs of a discontented party. So for example if I should say in a letter to a friend, Our brother Tom has just got the piles, a skillful decipherer would discover that the same letters which compose that sentence may be analyzed into the following words: Resist -- a plot is brought home -- the tour. And this is the anagrammatic method. The professor made me great acknowledgments for communicating these observations, and promised to make honorable mention of me in his treatise. I saw nothing in this country that could invite me to a longer continuance, and began to think of returning home to England. P_3|CH_7 CHAPTER VII - The continent of which this kingdom is a part extends itself, as I have reason to believe, eastward to that unknown tract of America, westward to California, and north to the Pacific Ocean, which is not above a hundred and fifty miles from Lagado, where there is a good port and much commerce with the great island of Luggnagg, situated to the northwest about 29 degrees north latitude, and 140 longitude. The island of Luggnagg stands southeastward of Japan, about a hundred leagues distant. There is a strict alliance between the Japanese Emperor and the King of Luggnagg, which affords frequent opportunities of sailing from one island to the other. I determined therefore to direct my course this way, in order to my return to Europe. I hired two mules with a guide to show me the way, and carry my small baggage. I took leave of my noble protector, who had shown me so much favor and made me a generous present at my departure. My journey was without any accident or adventure worth relating. When I arrived at the port of Maldonada (for so it is called) there was no ship in the harbor bound for Luggnagg, nor likely to be in some time. The town is about as large as Portsmouth. I soon fell into some acquaintance, and was very hospitably received. A gentleman of distinction said to me that since the ships bound for Luggnagg could not be ready in less than a month, it might be no disagreeable amusement for me to take a trip to the little island of Glubbdubdrib, about five leagues off to the southwest. He offered himself and a friend to accompany me, and that I should be provided with a small convenient barque for the voyage. Glubbdubdrib, as nearly as I can interpret the word, signifies the Island of Sorcerers or Magicians. It is about one-third as large as the Isle of Wight, and extremely fruitful; it is governed by the head of a certain tribe, who are all magicians. This tribe marries only among each other, and the eldest in succession is Prince or Governor. He has a noble palace, and a park of about three thousand acres, surrounded by a wall of hewn stone twenty feet high. In this park are several small enclosures for cattle, corn, and gardening. The Governor and his family are served and attended by domestics of a kind somewhat unusual. By his skill in necromancy, he has a power of calling whom he pleases from the dead, and commanding their service for twenty-four hours, but no longer; nor can he call the same persons up again in less than three months, except upon very extraordinary occasions. When we arrived at the island, which was about eleven in the morning, one of the gentlemen who accompanied me, went to the Governor, and desired admittance for a stranger, who came on purpose to have the honor of attending on his Highness. This was immediately granted, and we all three entered the gate of the palace between two rows of guards, armed and dressed after a very antic manner, and something in their countenances that made my flesh creep a horror I cannot express. We passed through several apartments, between servants of the same sort, ranked on each side as before, till we came to the chamber of presence, where after three profound obeisances, and a few general questions, we were permitted to sit on three stools near the lowest step of his Highness's throne. He understood the language of Balnibarbi, although it were different from that of his island. He desired me to give him some account of my travels; and to let me see that I should be treated without ceremony, he dismissed all his attendants with a turn of his finger, at which to my great astonishment they vanished in an instant, like visions in a dream, when we awake on a sudden. I could not recover myself in some time, till the Governor assured me that I should receive no hurt; and observing my two companions to be under no concern, who had been often entertained in the same manner, I began to take courage, and related to his Highness a short history of my several adventures, yet not without some hesitation, and frequently looking behind me to the place where I had seen those domestic specters. I had the honor to dine with the Governor, where a new set of ghosts served up the meat, and waited at table. I now observed myself to be less terrified than I had been in the morning. I stayed till sunset, but humbly desired his Highness to excuse me for not accepting his invitation of lodging in the palace. My two friends and I lay at a private house in the town adjoining, which is the capital of this little island; and the next morning we returned to pay our duty to the Governor, as he was pleased to command us. {P_3|CH_7 ^paragraph 5} After this manner we continued in the island for ten days, most part of every day with the Governor, and at night in our lodging. I soon grew so familiarized to the sight of spirits, that the third or fourth time they gave me no emotion at all; or if I had any apprehensions left, my curiosity prevailed over them. For his Highness the Governor ordered me to call up whatever persons I would choose to name, and in whatever numbers among all the dead from the beginning of the world to the present time, and command them to answer any questions I should think fit to ask; with this condition, that my questions must be confined within the compass of the times they lived in. And one thing I might depend upon, that they would certainly tell me truth, for lying was a talent of no use in the lower world. I made my humble acknowledgments to his Highness for so great a favor. We were in a chamber from whence there was a fair prospect into the park. And because my first inclination was to be entertained with scenes of pomp and magnificence, I desired to see Alexander the Great, at the head of his army just after the battle of Arbela; which upon a notion of the Governor's finger immediately appeared in a large field under the window where we stood. Alexander was called up into the room; it was with great difficulty that I understood his Greek, and had but little of my own. He assured me upon his honor that he was not poisoned, but died of a fever by excessive drinking. Next I saw Hannibal passing the Alps, who told me he had not a drop of vinegar in his camp. I saw Caesar and Pompey at the head of their troops, just ready to engage. I saw the former in his last great triumph. I desired that the senate of Rome might appear before me in one large chamber, and an assembly of somewhat a latter age in counterview in another. The first seemed to be an assembly of heroes and demigods; the other a knot of pedlars, pickpockets, highway-men, and bullies. The Governor at my request gave the sign for Caesar and Brutus to advance towards us. I was struck with a profound veneration at the sight of Brutus, and could easily discover the most consummate virtue, the greatest intrepidity and firmness of mind, the truest love of his country, and general benevolence for mankind in every lineament of his countenance. I observed with much pleasure that these two persons were in good intelligence with each other, and Caesar freely confessed to me that the greatest actions of his own life were not equal by many degrees to the glory of taking it away. I had the honor to have much conversation with Brutus; and was told, that his ancestor Junius, Socrates, Epaminondas, Cato the younger, Sir Thomas More, and himself were perpetually together: a sextumvirate to which all the ages of the world cannot add a seventh. {P_3|CH_7 ^paragraph 10} It would be tedious to trouble the reader with relating what vast numbers of illustrious persons were called up, to gratify that insatiable desire I had to see the world in every period of antiquity placed before me. I chiefly fed my eyes with beholding the destroyers of tyrants and usurpers, and the restorers of liberty to oppressed and injured nations. But it is impossible to express the satisfaction I received in my own mind, after such a manner as to make it a suitable entertainment to the reader. P_3|CH_8 CHAPTER VIII - Having a desire to see those ancients who were most renowned for wit and learning, I set apart one day on purpose. I proposed that Homer and Aristotle might appear at the head of all their commentators; but these were so numerous that some hundreds were forced to attend in the court and outward rooms of the palace. I knew and could distinguish those two heroes at first sight, not only from the crowd but from each other. Homer was the taller and comelier person of the two, walked very erect for one of his age, and his eyes were the most quick and piercing I ever beheld. Aristotle stooped much, and made use of a staff. His visage was meager, his hair lank and thin, and his voice hollow. I soon discovered that both of were perfect strangers to the rest of the company, and had never seen or heard of them before. And I had a whisper from a ghost, who shall be nameless, that these commentators always kept in the most distant quarters from their principals in the lower world, through a consciousness of shame and guilt, because they had so horribly misrepresented the meaning of those authors to posterity. I introduced Didymus and Eustathius to Homer, and prevailed on him to treat them better than perhaps they deserved; for he soon found they wanted a genius to enter into the spirit of a poet. But Aristotle was out of all patience with the account I gave him of Scotus and Ramus, as I presented them to him; and he asked them whether the rest of the tribe were as great dunces as themselves. I then desired the Governor to call up Descartes and Gassendi, with whom I prevailed to explain their systems to Aristotle. This great philosopher freely acknowledged his own mistakes in natural philosophy, because he proceeded in many things upon conjecture, as all men must do; and he found, that Gassendi, who had made the doctrine of Epicurus as palatable as he could, and the vortices of Descartes, were equally exploded. He predicted the same fate to attraction, whereof the present learned are such zealous asserters. He said that new systems of nature were but new fashions, which would vary in every age; and even those who pretend to demonstrate them from mathematical principles, would flourish but a short period of time, and be out of vogue when that was determined. I spent five days in conversing with many others of the ancient learned. I saw most of the first Roman emperors. I prevailed on the Governor to call up Eliogabalus's cooks to dress us a dinner, but they could not show us much of their skill, for want of materials. A helot of Agesilaus made us a dish of Spartan broth, but I was not able to get down a second spoonful. The two gentlemen who conducted me to the island were pressed by their private affairs to return in three days, which I employed in seeing some of the modern dead, who had made the greatest figure for two or three hundred years past in our own and other countries of Europe; and having been always a great admirer of old illustrious families, I desired the Governor call up a dozen or two of kings with their ancestors in order for eight or nine generations. But my disappointment was grevious and unexpected. For instead of a long train with royal diadems, I saw in one family two fiddlers, three spruce courtiers, and an Italian prelate. In another, a barber, an abbot, and two cardinals. I have too great a veneration for crowned heads to dwell any longer on so nice a subject. But as to counts, marquesses, dukes, earls, and the like, I was not so scrupulous. And I confess it was not without some pleasure that I found myself able to trace the particular features, by which certain families are distinguished, up to their originals. I could plainly discover from whence one family derives a long chin, why a second has abounded with knaves for two generations, and fools for two more; why a third happened to be crack-brained, and a fourth to be sharpers. Whence it came what Polydore Virgil says of a certain great house, Nec vir fortis, nec femina casta. How cruelty, falsehood, and cowardice grew to be characteristics by which certain families are distinguished as much as by their coat of arms. Who first brought the pox into a noble house, which has lineally descended in scrofulous tumors to their posterity. Neither could I wonder at all this, when I saw such an interruption of lineages by pages, lackeys, valets, coachmen, gamesters, captains and pickpockets. I was chiefly disgusted with modern history. For having strictly examined all the persons of greatest name in the courts of princes for a hundred years past, I found how the world had been misled by prostitute writers, to ascribe the greatest exploits in war to cowards, the wisest counsel to fools, sincerity to flatterers, Roman virtue to betrayers of their country, piety to atheists, chastity to sodomites, informers. How many innocent and excellent persons had been condemned to death or banishment, by the practising of great ministers upon the corruption of judges, and the malice of factions. How many villains had been exalted to the highest places of trust, power, dignity, and profit: how great a share in the motions and events of courts, councils, and senates might be challenged by bawds, whores, pimps, parasites, and buffoons. How low an opinion I had of human wisdom and integrity, when I was truly informed of the springs and motives of great enterprises and revolutions in the world, and of the contemptible accidents to which they owed their success. {P_3|CH_8 ^paragraph 5} Here I discovered the roguery and ignorance of those who pretend to write anecdotes, or secret history, who send so many kings to their graves with a cup of poison; will repeat the discourse between a prince and chief minister, where no witness was by; unlock the thoughts and cabinets of ambassadors and secretaries of state, and have the perpetual misfortune to be mistaken. Here I discovered the secret causes of many great events that have surprised the world, how a whore can govern the backstairs, the backstairs a council, and the council a senate. A general confessed in my presence, that he got a victory purely by the force of cowardice and ill conduct; and an admiral, that for want of proper intelligence, he beat the enemy to whom he intended to betray the fleet. Three kings protested to me, that in their whole reigns they never did once prefer any person of merit, unless by mistake or treachery of some minister in whom they confided; neither would they do it if they were to live again; and they showed with great strength of reason that the royal throne could not be supported without corruption, because that positive, confident, restive temper, which virtue infused into man, was a perpetual clog to public business. I had the curiosity to enquire in a particular manner, by what method great numbers had procured to themselves high titles of honor, and prodigious estates; and I confined my inquiry to a very modern period; however, without grating upon present times, because I would be sure to give no offense even to foreigners (for I hope the reader need not be told that I do not in the least intend my own country in what I say upon this occasion), a great number of persons concerned were called up, and upon a very slight examination, discovered such a scene of infamy, that I cannot reflect upon it without some seriousness. Perjury, oppression, subornation, fraud, panderism, and the like infirmities, were amongst the most excusable arts they had to mention, and for these I gave, as it was reasonable, great allowance. But when some confessed they owed their greatness and wealth to sodomy or incest, others to the prostituting of their own wives and daughters; others to the betraying of their country or their prince; some to poisoning, more to the perverting of justice in order to destroy the innocent; I hope I may be pardoned if these discoveries inclined me little to abate of that profound veneration which I am naturally apt to pay to persons of high rank, who ought to be treated with the utmost respect due to their sublime dignity, by us their inferiors. I had often read of some great services done to princes and states, and desired to see the persons by whom those services were performed. Upon inquiry I was told that their names were to be found on no record, except a few of them whom history has represented as the vilest rogues and traitors. As to the rest, I had never once heard of them. They all appeared with dejected looks, and in the meanest habit, most of them telling me they died in poverty and disgrace, and the rest on a scaffold or a gibbet. Among the rest there was one person whose case appeared a little singular. He had a youth about eighteen years old standing by his side. He told me he had for many years been commander of a ship, and in the sea fight of Actium had the good fortune to break through the enemy's great line of battle, sink three of their capital ships, and take a fourth, which was the sole cause of Antony's flight, and of the victory that ensued; that the youth standing by him, his only son, was killed in action. He added that upon the confidence of some merit, the war being at an end, he went to Rome, and solicited at the court of Augustus to be preferred to a greater ship, whose commander had been killed; but without any regard to his pretensions, it was given to a youth who had never seen the sea, the son of Libertine, who waited on one of the emperor's mistresses. Returning back to his own vessel, he was charged with neglect of duty, and the ship given to a favorite page of Publicola, the vice-admiral; whereupon he retired to a poor farm at a great distance from Rome, and there ended his life. I was so curious to know the truth of this story, that I desired Agrippa might be called, who was admiral in that fight. He appeared, and confirmed the whole account, but with much more advantage to the captain, whose modesty had extenuated or concealed a great part of his merit. I was surprised to find corruption grown so high and so quick in that empire, by the force of luxury so lately introduced, which made me less wonder at many parallel cases in other countries, where vices of all kinds have reigned so much longer, and where the whole praise as well as pillage has been engrossed by the chief commander, who perhaps had the least title to either. {P_3|CH_8 ^paragraph 10} As every person called up made exactly the same appearance he had done in the world, it gave me melancholy reflections to observe how much the race of human kind was degenerate among us, within these hundred years past. How the pox under all its consequences and denominations had altered every lineament of an English countenance, shortened the size of bodies, unbraced the nerves, relaxed the sinews and muscles, introduced a sallow complexion, and rendered the flesh loose and rancid. I descended so low as to desire that some English yeomen of the old stamp might be summoned to appear, once so famous for the simplicity of their manners, diet and dress, for justice in their dealings, for their true spirit of liberty, for their valor and love of their country. Neither could I be wholly unmoved after comparing the living with the dead, when I considered how all these pure native virtues were prostituted for a piece of money by their grandchildren, who in selling their votes, and managing at elections, have acquired every vice and corruption that can possibly be learned in a court. P_3|CH_9 CHAPTER IX - The day of our departure being come, I took leave of his Highness the Governor of Glubbdubdrib, and returned with my two companions to Maldonada, where after a fortnight's waiting, a ship was ready to sail for Luggnagg. The two gentlemen, and some others, were so generous and kind as to furnish me with provisions, and see me on board. I was a month in this voyage. We had one violent storm and were under a necessity of steering westward to get into the tradewind, which holds for above sixty leagues. On the 21st of April, 1709, we sailed into the river of Clumegnig, which is a seaport town, at the southeast point of Luggnagg. We cast anchor within a league of the town, and made a signal for a pilot. Two of them came on board in less than half an hour, by whom we were guided between certain shoals and rocks, which are very dangerous in the passage, to a large basin, where fleet may ride in safety within a cable's length of the town wall. Some of our sailors, whether out of treachery or inadvertence, had informed the pilots that I was a stranger and a traveler, whereof these gave notice to a custom house officer, by whom I was examined very strictly upon my landing. This officer spoke to me in the language of Balnibarbi, which by the force of much commerce is generally understood in that town, especially by seamen, and those employed in the customs. I gave him a short account of some particulars, and made my story as plausible and consistent as I could; but I thought it necessary to disguise my country, and call myself an Hollander, because my intentions were for Japan, and I knew the Dutch were the only Europeans permitted to enter into that kingdom. I therefore told the officer, that having been shipwrecked on the coast of Balnibarbi, and cast on a rock, I was received up into Laputa, or the Flying Island (of which he had often heard), and was now endeavoring to get to Japan, from whence I might find a convenience of returning to my own country. The officer said I must be confined till he could receive orders from court, for which he would write immediately, and hoped to receive an answer in a fortnight. I was carried to a convenient lodging, with a sentry placed at the door; however I had the liberty of a large garden, and was treated with humanity enough, being maintained all the time at the King's charge. I was visited by several persons, chiefly out of curiosity, because it was reported that I came from countries very remote of which they had never heard. I hired a young man who came in the same ship to be an interpreter; he was a native of Luggnagg, but had lived some years at Maldonada, and was a perfect master of both languages. By his assistance I was able to hold a conversation with those who came to visit me; but this consisted only of their questions, and my answers. The dispatch came from court about the time we expected. It contained a warrant for conducting me and my retinue to Traldragdubb or Trildrogdrib, for it is pronounced both ways as near as I can remember, by a party of ten horse. All my retinue was that poor lad for an interpreter, whom I persuaded into my service, and at my humble request, we had each of us a mule to ride on. A messenger was dispatched half a day's journey before us, to give the King notice of my approach, and to desire that his Majesty would please appoint a day and hour, when it would be his gracious pleasure that I might have the honor to lick the dust before his footstool. This is the court style, and I found it to be more than matter of form. For upon my admittance two days after my arrival, I was commanded to crawl on my belly, and lick the floor as I advanced; but on account of my being a stranger, care was taken to have it made so clean that the dust was not offensive. However, this was a peculiar grace, not allowed to any but persons of the highest rank, when they desire an admittance. Nay, sometimes the floor is strewn with dust on purpose, when the person to be admitted happens to have powerful enemies at court. And I have seen a great lord with his mouth so crammed, that when he had crept to the proper distance from the throne, he was not able to speak a word. Neither is there any remedy, because it is capital for those who receive an audience to spit or wipe their mouths in his Majesty's presence. There is indeed another custom, which I cannot altogether approve of. When the king has a mind to put any of his nobles to death in a gentle indulgent manner, he commands to have the floor strewn with a certain brown powder, of a deadly composition, which being licked up infallibly kills him in twenty-four hours. But in justice to this prince's great clemency, and the care he has of his subject's lives (wherein it were much to be wished that the monarchs of Europe would imitate him), it must be mentioned for his honor, that strict orders are given to have the infected parts of the floor well after every such execution; which if his domestics neglect, they are in danger of incurring his royal displeasure. I myself heard him give directions, that one of his pages should be whipped, whose turn it was to give notice about washing the floor after an execution, but maliciously had omitted it; by which neglect a young lord of great hopes coming to an audience, was unfortunately poisoned, although the King at that time had not design against his life. But this good prince was so gracious as to forgive the poor page his whipping, upon promise that he would do so no more, without special orders. To return from this digression; when I had crept within four yards of the throne, I raised myself gently upon my knees, and then striking my forehead seven times on the ground, I pronounced the following words, as they had been taught me the night before, Ickpling gloffthrobb squutserumm blhiop mlashnalt zwin tnodbalkguffh slhiophad gurdlubh asht. This is the compliment established by the laws of the land for all persons admitted to the King's presence. It may be rendered into English thus: May your Celestial Majesty outlive the sun, eleven moons and a half. To this the King returned some answer, which although I could not understand, yet I replied as I had been directed: Fluft drin yalerick dwuldom prastrad mirpush, which properly signifies, My tongue is in the mouth of my friend, and by this expression was meant that I desired leave to bring my interpreter; whereupon the young man already mentioned was accordingly introduced, by whose intervention I answered as many questions as his Majesty could put in over an hour. I spoke in the Balnibarbian tongue, and my interpreter delivered my meaning in that of Luggnagg. {P_3|CH_9 ^paragraph 5} The King was much delighted with my company, and ordered his Bliffmarklub or High Chamberlain, to appoint a lodging in the court for me and my interpreter, with a daily allowance for my table, and a large purse of gold for my common expenses. I stayed three months in this country out of perfect obedience to his Majesty, who was pleased highly to favor me, and made me very honorable offers. But I thought it more consistent with prudence and justice to pass the remainder of my days with my wife and family. P_3|CH_10 CHAPTER X - The Luggnaggians are a polite and generous people, and although they are not without some share of that pride which is peculiar to all Eastern countries, yet they show themselves courteous to strangers, especially such who are countenanced by the court. I had many acquaintance among persons of the best fashion, and being always attended by my interpreter, the conversation we had was not disagreeable. One day in much good company I was asked by a person of quality, whether I had seen any of their Struldbrugs, or Immortals. I said I had not, and desired he would explain to me what he meant by such an appellation applied to a mortal creature. He told me, that sometimes, though very rarely, a child happened to be born in a family with a red circular spot in the forehead, directly over the left eyebrow, which was an infallible mark that it should never die. The spot, as he described it, was about the compass of a silver threepence, but in the course of time grew larger, and changed its color; for at twelve years old it became green, so continued till twenty-five, then turned to a deep blue; at forty-five it grew coal black, and as large as an English shilling, but never admitted any further alteration. He said these births were so rare, that he did not believe there could be above eleven hundred struldbrugs of both sexes in the whole kingdom, of which he computed about fifty in the metropolis, and among the rest a young girl born about three years ago. That these productions were not peculiar to any family, but a mere effect of chance; and the children of the struldbrugs themselves were equally mortal with the rest of the people. I freely own myself to have been struck with inexpressible delight upon hearing this account, and the person who gave it me happening to understand the Balnibarbian language, which I spoke very well, I could not forbear breaking out into expressions perhaps a little too extravagant. I cried out as in a rapture: Happy nation where every child hath at least a chance for being immortal! Happy people who enjoy so many living examples of ancient virtue, and have masters ready to instruct them in the wisdom of all former ages! but, happiest beyond all comparison are those excellent struldbrugs, who being born exempt from that universal calamity of human nature, have their minds free and disengaged, without the weight and depression of spirits caused by the continual apprehension of death. I discovered my admiration that I had not observed any of these illustrious persons at court; the black spot on the forehead being so remarkable a distinction, that I could not have easily overlooked and it was impossible that his Majesty, a most judicious prince, should not provide himself with a good number of such wise and able counselors. Yet perhaps the virtue of those reverend sages was too strict for the corrupt and libertine manners of a court. And we often find by experience that young men are too opinionative and volatile to be guided by the sober dictates of their seniors. However, since the King was pleased to allow me access to his royal person, I was resolved upon the very first occasion to deliver my opinion to him on this matter freely and at large, by the help of my interpreter; and whether he would please to take my advice or not, yet in one thing I was determined, that his Majesty having frequently offered me an establishment in this country, I would with great thankfulness accept the favor, and pass my life here in the conversation of those superior beings the struldbrugs, if they would please to admit me. The gentleman to whom I addressed my discourse, because (as I have already observed) he spoke the language of Balnibarbi, said to me with a sort of a smile, which usually arises from pity to the ignorant, that he was glad of any occasion to keep me among them, and desired my permission to explain to the company what I had spoke. He did so, and they talked together for some time in their own language, whereof I understood not a syllable, neither could I observe by their countenances what impression my discourse had made on them. After a short silence, the same person told me that his friends and mine (so he thought fit to express himself) were very much pleased with the judicious remarks I had made on the great happiness and advantages of immortal life; and they were desirous to know in a particular manner, what scheme of living I should have formed to myself, if it had fallen to my lot to have been born a struldbrug. I answered, it was easy to be eloquent on so copious and delightful a subject, especially to me who have been often apt to amuse myself with visions of what I should do if I were a king, a general, or a great lord; and upon this very case I had frequently run over the whole system how I should employ myself and pass the time if I were sure to live for ever. {P_3|CH_10 ^paragraph 5} That if it had been my good fortune to come into the world a struldbrug, as soon as I could discover my own happiness by understanding the difference between life and death, I would first resolve by an arts and methods whatsoever to procure myself riches. In the pursuit of which by thrift and management, I might reasonably expect, in about two hundred years to be the wealthiest man in the kingdom. In the second place, I would from my earliest youth apply myself to the study of arts and sciences, by which I should arrive in time to excell all others in learning. Lastly, I would carefully record every action and event of consequence that happened in the public, impartially draw the characters of the several successions of princes and great ministers of state, with my own observations on every point. I would exactly set down the several changes in customs, language, fashions of dress, diet and diversions. By all which acquirements, I should be a living treasury of knowledge and wisdom, and certainly become the oracle of the nation. I would never marry after threescore, but live in an hospitable manner, yet still on the saving side. I would entertain myself in forming and directing the minds of hopeful young men, by convincing them from my own remembrance, experience and observation, fortified by numerous examples, of the usefulness of virtue in public and private life. But my choice and constant companions should be a set of my own immortal brotherhood, among whom I would elect a dozen from the most ancient down to my own contemporaries. Where any of these wanted fortunes, I would provide them with convenient lodges round my own estate, and have some of them always at my table, only mingling a few of the most valuable among you mortals, whom length of time would harden me to lose with little or no reluctance, and treat your posterity after the same manner; just as a man diverts himself with the annual succession of pinks and tulips in his garden, without regretting the loss of those which withered the preceding year. These struldbrugs and I would mutually communicate our observations and memorials through the course of time, remark the several gradations by which corruption steals into the world, and oppose it in every step, by giving perpetual warning and instruction to mankind; which, added to the strong influence of our own example, would probably prevent that continual degeneracy of human nature so justly complained of in all ages. Add to all this the pleasure of seeing the various revolutions of states and empires, the changes in the lower and upper world, ancient cities in ruins, and obscure villages become the seats of kings. Famous rivers lessening into shallow brooks, the ocean leaving one coast dry, and overwhelming another; the discovery of many countries yet unknown. Barbarity over-running the politest nations, and the most barbarous become civilized. I should then see the discovery of the longitude, the perpetual motion, the universal medicine, and many other great inventions brought to the utmost perfection. What wonderful discoveries should we make in astronomy, by outliving and confirming our own predictions, by observing the progress and returns of comets, with the changes of motion in the sun, moon, and stars. {P_3|CH_10 ^paragraph 10} I enlarged upon many other topics, which the natural desire of endless life and sublunary happiness could easily furnish me with. When I had ended, and the sum of my discourse had been interpreted as before, to the rest of the company, there was a good deal of talk among them the language of the country, not without some laughter at my expense. At last the same gentleman who had been my interpreter said he was desired by the rest to set me right in a few mistakes, which I had fallen into through the common imbecility of human nature, and upon that allowance was less answerable for them. That this breed of struldbrugs was peculiar to their country, for there were no such people either in Balnibarbi or Japan, where he had the honor to be ambassador from his Majesty, and found the natives in both those kingdoms very hard to believe that the fact was possible; and it appeared from my astonishment when he first mentioned the matter to me, that I received it as a thing wholly new, and scarcely to be credited. That in the two kingdoms above mentioned, where during his residence he had conversed very much, he observed long life to be the universal desire and wish of mankind. That whoever had one foot in the grave was sure to hold back the other as strongly as he could. That the oldest had still hopes of living one day longer, and looked on death as the greatest evil, from which nature always prompted him to retreat; only in this island of Luggnagg the appetite for living was not so eager, from the continual example of the struldbrugs before their eyes. That the system of living contrived by me was unreasonable and unjust, because it supposed a perpetuity of youth, health, and vigor, which no man could be so foolish to hope, however extravagant he may be in his wishes. That the question therefore was not whether a man would choose to be always in the prime of youth, attended with prosperity and health, but how he would pass a perpetual life under all the usual disadvantages which old age brings along with it. For although few men will avow their desires of being immortal upon such hard conditions, yet in the two kingdoms before mentioned of Balnibarbi and Japan, he observed that every man desired to put off death for some time longer, let it approach ever so late; and he rarely heard of any man who died willingly, except he were incited by the extremity of grief or torture. And he appealed to me whether in those countries I had traveled as well as my own, I had not observed the same general disposition. After this preface he gave me a particular account of the struldbrugs among them. He said they commonly acted like mortals, till about thirty years old, after which by degrees they grew melancholy and dejected, increasing in both till they came to fourscore. This he learned from their own confession; for otherwise there not being above two or three of that species born in an age, they were too few to form a general observation by. When they came to fourscore years, which is reckoned the extremity of living in this country, they had not only all the follies and infirmities of other old men, but many more which arose from the dreadful prospect of never dying. They were not only opinionative, peevish, covetous, morose, vain, talkative, but uncapable of friendship, and dead to all natural affection, which never descended below their grandchildren. Envy and impotent desires are their prevailing passions. But those objects against which their envy principally directed, are the vices of the younger sort, and the deaths of the old. By reflecting on the former, they find themselves cut off from all possibility of pleasure; and whenever they see a funeral, they lament and repine that others have gone to a harbor of rest, to which they themselves never can hope to arrive. They have no remembrance of anything but what they learned and observed in their youth and middle age, and even that is very imperfect. And for the truth or particulars of any fact, it is safer to depend on common traditions than upon their best recollections. The least miserable among them appear to be those who turn to dotage, and entirely lose their memories; these meet with more pity and assistance, because they want many bad qualities which abound in others. If a struldbrug happen to marry one of his own kind, the marriage is dissolved of course by the courtesy of the kingdom, as soon as the younger of the two comes to be fourscore. For the law thinks it a reasonable indulgence, that those who are condemned without any fault of their own to a perpetual continuance in the world, should not have their misery doubled by the load of a wife. As soon as they have completed the term of eighty years, they are looked on as dead in law; their heirs immediately succeed to their estates, only a small pittance is reserved for their support, and the poor ones are maintained at the public charge. After that period they are held incapable of any employment of trust or profit, they cannot purchase lands or take leases, neither are they allowed to be witnesses in any cause, either civil or criminal, not even for the decision of meers and bounds. {P_3|CH_10 ^paragraph 15} At ninety they lose their teeth and hair, they have at that age no distinction of taste, but eat and drink whatever they can get, without relish or appetite. The diseases they were subject to still continue without increasing or diminishing. In talking they forget the common appellation of things, and the names of persons, even of those who are their nearest friends and relations. For the same reason they never can amuse themselves with reading, because their memory will not serve to carry them from the beginning of a sentence to the end; and by this defect they are deprived of the only entertainment whereof they might otherwise be capable. The language of this country being always upon the flux, the struldbrugs of one age do not understand those of another, neither are they able after two hundred years to hold any conversation (farther than by a few general words) with their neighbors the mortals; and thus they lie under the disadvantage of living like foreigners in their own country. This was the account given me of the struldbrugs, as near as I can remember. I afterwards saw five or six of different ages, the youngest not above two hundred years old, who were brought to me at several times by some of my friends; but although they were told that I was a great traveler, and had seen all the world, they had not the least curiosity to ask me a question; only desired I would give them slumskudask, or a token of remembrance, which is a modest way of begging, to avoid the law that strictly forbids it, because they are provided for by the public, although indeed with a very scanty allowance. They are despised and hated by all sorts of people; when one of them is born, it is reckoned ominous, and their birth is recorded very particularly; so that you may know their age by consulting the registry, which however hath not been kept above a thousand years past, or at least hath been destroyed by time or public disturbances. But the usual way of computing how old they are, is by asking them what kings or great persons they can remember, and then consulting history, for infallibly the last prince in their mind did not begin his reign after they were fourscore years old. They were the most mortifying sight I ever beheld, and the women more horrible than the men. Besides the usual deformities in extreme old age, they acquired an additional ghastliness in proportion to their number of years, which is not to be described; and among half a dozen, I soon distinguished which was the eldest, although there were not above a century or two between them. {P_3|CH_10 ^paragraph 20} The reader will easily believe, that from what I had heard and seen, my keen appetite for perpetuity of life was much abated. I grew heartily ashamed of the pleasing visions I had formed, and thought no tyrant could invent a death into which I would not run with pleasure from such a life. The king heard of all that had passed between me and my friends upon this occasion, and rallied me very pleasantly, wishing I would send a couple of struldbrugs to my own country, to arm our people against the fear of death; but this it seems is forbidden by the fundamental laws of the kingdom, or else I should have been well content with the trouble and expense of transporting them. I could not but agree that the laws of this kingdom relating to the struldbrugs, were founded upon the strongest reasons, and such as any other country would be under the necessity of enacting in the like circumstances. Otherwise, as avarice is the necessary consequent of old age, those immortals would in time become proprietors of the whole nation, and engross the civil power, which, for want of abilities to manage, must end in the ruin of the public. P_3|CH_11 CHAPTER XI - I thought this account of the Struldbrugs might be some entertainment to the reader, because it seems to be a little out of the common way, at least I do not remember to have met the like in any book of travels that has come to my hands; and if I am deceived, my excuse must be, that it is necessary for travelers, who describe the same country, very often to agree in dwelling on the same particulars, without deserving the censure of having borrowed or transcribed from those who wrote before them. There is indeed a perpetual commerce between this kingdom and the great empire of Japan, and it is very probable that the Japanese authors may have given some account of the struldbrugs; but my stay in Japan was so short, and I was so entirely a stranger to that language, that I was not qualified to make any inquiries. But I hope the Dutch upon this notice will be curious and able enough to supply my defects. His Majesty having often pressed me to accept some employment in his court, and finding me absolutely determined to return to my native country, was pleased to give me his license to depart, and honored me with a letter of recommendation under his own hand to the Emperor of Japan. He likewise presented me with four hundred and forty-four large pieces of gold (this nation delighting in even numbers), and a red diamond which I sold in England for eleven hundred pounds. On the 6th day of May, 1709 I took a solemn leave of his Majesty and all my friends. This prince was so gracious as to order a guard to conduct me Glanguenstald, which is a royal port to the southwest part of the island. In six days I found a vessel ready to carry me to Japan, and spent fifteen days in the voyage. We landed at a small port town called Xamoschi, situated on the southeast part of Japan; the town lies on the western point, where there is a narrow strait, leading northward into a long arm of the sea, upon the northwest part of which, Yedo the metropolis stands. At landing, I showed the custom house officers my letter from the King of Luggnagg to his Imperial Majesty. They knew the seal perfectly well; it was as broad as the palm of my hand. The impression was, a King lifting up a lame beggar from the earth. The magistrates of the town hearing of my letter, received me as a public minister. They provided me with carriages and servants, and bore my charges to Yedo, where I was admitted to an audience, and delivered my letter, which was opened with great ceremony, and explained to the Emperor by an interpreter, who then gave me notice by his Majesty's order, that I should signify my request, and, whatever it were, it should be granted for the sake of his royal brother of Luggnagg. This interpreter was a person employed to transact affairs with the Hollanders; he soon conjectured by my countenance that I was a European, and therefore repeated his Majesty's commands in Low Dutch, which he spoke perfectly well. I answered (as I had before determined) that I was a Dutch merchant, shipwrecked in a very remote country, from whence I traveled by sea and land to Luggnagg, and then took shipping for Japan, where I knew my countrymen often traded, and with some of these I hoped to get an opportunity of returning into Europe: I therefore most humbly entreated his royal favor, to give order that I should be conducted in safety to Nangasac. To this I added another petition, that for the sake of my patron the King of Luggnagg, his Majesty would condescend to excuse my performing the ceremony imposed on my countrymen, of trampling upon the crucifix, because I had been thrown into his kingdom by my misfortunes, without any intention of trading. When this latter petition was interpreted to the Emperor, he seemed a little surprised, and said he believed I was the first of my countrymen who ever made any scruple in this point, and that he began to doubt whether I was a real Hollander or not, but rather suspected I must be a Christian. However, for the reasons I had offered, but chiefly to gratify the King of Luggnagg by an uncommon mark of his favor, he would comply with the singularity of my humor; but the affair must be managed with dexterity, and his officers should be commanded to let me pass as it were by forgetfulness. For he assured me, that if the secret should be discovered by my countrymen the Dutch, they would cut my throat in the voyage. I returned my thanks by the interpreter for so unusual a favor, and some troops being at that time on their march to Nangasac, the commanding officer had orders to convey me safe thither, with particular instructions about the business of the crucifix. On the 9th day of June, 1709, I arrived at Nangasac, after a very long and troublesome journey. I soon fell into the company of some Dutch sailors belonging to the Amboyna, of Amsterdam, a stout ship of 450 tons. I had lived long in Holland, pursuing my studies at Leyden, and I spoke Dutch well. The seamen soon knew from whence I came last: they were curious to inquire into my voyages and course of life. I made up a story as short and probable as I could, but concealed the greatest part. I knew many persons in Holland; I was able to invent names for my parents, whom I pretended to be obscure people in the province of Gelderland. I would have given the captain (one Theodorus Vangrult) what he pleased to ask for my voyage to Holland; but understanding I was a surgeon, he was contented to take half the usual rate, on condition that I would serve him in the way of my calling. Before we took shipping, I was often asked by some of the crew whether I had performed the ceremony above mentioned. I evaded the question by general answers, that I had satisfied the Emperor and court in all particulars. However, a malicious rogue of a skipper went to an officer, and pointing to me, told him I had not yet trampled on the crucifix: but the other, who had received instructions to let me pass, gave the rascal twenty strokes on the shoulders with a bamboo, after which I was no more troubled with such questions. {P_3|CH_11 ^paragraph 5} Nothing happened worth mentioning in this voyage. We sailed with a fair wind to the Cape of Good Hope, where we stayed only to take in fresh water. On the 10th of April we arrived safe at Amsterdam, having lost only three men by sickness in the voyage, and a fourth who fell from the foremast into the sea, not far from the coast of Guinea. From Amsterdam I soon after set sail for England in a small vessel belonging to that city. On the 16th of April, 1710, we put in at the Downs. I landed the next morning, and saw once more my native country after an absence of five years and six months complete. I went straight to Redriff, where I arrived the same day at two in the afternoon, and found my wife and family in good health. - THE END OF THE THIRD PART PART IV A VOYAGE TO THE HOUYHNHNMS (SEE PLATE 6) P_4|CH_1 CHAPTER I - I continued at home with my wife and children about five months in a very happy condition, if I could have learned the lesson of knowing when I was well. I left my poor wife big with child, and accepted an advantageous offer made me to be Captain of the Adventure, a stout merchantman of 350 tons: for I understood navigation well, and being grown weary of a surgeon's employment at sea, which however I could exercise upon occasion, I took a skillful young man of that calling, one Robert Purefoy, into my ship. We set sail from Portsmouth upon the seventh day of August, 1710; on the fourteenth we met with Captain Pocock of Bristol, at Teneriffe, who was going to the bay of Campechy, to cut logwood. On the sixteenth he was parted from us by a storm; I heard since my return that his ship foundered, and none escaped but one cabin boy. He was an honest man, and a good sailor, but a little too positive in his own opinions, which was the cause of his destruction, as it has been of several others. For if he had followed my advice, he might have been safe at home with his family at this time, as well as myself. I had several men die in my ship of calentures, so that I was forced to get recruits out of Barbadoes, and the Leeward Islands, where I touched by the direction of the merchants who employed me, which I had soon too much cause to repent: for I found afterwards that most of them had been buccaneers. I had fifty hands on board, and my orders were that I should trade with the Indians in the South Sea, and make what discoveries I could. These rogues whom I had picked up debauched my other men, and they all formed a conspiracy to seize the ship and secure me; which they did one morning, rushing into my cabin, and binding me hand and foot, threatening to throw me overboard, if I offered to stir. I told them I was their prisoner and would submit. This they made me swear to do, and then they unbound me, only fastening one of my legs with a chain near my bed, and placed a sentry at my door with his piece charged, who was commanded to shoot me dead, if I attempted my liberty. They sent me down victuals and drink, and took the government of the ship to themselves. Their design was to turn pirates, and plunder the Spaniards, which they could not do, till they got more men. But first they resolved to sell the goods in the ship, and then go to Madagascar for recruits, several among them having died since my confinement. They sailed many weeks, and traded with the Indians, but I knew not what course they took, being kept a close prisoner in my cabin, and expecting nothing less than to be murdered, as they often threatened me. Upon the ninth day of May, 1711, one James Welch came down to my cabin; and said he had orders from the Captain to set me ashore. I expostulated with him but in vain; neither would he so much as tell me who their new Captain was. They forced me into the longboat, letting me put on my best suit of clothes, which were as good as new, and a small bundle of linen, but no arms except my hanger; and they were so civil as not to search my pockets, into which I conveyed what money I had, with some other little necessaries. They rowed about a league, and then set me down on a strand. I desired them to tell me what country it was. They all swore they knew no more than myself, but said that the Captain (as they called him) was resolved, after they had sold the lading, to get rid of me in the first place where they could discover land. They pushed off immediately, advising me to make haste, for fear of being overtaken by the tide, and so bade me farewell. In this desolate condition I advanced forward, and soon got upon ground, where I sat down on a bank to rest myself, and consider what I had best do. When I was a little refreshed I went up into the country, resolving to deliver myself to the first savages I should meet, and purchase my life from them by some bracelets, glass rings, and other toys which sailors usually provide themselves with in those voyages, and whereof I had some about me. The land was divided by long rows of trees, not regularly planted, but naturally growing; there was plenty of grass, and several fields of oats. I walked very circumspectly for fear of being surprised, or suddenly shot with an arrow from behind or on either side. I fell into a beaten road, where I saw many tracks of human feet, and some of cows, but most of horses. At last I beheld several animals in a field, and one or two of the same kind sitting in trees. Their shape was very singular and deformed, which a little discomposed me, so that I lay down behind a thicket to observe them better. Some of them coming forward near the place where I lay, gave me an opportunity of distinctly marking their form. Their heads and breasts were covered with a thick hair, some frizzled and others lank; they had beards like goats, and a long ridge of hair down their backs and the foreparts of their legs and feet, but the rest of their bodies were bare, so that I might see their skins, which were of a brown buff color. They had no tails, nor any hair at all on their buttocks, except about the anus; which, I presume, nature had placed there to defend them as they sat on the ground; for this posture they used, as well as lying down and often stood on their hind feet. They climbed high trees, as nimbly as a squirrel, for they had strong extended claws before and behind, terminating in sharp points, and hooked. They would often spring and bound and leap with prodigious agility. The females were not so large as the males; they had long lank hair on their heads, but none on their faces, nor anything more than a sort of down on the rest of their bodies, except about the anus, and pudenda. Their dugs hung between their forefeet, and often reached almost to the ground as they walked. The hair of both sexes was of several colors, brown, red, black, and yellow. Upon the whole, I never beheld in all my travels so disagreeable an animal, nor one against which I naturally conceived so strong an antipathy. So that thinking I had seen enough, full of contempt and aversion, I got up and pursued the beaten road, hoping it might direct me to the cabin of some Indian. I had not got far when I met one of these creatures full in my way, and coming up directly to me. The ugly monster, when he saw me, distorted several ways every feature of his visage, and stared as at an object he had never seen before; then approaching nearer, lifted up his forepaw, whether out of curiosity or mischief, I could not tell. But I drew my hanger, and gave him a good blow with the flat side of it, for I dare not strike him with the edge, fearing the inhabitants might be provoked against me, if they should come to know that I had killed or maimed any of their cattle. When the beast felt the smart, he drew back, and roared so loud that a herd of at least forty came flocking about me from the next field, howling and making odious faces; but I ran to the body of a tree, and leaning my back against it, kept them off by waving my hanger. Several of this cursed brood getting hold of the branches behind, leaped up into the tree, from where they began to discharge their excrements on my head; however, I escaped pretty well, by sticking close to the stem of the tree, but was almost stifled with the filth, which fell about me on every side. In the midst of this distress, I observed them all to run away of a sudden as fast as they could, at which I ventured to leave the tree, and pursue the road, wondering what it was that could put them into this fright. But looking on my left hand, I saw a horse walking softly in the field; which my persecutors having sooner discovered, was the cause of their flight. The horse started a little when he came near me, but soon recovering himself, looked full in my face with manifest tokens of wonder; he viewed my hands and feet, walking round me several times. I would have pursued my journey, but he placed himself directly in the way, yet looking with a very mild aspect, never offering the least violence. We stood gazing at each other for some time; at last I took the boldness to reach my hand towards his neck, with a design to stroke it, using the common style and whistle of jockeys when they are going to handle a strange horse. But this animal seeming to receive my civilities with disdain, shook his head, and bent his brows, softly raising up his right forefoot to remove my hand. Then he neighed three or four times, but in so different a cadence, that I almost began to think he was speaking to himself in some language of his own. {P_4|CH_1 ^paragraph 5} While he and I were thus employed, another horse came up; who applying himself to the first in a very formal manner, they gently struck each other's right hoof before, neighing several times by turns, and varying the sound, which seemed to be almost articulate. They went some paces off, as if it were to confer together, walking side by side, backward and forward, like persons deliberating upon some affair of weight, but often turning their eyes towards me, as it were to watch that I might not escape. I was amazed to see such actions and behavior in brute beasts, and concluded with myself, that if the inhabitants of this country were endued with a proportionable degree of reason, they must needs be the wisest people upon earth. This thought gave me so much comfort, that I resolved to go forward until I could discover some house or village, or meet with any of the natives, leaving the two horses to discourse together as they pleased. But the first, who was a dapple gray, observing me to steal off, neighed after me in so expressive a tone, that I fancied myself to understand what he meant; whereupon I turned back, and came near him, to expect his farther commands, but concealing my fear as much as I could, for I began to be in some pain, how this adventure might terminate; and the reader will easily believe I did not much like my present situation. The two horses came up close to me, looking with great earnestness upon my face and hands. The gray steed rubbed my hat all round with his right forehoof, and discomposed it so much that I was forced to adjust it better, by taking it off, and settling it again; whereat both he and his companion (who was a brown bay) appeared to be much surprised; the latter felt the lappet of my coat, and finding it to hang loose about me, they both looked with new signs of wonder. He stroked my right hand, seeming to admire the softness and color; but he squeezed it so hard between his hoof and his pastern, that I was forced to roar; after which they both touched me with all possible tenderness. They were under great perplexity about my shoes and stockings, which they felt very often, neighing to each other, and using various gestures, not unlike those of a philosopher, when he would attempt to solve some new and difficult phenomenon. Upon the whole, the behavior of these animals was so orderly and rational, so acute and judicious, that I at last concluded they must needs be magicians, who had thus metamorphosed themselves upon some design, and seeing a stranger the way, were resolved to divert themselves with him; or perhaps were really amazed at the sight of a man so very different in habit, feature, and complexion from those who might probably live so remote a climate. Upon the strength of this reasoning, I ventured to address them in the following manner: Gentlemen, if you be conjurers, as I have good cause to believe, you can understand any language; therefore I make bold to let your worships know that I am a poor distressed Englishman, driven by his misfortunes upon your coast, and I entreat one of you, to let me ride upon his back, as if he were a real horse, to some house or village where I can be relieved. In return of which favor I will make you a present of this knife and bracelet (taking them out of my pocket). The two creatures stood silent while I spoke, seeming to listen with great attention; and when I had ended, they neighed frequently towards each other, as if they were engaged in serious conversation. I plainly observed, that their language expressed the passions very well, and the words might with little pains be resolved into an alphabet more easily than the Chinese. I could frequently distinguish the word Yahoo, which was repeated by each of them several times; and although it was impossible for me to conjecture what it meant, yet while the two horses were busy in conversation, I endeavored to practice this word upon my tongue; and as soon as they were silent, I boldly pronounced Yahoo in a loud voice, imitating, at the same time, as near as I could, the neighing of a horse; at which they were both visibly surprised, and the gray repeated the same word twice, as if he meant to teach me the right accent, wherein I spoke after him as well as I could, and found myself perceivably to improve every time, though very far from any degree of perfection. Then the bay tried me with a second word, much harder to be pronounced; but reducing it to the English orthography, may be spelt thus, Houyhnhnm. I did not succeed in this so well as the former, but after two or three farther trials, I had better fortune; and they both appeared amazed at my capacity. After some further discourse, which I then conjectured might relate to me, the two friends took their leave, with the same compliment of striking each other's hoof; and the gray made me signs that I should walk before him, wherein I thought it prudent to comply, till I could find a better director. When I offered to slacken my pace, he would cry Hhuun, Hhuun; I guessed his meaning, and gave him to understand as well as I could, that I was weary, and not able to walk faster; upon which he would stand a while to let me rest. P_4|CH_2 CHAPTER II - Having traveled about three miles, we came to a long kind of building, made of timber stuck in the ground, and wattled across; the roof was low, and covered with straw. I now began to be a little comforted, and took out some toys, which travelers usually carry for presents to the savage Indians of America and other parts, in hopes the people of the house would be thereby encouraged to receive me kindly. The horse made me a sign to go in first; it was a large room with a smooth clay floor, and a rack and manger extending the whole length on one side. There were three nags, and two mares, not eating, but some of them sitting down upon their hams, which I very much wondered at; but wondered more to see the rest employed in domestic business. These seemed but ordinary cattle; however, this confirmed my first opinion, that a people who could so far civilize brute animals, must needs excel in wisdom all the nations of the world. The gray came in just after, and thereby prevented any ill treatment which the others might have given me. He neighed to them several times in a style of authority, and received answers. Beyond this room there were three others, reaching the length of the house, to which you passed through three doors, opposite to each other, in the manner of a vista; we went through the second room towards the third; here the gray walked in first, beckoning me to attend: I waited in the second room, and got ready my presents for the master and mistress of the house: they were two knives, three bracelets of pearl, a small looking glass, and a bead necklace. The horse neighed three or four times, and I waited to hear some answers in a human voice, but I heard no other returns than in the same dialect, only one or two a little shriller than his. I began to think that this house must belong to some person of great note among them, because there appeared so much ceremony before I could gain admittance. But, that a man of quality should be served all by horses, was beyond my comprehension. I feared my brain was disturbed by my sufferings and misfortunes: I roused myself, and looked about me in the room where I was left alone; this was furnished like the first, only after a more elegant manner. I rubbed my eyes often, but the same objects still occurred. I pinched my arms and sides to awake myself, hoping I might be in a dream. then absolutely concluded, that all these appearances could be nothing else but necromancy and magic. But I had no time to pursue these reflections; for the gray horse came to the door, and made me a sign to follow him into the third room, where I saw a very comely mare, together with a colt and foal, sitting on their haunches, upon mats of straw, not unartfuUy made, and perfectly neat and clean. The mare soon after my entrance, rose from her mat, and coming up close, after having nicely observed my hands and face, gave me a most contemptuous look; then turning to the horse, I heard the word Yahoo often repeated betwixt them; the meaning of which word I could not then comprehend, although it were the first I had learned to pronounce; but I was soon better informed, to my everlasting mortification: for the horse beckoning to me with his head, and repeating the word Hhuun, Hhuun, as he did upon the road, which I understood was to attend him, led me out into a kind of court, where was another building at some distance from the house. Here we entered, and I saw three of these detestable creatures, whom I first met after my landing, feeding upon roots, and the flesh of some animals, which I afterwards found to be that of asses and dogs, and now and then a cow dead by accident or discase. were all tied by the neck with strong withes, fastened to a beam; they held their food between the claws of their forefeet, and tore it with their teeth. The master horse ordered a sorrel nag, one of his servants, to untie the largest of these animals, and take him into the yard. The beast and I were brought close together, and our countenances diligently compared, both by master and servant, who thereupon repeated several times the word Yahoo. My horror and astonishment are not to be described, when I observed in this abominable animal a perfect human figure: the face of it indeed was flat and broad, the nose depressed, the lips large, and the mouth wide. But these differences are common to all savage nations, where the lineaments of the countenance are distorted by the natives suffering their infants to lie groveling on the earth, or by carrying them on their backs, nuzzling with their face against the mother's shoulders. The fore feet of the Yahoo differed from my hands in nothing else but the length of the nails, the coarseness and brownness of the palms, and the hairiness on the backs. There was the same resemblance between our feet, with the same differences, which I knew very well, though the horses did not, because of my shoes and stockings; the same in every part of our bodies, except as to hairiness and color, which I have already described. The great difficulty that seemed to stick with the two horses, was to see the rest of my body so very different from that of a Yahoo, for which I was obliged to my clothes whereof they had no conception. The sorrel nag offered me a root, which he held (after their manner, as we shall describe in its proper place) between his hoof and pastern; I took it in my hand, and having smelt it, returned it to him again as civilly as I could. He brought out of the Yahoo's kennel a piece of ass's flesh, but it smelt so offensively that I turned from it with loathing: he then threw it to the Yahoo, by whom it was greedily devoured. He afterwards showed me a wisp of hay, and a fetlock full of oats; but I shook my head, to signify that neither of these were food for me. And indeed, I now apprehended that I must absolutely starve, if I did not get to some of my own species; for as to those filthy Yahoos, although there were few greater lovers of mankind, at that time, than myself, yet I confess I never saw any sensitive being so detestable on all accounts; and the more I came near them, the more hateful they grew, while I stayed in that country. This the master horse observed by my behavior, and therefore sent the Yahoo back to his kennel. He then put his fore hoof to his mouth, at which I was much surprised, although he did it with ease, and with a motion that appeared perfectly natural, and made other signs to know what I would eat; but I could not return him such an answer as he was able to apprehend; and if he had understood me, I did not see how it was possible to contrive any way for finding myself nourishment. While we were thus engaged, I observed a cow passing by, whereupon I pointed to her, and expressed a desire to let me go and milk her. This had its effect; for he led me back into the house, and ordered a mareservant to open a room, where a good store of milk lay in earthen and wooden vessels, after a very orderly and cleanly manner. She gave me a large bowl full, of which I drank very heartily, and found myself well refreshed. {P_4|CH_2 ^paragraph 5} About noon I saw coming towards the house a kind of vehicle, drawn like a sledge by four Yahoos. There was in it an old steed, who seemed to be of quality; he alighted with his hind feet forward, having by accident got a hurt in his left fore foot. He came to dine with our horse, who received him with great civility. They dined in the best room, and had oats boiled in milk for the second course, which the old horse ate warm, but the rest cold. Their mangers were placed circular in the middle of the room, and divided into several partitions, round which they sat on their haunches upon bosses of straw. In the middle was a large rack with angles answering to every partition of the manger; so that each horse and mare ate their own hay, and their own mash of oats and milk, with much decency and regularity. The behavior of the young colt and foal appeared very modest, and that of the master and mistress extremely cheerful and complaisant to their guest. The gray ordered me to stand by him, and much discourse passed between him and his friend concerning me, as I found by the stranger's often looking on me, and the frequent repetition of the word Yahoo. I happened to wear my gloves, which the master gray observing, seemed perplexed, discovering signs of wonder what I had done to my fore feet; he put his hoof three or four times to them, as if he would signify that I should reduce them to their former shape, which I presently did, pulling off both my gloves, and putting them into my pocket. This occasioned farther talk, and I saw the company was pleased with my behavior, whereof I soon found the good effects. I was ordered to speak the few words I understood, and while they were at dinner the master taught the names for oats, milk, fire, water, and some others; which I could readily pronounce after him, having from my youth a great facility in learning languages. When dinner was done the master horse took me aside, and by signs and words made me understand the concern that he was in, that I had nothing to eat. Oats in their tongue are called hlunnh. This word I pronounced two or three times; for although I had refused them at first, yet upon second thoughts I considered that I could contrive to make of them a kind of bread, which might be sufficient with milk to keep me alive, till I could make my escape to some other country and to creatures of my own species. The horse immediately ordered a white mare-servant of his family to bring me a good quantity of oats in a sort of wooden tray. These I heated before the fire as well as I could, and rubbed them till the husks came off, which I made a shift to winnow from the grain; I ground and beat them between two stones, then took water, and made them into a paste or cake, which I toasted at the fire, and ate warm with milk. It was at first a very insipid diet, though common enough in many parts of Europe, but grew tolerable by time; and having been often reduced to hard fare in my life, this was not the first experiment I had made how easily nature is satisfied. And I cannot but observe, that I never had one hour's sickness while I stayed in this island. 'Tis true, I sometimes made a shift to catch a rabbit or bird by springes made of Yahoos' hairs, and I often gathered wholesome herbs, which I boiled, or ate as salads with my bread, and now and then, for a rarity, I made a little butter, and drank the whey. I was at first at a great loss for salt; but custom soon reconciled the want of it; and I am confident that the frequent use of salt among us is an effect of luxury, and was first introduced only as a provocative to drink; except where it is necessary for preserving of flesh in long voyages, or in places remote from great markets. For we observe no animal to be fond of it but man: and as to myself, when I left this country, it was a great while before I could endure the taste of it in anything that I ate. This is enough to say upon the subject of my diet, wherewith other travelers fill their books, as if the readers were personally concerned whether we fared well or ill. However, it necessary to mention this matter, lest the world should think it impossible that I could find sustenance for three years in such a country, and among such inhabitants. When it grew towards evening, the master horse ordered a place for me to lodge in; it was but six yards from the house, and separated from the stable of the Yahoos. Here I got some straw, and covering myself with my own clothes, slept very sound. But I was in a short time better accommodated, as the reader shall know hereafter, when I come to treat more particularly about my way of living. P_4|CH_3 CHAPTER III - My principal endeavor was to learn the language, which my master (for so I shall henceforth call him) and his children, and every servant of his house, were desirous to teach me. For they looked upon it as a prodigy that a brute animal should discover such marks of a rational creature. I pointed to every thing and inquired the name of it, which I wrote down in my journal book when I was alone, and corrected my bad accent by desiring those of the family to pronounce it often. In this employment, a sorrel nag, one of the under servants, was ready to assist me. In speaking they pronounce through the nose and throat, and their language approaches nearest to the High Dutch or German of any I know in Europe; but is much more graceful and significant. The Emperor Charles made almost the same observation, when he said that if he were to speak to his horse it should be in High Dutch. The curiosity and impatience of my master were so great, that he spent many hours of his leisure to instruct me. He was convinced (as he afterwards told me) that I must be a Yahoo, but my teachableness, civility, and cleanliness, astonished him; which were qualities altogether so opposite to those animals. He was most perplexed about my clothes, reasoning sometimes with himself whether they were a part of my body; for I never pulled them off till the family were asleep, and got them on before they waked in the morning. My master was eager to learn from where I came, how I acquired those appearances of reason which I discovered in all my actions, and to know my story from my own mouth, which he hoped he should soon do by the great proficiency I made in learning and pronouncing their words and sentences. To help my memory, I formed all I learned into the English alphabet, and wrote the words down with the translations. Ibis last after some time I ventured to do in my master's presence. It cost me much trouble to explain to him what I was doing; for the inhabitants have not the least idea of books or literature. In about ten weeks time I was able to understand most of his questions, and in three months could give him some tolerable answers. He was extremely curious to know from what part of the country I came, and how I was taught to imitate a rational creature; because the Yahoos (whom he saw I exactly resembled in my head, hands, and face, that were only visible), with some appearance of cunning, and the strongest disposition to mischief, were observed to be the most unteachable of all brutes. I answered that I came over the sea from a far place, with many others of my own kind, in a great hollow vessel made of the bodies of trees. That my companions forced me to land on this coast, and then left me to shift for myself. It was with some difficulty, and by the help of many signs, that I brought him to understand me. He replied, that I must needs be mistaken, or that I said the thing which was not. (For they have no word in their language to express lying or falsehood.) He knew it was impossible that there could be a country beyond the sea, or that a parcel of brutes could move a wooden vessel whither they pleased upon water. He was sure no Houyhnhnm alive could make such a vessel, nor would trust Yahoos to manage it. The word Houyhnhnm, in their tongue, signifies a horse, and in its etymology, the perfection of nature. I told my master, that I was at a loss for expression, but would improve as fast as I could; and hoped in a short time I should be able to tell him wonders: he was pleased to direct his own mare, his colt and foal, and the servants of the family, to take all opportunities of instructing me, and every day for two or three hours he was at the same pains himself. Several horses and mares of quality in the neighborhood came often to our house upon the report spread of a wonderful Yahoo, that could speak like a Houyhnhnm, and seemed in his words and actions to discover some glimmerings of reason. These delighted to converse with me: they put many questions, and received such answers as I was able to return. By all these advantages I made so great a progress that in five months from my arrival I understood whatever was spoke, and could express myself tolerably well. {P_4|CH_3 ^paragraph 5} The Houyhnhnms who came to visit my master with the design of seeing and talking with me, could hardly believe me to be a right Yahoo, because my body had a different covering from others of my kind. They were astonished to observe me without the usual hair or skin, except on my head, face, and hands; but I discovered that secret to my master, upon an accident which happened about a fortnight before. I have already told the reader, that every night when the family were gone to bed it was my custom to strip and cover myself with my clothes. It happened one morning early, that my master sent for me by the sorrel nag, who was his valet; when he came I was fast asleep, my clothes fallen off on one side, and my shirt above my waist. I awakened at the noise he made, and observed him to deliver his message in some disorder; after which he went to my master, and in a great fright gave him a very confused account of what he had seen. This I presently discovered; for going as soon as I was dressed to pay my attendance upon his Honor, he asked me the meaning of what his servant had reported, that I was not the same thing when I slept as I appeared to be at other times; that his valet assured him, some part of me was white, some yellow, at least not so white, and some brown. I had hitherto concealed the secret of my dress, in order to distinguish myself as much as possible from that cursed race of Yahoos; but now I found it in vain to do so any longer. Besides, I considered that my clothes and shoes would soon wear out, which already were in a declining condition, and must be supplied by some contrivance from the hides of Yahoos or other brutes; whereby the whole secret would be known. I therefore told my master that in the country from which I came those of my kind always covered their bodies with the hairs of certain animals prepared by art, as well for decency as to avoid the inclemencies of air, both hot and cold; of which, as to my own person, I would give him immediate conviction, if he pleased to command me; only desiring his excuse, if I did not expose those parts that nature taught us to conceal. He said my discourse was all very strange, but especially the last part; for he could not understand why nature should teach us to conceal what nature had given. That neither himself nor family were ashamed of any parts of their bodies; but however I might do as I pleased. Whereupon I first unbuttoned my coat and pulled it off. I did the same with my waistcoat; I drew off my shoes, stockings, and breeches. I let my shirt down to my waist, and drew up the bottom, fastening it like a girdle about my middle to hide my nakedness. My master observed the whole performance with great signs of curiosity and admiration. He took up all my clothes in his pastern, one piece after another, and examined them diligently; he then stroked my body very gently and looked round me several times, after which he said it was plain I must be a perfect Yahoo; but that I differed very much from the rest of my species, in the softness and whiteness and smoothness of my skin, my want of hair in several parts of my body, the shape and shortness of my claws behind and before, and my affectation of walking continually on my two hind feet. He desired to see no more, and gave me leave to put on my clothes again, for I was shuddering with cold. I expressed my uneasiness at his giving me so often the appellation of Yahoo, an odious animal for which I had so utter a hatred and contempt. I begged he would forbear applying that word to me, and take the same order in his family, and among his friends whom he suffered to see me. I requested likewise that the secret of my having a false covering to my body might be known to none but himself, at least as long as my present clothing should last; for as to what the sorrel nag his valet had observed, his Honor might command him to conceal it. {P_4|CH_3 ^paragraph 10} All this my master very graciously consented to, and thus the secret was kept till my clothes began to wear out, which I was forced to supply by several contrivances that shall hereafter be mentioned. In the meantime he desired I would go on with my utmost diligence to learn their language, because he was more astonished at my capacity for speech and reason than at the figure of my body, whether it were covered or not; adding that he waited with some impatience to hear the wonders which I promised to tell him. From thenceforward he doubled the pains he had been at to instruct me; he brought me into all company, and made them treat me with civility, because, as he told them privately, this would put me into good humor and make me more diverting. Every day when I waited on him, beside the trouble he was at in teaching, he would ask me several questions concerning myself, which I answered as well as I could; and by these means he had already received some general ideas, though very imperfect. It would be tedious to relate the several steps by which I advanced to a more regular conversation: but the first account I gave of myself in any order and length, was to this purpose: That I came from a very far country, as I already had attempted to tell him, with about fifty more of my own species; that we traveled upon the seas, in a great hollow vessel made of wood, and larger than his Honor's house. I described the ship to him in the best terms I could, and explained by the help of my handkerchief displayed, how it was driven forward by the wind. That upon a quarrel among us, I was set on shore on this coast, where I walked forward without knowing whither, till he delivered me from the persecution of those execrable Yahoos. He asked me who made the ship, and how it was possible that the Houyhnhnms of my country would leave it to the management of brutes? My answer was that I dare proceed no further in my relation, unless he would give me his word and honor that he would not be offended, and then I would tell him the wonders I had so often promised. He agreed; and I went on by assuring him that the ship was made by creatures was myself, who in all the countries I had traveled, as well as in my own, were the only governing, rational animals; and that upon my arrival here I was as much astonished to see the Houyhnhnms act like rational beings, as he or his friends could be finding some marks of reason in a creature he was pleased to call a Yahoo, to which I owned my resemblance in every part, but could not account for their degenerate and brutal nature. I said farther that if good fortune ever restored me to my native country, to relate my travels here, as I resolved to do, everybody would believe that I said the thing which was not; that I invented the story out of my own head; and with all possible respect to himself, his family and friends, and under his promise of not being offended, our countrymen would hardly think it probable, that a Houyhnhnm should be the presiding creature of a nation, and a Yahoo the brute. P_4|CH_4 CHAPTER IV - My master heard me with great appearances of uneasiness in his countenance, because doubting, or not believing, are so little known in this country, that the inhabitants cannot tell how to behave themselves under such circumstances. And I remember in frequent discourses with my master concerning the nature of manhood in other parts of the world, having occasion to talk of lying and false representation, it was with much difficulty that he comprehended what I meant, although he had otherwise a most acute judgment. For he argued thus: that the use of speech was to make us understand one another, and to receive information of facts; now if anyone said the thing which was not, these ends were defeated; because I cannot properly be said to understand him; and I am so far from receiving information, that he leaves me worse than in ignorance, for I am led to believe a thing black when it is white, and short when it is long. And these were all the notions he had concerning that faculty of lying, so perfectly well understood among human creatures. To return from this digression; when I asserted that the Yahoos were the only governing animals in my country, which my master said was altogether past his conception, he desired to know whether we had Houyhnhnms among us, and what was their employment: I told him we had great numbers, that in summer they grazed in the fields, and in winter were kept in houses, with hay and oats, where Yahoo servants were employed to rub their skins smooth, comb their manes, pick their feet, serve them with food, and make their beds. I understand you well, said my master, it is now very plain, from all you have spoken, that whatever share of reason the Yahoos pretend to, the Houyhnhnms are your masters; I heartily wish our Yahoos would be so tractable. I begged his Honor would please to excuse me from proceeding any farther, because I was very certain that the account he expected from me would be highly displeasing. But he insisted in commanding me to let him know the best and the worst: I told him he should be obeyed. I owned that the Houyhnhnms among us, whom we called horses, were the most generous and comely animals we had, that they excelled in strength and swiftness; and when they belonged to persons of quality, employed in traveling, racing, or drawing chariots, they were treated with much kindness and till they fell into diseases or became foundered in the feet; and then they were sold, and used to all kind of drudgery till they died; after which their skins were stripped and sold for what they were worth, and their bodies left to be devoured by dogs and birds of prey. But the common race of horses had not so good fortune, being kept by farmers and carriers, and other mean people, who put them to great labor, and fed them worse. I described, as well as I could, our way of riding, the shape and use of a bridle, a saddle, a spur, and a whip, of harness and wheels. I added that we fastened plates of a certain hard substance called iron at the bottom of their feet, to preserve their hoofs from being broken by the stony ways on which we often traveled. My master, after some expressions of great indignation, wondered how we dared to venture upon a Houyhnhnm's back, for he was sure that the weakest servant in his house would be able to shake off the strongest Yahoo, or by lying down and rolling on his back squeeze the brute to death. I answered that our horses were trained up from three or four years old to the several uses we intended them for; that if any of them proved intolerably vicious, they were employed for carriages; that they were severely beaten while they were young, for any mischievous tricks; that the males, designed for common use of riding or draught, were generally castrated about two years after their birth, to take down their spirits and make them more tame and gentle; that they were indeed sensible of rewards and punishments; but his Honor would please to consider, that they had not the least tincture of reason any more than the Yahoos in this country. It put me to the pains of many circumlocutions to give my master a right idea of what I spoke; for their language does not abound in variety of words, because their wants and passions are fewer than among us. But it is impossible to represent his noble resentment at our savage treatment of the Houyhnhnm race, particularly after I had explained the manner and use of castrating horses among us, to hinder them from propagating their kind, and to render them more servile. He said if it were possible there could be any country where Yahoos alone were endued with reason, they certainly must be the governing animal, because reason will in time always prevail against brutal strength. But considering the frame of our bodies, and especially of mine, he thought no creature of equal bulk was so ill contrived, for employing that reason in the common offices of life; whereupon he desired to know whether those among whom I lived resembled me or the Yahoos of his country. I assured him, that I was as well shaped as most of my age; but the younger and the females were much more soft and tender, and the skins of the latter generally as white as milk. He said I differed indeed from other Yahoos, being much more cleanly, and not altogether so deformed, but in point of real advantage he thought I differed for the worse. That my nails were of no use either to my fore or hind feet; as to my fore feet, he could not properly call them by that name, for he never observed me to walk upon them; that they were too soft to bear the ground; that I generally went with them uncovered, neither was the covering I sometimes wore on them of the same shape or so strong as that on my feet behind. That I could not walk with any security, for if either of my hind feet slipped, I must inevitably fall. He then began to find fault with other parts of my body, the flatness of my face, the prominence of my nose, my eyes placed directly in front, so that I could not look on either side without turning my that I was not able to feed myself without lifting one of my fore feet to my mouth; and therefore nature had placed these joints to answer that necessity. He knew not what could be the use of those several clefts and divisions in my feet behind; that these were too soft to bear the hardness and sharpness of stones without a covering made from the skin of some other brute; that my whole body wanted a fence against heat cold, which I was forced to put on and off every day with tediousness and trouble. And lastly that he observed every animal in this country naturally to abhor the Yahoos, whom the weaker avoided and the stronger drove from them. So that supposing us to have the gift of reason, he could not see how it were possible to cure that natural antipathy which every creature discovered against us; nor consequently, how we could tame and render them serviceable. However, he would (as he said) debate the matter no farther, because he was more desirous to know my own story, the country where I was born, and the several actions and events of my life before I came here. I assured him how extremely desirous I was that he should be satisfied on every point; but I doubted much whether it would be possible for me to explain myself on several subjects whereof his Honor could have no conception, because I saw nothing in his country to which I could resemble them. That however I would do my best, and strive to express myself by similitudes, humbly desiring his assistance when I wanted proper words; which he was pleased to promise me. {P_4|CH_4 ^paragraph 5} I said my birth was of honest parents in an island called England, which was remote from this country, as many days' journey as the strongest of his Honor's servants could travel in the annual course of the sun. That I was bred a surgeon, whose trade it is to cure wounds and hurts in the body, got by accident or violence; that my country was governed by a female man, whom we called a Queen. That I left it to get riches, whereby I might maintain myself and family when I should return. That in my last voyage I was Commander of the ship, and had about fifty Yahoos under me, many of which died at sea, and I was forced to supply them by others picked out from several nations. That our ship was twice in danger of being sunk; the first time by a great storm, and the second, by striking against a rock. Here my master interposed, by asking me how I could persuade strangers out of different countries to venture with me, after the losses I had sustained, and the hazards I had run. I said they were fellows of desperate fortunes, forced to fly from the places of their birth, on account of their poverty or their crimes. Some were undone by lawsuits; others spent all they had in drinking, whoring, and gaming; others fled for treason; many for murder, theft, poisoning, robbery, perjury, forgery, coining false money, for committing rapes or sodomy, for flying from their colors, or deserting to the enemy, and most of them had broken prison; none of these dared return to their native countries for fear of being hanged, or of starving in a jail; and therefore were under the necessity of seeking a livelihood in other places. During this discourse my master was pleased to interrupt me several times; I had made use of many circumlocutions in describing to him the nature of the several crimes, for which most of our crew had been forced to fly their country. This labor took up several days' conversation before he was able to comprehend me. He was wholly at a loss to know what could be the use or necessity of practicing those vices. To clear up which I endeavored to give some ideas of the desire of power and riches, of the terrible effects of lust, intemperance, malice and envy. All this I was forced to define and describe by putting of cases, and making of suppositions. After which, like one whose imagination was struck with something never seen or heard of before, he would lift up his eyes with amazement and indignation. Power, government, war, law, punishment, and a thousand other things had no terms wherein that language could express them, which made the difficulty almost insuperable to give my master any conception of what I meant. But being of an excellent understanding, much improved by contemplation and converse, he at last arrived at a competent knowledge of what human nature in our parts of the world is capable to perform, and desired I would give him some particular account of that land which we call Europe, but especially of my own country. P_4|CH_5 CHAPTER V - The reader may please to observe, that the following extract of many conversations I had with my master, contains a summary of the most material points which were discoursed at several times for above two years; his Honor often desiring fuller satisfaction as I farther improved in the Houyhnhnm tongue. I laid before him, as well as I could, the whole state of Europe; I discoursed of trade and manufactures, of arts and sciences; and the answers I gave to all the questions he made, as they arose upon several subjects, were a fund of conversation not to be exhausted. But I shall here only set down the substance of what passed between us concerning my own country, reducing it into order as well as I can, without any regard to time or other circumstances, while I strictly adhere to truth. My only concern is that I shall hardly be able to do justice to my master's arguments and expressions, which must needs suffer by my want of capacity, as well as by a translation into our barbarous English. In obedience therefore to his Honor's commands, I related to him the Revolution under the Prince of Orange; the long war with France entered into by the said prince, and renewed by his successor the present Queen, wherein the greatest powers of Christendom were engaged, and which still continued: I computed at his request that about a million of Yahoos might have been killed in the whole progress of it, and perhaps a hundred or more cities taken, and thrice as many ships burnt or sunk. He asked me what were the usual causes or motives that made one country go to war with another. I answered they were innumerable, but I should only mention a few of the chief. Sometimes the ambition of princes, who never think they have land or people enough to govern; sometimes the corruption of ministers, who engage their master in a war in order to stifle or divert the clamor of the subjects against their evil administration. Difference in opinions has cost many millions of lives: for instance, whether flesh be bread, or bread be flesh; whether the juice of a certain berry be blood or wine; whether whistling be vice or a virtue; whether it be better to kiss a post, or throw it into the fire; what is the best color for a coat, whether black, white, red, or gray; and whether it should be long or short, narrow or wide, dirty or clean; with many more. Neither are any wars so furious and bloody, or of so long continuance, as those occasioned by difference in opinion, especially if it be in things indifferent. Sometimes the quarrel between two princes is to which of them shall dispossess a third of his dominions, where neither of them pretend to any right. Sometimes one prince quarrels with another, for fear the other should quarrel with him. Sometimes a war is entered upon, because the enemy is too strong, and sometimes because he is too weak. Sometimes our neighbors want the things which we have, or have the things which we want; and we both fight, till they take ours or give us theirs. It is a very justifiable cause of a war to invade a country after the people have been wasted by famine, destroyed by pestilence, or embroiled by factions among themselves. It is justifiable to enter into war against our nearest ally, when one of his towns lies convenient for us, or a territory of land, that would render our dominions round and complete. If a prince sends forces into a nation where the people are poor and ignorant, he may lawfully put half of them to death, and make slaves of the rest, in order to civilize and reduce them from their barbarous way of living. It is a very kingly, honorable, and frequent practice, when one prince desires the assistance of another to secure him against an invasion, that the assistant, when he has driven out the invader, should seize on the dominions himself, and kill, imprison or banish the prince he came to relieve. Alliance by blood or marriage is a frequent cause of war between princes; and the nearer the kindred is, the greater is their disposition to quarrel: poor nations are hungry, and rich nations are proud; and pride and hunger will ever be at variance. For these reasons, the trade of a soldier is held the most honorable of all others; because a soldier is a Yahoo hired to kill in cold blood as many of his own species, who have never offended him, as possibly he can. There is likewise a kind of beggarly princes in Europe, not able to make war by themselves, who hire out their troops to richer nations, for so much a day to each man; of which they keep three-fourths to themselves, and it is the best part of their maintenance; such are those in Germany and other northern parts of Europe. {P_4|CH_5 ^paragraph 5} What you have told me (said my master) upon the subject of war, does indeed discover most admirably the effects of that reason you pretend to: however, it is happy that the shame is greater than the danger; and that nature has left you utterly uncapable of doing much mischief. For your mouths lying flat with your faces, you can hardly bite each other to any purpose, unless by consent. Then as to the claws upon your feet before and behind, they are so short and tender, that one of our Yahoos would drive a dozen of yours before him. And therefore in recounting the numbers of those who have been killed in battle, I cannot but think that you have said the thing which is not. I could not forbear shaking my head and smiling a little at his ignorance. And being no stranger to the art of war, I gave him a description of cannons, culverins, muskets, carabines, pistols, bullets, powder, swords, bayonets, battles, sieges, retreats, attacks, undermines, countermines, bombardments, sea fights; ships sunk with a thousand men, twenty thousand killed on each side; dying groans, limbs flying in the air, smoke, noise, confusion, trampling to death under horses' feet; flight, pursuit, victory; fields strewed with carcases left for food to dogs, and wolves, and birds of prey; plundering, stripping, ravishing, burning, and destroying. And to set forth the valor of my own dear countrymen, I assured him that I had seen them blow up a hundred enemies at once in a siege, and as many in a ship, and beheld the dead bodies come down in pieces from the clouds, to the great diversion of the spectators. I was going on to more particulars, when my master commanded me silence. He said whoever understood the nature of Yahoos might easily believe it possible for so vile animals to be capable of every action I had named, if their strength and cunning squalled their malice. But as my discourse had increased his abhorrence of the whole species, so he found it gave him a disturbance in his mind, to which he was wholly a stranger before. He thought his ears being used to such abominable words, might by degrees admit them with less detestation. That although he hated the Yahoos of this country, yet he no more blamed them for their odious qualities, than he did a gnnayh (a bird of prey) for its cruelty, or a sharp stone for cutting his hoof. But when a creature pretending to reason could be capable of such enormities, he dreaded lest the corruption of that faculty might be worse than brutality itself. He seemed therefore confident, that instead of reason, we were only possessed of some quality fitted to increase our natural vices; as the reflection from a troubled stream returns the image of an ill-shapen body, not only larger, but more distorted. He added, that he had heard too much upon the subject of war, both in this and some former discourses. There was another point which a little perplexed him at present. I had informed him, that some of our crew left their country on account of being ruined by Law; that I had already explained the meaning of the word; but he was at a loss how it should come to pass, that the law which was intended for every man's preservation, should be any man's ruin. Therefore he desired to be further satisfied what I meant by law, and the dispensers thereof, according to the present practice in my own country; because he thought nature and reason were sufficient guides for a reasonable animal, as we pretended to be, in showing us what we ought to do, and what to avoid. {P_4|CH_5 ^paragraph 10} I assured his Honor that law was a science wherein I had not much conversed, further than by employing advocates, in vain, upon some injustices that had been done me: however, I would give him all the satisfaction I was able. I said there was a society of men among us, bred up from their youth in the art of proving by words multiplied for the purpose, that white is black, and black is white, according as they are paid. To this society all the rest of the people are slaves. For example, if my neighbor has a mind to my cow, he hires a lawyer to prove that he ought to have my cow from me. I must then hire another to defend my right, it being against all rules of law that any man should be allowed to speak for himself. Now in this case I who am the right owner lie under two great disadvantages. First, my lawyer, being practiced almost from his cradle in defending falsehood, is quite out of his element when he would be an advocate for justice, which as an office unnatural, he always attempts with great awkwardness if not with ill-will. The second disadvantage is that my lawyer must proceed with great caution, or else he will be reprimanded by the judges, and abhorred by his brethren, as one that would lessen the practice of the law. And therefore I have but two methods to preserve my cow. The first is to gain over my adversary's lawyer with a double fee, who will then betray his client by insinuating that he has justice on his side. The second way is for my lawyer to make my cause appear as unjust as he can by the cow to belong to my adversary: and this, if it be skillfully done will certainly bespeak the favor of the bench. Now, your Honour is to know that these judges an appointed to decide all controversies of property, as well as for the trial of criminals, and picked out from the most dexterous lawyers, who are grown old or lazy, and having been biassed all their lives against truth and equity, are under such a fatal necessity of favoring fraud, perjury, and oppression, that I have known several of them refuse a large bribe from the side where justice lay, rather than injure the faculty, by doing any thing unbecoming their nature or their office. It is a maxim among these lawyers, that whatever has been done before may legally be done again: and therefore they take special care to record all the decisions formerly made against common justice and the general reason of mankind. These, under the name of precedents, they produce as authorities, to justify the most iniquitous opinions; and the judges never fail of directing accordingly. In pleading they studiously avoid entering into the merits of the cause, but are loud, violent, and tedious in dwelling upon all circumstances which are not to the purpose. For instance, in the case already mentioned, they never desire to know what claim or title my adversary has to my cow; but whether the said cow were red or black, her horns long or short, whether the field I graze her in be round or square, whether she was milked at home or abroad, what diseases she is subject to, and the like; after which they consult precedents, adjourn the cause from time to time, and in ten, twenty, or thirty years, come to an issue. {P_4|CH_5 ^paragraph 15} It is likewise to be observed, that this society has a peculiar cant and jargon of their own, that no other mortal can understand, and wherein all their laws are written, which they take special care to multiply; whereby they have wholly confounded the very essence of truth and falsehood, of right and wrong; so that it will take thirty years to decide whether the field left me by my ancestors for six generations belongs to me, or to a stranger three hundred miles off. In the trial of persons accused for crimes against the state the method is much more short and commendable: the judge first sends to sound the disposition of those in power, after which he can easily hang or save the criminal, strictly preserving all due forms of law. Here my master interposing, said it was a pity that creatures endowed with such prodigious abilities of mind as these lawyers, by the description I gave of them, must certainly be, were not rather encouraged to be instructors of others in wisdom and knowledge. In answer to which I assured his Honor that in all points out of their own trade, they were usually the most ignorant and stupid generation among us, the most despicable in common conversation, avowed enemies to all knowledge and learning, and equally to pervert the general reason of mankind in every other subject of discourse, as in that of their own profession. P_4|CH_6 CHAPTER VI - My master was yet wholly at a loss to understand what motives could incite this race of lawyers to perplex, disquiet, and weary themselves, and engage in a confederacy of injustice, merely for the sake of injuring their fellow animals; neither could he comprehend what I meant in saying they did it for hire. Whereupon I was at much pains to describe to him the use of money, the materials it was made of, and the value of the metals; that when a Yahoo had got a great store of this precious substance, he was able to purchase whatever he had a mind to; the finest clothing, the noblest houses, great tracts of land, the most costly meats and drinks, and have his choice of the most beautiful females. Therefore since money alone was able to perform all these feats, our Yahoos thought they could never have enough of it to spend or save, as they found themselves inclined from their natural bent either to profusion or avarice. That the rich man enjoyed the fruit of the poor man's labor, and the latter were a thousand to one in proportion to the former. That the bulk of our people were forced to live miserably, by laboring every day for small wages to make a few live plentifully. I enlarged myself much on these and many other particulars to the same purpose; but his Honor was still to seek; for he went upon a supposition that all animals had a title to their share in the productions of the earth, and especially those who presided over the rest. Therefore he desired I would let him know what these costly meats were, and how any of us happened to want them. Whereupon I enumerated as many sorts as came into my head, with the various methods of dressing them, which could not be done without sending vessels by sea to every part of the world, as well for liquors to drink, as for sauces, and innumerable other conveniences. I assured him that this whole globe of earth must be at least three times gone round, before one of our better female Yahoos could get her breakfast or a cup to put it in. He said that must needs be a miserable country which cannot furnish food for its own inhabitants. But what he chiefly wondered at, was how such vast tracts of ground as I described should be wholly without fresh water, and the people put to the necessity of sending over the sea for drink. I replied that England (the dear place of my nativity) was computed to produce three times the quantity of food, more than its inhabitants are able to consume, as well as liquors extracted from grain, or pressed out of the fruit of certain trees, which made excellent drink, and the same proportion in every other convenience of life. But, in order to feed the luxury and intemperance of the males, and the vanity of the females, we sent away the greatest part of our necessary things to other countries, from whence in return we brought the materials of diseases, folly, and vice, to spend among ourselves. Hence it follows of necessity that vast numbers of our people are compelled to seek their livelihood by begging, robbing, stealing, cheating, pimping, forswearing, flattering, suborning, forging, gaming, lying, fawning, hectoring, voting, scribbling, star-gazing, poisoning, whoring, canting, libeling, free thinking, and the like occupations: every one of which terms, I was at much pains to make him understand. That wine was not imported among us from foreign countries, to supply the want of water or other drinks, but because it was a sort of liquid which made us merry by putting us out of our senses, diverted all melancholy thoughts, begat wild extravagant imaginations in the brain, raised our hopes, and banished our fears, suspended every office of reason for a time, and deprived us of the use of our limbs, till we fell into a profound sleep; although it must be confessed, that we always awoke sick and dispirited and that the use of this liquor filled us with diseases, which made our lives uncomfortable and short. But beside all this, the bulk of our people supported themselves by furnishing the necessities or conveniences of life to the rich, and to each other. For instance, when I am at home and dressed as I ought to be, I carry on my body the workmanship of a hundred tradesmen; the building and furniture of my house employ as many more, and five times the number to adorn my wife. I was going on to tell him of another sort of people, who get their livelihood by attending the sick, having upon some occasions informed his Honor that many of my crew had died of diseases. But here it was with the utmost difficulty that I brought him to apprehend what I meant. He could easily conceive that a Houyhnhnm grew weak and heavy a few days before his death, or by some accident might hurt a limb. But that nature, who works all things to perfection, should suffer any pains to breed in our bodies, he thought impossible, and desired to know the reason of so unaccountable an evil. I told him we fed on a thousand things which operated contrary to each other; that we ate when we were not hungry, and drank without the provocation of thirst; that we sat whole nights strong liquors without eating a bit, which disposed us to sloth, inflamed our bodies, and precipitated or prevented digestion. That prostitute female Yahoos acquired a certain malady, which bred rottenness in the bones of those who fell into their embraces; that this and many other diseases were propagated from father to son, so that great numbers come into the world with complicated maladies upon them; that it would be endless to give him a catalogue of all diseases incident to human bodies; for they could not be fewer than five or six hundred, spread over every limb and joint; in short, every part, external and intestine, having diseases appropriated to them. To remedy which there was a sort of people bred up among us, in the profession or pretense of curing the sick. And because I had some skill in the faculty, I would in gratitude to his Honor let him know the whole mystery and method by which they proceed. Their fundamental is that all diseases arise from repletion, from which they conclude that a great evacuation of the body is necessary, either through the natural passage or upwards at the mouth. Their next business is from herbs, minerals, gums, oils, shells, salts, juices, seaweed, excrements, barks of trees, serpents, toads, frogs, spiders, dead men's flesh and bone, birds, beasts and fishes, to form a composition for smell and taste the most abominable, nauseous and detestable they can possibly contrive, which the stomach immediately rejects with loathing; and this they call a vomit; or else from the same storehouse, with some other poisonous additions, they command us to take in at the orifice above or below (just as the physician then happens to be disposed) a medicine equally annoying and disgustful to the bowels; which relaxing the belly, drives down all before it, and this they call a purge or a cluster. For nature (as the physicians allege) having intended the superior anterior orifice only for the intromission of solids and liquids, and the inferior posterior for ejection, these artists ingeniously considering that in all diseases nature is forced out of her seat, therefore to replace her in it the body must be treated in a manner directly contrary, by interchanging the use of each orifice, forcing solids and liquids in at the anus, and making evacuations at the mouth. {P_4|CH_6 ^paragraph 5} But besides real diseases we are subject to many that are only imaginary, for which the physicians have invented imaginary cures; these have their several names, and so have the drugs that are proper for them, and with these our female Yahoos are always infested. One great excellency in this tribe is their skiff at prognostics, wherein they seldom fail; their predictions in real diseases, when they rise to any degree of malignity, generally portending death, which is always in their power, when recovery is not: and therefore, upon any unexpected signs of amendment, after they have pronounced their sentence, rather than be accused as false prophets, they know how to approve their sagacity to the world by a seasonable dose. They are likewise of special use to husbands and wives who are grown weary of their mates, to eldest sons, to great ministers of state, and often to princes. I had formerly upon occasion discoursed with my master upon the nature of government in general, and particularly of our own excellent constitution, deservedly the wonder and envy of the whole world. But having here accidentally mentioned a minister of state, he commanded me some time after to inform him what species of Yahoo I particularly meant by that appellation. I told him that a First or Chief Minister of State, who was the person I intended to describe, was a creature wholly exempt from joy and grief, love and hatred, pity and anger; at least made use of no other passions but a violent desire of wealth, power, and titles; that he applies his words to all uses, except to the indication of his mind; that he never tells a truth but with an intent that you should take it for a lie; nor a lie but with a design that you should take it for a truth; that those he speaks worst of behind their backs are in the surest way of preferment; and whenever he begins to praise you to others or to yourself, you are from that day forlorn. The worst mark you can receive is a promise, especially when it is confirmed with an oath; after which every wise man retires, and gives over all hopes. {P_4|CH_6 ^paragraph 10} There are three methods by which a man may rise to be chief minister: the first is by knowing how with prudence to dispose of a wife, a daughter, or a sister: the second, by betraying or undermining his predecessor: and the third is by a furious zeal in public assemblies against the corruptions of the court. But a wise prince would rather choose to employ those who practice the last of these methods; because such zealots prove always the most obsequious and subservient to the will and passions of their master. That these ministers having all employments at their disposal, preserve themselves in power by bribing the majority of a senate or great council; and at last, by an expedient called an Act of Indemnity (whereof I described the nature to him) they secure themselves from after reckonings, and retire from the public, laden with the spoils of the nation. The palace of a chief minister is a seminary to breed up others in his own trade: the pages, lackeys, and porter, by imitating their master, become ministers of state in their several districts, and learn to excel in the three principal ingredients of insolence, lying, and bribery. Accordingly they have a subaltern court paid to them by persons of the best rank, and sometimes by the force of dexterity and impudence arrive through several gradations to be successors to their lord. He is usually governed by a decayed wench or favorite footman, who are the tunnels through which all graces are conveyed, and may properly be called, in the last resort, the governors of the kingdom. One day in discourse my master, having heard me mention the nobility of my country, was pleased to make me a compliment which I could not pretend to deserve: that he was sure I must have been born of some noble family, because I far exceeded in shape, color, and cleanliness, all the Yahoos of his nation, although I seemed to fail in strength and agility, which must be imputed to my different way of living from those other brutes; and besides I was not only endowed with the faculty of speech, but likewise with some rudiments of reason, to a degree that with all his acquaintance I passed for a prodigy. He made me observe, that among the Houyhnhnms, the white, the sorrel, and the iron grey were not so exactly shaped as the bay, the dapple grey, and the black; nor born with equal talents of the mind, or a capacity to improve them; and therefore continued always in the condition of servants, without ever aspiring to match out of their own race, which in that country would be reckoned monstrous and unnatural. {P_4|CH_6 ^paragraph 15} I made his Honor my most humble acknowledgments for the good opinion he was pleased to conceive of me; but assured him at the same time that my birth was of the lower sort, having been born of plain honest parents, who were just able to give me a tolerable education; that nobility among us was altogether a different thing from the idea he had of it; that our young noblemen are bred from their childhood in idleness and luxury; that as soon as years will permit, they consume their vigor and contract odious diseases among lewd females; and when their fortunes are almost ruined, they marry some woman of mean birth, disagreeable person, and unsound constitution, merely for the sake of money, whom they hate and despise. That the productions of such marriages are generally scrofulous, rickety, or deformed children; by which means the family seldom continues above three generations, unless the wife takes care to provide a healthy father among her neighbors or domestics, in order to improve and continue the breed. That a weak diseased body, a meagre countenance, and sallow complexion, are the true marks of noble blood; and a healthy robust appearance is so disgraceful in a man of quality, that the world concludes his real father to have been a groom or a coachman. The imperfections of his mind run parallel with those of his body, being a composition of spleen, dullness, ignorance, caprice, sensuality and pride. Without the consent of this illustrious body no law can be enacted, repealed, or altered; and these have the decision of all our possessions without appeal. P_4|CH_7 CHAPTER VII - The reader may be disposed to wonder how I could prevail on myself to give so free a representation of my own species, among a race of mortals who were already too apt to conceive the vilest opinion of human kind, from that entire congruity betwixt me and their Yahoos. But I must freely confess that the many virtues of those excellent quadrupeds placed in opposite view to human corruptions, had so far opened my eyes and enlarged my understanding, that I began to view the actions and passions of man in a very different light, and to think the honor of my own kind not worth managing; which, besides, it was impossible for me to do before a person of so acute a judgment as my master, who daily convinced me of a thousand faults in myself, whereof I had not the least perception before, and which among us would never be numbered even among human infirmities. I had likewise learned from his example an utter detestation of all falsehood or disguise, and truth appeared so amiable to me, that I determined upon sacrificing everything to it. Let me deal so candidly with the reader as to confess that there was yet a much stronger motive for the freedom I took in my representation of things. I had not been a year in this country before I contracted such a love and veneration for the inhabitants, that I entered on a firm resolution never to return to human kind, but to pass the rest of my life among these admirable Houyhnhnms in the contemplation and practice of every virtue; where I could have no example or incitement to vice. But it was decreed by fortune, my perpetual enemy, that so great a felicity should not fall to my share. However, it is now some comfort to reflect that in what I said of my countrymen I extenuated their faults as much as I dared before so strict an examiner, and upon every article gave as favorable a turn as the matter would bear. For indeed who is there alive that will not be swayed by his bias and partiality to the place of his birth? I have related the substance of several conversations I had with my master, during the greatest part of the time I had the honor to be in his service, but have indeed for brevity sake omitted much more than is here set down. When I had answered all his questions, and his curiosity seemed to be fully satisfied, he sent for me one morning early, and commanding me to sit down at some distance (an honor which he had never before conferred upon me), he said he had been very seriously considering my whole story, as far as it related both to myself and my country; that he looked upon us as sort of animals to whose share, by what accident he could not conjecture, some small pittance of reason had fallen, whereof we made no other use than by its assistance to aggravate our natural corruptions, and to acquire new ones which nature had not given us. That we disarmed ourselves of the few abilities she had bestowed, had been very successful in multiplying our original wants, and seemed to spend our whole lives in vain endeavors to supply them by our own inventions. That as to myself, it was manifest I had neither the strength or agility of a common Yahoo, that I walked infirmly on my hinder feet, had found out a contrivance to make my claws of no use or defense, and to remove the hair from my chin, which was intended as a shelter from the sun and the weather. Lastly, that I could neither run with speed, nor climb trees like my brethren (as he called them) the Yahoos in this country. That our institutions of government and law were plainly owing to our gross defects in reason, and by consequence, in virtue; because reason alone is sufficient to govern a rational creature; which was therefore a character we had no pretense to challenge, even from the account I had given of my own people; although he manifestly perceived that in order to favor them I had concealed many particulars, and often said the thing which was not. {P_4|CH_7 ^paragraph 5} He was the more confirmed in this opinion, because he observed that as I agreed in every feature of my body with other Yahoos, except where it was to my real disadvantage in point of strength, speed and activity, the shortness of my claws, and some other particulars where nature had no part; so from the representation I had given him of our lives, our manners, and our actions, he found as near a resemblance in the disposition of our minds. He said the Yahoos were known to hate one another more than they did any different species of animals; and the reason usually assigned was the odiousness of their own shapes, which all could see in the rest, but not in themselves. He had therefore begun to think it not unwise in us to cover our bodies, and by that invention conceal many of our own deformities from each other, which would else be hardly supportable. But he now found he had been mistaken, and that the dissensions of those brutes in his country were owing to the same cause with ours, as I had described them. For if (said he) you throw among five Yahoos as much food as would be sufficient for fifty, they will, instead of eating peaceably, fall together by the ears, each single one impatient to have all to itself; and therefore a servant was usually employed to stand by while they were feeding abroad, and those kept at home were tied at a distance from each other: that if a cow died of age or accident, before a Houyhnhnm could secure it for his own Yahoos, those in the neighborhood would come in herds to seize it, and then would ensue such a battle as I had described, with terrible wounds made by their claws on both sides, although they seldom were able to kill one another, for want of such convenient instruments of death as we had invented. At other times the like battles have been fought between the Yahoos of several neighborhoods without any visible cause; those of one district watching all opportunities to surprise the next before they are prepared. But if they find their project has miscarried, they return home, and, for want of enemies, engage in what I call a civil war among themselves. That in some fields of his country there are certain shining stones of several colors, whereof the Yahoos are violently fond, and when part of these stones is fixed in the earth, as it sometimes happens, they will dig with their claws for whole days to get them out, then carry them away, and hide them by heaps in their kennels; but still looking round with great caution, for fear their comrades should find out their treasure. My master said he could never discover the reason of this unnatural appetite, or how these stones could be of any use to a Yahoo; but now he believed it might proceed from the same principle of avarice which I had ascribed to mankind: that he had once, by way of experiment, privately removed a heap of these stones from the place where one of his Yahoos had buried it: whereupon the sordid animal missing his treasure, by his loud lamenting brought the whole herd to the place, there miserably howled, then fell to biting and tearing the rest, began to pine away, would neither eat nor sleep nor work, till he ordered a servant privately to convey the stones into the same hole and hide them as before; which when his Yahoo had found, he presently recovered his spirits and good humor, but took good care to remove them to a better hiding place, and has ever since been a very serviceable brute. My master farther assured me, which I also observed myself, that in the fields where the shining stones abound, the fiercest and most frequent battles are fought, occasioned by perpetual inroads of the neighboring Yahoos. He said it was common when two Yahoos discovered such a stone in a field, and were contending which of them should be the proprietor, a third would take the advantage, and carry it away from them both; which my master would needs contend to have some kind of resemblance with our suits at law; wherein I thought it for our credit not to undeceive him; since the decision he mentioned was much more equitable than many decrees among us; because the plaintiff and defendant there lost nothing beside the stone they contended for, whereas our courts of equity would never have dismissed the cause while either of them had any thing left. My master, his discourse, said there was nothing that rendered the Yahoos more odious than their undistinguishing appetite to devour every thing that came in their way, whether herbs, roots, berries, the corrupted flesh of animals, or all mingled together; and it was peculiar in their temper that they were fonder of what they could get by rapine or stealth at a greater distance than much better food provided for them at home. If their prey held out, they would eat till they were ready to burst, after which nature had pointed out to them a certain root that gave them a general evacuation. {P_4|CH_7 ^paragraph 10} There was also another kind of root very juicy, but somewhat rare and difficult to be found, which the Yahoos sought for with much eagerness, and would suck it with great delight; and it produced in them the same effects that wine has upon us. It would make them sometimes hug, and sometimes tear one another; they would howl and grin, and chatter, and reel, and tumble, and then fall asleep in the dirt. I did indeed observe that the Yahoos were the only animals in this country subject to any diseases; which, however, were much fewer than horses have among us, and contracted not by any ill treatment they meet with, but by the nastiness and greediness of that sordid brute. Neither has their language any more than a general appellation for those maladies, which is borrowed from the name of the beast, and called Hnea-Yahoo, or the Yahoo's evil, and the cure prescribed is a mixture of their own dung and urine forcibly put down the Yahoo's throat. This I have since often known to have been taken with success, and do freely recommend it to my countrymen, for the public good, as an admirable specific against all diseases produced by repletion. As to learning, government, arts, manufactures, and the like, my master confessed he could find little or no resemblance between the Yahoos of that country and those in ours. For he only meant to observe what parity there was in our natures. He had heard indeed some curious Houyhnhnms observe that in most herds there was a sort of ruling Yahoo (as among us there is generally some leading or principal stag in a park), who was always more deformed in body and mischievous in disposition than any of the rest. That this leader had usually a favorite as like himself as he could get, whose employment was to lick his master's feet and posteriors, and drive the female Yahoos to his kennel; for which he was now and then rewarded with a piece of ass's flesh. This favorite is hated by the whole herd, and therefore to protect himself, keeps always near the person of his leader. He usually continues in office till worse can be found; but the very moment he is discarded, his successor, at the head of all the Yahoos in that district, young and old, male and female, come in a body, and discharge their excrements upon him from head to foot. But how far this might be applicable to our courts and favorites, and ministers of state, my master said I could best determine. I dared make no return to this malicious insinuation, which debased human understanding below the sagacity of a common hound, who has judgment enough to distinguish and follow the cry of the ablest dog in the pack, without being ever mistaken. My master told me there were some qualities remarkable in the Yahoos, which he had not observed me to mention, or at least very slightly, in the accounts I had given him of human kind. He said those animals, like other brutes, had their females in common; but in this they differed, that the she Yahoo would admit the male while she was pregnant; and that the hes would quarrel and fight with the females as fiercely as with each other. Both which practices were such degrees of brutality, that no other sensitive creature ever arrived at. {P_4|CH_7 ^paragraph 15} Another thing he wondered at in the Yahoos was their strange disposition to nastiness and dirt, whereas there appears to be a natural love of cleanliness in all other animals. As to the former accusation, I was glad to let it pass without any reply, because I had not a word to offer upon it in defense of my species, which otherwise I certainly had done from my own inclinations. But I could have easily vindicated human kind from the imputation of singularity upon the last article, if there had been any swine in that country (as unluckily for me there were not), which although it may be a sweeter quadruped than a Yahoo, cannot I humbly conceive in justice pretend to more cleanliness; and so his Honor himself must have owned, if he had seen their filthy way of feeding, and their custom of wallowing and sleeping in the mud. My master likewise mentioned another quality which his servants had discovered in several Yahoos, and to him was wholly unaccountable. He said, a fancy would sometimes take a Yahoo to retire into a corner, to lie down and howl and groan, and spurn away all that came near him, although he were young and fat, wanted neither food nor water; nor did the servants imagine what could possibly ail him. And the only remedy they found was to set him to hard work, after which he would infallibly come to himself. To this I was silent out of partiality to my own kind; yet here I could plainly discover the true seeds of spleen, which only seizes on the lazy, the luxurious, and the rich; who, if they were forced to undergo the same regimen, I would undertake for the cure. His Honor had further observed that a female Yahoo would often stand behind a bank or a bush, to gaze on the young males passing by, and then appear, and hide, using many antic gestures and grimaces, at which time it was observed that she had a most offensive smell; and when any of the males advanced, would slowly retire, looking often back, and with a counterfeit show of fear, run off into some convenient place where she knew the male would follow her. At other times if a female stranger came among them, three or four of her own sex would get about her, and stare and chatter, and grin, and smell her all over; and then turn off with gestures that seemed to express contempt and disdain. Perhaps my master might refine a little in these speculations, which he had drawn from what he observed himself, or had been told him by others; however, I could not reflect without some amazement, and much sorrow, that the rudiments of coquetry, censure, and scandal, should have place by instinct in womankind. {P_4|CH_7 ^paragraph 20} I expected every moment that my master would accuse the Yahoos of those unnatural appetites in both sexes, so common among us. But nature, it seems, has not been so expert a school mistress; and these politer pleasures are entirely the productions of art and reason, on our side of the globe. P_4|CH_8 CHAPTER VIII - As I ought to have understood human nature much better than I supposed it possible for my master to do, so it was easy to apply the character he gave of the Yahoos to myself and my countrymen; and I believed I could yet make farther discoveries from my own observation. I therefore often begged his favor to let me go among the herds of Yahoos in the neighborhood, to which he always very graciously consented, being perfectly convinced that the hatred I bore those brutes would never suffer me to be corrupted by them; and his Honor ordered one of his servants, a strong sorrel nag, very honest and good-natured, to be my guard, without whose protection I dare not undertake such adventures. For I have already told the reader how much I was pestered by those odious animals upon my first arrival. And I afterwards failed very narrowly three or four times of falling into their clutches, when I happened to stray at any distance without my hanger. And I have reason to believe they had some imagination that I was of their own species, which I often assisted myself, by stripping up my sleeves, and showing my naked arms and breast in their sight, when my protector was with me. At which times they would approach as near as they dare, and imitate my actions after the manner of monkeys, but ever with great signs of hatred; as a tame jackdaw with cap and stockings is always persecuted by the wild ones, when he happens to get among them. They are prodigiously nimble from their infancy; however, I once caught a young male of three years old, and endeavored by all marks of tenderness to make it quiet; but the little imp fell a squalling and scratching and biting with such violence that I was forced to let it go; and it was high time, for a whole troop of old ones came about us at the noise, but finding the cub was safe (for away it ran), and my sorrel nag being by, they dare not venture near us. I observed the young animal's flesh to smell very rank, and the stink was somewhat between a weasel and a fox, but much more disagreeable. I forgot another circumstance (and perhaps I might have the reader's pardon if it were wholly omitted), that while I held the odious vermin in my hands, it voided its filthy excrements of a yellow liquid substance, all over my clothes; but by good fortune there was a small brook hard by, where I washed myself as clean as I could; although I dare not come into my master's presence, until I were sufficiently aired. By what I could discover, the Yahoos appear to be the most unteachable of all animals, their capacities never reaching higher than to draw or carry burdens. Yet I am of opinion this defect arises chiefly from a perverse, restive disposition. For they are cunning, malicious, treacherous, and revengeful. They are strong and hardy, but of a cowardly spirit, and by consequence, insolent, abject, and cruel. It is observed that the red haired of both sexes are more libidinous and mischievous than the rest, whom yet they much exceed in strength and activity. The Houyhnhnms keep the Yahoos for present use in huts not far from the house; but the rest are sent abroad to certain fields, where they dig up roots, eat several kinds of herbs, and search about for carrion, or sometimes catch weasels and luhimuhs (a sort of wild rat), which they greedily devour. Nature has taught them to dig deep holes with their nails on the side of a rising ground, wherein they lie by themselves; only the kennels of the females are larger, sufficient to hold two or three cubs. They swim from their infancy like frogs, and are able to continue long under water, where they often take fish, which the females carry home to their young. And upon this occasion, I hope the reader will pardon my relating an odd adventure. {P_4|CH_8 ^paragraph 5} Being one day abroad with my protector, the sorrel nag, and the weather exceeding hot, I entreated him to let me bathe in a river that was near. He consented, and I immediately stripped myself stark naked, and went down softly into the stream. It happened that a young female Yahoo, standing behind a bank, saw the whole proceeding, and inflamed by desire, as the nag and I conjectured, came running with all speed, and leaped into the water, within five yards of the place where I bathed. I was never in my life so terribly frighted; the nag was grazing at some distance, not suspecting any harm. She embraced me after a most fulsome manner; I roared as loud as I could, and the nag came galloping towards me, whereupon she quitted her grasp, with the utmost reluctancy, and leaped upon the opposite bank, where she stood gazing and howling all the time I was putting on my clothes. This was matter of diversion to my master and his family, as well as of mortification to myself. For now I could no longer deny that I was a real Yahoo in every limb and feature, since the females had a natural propensity to me, as one of their own species. Neither was the hair of this brute of a red color (which might have been some excuse for an appetite a little irregular), but black as a sloe, and her countenance did not make an appearance altogether so hideous as the rest of the kind; for, I think, she could not be above eleven years old. Having lived three years in this country, the reader I suppose will expect that I should, like other travelers, give him some account of the manners and customs of its inhabitants, which it was indeed my principal study to learn. As these noble Houyhnhnms are endowed by nature with a general disposition to all virtues, and have no conceptions or ideas of what is evil in a rational creature, so their grand maxim is to cultivate reason, and to be wholly governed by it. Neither is reason among them a point problematical as with us, where men can argue with plausibility on both sides of the question; but strikes you with immediate conviction; as it must needs do where it is not mingled, obscured, or discolored by passion and interest. I remember it was with extreme difficulty that I could bring my master to understand the meaning of the word opinion, or how a point could be disputable; because reason taught us to affirm or deny only where we are certain, and beyond our knowledge we cannot do either. So that controversies, wranglings, disputes, and positiveness in false or dubious propositions, are evils unknown among the Houyhnhnms. In the like manner when I used to explain to him our several systems of natural philosophy, he would laugh that a creature pretending to reason should value itself upon the knowledge of other people's conjectures, and in things where that knowledge, if it were certain, could be of no use. Wherein he agreed entirely with the sentiments of Socrates, as Plato delivers them; which I mention as the highest honor I can do that prince of philosophers. I have often since reflected what destruction such a doctrine would make in the libraries of Europe, and how many paths to fame would be then shut up in the learned world. Friendship and benevolence are the two principal virtues among the Houyhnhnms, and these not confined to particular objects, but universal to the whole race. For a stranger from the remotest part is equally treated with the nearest neighbor, and wherever he goes looks upon himself as at home. They preserve decency and civility in the highest degrees, but are altogether ignorant of ceremony. They have no fondness for their colts or foals, but the care they take in educating them proceeds entirely from the dictates of reason. And I observed my master to show the same affection to his neighbor's issue that he had for his own. They will have it that nature teaches them to love the whole species, and it is reason only that makes a distinction of persons, where there is a superior degree of virtue. {P_4|CH_8 ^paragraph 10} When the matron Houyhnhnms have produced one of each sex, they no longer accompany with their consorts, except they lose one of their issue by some casualty, which very seldom happens; but in such a case they meet again; or when the like accident befalls a person whose wife is past bearing, some other couple bestow on him one of their own colts, and then go together again till the mother is pregnant. This caution is necessary to prevent the country from being overburdened with numbers. But the race of inferior Houyhnhnms bred up to be servants is not so strictly limited upon this article; these are allowed to produce three of each sex, to be domestics in the noble families. In their marriages they are exactly careful to choose such colors as will not make any disagreeable mixture in the breed. Strength is chiefly valued in the male, and comeliness in the female; not upon the account of love, but to preserve the race from degenerating; for where a female happens to excel in strength, a consort is chosen with regard to comeliness. Courtship, love, presents, jointures, settlements, have no place in their thoughts, or terms whereby to express them in their language. The young couple meet and are joined, merely because it is the determination of their parents and friends: it is what they see done every day, and they look upon it as one of the necessary actions of a rational being. But the violation of marriage, or any other unchastity, was never heard of; and the married pair pass their lives with the same friendship and mutual benevolence that they bear to all others of the same species who come in their way; without jealousy, fondness, quarrelling, or discontent. In educating the youth of both sexes, their method is admirable, and highly deserves our imitation. These are not suffered to taste a grain of oats, except upon certain days, till eighteen years old; nor milk, but very rarely; and in summer they graze two hours in the morning, and as long in the evening, which their parents likewise observe; but the servants are not allowed above half that time, and a great part of their grass is brought home, which they eat at the most convenient hours, when they can be best spared from work. Temperance, industry, exercise and cleanliness, are the lessons equally enjoined to the young ones of both sexes; and my master thought it monstrous in us to give the females a different kind of education from the males, except in some articles of domestic management; whereby, as he truly observed, one half of our natives were good for nothing but bringing children into the world; and to trust the care of our children to such useless animals, he said, was yet a greater instance of brutality. But the Houyhnhnms train up their youth to strength, speed, and hardiness, by exercising them in running races up and down steep hills, and over hard stony grounds; and when they are all in a sweat, they are ordered to leap over head and ears into a pond or river. Four times a year the youth of a certain district meet to show their proficiency in running and leaping, and other feats of strength and agility; where the victor is rewarded with a song made in his or her praise. On this festival the servants drive a herd of Yahoos into the field, laden with hay and oats and milk, for a repast to the Houyhnhnms; after which these brutes are immediately driven back again, for fear of being noisome to the assembly. {P_4|CH_8 ^paragraph 15} Every fourth year, at the vernal equinox, there is a representative council of the whole nation, which meets in a plain about twenty miles from our house, and continues about five or six days. Here they inquire into the state and condition of the several districts; whether they abound or be deficient in hay or oats, or cows or Yahoos. And wherever there is any want (which is seldom) it is immediately supplied by unanimous consent and contribution. Here likewise the regulation of children is settled: as for instance, if a Houyhnhnm has two males, he changes one of them with another that has two females; and when a child has been lost by any casualty, where the mother is past breeding, it is determined what family in the district shall breed another to supply the loss. P_4|CH_9 CHAPTER IX - One of these grand assemblies was held in my time, about three months before my departure, whither my master went as the representative of our district. In this council was resumed their old debate, and indeed, the only debate which ever happened in that country; whereof my master after his return gave me a very particular account. The question to be debated was whether the Yahoos should be exterminated from the face of the earth. One of the members for the affirmative offered several arguments of great strength and weight, alleging that as the Yahoos were the most filthy, noisome, and deformed animal which nature ever produced, so they were the most restive and indocible, mischievous and malicious: they would privately suck the teats of the Houyhnhnms' cows, kill and devour their cats, trample down their oats and grass, if they were not continually watched, and commit a thousand other extravagancies. He took notice of a general tradition, that Yahoos had not been always in that country; but that many ages ago two of these brutes appeared together upon a mountain, whether produced by the heat of the sun upon corrupted mud and slime, or from the ooze and froth of the sea, was never known. That these Yahoos engendered, and their brood in a short time grew so numerous as to overrun and infest the whole nation. That the Houyhnhnms to get rid of this evil, made a general hunting, and at last enclosed the whole herd; and destroying the elder, every Houyhnhnm kept two young ones in a kennel, and brought them to such a degree of tameness, as an animal so savage by nature can be capable of acquiring; using them for draught and carriage. That there seemed to be much truth in this tradition, and that those creatures could not be Ylnhniamshy (or aborigines of the land), because of the violent hatred the Houyhnhnms, as well as all other animals, bore them; which although their evil disposition sufficiently deserved, could never have arrived at so high a degree, if they had been aborigines, or else they would have long since been rooted out. That the inhabitants taking a fancy to use the service of the Yahoos, had very imprudently neglected to cultivate the breed of asses, which were a comely animal, easily kept, more tame and orderly, without any offensive smell, strong enough for labor, although they yield to the other in agility of body; and if their braying be no agreeable sound, it is far preferable to the horrible howlings of the Yahoos. Several others declared their sentiments to the same purpose, when my master proposed an expedient to the assembly, whereof he had indeed borrowed the hint from me. He approved of the tradition mentioned by the honorable member who spoke before, and affirmed that the two Yahoos said to be first seen among them had been driven there over the sea; that coming to land and being forsaken by their companions they retired to the mountains, and degenerating by degrees, became in process of time, much more savage than those of their own species in the country from where these two originals came. The reason of his assertion was that he had now in his possession a certain wonderful Yahoo (meaning myself), which most of them had heard of, and many of them had seen. He then related to them how he first found me; that my body was all covered with an artificial composure of the skins and hairs of other animals; that I spoke in a language of my own, and had thoroughly learned theirs; that I had related to him the accidents which brought me there; that when he saw me without my covering I was an exact Yahoo in every part, only of a whiter color, less hairy, and with shorter claws. He added how I had endeavored to persuade him that in my own and other countries the Yahoos acted as the governing, rational animal, and held the Houyhnhnms in servitude; that he observed in me all the qualities of a Yahoo, only a little more civilized by some tincture of reason, which however was in a degree as far inferior to the Houyhnhnm race as the Yahoos of their country were to me; that among other things I mentioned a custom we had of castrating Houyhnhnms when they were young, in order to render them tame; that the operation was easy and safe; that it was no shame to learn wisdom from brutes, as industry is taught by the ant, and building by the swallow. (For so I translate the word lyhannh, although it be a much larger fowl.) That this invention might be practiced upon the younger Yahoos here, which, besides rendering them tractable and fitter for use, would in an age put an end to the whole species without destroying life. That in the meantime the Houyhnhnms should be exhorted to cultivate the breed of asses, which, as they are in all respects more valuable brutes, so they have this advantage, to be fit for service at five years old, which the others are not till twelve. This was all my master thought fit to tell me at that time of what passed in the grand council. But he was pleased to conceal one particular, which related personally to myself, whereof I soon felt the unhappy effect, as the reader will know in its proper place, and from which I date all the succeeding misfortunes of my life. The Houyhnhnms have no letters, and consequently their knowledge is all traditional. But there happening few events of any moment among a people so well united, naturally disposed to every virtue, wholly governed by reason, and cut off from all commerce with other nations, the historical part is easily preserved without burdening their memories. I have already observed that they are subject to no diseases, and therefore can have no need of physicians. However, they have excellent medicines composed of herbs, to cure accidental bruises and cuts in the pastern or frog of the foot by sharp stones, as well as other maims and hurts in the several parts of the body. {P_4|CH_9 ^paragraph 5} They calculate the year by the revolution of the sun and the moon, but use no subdivisions into weeks. They are well enough acquainted with the motions of those two luminaries, and understand the nature of eclipses; and this is the utmost progress of their astronomy. In poetry they must be allowed to excel all other mortals; wherein the justness of their similes, and the minuteness, as well as exactness of their descriptions, are indeed inimitable. Their verses abound very much in both of these, and usually contain either some exalted notions of friendship and benevolence, or the praises of those who were victors in races and other bodily exercises. Their buildings, although very rude and simple, are not inconvenient, but well contrived to defend them from all injuries of cold and heat. They have a kind of tree, which at forty years old loosens in the root, and falls with the first storm: they grow very straight, and being pointed like stakes with a sharp stone (for the Houyhnhnms know not the use of iron), they stick them erect in the ground about ten inches asunder, and then weave in oat straw, or sometimes wattles betwixt them. The roof is made after the same manner, and so are the doors. The Houyhnhnms use the hollow part between the pastern and the hoof of their fore feet as we do our hands, and this with greater dexterity than I could at first imagine. I have seen a white mare of our family thread a needle (which I lent her on purpose) with that joint. They milk their cows, reap their oats, and do all the work which requires hands, in the same manner. They have a kind of hard flints, which by grinding against other stones, they form into instruments, that serve instead of wedges, axes, and hammers. With tools made of these flints they likewise cut their hay and reap their oats, which there groweth naturally in several fields: the Yahoos draw home the sheaves in carriages, and the servants tread them in certain covered huts, to get out the grain, which is kept in stores. They make a rude kind of earthen and wooden vessels, and bake the former in the sun. If they can avoid casualties, they die only of old age, and are buried in the most obscure places that can be found, their friends and relations expressing neither joy nor grief at their departure; nor does the dying person discover the least regret that he is leaving the world, any more than if he were upon returning home from a visit to one of his neighbors. I remember my master having once made an appointment with a friend and his family to come to his house upon some affair of importance, on the day fixed the mistress and her two children came very late; she made two excuses, first for her husband, who, as she said, happened that very morning to shnuwnh. The word is strongly expressive in their language, but not easily rendered into English; it signifies, to retire to his first mother. Her excuse for not coming sooner was that her husband dying late in the morning, she was a good while consulting her servants about a convenient place where his body should be laid; and I observed she behaved herself at our house as cheerfully as the rest, and died about three months after. They live generally to seventy or seventy-five years, very seldom to fourscore: some weeks before their death they feel a gradual decay, but without pain. During this time they are much visited by their friends, because they cannot go abroad with their usual ease and satisfaction. However, about ten days before their death, which they seldom fail in computing, they return the visits that have been made them by those who are nearest in the neighborhood, being carried in a convenient sledge drawn by Yahoos; which vehicle they use, not only upon this occasion, but when they grow old, upon long journeys, or when they are lamed by any accident. And therefore when the dying Houyhnhnms return those visits, they take a solemn leave of their friends, as if they were going to some remote part of the country, where they designed to pass the rest of their lives. {P_4|CH_9 ^paragraph 10} I know not whether it may be worth observing that the Houyhnhnms have no word in their language to express any thing that is evil, except what they borrow from the deformities or ill qualities of the Yahoos. Thus they denote the folly of a servant, an omission of a child, a stone that cuts their feet, a continuance of foul or unseasonable weather, and the like, by adding to each the epithet of Yahoo. For instance, Hhnm Yahoo, Whnaholm Yahoo, Ynlhmndwihlma Yahoo, and an ill-contrived house Ynholmhnmrohlnw Yahoo. I could with great pleasure enlarge further upon the manners and virtues of this excellent people; but intending in a short time to publish a volume by itself expressly upon that subject, I refer the reader there, and in the meantime, proceed to relate my own sad catastrophe, P_4|CH_10 CHAPTER X - I had settled my little economy to my own heart's content. My master had ordered a room to be made for me after their manner, about six yards from the house; the sides and floors of which I plastered with clay, and covered with rushmats of my own contriving; I had beaten hemp, which there grows wild, and made of it a sort of ticking; this I filled with the feathers of several birds I had taken with springes made of Yahoos' hairs, and were excellent food. I had worked two chairs with my knife, the sorrel nag helping me in the grosser and more laborious part. When my clothes were worn to rags, I made myself others with the skins of rabbits, and of a certain beautiful animal about the same size, called nnuhnoh, the skin of which is covered with a fine down. Of these I likewise made very tolerable stockings. I soled my shoes with wood which I cut from a tree and fitted to the upper leather, and when this was worn out, I supplied it with the skins of Yahoos dried in the sun. I often got honey out of hollow trees, which I mingled with water, or ate with my bread. No man could more verify the truth of these two maxims, That nature is very easily satisfied; and That necessity is the mother of invention. I enjoyed perfect health of body, and tranquillity of mind; I did not feel the treachery or inconstancy of a friend, nor the injuries of a secret or open enemy. I had no occasion of bribing, flattering, or pimping to procure the favor of any great man or of his minion. I wanted no fence against fraud or oppression; here was neither physician to destroy my body, nor lawyer to ruin my fortune; no informer to watch my words and actions, or forge accusations against me for hire; here were no gibers, censurers, backbiters, pickpockets, highwaymen, housebreakers, attorneys, bawds, buffoons, gamesters, politicians, wits, splenetics, tedious talkers, controvertists, ravishers, murderers, robbers, virtuosos; no leaders or followers of party and faction; no encouragers to vice, by seducement or examples; no dungeon, axes, gibbets, whipping posts, or pillories; no cheating shopkeepers or mechanics; no pride, vanity, or affectation; no fops, bullies, drunkards, strolling whores, or poxes; no ranting, lewd, expensive wives; no stupid, proud pedants; no importunate, overbearing, quarrelsome, noisy, roaring, empty, conceited, swearing companions; no scoundrels, raised from the dust for the sake of their vices, or nobility thrown into it on account of their virtues: no lords, fiddlers, judges, or dancing masters. I had the favor of being admitted to several Houyhnhnms, who came to visit or dine with my master; where his Honor graciously suffered me to wait in the room, and listen to their discourse. Both he and his company would often descend to ask me questions, and receive my answers. I had also sometimes the honor of attending my master in his visits to others. I never presumed to speak, except in answer to a question; and then I did it with inward regret, because it was a loss of so much time for improving myself; but I was infinitely delighted with the station of an humble auditor in such conversations, where nothing passed but what was useful, expressed in the fewest and most significant words; where the greatest decency was observed, without the least degree of ceremony; where no person spoke without being pleased himself, and pleasing his companions; where there was no interruption, tediousness, heat, or difference of sentiments. They have a notion that when people are met together, a silence does much improve conversation: this I found to be true; for during those little intermissions of talk, new ideas would arise in their thoughts, which very much enlivened the discourse. Their subjects are generally on friendship and benevolence, or order and economy; sometimes upon the visible operations of nature, or ancient traditions; upon the bounds and limits of virtue; upon the unerring rules of reason, or upon some determinations to be taken at the next great assembly; and often upon the various excellencies of poetry. I may add without vanity that my presence often gave them sufficient matter for discourse, because it afforded my master an occasion of letting his friends into the history of me and my country, upon which they were all pleased to descant in a manner not very advantageous to human kind; and for that reason I shall not repeat what they said: only I may be allowed to observe that his Honor, to my great admiration, appeared to understand the nature of Yahoos in all countries much better than myself. He went through all our vices and follies, and discovered many which I had never mentioned to him, by only supposing what qualities a Yahoo of their country, with a small proportion of reason, might be capable of exerting; and concluded, with too much probability, how vile as well as miserable such a creature must be. I freely confess that all the little knowledge I have of any value was acquired by the lectures I received from my master, and from hearing the discourses of him and his friends; to which I should be prouder to listen than to dictate to the greatest and wisest assembly in Europe. I admired the strength, comeliness, and speed of the inhabitants; and such a constellation of virtues in such amiable persons produced in me the highest veneration. At first, indeed, I did not feel that natural awe which the Yahoos and all other animals bear towards them; but it grew upon me by degrees, much sooner than I imagined, and was mingled with a respectful love and gratitude, that they would condescend to distinguish me from the rest of my species. When I thought of my family, my friends, my countrymen, or human race in general, I considered them as they really were, Yahoos in shape and disposition, perhaps a little more civilized, and qualified with the gift of speech, but making no other use of reason than to improve and multiply those vices whereof their brethren in this country had only the share that nature allotted them. When I happened to behold the reflection of my own form in a lake or fountain, I turned away my face in horror and detestation of myself, and could better endure the sight of a common Yahoo than of my own person. By conversing with the Houyhnhnms, and looking upon them with delight, I fell to imitate their gait and gesture, which is now grown into an habit, and my friends often tell me in a blunt way, that I trot like a horse; which, however, I take for a great compliment. Neither shall I disown that in speaking I am apt to fall into the voice and manner of the Houyhnhnms, and hear myself ridiculed on that account without the least mortification. In the midst of all this happiness, and when I looked upon myself to be fully settled for life, my master sent for me one morning a little earlier than his usual hour. I observed by his countenance that he was in some perplexity, and at a loss how to begin what he had to speak. After a short silence he told me he did not know how I would take what he was going to say; that in the last general assembly, when the affair of the Yahoos was entered upon, the representatives had taken offense at his keeping a Yahoo (meaning myself) in his family more like a Houyhnhnm than a brute animal. That he was known frequently to converse with me, as if he could receive some advantage or pleasure in my company; that such a practice was not agreeable to reason or nature, nor a thing ever heard of before among them. The assembly did therefore exhort him, either to employ me like the rest of my species, or command me to swim back to the place from where I came. That the first of these expedients was utterly rejected by all the Houyhnhnms who had ever seen me at his house or their own: for they alleged that because I had some rudiments of reason, added to the natural pravity of those animals, it was to be feared I might be able to seduce them into the woody and mountainous parts of the country, and bring them in troops by night to destroy the Houyhnhnms cattle, as being naturally of the ravenous kind, and averse from labor. {P_4|CH_10 ^paragraph 5} My master added that he was daily pressed by the Houyhnhnms of the neighborhood to have the assembly's exhortation executed, which he could not put off much longer. He doubted it would be impossible for me to swim to another country, and therefore wished I would contrive some sort of vehicle resembling those I had described to him, that might carry me on the sea; in which work I should have the assistance of his own servants, as well as those of his neighbors. He concluded that for his own part he could have been content to keep me in his service as long as I lived; because he found I had cured myself of some bad habits and dispositions, by endeavoring, as far as my inferior nature was capable, to imitate the Houyhnhnms. I should here observe to the reader, that a decree of the general assembly in this country is expressed by the word hnhloayn, which signifies an exhortation, as near as I can render it; for they have no conception how a rational creature can be compelled, but only advised or exhorted, because no person can disobey reason without giving up his claim to be a rational creature. I was struck with the utmost grief and despair at my master's discourse, and being unable to support the agonies I was under, I fell into a swoon at his feet; when I came to myself he told me that he concluded I had been dead (for these people are subject to no such imbecilities of nature). I answered in a faint voice that death would have been too great a happiness; that although I could not blame the assembly's exhortation, or the urgency of his friends, yet, in my weak and corrupt judgment, I thought it might consist with reason to have been less rigorous. That I could not swim a league, and probably the nearest land to theirs might be distant above a hundred; that many materials necessary for making a small vessel to carry me off, were wholly wanting in this country, which, however, I would attempt in obedience and gratitude to his Honor, although I concluded the thing to be impossible, and therefore looked on myself as already devoted to destruction. That the certain prospect of an unnatural death was the least of my evils; for supposing I should escape with life by some strange adventure, how could I think with temper of passing my days among Yahoos, and relapsing into my old corruptions, for want of examples to lead and keep me within the paths of virtue? That I knew too well upon what solid reasons all the determinations of the wise Houyhnhnms were founded, not to be shaken by arguments of mine, a miserable Yahoo; and therefore, after presenting him with my humble thanks for the offer of his servants' assistance in making a vessel, and desiring a reasonable time for so difficult a work, I told him I would endeavor to preserve a wretched being; and if ever I returned to England, was not without hopes of being useful to my own species by celebrating the praises of the renowned Houyhnhnms, and proposing their virtues to the imitation of mankind. My master in a few words made me a very gracious reply, allowed me the space of two months to finish my boat; and ordered the sorrel nag, my fellow servant (for so this distance I may presume to call him) to follow my instructions, because I told my master that his help would be sufficient, and I knew he had a tenderness for me. In his company my first business was to go to that part of the coast where my rebellious crew had ordered me to be set on shore. I got upon a height, and looking on every side into the sea, fancied I saw a small island towards the northeast: I took out my pocket-glass, and could then clearly distinguish it about five leagues off, as I computed; but it appeared to the sorrel nag to be only a blue cloud; for as he had no conception of any country beside his own, so he could not be as expert in distinguishing remote objects at sea as we who so much converse in that element. {P_4|CH_10 ^paragraph 10} After I had discovered this island, I considered no farther; but resolved it should, if possible, be the first place of my banishment, leaving the consequence to fortune. I returned home, and consulting with the sorrel nag, we went into a copse at some distance, where I with my knife, and he with a sharp flint fastened very artificially after their manner to a wooden handle, cut down several oak wattles about the thickness of a walking-staff, and some larger pieces. But I shall not trouble the reader with a particular description of my own mechanics; let it suffice to say that in six weeks time, with the help of the sorrel nag, who performed the parts that required most labor, I finished a sort of Indian canoe, but much larger, covering it with the skins of Yahoos well stitched together, with hempen threads of my own making. My sail was likewise composed of the skins of the same animal; but I made use of the youngest I could get, the older being too tough and thick; and I likewise provided myself with four paddles. I laid in a stock of boiled flesh, of rabbits and fowls, and took with me two vessels, one fined with milk and the other with water. I tried my canoe in a large pond near my master's house, and then corrected in it what was amiss; stopping all the chinks with Yahoos' tallow, till I found it staunch, and able to bear me and my freight. And when it was as complete as I could possibly make it, I had it drawn on a carriage very gently by Yahoos to the seaside, under the conduct of the sorrel nag and another servant. - When all was ready, and the day came for my departure, I took leave of my master and lady and the whole family, my eyes flowing with tears, and my heart quite sunk with grief. But his Honor, out of curiosity, and perhaps (if I may speak it without vanity) partly out of kindness, was determined to see me in my canoe, and got several of his neighboring friends to accompany him. I was forced to wait above an hour for the tide, and then observing the wind very fortunately bearing towards the island to which I intended to steer my course, I took a second leave of my master; but as I was going to prostrate myself to kiss his hoof, he did me the honor to raise it gently to my mouth. I am not ignorant how much I have been censured for mentioning this last particular. For my detractors are pleased to think it improbable that so illustrious a person should descend to give so great a mark of distinction to a creature so inferior as I. Neither have I forgot how apt some travelers are to boast of extraordinary favors they have received. But if these censurers were better acquainted with the noble and courteous disposition of the Houyhnhnms, they would soon change their opinion. {P_4|CH_10 ^paragraph 15} I paid my respects to the rest of the Houyhnhnms in his Honor's company; then getting into my canoe, I pushed off from shore. P_4|CH_11 CHAPTER XI - I began this desperate voyage on February 15, 1714-5, at 9 o'clock in the morning. The wind was very favorable; however, I made use at first only of my paddles; but considering I should soon be weary, and that the wind might chop about, I ventured set up my little sail; and thus with the help of the tide I went at the rate of a league and a half an hour, as near as I could guess. My master and his friends continued on the shore till I was almost out of sight; and I often heard the sorrel nag (who always loved me) crying out, Hnuy illa nyha majah Yahoo, Take care of thyself, gentle Yahoo. My design was, if possible, to discover some small island uninhabited, yet sufficient by my labor to furnish me with the necessaries of life, which I would have thought a greater happiness than to be first minister in the politest court of Europe; so horrible was the idea I conceived of returning to live in the society and under the government of Yahoos. For in such a solitude as I desired I could at least enjoy my own thoughts, and reflect with delight on the virtues of those inimitable Houyhnhnms, without any opportunity of degenerating into the vices and corruptions of my own species. The reader may remember what I related when my crew conspired against me and confined me to my cabin. How I continued there several weeks without knowing what course we took; and when I was put ashore in the long-boat, how the sailors told me with oaths, whether true or false, that not in what part of the world we were. However, I did then believe us to be about ten degrees southward of the Cape of Good Hope, or about 45'degrees souther latitude, as I gathered from some general words I overheard among them, being I supposed to the southeast in their intended voyage to Madagascar. And although this were but little better than conjecture, I resolved to steer my course eastward, hoping to reach the south-west coast of New Holland, and perhaps some such island as I desired, lying westward of it. The wind was full west, and by six in the evening I computed I had gone eastward at least eighteen leagues, when I spied a very small island about half a league off, which I soon reached. It was nothing but a rock, with one creek, naturally arched by the force of tempests. Here I put in my canoe, and climbing up a part of the rock, I could plainly discover land to the east, extending from south to north. I lay all night in my canoe; and repeating my voyage early in the morning, I arrived in seven hours to the south-east point of New Holland. This confirmed me in the opinion I have long entertained, that the maps and charts place this country at least three degrees more to the east than it really is; which thought I communicated many years ago to my worthy friend Mr. Herman Moll, and gave him my reasons for it, although he has rather chosen to follow other authors. I saw no inhabitants in the place where I landed, and being unarmed, I was afraid of venturing far into the country. I found some shellfish on the shore, and ate them raw, not daring to kindle a fire, for fear of being discovered by the natives. I continued three days feeding on oysters and limpets, to save my own provisions; and I fortunately found a brook of excellent water, which gave me great relief. On the fourth day, venturing out early a little too far, I saw twenty or thirty natives upon a height, not above five hundred yards from me. They were stark naked, men, women, and children, round a fire, as I could discover by the smoke. One of them spied me, and gave notice to the rest; five of them advanced towards me, leaving the women and children at the fire. I made what haste I could to the shore, and getting into my canoe, shoved off: the savages observing me retreat, ran after me; and before I could get far enough into the sea, discharged an arrow, which wounded me deeply on the inside of my left knee (I shall carry the mark to my grave). I apprehended the arrow might be poisoned, and paddling out of the reach of their darts (being a calm day), I made a shift to suck the wound and dress it as well as I could. {P_4|CH_11 ^paragraph 5} I was at a loss what to do, for I durst not return to the same landing-place, but stood to the north, and was forced to paddle; for the wind, though very gentle, was against me, blowing northwest. As I was looking about for a secure landing-place, I saw a sail to the north-northeast, which appearing every minute more visible, I was in some doubt whether I should wait for them or no; but at last my detestation of the Yahoo race prevailed, and turning my canoe, I sailed and paddled together to the south, and got into the same creek from whence I set out in the morning, choosing rather to trust myself among these barbarians, than live with European Yahoos. I drew up my canoe as close as I could to the shore, and hid myself behind a stone by the little brook, which, as I have already said, was excellent water. The ship came within half a league of this creek, and sent her long-boat with vessels to take in fresh water (for the place it seems was very well known), but I did not observe it till the boat was almost on shore, and it was too late to seek another hiding-place. The seamen at their landing observed my canoe, and rummaging it all over, easily conjectured that the owner could not be far off. Four of them well armed searched every cranny and lurking-hole, till at last they found me flat on my face behind the stone. They gazed awhile in admiration at my strange uncouth dress, my coat made of skins, my wooden-soled shoes, and my furred stockings; from whence, however, they concluded I was not a native of the place, who all go naked. One of the seamen in Portuguese bid me rise, and asked who I was. I understood that language very well, and getting upon feet, said I was a poor Yahoo, banished from the Houyhnhnms, and desired they would please to let me depart. They admired to hear me answer them in their own tongue, and saw by my complexion I must be a European, but were at a loss to know what I meant by Yahoos and Houyhnhnms, and at the same time fell a laughing at my strange tone in speaking, which resembled the neighing of a horse. I trembled all the while between fear and hatred: I again desired leave to depart, and was gently moving to my canoe; but they laid hold of me, desiring to know what country I was of, whence I came, with many other questions. I told them I was born in England, from whence I came about five years ago, and then their country and ours were at peace. I therefore hoped they would not treat me as an enemy, since I meant them no harm, but was a poor Yahoo, seeking some desolate place where to pass the remainder of his unfortunate life. When they began to talk, I thought I never heard or saw any thing so unnatural; for it appeared to me as dog or a cow should speak in England, or a Yahoo in Houyhnhnm-land The honest Portuguese were equally amazed at my strange dress, and the odd manner of delivering my words, which however they understood very well. They spoke to me with great humanity, and said they were sure the Captain would carry me gratis to Lisbon, from whence I might return to my own country; that two of the seamen would go back to the ship, inform the Captain of what they had seen, and receive his order; in the mean time, unless I would give my solemn oath not to fly, they would secure me by force. I thought it best to comply with their proposal. They were very curious to know my story, but I gave them very little satisfaction; and they all conjectured my misfortunes had impaired my reason. In two hours the boat, which went laden with vessels of water, returned with the Captain's command to fetch me on board. I fell on my knees to preserve my liberty; but all was in vain, and the men having tied me with cords, heaved me into the boat, from whence I was taken into the ship, and from thence into the Captain's cabin. His name was Pedro de Mendez; he was a very courteous and generous person; he entreated me to give some account of myself, and desired to know what I would eat or drink; said I should be used as well as himself, and spoke so many obliging things, that I wondered to find such civilities from a Yahoo. However, I remained silent and sullen; I was ready to faint at the very smell of him and his men. At last I desired something to eat out of my own canoe; but he ordered me a chicken and some excellent wine, and then directed that I should be put to bed in a very clean cabin. I would not undress myself, but lay on the bed-clothes, and in half an hour stole out, when I thought the crew was at dinner, and getting to the side of the ship was going to leap into the sea, and swim for my life, rather than continue among Yahoos. But one of the seamen prevented me, and having informed the Captain, I was chained to my cabin. After dinner Don Pedro came to me, and desired to know my reason for so desperate an attempt, assured me he only meant to do me all the service he was able, and spoke so very movingly, that at last I descended to treat him like an animal which had some little portion of reason. I gave him a very short relation of my voyage, of the conspiracy against me by own men, of the country where they set me on shore, and of my three years residence there. All which he looked upon as if it were a dream or a vision; whereat I took great offense, for I had quite forgotten the faculty of lying, so peculiar to Yahoos in all countries where they preside, and, consequently the disposition of suspecting truth in others of their own I asked him whether it were the custom in his country to say the thing that was not. I assured him I had almost forgotten what he meant by falsehood, and if I had lived a thousand years in Houyhnhnm-land, I should never have heard a lie from the meanest servant, that I was altogether indifferent whether he believed me or not, but however, in return for his favors, I would give so much allowance to the corruption of his nature as to answer any objection he would please to make, and then he might easily discover the truth. {P_4|CH_11 ^paragraph 10} The Captain, a wise man, after many endeavors to catch me tripping in some part of my story, at last began to have a better opinion of my veracity, and the rather, because he confessed he met with a Dutch skipper, who pretended to have landed with five others of his crew upon a certain island or continent south of New Holland, where they went for fresh water, and observed a horse driving before him several animals exactly resembling those I described under the name of Yahoos, with some other particulars, which the Captain said he had forgotten; because he then concluded them all to be lies. But he added that since I professed so inviolable an attachment to truth, I must give him my word of honor to bear him company in this voyage, without attempting any thing against my life, or else he would continue me a prisoner till we arrived at Lisbon. I gave him the promise he required, but at the same time protested that I would suffer the greatest hardships rather than return to live among Yahoos. Our voyage passed without any considerable accident. In gratitude to the Captain I sometimes sat with him at his earnest request, and strove to conceal my antipathy to human kind, although it often broke out, which he suffered to pass without observation. But the greatest part of the day I confined myself to my cabin, to avoid seeing any of the crew. The Captain had often entreated me to strip myself of my savage dress, and offered to lend me the best suit of clothes he had. This I would not be prevailed on to accept, abhorring to cover myself with any thing that had been on the back of a Yahoo. I only desired he would lend me two clean shirts, which having been washed since he wore them, I believed would not so much defile me. These I changed every second day, and washed them myself. We arrived at Lisbon, Nov. 5, 1715. At our landing the Captain forced me to cover myself with his cloak, to prevent the rabble from crowding about me. I was conveyed to his own house, and at my earnest request he led me up to the highest room backwards. I conjured him to conceal from all persons what I had told him of the Houyhnhnms, because the least hint of such a story would not only draw numbers of people to see me, but probably put me in danger of being imprisoned, or burned by the Inquisition. The Captain persuaded me to accept a suit of clothes newly made; but I would not suffer the tailor to take my measure; however, Don Pedro being almost of my size, they fitted me well enough. He accoutred me with other necessaries all new, which I aired for twenty-four hours before I would use them. The Captain had no wife, nor above three servants, none of which were suffered to attend at meals, and his whole deportment was so obliging, added to very good human understanding, that I really began to tolerate his company. He gained so far upon me that I ventured to look out of the back window. By degrees I was brought into another room, from whence I peeped into the street, but drew my head back in a fright. In a week's time he seduced me down to the door. I found my terror gradually lessened, but my hatred and contempt seemed to increase. I was at last bold enough to walk the street in his company, but kept my nose well stopped with rue, or sometimes with tobacco. In ten days Don Pedro, to whom I had given some account of my domestic affairs, put it upon me as a matter of honor and conscience, that I ought to return to my native country, and live at home with my wife and children. He told me there was an English ship in the port just ready to sail, and he would furnish me with all things necessary. It would be tedious to repeat his arguments, and my contradictions. He said it was altogether impossible to find such a solitary island as I had desired to live in; but I might command in my own house, and pass my time in a manner as recluse as I pleased. {P_4|CH_11 ^paragraph 15} I complied at last, finding I could not do better. I left Lisbon the 24th day of November, in an English merchantman, but who was the master I never inquired. Don Pedro accompanied me to the ship, and lent me twenty pounds. He took kind leave of me, and embraced me at parting, which I bore as well as I could. During this last voyage I had no commerce with the master or any of his men; but pretending I was sick, kept close in my cabin. On the fifth of December, 1715, we cast anchor in the Downs about nine in the morning, and at three in the afternoon I got safe to my house at Rotherhith. My wife and family received me with great surprise and joy, because they concluded me certainly dead; but I must freely confess the sight of them filled me only with hatred, disgust, and contempt, and the more by reflecting on the near alliance I had to them. For although since my unfortunate exile from the Houyhnhnm country, I had compelled myself to tolerate the sight of Yahoos, and to converse with Don Pedro de Mendez, yet my memory and imagination were perpetually filled with the virtues and ideas of those exalted Houyhnhnms. And when I began to consider that by copulating with one of the Yahoo species I had become a parent of more, it struck me with the utmost shame, confusion, and horror. As soon as I entered the house, my wife took me in her arms and kissed me, at which, having not been used to the touch of that odious animal for so many years, I fell in a swoon for almost an hour. At the time I am writing it is five years since my last return to England: during the first year I could not endure my wife or children in my presence, the very smell of them was intolerable, much less could I suffer them to eat in the same room. To this hour they dare not presume to touch my bread, or drink out of the same cup, neither was I ever able to let one of them take me by the hand. The first money I laid out was to buy two young stone-horses, which I keep in a good stable, and next to them the groom is my greatest favorite; for I feel my spirits revived by the smell he contracts in the stable. My horses understand me tolerably well; I converse with them at least four hours every day. They are strangers to bridle or saddle; they live in great amity with me, and friendship to each other. P_4|CH_12 CHAPTER XII - Thus, gentle reader, I have given thee faithful history of my travels for sixteen years and above seven months; wherein I have not been so studious of ornament as truth. I could perhaps like others have astonished you with strange improbable tales; but I rather chose to relate plain matter of fact in the simplest manner and style; because my principal design was to inform, and not to amuse you. It is easy for us who travel into remote countries, which are seldom visited by Englishmen or other Europeans, to form descriptions of wonderful animals both at sea and land. Whereas a traveler's chief aim should be to make men wiser and better, and to improve their minds by the bad as well as good example of what they deliver concerning foreign places. I could heartily wish a law was enacted, that every traveler, before he were permitted to publish his voyages, should be obliged to make oath before the Lord High Chancellor that all he intended to print was absolutely true to the best of his knowledge; for then the world would no longer be deceived as it usually is, while some writers, to make their works pass the better upon the public, impose the grossest falsities on the unwary reader. I have perused several books of travels with great delight in my younger days; but having since gone over most parts of the globe, and been able to contradict many fabulous accounts from my own observation, it has given me a great disgust against this part of reading, and some indignation to see the credulity of mankind so impudently abused. Therefore since my acquaintances were pleased to think my poor endeavors might not be unacceptable to my country, I imposed on myself as a maxim, never to be swerved from, that I would strictly adhere to truth; neither indeed can I be ever under the least temptation to vary from it, while I retain in my mind the lectures and example of my noble master, and the other illustrious Houyhnhnms, of whom I had so long the honor to be a humble bearer. - ---Nec si miserum Fortuna Sinonem {P_4|CH_12 ^paragraph 5} Finxit, vanum etiam, mendacemque improba finget. - I know very well how little reputation is to be gotten by writings which require neither genius nor learning, nor indeed any other talent, except a good memory or an exact journal. I know likewise that writers of travels, like dictionary-makers, are sunk into oblivion by the weight and bulk of those who come after, and therefore lie uppermost. And it is highly probable that such travelers who shall hereafter visit the countries described in this work of mine, may, by detecting my errors (if there be any), and adding many new discoveries of their own, jostle me out of vogue, and stand in my place, making the world forget that I was ever an author. This indeed would be too great a mortification if I wrote for fame: but, as my sole intention was the PUBLIC GOOD, I cannot be altogether disappointed. For who can read of the virtues I have mentioned in the glorious Houyhnhnms, without being ashamed of his own vices, when he considers himself as the reasoning, governing animal of his country? I shall say nothing of those remote nations where Yahoos preside; amongst which the least corrupted are the Brobdingnagians, whose wise maxims in morality and government it would be our happiness to observe. But I forbear descanting farther, and rather leave the judicious reader to own remarks and applications. I am not a little pleased that this work of mine can possibly meet with no censurers: for what objections can be made against a writer who relates only plain facts that happened in such distant countries, where we have not the least interest with respect either to trade or negotiations? I have carefully avoided every fault with which common writers of travels are often too justly charged. Besides, I meddle not the least with any party, but write without passion, prejudice, or illwill against any man or number of men whatsoever. I write for the noblest end, to inform and instruct mankind, over whom I may, without breach of modesty, pretend to some superiority, from the advantages I received by conversing so long among the most accomplished Houyhnhnms. I write without any view towards profit or praise. I never suffer a word to pass that may look like reflection, or possibly give the least offence even to those who are most ready to take it. So that I hope I may with justice pronounce myself an author perfectly blameless, against whom the tribes of answerers, considerers, observers, reflecters, detecters, remarkers, will never be able to find matter for exercising their talents. I confess it was whispered to me that I was bound in duty as a subject of England to have given in a memorial to a Secretary of State at my first coming over; because whatever lands are discovered by a subject belong to the Crown. But I doubt whether our conquests in the countries I treat of, would be as easy as those of Ferdinando Cortez over the naked Americans. The Lilliputians I think are hardly worth the charge of a fleet and army to reduce them; and I question whether it might be prudent or safe to attempt the Brobdingnagians; or whether an English army would be much at their ease with the Flying Island over their heads. The Houyhnhnms, indeed, appear not to be so well prepared for war, a science to which they are perfect strangers, and especially against missive weapons. However, supposing myself to be a minister of state, I could never give my advice for invading them. Their prudence, unanimity, unacquaintedness with fear, and their love of their country, would amply supply all defects in the military art. Imagine twenty thousand of them breaking into the midst of a European army, confounding the confounding the ranks, overturning the carriages, battering the warriors' faces into mummy by terrible yerks from their hinder hoofs. For they would well deserve the character given to Augustus: Recalcitrat unclique tutus. But instead of proposals for conquering that magnanimous nation, I rather wish they were in a capacity or disposition to send a number of their inhabitants for civilizing Europe, by teaching us the first principles of honor, truth, temperance, public spirit, fortitude, chastity, benevolence, and fidelity. The names of all which virtues are still retained among us in languages, and are to be met with in modern as well as ancient which I am able to assert from my own small reading. {P_4|CH_12 ^paragraph 10} But I had another reason which made me less forward to enlarge his Majesty's dominions by my discoveries. To say the truth, I had conceived a few scruples with relation to the distributive justice of princes upon those occasions. For instance, a crew of pirates are driven by a storm they know not whither, at length a boy discovers land from the topmast, they go on shore to rob and plunder, they see a harmless people, are entertained with kindness, they give the country a new name, they take formal possession of it for their King, they set up a rotten plank or a stone for a memorial, they murder two or three dozen of the natives, bring away a couple more by force for a sample, return home, and get their pardon. Here commences a new dominion acquired with a title by divine right. Ships are sent with the first opportunity, the natives driven out or destroyed, their princes tortured to discover their gold, a free license given to all acts of inhumanity and lust, the earth reeking with the blood of its inhabitants: and this execrable crew of butchers employed in so pious an expedition, is a modern colony sent to convert and civilize an idolatrous and barbarous people. But this description, I confess, does by no means affect the British nation, who may be an example to the whole world for their wisdom, care, and justice in planting colonies; their liberal endowments for the advancement of religion and learning; their choice of devout and able pastors to propagate Christianity; their caution in stocking their provinces with people of sober lives and conversations from this the mother kingdom; their strict regard to the distribution of justice, in supplying the civil administration through all their colonies with officers of the greatest abilities, utter strangers to corruption; and to crown all, by sending the most vigilant and virtuous governors, who have no other views than the happiness of the people over whom they preside, and the honor of the King their master. But, as those countries which I have described do not appear to have any desire of being conquered, and enslaved, murdered or driven out by colonies, nor abound either in gold, silver, sugar, or tobacco; I did humbly conceive they were by no means proper objects of our zeal, our valor, or our interest. However, if those whom it more concerns think fit to be of another opinion, I am ready to depose, when I shall be lawfully called, that no European did ever visit these countries before me. I mean, if the inhabitants ought to he believed; unless a dispute may arise about the two Yahoos, said to have been seen many ages ago in a mountain in Houyhnhnm-land, from whence the opinion is, that the race of those brutes has descended; and these, for anything I know, may have been English, which indeed I was apt to suspect from the lineaments of their posterity's countenances, although very much defaced. But, how far that will go to make out a title, I leave to the learned in colony-law. But as to the formality of taking possession in my Sovereign's name, it never came once into my thoughts; and if it had, yet as my affairs then stood, I should perhaps in point of prudence and self-preservation have put it off to a better opportunity. Having thus answered the only objection that can ever be raised against me as a traveler, I here take a final leave of all my courteous readers, and return to enjoy my own speculations in my little garden at Redriff, to apply those excellent lessons of virtue which I learned among the Houyhnhnms, to instruct the Yahoos of my own family as far as I shall find them docile animals; to behold my figure often in a glass, and thus if possible habituate myself by time to tolerate the sight of a human creature; to lament the brutality of Houyhnhnms in my own country, but always treat their persons with respect, for the sake of my noble master, his family, his friends, and the whole Houyhnhnm race, whom these ours have the honor to resemble in all their lineaments, however their intellectuals came to degenerate. {P_4|CH_12 ^paragraph 15} I began last week to permit my wife to sit at dinner with me, at the farthest end of a long table, and to answer (but with the utmost brevity) the few questions I ask her. Yet the smell of a Yahoo continuing very offensive, I always keep my nose well stopped with rue, lavender, or tobacco leaves. And although it be hard for a man late in life to remove old habits, I am not altogether out of hopes in some time to suffer a neighbor Yahoo in my company, without the apprehensions I am yet under of his teeth or his claws. My reconcilement to the Yahoo-kind in general might not be so difficult, if they would be content with those vices and follies only which nature has entitled them to. I am not in the least provoked at the sight of a lawyer, a pick-pocket, a colonel, a fool, a lord, a gamester, a politician, a whore-master, a physician, an evidence, a suborner, an attorney, a traitor, or the like; this is all according to the due course of things: but when I behold a lump of deformity and diseases both in body and mind, smitten with pride, it immediately breaks all the measures of my patience; neither shall I be ever able to comprehend how such an animal and such a vice could tally together. The wise and virtuous Houyhnhnms, who abound in all excellencies that can adorn a rational creature, have no name for this vice in their language, which has no terms to express anything that is evil, except those whereby they describe the detestable qualities of their Yahoos, among which they were not able to distinguish this of pride, for want of thoroughly understanding human nature, as it shows itself in other countries, where that animal presides. But I, who had more experience, could plainly observe some rudiments of it among the wild Yahoos. But the Houyhnhnms, who live under the government of reason, are no more proud of the good qualities they possess, than I should be for not wanting a leg or an arm, which no man in his wits would boast of, although he must be miserable without them. I dwell the longer upon this subject from the desire I have to make the society of an English Yahoo by any means not insupportable; and therefore I here entreat those who have any tincture of this absurd vice, that they will not presume to come in my sight. - THE END Electronically Enhanced Text (C) Copyright 1991, 1992, World Library, Inc."/*.replace(/[^a-zA-Z \.]/g, " ").replace(/\b\w\b/g, "").replace(/\s+/g, " ").toLowerCase().trim()*/.split(". "); ================================================ FILE: demo/data/movies.js ================================================ export default [ "$ aka Dollars", "$1,000 a Touchdown", "$10 Raise", "$10,000 Under a Pillow", "$50,000 Reward", "(500) Days of Summer", "...All the Marbles", "...And Justice for All", "...So Goes the Nation", "...tick...tick...tick...", ".45", "…First Do No Harm", "1,000 Dollars a Minute", "10 Cloverfield Lane", "10 Items or Less", "10 MPH", "10 Things I Hate About You", "10 to Midnight", "10", "10,000 BC", "10:30 P.M. Summer", "100 Pygmies and Andy Panda", "100 Rifles", "100 Streets", "101 Dalmatians II: Patch's London Adventure", "101 Dalmatians", "102 Dalmatians", "10th & Wolf", "11 Harrowhouse", "11:14", "11-11-11", "12 Angry Men", "12 Rounds 2: Reloaded", "12 Rounds", "12 Strong", "12 to the Moon", "12 Years a Slave", "12/12/12", "12:01 PM", "127 Hours", "13 Fighting Men", "13 Ghosts", "13 Going on 30", "13 Hours by Air", "13 Hours: The Secret Soldiers of Benghazi", "13 Lead Soldiers", "13 Moons", "13 Rue Madeleine", "13 Sins", "13 Washington Square", "13 West Street", "1408", "1492: Conquest of Paradise", "15 Maiden Lane", "15 Minutes", "16 Blocks", "17 Again", "1776", "18 Again!", "1915", "1941", "1969", "1991: The Year Punk Broke", "2 Days in the Valley", "2 Fast 2 Furious", "2 Guns", "2:22", "20 Dates", "20 Feet from Stardom", "20 Million Miles to Earth", "20 Mule Team", "20,000 Leagues Under the Sea", "20,000 Men a Year", "20,000 Years in Sing Sing", "200 Cigarettes", "200 Motels", "2001: A Space Odyssey", "2010", "2012", "21 and Over", "21 Grams", "21 Jump Street", "21", "22 Jump Street", "23 1/2 Hours' Leave", "23 Paces to Baker Street", "24 Hours of a Woman's Life", "24 Hours", "25th Hour", "27 Dresses", "28 Days", "28 Hotel Rooms", "29 Reasons to Run", "29th Street", "2-Headed Shark Attack", "3 A.M.", "3 Bad Men", "3 Chains o' Gold", "3 Days to Kill", "3 Dumb Clucks", "3 Geezers!", "3 Generations", "3 Godfathers", "3 Men in White", "3 Nights in the Desert", "3 Ninjas Kick Back", "3 Ninjas Knuckle Up", "3 Ninjas", "3 Nuts in Search of a Bolt", "3 Ring Circus", "3 Strikes", "3 Women", "3,2,1... Frankie Go Boom", "3:10 to Yuma", "3:15", "30 Below Zero", "30 Days of Night", "30 Minutes or Less", "30 Nights of Paranormal Activity with the Devil Inside the Girl with the Dragon Tattoo", "-30-", "300", "300: Rise of an Empire", "3000 Miles to Graceland", "35 and Ticking", "36 Hours to Kill", "36 Hours", "365 Nights in Hollywood", "4 Devils", "4 for Texas", "4 Little Girls", "4 Minute Mile", "40 Carats", "40 Days and 40 Nights", "40 Days and Nights", "40 Guns to Apache Pass", "40 Point Plan", "40 Pounds of Trouble", "40-Horse Hawkins", "42", "42nd Street", "44 Minutes: The North Hollywood Shoot-Out", "45 Fathers", "47 Meters Down", "47 Ronin", "48 Hrs.", "4D Man", "5 Against the House", "5 Branded Women", "5 Card Stud", "5 Fingers", "5 Steps to Danger", "50 First Dates", "50 to 1", "50/50", "51 Birch Street", "52 Pick-Up", "52nd Street", "54", "55 Days at Peking", "5th Ave Girl", "6 Day Bike Rider", "6,000 Enemies", "633 Squadron", "'68", "6th Marine Division on Okinawa", "7 Days in Entebbe", "7 Faces of Dr. Lao", "7 Women", "70,000 Witnesses", "711 Ocean Drive", "7th Cavalry", "7th Heaven", "8 Ball Bunny", "8 Heads in a Duffel Bag", "8 Mile", "8 Million Ways to Die", "8 Seconds", "8 x 8", "8: The Mormon Proposition", "813", "84 Charing Cross Road", "84C MoPic", "88 Minutes", "8mm", "9 Full Moons", "9", "9/11", "9/Tenths", "9½ Weeks", "92 in the Shade", "99 and 44/100% Dead", "99 River Street", "A B C's of the U.S.A.", "A Bachelor's Wife", "A Bad Moms Christmas", "A Ballroom Tragedy", "A Bath House Beauty", "A Battle of Wits", "A Bear For Punishment", "A Beautiful Mind", "A Bedtime Story", "A Bell for Adano", "A Better Life", "A Better Place", "A Better Tomorrow 2", "A Big Hand for the Little Lady", "A Bill of Divorcement", "A Bird in the Head", "A Blind Bargain", "A Blonde for a Night", "A Blowout at Santa Banana", "A Blueprint for Murder", "A Bone for a Bone", "A Boy and His Dog", "A Boy Named Charlie Brown", "A Boy Named Sue", "A Boy of Flanders", "A Boy, a Girl and a Dog", "A Brave Irish Lass", "A Breath of Scandal", "A Bride for Henry", "A Bridge Too Far", "A Brief History of Time", "A Broadway Butterfly", "A Bronx Tale", "A Brooklyn State of Mind", "A Bucket of Blood", "A Bug's Life", "A Bullet for Joey", "A Bullet for Pretty Boy", "A Bullet Is Waiting", "A Burglar's Mistake", "A Business Buccaneer", "A Busy Corner at Armour's", "A Busy Day", "A Cafe in Cairo", "A Calamitous Elopement", "A California Romance", "A Captain's Courage", "A Certain Smile", "A Certain Young Man", "A Challenge to Democracy", "A Change of Seasons", "A Chapter in Her Life", "A Child for Sale", "A Child Is Born", "A Child Is Waiting", "A Christmas Carol", "A Christmas Story 2", "A Christmas Story", "A Chump at Oxford", "A Cinderella Story", "A Circle of Deception", "A Civil Action", "A Close Call for Boston Blackie", "A Coach Drive from Glengariffe to Kenmore", "A Cold Wind in August", "A Colonial Romance", "A Connecticut Yankee in King Arthur's Court", "A Connecticut Yankee", "A Cop", "A Covenant with Death", "A Cry in the Dark", "A Cry in the Night", "A Cure for Wellness", "A Damsel in Distress", "A Dangerous Adventure", "A Dangerous Game", "A Dangerous Profession", "A Dangerous Woman", "A Dark Truth", "A Date with Judy", "A Date with the Falcon", "A Date with Your Family", "A Daughter of Luxury", "A Daughter of the Gods", "A Daughter of the Sun", "A Daughter of the Wolf", "A Day at the Races", "A Day of Fury", "A Day Without a Mexican", "A Day's Pleasure", "A Delicate Balance", "A Desperate Adventure", "A Desperate Chance", "A Devil With Women", "A Different Story", "A Dirty Shame", "A Dispatch from Reuter's", "A Distant Thunder", "A Distant Trumpet", "A Divine Double Feature", "A Doctor's Diary", "A Dog Lost, Strayed or Stolen", "A Dog of Flanders", "A Dog's Best Friend", "A Dog's Life", "A Dog's Purpose", "A Doll's House", "A Double Life", "A Double-Dyed Deceiver", "A Drove of Wild Welsh Mountain Ponies", "A Drunkard's Reformation", "A Dry White Season", "A Ducking They Did Go", "A Face in the Crowd", "A Face in the Fog", "A Fallen Idol", "A Family Affair", "A Family Thing", "A Fantastic Woman", "A Far Off Place", "A Farewell to Arms", "A Favor to a Friend", "A Feather in Her Hat", "A Feather in His Hare", "A Ferry in the Far East", "A Fever in the Blood", "A Few Good Men", "A Fig Leaf for Eve", "A Fight for Honor", "A Fight for Love", "A Fight to the Finish", "A Fighting Colleen", "A Film Johnnie", "A Fine Madness", "A Fine Mess", "A Fish Called Wanda", "A Flash of Green", "A Florida Enchantment", "A Fool and His Money", "A Fool There Was", "A Fool's Advice", "A Fool's Awakening", "A Fool's Revenge", "A Foozle at the Tee Party", "A Force of One", "A Foreign Affair", "A Forest Romance", "A Fractured Leghorn", "A Free Soul", "A Friend in the Enemy's Camp", "A Friendly Husband", "A Front Page Story", "A Fugitive from Justice", "A Fugitive from Matrimony", "A Funny Thing Happened on the Way to the Forum", "A Gamblin' Fool", "A Game Chicken", "A Game of Death", "A Gasoline Wedding", "A Gathering of Eagles", "A Gem of a Jam", "A Gentle Gangster", "A Gentleman After Dark", "A Gentleman at Heart", "A Gentleman of Leisure", "A Gentleman of Quality", "A Ghost Story", "A Girl at Bay", "A Girl in Bohemia", "A Girl in Every Port", "A Girl Named Tamiko", "A Girl of the Limberlost", "A Girl of Yesterday", "A Girl with Ideas", "A Girl, A Gal and a Pal", "A Girl, a Guy and a Gob", "A Girl's Desire", "A Glimpse Inside the Mind of Charles Swan III", "A Global Affair", "A Good Business Deal", "A Good Day to Die Hard", "A Good Joke", "A Good Little Devil", "A Good Old Fashioned Orgy", "A Good Year", "A Goofy Movie", "A Great Wall", "A Guide for the Married Man", "A Guide to Recognizing Your Saints", "A Gun Fightin' Gentleman", "A Gunfight", "A Guy Could Change", "A Guy Named Joe", "A Guy Thing", "A Guy, a Gal and a Pal", "A Happy Coersion", "A Hatful of Rain", "A Haunted House 2", "A Haunted House", "A Heart in Pawn", "A Hero of the Big Snows", "A High Wind in Jamaica", "A History of Violence", "A Hobo's Christmas", "A Hold-Up", "A Hole in the Head", "A Hologram for the King", "A Holy Terror", "A Home at the End of the World", "A Home of Our Own", "A Homespun Vamp", "A Horseman of the Plains", "A House Divided", "A House in the Hills", "A House Is Not a Home", "A Jazzed Honeymoon", "A Joke on Grandma", "A Kentucky Cinderella", "A Kentucky Feud", "A Kid in King Arthur's Court", "A Kiss Before Dying", "A Kiss for Cinderella", "A Kiss for Corliss", "A Kiss in a Taxi", "A Kiss in the Dark", "A Knight's Tale", "A La Cabaret", "A Lad from Old Ireland", "A Lady of Chance", "A Lady of Quality", "A Lady Surrenders", "A Lady Takes a Chance", "A Lady to Love", "A Lady Without Passport", "A Lady's Morals", "A Lady's Profession", "A Late Quartet", "A Lawless Street", "A Lawman Is Born", "A League of Their Own", "A Leap for Love", "A Letter for Evie", "A Letter to Three Wives", "A Life at Stake", "A Life in the Balance", "A Life Less Ordinary", "A Life of Her Own", "A Likely Story", "A Lion Is in the Streets", "A Little Bit of Heaven", "A Little Brother of the Rich", "A Little Girl in a Big City", "A Little Help", "A Little Hero", "A Little Journey", "A Little Madonna", "A Little Night Music", "A Little Princess", "A Little Romance", "A Lost Lady", "A Lot Like Love", "A Love Song for Bobby Long", "A Love Sublime", "A Lovely Way to Die", "A Lover's Oath", "A Low Down Dirty Shame", "A Madea Christmas", "A Majority of One", "A Man Alone", "A Man and His Money", "A Man Apart", "A Man Betrayed", "A Man Called Dagger", "A Man Called Horse", "A Man Called Peter", "A Man Called Sarge", "A Man Could Get Killed", "A Man for All Seasons", "A Man Four-Square", "A Man from Wyoming", "A Man Must Live", "A Man of Action", "A Man of Honor", "A Man of Iron", "A Man of Nerve", "A Man of Quality", "A Man of Sentiment", "A Man of Stone", "A Man to Remember", "A Man's Country", "A Man's Fight", "A Man's Game", "A Man's Land", "A Man's Mate", "A Man's Way", "A Map of the World", "A Matter of Morals", "A Matter of Time", "A Medal for Benny", "A Merry Friggin' Christmas", "A Merry Mix Up", "A Message to Garcia", "A Midnight Clear", "A Midnight Romance", "A Midsummer Night's Dream", "A Midsummer Night's Sex Comedy", "A Midwinter's Tale", "A Mighty Heart", "A Mighty Wind", "A Million Bid", "A Million to Burn", "A Million to Juan", "A Million to One", "A Million Ways to Die in the West", "A Millionaire for Christy", "A Mixup for Mazie", "A Modern Marriage", "A Modern Musketeer", "A Modern Twain Story: The Prince and the Pauper", "A Mom for Christmas", "A Monster Calls", "A Month of Sundays", "A Mormon Maid", "A Most Immoral Lady", "A Mother's Instinct", "A Mouse in the House", "A Movie", "A Muddy Bride", "A New Cure for Divorce", "A New Kind of Love", "A New Leaf", "A New Life", "A Nice Little Bank That Should Be Robbed", "A Nigger in the Woodpile", "A Night at Earl Carroll's", "A Night at the Opera", "A Night at the Ritz", "A Night at the Roxbury", "A Night for Crime", "A Night Full of Rain", "A Night in Casablanca", "A Night in Heaven", "A Night in the Life of Jimmy Reardon", "A Night of Adventure", "A Night of Mystery", "A Night to Remember", "A Nightingale Sang in Berkeley Square", "A Nightmare on Elm Street 2: Freddy's Revenge", "A Nightmare on Elm Street 3: Dream Warriors", "A Nightmare on Elm Street 4: The Dream Master", "A Nightmare on Elm Street 5: The Dream Child", "A Nightmare on Elm Street", "A Noise in Newboro", "A Notorious Affair", "A Notorious Gentleman", "A Parisian Romance", "A Passage to India", "A Passport to Hell", "A Patch of Blue", "A Perfect Couple", "A Perfect Gentleman", "A Perfect Getaway", "A Perfect Murder", "A Perfect World", "A Perilous Journey", "A Personal Journey with Martin Scorsese Through American Movies", "A Pest in the House", "A Petal on the Current", "A Piece of the Action", "A Place for Lovers", "A Place in the Sun", "A Place to Live", "A Policeman's Love Affair", "A Poor Girl's Romance", "A Prairie Home Companion", "A Prayer Before Dawn", "A Prayer for the Dying", "A Price Above Rubies", "A Private Matter", "A Private War", "A Private's Affair", "A Prize of Gold", "A Question of Honor", "A Quiet Place", "A Race for a Kiss", "A Race for Life", "A Rage in Harlem", "A Rage to Live", "A Railway Tragedy", "A Raisin in the Sun", "A Reckless Romeo", "A Reflection of Fear", "A Regular Fellow", "A Regular Girl", "A Regular Scout", "A Resurrection", "A Return to Salem's Lot", "A River Made to Drown In", "A River Runs Through It", "A Roaring Adventure", "A Rodeo Mixup", "A Rogue's Romance", "A Romance of Happy Valley", "A Romance of the Redwoods", "A Royal Scandal", "A Rural Elopement", "A Safe Place", "A Sagebrush Hamlet", "A Sailor-Made Man", "A Sailor's Sweetheart", "A Sainted Devil", "A Sanitarium Scramble", "A Scandal in Paris", "A Scanner Darkly", "A Scream in the Dark", "A Scream in the Night", "A Search for Evidence", "A Self-Made Failure", "A Self-Made Man", "A Serious Man", "A Shock to the System", "A Shocking Accident", "A Shot in the Dark", "A Show of Force", "A Shriek in the Night", "A Simple Favor", "A Simple Plan", "A Simple Twist of Fate", "A Simple Wish", "A Single Man", "A Single Shot", "A Sister of Six", "A Sister's Love", "A Sister's Love: A Tale of the Franco-Prussian War", "A Six Cylinder Elopement", "A Six Shootin' Romance", "A Slave of Fashion", "A Slice of Life", "A Slight Case of Larceny", "A Slight Case of Murder", "A Slipping-Down Life", "A Small Circle of Friends", "A Small Town Girl", "A Small Town in Texas", "A Smile Like Yours", "A Snitch in Time", "A Social Celebrity", "A Society Exile", "A Society Scandal", "A Society Sherlock", "A Soldier's Plaything", "A Soldier's Story", "A Son Comes Home", "A Son of His Father", "A Son of the Sahara", "A Song for Miss Julie", "A Song Is Born", "A Song of Kentucky", "A Song to Remember", "A Soul Astray", "A Soul at Stake", "A Sound of Thunder", "A Southern Yankee", "A Splendid Hazard", "A Sporting Chance", "A Stage Romance", "A Star Is Born", "A State of Vine", "A Stitch in Time", "A Stolen Life", "A Story of Healing", "A Story of Little Italy", "A Strange Adventure", "A Strange Transgressor", "A Stranger Among Us", "A Stranger in My Arms", "A Stranger in Town", "A Streetcar Named Desire", "A Study in Scarlet", "A Submarine Pirate", "A Substantial Ghost", "A Successful Calamity", "A Successful Failure", "A Summer Place", "A Swingin' Summer", "A Symposium on Popular Songs", "A Tailor Made Man", "A Tailor-Made Man", "A Tale of the West", "A Tale of Two Cities", "A Tale of Two Kitties", "A Tale of Two Mice", "A Talent for Loving", "A Taste of Life", "A Teacher", "A Temperamental Wife", "A Tenderfoot Goes West", "A Terrible Beauty", "A Thief in Paradise", "A Thin Line Between Love and Hate", "A Thousand Acres", "A Thousand and One Nights", "A Thousand Clowns", "A Thousand Words", "A Thunder of Drums", "A Ticket to Red Horse Gulch", "A Ticket to Tomahawk", "A Ticklish Affair", "A Tiger Walks", "A Tiger's Tale", "A Time for Dying", "A Time for Killing", "A Time of Destiny", "A Time to Heal", "A Time to Kill", "A Time to Love and a Time to Die", "A Town Called Bastard", "A Tragedy at Midnight", "A Tree Grows in Brooklyn", "A Trick of Fate", "A Trip Around the Pan-American Exposition", "A Trip Down Market Street", "A Trip Through the Gap of Dunloe", "A Trip to Chinatown", "A Trip to Paris", "A Trip to the Giant's Causeway", "A Troll in Central Park", "A Turn of the Cards", "A Very Brady Christmas", "A Very Brady Sequel", "A Very Good Young Man", "A Very Harold & Kumar 3D Christmas", "A Very Honorable Guy", "A Very Long Engagement", "A Very Natural Thing", "A Very Special Favor", "A Very Young Lady", "A View from the Bridge", "A View to a Kill", "A Virgin's Sacrifice", "A Virtuous Vamp", "A Visit to the Zoo", "A Walk Among the Tombstones", "A Walk in the Clouds", "A Walk in the Spring Rain", "A Walk in the Sun", "A Walk on the Moon", "A Walk to Remember", "A Wave, a WAC and a Marine", "A Wedding", "A Week Off", "A White Man's Chance", "A Wicked Woman", "A Wide Open Town", "A Wife's Romance", "A Wild Hare", "A Woman Against the World", "A Woman Commands", "A Woman of Affairs", "A Woman of Distinction", "A Woman of Experience", "A Woman of Paris", "A Woman of Pleasure", "A Woman of the Sea", "A Woman of the World", "A Woman Rebels", "A Woman Scorned", "A Woman There Was", "A Woman Under the Influence", "A Woman Who Sinned", "A Woman's Devotion", "A Woman's Face", "A Woman's Faith", "A Woman's Man", "A Woman's Secret", "A Woman's Vengeance", "A Woman's Way", "A Wonderful Wife", "A Word to the Wives….", "A Wreath in Time", "A Wrinkle in Time", "A Yank at Eton", "A Yank at Oxford", "A Yank in Indo-China", "A Yank in Korea", "A Yank in Libya", "A Yank in the RAF", "A Yank on the Burma Road", "A Yankee Princess", "A.C.O.D.", "A.D.", "A.I. Artificial Intelligence", "A.X.L.", "a/k/a Tommy Chong", "Aaron Loves Angela", "Aaron Slick from Punkin Crick", "Abandon", "Abandoned Mine", "Abandoned", "Abbott and Costello Go to Mars", "Abbott and Costello in Hollywood", "Abbott and Costello in the Foreign Legion", "Abbott and Costello Meet Captain Kidd", "Abbott and Costello Meet Dr. Jekyll and Mr. Hyde", "Abbott and Costello Meet Frankenstein", "Abbott and Costello Meet the Invisible Man", "Abbott and Costello Meet the Keystone Kops", "Abbott and Costello Meet the Killer, Boris Karloff", "Abbott and Costello Meet the Mummy", "Abby Singer", "Abby", "Abduction", "Abe Lincoln in Illinois", "Aberration", "Abie's Irish Rose", "Abilene Town", "Abilene Trail", "Abominable", "About a Boy", "About Cherry", "About Face", "About Last Night", "About Last Night...", "About Mrs. Leslie", "About Schmidt", "Above and Beyond", "Above Suspicion", "Above the Clouds", "Above the Law", "Above the Rim", "Abraham Lincoln vs. Zombies", "Abraham Lincoln", "Abraham Lincoln: Vampire Hunter", "Abraham Lincoln's Clemency", "Abraham", "Abroad with Two Yanks", "Absence of Malice", "Absolute Power", "Absolute Quiet", "Abysmal Brute", "Accent on Crime", "Accent on Youth", "Accepted", "Accidental Love", "Accidents Will Happen", "Accomplice", "According to Hoyle", "According to Mrs. Hoyle", "Accused of Murder", "Accused", "Ace Eli and Rodger of the Skies", "Ace in the Hole", "Ace of Aces", "Ace of Action", "Ace of Cactus Range", "Ace of Spades", "Ace of the Saddle", "Ace Ventura: Pet Detective", "Ace Ventura: When Nature Calls", "Aces and Eights", "Aces Wild", "Acquitted", "Acrobats in Cairo", "Across 110th Street", "Across the Atlantic", "Across the Badlands", "Across the Continent", "Across the Deadline", "Across the Divide", "Across the Great Divide", "Across the Line", "Across the Pacific", "Across the Plains", "Across the Rio Grande", "Across the Tracks", "Across the Universe", "Across the Wide Missouri", "Across to Singapore", "Act of Love", "Act of Valor", "Act of Violence", "Act One", "Acting on Impulse", "Action in Arabia", "Action in the North Atlantic", "Action Jackson", "Action of the Tiger", "Action Point", "Action", "Actors and Sin", "Acts of Violence", "Ada", "Adam and Eva", "Adam and Evil", "Adam at Six A.M.", "Adam Had Four Sons", "Adam", "Adam's Rib", "Adaptation.", "Addams Family Reunion", "Addams Family Values", "Addicted to Love", "Addicted", "Address Unknown", "Admission", "Adopting a Bear Cub", "Adorable", "Adoration", "Adrenalin: Fear the Rush", "Adrift in Manhattan", "Adrift", "Adult Beginners", "Adult World", "Advance to the Rear", "Adventure in Baltimore", "Adventure in Diamonds", "Adventure in Iraq", "Adventure in Manhattan", "Adventure in Music", "Adventure in Sahara", "Adventure in Washington", "Adventure Island", "Adventure", "Adventureland", "Adventure's End", "Adventures in Babysitting", "Adventures in Silverado", "Adventures in Wild California", "Adventures of a Drummer Boy", "Adventures of Captain Fabian", "Adventures of Captain Marvel", "Adventures of Don Juan", "Adventures of Frank and Jesse James", "Adventures of Gallant Bess", "Adventures of Johnny Tao", "Adventures of Kitty O'Day", "Adventures of Power", "Adventures of Red Ryder", "Adventures of Rusty", "Adventures of Sherlock Holmes; or, Held for Ransom", "Adventures of Sir Galahad", "Adventures of the Flying Cadets", "Adventures of the Masked Phantom", "Advice to the Lovelorn", "Advise & Consent", "Æon Flux", "Aerial Gunner", "Affair in Havana", "Affair in Reno", "Affair in Trinidad", "Affair with a Stranger", "Affairs of a Gentleman", "Affairs of Cappy Ricks", "Affairs of Geraldine", "Affectionately Yours", "Afghan Knights", "Aflame in the Sky", "Afraid of His Wife", "Afraid to Fight", "Afraid to Talk", "Africa Screams", "Africa Speaks!", "Africa: The Serengeti", "African Cats", "African Manhunt", "African Treasure", "Afro-Punk", "After Business Hours", "After Dark in Central Park", "After Dark, My Sweet", "After Dark; or, the Policeman and His Lantern", "After Death", "After Earth", "After Five", "After His Own Heart", "After Hours", "After Midnight with Boston Blackie", "After Midnight", "After Office Hours", "After Sex", "After the Ball", "After the Dance", "After the Dark", "After the Dough", "After the Show", "After the Storm", "After the Sunset", "After the Thin Man", "After Tomorrow", "After Tonight", "After Your Own Heart", "After", "After...", "After.Life", "Afterglow", "Aftermath", "Aftershock", "Against All Flags", "Against All Odds", "Against the Law", "Against the Ropes", "Agatha", "Age 13", "Age of Indiscretion", "Agent 47", "Agent Cody Banks 2: Destination London", "Agent Cody Banks", "Agent for H.A.R.M.", "Aggie Appleby, Maker of Men", "Agnes of God", "Ah, Wilderness!", "A-Haunting We Will Go", "Aileen Wuornos: The Selling of a Serial Killer", "Aileen: Life and Death of a Serial Killer", "Ain't Misbehavin'", "Ain't Them Bodies Saints", "Air America", "Air Cadet", "Air Collision", "Air Devils", "Air Force One", "Air Force", "Air Guitar Nation", "Air Hawks", "Air Hostess", "Air Mail", "Air Raid Wardens", "Air Strike", "Airborne", "Airheads", "Airplane II: The Sequel", "Airplane!", "Airport 1975", "Airport '77", "Airport", "Airy Fairy Lillian Tries on Her New Corsets", "Akeelah and the Bee", "Al Capone", "Al Franken: God Spoke", "Al Jennings of Oklahoma", "Aladdin and His Lamp", "Aladdin and the King of Thieves", "Aladdin", "Aladdin: The Return of Jafar", "A-Lad-In His Lamp", "Alamo Bay", "Alaska Highway", "Alaska Passage", "Alaska Seas", "Alaska", "Alaska: Spirit of the Wild", "Albino Alligator", "Albuquerque", "Alcatraz Island", "Alex & Emma", "Alex Cross", "Alex in Wonderland", "Alex of Venice", "Alex the Great", "Alexander and the Terrible, Horrible, No Good, Very Bad Day", "Alexander Hamilton", "Alexander the Great", "Alexander", "Alexander's Ragtime Band", "Alfalfa Love", "Alfie", "Algiers", "Ali Baba and the Forty Thieves", "Ali Baba Bunny", "Ali Baba Goes to Town", "Ali", "Alias a Gentleman", "Alias Billy the Kid", "Alias Boston Blackie", "Alias French Gertie", "Alias Jesse James", "Alias Jimmy Valentine", "Alias Julius Caesar", "Alias Mary Dow", "Alias Mary Flynn", "Alias Mike Moran", "Alias Mr. Twilight", "Alias Nick Beal", "Alias the Champ", "Alias the Deacon", "Alias the Doctor", "Alias the Night Wind", "Alibi for Murder", "Alibi Ike", "Alibi", "Alice Adams", "Alice Doesn't Live Here Anymore", "Alice in Wonderland", "Alice Through the Looking Glass", "Alice Upside Down", "Alice", "Alice, Sweet Alice", "Alice's Restaurant", "Alien 3", "Alien Abduction", "Alien Dead", "Alien from L.A.", "Alien Hunter", "Alien Nation", "Alien Nation: Dark Horizon", "Alien Nation: Millennium", "Alien Nation: The Enemy Within", "Alien Nation: The Udara Legacy", "Alien Origin", "Alien Resurrection", "Alien Seed", "Alien Trespass", "Alien vs. Predator", "Alien", "Alien: Covenant", "Alienator", "Aliens in the Attic", "Aliens of the Deep", "Aliens vs. Predator: Requiem", "Aliens", "Alimony", "Alive", "All Aboard", "All About Eve", "All About Steve", "All About the Benjamins", "All American Chump", "All Around Frying Pan", "All Ashore", "All at Sea", "All by Myself", "All Dogs Go to Heaven 2", "All Dogs Go to Heaven", "All Eyez on Me", "All Fall Down", "All for a Girl", "All Good Things", "All Hands on Deck", "All I Desire", "All I See Is You", "All I Want for Christmas", "All in a Night's Work", "All Is Lost", "All Men Are Enemies", "All Mine to Give", "All My Babies", "All My Friends Are Funeral Singers", "All My Sons", "All Night Long", "All Nighter", "All of a Sudden Norma", "All of Me", "All Over Me", "All Over the Guy", "All Over Town", "All Quiet on the Western Front", "All Saints", "All That Heaven Allows", "All That Jazz", "All the Brothers Were Valiant", "All the Days Before Tomorrow", "All the Fine Young Cannibals", "All the Kind Strangers", "All the King's Horses", "All the King's Men", "All the Money in the World", "All the President's Men", "All the Pretty Horses", "All the Real Girls", "All the Right Moves", "All the Way Home", "All the World's a Stooge", "All the Young Men", "All This and Rabbit Stew", "All This, and Heaven Too", "All Through the Night", "All Woman", "All Women Have Secrets", "All Wrong", "All-American Co-Ed", "All-American Sweetheart", "Allan Quatermain and the Lost City of Gold", "Allegheny Uprising", "Allegiance", "Allergic to Love", "Alley Cat", "Alligator Farm", "Alligator", "Allotment Wives", "Almost a Gentleman", "Almost a Husband", "Almost a Lady", "Almost an Angel", "Almost Famous", "Almost Heroes", "Almost Married", "Almost Summer", "Aloha", "Aloha, Bobby and Rose", "Aloma of the South Seas", "Alone in the Dark", "Alone with Her", "Alone yet Not Alone", "Along Came a Spider", "Along Came Jones", "Along Came Love", "Along Came Polly", "Along Came Ruth", "Along Came Youth", "Along the Great Divide", "Along the Navajo Trail", "Along the Oregon Trail", "Alpha and Omega", "Alpha", "Alter Egos", "Altered States", "Alucarda", "Alvarez Kelly", "Alvin and the Chipmunks", "Alvin and the Chipmunks: Chipwrecked", "Alvin and the Chipmunks: The Squeakquel", "Always a Bride", "Always a Bridesmaid", "Always Goodbye", "Always in My Heart", "Always in Trouble", "Always Leave Them Laughing", "Always the Woman", "Always Together", "Always", "Am I Guilty?", "Amadeus", "Amanda and the Alien", "Amandla!: A Revolution in Four-Part Harmony", "Amarilly of Clothes-Line Alley", "Amateur Crook", "Amateur Daddy", "Amateur Night; or, Get the Hook", "Amateur", "Amazing Grace and Chuck", "Amazing Grace", "Amazon Women on the Moon", "Amazons", "Ambassador Bill", "Ambush at Cimarron Pass", "Ambush at Tomahawk Gap", "Ambush Bay", "Ambush Trail", "Ambush Valley", "Ambush", "Ambushed", "Amelia", "America", "America, America", "America: Freedom to Fascism", "America: Imagine the World Without Her", "American Animals", "American Anthem", "American Aristocracy", "American Assassin", "American Beauty", "American Blackout", "American Born", "American Boy: A Profile of Steven Prince", "American Dream", "American Dreamer", "American Dreamz", "American Empire", "American Fable", "American Flyers", "American Gangster", "American Gigolo", "American Gothic", "American Graffiti", "American Guerrilla in the Philippines", "American Hardcore", "American History X", "American Hot Wax", "American Hustle", "American Made", "American Madness", "American Manners", "American Me", "American Movie", "American Ninja 2: The Confrontation", "American Ninja 3: Blood Hunt", "American Ninja 4: The Annihilation", "American Ninja", "American Outlaws", "American Pastime", "American Pastoral", "American Perfekt", "American Pie 2", "American Pie", "American Pluck", "American Pop", "American Psycho", "American Reunion", "American Splendor", "American Teen", "American Violet", "American Wedding", "Americana", "Americaner Shadchen", "Americanizing Shelley", "America's Answer", "America's Heart and Soul", "America's Sweethearts", "Americathon", "Amistad", "Amityville 3-D", "Amityville 4: The Evil Escapes", "Amityville Dollhouse", "Amityville II: The Possession", "Amityville: The Awakening", "Among the Cannibal Isles of the South Pacific", "Among the Living", "Among the Missing", "Among Those Present", "Amos & Andrew", "Amy", "Amy's Orgasm", "An Ache in Every Stake", "An Act of Murder", "An Adventure in Hearts", "An Affair of Honor", "An Affair to Remember", "An Alan Smithee Film: Burn Hollywood Burn", "An Almost Perfect Affair", "An American Carol", "An American Crime", "An American in Paris", "An American Rhapsody", "An American Romance", "An American Tail: Fievel Goes West", "An American Tragedy", "An American Werewolf in London", "An American Werewolf in Paris", "An Angel Comes to Brooklyn", "An Angel from Texas", "An Annapolis Story", "An Arcadian Maid", "An Eastern Westerner", "An Enemy of Men", "An Enemy of the People", "An Everlasting Piece", "An Eye for an Eye", "An Ideal Husband", "An Inconvenient Sequel: Truth to Power", "An Inconvenient Truth", "An Innocent Adventuress", "An Innocent Affair", "An Innocent Magdalene", "An Innocent Man", "An Intelligent Elephant", "An Invisible Sign", "An Itch in Time", "An Officer and a Gentleman", "An Old Sweetheart of Mine", "An Ozark Romance", "An Unfinished Life", "An Unmarried Woman", "An Unseen Enemy", "An Unwilling Hero", "An Up-to-Date Studio", "Anaconda", "Anacondas: The Hunt for the Blood Orchid", "Analog Days", "Analyze That", "Analyze This", "Anamorph", "Anastasia", "Anatomy of a Murder", "Anchorman 2: The Legend Continues", "Anchorman: The Legend of Ron Burgundy", "Anchors Aweigh", "And a Little Child Shall Lead Them", "And Baby Makes Three", "And God Created Woman", "And Now Tomorrow", "And One Was Beautiful", "And So It Goes", "And So They Were Married", "And Sudden Death", "And the Angels Sing", "And the Sea Will Tell", "And Then There Were None", "Andersonville", "Andre", "Andre's Mother", "Androcles and the Lion", "Android Cop", "Android", "Andy Hardy Comes Home", "Andy Hardy Gets Spring Fever", "Andy Hardy Meets Debutante", "Andy Hardy's Blonde Trouble", "Andy Hardy's Double Life", "Andy Hardy's Private Secretary", "Andy Warhol: A Documentary Film", "Andy Warhol's Bad", "Anesthesia", "Angel and the Badman", "Angel Baby", "Angel Eyes", "Angel Face", "Angel Heart", "Angel in Exile", "Angel in My Pocket", "Angel of Crooked Street", "Angel on My Shoulder", "Angel on the Amazon", "Angel Town", "Angel Unchained", "Angel", "Angel, Angel, Down We Go", "Angela", "Angelo My Love", "Angels & Demons", "Angels' Alley", "Angels and Insects", "Angels' Brigade", "Angel's Dance", "Angel's Holiday", "Angels in Disguise", "Angels in Stardust", "Angels in the Endzone", "Angels in the Outfield", "Angels Over Broadway", "Angels Sing", "Angels with Broken Wings", "Angels with Dirty Faces", "Anger Management", "Angie", "Anguish", "Angus", "Animal Crackers", "Animal Factory", "Animal Farm", "Animalympics", "Ann Carver's Profession", "Ann Vickers", "Anna and the Apocalypse", "Anna and the King of Siam", "Anna and the King", "Anna Ascends", "Anna Christie", "Anna Karenina", "Anna Lucasta", "Anna to the Infinite Power", "Anna", "Annabel Takes a Tour", "Annabelle", "Annabelle: Creation", "Annabelle's Affairs", "Annapolis Farewell", "Annapolis Salute", "Annapolis", "Anne Against the World", "Anne B. Real", "Anne of Avonlea", "Anne of Green Gables", "Anne of the Indies", "Anne of Windy Poplars", "Annie Get Your Gun", "Annie Hall", "Annie Laurie", "Annie Oakley", "Annie", "Annihilation", "Another 48 Hrs.", "Another Dawn", "Another Day in Paradise", "Another Earth", "Another Face", "Another Gay Movie", "Another Job for the Undertaker", "Another Language", "Another Man's Poison", "Another Man's Shoes", "Another Man's Wife", "Another Midnight Run", "Another Nice Mess", "Another Part of the Forest", "Another Scandal", "Another Stakeout", "Another Thin Man", "Another Time, Another Place", "Another Woman", "Another You", "Anthem to Beauty", "Anthony Adverse", "Antitrust", "Ant-Man and the Wasp", "Ant-Man", "Antony and Cleopatra", "Antropophagus[citation needed]", "Antwone Fisher", "Antz", "Any Bonds Today?", "Any Day Now", "Any Given Sunday", "Any Number Can Play", "Any Wednesday", "Any Which Way You Can", "Any Wife", "Any Woman", "Anybody's War", "Anybody's Woman", "Anything Can Happen", "Anything Else", "Anything Goes", "Anywhere but Here", "Anzio", "Apache Ambush", "Apache Country", "Apache Drums", "Apache Rose", "Apache Territory", "Apache Trail", "Apache Uprising", "Apache War Smoke", "Apache Warrior", "Apache Woman", "Apache", "Apartment 1303 3D", "Apartment for Peggy", "Apartment Wanted", "Apocalypse Earth", "Apocalypse Now", "Apocalypse Pompeii", "Apocalypto", "Apollo 13", "Apollo 18", "Apology for Murder", "Apostle Peter and the Last Supper", "Appaloosa", "Applause", "Appointment for Love", "Appointment in Berlin", "Appointment in Honduras", "Appointment in Tokyo", "Appointment with a Shadow", "Appointment with Danger", "Appointment With Death", "Appointment with Fear", "Appointment with Murder", "April Folly", "April Fool", "April Fool's Day", "April in Paris", "April Love", "April Showers", "Apt Pupil", "Aqua Teen Hunger Force Colon Movie Film for Theaters", "Aquaman", "Aquamania", "Aquamarine", "Arabesque", "Arabian Love", "Arabian Nights", "Arachnophobia", "Arbitrage", "Arcadia Lost", "Arcadia", "Arch of Triumph", "Arctic Flight", "Arctic Manhunt", "Arctic Tale", "Are All Men Alike?", "Are Crooks Dishonest?", "Are Husbands Necessary?", "Are Parents People?", "Are These Our Children?", "Are These Our Parents?", "Are We Civilized?", "Are We Done Yet?", "Are We There Yet?", "Are You Listening?", "Are You There?", "Are You with It?", "Arena", "Argentine Love", "Argentine Nights", "Argo", "Arise, My Love", "Arizona Bound", "Arizona Days", "Arizona Dream", "Arizona Frontier", "Arizona Gang Busters", "Arizona Gunfighter", "Arizona Legion", "Arizona Mahoney", "Arizona Manhunt", "Arizona Sweepstakes", "Arizona Territory", "Arizona Terror", "Arizona to Broadway", "Arizona Trail", "Arizona Whirlwind", "Arizona", "Arkansas Judge", "Arlington Road", "Armageddon", "Armed and Dangerous", "Armed Response", "Armed", "Armored Car Robbery", "Armored Car", "Armored Command", "Armored", "Army Bound", "Army Girl", "Army Surgeon", "Army Wives", "Arnold", "Arnold's Wrecking Co.", "Around the Bend", "Around the Corner", "Around the World in 80 Days", "Around the World in Eighteen Days", "Around the World in Eighty Days", "Around the World Under the Sea", "Around the World", "Arrest Bulldog Drummond", "Arrival of Tongkin Train", "Arrivederci Roma", "Arrow in the Dust", "Arrowhead", "Arrowsmith", "Arsenal", "Arsène Lupin Returns", "Arsène Lupin", "Arsenic and Old Lace", "Arson for Hire", "Arson Gang Busters", "Arson Squad", "Arson, Inc.", "Art Machine", "Art School Confidential", "Arthur 2: On the Rocks", "Arthur Christmas", "Arthur Newman", "Arthur Takes Over", "Arthur", "Arthur's Desperate Resolve", "Article 47, L'", "Article 99", "Artie Lange's Beer League", "Artie Shaw: Time Is All You've Got", "Artifact", "Artists and Models Abroad", "Artists and Models", "As a Man Thinks", "As Above, So Below", "As Good as It Gets", "As Good as Married", "As Husbands Go", "As Is", "As It Is In Life", "As Man Desires", "As Men Love", "As the Earth Turns", "As the Sun Went Down", "As You Desire Me", "As You Like It", "As You Were", "As Young as We Are", "As Young as You Feel", "Ash Wednesday", "Ashanti", "Ashes of Vengeance", "Asian School Girls", "Asian Stories", "Ask Any Girl", "Ask Father", "Ask the Dust", "Aspen Extreme", "Ass Backwards", "Assassination Nation", "Assassination of a High School President", "Assassination", "Assassins", "Assault on a Queen", "Assault on Precinct 13", "Assault on Wall Street", "Assigned to Danger", "Assignment – Paris!", "Assignment in Brittany", "Assignment to Kill", "Asteroid", "Astro Boy", "At Any Price", "At Close Range", "At First Sight", "At Gunpoint", "At Long Last Love", "At Play in the Fields of the Lord", "At Sword's Point", "At the Altar", "At the Circus", "At the Earth's Core", "At the Foot of the Ladder", "At the Old Stage Door", "At the Potter's Wheel", "At War with the Army", "At Work in a Peat Bog", "Athena", "Athens, Georgia: Inside Out", "ATL", "Atlantic Adventure", "Atlantic City", "Atlantic Convoy", "Atlantic Flight", "Atlantis, the Lost Continent", "Atlantis: The Lost Empire", "Atlas Shrugged: Part I", "Atlas Shrugged: Part II", "Atlas Shrugged: Part III", "ATM", "Atom Age Vampire", "Atom Man vs. Superman", "Atom the Amazing Zombie Killer", "Atomic Blonde", "Atomic Train", "Atomica", "Atta Boy", "Attack of the 50 Foot Woman", "Attack of the 60 Foot Centerfold", "Attack of the Crab Monsters", "Attack of the Giant Leeches", "Attack of the Killer Tomatoes", "Attack of the Puppet People", "Attack! Battle of New Britain", "Attack", "Attorney for the Defense", "Audrey Rose", "August Rush", "August Weekend", "August", "August: Osage County", "Auntie Mame", "Auntie's Portrait", "Aurora Floyd", "Aurora: Operation Intercept", "Austin Powers in Goldmember", "Austin Powers: International Man of Mystery", "Austin Powers: The Spy Who Shagged Me", "Author! Author!", "Authors Anonymous", "Autism: The Musical", "Auto Focus", "Autobiography of a 'Jeep'", "Automaton Transfusion", "Automatons", "Automobile Explosion", "Autumn in New York", "Autumn Leaves", "Avalanche Express", "Avalanche", "Avalon", "Avanti!", "Avatar", "Avengers: Age of Ultron", "Avengers: Infinity War", "Avenging Angel", "Avenging Angelo", "Avenging Force", "Avenging Waters", "Aviation Vacation", "Aviator, The", "Awake", "Awakenings", "Away All Boats", "Away We Go", "Awesome; I Fuckin' Shot That!", "B*A*P*S", "B. Monkey", "B.F.'s Daughter", "BAADASSSSS!", "Babbitt", "Babe Comes Home", "Babe", "Babe: Pig in the City", "Babel", "Babes in Arms", "Babes in Bagdad", "Babes in Toyland", "Babes on Broadway", "Babes on Swing Street", "Babies for Sale", "Baby Beethoven", "Baby Blue Marine", "Baby Boom", "Baby Bottleneck", "Baby Boy", "Baby Buggy Bunny", "Baby Butch", "Baby Doll", "Baby Driver", "Baby Face Harrington", "Baby Face Nelson", "Baby Face", "Baby Geniuses", "Baby Hands", "Baby It's You", "Baby M", "Baby Mama", "Baby Mine", "Baby Newton", "Baby Puss (Tom and Jerry)", "Baby Sitters Jitters", "Baby Snakes", "Baby Take a Bow", "Baby the Rain Must Fall", "Baby: Secret of the Lost Legend", "Babylon A.D.", "Baby's Day Out", "Bachelor Apartment", "Bachelor Bait", "Bachelor Brides", "Bachelor Flat", "Bachelor in Paradise", "Bachelor Mother", "Bachelor of Arts", "Bachelor Party", "Bachelorette", "Bachelor's Affairs", "Back at the Front", "Back by Midnight", "Back Door to Heaven", "Back Door to Hell", "Back from Eternity", "Back from the Dead", "Back from the Front", "Back Home and Broke", "Back in Business", "Back in Circulation", "Back in the Day", "Back Pay", "Back Roads", "Back Stage", "Back Street", "Back to Back", "Back to Bataan", "Back to God's Country", "Back to Life", "Back to Nature", "Back to School", "Back to the Beach", "Back to the Farm", "Back to the Future Part II", "Back to the Future Part III", "Back to the Future", "Back to the Woods", "Back Trail", "Backbone", "Backdraft", "Backfire", "Background to Danger", "Backlash", "Backstabbing for Beginners", "Bad Ass", "Bad Asses on the Bayou", "Bad Bascomb", "Bad Boy", "Bad Boys II", "Bad Boys", "Bad Company", "Bad Day at Black Rock", "Bad Dreams", "Bad for Each Other", "Bad Girl", "Bad Girls Go to Hell", "Bad Girls", "Bad Guy", "Bad Influence", "Bad Johnson", "Bad Kids Go to Hell", "Bad Lands", "Bad Lieutenant", "Bad Little Angel", "Bad Man from Red Butte", "Bad Manners", "Bad Man's Bluff", "Bad Man's River", "Bad Medicine", "Bad Men of Missouri", "Bad Men of the Border", "Bad Men of Thunder Gap", "Bad Men of Tombstone", "Bad Moms", "Bad Moon", "Bad News Bears", "Bad Samaritan", "Bad Santa", "Bad Seed, The", "Bad Sister", "Bad Teacher", "Bad Times at the El Royale", "Badge 373", "Badlands of Dakota", "Badlands of Montana", "Badlands", "Badman's Country", "Badman's Gold", "Badman's Territory", "Baffled", "Bag and Baggage", "Bagdad Café", "Bagdad", "Baggage Claim", "Bahama Passage", "Bailout at 43,000", "Bait", "Bal Tabarin", "Balalaika", "Balked at the Altar", "Ball of Fire", "Ballast", "Ballistic: Ecks vs. Sever", "Ballot Box Bunny", "Balls of Fury", "Balto", "Bambi II", "Bambi Meets Godzilla", "Bambi", "Bamboozled", "Bananas", "Band and Battalion of the U.S. Indian School", "Band of Angels", "Band of the Hand", "Bandido", "Bandit King of Texas", "Bandits Beware", "Bandits of Dark Canyon", "Bandits of El Dorado", "Bandits of the Badlands", "Bandits of the West", "Bandits", "Bandolero!", "Bandslam", "Bang the Drum Slowly", "Bang", "Bangkok Dangerous", "Banjo on My Knee", "Banjo", "Bank Alarm", "Bank Robber", "Bank Shot", "Bannerline", "Banning", "Bar 20 Justice", "Bar 20 Rides Again", "Bar 20", "Bar Girls", "Barb Wire", "Barbara Broadcast", "Barbara Frietchie", "Barbarian Queen", "Barbarosa", "Barbary Coast Gent", "Barbary Coast", "Barbary Pirate", "Barbecue Brawl", "Barbed Wire", "Barbershop 2: Back in Business", "Barbershop", "Barbershop: The Next Cut", "Barbie in A Mermaid Tale 2", "Barbie: The Princess and the Popstar", "Barcelona", "Bardelys the Magnificent", "Bare Fists", "Bare Knuckles", "Baree, Son of Kazan", "Bare-Fisted Gallagher", "Barefoot Boy", "Barefoot in the Park", "Barefoot", "Barfly", "Barnacle Bill", "Barney Oldfield's Race for a Life", "Barnum and Bailey's Circus", "Barnum Was Right", "Barnyard Flirtations", "Barnyard Follies", "Barnyard", "Barquero", "Barricade", "Barriers Burned Away", "Barriers of the Law", "Barry Lyndon", "Barry Munday", "Bartleby", "Barton Fink", "Bar-Z Bad Men", "Baseball and Bloomers", "Baseball Bugs", "BASEketball", "Bashful Buccaneer", "Bashful", "Basic Instinct 2", "Basic Instinct", "Basic", "Basket Case 2", "Basket Case", "Basquiat", "Bastard Out of Carolina", "Bat*21", "Bataan", "Bates Motel", "Bathing Beauty", "Batman and Robin", "Batman Begins", "Batman Dracula", "Batman Forever", "Batman Returns", "Batman Revealed", "Batman v Superman: Dawn of Justice", "Batman", "Batman: Mask of the Phantasm", "Batman: The Dark Knight Returns", "Bats", "Batteries Not Included", "Battle at Bloody Beach", "Battle Beyond the Stars", "Battle Circus", "Battle Cry", "Battle Flame", "Battle for Terra", "Battle for the Planet of the Apes", "Battle Hymn", "Battle of Broadway", "Battle of Greed", "Battle of the Bulge", "Battle of the Coral Sea", "Battle of the Sexes", "Battle of the Year", "Battle Stations", "Battle Taxi", "Battle Zone", "Battle: Los Angeles", "Battlefield America", "Battlefield Earth", "Battleground", "Battles of Chief Pontiac", "Battleship", "Battlestar Galactica", "Battling Brewster", "Battling Bunyan", "Battling Butler", "Battling Orioles", "Bavu", "Bayou", "Baywatch the Movie: Forbidden Paradise", "Baywatch", "Be a Little Sport", "Be Cool", "Be Kind Rewind", "Be My Wife", "Be Yourself", "Beach Blanket Bingo", "Beach Party", "Beach Rats", "Beach Red", "Beaches", "Beachhead", "Bean", "Beanstalk Bunny", "BearCity 2: The Proposal", "BearCity", "Beast from Haunted Cave", "Beastly", "Beasts of the Southern Wild", "Beat It", "Beat Street", "Beat the Band", "Beat the Devil", "Beat", "Beating the Game", "Beating the Odds", "Beatriz at Dinner", "Beats, Rhymes & Life: The Travels of a Tribe Called Quest", "Beau Bandit", "Beau Broadway", "Beau Brummel", "Beau Brummell", "Beau Geste", "Beau Ideal", "Beau James", "Beau Sabreur", "Beautiful Bismark", "Beautiful Boy", "Beautiful But Broke", "Beautiful Creatures", "Beautiful Girls", "Beautiful Joe", "Beautiful Stranger", "Beautiful", "Beauty and Bullets", "Beauty and the Bad Man", "Beauty and the Bandit", "Beauty and the Beast 3D", "Beauty and the Beast", "Beauty and the Beast: The Enchanted Christmas", "Beauty and the Boss", "Beauty and the Rogue", "Beauty for Sale", "Beauty for the Asking", "Beauty on Parade", "Beauty Parlor", "Beauty Shop", "Beauty-Proof", "Beauty's Worth", "Beavis and Butt-Head Do America", "Bébé's Kids", "Because I Said So", "Because of Him", "Because of Winn-Dixie", "Because of You", "Because They're Young", "Because You're Mine", "Becket", "Beckoning Roads", "Becky Sharp", "Bed of Roses", "Bedazzled", "Bedeviled Rabbit", "Bedevilled", "Bedknobs and Broomsticks", "Bedlam", "Bedside Manner", "Bedside", "Bedtime for Bonzo", "Bedtime Stories", "Bedtime Story", "Bee Movie", "Bee Season", "Beef Extract Room", "Beep Prepared", "Beer Barrel Polecats", "Beer for My Horses", "Beer", "Beerfest", "Bees in His Bonnet", "Beethoven", "Beethoven's 2nd", "Beetlejuice", "Before and After", "Before Breakfast", "Before Dawn", "Before I Fall", "Before I Hang", "Before Midnight", "Before Night Falls", "Before Stonewall", "Before Sunrise", "Before Sunset", "Before the Devil Knows You're Dead", "Beg, Borrow or Steal", "Beggar on Horseback", "Beggars in Ermine", "Beggars of Life", "Beginners", "Beginning of the End", "Behave Yourself!", "Behemoth, the Sea Monster", "Behind City Lights", "Behind Closed Doors", "Behind Enemy Lines", "Behind Green Lights", "Behind Locked Doors", "Behind Office Doors", "Behind Prison Gates", "Behind That Curtain", "Behind the Candelabra", "Behind the Curtain", "Behind the Door", "Behind the Evidence", "Behind the Front", "Behind the Green Door", "Behind the Green Lights", "Behind the Headlines", "Behind the High Wall", "Behind the Make-Up", "Behind the Mask", "Behind the Mask: The Rise of Leslie Vernon", "Behind the Mike", "Behind the News", "Behind the Rising Sun", "Behind the Screen", "Behind Two Guns", "Behold a Pale Horse", "Behold My Wife!", "Behold This Woman", "Being Flynn", "Being John Malkovich", "Being Julia", "Being Respectable", "Being There", "Beirut", "Bela Lugosi Meets a Brooklyn Gorilla", "Believe Me, Xantippe", "Believe", "Bell Boy 13", "Bell, Book and Candle", "Bella", "Belle Le Grand", "Belle of Old Mexico", "Belle of the Nineties", "Belle of the Yukon", "Belle Starr", "Belle Starr's Daughter", "Belles on Their Toes", "Bells Are Ringing", "Bells of Capistrano", "Bells of Coronado", "Bells of Innocence", "Bells of Rosarita", "Bells of San Angelo", "Bells of San Fernando", "Bells of San Juan", "Belly", "Bellyfruit", "Beloved Enemy", "Beloved Infidel", "Beloved", "Below the Belt", "Below the Deadline", "Below the Line", "Below the Sea", "Below Utopia", "Below", "Ben and Me", "Ben Banks", "Ben Is Back", "Ben", "Bend of the River", "Bending the Rules", "Beneath the 12-Mile Reef", "Beneath the Planet of the Apes", "Beneath the Valley of the Ultra-Vixens", "Beneath Western Skies", "Beneath", "Bengal Brigade", "Bengal Tiger", "Bengazi", "Ben-Hur", "Ben-Hur: A Tale of the Christ", "Benji the Hunted", "Benji", "Benji: Off the Leash!", "Benny & Joon", "Beowulf", "Berdella[2]", "Beretta's Island", "Berkeley Square", "Berlin Express", "Bermuda Mystery", "Bernardine", "Bernie", "Berserker", "Bert Rigby, You're a Fool", "Berth Marks", "Bertha, the Sewing Machine Girl", "Beside Still Waters", "Best Boy", "Best Defense", "Best Foot Forward", "Best Friends", "Best in Show", "Best Kept Secret", "Best Laid Plans", "Best Man Down", "Best Man Wins", "Best Men", "Best of Enemies", "Best of the Badmen", "Best of the Best 2", "Best of the Best 3: No Turning Back", "Best of the Best", "Best Seller", "Betrayal from the East", "Betrayal", "Betrayed", "Betsy's Wedding", "Better Late Than Never", "Better Living Through Chemistry", "Better Luck Tomorrow", "Better Off Dead", "Better Times", "Betty Co-Ed", "Betty of Greystone", "Between Friends", "Between Heaven and Hell", "Between Midnight and Dawn", "Between Showers", "Between the Lines", "Between Two Women", "Between Two Worlds", "Between Us Girls", "Between Us", "Beverly Hills Brats", "Beverly Hills Chihuahua 3: Viva la Fiesta!", "Beverly Hills Chihuahua", "Beverly Hills Cop II", "Beverly Hills Cop III", "Beverly Hills Cop", "Beverly Hills Ninja", "Beverly of Graustark", "Beware of Bachelors", "Beware of Blondes", "Beware of Blondie", "Beware of Ladies", "Beware of Married Men", "Beware of Widows", "Beware Spooks!", "Beware! The Blob", "Beware!", "Beware", "Beware, My Lovely", "Bewitched", "Beyond a Reasonable Doubt", "Beyond All Odds", "Beyond Belief", "Beyond Borders", "Beyond Conviction", "Beyond Glory", "Beyond His Fondest Hopes", "Beyond London Lights", "Beyond Mombasa", "Beyond Rangoon", "Beyond Reason", "Beyond Skyline", "Beyond the Blue Horizon", "Beyond the Border", "Beyond the Caribbean", "Beyond the Door", "Beyond the Farthest Star", "Beyond the Forest", "Beyond the Last Frontier", "Beyond the Law", "Beyond the Lights", "Beyond the Mat", "Beyond the Pecos", "Beyond the Poseidon Adventure", "Beyond the Purple Hills", "Beyond the Rainbow", "Beyond the Reach", "Beyond the Rockies", "Beyond the Rocks", "Beyond the Sacramento", "Beyond the Sea", "Beyond the Sierras", "Beyond the Stars", "Beyond the Time Barrier", "Beyond the Trail", "Beyond the Valley of the Dolls", "Beyond Therapy", "Beyond Tomorrow", "Beyond Victory", "Beyond", "Bhowani Junction", "Bianca", "Bicentennial Man", "Bickford Shmeckler's Cool Ideas", "Bicycle Dive", "Biff Bang Buddy", "Big Ass Spider!", "Big Bad Love", "Big Bad Mama", "Big Bad Wolf", "Big Boy", "Big Brother", "Big Brown Eyes", "Big Bully", "Big Business Girl", "Big Business", "Big City Blues", "Big City", "Big Daddy", "Big Dan", "Big Dreams Little Tokyo", "Big Executive", "Big Eyes", "Big Fan", "Big Fat Liar", "Big Fish", "Big Hero 6", "Big House Bunny", "Big House, U.S.A.", "Big Jack", "Big Jake", "Big Jim McLain", "Big Leaguer", "Big Miracle", "Big Momma's House 2", "Big Momma's House", "Big Mommas: Like Father, Like Son", "Big Money Rustlas", "Big Night", "Big Pal", "Big Red", "Big Stan", "Big Sur", "Big Timber", "Big Time", "Big Top Bunny", "Big Top Pee-wee", "Big Top Scooby-Doo!", "Big Town After Dark", "Big Town Czar", "Big Town Girl", "Big Town Scandal", "Big Town", "Big Trouble in Little China", "Big Trouble", "Big Wednesday", "Big", "Bigfoot", "Bigger Than Barnum's", "Bigger Than Life", "Bigger Than the Sky", "Biggie & Tupac", "Biker Boyz", "Bikini Beach", "Bill & Ted's Bogus Journey", "Bill & Ted's Excellent Adventure", "Bill and Coo", "Bill Apperson's Boy", "Bill Cosby: Himself", "Bill Cracks Down", "Bill Henry", "Bill W", "Billie", "Billion Dollar Brain", "Billionaire Boys Club", "Billy Bathgate", "Billy Blazes, Esq.", "Billy Jack Goes to Washington", "Billy Jack", "Billy Jim", "Billy Madison", "Billy Rose's Jumbo", "Billy the Kid in Texas", "Billy the Kid Outlawed", "Billy the Kid Returns", "Billy the Kid Trapped", "Billy the Kid vs. Dracula", "Billy the Kid", "Billy the Kid's Gun Justice", "Billy Two Hats", "Billy's Hollywood Screen Kiss", "Billy's Rival", "Billy's War Brides", "Biloxi Blues", "Bindlestiffs", "Bingo", "Bio-Dome", "Biography of a Bachelor Girl", "Bird of Paradise", "Bird on a Wire", "Bird", "Birdman of Alcatraz", "Birdman or (The Unexpected Virtue of Ignorance)", "Birds Anonymous", "Birds Do It", "Birds of a Feather", "Birdy", "Birth of a Nation", "Birth of The Beatles", "Birth of the Blues", "Birth", "Birthday Girl", "Birthright", "Bite the Bullet", "Bitter Creek", "Bitter Sweet", "Bitter Victory", "Black Aces", "Black and White", "Black Angel", "Black Arrow", "Black Bandit", "Black Bart", "Black Beauty", "Black Belt Jones", "Black Cadillac", "Black Caesar", "Black Cauldron, The", "Black Christmas", "Black Cloud", "Black Cyclone", "Black Diamonds", "Black Dog", "Black Dragons", "Black Eagle", "Black Eye", "Black Fist", "Black Friday", "Black Fury", "Black Gold", "Black Hand", "Black Hawk Down", "Black Heat", "Black Hills Ambush", "Black Hills", "Black Horse Canyon", "Black Jack", "Black Knight", "Black Legion", "Black Lightning", "Black Like Me", "Black Magic", "Black Market Babies", "Black Market Rustlers", "Black Marketing", "Black Mass", "Black Midnight", "Black Moon Rising", "Black Moon", "Black Nativity", "Black or White", "Black Orchids", "Black Oxen", "Black Oxfords", "Black Panther", "Black Paradise", "Black Patch", "Black Rage", "Black Rain", "Black Rock", "Black Roses", "Black Samson", "Black Scorpion II: Aftershock", "Black Scorpion", "Black Shampoo", "Black Sheep", "Black Snake", "Black Spurs", "Black Sun", "Black Sunday", "Black Swan", "Black Tuesday", "Black Water Vampire", "Black Widow", "Blackbeard the Pirate", "Blackbeard's Ghost", "Blackbird", "Blackboard Jungle", "Blackenstein", "Blackfish", "Blackhat", "Blackie's Redemption", "Blackjack Ketchum, Desperado", "BlacKkKlansman", "Blackmail", "Blackmailer", "Blackwell's Island", "Blacula", "Blade II", "Blade Runner 2049", "Blade Runner", "Blade", "Blade: Trinity", "Blades of Glory", "Blair Witch", "Blame It on Rio", "Blame It on the Bellboy", "Blank Check", "Blankman", "Blarney", "Blast from the Past", "Blast of Silence", "Blast-Off Girls", "Blaze of Noon", "Blaze", "Blazing Across the Pecos", "Blazing Bullets", "Blazing Days", "Blazing Frontier", "Blazing Guns", "Blazing Saddles", "Blazing Six Shooters", "Blazing Sixes", "Blazing Stewardesses", "Blazing the Overland Trail", "Blazing the Western Trail", "Blended", "Bless Me, Ultima", "Bless the Beasts and Children", "Bless the Child", "Blessed Event", "Blind Adventure", "Blind Alibi", "Blind Alley", "Blind Date", "Blind Dating", "Blind Faith", "Blind Fury", "Blind Hearts", "Blind Horizon", "Blind Husbands", "Blind Justice", "Blind Man's Eyes", "Blind Spot", "Blind Youth", "Blindfold", "'Blindfold", "Blindspotting", "Blink", "Blinky", "Bliss", "Blizzard", "Block Busters", "Blockade", "Blockers", "Block-Heads", "Blond Cheat", "Blonde Alibi", "Blonde Ambition", "Blonde Cobra", "Blonde Crazy", "Blonde Dynamite", "Blonde Fever", "Blonde for a Day", "Blonde from Brooklyn", "Blonde Ice", "Blonde Ransom", "Blonde Savage", "Blonde Trouble", "Blonde Venus", "Blondes At Work", "Blondie Brings Up Baby", "Blondie Goes to College", "Blondie Has Servant Trouble", "Blondie Hits the Jackpot", "Blondie in the Dough", "Blondie Johnson", "Blondie Knows Best", "Blondie Meets the Boss", "Blondie of the Follies", "Blondie on a Budget", "Blondie Plays Cupid", "Blondie Takes a Vacation", "Blondie", "Blondie's Anniversary", "Blondie's Big Deal", "Blondie's Big Moment", "Blondie's Hero", "Blondie's Holiday", "Blondie's Lucky Day", "Blondie's Reward", "Blondie's Secret", "Blood Alley", "Blood and Lace", "Blood and Sand", "Blood and Steel", "Blood and Wine", "Blood Arrow", "Blood Bath", "Blood Brothers", "Blood Diamond", "Blood Diner", "Blood Feast", "Blood for Irina", "Blood In Blood Out", "Blood Money", "Blood of Dracula's Castle", "Blood on the Arrow", "Blood on the Moon", "Blood on the Sun", "Blood Orgy of the Leather Girls", "Blood Simple", "Blood Tea and Red String", "Blood Thirst", "Blood Work", "Bloodbrothers", "Bloodfist II", "Bloodfist V: Human Target", "Bloodfist VI: Ground Zero", "Bloodfist VII: Manhunt", "Bloodhounds of Broadway", "Bloodhounds of the North", "Bloodhounds Tracking a Convict", "Bloodline", "Bloodlust!", "Bloodlust: Subspecies III", "Bloodsport II: The Next Kumite", "Bloodsport III", "Bloodsport", "Bloodstone", "Bloody Mama", "Bloody Mary", "Bloody Pit of Horror", "Blossoms in the Dust", "Blossoms on Broadway", "Blow Out", "Blow Your Own Horn", "Blow", "Blowing Wild", "Blown Away", "Blue Blazes", "Blue Blood", "Blue Canadian Rockies", "Blue Caprice", "Blue Cat Blues", "Blue Chips", "Blue City", "Blue Collar", "Blue Crush", "Blue Denim", "Blue Desert", "Blue Grass of Kentucky", "Blue Hawaii", "Blue in the Face", "Blue Jasmine", "Blue Montana Skies", "Blue Movie", "Blue Ruin", "Blue Skies", "Blue Sky", "Blue State", "Blue Steel", "Blue Streak", "Blue Thunder", "Blue Valentine", "Blue Velvet", "Bluebeard", "Bluebeard's 8th Wife", "Bluebeard's Eighth Wife", "Bluebeard's Seven Wives", "Blues Brothers 2000", "Blues Busters", "Blues in the Night", "Bluff", "Blume in Love", "Boarding School Girls' Pajama Parade", "Boardwalk", "Boat Trip", "Bob & Carol & Ted & Alice", "Bob and Sally", "Bob Roberts", "Bobbed Hair", "Bobbie Jo and the Outlaw", "Bobby Deerfield", "Bobby Jones: Stroke of Genius", "Bobby", "Bob's Baby", "Bodied", "Bodies, Rest & Motion", "Body and Soul", "Body Double", "Body Heat", "Body of Evidence", "Body of Lies", "Body Parts", "Body Rock", "Body Slam", "Body Snatchers", "Bodyguard", "Bodyhold", "Boeing Boeing", "Bohemian Rhapsody", "Boiler Room", "Boiling Point", "Bolero", "Bolshevism on Trial", "Bolt", "Bomba and the Jungle Girl", "Bomba on Panther Island", "Bomba, the Jungle Boy", "Bombardier", "Bombay Clipper", "Bombay Mail", "Bombers B-52", "Bomber's Moon", "Bombs Over Burma", "Bombshell", "Bon Voyage!", "Bon Voyage, Charlie Brown (and Don't Come Back!!)", "Bonanza Bunny", "Bonanza Town", "Bondage", "Bonds of Honor", "Bonds of Love", "Bone", "Bones", "Bongwater", "Bonjour Tristesse", "Bonnie and Clyde", "Bonnie Bonnie Lassie", "Bonnie Scotland", "Bonzo Goes to College", "Boo 2! A Madea Halloween", "Boo! A Madea Halloween", "Boobs in the Woods", "Booby Dupes", "Boogeyman", "Boogie Boy", "Boogie Nights", "Boogie Town", "Book Club", "Book of Love", "Book of Numbers", "Book of Shadows: Blair Witch 2", "Book Revue", "Booloo", "Boom Town", "Boom: The Sound of Eviction", "Boomerang Bill", "Boomerang", "Boothill Brigade", "Boots and Saddles", "Boots Malone", "Boots", "Booty Call", "Bop Girl Goes Calypso", "Bopha!", "Borat! Cultural Learnings of America for Make Benefit Glorious Nation of Kazakhstan", "Border Badmen", "Border Bandits", "Border Brigands", "Border Buckaroos", "Border Caballero", "Border Cafe", "Border Devils", "Border Feud", "Border Flight", "Border G-Man", "Border Incident", "Border Intrigue", "Border Justice", "Border Law", "Border Outlaws", "Border Patrol", "Border Phantom", "Border Rangers", "Border River", "Border Romance", "Border Run", "Border Saddlemates", "Border Treasure", "Border Vengeance", "Border Wolves", "Borderland", "Borderline", "Bordertown Gun Fighters", "Bordertown Trail", "Bordertown", "Born Again", "Born in China", "Born in East L.A.", "Born in Flames", "Born into Brothels", "Born on the Fourth of July", "Born Reckless", "Born Rich", "Born to Battle", "Born to be Bad", "Born to Be Loved", "Born to Be Wild 3D", "Born to be Wild", "Born to Dance", "Born to Fight", "Born to Gamble", "Born to Kill", "Born to Love", "Born to Sing", "Born to Speed", "Born to the Saddle", "Born to the West", "Born to Win", "Born Yesterday", "Borneo", "Bornless Ones", "Borrowed Finery", "Borrowed Hero", "Borrowed Husbands", "Borrowed Trouble", "Borrowed Wives", "Borrowing Trouble", "Boss Nigger", "Boss of Boomtown", "Boss of Bullion City", "Boss of Rawhide", "Boston Blackie and the Law", "Boston Blackie Booked on Suspicion", "Boston Blackie Goes Hollywood", "Boston Blackie", "Boston Blackie's Chinese Venture", "Boston Blackie's Rendezvous", "Boston Quackie", "Botany Bay", "Both Barrels Blazing", "Bottle Rocket", "Bottoms Up", "Bought and Paid For", "Bought", "Boulder Dam", "Bounce", "Bouncing Cats", "Bound for Glory", "Bound in Morocco", "Bound", "Boundaries", "Bounty Killer", "Bowery at Midnight", "Bowery Battalion", "Bowery Blitzkrieg", "Bowery Bombshell", "Bowery Boy", "Bowery Buckaroos", "Bowery Champs", "Bowery to Bagdad", "Bowery to Broadway", "Bowfinger", "Bowling for Columbine", "Box of Moon Light", "Boxcar Bertha", "Boxing Helena", "Boxing in Barrels", "Boy Crazy", "Boy Erased", "Boy Friend", "Boy Meets Girl", "Boy of Mine", "Boy of the Streets", "Boy on a Dolphin", "Boy Slaves", "Boy Trouble", "Boy, Did I Get a Wrong Number!", "Boyhood", "Boys and Girls", "Boys Beware", "Boys Don't Cry", "Boys Life 2", "Boys Life", "Boys Next Door, The", "Boys' Night Out", "Boys of the City", "Boys on the Side", "Boys' Ranch", "Boys' Reformatory", "Boys Town", "Boys", "Boyz n the Hood", "Braddock: Missing in Action III", "Brad's Status", "Brain Candy", "Brain Damage", "Brain Donors", "Brainscan", "Brainstorm", "Brake", "Bram Stoker's Dracula", "Brand of Fear", "Brand of the Devil", "Brand Upon the Brain!", "Branded Man", "Branded Men", "Branded", "Branding Hams", "Brannigan", "Brass Buttons", "Brass Commandments", "Brass Knuckles", "Brass", "Brassed Off", "Bratz: The Movie", "Brave Warrior", "Brave", "Braveheart", "Bravestarr: The Legend", "Brawl in Cell Block 99", "Brawn of the North", "Brazen Beauty", "Brazil at War", "Brazil", "Breach of Conduct", "Breach of Promise", "Breach", "Bread", "Break of Hearts", "Break Up", "Break, Break, Break", "Breakdown", "Breaker! Breaker!", "Breakfast at Sunrise", "Breakfast at Tiffany's", "Breakfast for Two", "Breakfast in Hollywood", "Breakfast of Champions", "Breakheart Pass", "Breakin' 2: Electric Boogaloo", "Breakin' All the Rules", "Breakin'", "Breaking and Entering", "Breaking Away", "Breaking In", "Breaking Into Society", "Breaking Point", "Breaking the Ice", "Breaking the Waves", "Breaking Wind", "Breakout", "Breakthrough", "Breast Men", "Breathe In", "Breathe", "Breathing Fire", "Breathing Lessons", "Breathless", "Bred in Old Kentucky", "Breed of Men", "Breed of the Border", "Breed of the Sea", "Breed of the Sunsets", "Breezing Home", "Breezy", "Brenda Starr, Reporter", "Brewster McCloud", "Brewster's Millions", "Brian's Song", "Brick Bradford", "Brick Mansions", "Brick", "Bridal Suite", "Bride and Gloom", "Bride by Mistake", "Bride for Sale", "Bride of Boogedy", "Bride of Chucky", "Bride of Frankenstein", "Bride of Re-Animator", "Bride of the Desert", "Bride of the Gorilla", "Bride of the Monster", "Bride of the Regiment", "Bride of the Storm", "Bride of Vengeance", "Bride Wars", "Bridegroom", "Brideless Groom", "Brides Are Like That", "Bridesmaids", "Bridge to Terabithia", "Bridge to the Sun", "Bridget Jones: The Edge of Reason", "Bridget Jones's Baby", "Brief Moment", "Brigadoon", "Brigham Young", "Bright Eyes", "Bright Leaf", "Bright Lights of Broadway", "Bright Lights", "Bright Lights, Big City", "Bright Road", "Bright Victory", "Bright", "Brighton Beach Memoirs", "Brigsby Bear", "Brilliant Marriage", "Brimstone", "Bring It On", "Bring Me the Head of Alfredo Garcia", "Bring on the Girls", "Bring on the Night", "Bring Your Smile Along", "Bringin' Home the Bacon", "Bringing Down the House", "Bringing Out the Dead", "Bringing Rain", "Bringing Up Baby", "Bringing Up Betty", "Bringing Up Father", "British Agent", "British Intelligence", "Broad Daylight", "Broadcast News", "Broadminded", "Broadway After Dark", "Broadway Babies", "Broadway Bad", "Broadway Bill", "Broadway Billy", "Broadway Broke", "Broadway Daddies", "Broadway Damage", "Broadway Danny Rose", "Broadway Fever", "Broadway Gold", "Broadway Gondolier", "Broadway Hostess", "Broadway Lady", "Broadway Limited", "Broadway Melody of 1936", "Broadway Melody of 1938", "Broadway Melody of 1940", "Broadway Musketeers", "Broadway or Bust", "Broadway Rhythm", "Broadway Rose", "Broadway Scandals", "Broadway Serenade", "Broadway Through a Keyhole", "Broadway to Cheyenne", "Broadway to Hollywood", "Broadway", "Brokeback Mountain", "Brokedown Palace", "Broken Arrow", "Broken Barriers", "Broken Blossoms", "Broken Bridges", "Broken Chains", "Broken City", "Broken Dreams", "Broken English", "Broken Fetters", "Broken Flowers", "Broken Hearts of Broadway", "Broken Hearts of Hollywood", "Broken Lance", "Broken Laws", "Broken Lullaby", "Broken Roads", "Broken Strings", "Broken Vessels", "Bronco Billy", "Bronco Buster", "Brooding Eyes", "Brooklyn Orchid", "Brooklyn Rules", "Brooklyn's Finest", "Broom-Stick Bunny", "Brother Bear", "Brother Future", "Brother John", "Brother of the Wind", "Brother Orchid", "Brother Rat and a Baby", "Brother Rat", "Brother to Brother", "Brother", "Brother, Can You Spare a Dime?", "Brotherhood of Blood", "Brotherly Love", "Brothers at War", "Brothers in the Saddle", "Brothers Under the Skin", "Brother's War", "Brothers", "Brown of Harvard", "Brown Sugar", "Brownie's Baby Doll", "Brownie's Little Venus", "Brown's Requiem", "Brubaker", "Bruce Almighty", "Bruce Gentry", "Brüno", "Brushfire", "Brutal Beauty: Tales of the Rose City Rollers", "Brute Force", "Bubba Ho-tep", "Bubble Boy", "Buccaneer Bunny", "Buccaneer's Girl", "Buchanan Rides Alone", "Buck and the Preacher", "Buck Benny Rides Again", "Buck Privates Come Home", "Buck Privates", "Buck Rogers", "Buck", "Buckaroo from Powder River", "Buckaroo Sheriff of Texas", "Bucking Broadway", "Bucking the Barrier", "Bucking the Truth", "Buckshot John", "Buckskin Frontier", "Bucktown", "Bucky Larson: Born to Be a Star", "Buddies Thicker Than Water", "Buddy Buddy", "Buddy", "Bud's Recruit", "Buena Vista Social Club: Adios", "Buffalo '66", "Buffalo Bill and the Indians, or Sitting Bull's History Lesson", "Buffalo Bill in Tomahawk Territory", "Buffalo Bill Rides Again", "Buffalo Bill", "Buffalo Bill's Wild West Parad", "Buffalo Soldiers", "Buffalo Street Parade", "Buffy the Vampire Slayer", "Bug", "Bughouse Bellhops", "Bugles in the Afternoon", "Bugs and Thugs", "Bugs Bunny and the Three Bears", "Bugs Bunny Gets the Boid", "Bugs Bunny Nips the Nips", "Bugs Bunny Rides Again", "Bugs Bunny: Superstar", "Bugs Bunny's 3rd Movie: 1001 Rabbit Tales", "Bugsy and Mugsy", "Bugsy", "Bull Durham", "Bulldog Drummond at Bay", "Bulldog Drummond Comes Back", "Bulldog Drummond Escapes", "Bulldog Drummond in Africa", "Bulldog Drummond Strikes Back", "Bulldog Drummond", "Bulldog Drummond's Bride", "Bulldog Drummond's Peril", "Bulldog Drummond's Revenge", "Bulldog Drummond's Secret Police", "Bulldog Edition", "Bullet Code", "Bullet for a Badman", "Bullet Scars", "Bullet to the Head", "Bullet", "Bulletproof Monk", "Bulletproof", "Bullets and Saddles", "Bullets for O'Hara", "Bullets for Rustlers", "Bullets or Ballots", "Bullets over Broadway", "Bullfighter and the Lady", "Bullitt", "Bullwhip", "Bully for Bugs", "Bully", "Bulworth", "Bumblebee", "Bumping Into Broadway", "Bunco Squad", "Bundle of Joy", "Bungalow 13", "Bunker Bean", "Bunker Hill Bunny", "Bunny Hugged", "Bunny O'Hare", "Buona Sera, Mrs. Campbell", "Burden of Dreams", "Bureau of Missing Persons", "Burglar Bill", "Burglar by Proxy", "Burglar", "Buried Alive", "Buried", "Burlesque on Carmen", "Burlesque", "Burma Convoy", "Burn After Reading", "Burn 'Em Up O'Connor", "Burned at the Stake", "Burning Bridges", "Burning Daylight", "Burning Gold", "Burning of Durland's Riding Academy", "Burning Sands", "Burning the Wind", "Burning Up Broadway", "Burning Up", "Burning Words", "Burnt Offerings", "Bury Me Dead", "Bus Riley's Back in Town", "Bus Stop", "Busgirl", "Bush Pilot", "Bushwhacked", "Bushy Hare", "Business and Pleasure", "Business Versus Love", "Busses Roar", "Busted", "Buster and Billie", "Buster Keaton: A Hard Act to Follow", "Bustin' Loose", "Bustin' Thru", "Busting", "Busy Buddies", "But I'm a Cheerleader", "But Not for Me", "But the Flesh Is Weak", "Butch and Sundance: The Early Days", "Butch Camp", "Butch Cassidy and the Sundance Kid", "Butter", "Butterfield 8", "Butterflies Are Free", "Butterfly", "Buy & Cell", "Buy Me That Town", "Buying a Baby", "Buying the Cow", "Buzzy Rides the Range", "Bwana Devil", "By Appointment Only", "By Candlelight", "By Divine Right", "By Indian Post", "By Love Possessed", "By the Light of the Silvery Moon", "By the Sad Sea Waves", "By the Sword", "By Whose Hand?", "By Your Leave", "Bye Bye Birdie", "Bye Bye Braverman", "Bye Bye Love", "C Me Dance", "C.H.O.M.P.S.", "C.H.U.D. II: Bud the C.H.U.D.", "C.H.U.D.", "C.O.G.", "C.Q.D.; or, Saved by Wireless; a True Story of the Wreck of the Republic", "Cabaret", "Cabin Boy", "Cabin Fever", "Cabin in the Sky", "Caboblanco", "Cactus Flower", "Cactus Trails", "Caddyshack II", "Caddyshack", "Cadence", "Cadet Girl", "Cadillac Man", "Cadillac Records", "Caesar and Otto's Deadly Christmas", "Cafe Hostess", "Cafe Metropole", "Cafe Society", "Caffeine", "Cage of Evil", "Caged Fury", "Caged Heat", "Caged", "Cahill U.S. Marshal", "Cain and Mabel", "Cain's Cutthroats", "Cairo", "Cake", "Cake: A Wedding Story", "Calaboose", "Calamity Anne, Heroine", "Calamity Anne's Beauty", "Calamity Anne's Dream", "Calamity Anne's Inheritance", "Calamity Anne's Love Affair", "Calamity Anne's Vanity", "Calamity Jane and Sam Bass", "Calamity Jane", "Calcutta", "Calendar Girl", "Calibre 45", "California Conquest", "California Dreaming", "California Firebrand", "California Frontier", "California Gold Rush", "California Joe", "California Mail", "California or Bust", "California Passage", "California Solo", "California Split", "California Straight Ahead", "California Suite", "California Winter", "California", "Caligula", "Call a Messenger", "Call Her Savage", "Call It a Day", "Call It Luck", "Call Me Bwana", "Call Me by Your Name", "Call Me Kuchu", "Call Me Madam", "Call Me Mister", "Call Me", "Call Northside 777", "Call of the Flesh", "Call of the Jungle", "Call of the Klondike", "Call of the Prairie", "Call of the Rockies", "Call of the South Seas", "Call of the West", "Call of the Yukon", "Call Out the Marines", "Call the Mesquiteers", "Callaway Went Thataway", "Calling All Cars", "Calling All Curs", "Calling All Husbands", "Calling All Marines", "Calling Dr. Death", "Calling Dr. Gillespie", "Calling Dr. Kildare", "Calling Homicide", "Calling Philo Vance", "Calling Wild Bill Elliott", "Cally's Comet", "Calm Yourself", "Calvert's Valley", "Calypso Heat Wave", "Camelot", "Cameo Kirby", "Cameron's Closet", "Camille of the Barbary Coast", "Camille", "Camp Cucamonga", "Camp Nowhere", "Camp Stories", "Camp Takota", "Camp X-Ray", "Camp", "Campfire Tales", "Campground Massacre", "Campus Confessions", "Campus Honeymoon", "Campus Knights", "Campus on the March", "Campus Rhythm", "Campus Sleuth", "Can a Song Save Your Life?", "Can a Woman Love Twice?", "Can She Bake a Cherry Pie?", "Can This Be Dixie?", "Can You Ever Forgive Me?", "Canadian Bacon", "Canadian Mounties vs Atomic Invaders", "Canadian Pacific", "Canary Row", "Can-Can", "Cancer Wars", "Candleshoe", "Candy", "Candyman 2: Farewell to the Flesh", "Candyman", "Cannery Row", "Cannes Man", "Cannibal Attack", "Cannibal Hookers", "Cannibal Rollerbabes", "Cannibal Women in the Avocado Jungle of Death", "Cannibal! The Musical", "Cannon for Cordoba", "Cannonball Run II", "Cannonball", "Canon City", "Can't Buy Me Love", "Can't Hardly Wait", "Can't Help Singing", "Can't Stop the Music", "Canyon Ambush", "Canyon City", "Canyon Crossroads", "Canyon of the Fools", "Canyon Passage", "Canyon Raiders", "Canyon River", "Cape Fear", "Capitalism: A Love Story", "Capone", "Capote", "Cappy Ricks Returns", "Caprice", "Capricorn One", "Captain Alvarez", "Captain America", "Captain America: Civil War", "Captain America: The First Avenger", "Captain America: The Winter Soldier", "Captain Applejack", "Captain Blood", "Captain Calamity", "Captain Carey, U.S.A.", "Captain Caution", "Captain China", "Captain Corelli's Mandolin", "Captain Eddie", "Captain EO", "Captain Fly-by-Night", "Captain from Castile", "Captain Fury", "Captain Horatio Hornblower R.N.", "Captain Hurricane", "Captain January", "Captain John Smith and Pocahontas", "Captain Kidd and the Slave Girl", "Captain Kidd", "Captain Kidd, Jr.", "Captain Kidd's Kids", "Captain Lash", "Captain Lightfoot", "Captain Newman, M.D.", "Captain of the Guard", "Captain Phillips", "Captain Pirate", "Captain Ron", "Captain Salvation", "Captain Scarlett", "Captain Thunder", "Captain Tugboat Annie", "Captain Underpants: The First Epic Movie", "Captain Video: Master of the Stratosphere", "Captains Courageous", "Captains of the Clouds", "Captive Girl", "Captive Hearts", "Captive of Billy the Kid", "Captive Wild Woman", "Captive Women", "Captivity", "Capture of Boer Battery by British", "Captured!", "Car 54, Where Are You?", "Car 99", "Car Dogs", "Car Wash", "Caravan", "Caravans", "Carbine Williams", "Carbon Copy", "Cardiac Arrest", "Cardinal Richelieu", "Care Bears: Oopsy Does It!", "Career Girl", "Career Opportunities", "Career Woman", "Career", "Careers", "Carefree", "Careful, Soft Shoulder", "Careless Lady", "Cargo to Capetown", "Caribbean Gold", "Carlito's Way", "Carmen Get It!", "Carmen Jones", "Carmen", "Carnage", "Carnal Knowledge", "Carnegie Hall", "Carnival Boat", "Carnival in Costa Rica", "Carnival of Souls", "Carnival Queen", "Carnival Story", "Carnival", "Carnosaur 2", "Carnosaur 3: Primal Species", "Carny", "Carol for Another Christmas", "Carolina Blues", "Carolina Cannonball", "Carolina Moon", "Carolina", "Caroline and Jackie", "Carolyn of the Corners", "Carousel", "Carpool", "Carrie Pilby", "Carrie", "Carried Away", "Carriers", "Cars 2", "Cars 3", "Cars", "Carson City Cyclone", "Carson City Raiders", "Carson City", "Cartoon All-Stars to the Rescue", "Caryl of the Mountains", "Casa de los Babys", "Casa de Mi Padre", "Casa Manana", "Casablanca", "Casanova Brown", "Casanova Cat", "Casanova in Burlesque", "Casanova", "Casanova's Big Night", "Casbah", "Case 39", "Case of the Missing Man", "Casey's Shadow", "Cash McCall", "Casino Jack and the United States of Money", "Casino Jack", "Casino Royale", "Casino", "Casper Meets Wendy", "Casper", "Casper: A Spirited Beginning", "Cass Timberlane", "Cassandra's Dream", "Cassidy of Bar 20", "Cast a Giant Shadow", "Cast a Long Shadow", "Cast Away", "Castle Freak", "Castle in the Desert", "Castle Keep", "Castle of Evil", "Castle on the Hudson", "Castles in the Air", "Casual Sex?", "Casualties of War", "Cat Ballou", "Cat Chaser", "Cat Fishin'", "Cat Napping", "Cat on a Hot Tin Roof", "Cat People", "Catacombs", "Catalina Caper", "Catch a Fire", "Catch Me If You Can", "Catch My Smoke", "Catch That Kid", "Catch-22", "Catchfire", "Catfight", "Catherine the Great", "Catlow", "Cats & Dogs", "Cats & Dogs: The Revenge of Kitty Galore", "Cats Don't Dance", "Cat's Eye", "Cat-Tails for Two", "Cattle Annie and Little Britches", "Cattle Call", "Cattle Drive", "Cattle Empire", "Cattle Queen of Montana", "Cattle Queen", "Cattle Raiders", "Cattle Stampede", "Cattle Town", "Catty Cornered", "Catwoman", "Cat-Women of the Moon", "Caught Bluffing", "Caught Cheating", "Caught in a Cabaret", "Caught in the Draft", "Caught in the Fog", "Caught in the Rain", "Caught Plastered", "Caught Short", "Caught Up", "Caught", "Cause for Alarm!", "Cavalcade of the West", "Cavalcade", "Cavalry Scout", "Cavalry", "Cave of Outlaws", "Caveman", "CB4", "CBGB", "Cease Fire!", "Cecil B. Demented", "Cecilia of the Pink Roses", "Cedar Rapids", "Ceiling Zero", "Celebration Family", "Celebrity", "Celeste and Jesse Forever", "Cellar Dweller", "Cellular", "Celsius 41.11", "Celtic Pride", "Centennial Summer", "Center Stage", "Central Airport", "Central Intelligence", "Central Park", "Certain Fury", "Cha-Cha-Cha Boom!", "Chad Hanna", "Chain Gang", "Chain Lightning", "Chain of Circumstance", "Chain of Desire", "Chain of Evidence", "Chain Reaction", "Chained for Life", "Chained", "Chairman of the Board", "Chalk Marks", "Chalk", "Challenge of the Range", "Challenge to Lassie", "Challenger", "Chamber of Horrors", "Chameleon Street", "Champ for a Day", "Champagne Charlie", "Champagne for Breakfast", "Champagne for Caesar", "Champagne Waltz", "Champion", "Chan Is Missing", "Chance at Heaven", "Chance", "Chances Are", "Chances", "Chandu the Magician", "Chang: A Drama of the Wilderness", "Change of Habit", "Change of Heart", "Change of Mind", "Changeling", "Changes", "Changing Husbands", "Changing Lanes", "Changing the Game", "Channing of the Northwest", "Chaplin", "Chappaqua", "Chappaquiddick", "Chappie", "Chapter 27", "Chapter Two", "Characterz", "Charade", "Charge It to Me", "Charge of the Lancers", "Chariots of Fur", "Charley and the Angel", "Charley Varrick", "Charley's Aunt", "Charlie and the Chocolate Factory", "Charlie Chan and the Curse of the Dragon Queen", "Charlie Chan at Monte Carlo", "Charlie Chan at the Circus", "Charlie Chan at the Olympics", "Charlie Chan at the Opera", "Charlie Chan at the Race Track", "Charlie Chan at the Wax Museum", "Charlie Chan at Treasure Island", "Charlie Chan Carries On", "Charlie Chan in City in Darkness", "Charlie Chan in Egypt", "Charlie Chan in Honolulu", "Charlie Chan in London", "Charlie Chan in Panama", "Charlie Chan in Paris", "Charlie Chan in Reno", "Charlie Chan in Rio", "Charlie Chan in Shanghai", "Charlie Chan in The Chinese Cat", "Charlie Chan in the Secret Service", "Charlie Chan on Broadway", "Charlie Chan's Chance", "Charlie Chan's Courage", "Charlie Chan's Greatest Case", "Charlie Chan's Murder Cruise", "Charlie Chan's Secret", "Charlie Countryman", "Charlie McCarthy, Detective", "Charlie St. Cloud", "Charlie Wilson's War", "Charlie's Angels", "Charlie's Angels: Full Throttle", "Charlotte's Web", "Charly", "Charter Pilot", "Chartroose Caboose", "Chasers", "Chasing Amy", "Chasing Danger", "Chasing Ice", "Chasing Liberty", "Chasing Mavericks", "Chasing Papi", "Chasing Rainbows", "Chasing the Horizon", "Chasing the Moon", "Chasing Through Europe", "Chasing Trouble", "Chasing Yesterday", "Chastity", "Chat Room", "Chato's Land", "Chattahoochee", "Chatterbox", "Che!", "Che", "Cheap Kisses", "Cheap Thrills (film)", "Cheaper by the Dozen 2", "Cheaper by the Dozen", "Cheaper to Marry", "Cheaters at Play", "Cheaters", "Cheating Cheaters", "Cheating Herself", "Check and Double Check", "Check Your Guns", "Checkered Flag", "Checkers", "Checking Out", "Cheech & Chong's Animated Movie", "Cheech & Chong's Next Movie", "Cheer Up and Smile", "Cheerleaders' Wild Weekend", "Cheers for Miss Bishop", "Cheers of the Crowd", "Cheese Chasers", "Cheetah", "Chelsea Girls", "Chelsea Walls", "Chernobyl Diaries", "Cherokee Strip", "Cherokee Uprising", "Cherry 2000", "Cherry Falls", "Cherry, Harry & Raquel!", "Chesty Anderson, USN", "Chetniks! The Fighting Guerrillas", "Cheyenne Autumn", "Cheyenne Roundup", "Cheyenne Takes Over", "Cheyenne Wildcat", "Cheyenne", "Cheyenne's Pal", "Chicago 10", "Chicago After Midnight", "Chicago Cab", "Chicago Calling", "Chicago Confidential", "Chicago Deadline", "Chicago Syndicate", "Chicago", "Chick Carter, Detective", "Chicken A La King", "Chicken Chaser", "Chicken Every Sunday", "Chicken Little", "Chicken Ranch", "Chicken Wagon Family", "Chickie", "Chief Crazy Horse", "Child 44", "Child Bride", "Child in the Night", "Child of Divorce", "Child of Manhattan", "Child of Rage", "Childish Things", "Children of a Lesser God", "Children of Divorce", "Children of Dreams", "Children of Dust", "Children of Jazz", "Children of Men", "Children of Pleasure", "Children of the Corn II: The Final Sacrifice", "Children of the Corn III: Urban Harvest", "Children of the Corn IV: The Gathering", "Children of the Corn", "Children of the Night", "Children of the Ritz", "Children of the Whirlwind", "Children Shouldn't Play with Dead Things", "Child's Play 2", "Child's Play 3", "Child's Play", "Chill Factor", "Chilling Visions: 5 Senses of Fear", "Chimmie Fadden Out West", "Chimmie Fadden", "Chimpanzee", "China Clipper", "China Corsair", "China Cry", "China Doll", "China Gate", "China Girl", "China Moon", "China O'Brien", "China Passage", "China Seas", "China Sky", "China Venture", "China", "China's Little Devils", "Chinatown After Dark", "Chinatown at Midnight", "Chinatown Charlie", "Chinatown Nights", "Chinatown Squad", "Chinatown", "Chinese Box", "Chinese Coffee", "Chip of the Flying U", "Chip Off the Old Block", "CHiPs", "Chisum", "Chocolat", "Choke Canyon", "Choke", "Choose Me", "Chop Suey & Co.", "Chopper Chicks in Zombietown", "Chopping Mall", "Chorus Line, A", "Chosen Survivors", "Chow Hound", "Christina", "Christine of the Big Tops", "Christine of the Hungry Heart", "Christine", "Christmas at Maxwell's", "Christmas Child", "Christmas Comes to Willow Creek", "Christmas Eve", "Christmas Every Day", "Christmas Evil", "Christmas Holiday", "Christmas in Connecticut", "Christmas in July", "Christmas in Tattertown", "Christmas Memories", "Christmas with the Dead", "Christmas with the Kranks", "Christopher Bean", "Christopher Columbus: The Discovery", "Christopher Robin", "Christopher Strong", "Chrome and Hot Leather", "Chronicle", "Chu Chu and the Philly Flash", "Chuck & Buck", "Chuck Amuck: The Movie", "Chuka", "Church Ball", "Chutney Popcorn", "Ciao America\nCinderella 2 Dreams Come True The Movie", "Cigarrette Girl", "Cimarron", "Cinderella Jones", "Cinderella Liberty", "Cinderella Man", "Cinderella Swings It", "Cinderella", "Cinderfella", "Cinders", "Cinema Europe: The Other Hollywood", "Cinerama Holiday", "Circe, the Enchantress", "Circle of Power", "Circle of Two", "Circular Panorama of the Electric Tower and Pond", "Circumstantial Evidence", "Circus Days", "Circus Girl", "Circus Rookies", "Circus World", "Cirque du Freak: The Vampire's Assistant", "Cisco Pike", "Citadel of Crime", "Citizen Kane", "Citizen Ruth", "Citizen X", "Citizen's Band", "City Across the River", "City Beneath the Sea", "City by the Sea", "City for Conquest", "City Girl", "City Hall", "City Heat", "City Island", "City Lights", "City Limits", "City of Angels", "City of Bad Men", "City of Chance", "City of Ember", "City of Fear", "City of Ghosts", "City of Hope", "City of Industry", "City of Lost Men", "City of Missing Girls", "City of Shadows", "City on Fire", "City Park", "City Slickers II: The Legend of Curly's Gold", "City Slickers", "City Streets", "City That Never Sleeps", "City Under the Sea", "City Without Men", "Civic Duty", "Civil Brand", "Civilization", "Claire of the Moon", "Clambake", "Clancy Street Boys", "Clancy's Kitchen", "Clara's Heart", "Clarence", "Clarence, the Cross-Eyed Lion", "Clark: A Gonzomentary", "Clash by Night", "Clash of the Empires", "Clash of the Titans", "Class Act", "Class Action", "Class of 1999", "Class of '44", "Class of Nuke 'Em High 3: The Good, the Bad and the Subhumanoid", "Class of Nuke 'Em High II: Subhumanoid Meltdown", "Class", "Classified", "Classmates", "Claudelle Inglish", "Claudia and David", "Claudia", "Claudine", "Clay Pigeon", "Clay Pigeons", "Clean and Sober", "Clean Slate", "Clean, Shaven", "Clear All Wires!", "Clear and Present Danger", "Clear Cut: The Story of Philomath, Oregon", "Clear the Decks", "Clearing the Range", "Cleopatra Jones and the Casino of Gold", "Cleopatra Jones", "Cleopatra", "Clerks II", "Clerks", "Click", "Client 9: The Rise and Fall of Eliot Spitzer", "Cliff Scenery at the Fabbins", "Cliffhanger", "Clifford", "Clifford's Really Big Movie", "Clipped Wings", "Clive of India", "Cloak & Dagger", "Cloak and Dagger", "Clockers", "Clockstoppers", "Clockwatchers", "Close Encounters of the Third Kind", "Close Harmony", "Close Quarters, with a Notion of the Motion of the Ocean", "Close to My Heart", "Close Up", "Close-Cropped Clippings", "Closed Gates", "Closer", "Closet Land", "Close-Up", "Clothes Make the Pirate", "Clothes Make the Woman", "Cloud Atlas", "Cloud Cuckoo Land", "Cloudy with a Chance of Meatballs 2", "Cloudy with a Chance of Meatballs", "Cloverfield", "Clownhouse", "Clowns Spinning Hats", "Club Dread", "Club Havana", "Club Paradise", "Clubs Are Trump", "Clue", "Clueless", "Cluny Brown", "C-Man", "C'mon, Let's Live a Little", "Coach Carter", "Coal Miner's Daughter", "Coaling a Steamer, Nagasaki Bay, Japan", "Coast Guard", "Coast to Coast", "Cobb", "Cobra Woman", "Cobra", "Cocaine Cowboys", "Cock of the Air", "Cockeyed Cavaliers", "Cockeyed Cowboys of Calico County", "Cockfighter", "Cocktail Hour", "Cocktail", "Coco", "Cocoanut Grove", "Cocoon", "Cocoon: The Return", "Code Name: The Cleaner", "Code of Silence", "Code of the Cactus", "Code of the Fearless", "Code of the Lawless", "Code of the Northwest", "Code of the Prairie", "Code of the Range", "Code of the Saddle", "Code of the Sea", "Code of the Secret Service", "Code of the Silver Sage", "Code of the Streets", "Code of the West", "Code of the Wilderness", "Code Two", "Cody of the Pony Express", "Coffee and Cigarettes", "Coffee Date", "Coffin Baby", "Coffy", "Cohen and Tate", "Cohen at Coney Island", "Cohen Saves the Flag", "Cohen's Dream", "Coins in the Fountain", "Cold Comes the Night", "Cold Creek Manor", "Cold Feet", "Cold Heaven", "Cold in July", "Cold Mountain", "Cold Souls", "Cold Steel", "Cold Turkey", "Coldblooded", "Cole Younger, Gunfighter", "Colette", "Collateral Damage", "Collateral", "Colleen of the Pines", "Colleen", "College Coach", "College Confidential", "College Days", "College Holiday", "College Humor", "College Love", "College Lovers", "College Rhythm", "College Road Trip", "College Scandal", "College Swing", "College", "Collegiate", "Collide", "Collision Course", "Colombiana", "Colonel Effingham's Raid", "Color Me Blood Red", "Color of a Brisk and Leaping Day", "Color of Night", "Color of the Cross", "Color Purple, The", "Colorado Ambush", "Colorado Pioneers", "Colorado Ranger", "Colorado Serenade", "Colorado Sundown", "Colorado Sunset", "Colorado Territory", "Colorado", "Colors", "Colossal", "Colossus: The Forbin Project", "Colt .45", "Colt Comrades", "Column South", "Coma", "Comanche Station", "Comanche Territory", "Comanche", "Comandante", "Combat America", "Combat Shock", "Combat Squad", "Come Across", "Come Again Smith", "Come and Get It", "Come Back to the Five and Dime, Jimmy Dean, Jimmy Dean", "Come Back, Charleston Blue", "Come Back, Little Sheba", "Come Blow Your Horn", "Come Closer, Folks", "Come Early Morning", "Come Fill the Cup", "Come Fly with Me", "Come Live With Me", "Come Morning", "Come Next Spring", "Come On Danger!", "Come On Marines!", "Come on Over", "Come On, Cowboys", "Come On, Rangers", "Come Out Fighting", "Come Out of the Kitchen", "Come See the Paradise", "Come September", "Come Spy with Me", "Come to My House", "Come to the Stable", "Comeback", "Comedian", "Comes a Horseman", "Comes Midnight", "Comet Over Broadway", "Comet", "Comic Book Confidential", "Comin' Round the Mountain", "Coming Home", "Coming Out Party", "Coming Soon", "Coming Through", "Coming to America", "Command Decision", "Commandments", "Commando Cody: Sky Marshal of the Universe", "Commando", "Commandos Strike at Dawn", "Common Bonds", "Common Clay", "Common Law Cabin", "Common Property", "Common Threads: Stories from the Quilt", "Commotion on the Ocean", "Communication Breakdown", "Communion", "Companionate Marriage", "Company Business", "Company of Heroes", "Competition", "Compliance", "Compromise", "Compromised", "Compromising Positions", "Compulsion", "Computer Chess", "Comrade X", "Comrades", "Con Air", "Conan the Barbarian", "Conan the Destroyer", "Concussion", "Condemned to Live", "Condemned Women", "Condemned", "Condorman", "Conduct Unbecoming", "Conductor 1492", "Conductor 786", "Coneheads", "Coney Island at Night", "Coney Island", "Confession", "Confessions of a Co-Ed", "Confessions of a Dangerous Mind", "Confessions of a Nazi Spy", "Confessions of a Queen", "Confessions of a Shopaholic", "Confessions of a Sorority Girl", "Confessions of a Teenage Drama Queen", "Confessions of Boston Blackie", "Confidence Girl", "Confidence", "Confidential Agent", "Confidential", "Confidentially Connie", "Confirm or Deny", "Conflict", "Congo Bill", "Congo Crossing", "Congo Maisie", "Congo", "Connie and Carla", "Conquering the Woman", "Conquest of Cheyenne", "Conquest of Cochise", "Conquest of Space", "Conquest of the Planet of the Apes", "Conquest", "Conrack", "Consenting Adults", "Consolation Marriage", "Conspiracy Theory", "Conspiracy", "Constantine", "Contact", "Contagion", "Contemporary Color", "Contest", "Continental Divide", "Continental", "Contraband", "Contract on Cherry Street", "Contracted", "Control Room", "Control", "Conundrum: Secrets Among Friends", "Convention City", "Convention Girl", "Convention of Railroad Passengers", "Convict 13", "Convicted Woman", "Convicted", "Conviction", "Convicts 4", "Convict's Code", "Convoy", "Coogan's Bluff", "Cook Off!", "Cookie", "Cool and the Crazy", "Cool as Ice", "Cool Breeze", "Cool Hand Luke", "Cool Runnings", "Cool World", "Cooley High", "Coonskin aka Street Fight", "Cop and a Half", "Cop Hater", "Cop Land", "Cop Out", "Cop", "Copacabana", "Copkiller", "Copper Canyon", "Copper Sky", "Cops and Robbers", "Cops and Robbersons", "Cops", "Copycat", "Copying Beethoven", "Coquette", "Coraline", "Cordelia the Magnificent", "Corky of Gasoline Alley", "Corky Romano", "Corky", "Cornered", "Coronado", "Coroner Creek", "Corpse Bride", "Corpus Christi Bandits", "Corregidor", "Corridors of Blood", "Corrina, Corrina", "Corruption", "Corsair", "Corvette K-225", "Corvette Summer", "Cosmic Voyage", "Cosmo Jones, Crime Smasher", "Cosmopolitan", "Cotton Comes to Harlem", "Couchee Dance on the Midway", "Counsel for Crime", "Counsel for the Defense", "Counsellor at Law", "Count Five and Die", "Count the Hours", "Count the Votes", "Count Three and Pray", "Count Yorga, Vampire", "Count Your Blessings", "Count Your Change", "Countdown to Zero", "Countdown", "Counter-Attack", "Counter-Espionage", "Counterfeit Lady", "Counterfeit", "Counterplot", "Counterpoint", "Counterspy Meets Scotland Yard", "Country Fair", "Country Gentlemen", "Country Music Holiday", "Country Strong", "Country", "County Fair", "Coupe de Ville", "Couples Retreat", "Courage of Lassie", "Courage Under Fire", "Courage", "Courageous", "Court House Crooks", "Court Martial", "Courtin' Trouble", "Courtin' Wildcats", "Courting Across the Court", "Cousin Bette", "Cousins", "Coven", "Cover Girl", "Cover Me", "Cover Up", "Covered Wagon Days", "Covered Wagon Raid", "Covered Wagon Trails", "Covert Action", "Cow Country", "Cow Town", "Cowardice Court", "Cowboy and the Senorita", "Cowboy Blues", "Cowboy Canteen", "Cowboy Cavalier", "Cowboy Commandos", "Cowboy Counsellor", "Cowboy from Brooklyn", "Cowboy from Lonesome River", "Cowboy in Manhattan", "Cowboy in the Clouds", "Cowboy Serenade", "Cowboy", "Cowboys & Aliens", "Cowgirls 'n Angels", "Coyote Ugly", "CQ", "Crack in the Mirror", "Crack in the World", "Crack o' Dawn", "Crack Your Heels", "Cracked Nuts", "Crackers", "Cracking Up", "Crack-Up", "Cradle 2 the Grave", "Cradle Snatchers", "Cradle Song", "Cradle Will Rock", "Craig's Wife", "Crank", "Crank: High Voltage", "Crash and Burn", "Crash Course", "Crash Dive", "Crash Donovan", "Crash Goes the Hash", "Crash Landing", "Crash", "Crashin' Thru", "Crashing Hollywood", "Crashing Las Vegas", "Crashing Thru Danger", "Crashout", "Crave", "Crawlspace", "Crayola Kids Adventures", "Crazy as Hell", "Crazy Eyes", "Crazy Heart", "Crazy House", "Crazy in Alabama", "Crazy Joe", "Crazy Knights", "Crazy Love", "Crazy Mama", "Crazy on the Outside", "Crazy Over Horses", "Crazy People", "Crazy Rich Asians", "Crazy That Way", "Crazy, Stupid, Love.", "Crazy/Beautiful", "Crazylegs", "Creaking Stairs", "Creator", "Creature from Black Lake", "Creature from the Black Lagoon", "Creature from the Haunted Sea", "Creature with the Atom Brain", "Creature", "Creed II", "Creepozoids", "Creeps", "Creepshow 2", "Creepshow", "Crime Against Joe", "Crime and Punishment U.S.A.", "Crime and Punishment", "Crime by Night", "Crime Doctor", "Crime Doctor's Man Hunt", "Crime Doctor's Warning", "Crime in the Streets", "Crime of Passion", "Crime of the Century", "Crime Ring", "Crime School", "Crime Spree", "Crime Takes a Holiday", "Crime Wave", "Crime Without Passion", "Crime, Inc.", "Crimes and Misdemeanors", "Crimes of Passion", "Crimes of the Heart", "Crimewave", "Criminal Court", "Criminal Law", "Criminal Lawyer", "Criminal", "Criminals of the Air", "Crimson Peak", "Crimson Romance", "Crimson Tide", "Crinoline and Romance", "Cripple Creek", "Crisis", "Criss Cross", "CrissCross", "Critical Condition", "Critic's Choice", "Critters", "Crocodile Dundee in Los Angeles", "Crooked Alley", "Crooked Arrows", "Crooked House", "Crooked River", "Crooked Straight", "Crooklyn", "Crooks and Coronets", "Crooks Can't Win", "Crooner", "Cross Channel", "Cross Country Cruise", "Cross Creek", "Cross Fire", "Cross My Heart", "Cross of Iron", "Cross Streets", "Cross-Country Romance", "Crossed Clues", "Crossed Signals", "Crossed Swords", "Crossed Trails", "Crossed Wires", "Cross-Examination", "Crossfire", "Crossing Delancey", "Crossing Over", "Crossing the Bridge", "Crossover", "Crossroads", "Crosswinds", "Crossworlds", "Crowded Paradise", "Crowned and Dangerous", "Crude Impact", "Cruel Doubt", "Cruel Intentions 2", "Cruel Intentions", "Cruel World", "Cruel, Cruel Love", "Cruelty on the High Seas", "Cruise Cat", "Cruise of the Jasper B", "Cruisin' Down the River", "Cruising", "Crumb", "Cry \"Havoc\"", "Cry Danger", "Cry for Happy", "Cry Freedom", "Cry Murder", "Cry of Battle", "Cry of the City", "Cry of the Hunted", "Cry of the Werewolf", "Cry Terror!", "Cry Tough", "Cry Uncle!", "Cry Vengeance", "Cry Wilderness", "Cry Wolf", "Cry, the Beloved Country", "Cry_Wolf", "Cry-Baby", "Cthulhu", "Cuba", "Cuban Fireball", "Cuban Pete", "Cuban Rebel Girls", "Cue Ball Cat", "Cujo", "Cult of the Cobra", "Cupid Forecloses", "Cupid in a Dental Parlor", "Cupid's Fireman", "Cupid's Rustler", "Curdled", "Cure for Bashfulness", "Curious George", "Curley", "Curly Sue", "Curly Top", "Curlytop", "Curse of the Faceless Man", "Curse of the Puppet Master", "Curse of the Swamp Creature", "Curse of the Undead", "Cursed", "Curtain Call at Cactus Creek", "Curtain Call", "Curucu, Beast of the Amazon", "Custer of the West", "Customs Agent", "Cutie and the Boxer", "Cutter's Way", "Cutthroat Island", "Cutting Class", "Cyborg 2087", "Cyborg 3: The Recycler", "Cyborg Cop II", "Cyborg", "Cyclone Cavalier", "Cyclone Fury", "Cyclone Prairie Rangers", "Cyclone", "Cymbeline", "Cynara", "Cynthia", "Cypher", "Cyrano de Bergerac", "Cyrus", "Cytherea", "Czechoslovakia 1968", "D.A.R.Y.L.", "D.C. Cab", "D.E.B.S.", "D.O.A.", "D2: The Mighty Ducks", "D3: The Mighty Ducks", "Da Sweet Blood of Jesus", "Da", "Dad", "Daddies", "Daddy Day Camp", "Daddy Day Care", "Daddy Long Legs", "Daddy", "Daddy-Long-Legs", "Daddy-O", "Daddy's Dyin': Who's Got the Will?", "Daddy's Girl", "Daddy's Gone A-Hunting", "Daddy's Home 2", "Daddy's Home", "Daddy's Little Girls", "Daffy – The Commando", "Daffy Duck's Fantastic Island", "Daffy Duck's Quackbusters", "Daffy's Inn Trouble", "Daft Punk's Electroma", "Daisy Kenyon", "Daisy Miller", "Dakota Incident", "Dakota Lil", "Dakota", "Dalai Lama Renaissance", "Dallas Buyers Club", "Dallas", "Dallas: J.R. Returns", "Daltry Calhoun", "Damaged Goods", "Damaged Hearts", "Damaged Lives", "Dames Ahoy!", "Dames", "Damien: Omen II", "Damn Citizen", "Damn Yankees", "Damnation Alley", "Damsel", "Damsels in Distress", "Dan in Real Life", "Dance Charlie Dance", "Dance Flick", "Dance Girl Dance", "Dance Hall Racket", "Dance Hall", "Dance Madness", "Dance of Death", "Dance Party USA", "Dance Team", "Dance 'Til Dawn", "Dance with Me", "Dance with Me, Henry", "Dance, Fools, Dance", "Dance, Girl, Dance", "Dancer, Texas Pop. 81", "Dancers in the Dark", "Dancers", "Dances with Wolves", "Dancing at Lughnasa", "Dancing Cheat", "Dancing Co-Ed", "Dancing Feet", "Dancing in Manhattan", "Dancing in the Dark", "Dancing Lady", "Dancing Mothers", "Dancing on a Dime", "Dancing Pirate", "Dancing Romeo", "Dancing Sweeties", "Danger – Love at Work", "Danger Ahead", "Danger Flight", "Danger in the Pacific", "Danger Lights", "Danger on the Air", "Danger on Wheels", "Danger Patrol", "Danger Quest", "Danger Signal", "Danger Street", "Danger Valley", "Danger Woman", "Danger Zone", "Danger! Women at Work", "Danger, Go Slow", "Dangerous Beauty", "Dangerous Blondes", "Dangerous Business", "Dangerous Corner", "Dangerous Crossing", "Dangerous Crossroads", "Dangerous Curves", "Dangerous Fists", "Dangerous Game", "Dangerous Ground", "Dangerous Holiday", "Dangerous Hours", "Dangerous Innocence", "Dangerous Intrigue", "Dangerous Intruder", "Dangerous Liaisons", "Dangerous Millions", "Dangerous Minds", "Dangerous Mission", "Dangerous Money", "Dangerous Nan McGrew", "Dangerous Number", "Dangerous Paradise", "Dangerous Partners", "Dangerous Passage", "Dangerous to Know", "Dangerous Traffic", "Dangerous Venture", "Dangerous Waters", "Dangerous When Wet", "Dangerous Years", "Dangerous", "Dangerously Close", "Dangerously They Live", "Dangerously Yours", "Dangers of the Canadian Mounted", "Daniel and the Towers", "Daniel Boone", "Daniel Boone, Trail Blazer", "Daniel", "Danika", "Danny Boy", "Danny Collins", "Danny Roane: First Time Director", "Dante's Inferno", "Dante's Peak", "Darby O'Gill and the Little People", "Darby's Rangers", "Daredevil Drivers", "Daredevil Jack", "Daredevil", "Daredevils of the Clouds", "Daredevils of the West", "Daredevil's Reward", "Darfur Now", "Daring Chances", "Daring Danger", "Daring Daughters", "Daring Days", "Daring Game", "Daring Hearts", "Daring Love", "Daring Youth", "Dark Alibi", "Dark Blue", "Dark City", "Dark Command", "Dark Corners", "Dark Delusion", "Dark Hazard", "Dark Manhattan", "Dark Matter", "Dark Mountain", "Dark Passage", "Dark Purpose", "Dark Ride", "Dark Secrets", "Dark Shadows", "Dark Skies", "Dark Stairways", "Dark Star", "Dark Streets of Cairo", "Dark Streets", "Dark Tide", "Dark Tower", "Dark Victory", "Dark Water", "Dark Waters", "Darkening Sky", "Darker than Amber", "Darkest Hour", "Darkman", "Darkness Falls", "Darkness", "Darling Companion", "Darling Lili", "Darling, How Could You!", "Darwin Was Right", "Date and Switch", "Date Movie", "Date Night", "Date with an Angel", "Date with Death", "Dating Do's and Don'ts", "Daughter from Da Nang", "Daughter of Darkness", "Daughter of Don Q", "Daughter of Mine", "Daughter of Shanghai", "Daughter of the Dragon", "Daughter of the Jungle", "Daughter of the Tong", "Daughter of the West", "Daughters Courageous", "Daughters of Pleasure", "Daughters of the Dust", "Daughters of the Night", "Daughters of the Rich", "Daughters of Today", "Daughters Who Pay", "Dave Chappelle's Block Party", "Dave", "David and Bathsheba", "David and Lisa", "David Copperfield", "David Gray's Estate", "David Harding, Counterspy", "David Harum", "Davy Crockett and the River Pirates", "Davy Crockett, Indian Scout", "Davy Crockett, King of the Wild Frontier", "Dawn at Socorro", "Dawn of the Dead", "Dawn of the Planet of the Apes", "Dawn on the Great Divide", "Dawn", "Day Dreams", "Day Night Day Night", "Day of Reckoning", "Day of the Badman", "Day of the Dead", "Day of the Evil Gun", "Day of the Fight", "Day of the Outlaw", "Day of the Wolves", "Day on Fire", "Day One", "Day the World Ended", "Day Zero", "Daybreak", "Daybreakers", "Daydreams", "Daylight", "Days of Buffalo Bill", "Days of Glory", "Days of Heaven", "Days of Old Cheyenne", "Days of Thunder", "Days of Wine and Roses", "Day-Time Wife", "Daytime Wives", "Dazed and Confused", "D-Day the Sixth of June", "De Sade", "Dead & Breakfast", "Dead & Buried", "Dead Again", "Dead Awake", "Dead Bang", "Dead Before Dawn", "Dead Birds", "Dead Calm", "Dead End", "Dead Game", "Dead Heat on a Merry-Go-Round", "Dead Heat", "Dead Man Down", "Dead Man on Campus", "Dead Man Walking", "Dead Man", "Dead Man's Burden", "Dead Man's Curve", "Dead Man's Eyes", "Dead Man's Gold", "Dead Man's Gulch", "Dead Man's Island", "Dead Man's Trail", "Dead Men Don't Wear Plaid", "Dead Men Tell", "Dead Men Walk", "Dead of Winter", "Dead or Alive", "Dead Poets Society", "Dead Presidents", "Dead Reckoning", "Dead Ringer", "Dead Ringers", "Dead Silence", "Dead Solid Perfect", "Dead Stop", "Deadfall", "Deadline – U.S.A.", "Deadline at Dawn", "Deadline for Murder", "Deadline", "Deadly Blessing", "Deadly Dreams", "Deadly Duo", "Deadly Friend", "Deadly Hero", "Deadly Outbreak", "Deadly Target", "Deadpool 2", "Deadpool", "Deadwood Dick", "Deadwood Pass", "Deal of the Century", "Deal", "Dean", "Dear America: Letters Home from Vietnam", "Dear Brat", "Dear Brigitte", "Dear Diary", "Dear God", "Dear Heart", "Dear John", "Dear Ruth", "Dear White People", "Dear Wife", "Death and the Compass", "Death and the Maiden", "Death at a Funeral", "Death Becomes Her", "Death Bed: The Bed That Eats", "Death Before Dishonor", "Death Dimension", "Death Faces", "Death Flies East", "Death from a Distance", "Death Hunt", "Death in Small Doses", "Death in the Air", "Death Journey", "Death Machine", "Death Machines", "Death Mills", "Death of a Centerfold: The Dorothy Stratten Story", "Death of a Champion", "Death of a Gunfighter", "Death of a Salesman", "Death of a Scoundrel", "Death on the Diamond", "Death Race 2000", "Death Race 3: Inferno", "Death Race", "Death Rides the Plains", "Death Rides the Range", "Death Run", "Death Sentence", "Death Takes a Holiday", "Death to Smoochy", "Death Valley Gunfighter", "Death Valley Manhunt", "Death Valley Rangers", "Death Valley", "Death Warrant", "Death Watch", "Death Wish 3", "Death Wish 4: The Crackdown", "Death Wish II", "Death Wish V: The Face of Death", "Death Wish", "Deathsport", "Deathtrap", "Decameron Nights", "Deceived Slumming Party", "Deceived", "December 7th", "December 7th: The Movie", "Deception", "Deceptions", "Decision at Sundown", "Decision Before Dawn", "Deck the Halls", "Déclassé", "Decoding Annie Parker", "Deconstructing Harry", "Decoration Day", "Decoy", "Decoyed", "Deduce, You Say!", "Deep Blue Sea", "Deep Cover", "Deep Dark Canyon", "Deep Impact", "Deep in My Heart", "Deep Rising", "Deep Throat 2", "Deep Throat", "Deep Valley", "Deep Waters", "DeepStar Six", "Deepwater Horizon", "Deepwater", "Deerslayer", "Def by Temptation", "Def Jam's How to Be a Player", "Def-Con 4", "Defending Your Life", "Defenseless", "Defiance", "Definitely, Maybe", "Defying Destiny", "Defying Gravity", "Defying the Law", "Déjà Vu", "Delgo", "Delicious", "Delightfully Dangerous", "Delinquent Daughters", "Delirious", "Deliver Us from Eva", "Deliver Us from Evil", "Deliverance", "Delivering Milo", "De-Lovely", "Delta Farce", "Delta Force 2: The Colombian Connection", "Deluge", "Delusion", "Dementia 13", "Dementia", "Demetrius and the Gladiators", "Demolition High", "Demolition Man", "Demolition University", "Demolition", "Demon Island", "Demon Knight", "Demon of the Sea", "Demon Seed", "Den of Thieves", "Den", "Denial", "Denise Calls Up", "Dennis the Menace Strikes Again", "Dennis the Menace", "Denver and Rio Grande", "Deported", "Der Fuehrer's Face", "Derailed", "Derelict", "Desert Bloom", "Desert Blue", "Desert Desperadoes", "Desert Driven", "Desert Dust", "Desert Fury", "Desert Gold", "Desert Guns", "Desert Hearts", "Desert Hell", "Desert Legion", "Desert Nights", "Desert of Lost Men", "Desert Passage", "Desert Patrol", "Desert Phantom", "Desert Pursuit", "Desert Sands", "Desert Valley", "Desert Vigilante", "Deserted at the Altar", "Design for Living", "Design for Scandal", "Designing Woman", "Designs on Jerry", "Desire in the Dust", "Desire Me", "Desire Under the Elms", "Desire", "Désirée", "Desk Set", "Desperado", "Desperadoes of Dodge City", "Desperadoes of the West", "Desperadoes' Outpost", "Desperate Cargo", "Desperate Characters", "Desperate Hours", "Desperate Journey", "Desperate Living", "Desperate Measures", "Desperate Search", "Desperate", "Desperately Seeking Susan", "Despicable Me 2", "Despicable Me 3", "Despicable Me", "Destination Big House", "Destination Gobi", "Destination Inner Space", "Destination Meatball", "Destination Moon", "Destination Murder", "Destination Tokyo", "Destination Unknown", "Destination Wedding", "Destinies Fulfilled", "Destiny", "Destroyer", "Destry Rides Again", "Destry", "Detachment", "Detective Conan: The Time-Bombed Skyscraper", "Detective Kitty O'Day", "Detective Story", "Detectives", "Detention of the Dead", "Deterrence", "Detour", "Detroit 9000", "Detroit Rock City", "Detroit", "Deuce Bigalow: European Gigolo", "Deuce Bigalow: Male Gigolo", "Deuce High", "Deuces Wild", "Devil and the Deep", "Devil Bat's Daughter", "Devil Dogs of the Air", "Devil Doll", "Devil Girl", "Devil Goddess", "Devil in a Blue Dress", "Devil May Hare", "Devil McCare", "Devil on Deck", "Devil Riders", "Devil Ship", "Devil Tiger", "Devil", "Devil-May-Care", "Devil's Angels", "Devil's Canyon", "Devil's Cargo", "Devil's Doorway", "Devil's Due", "Devil's Island", "Devil's Knot", "Devil's Lottery", "Devil's Pond", "Devil's Squadron", "Devotion", "DeVoy's Revolving Ladder Act", "Diabolique", "Dial 1119", "Dial M for Murder", "Dial Red O", "Diamond Frontier", "Diamond Handcuffs", "Diamond Head", "Diamond Horseshoe", "Diamond Jim", "Diamond Safari", "Diamond Trail", "Diamonds", "Diane of the Green Van", "Diane", "Diary of a Camper", "Diary of a Mad Black Woman", "Diary of a Mad Housewife", "Diary of a Madman", "Diary of a Nudist", "Diary of a Wimpy Kid", "Diary of a Wimpy Kid: Dog Days", "Diary of a Wimpy Kid: Rodrick Rules", "Diary of a Wimpy Kid: The Long Haul", "Diary of the Dead", "Dick Tracy Meets Gruesome", "Dick Tracy vs Crime Inc", "Dick Tracy vs. Cueball", "Dick Tracy", "Dick Tracy's Dilemma", "Dick Tracy's G-Men", "Dick Turpin", "Dick Turpin's Ride", "Dick", "Dickie Roberts: Former Child Star", "Did You Hear About the Morgans?", "Did You Hear the One About the Traveling Saleslady?", "Die Hard 2", "Die Hard with a Vengeance", "Die Hard", "Die Laughing", "Die Screaming, Marianne", "Die, Monster, Die!", "Different Strokes", "Dig That Uranium", "Dig!", "Diggers", "Digging to China", "Diggstown", "Digimon: The Movie", "Dill Scallion", "Dillinger", "Dim Sum: A Little Bit of Heart", "Dimension 5", "Dimples", "Diner", "Ding Dong Williams", "Dinky", "Dinner at Eight", "Dinner for Schmucks", "Dino", "Dinosaur Island", "Dinosaur", "Dinosaurs! – A Fun-Filled Trip Back in Time!", "Dinosaurus!", "Diplomacy", "Diplomaniacs", "Diplomatic Courier", "Dirigible", "Dirty Country", "Dirty Dancing", "Dirty Dancing: Havana Nights", "Dirty Dingus Magee", "Dirty Girl", "Dirty Grandpa", "Dirty Harry", "Dirty Little Billy", "Dirty Love", "Dirty Mary, Crazy Larry", "Dirty Rotten Scoundrels", "Dirty Wars", "Dirty Work", "Disappearances", "Disaster Movie", "Disaster Zone: Volcano in New York", "Disaster", "Disbarred", "Disc Jockey", "Discarded Lovers", "Disclosure", "Disconnect", "Discontented Husbands", "Discovered Through an Opera Glass", "Disgraced!", "Dishonored Lady", "Dishonored", "Disobedience", "Disorderlies", "Disorderly Conduct", "Disorganized Crime", "Disputed Passage", "Disraeli", "Distant Drums", "Distant Thunder", "District 9", "Disturbia", "Disturbing Behavior", "Dive Bomber", "Divergent", "Divine Madness!", "Divine Secrets of the Ya-Ya Sisterhood", "Divorce American Style", "Divorce Among Friends", "Divorce and the Daughter", "Divorce Coupons", "Divorce in the Family", "Divorce Invitation", "Divorce", "Dixiana", "Dixie Chicks: Shut Up and Sing", "Dixie Dugan", "Dixie Dynamite", "Dixie Jamboree", "Dixie", "Dixieland Droopy", "Dizzy Dames", "Dizzy Detectives", "Dizzy Pilots", "Django Unchained", "Do and Dare", "Do or Die", "Do the Right Thing", "Do You Believe", "Do You Love Me", "Do Your Duty", "DOA: Dead or Alive", "Doc Hollywood", "Doc Savage: The Man of Bronze", "Doc", "Docks of New Orleans", "Docks of New York", "Docks of San Francisco", "Doctor Bull", "Doctor Detroit", "Doctor Dolittle", "Doctor Faustus", "Doctor X", "Doctor Zhivago", "Doctor, You've Got to Be Kidding!", "Doctors Don't Tell", "Doctors' Wives", "Dodge City Trail", "Dodge City", "DodgeBall: A True Underdog Story", "Dodsworth", "Does It End Right?", "Does It Pay?", "Dog Day Afternoon", "Dog Days", "Dog Pounded", "Dog Trouble", "Dogfight", "Dogma", "Dogs", "Dogtown and Z-Boys", "Dogtown", "Dolemite", "Doll Face", "Dollar Devils", "Dollar Down", "Dolls", "Dolores Claiborne", "Dolphin Tale 2", "Dolphin Tale", "Domestic Disturbance", "Domestic Meddlers", "Domestic Relations", "Domestic Troubles", "Dominick and Eugene", "Dominion: Prequel to the Exorcist", "Domino Kid", "Domino", "Don Dare Devil", "Don Daredevil Rides Again", "Don Jon", "Don Juan DeMarco", "Don Juan Quilligan", "Don Juan", "Don Q, Son of Zorro", "Don Quickshot of the Rio Grande", "Don Ricardo Returns", "Don Winslow of the Coast Guard", "Don Winslow of the Navy", "Donald Cried", "Donald Gets Drafted", "Donald in Mathmagic Land", "Dondi", "Donnie Brasco", "Donnie Darko", "Donovan's Brain", "Donovan's Reef", "Don's Fountain of Youth", "Don't Be a Menace to South Central While Drinking Your Juice in the Hood", "Don't Be a Sucker", "Don't Be Afraid of the Dark", "Don't Bet on Blondes", "Don't Bet on Love", "Don't Bet on Women", "Don't Bother to Knock", "Don't Breathe", "Don't Call It Love", "Don't Change the Subject", "Don't Change Your Husband", "Don't Come Knocking", "Don't Cry, It's Only Thunder", "Don't Doubt Your Husband", "Don't Drink the Water", "Don't Fence Me In", "Don't Gamble with Love", "Don't Gamble with Strangers", "Don't Get Personal", "Don't Give Up the Ship", "Don't Go in the Woods", "Don't Go Near the Water", "Don't Go to Sleep", "Don't Hang Up, Tough Guy!", "Don't Just Stand There!", "Don't Knock the Rock", "Don't Knock the Twist", "Dont Look Back", "Don't Look Back", "Don't Make Waves", "Don't Marry for Money", "Don't Marry", "Don't Play Us Cheap", "Don't Raise the Bridge, Lower the River", "Don't Say a Word", "Don't Shoot", "Don't Shove", "Don't Stop Believin': Everyman's Journey", "Don't Tell Mom the Babysitter's Dead", "Don't Tell the Wife", "Don't Throw That Knife", "Don't Turn 'Em Loose", "Don't Worry, He Won't Get Far on Foot", "Don't Write Letters", "Don't", "Doom", "Doomed at Sundown", "Doomed Battalion", "Doomed to Die", "Doomsday Gun", "Doomsday", "Dope", "Dopey Dicks", "Dora's Dunking Doughnuts", "Dorf Goes Auto Racing", "Dorf on Golf", "Dorf on the Diamond", "Dorf's Golf Bible", "Dorothy Vernon of Haddon Hall", "Double Adventure", "Double Alibi", "Double Cross Roads", "Double Cross", "Double Crossbones", "Double Crossers", "Double Danger", "Double Daring", "Double Deal", "Double Dealing", "Double Door", "Double Dragon", "Double Dynamite", "Double Exposure", "Double Harness", "Double Impact", "Double Indemnity", "Double Jeopardy", "Double Negative[citation needed]", "Double or Nothing", "Double Take", "Double Team", "Double Trouble", "Double Wedding", "Double-Fisted", "Doubling with Danger", "Doubt", "Doubting Thomas", "Dough and Dynamite", "Doughboys in Ireland", "Doughboys", "Doughnuts and Society", "Down a Dark Hall", "Down Among the Sheltering Palms", "Down and Dirty Duck", "Down and Out in Beverly Hills", "Down and Outing", "Down Argentine Way", "Down Beat Bear", "Down by Law", "Down Dakota Way", "Down in 'Arkansaw'", "Down in San Diego", "Down in the Delta", "Down Laredo Way", "Down Mexico Way", "Down Missouri Way", "Down on the Farm", "Down Periscope", "Down the Stretch", "Down Three Dark Streets", "Down to Earth", "Down to the Sea in Ships", "Down to the Sea", "Down to Their Last Yacht", "Down to You", "Down Twisted", "Down with Love", "Downhearted Duckling", "Downhill Racer", "Downsizing", "Downstairs", "Downtown", "Dr. Black, Mr. Hyde", "Dr. Broadway", "Dr. Caligari", "Dr. Christian Meets the Women", "Dr. Cyclops", "Dr. Dolittle 2", "Dr. Dolittle", "Dr. Ehrlich's Magic Bullet", "Dr. Giggles", "Dr. Gillespie's Criminal Case", "Dr. Gillespie's New Assistant", "Dr. Goldfoot and the Bikini Machine", "Dr. Jack", "Dr. Jekyll and Mr. Hyde", "Dr. Jekyll and Mr. Mouse", "Dr. Jekyll and Ms. Hyde", "Dr. Kildare's Crisis", "Dr. Kildare's Victory", "Dr. Kildare's Wedding Day", "Dr. Monica", "Dr. Otto and the Riddle of the Gloom Beam", "Dr. Phibes Rises Again", "Dr. Renault's Secret", "Dr. Rhythm", "Dr. Seuss' The Lorax", "Dr. Socrates", "Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb", "Dr. T & the Women", "Dracula 2000", "Dracula Reborn", "Dracula Untold", "Dracula vs. Frankenstein", "Dracula", "Dracula: Dead and Loving It", "Dracula: The Dark Prince", "Dracula's Daughter", "Draegerman Courage", "Draft Day", "Draftee Daffy", "Drag Me to Hell", "Drag", "Dragnet", "Dragon Eyes", "Dragon Seed", "Dragon: The Bruce Lee Story", "Dragonball Evolution", "Dragonfly Squadron", "Dragonfly", "Dragonheart", "Dragon's Gold", "Dragonslayer", "Dragonwyck", "Dragoon Wells Massacre", "Dragstrip Riot", "Dramatic School", "Drango", "Drawing Flies", "Drawing the Line", "Dream a Little Dream 2", "Dream a Little Dream", "Dream for an Insomniac", "Dream Girl", "Dream House", "Dream Lover", "Dream Machine", "Dream of a Rarebit Fiend", "Dream of Love", "Dream Street", "Dream Wife", "Dream with the Fishes", "Dreamboat", "Dreamcatcher", "Dreamer", "Dreamgirls", "Dreaming Out Loud", "Dreamland", "Dreams That Money Can Buy", "Dreamscape", "Dressed to Kill", "Dressed to Thrill", "Drift Fence", "Driftin' River", "Driftin' Sands", "Driftin' Thru", "Drifting Along", "Drifting Souls", "Drifting", "Driftwood", "Drillbit Taylor", "Drinking Buddies", "Drip-Along Daffy", "Drive a Crooked Road", "Drive Angry", "Drive", "Drive, He Said", "Drive-In Massacre", "Driven", "Driving Miss Daisy", "Drop Dead Darling", "Drop Dead Fred", "Drop Dead Gorgeous", "Drop Zone", "Drowning Mona", "Drugstore Cowboy", "Drum Beat", "Drum", "Drumline", "Drums Across the River", "Drums Along the Mohawk", "Drums in the Deep South", "Drums of Africa", "Drums of Fate", "Drums of Love", "Drums of Tahiti", "Drums of the Desert", "Drusilla with a Million", "Dry Martini", "Du Barry, Woman of Passion", "DuBarry Was a Lady", "Duchess of Idaho", "Duck and Cover", "Duck Dodgers", "Duck Soup", "Duck! Rabbit, Duck!", "Duck", "Ducking the Devil", "DuckTales the Movie: Treasure of the Lost Lamp", "Dude Cowboy", "Dude Ranch", "Dude, Where's My Car?", "Dudley Do-Right", "Due Date", "Duel at Apache Wells", "Duel at Diablo", "Duel in the Sun", "Duel on the Mississippi", "Duel", "Duet for One", "Duets", "Duffy of San Quentin", "Duffy's Tavern", "Duke of Chicago", "Dulcie's Adventure", "Dulcy", "Duma", "Dumb and Dumber To", "Dumb and Dumber", "Dumb and Dumberer: When Harry Met Lloyd", "Dumb Luck", "Dumb Patrol", "Dumbbells in Ermine", "Dumbbells", "Dumbo", "Dune", "Dungeonmaster, The", "Dungeons & Dragons", "Dunked in the Deep", "Dunkirk", "Dunston Checks In", "Duped", "Duplex", "Duplicity", "Durand of the Bad Lands", "Durango Valley Raiders", "Dusk to Dawn", "Dust Be My Destiny", "Dust Flower", "Dutch", "Dutiful But Dumb", "Dying Young", "Dylan Dog: Dead of Night", "Dynamite Chicken", "Dynamite Dan", "Dynamite Pass", "Dynamite Smith", "Dynamite", "Dysfunctional Friends", "Dysfunktional Family", "E.T. the Extra-Terrestrial", "Each Dawn I Die", "Eadie Was a Lady", "Eagle Eye", "Eagle Squadron", "Earl Carroll Sketchbook", "Earl Carroll Vanities", "Early to Bed", "Early to Wed", "Earth Girls Are Easy", "Earth to Echo", "Earth vs. the Flying Saucers", "Earth vs. the Spider", "Earthbound", "Earthquake", "Earthworm Tractors", "East Is West", "East Lynne", "East of Borneo", "East of Broadway", "East of Eden", "East of Fifth Avenue", "East of Java", "East of Suez", "East of Sumatra", "East of the River", "East Palace, West Palace", "East Side - West Side", "East Side Kids", "East Side Story", "East Side, West Side", "Easter Parade", "Easter Yeggs", "Eastern Promises", "Eastward Ho!", "Easy A", "Easy Come, Easy Go", "Easy Listening", "Easy Living", "Easy Money", "Easy Rider", "Easy Street", "Easy to Look at", "Easy to Love", "Easy to Make Money", "Easy to Take", "Easy to Wed", "Eat a Bowl of Tea", "Eat My Dust", "Eat Pray Love", "Eat", "Eaten Alive", "Eating Out 5: The Open Weekend", "Eating Raoul", "Ebb Tide", "Ebbie", "Echelon Conspiracy", "Echo Park", "Ed Gein: The Butcher of Plainfield", "Ed Wood", "Ed", "Eddie and the Cruisers II: Eddie Lives!", "Eddie and the Cruisers", "Eddie Macon's Run", "Eddie Murphy Raw", "Eddie the Eagle", "Eddie", "Eden and Return", "Eden", "Edgar Allan Poe", "Edge of Darkness", "Edge of Doom", "Edge of Eternity", "Edge of Fury", "Edge of Hell", "Edge of Honor", "Edge of Sanity", "Edge of Seventeen", "Edge of the City", "Edge of Tomorrow", "Edison, the Man", "Ed's Next Move", "EDtv", "Educating Father", "Education for Death", "Edward Scissorhands", "Edward, My Son", "Eggshells", "Egyptian Fakir with Dancing Monkey", "Eight Bells", "Eight Below", "Eight Crazy Nights", "Eight Days a Week", "Eight Girls in a Boat", "Eight Iron Men", "Eight Legged Freaks", "Eight Men Out", "Eight on the Lam", "Eighteen and Anxious", "Eighth Grade", "El Alamein", "El Cid", "El codigo penal", "El Condor", "El Dorado Pass", "El Dorado", "El Impostor", "El Norte", "El Paso Stampede", "El Paso", "Election", "Electra Glide in Blue", "Electric Dreams", "Electrocuting an Elephant", "Elegy", "Elektra", "Eleni", "Elephant Stampede", "Elephant Walk", "Elephant", "Elevated", "Elevator", "Elf", "Elf-Man", "Elinor Norton", "Elizabethtown", "Ella Cinders", "Ella Enchanted", "Ellis Island", "Elmer and Elsie", "Elmer Gantry", "Elmer, the Great", "Elmer's Candid Camera", "Elmer's Pet Rabbit", "Elope If You Must", "Elopement", "Elsa & Fred", "Elvira, Mistress of the Dark", "Elvira's Haunted Hills", "Elvis & Nixon", "Elvis and Anabelle", "Elvis and Me", "Elvis Meets Nixon", "Elvis on Tour", "Elvis: That's the Way It Is", "Elysium", "Embarrassing Moments", "Embraceable You", "Embryo", "Emerald Forest, The", "Emergency Call", "Emergency Hospital", "Emergency Wedding", "Emma", "Emmanuelle in Space", "Emperor of the North", "Emperor", "Empire of the Ants", "Empire of the Sun", "Empire Records", "Empire State", "Empire", "Employee of the Month", "Employees' Entrance", "Empty Hands", "Empty Hearts", "Empty Holsters", "Empty Saddles", "Enchanted April", "Enchanted Island", "Enchanted", "Enchantment", "Encino Man", "Encounter Point", "Encounter with the Unknown", "End Game", "End of Days", "End of the Line", "End of the Road", "End of the Spear", "End of the Trail", "End of Watch", "Endangered Species", "Ender's Game", "Endless Love", "Endless Night", "Enemies of Society", "Enemies of Women", "Enemies, a Love Story", "Enemy Agents Meet Ellery Queen", "Enemy at the Gates", "Enemy Mine", "Enemy of the Law", "Enemy of the State", "Enemy of Women", "Enemy Territory", "Energetic Eva", "English Barnyard Scene", "Enlighten Thy Daughter", "Enoch Arden", "Enough Said", "Enough", "Enron: The Smartest Guys in the Room", "Ensign Pulver", "Enter Arsène Lupin", "Enter Laughing", "Enter Madame", "Enter the Dangerous Mind", "Enter the Dragon", "Enter the Ninja", "Entertaining Angels: The Dorothy Day Story", "Enticement", "Entourage", "Entrapment", "Entropy", "Envy", "Epic Movie", "Epic", "Equalizer 2000", "Equilibrium", "Equinox", "Equus", "Eragon", "Eran Trece", "Erased", "Eraser", "Eraserhead", "Erin Brockovich", "Ernest Goes to Africa", "Ernest Goes to Camp", "Ernest Goes to Jail", "Ernest Rides Again", "Ernest Saves Christmas", "Ernest Scared Stupid", "Escapade in Japan", "Escapade", "Escape by Night", "Escape from Alcatraz", "Escape from Crime", "Escape from Devil's Island", "Escape from East Berlin", "Escape from Fort Bravo", "Escape from Hell", "Escape from Hong Kong", "Escape from L.A.", "Escape from New York", "Escape from Planet Earth", "Escape from Red Rock", "Escape from San Quentin", "Escape from Sing Sing", "Escape from Sobibor", "Escape from the Planet of the Apes", "Escape from Zahrain", "Escape in the Desert", "Escape in the Fog", "Escape Me Never", "Escape Plan", "Escape Route", "Escape to Burma", "Escape to Paradise", "Escape to Victory", "Escape to Witch Mountain", "Escape", "Escort West", "Eskimo", "Espionage Agent", "Espionage", "Esther and the King", "Eternal Love", "Eternal Sunshine of the Spotless Mind", "Eternally Yours", "Ethan Frome", "Eureka", "Europa Report", "EuroTrip", "Evan Almighty", "Evangeline", "Eve in Exile", "Eve Knew Her Apples", "Eve of Destruction", "Evel Knievel", "Evelyn Prentice", "Evelyn", "Even Cowgirls Get the Blues", "Evening", "Evenings for Sale", "Event Horizon", "Ever After", "Ever Since Eve", "Ever Since Venus", "Everest", "Everly", "Every Day Is Sunshine When the Heart Beats True", "Every Day", "Every Day's a Holiday", "Every Girl Should Be Married", "Every Little Step", "Every Man's Wife", "Every Night at Eight", "Every Saturday Night", "Every Time We Say Goodbye", "Every Which Way but Loose", "Every Woman's Problem", "Everybody Does It", "Everybody Sing", "Everybody Wants Some!!", "Everybody Wins", "Everybody's Acting", "Everybody's All-American", "Everybody's Dancin'", "Everybody's Doing It", "Everybody's Fine", "Everybody's Hobby", "Everybody's Old Man", "Everyone Says I Love You", "Everyone's Hero", "Everything but the Truth", "Everything Happens at Night", "Everything I Have Is Yours", "Everything Is Illuminated", "Everything Must Go", "Everything You Always Wanted to Know About Sex* (*But Were Afraid to Ask)", "Everything, Everything", "Everything's Ducky", "Everything's Rosie", "Everywoman", "Eve's Bayou", "Eve's Leaves", "Eve's Lover", "Eve's Secret", "Evidence", "Evil Alien Conquerors", "Evil Bong", "Evil Dead 3: Army of Darkness", "Evil Dead II", "Evil Dead", "Evil Head", "Evita", "Evolution", "Evolver", "Ewoks: The Battle for Endor", "Ex Machina", "Ex-Bad Boy", "Excalibur", "Excess Baggage", "Excessive Force", "Ex-Champ", "Exchange of Wives", "Excision", "Excitement", "Exclusive Rights", "Exclusive Story", "Exclusive", "Excuse Me", "Excuse My Dust", "Executive Action", "Executive Decision", "Executive Suite", "Ex-Flame", "Exile Express", "Exiled to Shanghai", "eXistenZ", "Exit Strategy", "Exit to Eden", "Exit Wounds", "Ex-Lady", "Exodus", "Exodus: Gods and Kings", "Exorcist II: The Heretic", "Exorcist: The Beginning", "Expelled: No Intelligence Allowed", "Expensive Husbands", "Expensive Women", "Experience", "Experiment Alcatraz", "Experiment in Terror", "Experiment Perilous", "Experimental Marriage", "Expiration Date", "Exploratorium", "Explorers", "Exposed", "Exposure", "Extortion", "Extra! Extra!", "Extract", "Extracted", "Extraordinary Measures", "Extravagance", "Extreme Justice", "Extreme Measures", "Extreme Movie", "Extreme Prejudice", "Extremely Loud and Incredibly Close", "Extremities", "Eye for an Eye", "Eye of the Beholder", "Eye of the Cat", "Eye of the Needle", "Eye of the Tiger", "Eyes Beyond Seeing", "Eyes in the Night", "Eyes of an Angel", "Eyes of Laura Mars", "Eyes of Texas", "Eyes of the Forest", "Eyes of the Navy", "Eyes of the Soul", "Eyes of the Underworld", "Eyes of Youth", "Eyes on the Prize", "Eyes Wide Shut", "Eyewitness", "F for Fake", "F.I.S.T.", "F.T.W.", "F/X", "F/X2", "Face in the Rain", "Face in the Sky", "Face of a Fugitive", "Face of Fire", "Face of the Screaming Werewolf", "Face to Face", "Face Value", "Face/Off", "Faces in the Fog", "Faces of Death 5", "Faces of Death IV", "Faces of Death VI", "Faces", "Facing the Giants", "Factory Girl", "Fade to Black", "Fahrenheit 11/9", "Fahrenheit 9/11", "Fail-Safe", "Failure to Launch", "Faint Perfume", "Fair and Warmer", "Fair Game", "Fair Lady", "Fair Warning", "Fair Week", "Fair Wind to Java", "FairyTale: A True Story", "Faith", "Faithful in My Fashion", "Faithful", "Faithless", "Fakin' Da Funk", "Falcon and the Snowman, The", "Fall Guy", "Fall Time", "Fallen Angel", "Fallen", "Falling Down", "Falling from Grace", "Falling Hare", "Falling in Love", "False Colors", "False Evidence", "False Faces", "False Hare", "False Paradise", "False Pretenses", "Fame", "Family Business", "Family Honeymoon", "Family Plot", "Family Troubles", "Family Weekend", "Fancy Baggage", "Fancy Pants", "Fandango", "Fangs of Fate", "Fangs of the Arctic", "Fangs of the Wild", "Fanny Foley Herself", "Fanny Hill", "Fanny", "Fantasia 2000", "Fantasia", "Fantasies", "Fantastic Beasts: The Crimes of Grindelwald", "Fantastic Four", "Fantastic Four: Rise of the Silver Surfer", "Fantastic Mr. Fox", "Fantastic Voyage", "Far and Away", "Far from Heaven", "Far from Home: The Adventures of Yellow Dog", "Far from the Madding Crowd", "Far Marfa", "Far North", "Far Out Man", "Farewell to the King", "Farewell, My Lovely", "Fargo Express", "Fargo", "Fascinating Youth", "Fascination", "Fashion Madness", "Fashion Model", "Fashion Row", "Fashionable Fakers", "Fashions in Love", "Fashions of 1934", "Fast & Furious 6", "Fast & Furious", "Fast and Fearless", "Fast and Furious", "Fast and Loose", "Fast Break", "'Fast Bullets", "Fast Companions", "Fast Company", "Fast Five", "Fast Food Nation", "Fast Forward", "Fast Getaway II", "Fast Life", "Fast on the Draw", "Fast Times at Ridgemont High", "Fast Workers", "Fast, Cheap and Out of Control", "Faster", "Faster, Pussycat! Kill! Kill!", "Fast-Walking", "Fat Albert", "Fat City", "Fat Man and Little Boy", "Fatal Attraction", "Fatal Beauty", "Fatal Instinct", "Fatal Lady", "Fate Is the Hunter", "Fate's Decree", "Father Brown, Detective", "Father Figures", "Father Goose", "Father Is a Bachelor", "Father Is a Prince", "Father Makes Good", "Father of Invention", "Father of the Bride Part II", "Father of the Bride", "Father Takes the Air", "Father Was a Fullback", "Fathers' Day", "Father's Little Dividend", "Father's Son", "Father's Wild Game", "Fatso", "Fatty and Mabel Adrift", "Fatty's Magic Pants", "Fatty's Tintype Tangle", "Fatwa", "Favela Rising", "Favorite Son", "Fay Grim", "Fazil", "FBI Code 98", "FBI Girl", "Fear and Desire", "Fear and Loathing in Las Vegas", "Fear City", "Fear in the Night", "Fear of a Black Hat", "Fear of a Black Republican", "Fear Strikes Out", "Fear", "Fear-Bound", "FeardotCom", "Fearless Fagan", "Fearless", "Feast and Famine", "Federal Agent at Large", "Federal Agent", "Federal Agents vs. Underworld, Inc", "Federal Man", "Federal Man-Hunt", "Federal Operator 99", "Fedora", "Feds", "Feed the Kitty", "Feedin' the Kiddie", "Feeding Sea Lions", "Feel My Pulse", "Feel the Noise", "Feeling Minnesota", "Feet First", "Feet of Clay", "Felix on the Job", "Felon", "Female Jungle", "Female on the Beach", "Female Trouble", "Female", "Fence Riders", "Ferdinand", "Ferestadeh", "FernGully: The Last Rainforest", "Ferris Bueller's Day Off", "Ferry Cross the Mersey", "Festival Express", "Festival in Cannes", "Festival", "Fetishes", "Feud of the West", "Feudin' Fools", "Feudin' Rhythm", "Feudin', Fussin' and A-Fightin'", "Fever Lake", "Fever Pitch", "Fiddler on the Roof", "Fiddlers Three", "Field of Dreams", "Fierce Creatures", "Fiesta", "Fife Getting Instructions from Committee", "Fifi Blows Her Top", "Fifteen Wives", "Fifth Avenue Models", "Fifth Avenue", "Fifty Fathoms Deep", "Fifty Million Frenchmen", "Fifty Pills", "Fifty Roads to Town", "Fifty Shades Darker", "Fifty Shades Freed", "Fifty Shades of Black", "Fifty Shades of Grey", "Fifty/Fifty", "Fifty-Fifty", "Fig Leaves", "Fight Club", "Fight for Life", "Fight for Your Lady", "Fight for Your Life", "Fight It Out", "Fighter Attack", "Fighter Squadron", "Fighting Back", "Fighting Bill Carson", "Fighting Caballero", "Fighting Caravans", "Fighting Coast Guard", "Fighting Cressy", "Fighting Destiny", "Fighting Fate", "Fighting Father Dunne", "Fighting Fools", "Fighting for Gold", "Fighting for Justice", "Fighting Frontier", "Fighting Fury", "Fighting Hero", "Fighting Lady", "Fighting Lawman", "Fighting Luck", "Fighting Mad", "Fighting Man of the Plains", "Fighting Mustang", "Fighting Shadows", "Fighting the Flames", "Fighting to Live", "Fighting Trouble", "Fighting Valley", "Fighting Youth", "Fighting", "Figures Don't Lie", "File 113", "Fillmore", "Filly Brown", "Final Analysis", "Final Approach", "Final Destination 2", "Final Destination 3", "Final Destination 5", "Final Destination", "Final Edition", "Final Fantasy: The Spirits Within", "Final Justice", "Final Portrait", "Find Me Guilty", "Find the Blackmailer", "Find the Witness", "Find the Woman", "Find Your Man", "Finder's Fee", "Finders Keepers", "Finders Keepers, Lovers Weepers!", "Finding Dory", "Finding Forrester", "Finding Graceland", "Finding Nemo 3D", "Finding Nemo", "Finding Neverland", "Fine Clothes", "Fine Feathered Friend", "Fine Manners", "Finger Man", "Finger Prints", "Fingerprints Don't Lie", "Fingers at the Window", "Fingers", "Finian's Rainbow", "Finishing School", "Finishing the Game", "Finn and Hattie", "Fire and Ice", "Fire and Rain", "Fire Birds", "Fire Down Below", "Fire in the Sky", "Fire Maidens from Outer Space", "Fire on the Amazon", "Fire on the Mountain", "Fire with Fire", "Fire!", "Fireball 500", "Firebrands of Arizona", "Firecreek", "Fired Up!", "Fired Wife", "Fireflies in the Garden", "Firefox", "Firehouse Dog", "Firehouse", "Firelight", "Fireman Save My Child", "Fireman, Save My Child", "Firemen to the Rescue", "Fireproof", "Fires of Faith", "Firestarter", "Firestorm", "Firewalker", "Firewall", "Fireworks", "First Blood", "First Comes Courage", "First Daughter", "First Descent", "First Family", "First Kid", "First Kill", "First Knight", "First Lady", "First Love", "First Man into Space", "First Man", "First Men in the Moon", "First Monday in October", "First Offenders", "First Reformed", "First Snow", "First Sunday", "First Time Felon", "First to Fight", "First Yank into Tokyo", "Firstborn", "Fist Fight", "Fit for a King", "Fit to Be Tied", "Fitzwilly", "Five and Ten Cent Annie", "Five and Ten", "Five Came Back", "Five Corners", "Five Dances", "Five Days from Home", "Five Days One Summer", "Five Days to Live", "Five Easy Pieces", "Five Finger Exercise", "Five Gates to Hell", "Five Golden Hours", "Five Graves to Cairo", "Five Guns to Tombstone", "Five Little Peppers in Trouble", "Five Miles to Midnight", "Five Minutes to Live", "Five Minutes to Love", "Five of a Kind", "Five on the Black Hand Side", "Five Star Final", "Five Summer Stories", "Five Weeks in a Balloon", "Five Were Chosen", "Five", "Fixed Bayonets!", "Fixer Dugan", "Flagpole Jitters", "Flags of Our Fathers", "Flame and the Flesh", "Flame of Araby", "Flame of Barbary Coast", "Flame of Calcutta", "Flame of Stamboul", "Flame of the Argentine", "Flame of the Desert", "Flame of the Islands", "Flame of the West", "Flame of Youth", "Flames and Fortune", "Flames of Desire", "Flames", "Flaming Barriers", "Flaming Bullets", "Flaming Creatures", "Flaming Feather", "Flaming Frontier", "Flaming Fury", "Flaming Gold", "Flaming Guns", "Flaming Love", "Flaming Star", "Flaming Waters", "Flaming Youth", "Flamingo Road", "Flapper Wives", "Flareup", "Flash Gordon", "Flash of Genius", "Flash", "Flashback", "Flashdance", "Flashing Fangs", "Flashing Guns", "Flashing Spurs", "Flashing Steeds", "Flashpoint", "Flat Top", "Flatliners", "Flattery", "Flawless", "Flaxy Martin", "Fled", "Fleetwing", "Flesh and Blood", "Flesh and Bone", "Flesh and Bullets", "Flesh and Fantasy", "Flesh and Fury", "Flesh and the Devil", "Flesh and the Spur", "Flesh Feast", "Flesh Gordon Meets the Cosmic Cheerleaders", "Flesh Gordon", "Flesh", "Flesheater", "Fletch Lives", "Fletch", "Flicka", "Flicka: Country Pride", "Flight Angels", "Flight at Midnight", "Flight Command", "Flight for Freedom", "Flight from Ashiya", "Flight from Destiny", "Flight from Glory", "Flight into Nowhere", "Flight Lieutenant", "Flight Nurse", "Flight of the Intruder", "Flight of the Lost Balloon", "Flight of the Navigator", "Flight of the Phoenix", "Flight to Fame", "Flight to Fury", "Flight to Hong Kong", "Flight to Mars", "Flight to Nowhere", "Flight to Tangier", "Flight", "Flightplan", "Flipped", "Flipper", "Flipper's New Adventure", "Flirt", "Flirtation Walk", "Flirting with Danger", "Flirting with Disaster", "Flirting with Fate", "Flirting with Love", "Flirty Birdy", "Flock of Dodos", "Flood Tide", "Florence Foster Jenkins", "Florian", "Florida Special", "Flourish", "Flower Drum Song", "Flower of Night", "Flower", "Flowers and Trees", "Flowers in the Attic", "Flowing Gold", "Flubber", "Fluffy", "Fluke", "Flushed Away", "Fly Away Home", "Fly by Night", "Fly-Away Baby", "Flyboys", "Fly-by-Night", "Flying Blind", "Flying Devils", "Flying Disc Man from Mars", "Flying Down to Rio", "Flying High", "Flying Hoofs", "Flying Hostess", "Flying Leathernecks", "Flying Padre", "Flying Romeos", "Flying Saucer Daffy", "Flying Saucer Rock'n'Roll", "Flying Tigers", "Flying Wild", "Flywheel", "FM", "F-Man", "Focus", "Fog Bound", "Fog Island", "Fog Over Frisco", "Fog", "Folies Bergère de Paris", "Folks!", "Follies Girl", "Follow Me Home", "Follow Me Quietly", "Follow Me, Boys!", "Follow that Dream", "Follow That Woman", "Follow the Band", "Follow the Boys", "Follow the Crowd", "Follow the Fleet", "Follow the Leader", "Follow the Sun", "Follow Thru", "Follow Your Heart", "Followers", "Folly of Youth", "Food for Fighters", "Food, Inc.", "Foodfight!", "Fool Coverage", "Fool for Love", "Foolin' Around", "Foolish Wives", "Fools and Riches", "Fools and Their Money", "Fools First", "Fools for Luck", "Fools for Scandal", "Fool's Gold", "Fools Highway", "Fools in the Dark", "Fool's Luck", "Fools of Fashion", "Fools' Parade", "Fool's Paradise", "Fools Rush In", "Footlight Glamour", "Footlight Parade", "Footlight Serenade", "Footlight Varieties", "Footlights and Fools", "Footloose Widows", "Footloose", "Footsteps in the Dark", "Footsteps in the Night", "For a Good Time, Call...", "For a Woman's Honor", "For Alimony Only", "For All Mankind", "For Another Woman", "For Beauty's Sake", "For Better, For Worse", "For Big Stakes", "For Colored Girls", "For Crimin' Out Loud", "For Ellen", "For Heaven's Sake", "For Her Sake", "For His Son", "For Hope", "For Keeps?", "For Love of Ivy", "For Love of the Game", "For Love or Country: The Arturo Sandoval Story", "For Love or Money", "For Me and My Gal", "For Men Only", "For Pete's Sake", "For Richer or Poorer", "For Sale", "For Scent-imental Reasons", "For Singles Only", "For the Bible Tells Me So", "For the Boys", "For the Crown", "For the Defense", "For the First Time", "For the Flag", "For the Love of Benji", "For the Love of Mary", "For the Love of Mike", "For the Love of Money", "For the Love of Rusty", "For the Service", "For Those Who Think Young", "For Whom the Bell Tolls", "For Wives Only", "For You I Die", "For Your Consideration", "Forbidden Adventure", "Forbidden Cargo", "Forbidden Company", "Forbidden Fruit", "Forbidden Heaven", "Forbidden Hours", "Forbidden Island", "Forbidden Jungle", "Forbidden Paradise", "Forbidden Planet", "Forbidden Trail", "Forbidden Valley", "Forbidden Waters", "Forbidden World", "Forbidden Zone", "Forbidden", "Force of Arms", "Force of Evil", "Forced Entry", "Forced Landing", "Forced Vengeance", "Forces of Nature", "Fording a Stream", "Fore Play", "Foreign Agent", "Foreign Correspondent", "Foreign Devils", "Foreign Intrigue", "Forever After", "Forever Amber", "Forever and a Day", "Forever Female", "Forever My Girl", "Forever Strong", "Forever Young", "Forever Yours", "Forever", "Forever, Darling", "Forget About It", "Forget Me Not", "Forget Paris", "Forgetting Sarah Marshall", "Forgetting the Girl", "Forgive and Forget", "Forgotten Commandments", "Forgotten Faces", "Forgotten Girls", "Forgotten Women", "Forgotten", "Forlorn River", "Forrest Gump", "Forsaking All Others", "Fort Apache", "Fort Apache, the Bronx", "Fort Bowie", "Fort Defiance", "Fort Dobbs", "Fort Dodge Stampede", "Fort Massacre", "Fort Osage", "Fort Savage Raiders", "Fort Ti", "Fort Utah", "Fort Vengeance", "Fort Worth", "Fort Yuma", "Fortress", "Fortune's Mask", "Fortunes of Captain Blood", "Forty Guns", "Forty Little Mothers", "Forty Naughty Girls", "Forty Shades of Blue", "Forty Thieves", "Forty Winks", "Foul Play", "Four Boys and a Gun", "Four Brothers", "Four Christmases", "Four Daughters", "Four Days' Wonder", "Four Eyes and Six Guns", "Four Faces West", "Four Fast Guns", "Four Feathers", "Four Friends", "Four Frightened People", "Four Girls in Town", "Four Guns to the Border", "Four Horsemen of the Apocalypse", "Four Hours to Kill!", "Four Jills in a Jeep", "Four Men and a Prayer", "Four Mothers", "Four Rode Out", "Four Rooms", "Four Sons", "Four Walls", "Four Wives", "Four's a Crowd", "Fourteen Hours", "Fox Movietone Follies of 1929", "Fox Terror", "Foxcatcher", "Foxes", "Foxfire", "Foxy Brown", "Fracture", "Fragments", "Fraidy Cat", "Frailty", "Framed", "Frances Ha", "Frances", "Francis Covers the Big Town", "Francis Goes to the Races", "Francis Goes to West Point", "Francis in the Navy", "Francis Joins the WACS", "Francis of Assisi", "Francis", "Frank & Jesse", "Frank Film", "Frankenhooker", "Frankenstein 1970", "Frankenstein Meets the Wolf Man", "Frankenstein Unbound", "Frankenstein vs. the Creature from Blood Cove", "Frankenstein", "Frankenstein's Daughter", "Frankenweenie", "Frankfurters and Quail", "Frankie and Johnny", "Frantic", "Fraternity Vacation", "Fraulein", "Freak Show", "Freakonomics", "Freaks", "Freakshow", "Freaky Friday", "Freckles Comes Home", "Freckles", "Fred Claus", "Freddie Steps Out", "Freddy Got Fingered", "Freddy vs. Jason", "Freddy's Dead: The Final Nightmare", "Free and Easy", "Free Birds", "Free for All", "Free Jimmy", "Free Love", "Free State of Jones", "Free to Love", "Free Willy 2: The Adventure Home", "Free Willy 3: The Rescue", "Free Willy", "Free, Blonde and 21", "Free, White and 21", "Freebie and the Bean", "Freedom of the Press", "Freedom Writers", "Freedomland", "Freejack", "Freeway", "Freighters of Destiny", "French Connection II", "French Dressing", "French Heels", "French Kiss", "French Leave", "French Postcards", "French Rarebit", "Frenchie", "Frenchman's Creek", "Frequency", "Frequent Flyer", "Fresh from the Farm", "Fresh Hare", "Fresh Horses", "Fresh", "Freshman Love", "Freshman Year", "Freud: The Secret Passion", "Frida", "Friday After Next", "Friday Foster", "Friday Night Lights", "Friday the 13th Part 2", "Friday the 13th Part III", "Friday the 13th Part VI: Jason Lives", "Friday the 13th Part VII: The New Blood", "Friday the 13th Part VIII: Jason Takes Manhattan", "Friday the 13th", "Friday the 13th: A New Beginning", "Friday the 13th: The Final Chapter", "Friday", "Fried Green Tomatoes", "Friend Husband", "Friend Request", "Friendly Enemies", "Friendly Persuasion", "Friends & Lovers", "Friends and Lovers", "Friends of Mr. Sweeney", "Friends with Benefits", "Friends with Kids", "Friends with Money", "Friendship's Field", "Fright Night 2: New Blood", "Fright Night II", "Fright Night", "Fright", "Frigid Hare", "Frisco Jenny", "Frisco Kid", "Frisco Sal", "Frisco Tornado", "Frisco Waterfront", "Frisk", "Fritz the Cat", "Frogs", "From Beyond", "From Dusk Till Dawn", "From Hand to Mouth", "From Headquarters", "From Hell It Came", "From Hell to Heaven", "From Hell to Texas", "From Hell", "From Here to Eternity", "From Italy's Shores", "From Justin to Kelly", "From Laramie to London", "From Leadville to Aspen: A Hold-Up in the Rockies", "From Nine to Nine", "From Noon till Three", "From Nurse to Worse", "From Paris with Love", "From Prada to Nada", "From the Earth to the Moon", "From the Hip", "From the Journals of Jean Seberg", "From the Manger to the Cross", "From the Mixed-Up Files of Mrs. Basil E. Frankweiler", "From the Terrace", "From This Day Forward", "Front Page Woman", "Frontier Agent", "Frontier Badmen", "Frontier Feud", "Frontier Fugitives", "Frontier Fury", "Frontier Gal", "Frontier Gun", "Frontier Gunlaw", "Frontier Investigator", "Frontier Law", "Frontier Marshal", "Frontier Outlaws", "Frontier Outpost", "Frontier Rangers", "Frontier Revenge", "Frost/Nixon", "Frostbiter: Wrath of the Wendigo", "Frozen Assets", "Frozen Justice", "Frozen River", "Frozen", "Fruitvale Station", "Fugitive from Sonora", "Fugitive in the Sky", "Fugitive Lady", "Fugitive Lovers", "Fugitive of the Plains", "Fugitive Road", "Fugitives for a Night", "Fugitives", "Full Body Massage", "Full Fathom Five", "Full Frontal", "Full Grown Men", "Full Metal Jacket", "Full Moon High", "Full Moon in Blue Water", "Full of It", "Full of Life", "Full of Pep", "Full Speed", "Full Tilt Boogie", "Fun and Fancy Free", "Fun at a Children's Party", "Fun Down There", "Fun in Acapulco", "Fun on a Weekend", "Fun on Board a Fishing Smack", "Fun Size", "Fun with Dick and Jane", "Funny About Love", "Funny Bones", "Funny Face", "Funny Farm", "Funny Games", "Funny Girl", "Funny Lady", "Funny People", "Fur", "Furious 7", "Furlough", "Furry Vengeance", "Fury at Furnace Creek", "Fury at Gunsight Pass", "Fury at Showdown", "Fury in the Pacific", "Fury of the Congo", "Fury of the Jungle", "Fury", "Fuss and Feathers", "Future War", "Future World", "Futureworld", "Fuzz", "Fuzzy Settles Down", "G Men", "G. I. Honeymoon", "G.B.F.", "G.I. Blues", "G.I. Jane", "G.I. Joe: Retaliation", "G.I. Joe: The Movie", "G.I. Joe: The Rise of Cobra", "G.I. Wanna Home", "G.I. War Brides", "Gable and Lombard", "Gabriel Over the White House", "Gaby", "Gaily, Gaily", "Gal Young 'Un", "Galaxina", "Galaxy of Terror", "Galaxy Quest", "Gallant Bess", "Gallant Defender", "Gallant Journey", "Gallant Lady", "Gallant Sons", "Gallavants", "Galloping Hoofs", "Galloping On", "Galloping Romeo", "Galloping Thunder", "Galloping Vengeance", "Gallowwalker", "Gals, Incorporated", "Gambit", "Gambler's Choice", "Gambling Daughters", "Gambling House", "Gambling in Souls", "Gambling Lady", "Gambling on the High Seas", "Gambling Sex", "Gambling Ship", "Gambling with Souls", "Gambling Wives", "Gambling", "Game 6", "Game Night", "Game of Death", "Gamebox 1.0", "Gamer", "Games", "Gandhi", "Gang Bullets", "Gang Busters", "Gang in Blue", "Gang of Roses", "Gang Related", "Gang Smashers", "Gang War", "Gang War: Bangin' In Little Rock", "Gang Wars", "Gangs of Chicago", "Gangs of New York", "Gangs of the Waterfront", "Gangster Squad", "Gangster Story", "Gangster Wars", "Gangster's Boy", "Gangster's Den", "Gangsters of the Frontier", "Gangway for Tomorrow", "Ganja & Hess", "Garbo Talks", "Garden of Evil", "Garden of the Moon", "Garden Party", "Garden State", "Gardens of Stone", "Garfield: A Tail of Two Kitties", "Garfield: His 9 Lives", "Garfield: The Movie", "Garrison's Finish", "Gary Larson's Tales from the Far Side", "Gas Food Lodging", "Gas House Kids Go West", "Gas House Kids in Hollywood", "Gas House Kids", "Gas, Oil and Water", "Gaslight", "Gasoline Alley", "Gas-s-s-s", "Gates of Brass", "Gates of Heaven", "Gateway", "'Gator Bait II: Cajun Justice", "'Gator Bait", "Gator", "Gattaca", "Gaucho Serenade", "Gauguin - Voyage de Tahiti", "Gay and Devilish", "Gay Blades", "Gay Purr-ee", "Gay USA", "Gayby", "Geared to Go", "Geisha Girl", "Gemini", "Gene Autry and the Mounties", "General Crack", "General Spanky", "Generation Iron", "Generation X", "Genghis Khan", "Genius at Work", "Genocide", "Gentle Annie", "Gentle Giant", "Gentle Julia", "Gentleman Jim", "Gentleman Joe Palooka", "Gentleman Joe", "Gentleman Prefer Blondes", "Gentleman's Agreement", "Gentleman's Fate", "Gentlemen Are Born", "Gentlemen Broncos", "Gentlemen Marry Brunettes", "Gentlemen of Nerve", "Gentlemen Prefer Blondes", "Gentlemen with Guns", "Gents Without Cents", "Geography Club", "George of the Jungle", "George Wallace", "George Washington Jr.", "George Washington Slept Here", "George White's 1935 Scandals", "George White's Scandals", "Georgia Rule", "Georgia", "Geostorm", "Gerald Cranston's Lady", "Gerald McBoing-Boing", "Geraldine", "Geri's Game", "Geronimo", "Geronimo: An American Legend", "Gertie the Dinosaur", "Get a Clue", "Get Carter", "Get Crazy", "Get Going", "Get Hard", "Get Hep to Love", "Get Him to the Greek", "Get Low", "Get on the Bus", "Get on Up", "Get Out and Get Under", "Get Out", "Get Over It", "Get Rich or Die Tryin'", "Get Shorty", "Get Smart", "Get to Know Your Rabbit", "Get Yourself a College Girl", "Getaway", "Get-Rich-Quick Peggy", "Getting Acquainted", "Getting Away with Murder", "Getting 'Em Right", "Getting Even with Dad", "Getting Even", "Getting Gertie's Garter", "Getting Lucky", "Getting Mary Married", "Getting Played", "Getting Straight", "Gettysburg", "G-Force", "Ghost Catchers", "Ghost Chasers", "Ghost Dad", "Ghost Diver", "Ghost Dog: The Way of the Samurai", "Ghost Guns", "Ghost in the Machine", "Ghost in the Shell", "Ghost of Hidden Valley", "Ghost of the China Sea", "Ghost of Zorro", "Ghost Patrol", "Ghost Rider", "Ghost Rider: Spirit of Vengeance", "Ghost Ship", "Ghost Story", "Ghost Town Renegades", "Ghost Town Riders", "Ghost Town", "Ghost Valley", "Ghost World", "Ghost", "Ghostbusters II", "Ghostbusters", "Ghosts Can't Do It", "Ghosts of Abu Ghraib", "Ghosts of Girlfriends Past", "Ghosts of Mars", "Ghosts of Mississippi", "Ghosts on the Loose", "Ghosts", "Ghost-Town Gold", "Ghoulies", "GI Jesus", "Giant Mine", "Giant", "Gidget Goes Hawaiian", "Gidget Goes to Rome", "Gidget Grows Up", "Gidget", "Gift of Gab", "Gifted", "Gigantic", "Gigi", "Gigli", "Gigolette", "Gigot", "Gilda Live", "Gilda", "Gildersleeve on Broadway", "Gildersleeve's Bad Day", "Gildersleeve's Ghost", "Gimme an 'F'", "Gimme Shelter", "Gimme", "Ginger Ale Afternoon", "Ginger in the Morning", "Ginger", "Girl 6", "Girl Crazy", "Girl from Havana", "Girl Happy", "Girl in 313", "Girl in Danger", "Girl in Gold Boots", "Girl in Progress", "Girl in the Case", "Girl in the Woods", "Girl Missing", "Girl Most Likely", "Girl o' My Dreams", "Girl of the Night", "Girl of the Ozarks", "Girl of the Port", "Girl of the Rio", "Girl on the Barge", "Girl on the Run", "Girl on the Spot", "Girl Overboard", "Girl Rush", "Girl Shy", "Girl Trouble", "Girl Without a Room", "Girl, Interrupted", "Girlfight", "Girlfriend from Hell", "Girlfriends", "Girls About Town", "Girls Can Play", "Girls Demand Excitement", "Girls' Dormitory", "Girls Gone Wild", "Girls in Chains", "Girls in Prison", "Girls in the Night", "Girls Just Want to Have Fun", "Girls Men Forget", "Girls Nite Out", "Girls of the Big House", "Girls on Probation", "Girls on the Loose", "Girls' School", "Girls Town", "Girls Trip", "Girls! Girls! Girls!", "Girls", "Git Along Little Dogies", "Give a Girl a Break", "Give And Tyke", "Give 'em Hell, Harry!", "Give Me a Sailor", "Give Me Your Heart", "Give My Regards to Broadway", "Give Out, Sisters", "Give Us This Night", "Give Us Wings", "Giving Them Fits", "Glad Rag Doll", "Gladiator", "Glamour Boy", "Glamour for Sale", "Glamour Girl", "Glamour", "Glass Houses", "Glass Lips", "Glastonbury", "Gleam o'Dawn", "Gleaming the Cube", "Glee: The 3D Concert Movie", "Glen or Glenda", "Glengarry Glen Ross", "Glenister of the Mounted", "Glitter", "Gloria", "Glorifying the American Girl", "Glorious Betsy", "Glory Alley", "Glory Road", "Glory", "G-Men Never Forget", "G-Men vs. the Black Dragon", "Gnomeo and Juliet", "Go Chase Yourself", "Go Fish", "Go for Broke!", "Go for Sisters", "Go Into Your Dance", "Go Man Go", "Go Now", "Go Straight", "Go Tell the Spartans", "Go West", "Go West, Young Lady", "Go West, Young Man", "Go", "Goal!", "Goat Getter", "GoBots: Battle of the Rock Lords", "Gobs and Gals", "God Bless America", "God Didn't Give Me a Week's Notice", "God Gave Me Twenty Cents", "God Grew Tired of Us", "God Is My Co-Pilot", "God Is My Partner", "God Said \"Ha!\"", "God Told Me To", "God Wants Me To Forgive Them!?!", "Gods and Generals", "Gods and Monsters", "God's Country and the Woman", "God's Country", "God's Crucible", "God's Ears", "God's Gift to Women", "God's Little Acre", "God's Not Dead 2", "God's Not Dead", "God's Not Dead: A Light in Darkness", "Gods of Egypt", "God's Outlaw", "God's Pocket", "God's Step Children", "Godsend", "Godspell", "Godzilla", "Godzilla, King of the Monsters!", "Godzilla: Final Wars", "Gog", "Go-Get-'Em, Haines", "Goin' South", "Goin' to Town", "Going All the Way", "Going Ape!", "Going Berserk", "Going Crooked", "Going Highbrow", "Going Hollywood", "Going Home", "Going in Style", "Going My Way", "Going Overboard", "Going Places", "Going Steady", "Going Straight", "Going the Distance", "Going the Limit", "Going Up", "Going Wild", "Going! Going! Gone!", "Gold and the Girl", "Gold Diggers in Paris", "Gold Diggers of 1933", "Gold Diggers of 1935", "Gold Diggers of 1937", "Gold Diggers of Broadway", "Gold Diggers: The Secret of Bear Mountain", "Gold Dust Gertie", "Gold Fever", "Gold Heels", "Gold Is Where You Find It", "Gold Mine in the Sky", "Gold of the Seven Saints", "Gold Raiders", "Gold Rush Maisie", "Gold", "Golden Boy", "Golden Dawn", "Golden Dreams", "Golden Earrings", "Golden Exits", "Golden Gate", "Golden Girl", "Golden Gloves", "Golden Harvest", "Golden Hoofs", "Golden Needles", "Golden Rule Kate", "Golden Yeggs", "Goldengirl", "Goldie Gets Along", "Goldie", "Goldilocks and the Three Bares", "Goldstein", "Goldtown Ghost Riders", "Golf Widows", "Golfing", "Gone Baby Gone", "Gone Fishin'", "Gone Girl", "Gone in 60 Seconds", "Gone in the Night", "Gone with the West", "Gone with the Wind", "Gone", "Good and Naughty", "Good Bad Boy", "Good Boy!", "Good Burger", "Good Dame", "Good Day for a Hanging", "Good Deeds", "Good Girls Go to Paris", "Good Gracious, Annabelle", "Good Guys Wear Black", "Good Intentions", "Good Luck Chuck", "Good Luck, Mr. Yates", "Good Men and True", "Good Morning and... Goodbye!", "Good Morning Miss Dove", "Good Morning, Judge", "Good Morning, Vietnam", "Good Neighbor Sam", "Good Neighbor", "Good News", "Good Night, and Good Luck", "Good Sam", "Good Sport", "Good Time Charley", "Good Time", "Good Times", "Good to Go", "Good Will Hunting", "Goodbye Again", "Goodbye America", "Goodbye Broadway", "Goodbye Charlie", "Goodbye Christopher Robin", "Goodbye Girls", "Goodbye Love", "Goodbye Lover", "Goodbye Promise", "Goodbye To All That", "Goodbye, Columbus", "Goodbye, Mr. Chips", "Goodbye, My Fancy", "Good-bye, My Lady", "Goodbye, New York", "Goodfellas", "Goodnight, Sweetheart", "Goonies, The", "Goosebumps 2: Haunted Halloween", "Gordon's War", "Gordy", "Gorilla at Large", "Gorilla My Dreams", "Gorillas in the Mist: The Story of Dian Fossey", "Gorky Park", "Gorp", "Gosford Park", "Gossip", "Gotcha!", "Gotham", "Gothika", "Gotti", "Government Agents vs Phantom Legion", "Government Girl", "Goya's Ghosts", "Grace Is Gone", "Grace of My Heart", "Grace Unplugged", "Gracie", "Graduation Day", "Graffiti Bridge", "Graft", "Gran Torino", "Grand Canary", "Grand Canyon Trail", "Grand Canyon", "Grand Central Murder", "Grand Entry, Indian Congress", "Grand Exit", "Grand Hotel", "Grand Jury Secrets", "Grand Jury", "Grand Larceny", "Grand Old Girl", "Grand Prix", "Grand Slam", "Grand Theft Auto", "Grand Theft Parsons", "Grandad of Races", "Grandma's Boy", "Grandview, U.S.A.", "Granny Get Your Gun", "Grass", "Grateful Dead: Downhill from Here", "Grateful Dead: So Far", "Graustark", "Grave of the Fireflies", "Grave of the Vampire", "Gravity", "Gray Lady Down", "Grayeagle", "Gray's Anatomy", "Grease 2", "Grease", "Greased Lightning", "Great Balls of Fire!", "Great Catherine", "Great Day in the Morning", "Great Diamond Mystery", "Great Expectations", "Great God Gold", "Great Guns", "Great Guy", "Great Stagecoach Robbery", "Great While It Lasted", "Greater Than a Crown", "Greater Than Love", "Greater Than Marriage", "Greed", "Greedy", "Green Book", "Green Card", "Green Dolphin Street", "Green Eyes", "Green Fields", "Green Fire", "Green Grass of Wyoming", "Green Grass Widows", "Green Hell", "Green Lantern", "Green Light", "Green Mansions", "Green Room", "Green Street", "Green Zone", "Greenberg", "Greenwich Village", "Greetings from Tim Buckley", "Greetings", "Gremlins 2: The New Batch", "Gremlins", "Gretchen the Greenhorn", "Grey Gardens", "Gridiron Flash", "Gridiron Gang", "Gridlock'd", "Grief Street", "Grim Prairie Tales", "Grind", "Grindhouse", "Gringo", "Grissly's Millions", "Grit Wins", "Grit", "Grizzly Man", "Grizzly", "Groom Lake", "Gross Anatomy", "Grosse Pointe Blank", "Groundhog Day", "Grounds for Divorce", "Grounds for Marriage", "Grow Up, Tony Phillips", "Growing Up Smith", "Grown Ups 2", "Grown Ups", "Grudge Match", "Grumpier Old Men", "Grumpy Old Men", "Grumpy", "Guadalcanal Diary", "Guard That Girl", "Guardians of the Galaxy Vol. 2", "Guardians of the Galaxy", "Guardians of the Wild", "Guarding Tess", "Guerrilla Girl", "Guess Who", "Guess Who's Coming to Dinner", "Guest in the House", "Guest Wife", "Guilty as Hell", "Guilty as Sin", "Guilty by Suspicion", "Guilty Bystander", "Guilty Hands", "Guilty of Treason", "Guilty or Not Guilty", "Guilty Trails", "Guilty?", "Guinevere", "Gulliver's Travels", "Gumby: The Movie", "Gun Battle at Monterey", "Gun Belt", "Gun Brothers", "Gun Crazy", "Gun Duel in Durango", "Gun Fever", "Gun Fight", "Gun for a Coward", "Gun Fury", "Gun Glory", "Gun Justice", "Gun Law Justice", "Gun Law", "Gun Lords of Stirrup Basin", "Gun Packer", "Gun Runner", "Gun Shy", "Gun Smoke", "Gun Smugglers", "Gun Street", "Gun Talk", "Gun the Man Down", "Gun Town", "Gunfight at Comanche Creek", "Gunfight at the O.K. Corral", "Gunfight in Abilene", "Gunfighters of Abilene", "Gunfighters of the Northwest", "Gunfighters", "Gunfire at Indian Gap", "Gunfire", "Gung Ho!", "Gung Ho", "Gunga Din", "Gunman in the Streets", "Gunman's Code", "Gunman's Walk", "Gunmen from Laredo", "Gunmen of Abilene", "Gunmen", "Gunn", "Gunning for Justice", "Gunning for Vengeance", "Gunplay", "Gunpoint", "Guns a Poppin", "Guns and Guitars", "Guns Girls and Gangsters", "Guns in the Dark", "Guns in the Heather", "Guns of Diablo", "Guns of Hate", "Guns of the Law", "Guns of the Magnificent Seven", "Guns of the Pecos", "Guns of the Timberland", "Guns", "Gunsight Ridge", "Gunslinger", "Gunslingers", "Gunsmoke in Tucson", "Gunsmoke Mesa", "Gunsmoke Ranch", "Gunsmoke", "Gus", "Guys and Dolls", "Gymkata", "Gypsy Colt", "Gypsy Wildcat", "Gypsy", "H. M. Pulham, Esq.", "Hachiko: A Dog's Story", "Hackers", "Hacks", "Hag in a Black Leather Jacket", "Hail Caesar", "Hail the Conquering Hero", "Hail the Woman", "Hail to the Rangers", "Hail, Caesar!", "Hail, Hero!", "Hair Show", "Hair Trigger Stuff", "Hair", "Hairpins", "Hair-Raising Hare", "Hairspray", "Hair-Trigger Baxter", "Haldane of the Secret Service", "Half a Bride", "Half a Hero", "Half a Sinner", "Half Angel", "Half Baked", "Half Life", "Half Marriage", "Half Moon Street", "Half Nelson", "Half Past Dead", "Half Past Midnight", "Half Shot at Sunrise", "Half-A-Dollar-Bill", "Half-Wits Holiday", "Hall Pass", "Hallelujah!", "Hallelujah, I'm a Bum", "Halloween 4: The Return of Michael Myers", "Halloween 5: The Revenge of Michael Myers", "Halloween H20: 20 Years Later", "Halloween II", "Halloween III: Season of the Witch", "Halloween", "Halloween: Resurrection", "Halloween: The Curse of Michael Myers", "Halls of Montezuma", "Hallucination Generation", "Ham and Eggs at the Front", "Hamburger Hill", "Hamburger: The Motion Picture", "Hamlet 2", "Hamlet", "Hammer", "Hammerhead", "Hammersmith Is Out", "Hammett", "Hancock", "Handle with Care", "Hands Across the Border", "Hands Across the Table", "Hands of Stone", "Hands on a Hard Body: The Documentary", "Hands Up!", "Handy Andy", "Hang 'Em High", "Hangar 18", "Hangin' with the Homeboys", "Hanging Up", "Hangman's House", "Hangman's Knot", "Hangmen Also Die!", "Hangover Square", "Hangup", "Hanky Panky", "Hanna", "Hannah and Her Sisters", "Hannah Lee", "Hannah Montana & Miley Cyrus: Best of Both Worlds Concert", "Hannah Montana: The Movie", "Hanna's War", "Hannibal", "Hannie Caulder", "Hanover Street", "Hans Christian Andersen", "Hansel & Gretel Get Baked", "Hansel & Gretel", "Hansel & Gretel: Warriors of Witchcraft", "Hansel & Gretel: Witch Hunters", "Hansel and Gretel: An Opera Fantasy", "Happily Ever After", "Happily N'Ever After", "Happiness a la Mode", "Happiness Ahead", "Happiness C.O.D.", "Happiness", "Happy Anniversary", "Happy Birthday, Wanda June", "Happy Christmas", "Happy Days", "Happy Death Day", "Happy Endings", "Happy Feet Two", "Happy Feet", "Happy Gilmore", "Happy Go Ducky", "Happy Go Lucky", "Happy Hooligan April-Fooled", "Happy Hooligan Surprised", "Happy Land", "Happy Landing", "Happy Mother's Day, Love George", "Happy New Year", "Happy Tears", "Happy Though Married", "Happy, Texas", "Happythankyoumoreplease", "Harbor of Missing Men", "Harbor of Shanghai", "Hard Boiled Mahoney", "Hard Boiled", "Hard Contract", "Hard Country", "Hard Eight", "Hard Fists", "Hard Guy", "Hard Hombre", "Hard Promises", "Hard Rain", "Hard Rock Harrigan", "Hard Target", "Hard Ticket to Hawaii", "Hard Times", "Hard to Get", "Hard to Handle", "Hard to Hold", "Hard to Kill", "Hard, Fast and Beautiful", "Hardball", "Hardboiled Rose", "Hardcore Henry", "Hardcore", "Hard-Hittin' Hamilton", "Hardly Working", "Hardware", "Hare Brush", "Hare Force", "Hare Lift", "Hare Remover", "Hare Tonic", "Hare Trigger", "Haredevil Hare", "Harem Girl", "Harlan County, USA", "Harlem After Midnight", "Harlem Is Heaven", "Harlem Nights", "Harlem on the Prairie", "Harley Davidson and the Marlboro Man", "Harlow", "Harmony at Home", "Harmony Lane", "Harmony Trail", "Harold & Kumar Escape from Guantanamo Bay", "Harold & Kumar Go to White Castle", "Harold and Maude", "Harold Teen", "Harold", "Harper Valley PTA", "Harper", "Harpoon", "Harriet Craig", "Harriet the Spy", "Harrigan's Kid", "Harrison Bergeron", "Harry & Son", "Harry and the Hendersons", "Harry and Tonto", "Harry and Walter Go to New York", "Harry Black", "Harry in Your Pocket", "Harry Potter and the Chamber of Secrets", "Harry Potter and the Deathly Hallows: Part 1", "Harry Potter and the Deathly Hallows: Part 2", "Harry Potter and the Goblet of Fire", "Harry Potter and the Half-Blood Prince", "Harry Potter and the Order of the Phoenix", "Harry Potter and the Prisoner of Azkaban", "Harry Potter and the Sorcerer's Stone", "Harry Tracy", "Harry's War", "Harsh Times", "Hart's War", "Harum Scarum", "Harvard Man", "Harvard, Here I Come", "Harvest Melody", "Harvey", "Has Anybody Seen My Gal?", "Hat Check Girl", "Hat Check Honey", "Hat, Coat, and Glove", "Hatari!", "Hatch Up Your Troubles", "Hatchet III", "Hate", "Hated: GG Allin and the Murder Junkies", "Hateship, Loveship", "Hats Off", "Haunted Gold", "Haunted Harbor", "Haunted Honeymoon", "Haunted Ranch", "Haunted Spooks", "Haunted", "Haunting Shadows", "Hav Plenty", "Havana Rose", "Havana Widows", "Havana", "Have a Heart", "Have Rocket, Will Travel", "Having a Wild Weekend", "Having Wonderful Crime", "Having Wonderful Time", "Havoc in Heaven", "Havoc", "Hawaii Calls", "Hawaii", "Hawaiian Buckaroo", "Hawaiian Nights", "Hawmps!", "Hawthorne of the U.S.A.", "Hay Foot, Straw Foot", "Haywire", "Hazard", "He Comes Up Smiling", "He Couldn't Say No", "He Got Game", "He Hired the Boss", "He Knew Women", "He Knows You're Alone", "He Laughed Last", "He Leads, Others Follow", "He Learned About Women", "He Married His Wife", "He Ran All the Way", "He Said, She Said", "He Walked by Night", "He Was Her Man", "He Who Gets Slapped", "He Wrote a Book", "Head of State", "Head Office", "Head over Heels", "Head Winds", "Head", "Header", "Headin' East", "Headin' for God's Country", "Headin' for the Rio Grande", "Headin' South", "Headin' West", "Heading for Heaven", "Heading West", "Headline Hunters", "Headline Shooter", "Headlines", "Heads Up", "Health", "Heap Big Chief", "Hear and Now", "Hear 'Em Rave", "Hear Me Good", "Hear My Song", "Heart and Souls", "Heart Beat", "Heart Condition", "Heart Like a Wheel", "Heart o' the Hills", "Heart of a Siren", "Heart of Arizona", "Heart of Dixie", "Heart of Midnight", "Heart of the North", "Heart of the Rio Grande", "Heart of the Rockies", "Heart of the West", "Heart of Virginia", "Heart to Heart", "Heart Trouble", "Heartaches", "Heartbeat", "Heartbeeps", "Heartbreak Hotel", "Heartbreak Ridge", "Heartbreak", "Heartbreakers", "Heartburn", "Heartland", "Heartless Husbands", "Hearts Aflame", "Hearts and Fists", "Hearts and Masks", "Hearts and Minds", "Hearts and Spangles", "Hearts and Spurs", "Hearts Asleep", "Hearts Divided", "Hearts in Atlantis", "Hearts in Bondage", "Hearts in Dixie", "Hearts in Exile", "Hearts in Shadow", "Hearts of Darkness: A Filmmaker's Apocalypse", "Hearts of Fire", "Hearts of Humanity", "Hearts of Oak", "Hearts of the West", "Hearts of the World", "Hearts or Diamonds", "Heartsease", "Heat Lightning", "Heat", "Heathcliff: The Movie", "Heathers", "Heaven & Earth", "Heaven and Earth Magic", "Heaven Can Wait", "Heaven Help Us", "Heaven Is for Real", "Heaven Knows, Mr. Allison", "Heaven on Earth", "Heaven Only Knows", "Heaven or Vegas", "Heaven with a Barbed Wire Fence", "Heavenly Days", "Heaven's Gate", "Heaven's Prisoners", "Heavy Metal", "Heavy Traffic", "Heavy", "Heavyweights", "Hedwig and the Angry Inch", "Heedless Moths", "Heidi Fleiss: Hollywood Madam", "Heidi", "Heidi's Song", "Heights", "Heir to Trouble", "Heir-Loons", "Heist", "Held to Answer", "Held Up for the Makin's", "Held Up", "Heldorado", "Helen of Troy", "Helen's Babies", "Hell and High Water", "Hell Baby", "Hell Below Zero", "Hell Below", "Hell Bent for Leather", "Hell Bent for Love", "Hell Bent", "Hell Bound", "Hell Canyon Outlaws", "Hell Comes to Frogtown", "Hell Divers", "Hell Drivers", "Hell Fest", "Hell Fire Austin", "Hell Harbor", "Hell Hunters", "Hell in the Heavens", "Hell in the Pacific", "Hell Is for Heroes", "Hell Night", "Hell on Devil's Island", "Hell on Frisco Bay", "Hell on Wheels", "Hell Ship Mutiny", "Hell to Eternity", "Hell Up in Harlem", "Hell-Bent for Election", "Hell-Bent for Heaven", "Hellbound: Hellraiser II", "Hellboy II: The Golden Army", "Hellboy", "Hellcats of the Navy", "Helldorado", "Heller in Pink Tights", "Hellfighters", "Hellfire", "Hellgate", "Hellion", "Hello Again", "Hello Cheyenne", "Hello Down There", "Hello I Must Be Going", "Hello Mary Lou: Prom Night II", "Hello Trouble", "Hello, Dolly!", "Hello, Everybody!", "Hello, Frisco, Hello", "Hello, My Name Is Doris", "Hello, Sister!", "Hello, Sucker", "Hellraiser", "Hellraiser: Bloodline", "Hell-Roarin' Reform", "Hell's Angels '69", "Hells Angels on Wheels", "Hell's Angels", "Hell's Belles", "Hell's Crossroads", "Hell's Five Hours", "Hell's Four Hundred", "Hell's Half Acre", "Hell's Heroes", "Hell's Highroad", "Hell's Highway", "Hell's Hinges", "Hell's Hole", "Hell's Horizon", "Hell's House", "Hell's Island", "Hell's Kitchen", "Hell's Outpost", "Hell-Ship Morgan", "Hellzapoppin'", "Help! Help! Police!", "Help!", "Hemingway's Adventures of a Young Man", "Hemlock Hoax, the Detective", "Hemo the Magnificent", "Hemp for Victory", "Henry & June", "Henry Aldrich for President", "Henry Aldrich Gets Glamour", "Henry Aldrich Haunts a House", "Henry Aldrich Plays Cupid", "Henry Aldrich Swings It", "Henry Aldrich, Boy Scout", "Henry Aldrich's Little Secret", "Henry and Dizzy", "Henry Browne, Farmer", "Henry Fool", "Henry Goes Arizona", "Henry Poole Is Here", "Henry: Portrait of a Serial Killer", "Her Accidental Husband", "Her Adventurous Night", "Her Alibi", "Her Awakening", "Her Bodyguard", "Her Cardboard Lover", "Her Circus Man", "Her Code of Honor", "Her Fatal Millions", "Her First Beau", "Her First Mate", "Her First Romance", "Her Friend the Bandit", "Her Gilded Cage", "Her Highness and the Bellboy", "Her Honor, the Governor", "Her Husband Lies", "Her Husband's Affairs", "Her Husband's Friend", "Her Husband's Secret", "Her Husband's Secretary", "Her Husband's Trademark", "Her Jungle Love", "Her Kind of Man", "Her Kingdom of Dreams", "Her Love Story", "Her Lucky Night", "Her Mad Night", "Her Majesty, Love", "Her Man o' War", "Her Market Value", "Her Marriage Vow", "Her Master's Voice", "Her Minor Thing", "Her Night of Romance", "Her Only Way", "Her Own Free Will", "Her Own Money", "Her Primitive Man", "Her Private Life", "Her Purchase Price", "Her Reputation", "Her Right to Live", "Her Second Chance", "Her Sister from Paris", "Her Sister's Secret", "Her Summer Hero", "Her Summer", "Her Temporary Husband", "Her Twelve Men", "Her Wedding Night", "Her Wild Oat", "Her Younger Sister", "Her", "Herbie Goes Bananas", "Herbie Goes to Monte Carlo", "Herbie Rides Again", "Herbie: Fully Loaded", "Hercules in New York", "Hercules in the Haunted World", "Hercules", "Here Come the Co-Eds", "Here Come the Girls", "Here Come the Jets", "Here Come the Littles", "Here Come the Marines", "Here Come The Munsters", "Here Come the Nelsons", "Here Come the Waves", "Here Comes Carter", "Here Comes Cookie", "Here Comes Elmer", "Here Comes Happiness", "Here Comes Kelly", "Here Comes Mr. Jordan", "Here Comes the Band", "Here Comes the Boom", "Here Comes the Bride", "Here Comes the Groom", "Here Comes the Navy", "Here Comes Trouble", "Here I Am a Stranger", "Here Is Germany", "Here is My Heart", "Here on Earth", "Here We Go Again", "Hereafter", "Hereditary", "Here's to Romance", "Heritage of the Desert", "Herman U.S.A.", "Hero and the Terror", "Hero at Large", "Hero Wanted", "Hero", "Heroes and Husbands", "Heroes for Sale", "Heroes in Blue", "Heroes of the Alamo", "Heroes of the Hills", "Heroes of the Range", "Heroes of the Street", "Heroes", "Hero's Island", "Herr Meets Hare", "Hers to Hold", "He's a Cockeyed Wonder", "He's Just Not That Into You", "He's My Guy", "He's Way More Famous Than You", "Hesher", "Hester Street", "Hexed", "Hey Arnold!: The Movie", "Hey Boy! Hey Girl!", "Hey DJ", "Hey Good Lookin'", "Hey There!", "Hey There, It's Yogi Bear!", "Hey, Let's Twist!", "Hey, Rookie", "Hi Diddle Diddle", "Hi Nellie!", "Hi, Beautiful", "Hi, Buddy", "Hi, Gaucho!", "Hi, Good Lookin'!", "Hi, Mom!", "Hi, Neighbor", "Hiawatha", "Hiawatha's Rabbit Hunt", "Hic-cup Pup", "Hick", "Hickey & Boggs", "Hidalgo", "Hidden Danger", "Hidden Fear", "Hidden Gold", "Hidden Guns", "Hidden Loot", "Hidden Valley Outlaws", "Hide and Seek", "Hideaway Girl", "Hideaway", "Hi-De-Ho", "Hideout", "Hide-Out", "Hiding Out", "High and Dizzy", "High and Handsome", "High Anxiety", "High Art", "High Barbaree", "High Conquest", "High Crimes", "High Diving and Reverse", "High Diving Hare", "High Explosive", "High Fidelity", "High Flyers", "High Gear", "High Hell", "High Lonesome", "High Noon", "High on Crack Street", "High Plains Drifter", "High Powered", "High Pressure", "High Road to China", "High School Confidential", "High School Hero", "High School High", "High School Musical 3: Senior Year", "High School", "High Sierra", "High Society Blues", "High Society", "High Speed", "High Spirits", "High Stakes", "High Steaks", "High Steppers", "High Tension", "High Tide", "High Time", "High Wall", "High, Wide and Handsome", "Highball", "High-Ballin'", "Higher and Higher", "Higher Ground", "Higher Learning", "Higher Than a Kite", "Highlander II: The Quickening", "Highlander III: The Sorcerer", "Highlander", "Highlander: Endgame", "Highlander: The Source", "Highway 13", "Highway 301", "Highway 61", "Highway Dragnet", "Highway Patrol", "Highway to Hell", "Highway", "Highwaymen", "Highways by Night", "Hi-Jacked", "Hilda Crane", "Hillbilly Hare", "Hillbillys in a Haunted House", "Hills Have Eyes Part II, The", "Hills of Home", "Hills of Oklahoma", "Hills of Old Wyoming", "Hillsong: Let Hope Rise", "Hip-Hop: Beyond Beats and Rhymes", "Hippodrome Races, Dreamland, Coney Island", "Hips, Hips, Hooray!", "Hired Wife", "Hired!", "His Back Against the Wall", "His Brother's Ghost", "His Brother's Wife", "His Buddy's Wife", "His Butler's Sister", "His Captive Woman", "His Children's Children", "His Chum the Baron", "His Debt", "His Divorced Wife", "His Double Life", "His Exciting Night", "His Faith in Humanity", "His Family Tree", "His Father's Rifle", "His Favourite Pastime", "His First Command", "His First Flame", "His Forgotten Wife", "His Girl Friday", "His Glorious Night", "His Greatest Gamble", "His Hour", "His Jazz Bride", "His Kind of Woman", "His Lucky Day", "His Majesty O'Keefe", "His Majesty, Bunker Bean", "His Majesty, the American", "His Majesty, the Scarecrow of Oz", "His Master's Voice", "His Mouse Friday", "His Musical Career", "His Mystery Girl", "His New Job", "His New Profession", "His New York Wife", "His Night Out", "His Nose in the Book", "His Official Fiancée", "His Only Father", "His Only Son", "His Parisian Wife", "His People", "His Picture", "His Prehistoric Past", "His Private Life", "His Private Secretary", "His Robe of Honor", "His Royal Slyness", "His Secretary", "His Supreme Moment", "His Tiger Lady", "His Trust Fulfilled", "His Trysting Place", "His Ward's Love", "His Wedding Night", "His Wife's Friend", "His Wife's Husband", "His Wife's Mother", "His Woman", "History Is Made at Night", "History of the World: Part I", "Hit & Stay", "Hit and Run", "Hit Him Again", "Hit List", "Hit Man", "Hit of the Show", "Hit Parade of 1937", "Hit Parade of 1941", "Hit Parade of 1943", "Hit Parade of 1947", "Hit Parade of 1951", "Hit the Deck", "Hit the Hay", "Hit the Ice", "Hit the Road", "Hit the Saddle", "Hit!", "Hitch Hike Lady", "Hitch Hike to Heaven", "Hitch", "Hitchhike to Happiness", "Hitler", "Hitler's Children", "Hitler's Madman", "Hitman", "Hitting a New High", "Hi'ya, Chum", "Hi'ya, Sailor", "Hobgoblins", "Hockey Homicide", "Hocus Pocus", "Hoedown", "Hoffa", "Hogan's Alley", "Hold Back the Dawn", "Hold Back the Night", "Hold Back Tomorrow", "Hold 'Em Jail", "Hold 'Em Yale", "Hold Everything", "Hold Me Tight", "Hold Me While I'm Naked", "Hold On!", "Hold That Baby!", "Hold That Blonde", "Hold That Co-ed", "Hold That Ghost", "Hold That Girl", "Hold That Hypnotist", "Hold That Kiss", "Hold That Line", "Hold That Lion!", "Hold That Lion", "Hold That Woman!", "Hold the Lion, Please", "Hold the Press", "Hold Your Breath", "Hold Your Man", "Holes", "Holiday Affair", "Holiday for Lovers", "Holiday for Sinners", "Holiday in Havana", "Holiday in Mexico", "Holiday Inn", "Holiday Rhythm", "Holiday", "Hollow Man 2", "Hollow Man", "Hollow Point", "Hollow Triumph", "Hollywood and Vine", "Hollywood Barn Dance", "Hollywood Boulevard", "Hollywood Canteen", "Hollywood Cavalcade", "Hollywood Chainsaw Hookers", "Hollywood Cowboy", "Hollywood Ending", "Hollywood Homicide", "Hollywood Hotel", "Hollywood or Bust", "Hollywood Party", "Hollywood Round-Up", "Hollywood Shuffle", "Hollywood Speaks", "Hollywood Stadium Mystery", "Hollywood Story", "Hollywood Varieties", "Hollywood", "Hollywoodland", "Holmes and Watson", "Holocaust", "Holy Ghost People", "Holy Man", "Holy Matrimony", "Holy Rollers", "Hombre", "Home Again", "Home Alone 2: Lost in New York", "Home Alone 3", "Home Alone", "Home Before Dark", "Home Cured", "Home for the Holidays", "Home Fries", "Home from the Hill", "Home in Indiana", "Home in Oklahoma", "Home in San Antone", "Home in Wyomin'", "Home Movies", "Home of the Brave", "Home on the Range", "Home Room", "Home Run", "Home Struck", "Home Sweet Hell", "Home Sweet Homicide", "Home Town Story", "Home", "Home, Sweet Home", "Homeboy", "Homecoming", "Homefront", "Homegrown", "Homeless Hare", "Homer and Eddie", "Homesick", "Homesteaders of Paradise Valley", "Homeward Bound II: Lost in San Francisco", "Homeward Bound", "Homeward Bound: The Incredible Journey", "Homework", "Homicidal", "Homicide Bureau", "Homicide for Three", "Homicide", "Hondo", "Honest Hutch", "Honesty - The Best Policy", "Honey", "Honey, I Blew Up the Kid", "Honey, I Shrunk the Audience", "Honey, I Shrunk the Kids", "Honey, We Shrunk Ourselves", "Honeychile", "Honeydripper", "Honeymoon Ahead", "Honeymoon Flats", "Honeymoon for Three", "Honeymoon Hotel", "Honeymoon in Bali", "Honeymoon in Vegas", "Honeymoon Lane", "Honeymoon Limited", "Honeymoon Lodge", "Honeymoon", "Honeysuckle Rose", "Hong Kong Confidential", "Hong Kong Nights", "Hong Kong", "Honky Tonk Freeway", "Honky Tonk", "Honkytonk Man", "Honolulu Lu", "Honolulu", "Honor Among Lovers", "Honor Among Men", "Honor Bound", "Honor First", "Honor of the Family", "Hood of Horror", "Hoodlum Empire", "Hoodlum Priest", "Hoodlum", "Hoodman Blind", "Hoodoo Ann", "Hoodwinked Too! Hood vs. Evil", "Hoodwinked!", "Hoofs and Goofs", "Hook and Ladder", "Hook", "Hook, Line & Sinker", "Hook, Line and Sinker", "Hooked Bear", "Hoop Dreams", "Hooper", "Hoop-La", "Hooray for Love", "Hoosier Holiday", "Hoosier Schoolboy", "Hoosiers", "Hoot", "Hop Harrigan", "Hop Picking", "Hop", "Hop, Look and Listen", "Hopalong Cassidy Returns", "Hop-Along Cassidy", "Hopalong Rides Again", "Hope and Glory", "Hope Floats", "Hope Springs", "Hoppy Serves a Writ", "Hoppy's Holiday", "Hopscotch", "Horizons West", "Hornet's Nest", "Hornets’ Nest", "Horrible Bosses 2", "Horrible Bosses", "Horror Island", "Horrorween", "Horse Feathers", "Horse Play", "Horse Shoes", "Horsemen of the Sierras", "Horsemen", "Horsing Around", "Horton Hears a Who!", "Hospital Massacre", "Hostage", "Hostages", "Hostel", "Hostel: Part II", "Hostile Country", "Hostile Guns", "Hostiles", "Hot Blood", "Hot Car Girl", "Hot Cargo", "Hot Cars", "Hot Cars, Cold Facts", "Hot Cross Bunny", "Hot Curves", "Hot Dog... The Movie", "Hot for Paris", "Hot Lead and Cold Feet", "Hot Lead", "Hot Millions", "Hot Money", "Hot News", "Hot Off the Press", "Hot Pepper", "Hot Potato", "Hot Pursuit", "Hot Rhythm", "Hot Rod Gang", "Hot Rod Girl", "Hot Rod Rumble", "Hot Rod", "Hot Rods to Hell", "Hot Saturday", "Hot Shots! Part Deux", "Hot Shots!", "Hot Shots", "Hot Spell", "Hot Stuff", "Hot Summer Night", "Hot Summer Nights", "Hot Tip", "Hot to Trot", "Hot Tub Time Machine 2", "Hot Tub Time Machine", "Hot Water", "Hotel Artemis", "Hotel Berlin", "Hotel Continental", "Hotel for Dogs", "Hotel for Women", "Hotel Haywire", "Hotel Imperial", "Hotel Rwanda", "Hotel Transylvania 3: Summer Vacation", "Hotel Transylvania", "Hotel", "Houdini", "Hound-Dog Man", "Hour of the Gun", "Hours", "House Arrest", "House at the End of the Street", "House by the River", "House Calls", "House II: The Second Story", "House of 1000 Corpses", "House of a Thousand Dolls", "House of Bamboo", "House of Cards", "House of D", "House of Dark Shadows", "House of Dracula", "House of Dust", "House of Evil", "House of Frankenstein", "House of Games", "House of Horror", "House of Horrors", "House of Mystery", "House of Numbers", "House of Sand and Fog", "House of Secrets", "House of Strangers", "House of the Dead", "House of Usher", "House of Wax", "House of Women", "House on Haunted Hill", "House Party 2", "House Party 3", "House Party", "House", "Houseboat", "Houseguest", "Housekeeping", "Housesitter", "Housewife", "How Baxter Butted In", "How Brown Saw the Baseball Game", "How Do I Love Thee?", "How Do You Dooo?", "How Do You Know", "How Green Was My Valley", "How High Is Up?", "How High", "How I Got into College", "How Stella Got Her Groove Back", "How Sweet It Is!", "How the Grinch Stole Christmas", "How the Telephone Talks", "How the West Was Fun", "How the West Was Won", "How to Be a Latin Lover", "How to Be a Man", "How to Be Single", "How to Be Very, Very Popular", "How to Beat the High Co$t of Living", "How to Commit Marriage", "How to Deal", "How to Eat Fried Worms", "How to Educate a Wife", "How to Frame a Figg", "How to Handle Women", "How to Kill Your Neighbor's Dog", "How to Lose a Guy in 10 Days", "How to Lose Your Virginity", "How to Make a Fat Wife Out of Two Lean Ones", "How to Make a Monster", "How to Make an American Quilt", "How to Make the Cruelest Month", "How to Marry a Millionaire", "How to Murder Your Wife", "How to Play Baseball", "How to Play Golf", "How to Save a Marriage and Ruin Your Life", "How to Shut Up a Quarrelsome Wife", "How to Steal a Million", "How to Stuff a Wild Bikini", "How to Succeed in Business Without Really Trying", "How to Swim", "How to Talk to Girls at Parties", "How to Train Your Dragon 2", "How to Train Your Dragon", "How Women Love", "Howard the Duck", "Howling II", "Howling III", "Howling IV: The Original Nightmare", "Howling V: The Rebirth", "Howling: New Moon Rising", "How's About It", "Hubble 3D", "Huck and Tom", "Huckleberry Finn", "Hud", "Huddle", "Hudson Hawk", "Hudson's Bay", "Hugo Pool", "Hugo", "Hugs and Mugs", "Huk!", "Hula", "Hula-La-La", "Hulda from Holland", "Hulda of the Netherlands", "Hulk", "Hullabaloo", "Human Cargo", "Human Desire", "Human Experiments", "Human Hearts", "Human Highway", "Human Nature", "Human Wreckage", "Humanity", "Humanoids from the Deep", "Humor Me", "Humor Risk", "Humoresque", "Humorous Phases of Funny Faces", "Humpday", "Hungry Hearts", "Hunk", "Hunt the Man Down", "Hunted Men", "Hunter Killer", "Hurdle Jumping", "Hurdy-Gurdy Hare", "Hurlyburly", "Hurricane in Galveston", "Hurricane Island", "Hurricane on the Bayou", "Hurricane Season", "Hurricane Smith", "Hurricane Streets", "Hurricane", "Hurricane's Gal", "Hurry Sundown", "Hurry, Charlie, Hurry", "Husband Hunters", "Husbands and Lovers", "Husbands and Wives", "Husbands Beware", "Husband's Holiday", "Husbands", "Hush Money", "Hush", "Hush… Hush, Sweet Charlotte", "Hustle & Flow", "Hustle", "Hustler White", "Hyde and Go Tweet", "Hyde and Hare", "Hyenas", "Hymn of the Nations", "Hype!", "Hysterical Blindness", "I Accuse My Parents", "I Accuse!", "I Aim at the Stars", "I Am a Camera", "I Am a Criminal", "I Am a Fugitive from a Chain Gang", "I Am a Thief", "I Am an American Soldier", "I Am David", "I Am Divine", "I Am Legend", "I Am Michael", "I Am Not Your Negro", "I Am Number Four", "I Am Sam", "I Am Suzanne", "I Am the Law", "I Am the Man", "I Believed in You", "I Bury the Living", "I Can Do Bad All By Myself", "I Can Explain", "I Can Get It for You Wholesale", "I Can Hardly Wait", "I Can Only Imagine", "I Cheated the Law", "I Come in Peace", "I Confess", "I Conquer the Sea!", "I Could Go On Singing", "I Could Never Be Your Woman", "I Cover Big Town", "I Cover Chinatown", "I Cover the Underworld", "I Cover the War", "I Cover the Waterfront", "I Demand Payment", "I Did It", "I Died a Thousand Times", "I Do", "I Don't Feel at Home in This World Anymore", "I Don't Hate Las Vegas Anymore", "I Don't Know How She Does It", "I Dood It", "I Dream of Jeanie", "I Dream Too Much", "I Dreamed of Africa", "I Drink Your Blood", "I Escaped from the Gestapo", "I Feel Pretty", "I Found Stella Parish", "I Give My Love", "I Got the Hook Up", "I Hate Valentine's Day", "I Have Lived", "I Heart Huckabees", "I Hope They Serve Beer in Hell", "I Killed Geronimo", "I Killed That Man", "I Killed Wild Bill Hickok", "I Know That Voice", "I Know What You Did Last Summer", "I Know Who Killed Me", "I Like It Like That", "I Like It That Way", "I Like Your Nerve", "I Live for Love", "I Live My Life", "I Live on Danger", "I Love a Bandleader", "I Love a Mystery", "I Love a Soldier", "I Love Lucy", "I Love Melvin", "I Love My Wife", "I Love That Man", "I Love Trouble", "I Love You Again", "I Love You Phillip Morris", "I Love You to Death", "I Love You, Alice B. Toklas", "I Love You, Beth Cooper", "I Love You, I Love You Not", "I Love You, Man", "I Loved a Woman", "I Loved You Wednesday", "I Married a Communist", "I Married a Doctor", "I Married a Monster from Outer Space", "I Married a Strange Person!", "I Married a Witch", "I Married a Woman", "I Married an Angel", "I Melt with You", "I Met Him in Paris", "I Met My Love Again", "I Never Promised You a Rose Garden", "I Never Sang for My Father", "I Now Pronounce You Chuck and Larry", "I Origins", "I Ought to Be in Pictures", "I Promise to Pay", "I Remember Mama", "I Ring Doorbells", "I Saw the Light", "I Saw What You Did", "I Sell Anything", "I Shot Andy Warhol", "I Shot Billy the Kid", "I Shot Jesse James", "I Spit on Your Grave 2", "I Spit on Your Grave", "I Spy", "I Stand Accused", "I Still Know What You Did Last Summer", "I Stole a Million", "I Surrender Dear", "I Take This Oath", "I Take This Woman", "I Taw a Putty Tat", "I Thank a Fool", "I Think I Love My Wife", "I Used to Be Darker", "I Wake Up Screaming", "I Walk Alone", "I Walk the Line", "I Walked with a Zombie", "I Wanna Hold Your Hand", "I Want a Divorce", "I Want My Man", "I Want Someone to Eat Cheese With", "I Want to Live!", "I Want You", "I Wanted Wings", "I Was a Communist for the FBI", "I Was a Convict", "I Was a Male War Bride", "I Was a Shoplifter", "I Was a Teenage Frankenstein", "I Was a Teenage Werewolf", "I Was a Teenage Zombie", "I Was an Adventuress", "I Was an American Spy", "I Was Framed", "I Will, I Will... for Now", "I Wonder Who's Killing Her Now?", "I Wonder Who's Kissing Her Now", "I Won't Play", "I Wouldn't Be in Your Shoes", "I, Frankenstein", "I, Jane Doe", "I, Mobster", "I, Robot", "I, the Jury", "I, Tonya", "I.B. Dam and the Whole Dam Family", "I.Q.", "iBoy", "Ice Age", "Ice Age: Collision Course", "Ice Age: Continental Drift", "Ice Age: Dawn of the Dinosaurs", "Ice Age: The Meltdown", "Ice Castles", "Ice Cream Man", "Ice Palace", "Ice Princess", "Ice Station Zebra", "Ice-Boat Racing at Redbank, N.J.", "Icebound", "Icebreaker", "Ice-Capades", "Iceman", "I'd Climb the Highest Mountain", "I'd Give My Life", "I'd Rather Be Rich", "Idaho Kid", "Idaho Red", "Idaho Transfer", "Idaho", "Idea Girl", "Identity Crisis", "Identity Thief", "Identity Unknown", "Identity", "Idiocracy", "Idiot's Delight", "Idiots Deluxe", "Idle Hands", "Idle Roomers", "Idle Tongues", "Idlewild", "Idol of the Crowds", "If a Body Meets a Body", "If a Man Answers", "If Beale Street Could Talk", "If Ever I See You Again", "If I Had a Million", "If I Had My Way", "If I Marry Again", "If I Stay", "If I Were Free", "If I Were King", "If I Were Queen", "If I Were Single", "If I'm Lucky", "If It's Tuesday, This Must Be Belgium", "If Looks Could Kill", "If Lucy Fell", "If Marriage Fails", "If These Walls Could Talk", "If Winter Comes", "If You Believe It, It's So", "If You Could Only Cook", "If You Knew Susie", "Igby Goes Down", "Igor", "Ika Hands", "I'll Be Home For Christmas", "I'll Be Seeing You", "I'll Be Yours", "I'll Cry Tomorrow", "I'll Do Anything", "I'll Fix It", "I'll Get By", "I'll Get Him Yet", "I'll Get You for This", "I'll Give a Million", "I'll Give My Life", "I'll Love You Always", "I'll Never Forget You", "I'll Never Heil Again", "I'll Remember April", "I'll See You in My Dreams", "I'll Show You the Town", "I'll Take Romance", "I'll Take Sweden", "I'll Tell the World", "I'll Wait for You", "Illegal Entry", "Illegal Traffic", "Illegal", "Illegally Yours", "Illicit", "Illuminata", "Illusion of Love", "I'm a Man", "I'm Bout It", "I'm Dancing as Fast as I Can", "I'm Dangerous Tonight", "I'm from Arkansas", "I'm from Missouri", "I'm From the City", "I'm Gonna Git You Sucka", "I'm in Love with a Church Girl", "I'm No Angel", "I'm Nobody's Sweetheart Now", "I'm Not Ashamed", "I'm Not Rappaport", "I'm Not There", "I'm on My Way", "I'm Still Alive", "I'm Still Here", "Image of the Beast", "Images", "Imaginary Crimes", "Imaginary Heroes", "Imagine That", "Imagining Argentina", "Imar the Servitor", "Imitation General", "Imitation of Life", "Immediate Family", "Immortal Beloved", "Immortal Sergeant", "Immortals", "Impact", "Impasse", "Impatient Maiden", "Impersonation of Britt-Nelson Fight", "Impossible Catherine", "Impostor", "In & Out", "In a Lonely Place", "In a World...", "In Again, Out Again", "In Beaver Valley", "In Bruges", "In Caliente", "In Cold Blood", "In Country", "In Darkness", "In Dreams", "In Early Arizona", "In Enemy Country", "In Every Woman's Life", "In Fast Company", "In for Thirty Days", "In Gay Madrid", "In God We Tru$t", "In Good Company", "In Harm's Way", "In Her Shoes", "In His Brother's Place", "In His Steps", "In Hollywood with Potash and Perlmutter", "In Honor's Web", "In Like Flint", "In Line of Duty", "In Love and War", "In Love with Life", "In Love with Love", "In Mizzoura", "In Name Only", "In Old Amarillo", "In Old Arizona", "In Old California", "In Old Chicago", "In Old Kentucky", "In Old Mexico", "In Old New Mexico", "In Old Oklahoma", "In Old Sacramento", "In Old Santa Fe", "In Our Time", "In Person", "In Search of a Thrill", "In Search of Dr. Seuss", "In Search of the Castaways", "In Society", "In Spite of Danger", "In the Army Now", "In the Bag", "In the Bedroom", "In the Blood", "In the Border States", "In the Candlelight", "In the Company of Men", "In the Cool of the Day", "In the Cut", "In the First Degree", "In the Footprints of Mozart", "In the Gloaming", "In the Good Old Summertime", "In the Headlines", "In the Heart of a Fool", "In the Heart of the Sea", "In the Heat of the Night", "In the Land of the Head Hunters", "In the Land of Women", "In the Line of Fire", "In the Meantime, Darling", "In the Mix", "In the Money", "In the Mood", "In the Mouth of Madness", "In the Name of Love", "In the Name of the Father", "In the Name of the Law", "In the Navy", "In the Next Room", "In the Open", "In the Palace of the King", "In The Park", "In the Secret Service", "In the Soup", "In the Sweet Pie and Pie", "In the Valley of Elah", "In the Year of the Pig", "In This Corner", "In This Our Life", "In Time", "In Too Deep", "In Trust", "In Tune", "'In Wrong' Wright", "In Wrong", "InAPPropriate Comedy", "Inauguration of the Pleasure Dome", "Incendiary Blonde", "Inception", "Inchon", "Incident", "Inconceivable", "Incredibles 2", "Indecent Proposal", "Independence Day", "Independence Day: Resurgence", "Indescribable", "Indestructible Man", "India Speaks", "Indian Agent", "Indian Summer", "Indian Territory", "Indian Uprising", "Indiana Jones and the Kingdom of the Crystal Skull", "Indiana Jones and the Last Crusade", "Indiana Jones and the Temple of Doom", "Indianapolis Speedway", "Indians No. 1", "Indictment: The McMartin Trial", "Indigo", "Indiscreet", "Indivisible", "Industrial Symphony No. 1", "Inequality for All", "Inez from Hollywood", "Infamous", "Infatuation", "Infernal Machine", "Inferno", "Infinity", "Ingagi", "Inglourious Basterds", "Ingrid Goes West", "Inherent Vice", "Inherit the Wind", "Initiation: Silent Night, Deadly Night 4", "Ink", "Inkheart", "Inland Empire", "Inner Sanctum", "Innerspace", "Innocence", "Innocent Blood", "Innocents of Paris", "Inserts", "Inside Daisy Clover", "Inside Deep Throat", "Inside Detroit", "Inside Job", "Inside Llewyn Davis", "Inside Man", "Inside Moves", "Inside Out", "Inside Straight", "Inside the Lines", "Inside the Mafia", "Inside the Walls of Folsom Prison", "Insidious", "Insidious: Chapter 2", "Insidious: Chapter 3", "Insidious: The Last Key", "Insignificance", "Insomnia", "Inspector Gadget", "Inspiration", "Instant Family", "Instinct", "Insurance Investigator", "Intelligence and the Japanese Civilian", "Intensity", "Interference", "Interiors", "Interkosmos", "Interlude", "Intermedio", "Intermezzo: A Love Story", "Internal Affairs", "International House", "International Lady", "International Settlement", "International Squadron", "International Velvet", "Internes Can't Take Money", "Interrupted Melody", "Intersection", "Interstate 60", "Interstellar", "Interval", "Interview with the Vampire", "Interview", "Into Her Kingdom", "Into the Arms of Strangers: Stories of the Kindertransport", "Into the Blue", "Into the Night", "Into the Storm", "Into the Sun", "Into the Wild", "Into the Woods", "Intolerable Cruelty", "Intolerance", "Intrigue", "Introduce Me", "Intruder in the Dust", "Intruder", "Invaders from Mars", "Invasion from Inner Earth", "Invasion of the Bee Girls", "Invasion of the Body Snatchers", "Invasion of the Saucer Men", "Invasion U.S.A.", "Inventing the Abbotts", "Invictus", "Invincible", "Invisible Agent", "Invisible Avenger", "Invisible Enemy", "Invisible Ghost", "Invisible Invaders", "Invisible Stripes", "Invitation to a Gunfighter", "Invitation to Happiness", "Invitation to the Dance", "Invitation", "Ip Man 3", "Iraq for Sale: The War Profiteers", "Iraq in Fragments", "Irene", "Irish Eyes Are Smiling", "Irish Jam", "Irish Luck", "Irma la Douce", "Irma Vep", "Iron & Silk", "Iron Eagle II", "Iron Eagle", "Iron Man 2", "Iron Man 3", "Iron Man", "Iron Mountain Trail", "Iron to Gold", "Iron Will", "Ironweed", "Irreconcilable Differences", "Is Everybody Happy?", "Is Love Everything?", "Is Matrimony a Failure?", "Is My Face Red?", "Is Paris Burning?", "Is That Nice?", "Is There a Doctor in the Mouse?", "Is There Life Out There?", "Is Zat So?", "Ishtar", "Island in the Sky", "Island in the Sun", "Island of Doomed Men", "Island of Lost Souls", "Island of Lost Women", "Island of Love", "Island of the Blue Dolphins", "Island Wives", "Island Women", "Islands in the Stream", "Isle of Dogs", "Isle of Escape", "Isle of Forgotten Sins", "Isle of Fury", "Isle of the Dead", "Isn't It Romantic?", "Isn't Life Wonderful", "Isn't She Great", "Istanbul", "ISteve", "It Ain't Hay", "It All Came True", "It Came from Beneath the Sea", "It Came from Hollywood", "It Came from Outer Space", "It Came from Somewhere Else", "It Can Be Done", "It Can't Last Forever", "It Comes at Night", "It Comes Up Love", "It Conquered the World", "It Could Happen to You!", "It Could Happen to You", "It Couldn't Have Happened – But It Did", "It Don't Cost Nothin' to Say Good Morning", "It Follows", "It Grows on Trees", "It Had to Be You", "It Had to Happen", "It Happened at the World's Fair", "It Happened In Athens", "It Happened in Brooklyn", "It Happened in Harlem", "It Happened in Hollywood", "It Happened in New York", "It Happened on 5th Avenue", "It Happened One Night", "It Happened Out West", "It Happened Thus", "It Happened to Jane", "It Happened Tomorrow", "It Happens Every Spring", "It Happens Every Thursday", "It Is the Law", "It Might Get Loud", "It Must Be Love", "It Pays to Advertise", "It Runs in the Family", "It Should Happen to You", "It Shouldn't Happen to a Dog", "It Started in Naples", "It Started with a Kiss", "It Started with Eve", "It Takes All Kinds", "It Takes Two", "It! The Terror from Beyond Space", "It", "Itching Palms", "It's a Bear", "It's a Big Country", "It's a Bikini World", "It's a Date", "It's a Disaster", "It's a Dog's Life", "It's a Gift", "It's a Great Feeling", "It's a Great Life", "It's a Joke, Son!", "It's a Mad, Mad, Mad, Mad World", "It's a Pleasure", "It's a Small World", "It's a Wild Life", "It's a Wise Child", "It's a Wonderful Life", "It's a Wonderful World", "It's Alive", "It's All True", "It's All Yours", "It's Always Fair Weather", "It's Complicated", "It's Everybody's War", "It's Great to Be Alive", "It's Great to Be Young", "It's in the Air", "It's in the Bag!", "It's In the Water", "It's Kind of a Funny Story", "It's Love I'm After", "It's My Party", "It's My Turn", "It's Not Just You, Murray!", "It's Only Money", "It's Pat", "It's the Old Army Game", "It's Tough to Be Famous", "It's Up to You", "Itty Bitty Titty Committee", "Ivanhoe", "Ivans Xtc", "I've Always Loved You", "I've Been Around", "I've Got Your Number", "I've Heard the Mermaids Singing", "I've Lived Before", "Ivy", "J. D.'s Revenge", "J. Edgar", "J.W. Coop", "Jack and Jill", "Jack and the Beanstalk", "Jack Frost", "Jack Goes Boating", "Jack London", "Jack McCall, Desperado", "Jack O'Clubs", "Jack of Diamonds", "Jack Reacher", "Jack Reacher: Never Go Back", "Jack Ryan: Shadow Recruit", "Jack Smith and the Destruction of Atlantis", "Jack the Bear", "Jack the Giant Killer", "Jack the Giant Slayer", "Jack", "Jackass 3-D", "Jackass Mail", "Jackass Number Two", "Jackass Presents: Bad Grandpa", "Jackass: The Movie", "Jackie Brown", "Jacknife", "Jack-O", "Jack's Back", "Jackson County Jail", "Jacob", "Jacob's Ladder", "Jacques of the Silver North", "Jade", "Jagged Edge", "Jaguar Lives!", "Jaguar", "Jail Bait", "Jail Birds", "Jail Busters", "Jailbait", "Jailbreak", "Jailhouse Rock", "Jake Speed", "Jake's Women", "Jakob the Liar", "Jalna", "Jalopy", "Jam Session", "Jamaica Run", "Jamboree", "James and the Giant Peach", "Jamesy Boy", "Jammin' the Blues", "Jandek on Corwood", "Jane Austen in Manhattan", "Jane Austen's Mafia!", "Jane Eyre", "Jane Goes A' Wooing", "Jane Got a Gun", "Janice Meredith", "Janie Gets Married", "Janie Jones", "Janie", "Janky Promoters", "Japanese Relocation", "Japanese War Bride", "Jarhead", "Jason and the Argonauts", "Jason Bourne", "Jason Goes to Hell: The Final Friday", "Jason X", "Jason's Lyric", "Java Head", "Java Heat", "Jawbreaker", "Jaws 2", "Jaws 3-D", "Jaws of Steel", "Jaws", "Jaws: The Revenge", "Jay & Silent Bob's Super Groovy Cartoon Movie", "Jay and Silent Bob Strike Back", "Jayne Mansfield's Car", "Jazz Heaven", "Jazz Mad", "Jazz on a Summer's Day", "Jazzmania", "Jealous Husbands", "Jealousy", "Jeanne Eagels", "Jeepers Creepers 3", "Jeepers Creepers II", "Jeepers Creepers", "Jeff, Who Lives at Home", "Jefferson in Paris", "Jeffrey", "Jekyll and Hyde... Together Again", "Jennie Gerhardt", "Jennie", "Jennifer 8", "Jennifer", "Jennifer's Body", "Jenny Lind", "Jenny", "Jeopardy", "Jeremiah Johnson", "Jeremy", "Jerry and Jumbo", "Jerry and the Goldfish", "Jerry and the Lion", "Jerry Maguire", "Jerry's Cousin", "Jerry's Diary", "Jersey Boys", "Jersey Girl", "Jessabelle", "Jesse James Meets Frankenstein's Daughter", "Jesse James Rides Again", "Jesse James vs. the Daltons", "Jesse James' Women", "Jesse James", "Jessica", "Jessie, the Stolen Child", "Jesus Camp", "Jesus Christ Superstar", "Jesus' Son", "Jesus", "Jet Attack", "Jet Job", "Jet Over the Atlantic", "Jet Pilot", "Jet Storm", "Jetsons: The Movie", "Jewel of the Nile, The", "Jewel Robbery", "Jewels of Brandenburg", "Jewish American Princess", "Jezebel", "JFK", "Jiggs and Maggie in Court", "Jiggs and Maggie in Jackpot Jitters", "Jiggs and Maggie in Society", "Jiggs and Maggie Out West", "Jigsaw", "Jim Bludso", "Jim Hanvey, Detective", "Jim the Penman", "Jim Thorpe – All-American", "Jim, the Conqueror", "Jimmie's Millions", "Jimmy and Judy", "Jimmy and Sally", "Jimmy Hollywood", "Jimmy Neutron: Boy Genius", "Jimmy the Gent", "Jimmy Zip", "Jingle All the Way", "Jinn", "Jinx Money", "Jinx", "Jinxed!", "Jitterbugs", "Jivaro", "Jive Junction", "Jo Jo Dancer, Your Life Is Calling", "Joan of Arc", "Joan of Ozark", "Joan of Paris", "Joan the Woman", "Joanna", "Jobs", "Jocks", "Joe and Ethel Turp Call on the President", "Joe Butterfly", "Joe Dakota", "Joe Dirt", "Joe Gould's Secret", "Joe Kidd", "Joe MacBeth", "Joe Palooka in Fighting Mad", "Joe Palooka in Humphrey Takes a Chance", "Joe Palooka in the Big Fight", "Joe Palooka in the Counterpunch", "Joe Palooka in the Knockout", "Joe Palooka in The Squared Circle", "Joe Palooka in Triple Cross", "Joe Palooka in Winner Take All", "Joe Palooka Meets Humphrey", "Joe Palooka, Champ", "Joe Smith, American", "Joe Somebody", "Joe the King", "Joe Versus the Volcano", "Joe", "Joe's Apartment", "Johann Mouse", "John and Mary", "John Carter", "John Dies at the End", "John Goldfarb, Please Come Home", "John Loves Mary", "John Meade's Woman", "John Paul Jones", "John Petticoats", "John Q", "John Smith", "John Tucker Must Die", "John Wick: Chapter 2", "Johnny Allegro", "Johnny Angel", "Johnny Apollo", "Johnny Appleseed", "Johnny Be Good", "Johnny Belinda", "Johnny Come Lately", "Johnny Comes Flying Home", "Johnny Concho", "Johnny Cool", "Johnny Dangerously", "Johnny Dark", "Johnny Doesn't Live Here Anymore", "Johnny Doughboy", "Johnny Eager", "Johnny Get Your Gun", "Johnny Get Your Hair Cut", "Johnny Got His Gun", "Johnny Guitar", "Johnny Handsome", "Johnny Holiday", "Johnny Mnemonic", "Johnny O'Clock", "Johnny One-Eye", "Johnny Reno", "Johnny Rocco", "Johnny Stool Pigeon", "Johnny Suede", "Johnny Tremain", "Johnny Trouble", "Johnny-on-the-Spot", "Johns", "Johnson Family Vacation", "Join the Marines", "Jolson Sings Again", "Jonah Hex", "Jonah: A VeggieTales Movie", "Jonas Brothers: The 3D Concert Experience", "Jonathan Livingston Seagull", "Jones and His New Neighbors", "Jonestown: The Life and Death of Peoples Temple", "Jory", "Joseph", "Josette", "Josh and S.A.M.", "Josh Kirby... Time Warrior!", "Joshua Tree", "Joshua", "Josie and the Pussycats", "Josie", "Josselyn's Wife", "Journal of a Crime", "Journey 2: The Mysterious Island", "Journey Back to Oz", "Journey for Margaret", "Journey into Fear", "Journey into Light", "Journey to the Center of the Earth", "Journey to the Center of Time", "Journey to the Seventh Planet", "Journey's End", "Joy in the Morning", "Joy of Living", "Joy Ride", "Joy Street", "Joy", "Joyful Noise", "Joyride", "Juarez", "Jubal", "Jubilee Trail", "Jubilo", "Judas Kiss", "Judge Dredd", "Judge Hardy and Son", "Judge Hardy's Children", "Judge Not; or The Woman of Mona Diggings", "Judge Priest", "Judgment at Nuremberg", "Judgment Night", "Judith of Bethulia", "Judith", "Judy Moody and the Not Bummer Summer", "Judy of Rogue's Harbor", "Judy's Little No-No", "Jug Face", "Juice", "Juke Box Rhythm", "Juke Girl", "Julia Misbehaves", "Julia", "Julie & Julia", "Julie", "Juliet, Naked", "Julius Caesar", "Jumanji", "Jumanji: Welcome to the Jungle", "Jump into Hell", "Jumper", "Jumpin' Jack Flash", "Jumping Jacks", "Jumping the Broom", "Junction City", "June Bride", "June Madness", "June Moon", "Junebug", "Jungle 2 Jungle", "Jungle Book", "Jungle Bride", "Jungle Drums of Africa", "Jungle Emperor Leo", "Jungle Fever", "Jungle Flight", "Jungle Gents", "Jungle Girl", "Jungle Goddess", "Jungle Heat", "Jungle Jim in the Forbidden Land", "Jungle Jim", "Jungle Man", "Jungle Man-Eaters", "Jungle Manhunt", "Jungle Menace", "Jungle Moon Men", "Jungle Patrol", "Jungle Queen", "Jungle Raiders", "Jungle Woman", "Junior Bonner", "Junior G-Men of the Air", "Junior G-Men", "Junior Miss", "Junior Prom", "Junior", "Juno", "Jupiter Ascending", "Jupiter's Darling", "Jurassic Park III", "Jurassic Park", "Jurassic World", "Jurassic World: Fallen Kingdom", "Jury Duty", "Just a Gigolo", "Just a Little Harmless Sex", "Just a Woman", "Just Across the Street", "Just Another Blonde", "Just Another Girl on the I.R.T.", "Just Around the Corner", "Just Before Dawn", "Just Before I Go", "Just Between Friends", "Just Cause", "Just Dropped In", "Just Ducky", "Just for You", "Just Friends", "Just Getting Started", "Just Go with It", "Just Imagine", "Just in Time", "Just Like Heaven", "Just Married", "Just My Luck", "Just Neighbors", "Just Nuts", "Just Off Broadway", "Just One Night", "Just One of the Guys", "Just Out of College", "Just Pals", "Just Suppose", "Just Tell Me What You Want", "Just the Ticket", "Just the Way You Are", "Just This Once", "Just Tony", "Just Visiting", "Just Wright", "Just Write", "Just You and Me, Kid", "Just Your Luck", "Justice League of America", "Justice League", "Justice League: The Flashpoint Paradox", "Justice of the Far North", "Justice of the Range", "Justice of the Wild", "Justin Bieber: Never Say Never", "Justin Bieber's Believe", "Justin Case", "Justine", "Juvenile Court", "Juvenile Jungle", "Juwanna Mann", "K - The Unknown", "K-19: The Widowmaker", "K-9", "K-9: P.I.", "K-911", "Kaboom", "Kafka", "Kalamazoo?", "Kalifornia", "Kama Sutra: A Tale of Love", "Kamillions", "Kangaroo Jack", "Kangaroo", "Kansas City Bomber", "Kansas City Confidential", "Kansas City Kitty", "Kansas City Princess", "Kansas City", "Kansas Pacific", "Kansas Raiders", "Kansas Saloon Smashers", "Kansas Territory", "Kansas", "Karla", "Kate & Leopold", "Kathleen Mavourneen", "Kathleen", "Kathy O'", "Katie Did It", "Katy Perry: Part of Me 3D", "Kazaam", "Kazan", "Keane", "Keanu", "Keep 'Em Flying", "Keep 'Em Rolling", "Keep 'Em Slugging", "Keep Smiling", "Keep the Lights On", "Keep Your Powder Dry", "Keeper of the Bees", "Keeper of the Flame", "Keeping the Faith", "Keeping the Promise", "Keeping Up with the Joneses", "Keeping Up with the Steins", "Kelly and Me", "Kelly of the Secret Service", "Kelly the Second", "Kelly's Heroes", "Kenilworth", "Kenny", "Kentucky Days", "Kentucky Handicap", "Kentucky Jubilee", "Kentucky Kernels", "Kentucky Moonshine", "Kentucky Pride", "Kentucky Rifle", "Kentucky", "Kept Husbands", "Kevin Hart: Let Me Explain", "Kevin Hart: What Now?", "Key Largo", "Key to the City", "Key Witness", "Keys to Tulsa", "Khartoum", "Khyber Patrol", "Kick In", "Kickaroo", "Kick-Ass 2", "Kick-Ass", "Kickboxer 4", "Kickboxer", "Kicked Out", "Kickin' It Old Skool", "Kicking & Screaming", "Kicking and Screaming", "Kicking the Germ Out of Germany", "Kid Auto Races at Venice", "Kid Blue", "Kid Boots", "Kid Cannabis", "Kid Dynamite", "Kid Galahad", "Kid Glove Killer", "Kid Gloves", "Kid Millions", "Kid Monk Baroni", "Kidnap", "Kidnapped", "Kids in America", "Kids", "Kiki", "Kill a Dragon", "Kill Bill Volume 1", "Kill Bill Volume 2", "Kill Me Again", "Kill Me Later", "Kill or Be Killed", "Kill Squad", "Kill the Irishman", "Kill the Moonlight", "Kill the Umpire", "Kill Your Darlings", "Killer Ape", "Killer at Large", "Killer Dill", "Killer Elite", "Killer Flick", "Killer Force", "Killer Joe", "Killer Klowns from Outer Space", "Killer Leopard", "Killer McCoy", "Killer of Sheep", "Killer Shark", "Killer Tomatoes Strike Back", "Killer!", "Killer: A Journal of Murder", "Killers from Space", "Killer's Kiss", "Killers Three", "Killers", "Killing Me Softly", "Killing Season", "Killing Zelda Sparks", "Killing Zoe", "Killpoint", "Kilroy Was Here", "Kim", "Kin", "Kind Lady", "Kindergarten Cop", "Kindled Courage", "Kindling", "Kindred of the Dust", "King Arthur", "King Arthur: Legend of the Sword", "King Cowboy", "King Creole", "King David", "King for a Night", "King Kelly of the U.S.A.", "King Kong Escapes", "King Kong Lives", "King Kong", "King Kung Fu", "King Lear", "King of Alcatraz", "King of Burlesque", "King of Gamblers", "King of Hockey", "King of Jazz", "King of Kings", "King of New York", "King of Punk", "King of the Arena", "King of the Bandits", "King of the Bullwhip", "King of the Carnival", "King of the Congo", "King of the Cowboys", "King of the Forest Rangers", "King of the Gamblers", "King of the Gypsies", "King of the Herd", "King of the Hill", "King of the Jungle", "King of the Khyber Rifles", "King of the Lumberjacks", "King of the Mounties", "King of the Newsboys", "King of the Pecos", "King of the Rocket Men", "King of the Rodeo", "King of the Royal Mounted", "King of the Saddle", "King of the Turf", "King of the Underworld", "King of the Wild Horses", "King of the Wild Stallions", "King of the Zombies", "King Ralph", "King Rat", "King Richard and the Crusaders", "King Solomon of Broadway", "King Solomon's Mines", "King: A Filmed Record... Montgomery to Memphis", "Kingdom Come", "Kingdom of Heaven", "Kingdom of the Spiders", "Kingpin", "Kings Go Forth", "Kings of the Sun", "King's Ransom", "King's Rhapsody", "Kings Row", "Kings", "King-Size Canary", "Kingsman: The Golden Circle", "Kingsman: The Secret Service", "Kinjite: Forbidden Subjects", "Kink", "Kinsey", "Kismet", "Kiss and Make-Up", "Kiss and Tell", "Kiss Kiss Bang Bang", "Kiss Me Again", "Kiss Me Deadly", "Kiss Me Goodbye", "Kiss Me Quick!", "Kiss Me, Kate", "Kiss Me, Stupid", "Kiss of Araby", "Kiss of Death", "Kiss of Fire", "Kiss of the Damned", "Kiss of the Dragon", "Kiss of the Spider Woman", "Kiss the Blood Off My Hands", "Kiss the Boys Goodbye", "Kiss the Bride", "Kiss the Girls and Make Them Die", "Kiss the Girls", "Kiss the Sky", "Kiss Them for Me", "Kiss Toledo Goodbye", "Kiss Tomorrow Goodbye", "Kissed", "Kisses for Breakfast", "Kisses for My President", "Kisses", "Kissin' Cousins", "Kissing a Fool", "Kissing Jessica Stein", "Kit Carson", "Kit Kittredge: An American Girl", "Kitten with a Whip", "Kitty Foiled", "Kitty Foyle", "Kitty Kelly, M.D.", "Kitty Kornered", "Kitty", "Klondike Annie", "Klondike Kate", "Klondike", "Klute", "Knick Knack", "Knickerbocker Holiday", "Knife Fight", "Knight and Day", "Knight Moves", "Knight Rider 2010", "Knightriders", "Knights of the City", "Knights of the Round Table", "Knighty Knight Bugs", "Knock Knock", "Knock Off", "Knock on Any Door", "Knock on Wood", "Knockaround Guys", "Knocked Up", "Knockin' On Heaven's Door", "Knockout Reilly", "Knots", "Know Your Enemy: Japan", "Knowing", "Knucklehead", "Knute Rockne, All American", "Koch", "Kona Coast", "Kong: Skull Island", "Kongo", "Korea Patrol", "Kosher Kitty Kelly", "Kotch", "Kounterfeit", "Koyaanisqatsi", "K-PAX", "Krakatoa, East of Java", "Kramer vs. Kramer", "Krippendorf's Tribe", "Kronos", "Krush Groove", "Kubo and the Two Strings", "Kuffs", "Kull the Conqueror", "Kundun", "Kung Fu Panda 2", "Kung Fu Panda 3", "Kung Fu Panda", "Kung Fu: The Next Generation", "Kung Pow! Enter the Fist", "Kurt & Courtney", "Kurt Cobain About a Son", "L.A. Confidential", "L.A. Story", "L.A. Takedown", "L.I.E.", "La Bamba", "La Boda de Valentina", "La Bohème", "La ley del harem", "Labor Day", "Labor Pains", "Labyrinth", "Lace", "Ladder 49", "Laddie", "Ladies and Gentlemen, the Fabulous Stains", "Ladies and Gentlemen: The Rolling Stones", "Ladies at Play", "Ladies Courageous", "Ladies Crave Excitement", "Ladies' Day", "Ladies in Distress", "Ladies in Love", "Ladies in Retirement", "Ladies Love Brutes", "Ladies Love Danger", "Ladies' Man", "Ladies Must Dress", "Ladies Must Live", "Ladies Must Love", "Ladies' Night in a Turkish Bath", "Ladies of Leisure", "Ladies of the Big House", "Ladies of the Chorus", "Ladies of the Jury", "Ladies of the Mob", "Ladies of Washington", "Ladies Should Listen", "Ladies They Talk About", "Ladies to Board", "Lady and Gent", "Lady and the Tramp", "Lady at Midnight", "Lady Audley's Secret", "Lady Be Careful", "Lady Be Good", "Lady Behave!", "Lady Beware", "Lady Bird", "Lady Bodyguard", "Lady by Choice", "Lady Chaser", "Lady for a Day", "Lady for a Night", "Lady from Louisiana", "Lady from Nowhere", "Lady Gangster", "Lady Godiva of Coventry", "Lady Ice", "Lady in a Jam", "Lady in Cement", "Lady in the Dark", "Lady in the Death House", "Lady in the Iron Mask", "Lady in the Lake", "Lady in the Water", "Lady in White", "Lady Killer", "Lady Luck", "Lady Magdalene's", "Lady of Burlesque", "Lady of Secrets", "Lady of the Night", "Lady of the Pavements", "Lady of the Tropics", "Lady on a Train", "Lady Possessed", "Lady Raffles", "Lady Robinhood", "Lady Rose's Daughter", "Lady Scarface", "Lady Sings the Blues", "Lady Tubbs", "Lady Windermere's Fan", "Lady with a Past", "Lady with Red Hair", "Lady, Let's Dance", "Ladybug Ladybug", "Ladybugs", "Ladyhawke", "Lafayette Escadrille", "Laggies", "Laid to Rest", "Lake of Fire", "Lake Placid Serenade", "Lake Placid", "Lake Windfall", "Lakeview Terrace", "Lamb", "Lambert the Sheepish Lion", "Lancelot and Guinevere", "Lancer Spy", "Land Beyond the Law", "Land of Hunted Men", "Land of the Blind", "Land of the Dead", "Land of the Lawless", "Land of the Lost", "Land of the Outlaws", "Land of the Pharaohs", "Land of the Silver Fox", "Land of Wanted Men", "Land Raiders", "Landrush", "Lara Croft Tomb Raider: The Cradle of Life", "Lara Croft: Tomb Raider", "Laramie Mountains", "Laramie", "Larceny in Her Heart", "Larceny on the Air", "Larceny with Music", "Larceny", "Larceny, Inc.", "Larger Than Life", "Larry Crowne", "Larry the Cable Guy: Health Inspector", "Lars and the Real Girl", "Las Vegas Nights", "Las Vegas Shakedown", "Lasca of the Rio Grande", "Lasca", "Laser Mission", "Laserblast", "Lassie Come Home", "Lassie", "Lassiter", "Last Action Hero", "Last Call", "Last Chance Harvey", "Last Dance", "Last Days of Boot Hill", "Last Days", "Last Dragon, The", "Last Embrace", "Last Exit to Brooklyn", "Last Flag Flying", "Last Frontier Uprising", "Last Holiday", "Last House on Dead End Street", "Last Man Standing", "Last of the Badmen", "Last of the Buccaneers", "Last of the Comanches", "Last of the Dogmen", "Last of the Duanes", "Last of the Mobile Hot Shots", "Last of the Pagans", "Last of the Pony Riders", "Last of the Red Hot Lovers", "Last of the Redskins", "Last of the Warrens", "Last of the Wild Horses", "Last Rites", "Last Shift", "Last Stand at Saber River", "Last Summer", "Last Train from Bombay", "Last Train from Gun Hill", "Last Vegas", "Last Weekend", "Last Woman on Earth", "Latin Lovers", "Latino", "Latter-Day Saints", "Lauderdale", "Laugh and Get Rich", "Laugh It Off", "Laugh, Clown, Laugh", "Laughing at Danger", "Laughing at Life", "Laughing at Trouble", "Laughing Boy", "Laughing Gas", "Laughing Irish Eyes", "Laughing Sinners", "Laughter in Hell", "Laughter on the 23rd Floor", "Laughter", "Laura Comstock's Bag-Punching Dog", "Laura Lansing Slept Here", "Laura", "Laurel Canyon", "L'aviateur", "Law Abiding Citizen", "Law and Lawless", "Law and Order", "Law Beyond the Range", "Law Men", "Law of the Badlands", "Law of the Barbary Coast", "Law of the Canyon", "Law of the Golden West", "Law of the Jungle", "Law of the Lash", "Law of the Lawless", "Law of the Northwest", "Law of the Pampas", "Law of the Panhandle", "Law of the Plains", "Law of the Range", "Law of the Ranger", "Law of the Saddle", "Law of the Sea", "Law of the Texan", "Law of the Tropics", "Law of the Underworld", "Law of the Valley", "Law of the West", "Lawful Larceny", "Lawless Breed", "Lawless Code", "Lawless Cowboys", "Lawless Empire", "Lawless Land", "Lawless Range", "Lawless Riders", "Lawless Valley", "Lawless", "Lawman", "Lawn Dogs", "Lawrence of Arabia", "Lawyer Man", "Lay That Rifle Down", "Lazy Lightning", "Lazy River", "Lazybones", "LBJ", "Le Divorce", "Le Mans", "Le Petit chaperon rouge", "Le Rêve de Noël", "Leadbelly", "Leadville Gunslinger", "Lean on Me", "Leap of Faith", "Leap Year", "Learning to Love", "Leather Gloves", "Leatherface", "Leatherface: The Texas Chainsaw Massacre III", "Leatherheads", "Leathernecking", "Leave Her to Heaven", "Leave It to Beaver", "Leave It to Blondie", "Leave It to Susan", "Leave It to the Irish", "Leave It to the Marines", "Leave No Trace", "Leaving Las Vegas", "Leaving Normal", "Left Behind", "Left Hand of Gemini", "Left-Handed Law", "Legacy", "Legal Eagles", "Legally Blonde 2: Red, White & Blonde", "Legally Blonde", "Legally Dead", "Legend of Hollywood", "Legend of the Guardians: The Owls of Ga'Hoole", "Legend of the Lost", "Legend", "Legendary", "Legends of the Fall", "Legion of Terror", "Legion of the Lawless", "Legion", "Lemony Snicket's A Series of Unfortunate Events", "Lena Rivers", "Lend Me Your Husband", "Lenny Bruce: Swear to Tell the Truth", "Lenny", "Leonard Part 6", "Lepke", "Leprechaun 2", "Leprechaun 4: In Space", "Leprechaun", "Leprechaun: Origins", "Les Girls", "Les Misérables", "Less Than Zero", "Let 'Em Have It", "Let 'er Buck", "Let 'Er Go Gallegher", "Let Freedom Ring", "Let It Be Me", "Let It Rain", "Let It Ride", "Let Katy Do It", "Let Me Die a Woman", "Let Me In", "Let No Man Write My Epitaph", "Let Not Man Put Asunder", "Let the Fire Burn", "Let Them Live", "Let Us Be Gay", "Let Us Live", "Let Women Alone", "Lethal Weapon 2", "Lethal Weapon 3", "Lethal Weapon 4", "Lethal Weapon", "Let's All Go to the Lobby", "Let's Be Cops", "Let's Be Happy", "Let's Be Ritzy", "Let's Dance", "Let's Do It Again", "Let's Elope", "Let's Face It", "Let's Fall in Love", "Let's Get Harry", "Let's Get Married", "Let's Get Tough!", "Let's Go Native", "Let's Go Navy!", "Let's Go Places", "Let's Go Steady", "Let's Go to Prison", "Let's Go", "Let's Go, Gallagher", "Let's Have Fun", "Let's Kill Uncle", "Let's Kill Ward's Wife", "Let's Live a Little", "Let's Live Again", "Let's Live Tonight", "Let's Make a Million", "Let's Make It Legal", "Let's Make Love", "Let's Make Music", "Let's Rock", "Let's Scare Jessica to Death", "Let's Sing Again", "Let's Spend the Night Together", "Let's Talk About Sex", "Let's Talk It Over", "Let's Try Again", "Letter from an Unknown Woman", "Letter of Introduction", "Letters from Iwo Jima", "Letters to God", "Letters to Juliet", "Letty Lynton", "Leviathan", "Lew Tyler's Wives", "Lewis and Clark and George", "Lianna", "Liar Liar", "Liars All", "Libeled Lady", "Liberal Arts", "Liberty Heights", "Liberty!", "License No. 13; or, The Hoodoo Automobile", "License to Drive", "License to Wed", "Liebestraum", "Life 101", "Life After Beth", "Life as a House", "Life as We Know It", "Life at the Top", "Life Begins at 17", "Life Begins at 40", "Life Begins at Eight-Thirty", "Life Begins for Andy Hardy", "Life Begins in College", "Life Begins with Love", "Life Begins", "Life During Wartime", "Life in a Day", "Life in the Raw", "Life Itself", "Life of an American Policeman", "Life of Crime", "Life of Pi", "Life of the Party", "Life or Something Like It", "Life Returns", "Life Stinks", "Life with Billy", "Life with Blondie", "Life with Father", "Life with Feathers", "Life with Henry", "Life with Mikey", "Life with Tom", "Life", "Lifeboat", "Lifeforce", "Lifeguard", "Life's a Funny Proposition", "Life's Greatest Game", "Light Heavyweight Championship Contest Between Root and Gardner", "Light in the Piazza", "Light It Up", "Light of Day", "Light Sleeper", "Light Years", "Lighthouse", "Lightnin' Bill Carson", "Lightnin' Crandall", "Lightnin' in the Forest", "Lightnin'", "Lightning Guns", "Lightning Raiders", "Lightning Romance", "Lightning Strikes Twice", "Lights of New York", "Lights of Old Broadway", "Lights of Old Santa Fe", "Lights of the Desert", "Lights Out", "Like Crazy", "Like Dandelion Dust", "Like Father Like Son", "Like Mike", "Li'l Abner", "Lil Bub & Friendz", "Lilac Time", "Lili", "Lilies of the Field", "Liliom", "Lilith", "Lillian Russell", "Lillies of the City", "Lillo of the Sulu Seas", "Lilly Turner", "Lilo & Stitch", "Lily of the Dust", "Limbo", "Lime Salted Love", "Limehouse Blues", "Limelight", "Limit Up", "Limitless", "Lincoln", "Linda, Be Good", "Lion of the Desert", "Lionheart", "Lions for Lambs", "Lipstick", "Liquid Sky", "Lisbon", "Listen Lester", "Listen to Me", "Listen Up Philip", "Listen", "Listen, Darling", "Little Accidents", "Little Annie Rooney", "Little Big Horn", "Little Big League", "Little Big Man", "Little Big Shot", "Little Black Book", "Little Boy Lost", "Little Boy", "Little Buddha", "Little Caesar", "Little Chenier", "Little Children", "Little Church Around the Corner", "Little Cigars", "Little City", "Little Comrade", "Little Darlings", "Little Egypt", "Little Eva Ascends", "Little Eve Edgarton", "Little Fauss and Big Halsy", "Little Feet", "Little Fockers", "Little Fugitive", "Little Giant", "Little Giants", "Little Golden Book Land", "Little Iodine", "Little Johnny Jones", "Little Lord Fauntleroy", "Little Lost Sister", "Little Man Tate", "Little Man", "Little Man, What Now?", "Little Men", "Little Miss Big", "Little Miss Broadway", "Little Miss Marker", "Little Miss Nobody", "Little Miss Roughneck", "Little Miss Smiles", "Little Miss Sunshine", "Little Miss Thoroughbred", "Little Mister Jim", "Little Monsters", "Little Murders", "Little Nellie Kelly", "Little Nicky", "Little Nikita", "Little Odessa", "Little Old New York", "Little Orphan Annie", "Little Quacker", "Little Red Riding Hood", "Little Red Riding Rabbit", "Little Robinson Crusoe", "Little Runaway", "Little School Mouse", "Little Shop of Horrors", "Little Tich and His Funny Feet", "Little Tough Guy", "Little Tough Guys in Society", "Little Wildcat", "Little Witches", "Little Women", "Live a Little, Love a Little", "Live Fast, Die Young", "Live Freaky! Die Freaky!", "Live Free or Die Hard", "Live Free or Die", "Live Nude Girls", "Live Wire", "Live Wires", "Live, Love and Learn", "Living in a Big Way", "Living in Oblivion", "Living It Up", "Living on Love", "Living on Tokyo Time", "Living on Velvet", "Living Out Loud", "Living the Blues", "Liz: The Elizabeth Taylor Story", "Lizzie", "Lloyd", "Lloyd's of London", "Loaded Pistols", "Loaded Weapon 1", "Loan Shark", "Local Boy Makes Good", "Local Color", "Loch Ness", "Lock Up", "Locked Doors", "Lockout", "Logan Lucky", "Logan", "Logan's Run", "LOL", "Lola Versus", "Lola", "Lolita", "Lolly-Madonna XXX", "Lombardi, Ltd.", "London After Midnight", "London Blackout Murders", "London by Night", "London Has Fallen", "London Suite", "Lone Cowboy", "Lone Hand Saunders", "Lone Star Moonlight", "Lone Star", "Lone Survivor", "Lone Texan", "Lone Texas Ranger", "Lone Wolf McQuade", "Lonely Are the Brave", "Lonely Heart Bandits", "Lonely Hearts", "Lonely Wives", "Lonelyhearts", "Lonesome Dove", "Lonesome Jim", "Lonesome Luke Leans to the Literary", "Lonesome Luke Lolls in Luxury", "Lonesome Luke Loses Patients", "Lonesome Luke on Tin Can Alley", "Lonesome Luke, Circus King", "Lonesome Luke, Lawyer", "Lonesome Luke, Mechanic", "Lonesome Luke, Messenger", "Lonesome Luke, Plumber", "Lonesome Luke, Social Gangster", "Lonesome Luke's Honeymoon", "Lonesome Luke's Lively Life", "Lonesome Luke's Lovely Rifle", "Lonesome Luke's Wild Women", "Lonesome Trail", "Long Day's Journey Into Night", "Long Distance", "Long Gone", "Long Live the King", "Long Lost Father", "Long Night's Journey into Day", "Long Pants", "Long-Haired Hare", "Look for the Silver Lining", "Look in Any Window", "Look Out Below", "Look Pleasant, Please", "Look Who's Laughing", "Look Who's Talking Too", "Look Who's Talking", "Look Your Best", "Looker", "Lookin' to Get Out", "Looking for Danger", "Looking for Love", "Looking for Mr. Goodbar", "Looking for Richard", "Looking for Trouble", "Looking Forward", "Looney Tunes: Back in Action", "Looper", "Loophole", "Loose Ankles", "Loose Cannons", "Loose in London", "Loose Shoes", "Loot", "Lord and Lady Algy", "Lord Byron of Broadway", "Lord Jeff", "Lord Jim", "Lord Love a Duck", "Lord of Illusions", "Lord of the Flies", "Lord of War", "Lords of Dogtown", "Lords of the Deep", "Loren Cass", "Lorenzo's Oil", "Lorna Doone", "Lorna", "Lorraine of the Lions", "Los Amigos", "Loser", "Losin' It", "Losing Isaiah", "Lost & Found", "Lost and Found on a South Sea Island", "Lost and Found", "Lost and Won", "Lost Angel", "Lost Angels", "Lost at Sea", "Lost Boundaries", "Lost City of the Jungle", "Lost Command", "Lost Continent", "Lost Flight", "Lost Highway", "Lost Honeymoon", "Lost Horizon", "Lost in a Harem", "Lost in Alaska", "Lost in America", "Lost in Florence", "Lost In Space", "Lost in the Stars", "Lost in the Stratosphere", "Lost in Translation", "Lost in Yonkers", "Lost Lagoon", "Lost Money", "Lost River", "Lost Souls", "Lost, Lonely and Vicious", "Lost: A Wife", "Lot in Sodom", "Lottery Lover", "Lottery Ticket", "Lotus Lady", "Louisa", "Louisiana Hayride", "Louisiana Purchase", "Louisiana Story", "Louisiana", "Love & Basketball", "Love & Friendship", "Love & Mercy", "Love & Other Drugs", "Love Affair", "Love Among the Millionaires", "Love Among Thieves", "Love and a .45", "Love and Bullets", "Love and Death on Long Island", "Love and Death", "Love and Glory", "Love and Hisses", "Love and Kisses", "Love and Learn", "Love and Money", "Love and Pain and the Whole Damn Thing", "Love and the Devil", "Love at First Bite", "Love at First Sight", "Love at Large", "Love at Stake", "Love Before Breakfast", "Love Begins at 20", "Love Birds", "Love Bound", "Love by the Light of the Moon", "Love Camp 7", "Love Child", "Love Comes Along", "Love Crimes", "Love Don't Cost a Thing", "Love Field", "Love Finds a Way", "Love Finds Andy Hardy", "Love from a Stranger", "Love Happens", "Love Happy", "Love Has Many Faces", "Love Hungry", "Love Hurts", "Love in a Bungalow", "Love in a Goldfish Bowl", "Love in Bloom", "Love in High Gear", "Love in the Afternoon", "Love in the Dark", "Love in the Rough", "Love in the Time of Cholera", "Love Insurance", "Love Is a Ball", "Love Is a Headache", "Love Is a Many-Splendored Thing", "Love Is a Racket", "Love Is All There Is", "Love Is an Awful Thing", "Love Is Better Than Ever", "Love Is Dangerous", "Love Is Love", "Love Is News", "Love Is on the Air", "Love Is Strange", "Love Island", "Love Jones", "Love Laughs at Andy Hardy", "Love Letters of a Star", "Love Letters", "Love Me and the World Is Mine", "Love Me Forever", "Love Me or Leave Me", "Love Me Tender", "Love Me Tonight", "Love N' Dancing", "Love Nest", "Love Never Dies", "Love of Women", "Love on a Bet", "Love on a Budget", "Love on the Run", "Love on Toast", "Love Potion No. 9", "Love Ranch", "Love Slaves of the Amazons", "Love Stinks", "Love Story", "Love Streams", "Love Takes Flight", "Love That Brute", "Love That Pup", "Love Thy Neighbor", "Love Time", "Love Under Fire", "Love with the Proper Stranger", "Love! Valour! Compassion!", "Love", "Love, Honor and Behave", "Love, Honor and Goodbye", "Love, Honor, and Behave", "Love, Honor, and Oh Baby!", "Love, Laughs and Lather", "Love, Live and Laugh", "Love, Loot and Crash", "Love, Rosie", "Love, Simon", "Lovebound", "Lovelace", "Lovelife", "Lovely & Amazing", "Lovely to Look At", "Lover Come Back", "Loverboy", "Lovers and Other Strangers", "Lovers Courageous", "Lovers in Quarantine", "Lovers Lane", "Lovers' Lane", "Lovers", "Love's Blindness", "Love's Greatest Mistake", "Love's Masquerade", "Loves of an Actress", "Love's Penalty", "Love's Prisoner", "Love's Whirlpool", "Love's Wilderness", "Lovesick", "Lovesong", "Lovey Mary", "Lovin' Molly", "Lovin' the Ladies", "Loving Annabelle", "Loving Couples", "Loving Lies", "Loving You", "Loving", "Lowriders", "Loyal Lives", "Lt. Robin Crusoe, U.S.N.", "Lucas", "Lucille Love, Girl of Mystery", "Luck and Pluck", "Luck in Pawn", "Lucky Boy", "Lucky Cisco Kid", "Lucky Devils", "Lucky Dog", "Lucky Ghost", "Lucky Lady", "Lucky Larkin", "Lucky Losers", "Lucky Me", "Lucky Night", "Lucky Number Slevin", "Lucky Numbers", "Lucky Partners", "Lucky Star", "Lucky Terror", "Lucky Them", "Lucky Three", "Lucky You", "Lucky", "Lucretia Lombard", "Lucy Gallant", "Luke and the Bang-Tails", "Luke and the Bomb Throwers", "Luke and the Mermaids", "Luke and the Rural Roughnecks", "Luke Does the Midway", "Luke Foils the Villain", "Luke Joins the Navy", "Luke Laughs Last", "Luke Locates the Loot", "Luke Lugs Luggage", "Luke Pipes the Pippins", "Luke Rides Roughshod", "Luke Wins Ye Ladye Faire", "Luke, Crystal Gazer", "Luke, Patient Provider", "Luke, Rank Impersonator", "Luke, the Candy Cut-Up", "Luke, the Chauffeur", "Luke, the Gladiator", "Luke's Busy Day", "Luke's Double", "Luke's Fatal Flivver", "Luke's Fireworks Fizzle", "Luke's Late Lunchers", "Luke's Lost Lamb", "Luke's Lost Liberty", "Luke's Movie Muddle", "Luke's Newsie Knockout", "Luke's Preparedness Preparations", "Luke's Shattered Sleep", "Luke's Society Mixup", "Luke's Speedy Club Life", "Luke's Trolley Troubles", "Luke's Washful Waiting", "Lullaby of Broadway", "Lullaby", "Lulu Belle", "Lulu on the Bridge", "Lumberjack", "Lumière and Company", "Lummox", "Lure of the Swamp", "Lure of the Wilderness", "Lure of the Yukon", "Lured", "Lust for Gold", "Lust for Life", "Lust in the Dust", "Lust, Caution", "Luther", "Luv", "Luxo Jr.", "Luxury Liner", "Lycanthrope", "Lydia Bailey", "Lydia", "Lying Wives", "Lymelife", "M", "M. Butterfly", "M\"A*S*H", "Ma and Pa Kettle at Home", "Ma and Pa Kettle at the Fair", "Ma and Pa Kettle at Waikiki", "Ma and Pa Kettle Back on the Farm", "Ma and Pa Kettle Go to Town", "Ma and Pa Kettle on Vacation", "Ma and Pa Kettle", "Mabel and Fatty Viewing the World's Fair at San Francisco", "Mabel at the Wheel", "Mabel's Blunder", "Mabel's Busy Day", "Mabel's Married Life", "Mabel's Strange Predicament", "Mac and Me", "Mac", "Macabre", "Macao", "MacArthur", "Macbeth", "MacGruber", "Machete Kills", "Machete", "Machine Gun Mama", "Machine Gun Preacher", "Machine-Gun Kelly", "Macho Callahan", "Macho Dancer", "Mack the Knife", "Mackenna's Gold", "Mackintosh and T.J.", "Macon County Line", "Macumba Love", "Mad About Music", "Mad at the Moon", "Mad City", "Mad Cowgirl", "Mad Dog and Glory", "Mad Dog Coll", "Mad Dog Time", "Mad Holiday", "Mad Hot Ballroom", "Mad Hour", "Mad Love", "Mad Max Beyond Thunderdome", "Mad Max: Fury Road", "Mad Money", "Mad Monster Party", "Madagascar 3: Europe's Most Wanted", "Madagascar", "Madagascar: Escape 2 Africa", "Madam Satan", "Madame Behave", "Madame Bovary", "Madame Butterfly", "Madame Curie", "Madame DuBarry", "Madame Mystery", "Madame Racketeer", "Madame Sans-Gêne", "Madame Sousatzka", "Madame Spy", "Madame X", "Made for Each Other", "Made for Love", "Made in America", "Made in Heaven", "Made in Paris", "Made of Honor", "Made on Broadway", "Madea Goes to Jail", "Madea's Big Happy Family", "Madea's Family Reunion", "Madea's Witness Protection", "Madeline", "Mademoiselle Fifi", "Mademoiselle Midnight", "Mademoiselle Modiste", "Madhouse", "Madigan", "Madigan's Millions", "Madison Avenue", "Madison Square Garden", "Madman", "Madness of Youth", "Madonna of Avenue A", "Madonna of the Desert", "Madonna of the Streets", "Madonna: Truth or Dare", "Magenta", "Maggie Pepper", "Maggie", "Magic Brush", "Magic Fire", "Magic in the Moonlight", "Magic in the Water", "Magic Island (film)", "Magic Magic", "Magic Mike", "Magic Town", "Magic", "Magical Maestro", "Magnificent Brute", "Magnificent Doll", "Magnificent Obsession", "Magnificent Roughnecks", "Magnolia", "Magnum Force", "Mahogany", "Maid in Manhattan", "Maid of Salem", "Maid to Order", "Maid's Night Out", "Maidstone", "Mail Order Bride", "Main Street After Dark", "Main Street Lawyer", "Main Street to Broadway", "Main Street", "Maisie Goes to Reno", "Maisie Was a Lady", "Maisie", "Major Dundee", "Major League II", "Major League", "Major League: Back to the Minors", "Major Payne", "Make a Million", "Make a Wish", "Make Believe Ballroom", "Make Haste to Live", "Make Me a Star", "Make Mine Laughs", "Make Mine Music", "Make Way for a Lady", "Make Way for Tomorrow", "Make Your Own Bed", "Maker of Men", "Making a Living", "Making a Man", "Making Love", "Making Mr. Right", "Making the Grade", "Making the Headlines", "Mako: The Jaws of Death", "Maladies (2012 film)", "Malaya", "Malcolm X", "Male and Female", "Maleficent", "Malibu Express", "Malibu's Most Wanted", "Malice in the Palace", "Malice", "Malicious", "Mallrats", "Malone", "Mama Loves Papa", "Mama Runs Wild", "Mama Steps Out", "Mama's Affair", "Mamba", "Mambo", "Mame", "Mamma Mia! Here We Go Again", "Mamma Mia!", "Mammy", "Man About Town", "Man Afraid", "Man Against Woman", "Man Alive", "Man and Maid", "Man at Large", "Man Bait", "Man Beast", "Man Bites Dog", "Man Friday", "Man from Del Rio", "Man from Frisco", "Man from God's Country", "Man from Rainbow Valley", "Man from Sonora", "Man from the Black Hills", "Man Hunt", "Man in the Attic", "Man in the Dark", "Man in the Middle", "Man in the Rough", "Man in the Saddle", "Man in the Shadow", "Man in the Vault", "Man in the Wilderness", "Man Made Monster", "Man of a Thousand Faces", "Man of Action", "Man of Conflict", "Man of Conquest", "Man of Courage", "Man of Iron", "Man of La Mancha", "Man of Steel", "Man of the Century", "Man of the Forest", "Man of the Frontier", "Man of the House", "Man of the People", "Man of the West", "Man of the World", "Man of the Year", "Man of Two Worlds", "Man on a Ledge", "Man on a String", "Man on a Tightrope", "Man on Fire", "Man on the Flying Trapeze", "Man on the Moon", "Man on the Prowl", "Man on Wire", "Man or Gun", "Man Power", "Man to Man", "Man Trouble", "Man Under Cover", "Man Wanted", "Man with the Gun", "Man with the Steel Whip", "Man with Two Lives", "Man with Two Mothers", "Man Without a Star", "Man, Woman and Child", "Man, Woman and Wife", "Managed Money", "Management", "Manda Bala (Send a Bullet)", "Mandalay", "Mandingo", "Man-Eater of Kumaon", "Manfish", "Manhandled", "Manhattan Angel", "Manhattan Cocktail", "Manhattan Cowboy", "Manhattan Heartbeat", "Manhattan Love Song", "Manhattan Madness", "Manhattan Melodrama", "Manhattan Merry-Go-Round", "Manhattan Moon", "Manhattan Murder Mystery", "Manhattan Parade", "Manhattan Romance", "Manhattan Tower", "Manhattan", "Manhood", "Manhunt of Mystery Island", "Manhunt: The Search for Bin Laden", "Manhunter", "Maniac Cop 2", "Maniac Cop", "Maniac!", "Maniac", "Manila Calling", "Mannequin", "Manny & Lo", "Manos: The Hands of Fate", "Manpower", "Man-Proof", "Man's Best Friend", "Man's Castle", "Man's Desire", "Man's Favorite Sport?", "Man's Size", "Mansion of the Doomed", "Manslaughter", "Manson", "Mantrap", "Man-Trap", "Many a Slip", "Many Happy Returns", "Many Rivers to Cross", "Maps to the Stars", "Mara Maru", "Mara of the Wilderness", "Maracaibo", "Maraschino Cherry", "Marathon Man", "March or Die", "Marci X", "Mardi Gras", "Mare Nostrum", "Margaret", "Margaret's Museum", "Margie", "Margin Call", "Margin for Error", "Margot at the Wedding", "Maria Full of Grace", "Maria Rosa", "Marianne", "Maria's Lovers", "Marie Antoinette", "Marie Galante", "Marie", "Marie, Ltd.", "Marigold", "Marihuana", "Marilyn Hotchkiss' Ballroom Dancing and Charm School", "Marine Raiders", "Mariners of the Sky", "Marines, Let's Go", "Marjorie Morningstar", "Mark Felt: The Man Who Brought Down the White House", "Mark of the Damned", "Mark of the Gorilla", "Mark of the Lash", "Mark of the Vampire", "Marked for Death", "Marked for Murder", "Marked Men", "Marked Trails", "Marked Woman", "Marley & Me", "Marley", "Marlowe", "Marmaduke", "Marnie", "Marooned", "Marquis Preferred", "Marriage by Contract", "Marriage in Transit", "Marriage Is a Private Affair", "Marriage on the Rocks", "Married and in Love", "Married Bachelor", "Married Before Breakfast", "Married Flirts", "Married in Haste", "Married in Hollywood", "Married Life", "Married to the Mob", "Marry in Haste", "Marry Me Again", "Marry Me", "Marry the Boss's Daughter", "Marry the Girl", "Mars Attacks!", "Mars Needs Moms", "Mars Needs Women", "Marshal of Amarillo", "Marshal of Cedar Rock", "Marshal of Cripple Creek", "Marshal of Gunsmoke", "Marshal of Heldorado", "Marshal of Laredo", "Marshal of Reno", "Marshall", "Martha Marcy May Marlene", "Martha's Vindication", "Martian Child", "Martians Go Home", "Martin Luther", "Martin", "Marty", "Martyrs of the Alamo", "Marvin's Room", "Marwencol", "Mary Burns, Fugitive", "Mary Jane's Pa", "Mary Lou", "Mary of Scotland", "Mary of the Movies", "Mary Poppins Returns", "Mary Poppins", "Mary Queen of Scots", "Mary Regan", "Mary Reilly", "Mary Ryan, Detective", "Mary Shelley's Frankenstein", "Mary Stevens, M.D.", "Mary, Mary", "Maryam", "Maryland", "Mask of Murder", "Mask of the Avenger", "Mask of the Dragon", "Mask", "Masked and Anonymous", "Masked Emotions", "Masked Raiders", "Masked", "Mason of the Mounted", "Masquerade in Mexico", "Masquerade", "Mass Appeal", "Massacre at Central High", "Massacre Canyon", "Massacre", "Master and Commander: The Far Side of the World", "Master Minds", "Master of Men", "Master of the World", "Mastermind", "Masterminds", "Masters of Men", "Masters of Menace", "Masters of the Universe", "Masterson of Kansas", "Mata Hari", "Match Point", "Match", "Matching Dreams", "Matchstick Men", "Material Girls", "Matewan", "Matilda", "Matinee", "Matthew Barney: No Restraint", "Maurie", "Maverick", "Max Dugan Returns", "Max Keeble's Big Move", "Max Payne", "Max Steel", "Max", "Maxed Out", "Maxie", "Maximum Overdrive", "Maximum Risk", "May", "Maya", "Maybe It's Love", "Mayerling", "Mayhem", "Maytime", "Maze Runner: The Death Cure", "Maze", "McBain", "McCabe & Mrs. Miller", "McCanick", "McFadden's Flats", "McFarland, USA", "McGuire of the Mounted", "McHale's Navy Joins the Air Force", "McHale's Navy", "McKenna of the Mounted", "McLintock!", "McQ", "Me and Captain Kidd", "Me and Earl and the Dying Girl", "Me and My Gal", "Me and Orson Welles", "Me and the Colonel", "Me and the Kid", "Me and You and Everyone We Know", "Me Before You", "Me, Gangster", "Me, Myself & I", "Me, Myself & Irene", "Me, Natalie", "Mean Creek", "Mean Dog Blues", "Mean Girls", "Mean Guns", "Mean Johnny Barrows", "Mean Streets", "Meatballs 4", "Meatballs III: Summer Job", "Meatballs Part II", "Mechanic: Resurrection", "Meddling Women", "Medicine Man", "Medium Cool", "Medora", "Medusa: Dare to Be Truthful", "Meet Boston Blackie", "Meet Danny Wilson", "Meet Dave", "Meet Joe Black", "Meet John Doe", "Meet Me After the Show", "Meet Me at the Fair", "Meet Me in Las Vegas", "Meet Me in St. Louis", "Meet Me on Broadway", "Meet Miss Bobby Socks", "Meet Nero Wolfe", "Meet the Applegates", "Meet the Baron", "Meet the Blacks", "Meet the Boyfriend", "Meet the Browns", "Meet the Chump", "Meet the Deedles", "Meet the Fockers", "Meet the Girls", "Meet the Hollowheads", "Meet the Missus", "Meet the Parents", "Meet the People", "Meet the Prince", "Meet the Robinsons", "Meet the Spartans", "Meet the Stewarts", "Meet the Wildcat", "Meet Wally Sparks", "Mega Shark Versus Mecha Shark", "Megaforce", "Megamind", "Megan Leavey", "Megaville", "Megiddo: The Omega Code 2", "Mein Lieber Katrina Catches a Convict", "Mein Lieber Katrina", "Melba", "Melinda and Melinda", "Melinda", "Melody and Moonlight", "Melody Cruise", "Melody for Three", "Melody for Two", "Melody in Spring", "Melody Lane", "Melody Parade", "Melody Ranch", "Melody Time", "Melody Trail", "Melting Pot", "Melvin and Howard", "Memento", "Memoirs of a Geisha", "Memoirs of an Invisible Man", "Memories of Me", "Memory Run", "Memphis Belle", "Memphis Belle: A Story of a Flying Fortress", "Men and Women", "Men Are Like That", "Men Are Such Fools", "Men at Work", "Men Call It Love", "Men Don't Leave", "Men in Black 3", "Men in Black II", "Men in Black", "Men in Exile", "Men in Her Diary", "Men in Her Life", "Men in the Raw", "Men in War", "Men in White", "Men Must Fight", "Men of Action", "Men of America", "Men of Boys Town", "Men of Chance", "Men of Honor", "Men of Respect", "Men of Steel", "Men of Texas", "Men of the Fighting Lady", "Men of the Hour", "Men of the Night", "Men of the North", "Men of the Sky", "Men of War", "Men on Call", "Men on Her Mind", "Men Who Have Made Love to Me", "Men with Guns", "Men with Wings", "Men Without Names", "Men Without Women", "Men", "Men, Women, and Money", "Menace from Outer Space", "Menace II Society", "Menace", "Menashe", "Mercenaries", "Mercury Rising", "Mercy Island", "Mercy Streets", "Merely Mary Ann", "Meridian: Kiss of the Beast", "Merlin's Shop of Mystical Wonders", "Mermaids", "Merrill's Marauders", "Merrily We Go to Hell", "Merrily We Live", "Merry Andrew", "Merry Mavericks", "Merry-Go-Round", "Merton of the Movies", "Mesa of Lost Women", "Meshes of the Afternoon", "Message in a Bottle", "Message to Love", "Messenger of Death", "Messiah of Evil aka Dead People", "Metallica Through the Never", "Metalstorm: The Destruction of Jared-Syn", "Metamorphosis: The Alien Factor", "Meteor", "Metro", "Metropolitan", "Mexicali Rose", "Mexican Hayride", "Mexican Manhunt", "Mexican Spitfire at Sea", "Mexican Spitfire Sees a Ghost", "Mexican Spitfire", "Mexican Spitfire's Blessed Event", "Mexican Spitfire's Elephant", "Mexicana", "Mi ultimo amor", "Miami Blues", "Miami Connection", "Miami Expose", "Miami Rhapsody", "Miami Vice", "Miami", "Mice Follies", "Michael Clayton", "Michael Collins", "Michael Jackson's This Is It", "Michael O'Halloran", "Michael Shayne, Private Detective", "Michael Strogoff", "Michael", "Mickey Blue Eyes", "Mickey One", "Mickey", "Mickey's 60th Birthday", "Mickey's Christmas Carol", "Micki and Maude", "Micro-Phonies", "Mid90s", "Midas Run", "Middle Age Crazy", "Middle Men", "Middle of the Night", "Middle School: The Worst Years of My Life", "Midnight Alibi", "Midnight Club", "Midnight Court", "Midnight Cowboy", "Midnight Disturbance", "Midnight Express", "Midnight in Paris", "Midnight in the Garden of Good and Evil", "Midnight Intruder", "Midnight Lace", "Midnight Life", "Midnight Lovers", "Midnight Madness", "Midnight Madonna", "Midnight Manhunt", "Midnight Mary", "Midnight Molly", "Midnight Morals", "Midnight Mystery", "Midnight Phantom", "Midnight Rose", "Midnight Run for Your Life", "Midnight Run", "Midnight Runaround", "Midnight Special", "Midnight Sun", "Midnight Taxi", "Midnight Warning", "Midnight", "Midshipman Jack", "Midstream", "Midway Dance", "Midway", "Mighty Aphrodite", "Mighty Ducks the Movie: The First Face-Off", "Mighty Joe Young", "Mighty Lak' a Rose", "Mighty Morphin Power Rangers: The Movie", "Mike and Dave Need Wedding Dates", "Mike Hammer: Murder Takes All", "Mike", "Mike's Murder", "Mikey and Nicky", "Mildred Pierce", "Mile 22", "Mile-a-Minute Romeo", "Miles Ahead", "Miles Canyon Tramway", "Miles from Home", "Milestones", "Military Academy with That Tenth Avenue Gang", "Milk Money", "Milk", "Millennium", "Miller's Crossing", "Millie", "Millie's Daughter", "Million Dollar Arm", "Million Dollar Baby", "Million Dollar Kid", "Million Dollar Legs", "Million Dollar Mermaid", "Million Dollar Mystery", "Million Dollar Pursuit", "Million Dollar Ransom", "Million Dollar Weekend", "Millionaire Playboy", "Millionaires in Prison", "Millionaires", "Millions in the Air", "Mills of the Gods", "Milwaukee, Minnesota", "Mimic", "Min and Bill", "Mind Your Own Business", "Mindbenders", "Mindhunters", "Minesweeper", "Minions", "Mini's First Time", "Ministry of Fear", "Minnie and Moskowitz", "Minnie", "Minority Report", "Minotaur", "Minstrel Man", "Miracle at St. Anna", "Miracle Beach", "Miracle in Harlem", "Miracle in the Rain", "Miracle Mile", "Miracle of the White Stallions", "Miracle on 34th Street", "Miracle", "Miracles for Sale", "Miracles from Heaven", "Miracles", "Miraculous Journey", "Mirage", "Mirror Mirror", "MirrorMask", "Mirrors 2", "Mirrors", "Misbehaving Husbands", "Misbehaving Ladies", "Mischief", "Misery", "Mishima: A Life in Four Chapters", "Misleading Lady", "Mismates", "Miss Adventure", "Miss Annie Rooney", "Miss Bluebeard", "Miss Brewster's Millions", "Miss Congeniality 2: Armed and Fabulous", "Miss Congeniality", "Miss Dulcie from Dixie", "Miss Evers' Boys", "Miss Fane's Baby Is Stolen", "Miss Firecracker", "Miss Grant Takes Richmond", "Miss Julie", "Miss Lulu Bett", "Miss March", "Miss Mink of 1949", "Miss Nobody", "Miss Pacific Fleet", "Miss Peregrine's Home for Peculiar Children", "Miss Pinkerton", "Miss Polly", "Miss Potter", "Miss Robin Crusoe", "Miss Sadie Thompson", "Miss Susie Slagle's", "Miss Tatlock's Millions", "Miss V from Moscow", "Missile to the Moon", "Missile", "Missing Daughters", "Missing Evidence", "Missing Girls", "Missing in Action 2: The Beginning", "Missing in Action", "Missing Millions", "Missing Witnesses", "Missing Women", "Missing", "Mission Accomplished", "Mission Over Korea", "Mission to Mars", "Mission to Moscow", "Mission: Impossible – Fallout", "Mission: Impossible – Ghost Protocol", "Mission: Impossible 2", "Mission: Impossible III", "Mission: Impossible", "Mississippi Burning", "Mississippi Hare", "Mississippi Masala", "Mississippi", "Mistaken Orders", "Mister 880", "Mister Antonio", "Mister Big", "Mister Buddwing", "Mister Bug Goes to Town", "Mister Cory", "Mister Dynamite", "Mister Frost", "Mister Johnson", "Mister Lonely", "Mister Magoo's Christmas Carol", "Mister Moses", "Mister Roberts", "Mister Rock and Roll", "Mister Scoutmaster", "Mistress", "Misty", "Mitchell", "Mixed Company", "Mixed Faces", "Mixed Nuts", "M'Liss", "Mo' Better Blues", "Mo' Money", "Moana", "Mob Story", "Mob Town", "Mobs, Inc.", "Mobsters", "Moby Dick", "Moccasins", "Mockery", "Mockingbird Don't Sing", "Models Inc.", "Modern Girls", "Modern Husbands", "Modern Love", "Modern Man", "Modern Marriage", "Modern Matrimony", "Modern Mothers", "Modern Problems", "Modern Romance", "Modern Times", "Modigliani", "Mogambo", "Mohawk", "Mojados: Through the Night", "Mojave Firebrand", "Mojave Moon", "Moll Flanders", "Molly & Gina", "Molly and Me", "Molly of the Follies", "Molly's Game", "Mom and Dad Save the World", "Mom and Dad", "Moment by Moment", "Moment of Danger", "Moment to Moment", "Mommie Dearest", "'Moms' Night Out", "Mona Lisa Smile", "Mondovino", "Money and the Woman", "Money for Nothing", "Money from Home", "Money Mad", "Money Madness", "Money Matters", "Money Means Nothing", "Money Monster", "Money Talks", "Money to Burn", "Money Train", "Money, Money, Money", "Money, Women and Guns", "Moneyball", "Monkey Business", "Monkey Businessmen", "Monkey Kingdom", "Monkey on My Back", "Monkey Shines", "Monkey Trouble", "Monkey, Dog and Pony Circus", "Monkeybone", "Monkeys, Go Home!", "Monolith", "Monsieur Beaucaire", "Monsieur Verdoux", "Monsignor", "Monsoon Wedding", "Monsoon", "Monster A Go-Go", "Monster Camp", "Monster Family", "Monster from Green Hell", "Monster from the Ocean Floor", "Monster House", "Monster in a Box", "Monster in the Closet", "Monster Man", "Monster Mash", "Monster on the Campus", "Monster Trucks", "Monster", "Monster-in-Law", "Monster's Ball", "Monsters University", "Monsters vs. Aliens", "Monsters, Inc. 3D", "Monsters, Inc.", "Monstrosity", "Montana Belle", "Montana Desperado", "Montana Incident", "Montana Moon", "Montana Territory", "Montana", "Monte Carlo Nights", "Monte Carlo", "Monte Cristo", "Monterey Pop", "Montreal Fire Department on Runners", "Monty Roberts: A Real Horse Whisperer", "Monty Walsh", "Monument Ave.", "Moods of the Sea", "Moon 44", "Moon Over Burma", "Moon Over Harlem", "Moon Over Her Shoulder", "Moon Over Las Vegas", "Moon Over Miami", "Moon Over Montana", "Moon Over Parador", "Moon Pilot", "Moonfleet", "Moonlight and Cactus", "Moonlight and Pretzels", "Moonlight and Valentino", "Moonlight in Havana", "Moonlight in Hawaii", "Moonlight in Vermont", "Moonlight Masquerade", "Moonlight Mile", "Moonlight Murder", "Moonlight on the Prairie", "Moonrise Kingdom", "Moonrise", "Moonrunners", "Moonshine Valley", "Moonshine", "Moonstruck", "Moontide", "Moontrap", "Moonwalker", "Morals for Men", "Morals for Women", "Moran of the Lady Letty", "Moran of the Marines", "Moran of the Mounted", "More American Graffiti", "More Dead Than Alive", "More Deadly Than the Male", "More Pay, Less Work", "More Than a Secretary", "More to Be Pitied Than Scorned", "Morgan Stewart's Coming Home", "Morgan", "Morgan, the Pirate", "Morganson's Finish", "Morituri", "Morning Glory", "Morocco", "Mortal Engines", "Mortal Kombat", "Mortal Kombat: Annihilation", "Mortal Kombat: The Journey Begins", "Mortal Thoughts", "Mortdecai", "Moscow on the Hudson", "Moses in the Bullrushes", "Moss Rose", "Most Dangerous Man Alive", "Most Wanted", "Motel Hell", "Mother and Child", "Mother Carey's Chickens", "Mother Didn't Tell Me", "Mother Goose Rock 'n' Rhyme", "Mother Is a Freshman", "Mother Knows Best", "Mother Lode", "Mother Machree", "Mother Night", "Mother O' Mine", "Mother Wore Tights", "Mother!", "Mother", "Mother, Jugs & Speed", "Motherhood", "Mothers Cry", "Mother's Day", "Mothers-in-Law", "Motion Painting No. 1", "Motive for Revenge", "Motor Madness", "Motor Mania", "Motor Patrol", "Motorpsycho", "Moulin Rouge!", "Moulin Rouge", "Mountain Justice", "Mountain Mary", "Mountain Moonlight", "Mountain Music", "Mountain Rhythm", "Mountains of the Moon", "Mounted Police Charge", "Mourning Becomes Electra", "Mouse Cleaning", "Mouse for Sale", "Mouse Into Space", "Mouse Trouble", "MouseHunt", "Move On", "Move Over, Darling", "Move", "Movers & Shakers", "Movie 43", "Movie Crazy", "Movie Movie", "Moving Day; or, No Children Allowed", "Moving McAllister", "Moving Violation", "Moving Violations", "Moving", "MPG: Motion Picture Genocide", "Mr. & Mrs. Smith", "Mr. 3000", "Mr. Ace", "Mr. and Mrs. America", "Mr. and Mrs. Bridge", "Mr. Arkadin", "Mr. B Natural", "Mr. Barnes of New York", "Mr. Baseball", "Mr. Belvedere Goes to College", "Mr. Belvedere Rings the Bell", "Mr. Billings Spends His Dime", "Mr. Billion", "Mr. Blandings Builds His Dream House", "Mr. Boggs Steps Out", "Mr. Broadway", "Mr. Brooks", "Mr. Bug Goes to Town", "Mr. Cinderella", "Mr. Death", "Mr. Deeds Goes to Town", "Mr. Deeds", "Mr. Destiny", "Mr. District Attorney", "Mr. Dodd Takes the Air", "Mr. Doodle Kicks Off", "Mr. Duck Steps Out", "Mr. Fix-It", "Mr. Hex", "Mr. Hobbs Takes a Vacation", "Mr. Holland's Opus", "Mr. Imperium", "Mr. Jones Has a Card Party", "Mr. Jones", "Mr. Lemon of Orange", "Mr. Lucky", "Mr. Magoo", "Mr. Magorium's Wonder Emporium", "Mr. Majestyk", "Mr. Mike's Mondo Video", "Mr. Mom", "Mr. Moto in Danger Island", "Mr. Moto Takes a Chance", "Mr. Moto Takes a Vacation", "Mr. Moto's Gamble", "Mr. Muggs Rides Again", "Mr. Muggs Steps Out", "Mr. Music", "Mr. Nanny", "Mr. Nice Guy", "Mr. North", "Mr. Peabody & Sherman", "Mr. Peabody and the Mermaid", "Mr. Popper's Penguins", "Mr. Reckless", "Mr. Ricco", "Mr. Robinson Crusoe", "Mr. Roosevelt", "Mr. Saturday Night", "Mr. Schneider Goes to Washington", "Mr. Skeffington", "Mr. Skitch", "Mr. Smith Goes to Washington", "Mr. Soft Touch", "Mr. Universe", "Mr. Untouchable", "Mr. Walkie Talkie", "Mr. Winkle Goes to War", "Mr. Wonderful", "Mr. Wong in Chinatown", "Mr. Wong, Detective", "Mr. Woodcock", "Mr. Wrong", "Mrs Dalloway", "Mrs. 'Arris Goes to Paris", "Mrs. Doubtfire", "Mrs. Jones Entertains", "Mrs. Mike", "Mrs. Miniver", "Mrs. O'Malley and Mr. Malone", "Mrs. Palfrey at the Claremont", "Mrs. Parker and the Vicious Circle", "Mrs. Parkington", "Mrs. Pollifax-Spy", "Mrs. Santa Claus", "Mrs. Soffel", "Mrs. Wiggs of the Cabbage Patch", "Mrs. Winterbourne", "Ms. 45", "Much Ado About Nothing", "Mucho Mouse", "Muck", "Mud and Sand", "Mud", "Mudhoney", "Mugsy's Girls", "Mulan", "Mule Train", "Mulhall's Greatest Catch", "Mulholland Drive", "Mulholland Falls", "Multi-Facial", "Multiplicity", "Mumford", "Mummy's Boys", "Munchies", "Munich", "Munster, Go Home!", "Muppet Treasure Island", "Muppets from Space", "Muppets Most Wanted", "Murder 101", "Murder at 1600", "Murder at Glen Athol", "Murder at Midnight", "Murder at the Vanities", "Murder by an Aristocrat", "Murder by Contract", "Murder by Death", "Murder by Decree", "Murder by Invitation", "Murder by Numbers", "Murder by Television", "Murder by the Clock", "Murder Goes to College", "Murder in Greenwich Village", "Murder in Harlem", "Murder in Mississippi", "Murder in the Air", "Murder in the Big House", "Murder in the Blue Room", "Murder in the Clouds", "Murder in the First", "Murder in the Fleet", "Murder in the Music Hall", "Murder in the Private Car", "Murder in Times Square", "Murder in Trinidad", "Murder Is My Beat", "Murder Is My Business", "Murder on a Bridle Path", "Murder on a Honeymoon", "Murder on a Sunday Morning", "Murder on the Blackboard", "Murder on the Campus", "Murder on the Orient Express", "Murder on the Roof", "Murder on the Waterfront", "Murder Over New York", "Murder Scene from 'King of the Detectives'", "Murder Will Out", "Murder with Pictures", "Murder Without Tears", "Murder, He Says", "Murder, Inc.", "Murder, My Sweet", "Murderball", "Murderers' Row", "Murders in the Rue Morgue", "Murders in the Zoo", "Murphy's Law", "Murphy's Romance", "Murphy's Wake", "Murphy's War", "Muscle Beach Party", "Muscle Beach Tom", "Muscle Up a Little Closer", "Music and Lyrics", "Music Box", "Music for Madame", "Music for Millions", "Music from Another Room", "Music in Manhattan", "Music in My Heart", "Music in the Air", "Music Is Magic", "Music Man", "Music of the Heart", "Muss 'Em Up", "Must Love Dogs", "Mustang Sally", "Mutiny Ahead", "Mutiny on the Bounty", "Mutiny on the Bunny", "Mutiny", "My 5 Wives", "My American Wife", "My Antonia", "My Baby's Daddy", "My Best Friend Is a Vampire", "My Best Friend's Birthday", "My Best Friend's Girl", "My Best Friend's Wedding", "My Best Gal", "My Best Girl", "My Big Break", "My Big Fat Greek Wedding 2", "My Big Fat Greek Wedding", "My Bill", "My Bloody Valentine 3D", "My Blue Heaven", "My Bodyguard", "My Bollywood Bride", "My Boss's Daughter", "My Boy", "My Brother Talks to Horses", "My Brother's Wedding", "My Buddy", "My Bunny Lies Over The Sea", "My Chauffeur", "My Country, My Country", "My Cousin Rachel", "My Cousin Vinny", "My Dad", "My Darling Clementine", "My Dear Miss Aldrich", "My Dear Secretary", "My Dinner with Andre", "My Dog Rusty", "My Dog Shep", "My Dog Skip", "My Dream Is Yours", "My Entire High School Sinking Into the Sea", "My Fair Lady", "My Family", "My Father the Hero", "My Favorite Blonde", "My Favorite Brunette", "My Favorite Duck", "My Favorite Martian", "My Favorite Spy", "My Favorite Wife", "My Favorite Year", "My Fellow Americans", "My First Mister", "My Flesh and Blood", "My Foolish Heart", "My Forbidden Past", "My Friend Flicka", "My Friend Irma Goes West", "My Friend Irma", "My Friend, the Devil", "My Gal Loves Music", "My Gal Sal", "My Geisha", "My Giant", "My Girl 2", "My Girl Tisa", "My Girl", "My Gun Is Quick", "My Heart Belongs to Daddy", "My Husband's Wives", "My Japan", "My Kingdom for a Cook", "My Lady of Whims", "My Lady's Garter", "My Lady's Lips", "My Lady's Past", "My Life in Ruins", "My Life with Caroline", "My Life", "My Lips Betray", "My Little Chickadee", "My Little Girl", "My Little Pony: The Movie", "My Little Sister", "My Love Came Back", "My Lover, My Son", "My Lucky Star", "My Man and I", "My Man Godfrey", "My Man Is a Loser", "My Man", "My Marriage", "My Mom's a Werewolf", "My Name is Bill W.", "My Name Is Julia Ross", "My New Gun", "My Old Kentucky Home", "My One and Only", "My Outlaw Brother", "My Own Pal", "My Own Private Idaho", "My Own True Love", "My Pal Gus", "My Pal Trigger", "My Pal Wolf", "My Pal, the King", "My Past", "My Reputation", "My Sassy Girl", "My Science Project", "My Sexiest Year", "My Side of the Mountain", "My Sin", "My Sister Eileen", "My Sister's Keeper", "My Six Convicts", "My Six Loves", "My Son Is Guilty", "My Son John", "My Son", "My Son, My Son!", "My Son, the Hero", "My Soul to Take", "My Stars", "My Stepmother Is an Alien", "My Summer Story", "My Super Ex-Girlfriend", "My Teacher Ate My Homework", "My True Story", "My Tutor", "My Voyage to Italy", "My Weakness", "My Wife and I", "My Wife's Best Friend", "My Wife's Relations", "My Wild Irish Rose", "My Woman", "Myra Breckinridge", "Mysterious Crossing", "Mysterious Doctor Satan", "Mysterious Intruder", "Mysterious Island", "Mysterious Mr. Moto", "Mysterious Skin", "Mystery Broadcast", "Mystery Date", "Mystery House", "Mystery in Mexico", "Mystery Liner", "Mystery Man", "Mystery Men", "Mystery of the 13th Guest", "Mystery of the River Boat", "Mystery of the Wax Museum", "Mystery Ranch", "Mystery Science Theater 3000: The Movie", "Mystery Sea Raider", "Mystery Street", "Mystery Submarine", "Mystery Train", "Mystery Woman", "Mystery, Alaska", "Mystic Pizza", "Mystic River", "N.Y., N.Y.", "Nabonga", "Nacho Libre", "Nadine", "Nagana", "Naked Alibi", "Naked Angels", "Naked Came The Stranger", "Naked Gun 33⅓: The Final Insult", "Naked Gun", "Naked in New York", "Naked Lunch", "Naked Paradise", "Name the Man", "Name the Woman", "Nameless Men", "Nam's Angels", "Nana", "Nancy Drew and the Hidden Staircase", "Nancy Drew", "Nancy Drew... Detective", "Nancy Drew... Reporter", "Nancy Drew… Trouble Shooter", "Nancy from Nowhere", "Nancy Goes to Rio", "Nancy Steele Is Missing!", "Nanking", "Nanny McPhee Returns", "Nanny McPhee", "Nanook of the North", "Napoleon and Samantha", "Napoleon and the Empress Josephine", "Napoleon Dynamite", "Narc", "Narcotic", "Narrow Margin", "Nashville Rebel", "Nashville", "Nate and Hayes", "National Lampoon Presents Dorm Daze", "National Lampoon's Animal House", "National Lampoon's Christmas Vacation", "National Lampoon's European Vacation", "National Lampoon's Gold Diggers", "National Lampoon's Joy of Sex", "National Lampoon's Last Resort", "National Lampoon's Senior Trip", "National Lampoon's Vacation", "National Lampoon's Van Wilder", "National Lampoon's Van Wilder: The Rise of Taj", "National Security", "National Treasure", "National Treasure: Book of Secrets", "National Velvet", "Natural Born Killers", "Nature's Touch", "Naughty Baby", "Naughty but Nice", "Naughty Marietta", "Navajo Kid", "Navajo Trail Raiders", "Navy Blue and Gold", "Navy Blues", "Navy Bound", "Navy SEALs", "Navy Spy", "Navy Wife", "Nazi Agent", "Neapolitan Mouse", "Near Dark", "Near the Rainbow's End", "Nearly Eighteen", "'Neath Brooklyn Bridge", "Neath the Arizona Skies", "Nebraska", "Necessary Roughness", "Necromancer", "Necromancy", "Necromania", "Need for Speed", "Needful Things", "Negatives", "Negro Colleges in War Time", "Neighborhood House", "Neighbors 2: Sorority Rising", "Neighbors", "Neil Young: Heart of Gold", "Nell", "Nellie, the Beautiful Cloak Model", "Nemesis", "Neptune's Daughter", "Nero", "Nerve", "Nervy Nat Kisses the Bride", "Netherbeast Incorporated", "Network", "Nevada Badmen", "Nevada Smith", "Nevada", "Never a Dull Moment", "Never Back Down", "Never Been Kissed", "Never Been Thawed", "Never Cry Wolf", "Never Die Alone", "Never Fear", "Never Give a Sucker an Even Break", "Never Goin' Back", "Never Let Me Go", "Never on Sunday", "Never on Tuesday", "Never Say Die", "Never Say Goodbye", "Never Say Quit", "Never So Few", "Never Steal Anything Small", "Never Talk to Strangers", "Never the Twain Shall Meet", "Never Too Late", "Never Touched Me", "Never Trust a Gambler", "Never Wave at a WAC", "Never Weaken", "Neverwas", "New Adventures of Get Rich Quick Wallingford", "New Best Friend", "New Brooms", "New Faces of 1937", "New Faces", "New Frontier", "New in Town", "New Jack City", "New Jersey Drive", "New Life Rescue", "New Lives for Old", "New Mexico", "New Moon", "New Morals for Old", "New Morning Bath", "New Movietone Follies of 1930", "New Orleans Uncensored", "New Orleans", "New Rose Hotel", "New Toys", "New Year's Eve", "New York Confidential", "New York Doll", "New York Harbor Police Boat Patrol Capturing Pirates", "New York Minute", "New York Nights", "New York Stories", "New York Subway", "New York Town", "New York, I Love You", "New York, New York", "Newman's Law", "News Hounds", "News Is Made at Night", "News Parade", "Newsboys' Home", "Newsies", "Next Aisle Over", "Next Day Air", "Next Friday", "Next of Kin", "Next Stop Wonderland", "Next Stop, Greenwich Village", "Next Time I Marry", "Next Time We Love", "Next", "Niagara Falls", "Niagara", "Niagara, Niagara", "Nice Dreams", "Nice Girl?", "Nice People", "Nice Women", "Nicholas Nickleby", "Nick and Norah's Infinite Playlist", "Nick Carter, Master Detective", "Nick Knight", "Nick of Time", "Nickelodeon", "Night After Night", "Night and Day", "Night and the City", "Night at the Golden Eagle", "Night at the Museum", "Night at the Museum: Battle of the Smithsonian", "Night at the Museum: Secret of the Tomb", "Night Cargo", "Night Catches Us", "Night Club Girl", "Night Club Scandal", "Night Court", "Night Crossing", "Night Editor", "Night Eyes", "Night Falls on Manhattan", "Night Flight", "Night Game", "Night Has a Thousand Eyes", "Night in New Orleans", "Night in Paradise", "Night into Morning", "Night Key", "Night Life in Reno", "Night Life of New York", "Night Life of the Gods", "Night Monster", "Night Moves", "Night Must Fall", "Night Nurse", "Night of Dark Shadows", "Night of Mystery", "Night of Terror", "Night Of The Axe", "Night of the Comet", "Night of the Creeps", "Night of the Demon", "Night of the Demons 3", "Night of the Ghouls", "Night of the Lepus", "Night of the Living Dead 3D", "Night of the Living Dead", "Night of the Ninja", "Night of the Quarter Moon", "Night on Earth", "Night Parade", "Night Passage", "Night Patrol", "Night People", "Night Plane from Chungking", "Night Raiders", "Night Ride", "Night Riders of Montana", "Night School", "Night Shift", "Night Song", "Night Spot", "Night Stage to Galveston", "Night Tide", "Night Time in Nevada", "Night Train to Memphis", "Night Unto Night", "Night Waitress", "Night Warning", "Night Watch", "Night Wind", "Night Without Sleep", "Night Work", "Night World", "'night, Mother", "Nightbreed", "Nightcrawler", "Nightfall", "Nighthawks", "Nightkill", "Nightmare Alley", "Nightmare at Bittercreek", "Nightmare Honeymoon", "Nightmare in the Sun", "Nightmare in Wax", "Nightmare Man", "Nightmare", "Nightmares", "Nights in Rodanthe", "Nightwatch", "Nightwing", "Nightwish", "Nijinsky", "Nim's Island", "Nine Girls", "Nine Hours to Rama", "Nine Lives Are Not Enough", "Nine Lives", "Nine Months", "Nine to Five", "Nine", "Nine-Tenths of the Law", "Ninja Academy", "Ninja Assassin", "Ninja III: The Domination", "Ninja Strike Force", "Ninotchka", "Ninth Street", "Nitrate Kisses", "Nit-Witty Kitty", "Nix on Dames", "Nixon", "No Census, No Feeling", "No Country for Old Men", "No Defense", "No Deposit, No Return", "No Dessert, Dad, till You Mow the Lawn", "No Direction Home", "No Dough Boys", "No Down Payment", "No End in Sight", "No Escape", "No Exit", "No Good Deed", "No Greater Glory", "No Hands on the Clock", "No Holds Barred", "No Leave, No Love", "No Limit", "No Living Witness", "No Looking Back", "No Man Is an Island", "No Man of Her Own", "No Man's Gold", "No Man's Land", "No Man's Law", "No Man's Woman", "No Marriage Ties", "No Mercy", "No Minor Vices", "No More Ladies", "No More Orchids", "No More Women", "No Mother to Guide Her", "No Name on the Bullet", "No One Lives", "No One Man", "No Other Woman", "No Place for a Lady", "No Place to Go", "No Place to Hide", "No Place to Land", "No Questions Asked", "No Ransom", "No Reservations", "No Retreat, No Surrender 2", "No Retreat, No Surrender 3: Blood Brothers", "No Retreat, No Surrender", "No Room for the Groom", "No Sad Songs for Me", "No Small Affair", "No Smoking", "No Strings Attached", "No Time for Comedy", "No Time for Flowers", "No Time for Love", "No Time for Sergeants", "No Time to Be Young", "No Time to Marry", "No Trespassing", "No Way Out", "No Way to Treat a Lady", "No Woman Knows", "No, No, Nanette", "Noah", "Noah's Ark", "Nob Hill", "Nobel Son", "Nobody Home", "Nobody Lives Forever", "Nobody Runs Forever", "Nobody Walks", "Nobody's Baby", "Nobody's Bride", "Nobody's Darling", "Nobody's Fool", "Nobody's Money", "Nobody's Perfect", "Nobody's Perfekt", "Nobody's Widow", "Nocturnal Tunes", "Nocturne", "Noel", "Noises Off", "Nomads of the North", "Nomads", "None but the Brave", "None But the Lonely Heart", "None Shall Escape", "Non-Stop", "Noon Wine", "Noose for a Gunman", "Nora Prentiss", "Norbit", "Norm of the North", "Norma Jean & Marilyn", "Norma Rae", "Normal Adolescent Behavior", "North by Northwest", "North Country", "North Dallas Forty", "North of 36", "North of Arizona", "North of Hudson Bay", "North of Nevada", "North of Nome", "North of the Great Divide", "North of the Rio Grande", "North Shore", "North Star", "North to Alaska", "North West Mounted Police", "North", "Northern Code", "Northern Lights", "Northern Patrol", "Northern Pursuit", "Northwest Outpost", "Northwest Passage", "Northwest Rangers", "Northwest Stampede", "Northwest Territory", "Northwest Trail", "Norwood", "Nostalgia", "Not a Drum Was Heard", "Not Another Teen Movie", "Not as a Stranger", "Not Damaged", "Not Easily Broken", "Not Exactly Gentlemen", "Not for Publication", "Not Forgotten", "Not Guilty", "Not of This Earth", "Not Quite Decent", "Not So Dumb", "Not So Long Ago", "Not with My Wife, You Don't!", "Not Without My Daughter", "Nothin' 2 Lose", "Nothing But a Man", "Nothing but the Truth", "Nothing But Trouble", "Nothing in Common", "Nothing Lasts Forever", "Nothing Like the Holidays", "Nothing Personal", "Nothing Sacred", "Nothing to Lose", "Nothing to Wear", "Notoriety", "Notorious But Nice", "Notorious", "Notting Hill", "November", "Novitiate", "Novocaine", "Now and Forever", "Now and Then", "Now I'll Tell", "Now or Never", "Now You See Him, Now You Don't", "Now You See Me 2", "Now You See Me", "Now, Voyager", "Nowhere to Hide", "Nowhere to Run", "Nowhere", "Nude on the Moon", "Nugget Nell", "Numb", "Number One with a Bullet", "Number One", "Number, Please?", "Numbered Men", "Nurse 3D", "Nurse Betty", "Nurse Edith Cavell", "Nuts", "Nutty But Nice", "Nutty Professor II: The Klumps", "O Brother, Where Art Thou?", "O Jerusalem", "O Pioneers!", "O", "O, My Darling Clementine", "O. Henry's Full House", "O.C. and Stiggs", "O.S.S.", "Oath of Vengeance", "Oath-Bound", "Obey the Law", "Object: Alimony", "Objective, Burma!", "Obliging Young Lady", "Oblivion", "Observe and Report", "Obsessed", "Obsession", "Obvious Child", "Occupation 101", "Ocean's 11", "Ocean's 8", "Ocean's Eleven", "Ocean's Thirteen", "Ocean's Twelve", "Octaman", "October 22", "October Sky", "Oculus", "Odds Against Tomorrow", "Ode to Billy Joe", "Of Fox and Hounds", "Of Human Bondage", "Of Human Hearts", "Of Love and Desire", "Of Mice and Men", "Of Stars and Men", "Of Unknown Origin", "Off Beat", "Off Limits", "Off the Highway", "Off the Record", "Off the Trolley", "Off to the Races", "Offerings", "Office Killer", "Office Space", "Officer Down", "Officer Thirteen", "Oh Billy, Behave", "Oh Boy!", "Oh Dad, Poor Dad, Mamma's Hung You in the Closet and I'm Feelin' So Sad", "Oh Doctor!", "Oh Heavenly Dog", "Oh Sailor Behave", "Oh! Susanna", "Oh! What a Nurse!", "Oh, Baby!", "Oh, Doctor", "Oh, For a Man!", "Oh, God! Book II", "Oh, God!", "Oh, Kay!", "Oh, Men! Oh, Women!", "Oh, Saigon", "Oh, Susanna!", "Oh, What a Night", "Oh, You Beautiful Doll", "Oh, You Tony!", "Oh, You Women!", "Oil and Water", "Oil for the Lamps of China", "Oil's Well That Ends Well", "Oily Hare", "Oily to Bed, Oily to Rise", "Okay, America!", "Okinawa Bulletins", "Okinawa", "Okja", "Oklahoma Annie", "Oklahoma Badlands", "Oklahoma Blues", "Oklahoma Crude", "Oklahoma Cyclone", "Oklahoma Justice", "Oklahoma Raiders", "Oklahoma Territory", "Oklahoma Terror", "Oklahoma!", "Old Acquaintance", "Old Clothes", "Old Dogs", "Old English", "Old Enough to Be Her Grandpa", "Old Enough", "Old Gringo", "Old Home Week", "Old Hutch", "Old Irish Cabin", "Old Ironsides", "Old Joy", "Old Lady 31", "Old Los Angeles", "Old Louisiana", "Old Man Rhythm", "Old Oklahoma Plains", "Old Overland Trail", "Old Rockin' Chair Tom", "Old School", "Old Shoes", "Old Wives for New", "Old Yeller", "Oldboy", "Oliver & Company", "Oliver Twist", "Oliver's Story", "Ollie Hopnoodle's Haven of Bliss", "Olly Olly Oxen Free", "Olsen's Big Moment", "Olympus Has Fallen", "O'Malley of the Mounted", "Omar Khayyam", "Omar the Tentmaker", "Omega Doom", "On a Clear Day You Can See Forever", "On Account", "On Again-Off Again", "On an Island with You", "On Borrowed Time", "On Chesil Beach", "On Dangerous Ground", "On Deadly Ground", "On Dress Parade", "On Golden Pond", "On Line", "On Moonlight Bay", "On Our Merry Way", "On Probation", "On Record", "On Stage Everybody", "On Such a Night", "On the 2nd Day of Christmas", "On the Avenue", "On the Banks of the Wabash", "On the Basis of Sex", "On the Beach", "On the Black Hill", "On the Border", "On the Bow River Horse Ranch at Cochrane, North West Territory", "On the Double", "On the Edge of Innocence", "On the Edge", "On the Fire", "On the High Seas", "On the Isle of Samoa", "On the Jump", "On the Level", "On the Line", "On the Loose", "On the Old Spanish Trail", "On the Right Track", "On the Riviera", "On the Stroke of Three", "On the Sunny Side", "On the Threshold of Space", "On the Town", "On the Waterfront", "On Their Own", "On Thin Ice", "On Time", "On Top of Old Smoky", "On Trial", "On with the Show!", "On with the Show", "On Your Back", "On Your Toes", "Once a Doctor", "Once a Gentleman", "Once a Lady", "Once a Sinner", "Once a Thief", "Once Around", "Once Before I Die", "Once Bitten", "Once in a Blue Moon", "Once in a Lifetime", "Once in a Lifetime: The Extraordinary Story of the New York Cosmos", "Once Is Not Enough", "Once More, My Darling", "Once More, with Feeling!", "Once to Every Bachelor", "Once to Every Woman", "Once Upon a Crime", "Once Upon a Forest", "Once Upon a Honeymoon", "Once Upon a Horse...", "Once Upon a Scoundrel", "Once Upon a Time in America", "Once Upon a Time in Mexico", "Once Upon a Time", "One A.M.", "One Away", "One Big Affair", "One Body Too Many", "One Clear Call", "One Crazy Summer", "One Dangerous Night", "One Day", "One Desire", "One Direction: This Is Us", "One Eight Seven", "One Exciting Adventure", "One Exciting Night", "One Exciting Week", "One False Move", "One Fine Day", "One Flew Over the Cuckoo's Nest", "One Foot in Heaven", "One Foot in Hell", "One for the Money", "One Frightened Night", "One Froggy Evening", "One from the Heart", "One Girl's Confession", "One Glorious Day", "One Glorious Night", "One Good Cop", "One Got Fat", "One Heavenly Night", "One Hour Before Dawn", "One Hour Late", "One Hour Photo", "One Hour with You", "One Hundred and One Dalmatians", "One Hundred Men and a Girl", "One Hysterical Night", "One in a Million", "One Is a Lonely Number", "One Is Guilty", "One Last Fling", "One Law for All", "One Law for the Woman", "One Little Indian", "One Mad Kiss", "One Magic Christmas", "One Man Justice", "One Man's Hero", "One Man's Journey", "One Man's Law", "One Man's Way", "One Mile from Heaven", "One Million B.C.", "One Minute to Play", "One Minute to Zero", "One Missed Call", "One More River", "One More Saturday Night", "One More Spring", "One More Time", "One More Tomorrow", "One More Train to Rob", "One Mysterious Night", "One New York Night", "One Night at McCool's", "One Night at Susie's", "One Night in Lisbon", "One Night in Rome", "One Night in the Tropics", "One Night of Love", "One Night Stand", "One Night with the King", "One of Our Dinosaurs Is Missing", "One of the Bravest", "One of the Finest", "One on One", "One Potato, Two Potato", "One Punch O'Day", "One Rainy Afternoon", "One Romantic Night", "One Stolen Night", "One Summer Love", "One Sunday Afternoon", "One Sunday Morning", "One Survivor Remembers", "One Thrilling Night", "One Too Many aka Killer with a Label", "One Touch of Nature", "One Touch of Venus", "One Tough Cop", "One Trick Pony", "One True Thing", "One Way Passage", "One Way Street", "One Way Ticket", "One Way to Love", "One Week of Life", "One Week of Love", "One Week", "One Wild Night", "One Wonderful Night", "One Year to Live", "One, Two, Three", "One-Eyed Jacks", "One-Thing-At-a-Time O'Day", "Onionhead", "Only 38", "Only a Shop Girl", "Only a Soldier Boy", "Only Angels Have Wings", "Only for You", "Only Saps Work", "Only the Brave", "Only the Lonely", "Only the Strong", "Only the Valiant", "Only Two Can Play", "Only When I Laugh", "Only Yesterday", "Only You", "Open All Night", "Open Grave", "Open Range", "Open Season", "Open Secret", "Open Water", "Open Your Eyes", "Opened by Mistake", "Opening Night", "Operation Bikini", "Operation C.I.A.", "Operation Crossbow", "Operation Dames", "Operation Dumbo Drop", "Operation Finale", "Operation Haylift", "Operation Homecoming: Writing the Wartime Experience", "Operation Mad Ball", "Operation Pacific", "Operation Petticoat", "Operation Secret", "Operation: Daybreak", "Operation: Rabbit", "Operator 13", "Opie Gets Laid", "Opportunity Knocks", "Orange County", "Orca", "Orchestra Wives", "Orchids to You", "Ordeal by Innocence", "Ordinary People", "Oregon Passage", "Oregon Trail Scouts", "Oregon Trail", "Orgazmo", "Orient Express", "Orientation", "Original Gangstas", "Original Sin", "Orphan", "Orphans of the Storm", "Orphans of the Street", "Orphans", "Oscar and Lucinda", "Oscar", "O'Shaughnessy's Boy", "Osmosis Jones", "Otaku Unite!", "Othello", "Other Men's Daughters", "Other Men's Wives", "Other Men's Women", "Other People's Money", "Other Women's Husbands", "Ouija", "Ouija: Origin of Evil", "Our Better Selves", "Our Betters", "Our Blushing Brides", "Our Daily Bread", "Our Dancing Daughters", "Our Enemy- The Japanese", "Our Family Wedding", "Our Friend, Martin", "Our Hearts Were Growing Up", "Our Hearts Were Young and Gay", "Our Hospitality", "Our Idiot Brother", "Our Job in Japan", "Our Leading Citizen", "Our Little Girl", "Our Man Flint", "Our Man in Marrakesh", "Our Miss Brooks", "Our Modern Maidens", "Our Neighbors - The Carters", "Our New Cook", "Our Relations", "Our Son, the Matchmaker", "Our Song", "Our Town", "Our Very Own", "Our Vines Have Tender Grapes", "Out All Night", "Out California Way", "Out Cold", "Out for Justice", "Out o' Luck", "Out of Africa", "Out of Bounds", "Out of Darkness", "Out of Luck", "Out of Season", "Out of Sight", "Out of Singapore", "Out of the Black", "Out of the Blue", "Out of the Dark", "Out of the Depths", "Out of the Fog", "Out of the Furnace", "Out of the Inkwell", "Out of the Past", "Out of the Ruins", "Out of the Shadow", "Out of the Silent North", "Out of the Storm", "Out of the West", "Out of This World", "Out of Time", "Out on a Limb", "Out to Sea", "Out West with the Hardys", "Out West with the Peppers", "Out West", "Out Yonder", "Outbreak", "Outcast Lady", "Outcast of Black Mesa", "Outcast", "Outcasts of the City", "Outcasts of the Trail", "Outer Space Jitters", "Outlaw Blues", "Outlaw Brand", "Outlaw Express", "Outlaw Gold", "Outlaw Roundup", "Outlaw Trail", "Outlaw Treasure", "Outlaw Women", "Outlawed Guns", "Outlawed", "Outlaws of Pine Ridge", "Outlaws of Red River", "Outlaws of Santa Fe", "Outlaws of Sonora", "Outlaws of Stampede Pass", "Outlaws of Texas", "Outlaws of the Orient", "Outlaws of the Plains", "Outlaws of the Prairie", "Outlaws of the Rockies", "Outlaws' Paradise", "Outlaw's Son", "Outpost in Morocco", "Outrage", "Outrageous Fortune", "Outside of Paradise", "Outside Ozona", "Outside the Law", "Outside the Wall", "Outside These Walls", "Outward Bound", "Outwitted", "Over 21", "Over Her Dead Body", "Over My Dead Body", "Over the Border", "Over the Brooklyn Bridge", "Over the Edge", "Over the Fence", "Over the Garden Wall", "Over the Goal", "Over the Hedge", "Over the Hill to the Poorhouse", "Over the Hill", "Over the Santa Fe Trail", "Over the Top", "Over the Wall", "Over the Wire", "Overboard", "Over-Exposed", "Overland Mail Robbery", "Overland Mail", "Overland Pacific", "Overland Riders", "Overland Stage Raiders", "Overland Telegraph", "Overland Trails", "Overlord", "Overnight Delivery", "Oxford Blues", "Oz the Great and Powerful", "P.J.", "P.S. I Love You", "P.S.", "P.U.N.K.S.", "P2", "Pacific Blackout", "Pacific Heights", "Pacific Liner", "Pacific Rendezvous", "Pacific Rim Uprising", "Pacific Rim", "Pack Train", "Pack Up Your Troubles", "Paddington", "Paddy O'Day", "Paddy the Next Best Thing", "Padlocked", "Padre Nuestro", "Pagan Love Song", "Pagan Passions", "Page Miss Glory", "Page One: A Year Inside the New York Times", "Paid Back", "Paid in Advance", "Paid in Full", "Paid to Dance", "Paid to Love", "Paid", "Pain & Gain", "Paint and Powder", "Paint Your Wagon", "Painted Angels", "Painted Desert", "Painted People", "Painted Post", "Painters Painting", "Painting the Clouds with Sunshine", "Pajama Party", "Pal Joey", "Pal o' Mine", "Pale Rider", "Palindromes", "Palm Springs Weekend", "Palm Springs", "Palmetto", "Palmy Days", "Palominas", "Palooka", "Palookaville", "Pals First", "Pals in Paradise", "Pals of the Golden West", "Pals of the Saddle", "Pals", "Pampered Youth", "Panama Flo", "Panama Hattie", "Panama Lady", "Panama Sal", "Pan-American Exposition by Night", "Pan-Americana", "Panamint's Bad Man", "Pancho Villa", "Pandas", "Pandemonium", "Pandora and the Flying Dutchman", "Pandora's Clock", "Pandorum", "Panhandle", "Panic in the City", "Panic in the Streets", "Panic in Year Zero!", "Panic on the Air", "Panic Room", "Panic", "Panorama of the Exposition, No. 1", "Panorama of the Exposition, No. 2", "Panorama of the Lakes of Killarney from Hotel", "Panoramic View of the Fleet After Yacht Race", "Panoramic View of the Temple of Music and Esplanade", "Panoramic View, Asheville, N.C.", "Pan's Labyrinth", "Panthea", "Panther Girl of the Kongo", "Panther", "Pantry Panic", "Paparazzi", "Papa's Delicate Condition", "Paper Bullets", "Paper Dolls", "Paper Heart", "Paper Lion", "Paper Man", "Paper Moon", "Paper Soldiers", "Papillon", "Parachute Battalion", "Parachute Jumper", "Parachute Nurse", "Parade of the West", "Paradise Alley", "Paradise Canyon", "Paradise Express", "Paradise for Three", "Paradise Island", "Paradise Isle", "Paradise Lost: The Child Murders at Robin Hood Hills", "Paradise Road", "Paradise", "Paradise, Hawaiian Style", "Parallel Sons", "Paramount on Parade", "Paranoia", "Paranoid Park", "Paranormal Activity 2", "Paranormal Activity 3", "Paranormal Activity 4", "Paranormal Activity: The Marked Ones", "ParaNorman", "Parasite", "Paratroop Command", "Pardners", "Pardon My Backfire", "Pardon My Nerve!", "Pardon My Past", "Pardon My Pups", "Pardon My Rhythm", "Pardon My Sarong", "Pardon My Stripes", "Pardon Us", "Parental Guidance", "Parenthood", "Parents", "Paris After Dark", "Paris at Midnight", "Paris Blues", "Paris Bound", "Paris Calling", "Paris Can Wait", "Paris Follies of 1956", "Paris Holiday", "Paris Honeymoon", "Paris in Spring", "Paris Interlude", "Paris Is Burning", "Paris Model", "Paris Playboys", "Paris Trout", "Paris Underground", "Paris", "Paris, Texas", "Paris, When It Sizzles", "Parisian Love", "Parisian Nights", "Park Avenue Logger", "Park Row", "Park", "Parker", "Parkland", "Parlor, Bedroom and Bath", "Parnell", "Parole Fixer", "Parole Girl", "Parole Racket", "Parole!", "Parole, Inc.", "Paroled - To Die", "Parrish", "Parsifal", "Part Time Pal", "Part Time Wife", "Parting Glances", "Partners Again", "Partners in Crime", "Partners in Time", "Partners of the Plains", "Partners of the Sunset", "Partners of the Trail", "Partners Three", "Partners", "Parts: The Clonus Horror", "Party at Kitty and Stud's", "Party Girl", "Party Husband", "Party Monster", "Party Wire", "Party", "Pass the Ammo", "Passage from Hong Kong", "Passage to Marseille", "Passage to Zarahemla", "Passage West", "Passed Away", "Passenger 57", "Passengers Embarking from S.S. Augusta Victoria, at Beyrouth", "Passengers", "Passing Through", "Passion Fish", "Passion Flower", "Passion of Mind", "Passion Play", "Passion", "Passionate Youth", "Passion's Pathway", "Passkey to Danger", "Passport Husband", "Passport to Destiny", "Passport to Paradise", "Passport to Paris", "Passport to Suez", "Pastime", "Pat and Mike", "Pat Garrett and Billy the Kid", "Patch Adams", "Paternity", "Path to War", "Pathfinder", "Pathology", "Paths of Glory", "Paths to Paradise", "Patient Porky", "Patrick the Great", "Patriot Games", "Patterns", "Patti Cake$", "Patti Smith: Dream of Life", "Patton", "Patty Hearst", "Paul Blart: Mall Cop 2", "Paul Blart: Mall Cop", "Paul", "Paul, Apostle of Christ", "Paula", "Pauli", "Pauly Shore Is Dead", "Pawn Shop Chronicles", "Pawn Ticket 210", "Pawn", "Pawned", "Pawnee", "Pay as You Enter", "Pay Day", "Pay Dirt", "Pay It Forward", "Pay or Die", "Pay Your Dues", "Payback", "Paycheck", "Payday", "Payment Deferred", "Payment on Demand", "PCU", "Peace, Love, & Misunderstanding", "Peaceful Oscar", "Peaceful Warrior", "Peach O'Reno", "Peacock Alley", "Peacock Feathers", "Pearl Harbor", "Pearl Jam Twenty", "Pearl of the South Pacific", "Pecker", "Peck's Bad Boy with the Circus", "Peck's Bad Boy", "Pecos River", "Peculiar Patients' Pranks", "Peep World", "Peeper", "Peeping Tom in the Dressing Room", "Pee-wee's Big Adventure", "Peg o' My Heart", "Peggy Does Her Darndest", "Peggy Sue Got Married", "Peggy", "Peggy, the Will O' the Wisp", "Peking Express", "Penelope", "Penguins of Madagascar", "Penitentiary", "Penn & Teller Get Killed", "Pennies from Heaven", "Penny Serenade", "Penrod and His Twin Brother", "Penrod and Sam", "Penrod", "Penrod's Double Trouble", "Pentathlon", "Pent-House Mouse", "Penthouse North", "Penthouse Rhythm", "Penthouse", "People Are Funny", "People Like Us", "People Will Talk", "Pepe", "Pepper", "Peppermint", "Peppy Polly", "Percy Jackson & the Olympians: The Lightning Thief", "Percy Jackson: Sea of Monsters", "Percy", "Perfect Body", "Perfect Harmony", "Perfect Stranger", "Perfect Strangers", "Perfect", "Perfume", "Perfume: The Story of a Murderer", "Perilous Holiday", "Perilous Waters", "Perils of Nyoka", "Perils of the Coast Guard", "Perils of the Jungle", "Perils of the Rail", "Perils of the Wilderness", "Perils of the Yukon", "Perils of Thunder Mountain", "Period of Adjustment", "Permanent Midnight", "Permanent Record", "Permanent Vacation", "Permission to Kill", "Permission", "Pernicious", "Perri", "Persecuted", "Persepolis", "Personal Affair", "Personal Best", "Personal Maid", "Personal Maid's Secret", "Personal Property", "Personal Secretary", "Personal Velocity: Three Portraits", "Personality Kid", "Persons in Hiding", "Persuasion", "Pest Man Wins", "Pet Sematary Two", "Pet Sematary", "Pet Shop", "Pete Kelly's Blues", "Pete 'n' Tillie", "Pete, the Pedal Polisher", "Peter Ibbetson", "Peter Pan", "Peter Rabbit", "Pete's Dragon", "Pets or Meat: The Return to Flint", "Petticoat Camp", "Petticoat Fever", "Petticoat Larceny", "Petticoat Politics", "Pettigrew's Girl", "Petulia", "Peyton Place", "Phaedra", "Phantasm II", "Phantasm III: Lord of the Dead", "Phantasm IV: Oblivion", "Phantasm", "Phantom from Space", "Phantom Gold", "Phantom Justice", "Phantom Killer", "Phantom Lady", "Phantom of Chinatown", "Phantom of the Opera", "Phantom of the Paradise", "Phantom of the Plains", "Phantom of the Rue Morgue", "Phantom Patrol", "Phantom Police", "Phantom Raiders", "Phantom Stallion", "Phantom Thread", "Phantom Valley", "Phantom", "Phantoms", "Pharaoh's Curse", "Phase IV", "Phat Beach", "Phat Girlz", "Phenomenon", "Phffft!", "Philadelphia", "Philo Vance Returns", "Philo Vance's Gamble", "Philo Vance's Secret Mission", "Philomena", "Phoebe in Wonderland", "Phoenix", "Phone Booth", "Phone Call from a Stranger", "Phony Express", "Photographing the Audience", "Phyllis of the Follies", "Physical Evidence", "Pi", "Picadilly Jim", "Picasso Trigger", "Piccadilly Jim", "Pick a Star", "Picking Up the Pieces", "Pickings", "Pickup on South Street", "Pickup", "Pick-Up", "Picnic", "Picture Bride", "Picture Brides", "Picture Mommy Dead", "Picture Perfect", "Picture Snatcher", "Pie in the Sky", "Pie, Tramp and the Bulldog", "Pieces of April", "Pied Piper Malone", "Pier 13", "Pier 23", "Pier 5, Havana", "Pierre of the Plains", "Pies and Guys", "Piglet's Big Movie", "Pigs in a Polka", "Pigskin Parade", "Pilgrimage", "Pillars of the Sky", "Pillow of Death", "Pillow Talk", "Pillow to Post", "Pilot No. 5", "Pin Up Girl", "Pinched", "Pine Canyon is Burning", "Pineapple Express", "Piñero", "Ping Pong Summer", "Pink Cadillac", "Pink Flamingos", "Pink Gods", "Pink Narcissus", "Pinky", "Pinocchio and the Emperor of the Night", "Pinocchio", "Pinocchio's Revenge", "Pinto Rustlers", "Pioneer Justice", "Pioneer Marshal", "Pioneer Trail", "Pioneer Trails", "Pipe the Whiskers", "Piranha 3-D", "Piranha 3DD", "Piranha II: The Spawning", "Piranha", "Pirates of Monterey", "Pirates of the Caribbean: At World's End", "Pirates of the Caribbean: Dead Man's Chest", "Pirates of the Caribbean: Dead Men Tell No Tales", "Pirates of the Caribbean: On Stranger Tides", "Pirates of the Caribbean: The Curse of the Black Pearl", "Pirates of the High Seas", "Pirates of the Skies", "Pirates of the Sky", "Pirates of Tripoli", "Pirates", "Pistol Harvest", "Pistol Packin' Mama", "Pistols for Breakfast", "Pit Stop", "Pitch Black", "Pitch Perfect 2", "Pitch Perfect 3", "Pitch Perfect", "Pitfall", "Pitfalls of a Big City", "Pittsburgh Fire Department in Full Run", "Pittsburgh", "Pixels", "Places in the Heart", "Plain Clothes", "Plainsman and the Lady", "Plan 9 from Outer Space", "Plane Crazy", "Planes", "Planes, Trains and Automobiles", "Planes: Fire & Rescue", "Planet 51", "Planet of Dinosaurs", "Planet of the Apes", "Plastered in Paris", "Platinum Blonde", "Platinum High School", "Platoon Leader", "Platoon", "Play Girl", "Play It Again, Sam", "Play It as It Lays", "Play It to the Bone", "Play Misty for Me", "Playboy of Paris", "Playgirl", "Playing Around", "Playing by Heart", "Playing for Keeps", "Playing God", "Playing It Wild", "Playing with Souls", "Playmates", "Playthings of Desire", "Plaza Suite", "Pleasantville", "Please Believe Me", "Please Don't Eat My Mother", "Please Don't Eat the Daisies", "Please Get Married", "Please Give", "Please Help the Pore", "Please Murder Me", "Please Stand By", "Pleasure Buyers", "Pleasure Crazed", "Pleasure Cruise", "Pleasure Mad", "Pleasures of the Rich", "Plenty", "Plunder & Lightning", "Plunder of the Sun", "Plunder Road", "Plunderers of Painted Flats", "Plunging Hoofs", "Plus One", "Plush", "Plymouth Adventure", "Pocahontas", "Pocket Money", "Pocketful of Miracles", "Poetic Justice", "Point Blank", "Point Break", "Point of No Return", "Point of Order", "Pointed Heels", "Points West", "Poison Ivy II: Lily", "Poison Ivy", "Poison Ivy: The New Seduction", "Poison", "Poisoned Paradise", "Police 2020", "Police Academy 2: Their First Assignment", "Police Academy 3: Back in Training", "Police Academy 4: Citizens on Patrol", "Police Academy 5: Assignment Miami Beach", "Police Academy 6: City Under Siege", "Police Academy", "Police Academy: Mission to Moscow", "Police Car 17", "Police Court", "Police", "Polish Wedding", "Politics", "Polka-Dot Puss", "Pollock", "Polly of the Circus", "Polly of the Follies", "Pollyanna", "Polo Joe", "Polo Match for the Championship at Hurlingham", "Poltergeist II: The Other Side", "Poltergeist III", "Poltergeist", "Polyester", "Pontiac Moon", "Pontius Pilate", "Pony Express", "Pony Soldier", "Poodle Springs", "Pooh's Grand Adventure: The Search for Christopher Robin", "Pooh's Heffalump Movie", "Pool Sharks", "Poolhall Junkies", "Poor Little Rich Girl", "Poor Little Rich Girl: The Barbara Hutton Story", "Poor Men's Wives", "Poor Relations", "Poor White Trash", "Pootie Tang", "Pop 'im Pop", "Pope Francis: A Man of His Word", "Pope Joan", "Popeye", "Popi", "Poppy", "Popstar", "Popstar: Never Stop Never Stopping", "Porgy and Bess", "Pork Chop Hill", "Porky in Wackyland", "Porky Pig's Feat", "Porky's II: The Next Day", "Porky's Revenge", "Porky's", "Port of Dreams", "Port of Hell", "Port of Lost Dreams", "Port of New York", "Port of Seven Seas", "Port Said", "Port Sinister", "Portia on Trial", "Portland Exposé", "Portnoy's Complaint", "Portrait in Black", "Portrait of a Hitman", "Portrait of a Mobster", "Portrait of Jennie", "Ports of Call", "Poseidon", "Posse Cat", "Posse from Hell", "Posse", "Possessed", "Possession", "Post Grad", "Post No Bills", "Post Office Investigator", "Postal Inspector", "Postcards from the Edge", "Pot o' Gold", "Potash and Perlmutter", "Poultrygeist: Night of the Chicken Dead", "Pound Puppies and the Legend of Big Paw", "Powder Blue", "Powder My Back", "Powder River Rustlers", "Powder River", "Powder Town", "Powder", "Powdersmoke Range", "Power Dive", "Power of the Air", "Power of the Press", "Power Rangers", "Power", "Powers That Prey", "Practical Magic", "Practically Yours", "Prairie Badmen", "Prairie Chickens", "Prairie Express", "Prairie Justice", "Prairie Moon", "Prairie Raiders", "Prairie Roundup", "Prairie Rustlers", "Prairie Thunder", "Prancer", "Pray the Devil Back to Hell", "Pray TV", "Praying with Anger", "Preacher's Kid", "Preaching to the Perverted", "Precious", "Predator 2", "Predator", "Predators", "Prefontaine", "Prehistoric Women", "Pre-Hysterical Hare", "Prelude to a Kiss", "Prelude to War", "Premature Burial", "Premium Rush", "Premium", "Premonition", "Prep and Pep", "Prescription for Romance", "Presenting Lily Mars", "President McKinley and Escort Going to the Capitol", "President McKinley Taking the Oath", "President McKinley's Speech at the Pan-American Exposition", "Press Start", "Pressure Point", "Pressurecooker", "Prestige", "Presumed Innocent", "Prêt-à-Porter", "Pretty Baby", "Pretty Boy Floyd", "Pretty in Pink", "Pretty Ladies", "Pretty Maids All in a Row", "Pretty Persuasion", "Pretty Poison", "Pretty Smooth", "Pretty Woman", "Prey for Rock & Roll", "Price Check", "Price of Glory", "Pride + Prejudice + Zombies", "Pride and Glory", "Pride and Prejudice", "Pride of Maryland", "Pride of the Blue Grass", "Pride of the Bowery", "Pride of the Legion", "Pride of the Marines", "Pride of the Plains", "Pride of the Range", "Pride of the West", "Pride", "Priest", "Primal Fear", "Primary Colors", "Primary Motive", "Primavera en otoño", "Prime Cut", "Prime", "Primer", "Primeval", "Primrose Path", "Prince Avalanche", "Prince of Central Park", "Prince of Darkness", "Prince of Foxes", "Prince of Persia: The Sands of Time", "Prince of Pirates", "Prince of Players", "Prince of Tempters", "Prince of the City", "Prince of the Plains", "Prince Valiant", "Princess Caraboo", "Princess Clementina", "Princess Daisy", "Princess of the Nile", "Princess O'Hara", "Princess O'Rourke", "Princess Virtue", "Prison Break", "Prison Farm", "Prison Nurse", "Prison Planet", "Prison Shadows", "Prison Ship", "Prison Warden", "Prison", "Prisoner of Paradise", "Prisoner of the Volga", "Prisoner of War", "Prisoners in Petticoats", "Prisoners of the Casbah", "Prisoners of the Storm", "Prisoners", "Private Affairs", "Private Benjamin", "Private Buckaroo", "Private Detective 62", "Private Detective", "Private Eyes", "Private Hell 36", "Private Izzy Murphy", "Private Jones", "Private Lessons", "Private Lives", "Private Number", "Private Nurse", "Private Parts", "Private Property", "Private Resort", "Private Scandal", "Private School", "Private Worlds", "Prizzi's Honor", "Probation", "Problem Child 2", "Problem Child 3: Junior in Love", "Problem Child", "Problem Girls", "Prodigal Daughters", "Professional Soldier", "Professional Sweetheart", "Professor Beware", "Professor Marston and the Wonder Women", "Professor Tom", "Profit from Loss", "Project 2x1", "Project Almanac", "Project Moonbase", "Project X", "Prom Night III: The Last Kiss", "Prom Night IV: Deliver Us from Evil", "Prom Night", "Prom", "Prometheus", "Promise Her Anything", "Promised Land", "Promises in the Dark", "Promises! Promises!", "Proof of Life", "Proof", "Prophecy", "Prosperity", "Protection", "Protector, The", "Protocol", "Proud American", "Proud Flesh", "Proud Mary", "Proud", "Prowlers of the Night", "Prowlers of the Sea", "Proxy", "Prudence on Broadway", "Psychic Killer", "Psycho Beach Party", "Psycho II", "Psycho III", "Psycho IV: The Beginning", "Psycho", "Psych-Out", "PT 109", "Public Access", "Public Cowboy No. 1", "Public Deb No. 1", "Public Enemies", "Public Enemy's Wife", "Public Hero No. 1", "Public Opinion", "Public Pigeon No. 1", "Public Wedding", "Puddin' Head", "Puddle Cruiser", "Puff, Puff, Pass", "Pull My Daisy", "Pullet Surprise", "Pulp Fiction", "Pulse", "Pump Up the Volume", "Pumping Iron II: The Women", "Pumping Iron", "Pumpkin", "Pumpkinhead II: Blood Wings", "Pumpkinhead", "Punch-Drunk Love", "Punchline", "Punchy Cowpunchers", "Puncture", "Punisher: War Zone", "Punishment Park", "Puny Express", "Pup on a Picnic", "Puppet Master 4", "Puppet Master 5: The Final Chapter", "Puppet Master II", "Puppet Master III: Toulon's Revenge", "Puppet Master X: Axis Rising", "Puppet Master", "Puppet Master: The Legacy", "Puppy Love", "Puppy Tale", "Pure Country", "Pure Grit", "Pure Luck", "Puritan Passions", "Purple Heart Diary", "Purple Hearts", "Purple People Eater", "Purple Rain", "Purple Rose of Cairo, The", "Pursued", "Pursuit to Algiers", "Pursuit", "Push", "Push-Button Kitty", "Pushing Tin", "Pushover", "Puss Gets the Boot", "Puss in Boots", "Puss 'n' Toots", "Put 'Em Up", "Put on the Spot", "Put Up Your Hands!", "Put Yourself in His Place", "Putney Swope", "Puttin' on the Dog", "Puttin' On The Ritz", "Putting It Over", "Putting One Over", "Puzzle of a Downfall Child", "Puzzle", "Pygmy Island", "Q", "Q&A", "Quackser Fortune Has a Cousin in the Bronx", "Quality Street", "Quantez", "Quantrill's Raiders", "Quantum of Solace", "Quantum Quest: A Cassini Space Odyssey", "Quarantine", "Quarrelsome Neighbours", "Quebec", "Queen Bee", "Queen Christina", "Queen for a Day", "Queen High", "Queen Kelly", "Queen o'Diamonds", "Queen of Blood", "Queen of Burlesque", "Queen of Outer Space", "Queen of the Amazons", "Queen of the Damned", "Queen of the Mob", "Queen of the Night Clubs", "Queen of the Stardust Ballroom", "Queen of the Yukon", "Quentin Quail", "Quest for Camelot", "Question 7", "Quick Change", "Quick Millions", "Quick Money", "Quick on the Trigger", "Quick Pick", "Quick Triggers", "Quick, Before It Melts", "Quick, Let's Get Married", "Quicker'n a Wink", "Quicksand", "Quicksand: No Escape", "Quicksands", "Quicksilver Highway", "Quicksilver", "Quiet Cool", "Quiet Days in Hollywood", "Quiet Please!", "Quiet Please, Murder", "Quigley Down Under", "Quills", "Quincannon, Frontier Scout", "Quinceañera", "Quincy Adams Sawyer", "Quintet", "Quiz Show", "Quiz Whizz", "Quo Vadis", "R. P. M.", "R.I.P.D.", "R.O.T.O.R.", "R.S.V.P.", "Rabbit Fire", "Rabbit Hole", "Rabbit Hood", "Rabbit of Seville", "Rabbit Rampage", "Rabbit Seasoning", "Rabbit Test", "Rabbit, Run", "Rabbit's Moon", "Rabbitson Crusoe", "Rabid", "Race for Your Life, Charlie Brown", "Race Street", "Race the Sun", "Race to Witch Mountain", "Race with the Devil", "Race", "Rachel and the Stranger", "Rachel Getting Married", "Rachel, Rachel", "Racing Blood", "Racing for Life", "Racing Hearts", "Racing Lady", "Racing Luck", "Racing Romance", "Racing Stripes", "Racing with the Moon", "Racing Youth", "Rack, Shack, and Benny", "Racket Busters", "Racketeer Rabbit", "Racketeers in Exile", "Racketeers of the Range", "Rackety Rax", "Rad", "Radar Men from the Moon", "Radar Patrol vs Spy King", "Radar Secret Service", "Radio City Revels", "Radio Days", "Radio Flyer", "Radio Inside", "Radio Patrol", "Radio Stars on Parade", "Radio", "Radioactive Dreams", "Radioland Murders", "Rafferty and the Gold Dust Twins", "Raffles the Amateur Cracksman", "Raffles the Dog", "Raffles", "Raffles, the Amateur Cracksman", "Rafter Romance", "Rage at Dawn", "Rage in Heaven", "Rage", "Raggedy Ann & Andy: A Musical Adventure", "Raggedy Man", "Raggedy Rose", "Raging Bull", "Rags to Riches", "Ragtime Snap Shots", "Ragtime", "Raid on a Coiner's Den", "Raid on Entebbe", "Raid on Rommel", "Raiders of Ghost City", "Raiders of Old California", "Raiders of Red Gap", "Raiders of San Joaquin", "Raiders of Sunset Pass", "Raiders of the Border", "Raiders of the Lost Ark", "Raiders of the Seven Seas", "Raiders of the South", "Raiders of Tomahawk Creek", "Railroaded!", "Railroaded", "Rails Into Laramie", "Railway Ride in the Alps", "Rain Man", "Rain Or Shine", "Rain Without Thunder", "Rain", "Rainbow Brite and the Star Stealer", "Rainbow Island", "Rainbow on the River", "Rainbow Over Broadway", "Rainbow Over Texas", "Rainbow Over the Rockies", "Rainbow Riley", "Rainbow 'Round My Shoulder", "Rainbow Valley", "Rainbow's End", "Raintree County", "Raise the Titanic", "Raise Your Voice", "Raising Arizona", "Raising Cain", "Raising Helen", "Rally Round the Flag, Boys!", "Ralph Breaks the Internet", "Ralph S. Mouse", "Rambling Rose", "Rambo III", "Rambo", "Rambo: First Blood Part II", "Ramona and Beezus", "Ramona", "Rampage", "Rampart", "Ramrod", "Ramshackle House", "Ranchers and Rascals", "Rancho Deluxe", "Rancho Grande", "Rancho Notorious", "Random Harvest", "Random Hearts", "Randy Rides Alone", "Range Beyond the Blue", "Range Blood", "Range Buzzards", "Range Defenders", "Range Justice", "Range Law", "Range Renegades", "Range War", "Ranger Courage", "Ranger of Cherokee Strip", "Ranger of the Big Pines", "Rangers of Fortune", "Rango", "Ransom!", "Ransom", "Rapa Nui", "Rapid Fire Romance", "Rapid Fire", "Rappin'", "Rapture-Palooza", "Rarin' to Go", "Rascal", "Rascals", "Rasputin and the Empress", "Rasputin: Dark Servant of Destiny", "Rat Race", "Ratatouille", "Ratboy", "Ratchet & Clank", "Rationing", "Raton Pass", "Rattle and Hum", "Ravenous", "Ravished Armenia", "Raw Deal", "Raw Edge", "Raw Justice", "Raw Wind in Eden", "Rawhide Mail", "Rawhide", "Ray", "Raymie", "Raze", "Reach the Rock", "Reaching for the Moon", "Reaching for the Sun", "Ready for Love", "Ready Player One", "Ready to Rumble", "Ready, Willing and Able", "Real Adventure", "Real Genius", "Real Life", "Real Men", "Real Steel", "Real Women Have Curves", "Reality Bites", "Re-Animator", "Reap the Wild Wind", "Rear Window", "Rebecca of Sunnybrook Farm", "Rebecca", "Rebel City", "Rebel in Town", "Rebel Rabbit", "Rebel Without a Cause", "Rebellion", "Rebound", "Recaptured Love", "Received Payment", "Recess: School's Out", "Reckless Age", "Reckless Indifference", "Reckless Living", "Reckless Ranger", "Reckless Roads", "Reckless Romance", "Reckless Youth", "Reckless", "Recompense", "Record City", "Recreation", "RED 2", "Red Ball Express", "Red Canyon", "Red Clay", "Red Corner", "Red Courage", "Red Dawn", "Red Dice", "Red Dragon", "Red Dust", "Red Eye", "Red Garters", "Red Hair", "Red Haired Alibi", "Red Headed Stranger", "Red Heat", "Red Hook Summer", "Red Hot Dollars", "Red Hot Leather", "Red Hot Rhythm", "Red Hot Riding Hood", "Red Hot Romance", "Red Hot Skate Rock", "Red Hot Speed", "Red Hot Tires", "Red Light", "Red Lights Ahead", "Red Lights", "Red Line 7000", "Red Lips", "Red Morning", "Red Mountain", "Red Nightmare", "Red Planet Mars", "Red Planet", "Red Ribbon Blues", "Red Riders of Canada", "Red Riding Hood", "Red River Range", "Red River Renegades", "Red River Shore", "Red River Valley", "Red River", "Red Rock West", "Red Salute", "Red Scorpion", "Red Skies of Montana", "Red Sky at Morning", "Red Snow", "Red Sonja", "Red Sparrow", "Red Stallion in the Rockies", "Red State", "Red Sun", "Red Sundown", "Red Tails", "Red Tomahawk", "Red Wine", "Red Zone Cuba", "Red", "Red, Hot and Blue", "Redbelt", "Redbird Wins", "Redemption", "Redhead from Manhattan", "Red-Headed Woman", "Redheads on Parade", "Redheads Preferred", "Redline", "Redneck Zombies", "Redrum", "Red's Dream", "Reds", "Redskin", "Reducing", "Redwood Forest Trail", "Reefer Madness", "Reflections in a Golden Eye", "Reform Girl", "Reform School Girl", "Reformatory", "Refuge", "Regarding Henry", "Regeneration", "Registered Nurse", "Regret to Inform", "Reign of Fire", "Reign of Terror", "Reign Over Me", "Reindeer Games", "Relative Strangers", "Relentless", "Religulous", "Remains to Be Seen", "Remedy for Riches", "Remember Last Night?", "Remember Me", "Remember My Name", "Remember Pearl Harbor", "Remember the Day", "Remember the Daze", "Remember the Night", "Remember the Titans", "Remember These Faces", "Remember", "Remember?", "Remembrance", "Remo Williams: The Adventure Begins", "Remodeling Her Husband", "Remote Control", "Renaissance Man", "Renaldo and Clara", "Rendezvous 24", "Rendezvous at Midnight", "Rendezvous with Annie", "Rendezvous", "Rendition", "Renegade Girl", "Renegades of Sonora", "Renegades of the Rio Grand", "Renegades of the Sage", "Renegades of the West", "Renegades", "Renfrew of the Royal Mounted", "Reno 911!: Miami", "Reno", "Rent Free", "Rent", "Rent: Filmed Live on Broadway", "Rent-a-Cop", "Rented Lips", "Repeat Performance", "Repentance", "Repo Man", "Repo Men", "Repo! The Genetic Opera", "Report from the Aleutians", "Report to the Commissioner", "Reported Missing", "Repossessed", "Reprisal!", "Reproduction of the Corbett-McGovern Fight", "Reptilicus", "Requiem for a Dream", "Requiem for a Gunfighter", "Requiem for a Heavyweight", "Requiem", "Rescue Squad", "Reservation Road", "Reservoir Dogs", "Resident Evil: Afterlife", "Resident Evil: Extinction", "Resident Evil: Retribution", "Resident Evil: The Final Chapter", "Restaurant", "Restless Souls", "Restless Wives", "Restless Youth", "Restless", "Restoration", "Resurrecting the Champ", "Resurrection", "Retreat, Hell!", "Retro Puppet Master", "Return from the Sea", "Return from Witch Mountain", "Return of Techno-Destructo", "Return of the Ape Man", "Return of the Bad Men", "Return of the Fly", "Return of the Frontiersman", "Return of the Jedi", "Return of the Killer Tomatoes", "Return of the Lash", "Return of the Living Dead 3", "Return of the Living Dead Part II", "Return of the Living Dead, The", "Return of the Pink Panther", "Return of the Secaucus Seven", "Return of the Seven", "Return of the Terror", "Return of the Texan", "Return to Guam", "Return to Horror High", "Return to Macon County", "Return to Me", "Return to Never Land", "Return to Oz", "Return to Paradise", "Return to Peyton Place", "Return to the Blue Lagoon", "Return to Treasure Island", "Return to Warbow", "Reuben in the Opium Joint", "Reuben, Reuben", "Reunion in France", "Reunion in Reno", "Reunion in Vienna", "Reunion", "Reveille with Beverly", "Revelation aka Apocalypse", "Revelation", "Revenge of the Creature", "Revenge of the Nerds II: Nerds in Paradise", "Revenge of the Nerds III: The Next Generation", "Revenge of the Nerds IV: Nerds in Love", "Revenge of the Nerds", "Revenge of the Ninja", "Revenge of the Pink Panther", "Revenge of the Zombies", "Revenge!", "Revenge", "Revenue Agent", "Reversal of Fortune", "Revolt at Fort Laramie", "Revolt in the Big House", "Revolt of the Zombies", "Revolution OS", "Revolution", "Revolutionary Road", "Rhapsody in Blue", "Rhapsody Rabbit", "Rhapsody", "Rhinestone", "Rhino!", "Rhinoceros", "Rhubarb", "Rhythm and Weep", "Rhythm in the Clouds", "Rhythm Inn", "Rhythm of the Islands", "Rhythm of the Saddle", "Rhythm on the Range", "Rhythm on the River", "Rhythm Round-Up", "Rich and Famous", "Rich in Love", "Rich Kids", "Rich Man, Poor Girl", "Rich Man, Poor Man", "Rich Man's Folly", "Rich Men's Wives", "Rich, Young and Pretty", "Richard III", "Richard Pryor: Here and Now", "Richard Pryor: Live in Concert", "Richard Pryor: Live on the Sunset Strip", "Richard the Lion-Hearted", "Richelieu", "Richie Rich", "Ricochet Romance", "Ricochet", "Riddick", "Riddler's Moon", "Ride a Crooked Mile", "Ride a Crooked Trail", "Ride a Violent Mile", "Ride Along 2", "Ride Along", "Ride Beyond Vengeance", "Ride Clear of Diablo", "Ride 'Em Cowboy", "Ride for Your Life", "Ride Him, Cowboy", "Ride in the Whirlwind", "Ride Lonesome", "Ride Out for Revenge", "Ride Ranger Ride", "Ride the High Country", "Ride the High Iron", "Ride the Man Down", "Ride the Pink Horse", "Ride the Wild Surf", "Ride", "Ride, Tenderfoot, Ride", "Ride, Vaquero!", "Rider from Tucson", "Rider of the Law", "Riders in the Sky", "Riders of Black Mountain", "Riders of Death Valley", "Riders of Destiny", "Riders of the Black Hills", "Riders of the Dark", "Riders of the Dawn", "Riders of the Deadline", "Riders of the Lone Star", "Riders of the Northwest Mounted", "Riders of the Purple Sage", "Riders of the Range", "Riders of the Rio Grande", "Riders of the Santa Fe", "Riders of the Whistling Pines", "Riders of the Whistling Skull", "Riders of Vengeance", "Riders to the Stars", "Riders Up", "Ridgeway of Montana", "Ridin' Down the Trail", "Ridin' for Justice", "Ridin' on a Rainbow", "Ridin' On", "Ridin' Pretty", "Ridin' the Lone Trail", "Ridin' the Outlaw Trail", "Ridin' the Wind", "Ridin' Thunder", "Ridin' Wild", "Riding Giants", "Riding High", "Riding in Cars with Boys", "Riding on Air", "Riding Shotgun", "Riding the Bullet", "Riding the California Trail", "Riding West", "Riding Wild", "Riffraff", "Right at Your Door", "Right Cross", "Right to Die", "Right to the Heart", "Righteous Kill", "Riley the Cop", "Rim of the Canyon", "Rimfire", "Ring Around the Moon", "Ring of Fear", "Ring of Steel", "Ring Up the Curtain", "Ringmaster", "Rings on Her Fingers", "Rings", "Ringside Maisie", "Ringside", "Rinty of the Desert", "Rio 2", "Rio Bravo", "Rio Conchos", "Rio Grande Patrol", "Rio Grande Raiders", "Rio Grande Ranger", "Rio Grande Romance", "Rio Grande", "Rio Lobo", "Rio Rattler", "Rio Rita", "Rio", "Riot in Cell Block 11", "Riot in Juvenile Prison", "Riot on Sunset Strip", "Riot", "Rip Roarin' Roberts", "Ripley's Game", "Riptide", "Rise and Shine", "Rise of the Guardians", "Rise of the Planet of the Apes", "Risen", "Rising Sun", "Risky Business", "Rites of Passage", "River Gang", "River Lady", "River of Death", "River of No Return", "Riverboat Rhythm", "River's Edge", "River's End", "Rize", "Road Agent", "Road Demon", "Road Gang", "Road Hard", "Road House", "Road Movie", "Road Show", "Road to Alcatraz", "Road to Bali", "Road to Happiness", "Road to Morocco", "Road to Paloma", "Road to Paradise", "Road to Perdition", "Road to Rio", "Road to Ruin", "Road to Singapore", "Road to the Big House", "Road to Utopia", "Road to Zanzibar", "Road Trip", "Roadblock", "Roadhouse 66", "Roadhouse Nights", "Roadie", "Roadracers", "Roads of Destiny", "Roadside Prophets", "Roadside", "Roamin' Wild", "Roaming Lady", "Roar of the Crowd", "Roar of the Dragon", "Roar of the Press", "Roarin' Dan", "Roarin' Guns", "Roarin' Lead", "Roaring City", "Roaring Lions at Home", "Roaring Rails", "Roaring Ranch", "Roaring Rangers", "Roaring Timber", "Roaring Westward", "Rob Roy", "Rob Roy, the Highland Rogue", "Robbers' Roost", "Roberta", "Robin and Marian", "Robin and the 7 Hoods", "Robin Hood Daffy", "Robin Hood of El Dorado", "Robin Hood of Monterey", "Robin Hood of Texas", "Robin Hood of the Range", "Robin Hood", "Robin Hood: Men in Tights", "Robin Hood: Prince of Thieves", "Robin Hoodwinked", "Robinson Crusoe on Mars", "Robinson Crusoe", "RoboCop 2", "RoboCop 3", "RoboCop", "Robosapien: Rebooted", "Robot & Frank", "Robot Jox", "Robot Monster", "Robotech: Love Live Alive", "Robotech: The Movie", "Robotech: The Shadow Chronicles", "Robots", "Rock All Night", "Rock Around the Clock", "Rock Dog", "Rock Island Trail", "Rock 'n' Roll High School Forever", "Rock 'n' Roll High School", "Rock of Ages", "Rock Star", "Rock, Pretty Baby", "Rock, Rock, Rock", "Rockabilly Baby", "Rock-A-Bye Baby", "Rockabye", "Rock-a-Doodle", "Rocket Gibraltar", "Rocket Science", "RocketMan", "Rocketship X-M", "Rockin' in the Rockies", "Rockin' Thru the Rockies", "Rocking Gold in the Klondike", "Rocking Moon", "Rockshow", "Rocky Balboa", "Rocky II", "Rocky III", "Rocky IV", "Rocky Mountain Mystery", "Rocky Mountain Rangers", "Rocky Mountain", "Rocky Rhodes", "Rocky V", "Rocky", "Rodeo King and the Senorita", "Rodeo", "Roger & Me", "Roger Dodger", "Roger Touhy, Gangster", "Rogue Cop", "Rogue of the Rio Grande", "Rogue One: A Star Wars Story (film)", "Rogue River", "Rogues' Gallery", "Rogue's March", "Rogues of Sherwood Forest", "Rogues' Regiment", "Role Models", "Roll Along, Cowboy", "Roll Bounce", "Roll on Texas Moon", "Roller Boogie", "Roller Coaster Rabbit", "Rollerball", "Rollercoaster", "Rolling Caravans", "Rolling Home", "Rolling Thunder", "Rolling", "Rollover", "Roman Candles", "Roman Holiday", "Roman J. Israel, Esq.", "Roman Scandals", "Roman", "Romance and Arabella", "Romance in Manhattan", "Romance in the Dark", "Romance in the Rain", "Romance Land", "Romance of the Rio Grande", "Romance of the Underworld", "Romance of the West'", "Romance on the High Seas", "Romance on the Run", "Romance", "Romancing the Stone", "Roman-Legion Hare", "Romanoff and Juliet", "Romantic Comedy", "Rome Adventure", "Romeo & Juliet: Sealed with a Kiss", "Romeo + Juliet", "Romeo and Juliet", "Romeo Is Bleeding", "Romeo Must Die", "Romeo-Juliet", "Romola", "Romy and Michele's High School Reunion", "Ronin", "Roogie's Bump", "Rookie Fireman", "Rookie of the Year", "Rookies in Burma", "Rookies", "Room (2015 film)", "Room 6", "Room for One More", "Room Service", "Roommates", "Roosevelt in Africa", "Rooster Cogburn", "Roosters", "Rootin' Tootin' Rhythm", "Roots", "Rope of Sand", "Rope", "Roped", "Rory o' the Bogs", "Rosalie", "Rose Bowl", "Rose Kennedy: A Life to Remember", "Rose Marie", "Rose o' the River", "Rose o' the Sea", "Rose of Cimarron", "Rose of Santa Rosa", "Rose of the Rancho", "Rose of the Rio Grande", "Rose of the Tenements", "Rose of the West", "Rose of the World", "Rose of the Yukon", "Rose of Washington Square", "Rose O'Salem-Town", "Roseanna McCoy", "Rosebud", "Roseland", "Rose-Marie", "Rosemary, That's for Remembrance", "Rosemary's Baby", "Rosencrantz & Guildenstern Are Dead", "Roses Are Red", "Rosewood", "Rosie the Riveter", "Rosie!", "Rosita", "Roswell", "Rouged Lips", "Rough Cut", "Rough Going", "Rough Night in Jericho", "Rough Night", "Rough Riders of Cheyenne", "Rough Riders of Durango", "Rough Riders", "Rough Ridin' Justice", "Rough Ridin' Red", "Rough Ridin'", "Rough Riding Ranger", "Rough Romance", "Rough Shoot", "Rough Waters", "Rough, Tough and Ready", "Roughly Speaking", "Rough-Riding Romance", "Roughshod", "Roulette", "Round Midnight", "Rounders", "Round-Up Time in Texas", "Roustabout", "Rover Dangerfield", "Roxanne", "Roxie Hart", "Royal Cat Nap", "Royal Flash", "Royal Wedding", "Rubber Racketeers", "Ruby Cairo", "Ruby Gentry", "Ruby in Paradise", "Ruby Sparks", "Ruby", "Ruckus", "Rudolph the Red-Nosed Reindeer: The Movie", "Rudy", "Rugged Water", "Ruggles of Red Gap", "Rugrats Go Wild", "Rugrats in Paris: The Movie", "Ruhlin in His Training Quarters", "Rulers of the Sea", "Rules of Engagement", "Rumba", "Rumble Fish", "Rumble on the Docks", "Rumor Has It...", "Rumpus in the Harem", "Run All Night", "Run for Cover", "Run for the Roses", "Run for the Sun", "Run of the Arrow", "Run Silent, Run Deep", "Run", "Runaway Brain", "Runaway Bride", "Runaway Daughters", "Runaway Girls", "Runaway Jury", "Runaway Train", "Runaway", "Rune", "Runner Runner", "Runnin' Straight", "Running Brave", "Running on Empty", "Running Scared", "Running Target", "Running Time", "Running Wild", "Running with Scissors", "RuPaul Is: Starbooty!", "Rupert of Hentzau", "Ruses, Rhymes and Roughnecks", "Rush Hour 2", "Rush Hour 3", "Rush Hour", "Rush to Judgment", "Rush", "Rushmore", "Russian Rhapsody", "Russian Roulette", "Russkies", "Rustlers' Hideout", "Rustlers of Devil's Canyon", "Rustlers of the Badlands", "Rustlers on Horseback", "Rustler's Paradise", "Rustlers' Ranch", "Rustlers' Rhapsody", "Rustlers' Roundup", "Rustler's Round-up", "Rustlers' Valley", "Rustlers", "Rustling a Bride", "Rustling for Cupid", "Rusty Leads the Way", "Rusty Rides Alone", "Rusty Romeos", "Rusty Saves a Life", "Rusty's Birthday", "Ruthless People", "Ruthless", "RV", "Rx Murder", "S*H*E", "S*P*Y*S", "S.F.W.", "S.O.B.", "S.O.S. Perils of the Sea", "S.O.S. Tidal Wave", "S.S. St. Louis", "S.W.A.T.", "S1m0ne", "Saadia", "Sabaka", "Sabotage", "Saboteur", "Sabre Jet", "Sabrina the Teenage Witch", "Sabrina", "Sacco and Vanzetti", "Sackcloth and Scarlet", "Sacred Silence", "Saddle Leather Law", "Saddle Legion", "Saddle Pals", "Saddle Serenade", "Saddle the Wind", "Saddle Tramp", "Saddles and Sagebrush", "Sadie Love", "Sadie McKee", "Sadie Thompson", "Safari 3000", "Safari Drums", "Safari", "Safe at Home!", "Safe Haven", "Safe House", "Safe in Hell", "Safe Men", "Safe", "Safeguarding Military Information", "Safety in Numbers", "Safety Last!", "Safety Not Guaranteed", "Safety Second", "Saga of the Franklin", "Sagebrush Heroes", "Sagebrush Lady", "Sagebrush Law", "Sagebrush Trail", "Saginaw Trail", "Sahara Hare", "Sahara", "Saigon", "Sail a Crooked Ship", "Sailor Be Good", "Sailor Beware", "Sailor Moon SuperS movie", "Sailor of the King", "Sailor's Holiday", "Sailor's Lady", "Sailor's Luck", "Sailors on Leave", "Sailors' Wives", "Saint Jack", "Saint Joan", "Saint John of Las Vegas", "Saint-Ex", "Salesman", "Salinger", "Sally and Saint Anne", "Sally of the Sawdust", "Sally of the Scandals", "Sally Scraggs, Housemaid", "Sally", "Sally, Irene and Mary", "Sally's Shoulders", "Salome of the Tenements", "Salome", "Salomé", "Salome, Where She Danced", "Salomy Jane", "Salt Lake Raiders", "Salt of the Earth", "Salt Water Tabby", "Salt", "Salty O'Rourke", "Salute for Three", "Salute to the Marines", "Salute", "Salvador", "Salvage", "Salvation Jane", "Salvation Nell", "Salvation!", "Sam Whiskey", "Samantha", "Same Kind of Different as Me", "Same Time, Next Year", "Samsara", "Samson and Delilah", "Samson", "Samurai Cop", "Samurai Vampire Bikers From Hell", "San Andreas", "San Antone Ambush", "San Antone", "San Antonio Rose", "San Antonio", "San Diego, I Love You", "San Fernando Valley", "San Francisco", "San Quentin", "Sanctuary", "Sanctum", "Sand", "Sandflow", "Sandra", "Sands of Iwo Jima", "Sands of the Kalahari", "Sangaree", "Santa Claus Conquers the Martians", "Santa Claus: The Movie", "Santa Claws", "Santa Fe Bound", "Santa Fe Marshal", "Santa Fe Passage", "Santa Fe Pete", "Santa Fe Rides", "Santa Fe Saddlemates", "Santa Fe Scouts", "Santa Fe Stampede", "Santa Fe Trail", "Santa Fe Uprising", "Santa Fe", "Santa with Muscles", "Santiago", "Saps at Sea", "Sarah and Son", "Sarah Silverman: Jesus Is Magic", "Saratoga Trunk", "Saratoga", "Sarge Goes to College", "Sarong Girl", "Sarumba", "Saskatchewan", "Satan in High Heels", "Satan in Sables", "Satan Junior", "Satan Met a Lady", "Satan Never Sleeps", "Satan's Cheerleaders", "Satan's Cradle", "Satan's Waitin'", "Satisfaction", "Saturday Evening Puss", "Saturday Night Fever", "Saturday Night", "Saturday Shopping", "Saturday the 14th Strikes Back", "Saturday the 14th", "Saturday's Children", "Saturday's Hero", "Saturday's Heroes", "Saturday's Millions", "Saturn 3", "Sausage Party", "Savage Drums", "Savage Frontier", "Savage Grace", "Savage Mutiny", "Savage Streets", "Savages", "Savannah Smiles", "Savannah", "Save the Last Dance", "Save the Tiger", "Saved by the Belle", "Saved from the Titanic", "Saved!", "Saving Christmas", "Saving Grace", "Saving Lincoln", "Saving Mr. Banks", "Saving Private Ryan", "Saving Santa", "Saving Shiloh", "Saving Silverman", "Savior", "Saw II", "Saw III", "Saw IV", "Saw V", "Saw VI", "Saw VII", "Saw", "Sawdust", "Say Anything", "Say It in French", "Say It Isn't So", "Say It with Sables", "Say It with Songs", "Say One for Me", "Sayonara", "Scandal at Scourie", "Scandal for Sale", "Scandal Incorporated", "Scandal Proof", "Scandal Sheet", "Scandal Street", "Scandal", "Scar Hanan", "Scaramouche", "Scarecrow Pump", "Scarecrow", "Scarecrows", "Scared Stiff", "Scared to Death", "Scaredy Cat", "Scarface", "Scarlet Angel", "Scarlet Dawn", "Scarlet Days", "Scarlet Pages", "Scarlet River", "Scarlet Saint", "Scarlet Seas", "Scarlet Street", "Scars of Jealousy", "Scary Movie 2", "Scary Movie 3", "Scary Movie 4", "Scary Movie 5", "Scary Movie", "Scat Cats", "Scatterbrain", "Scattergood Meets Broadway", "Scattergood Survives a Murder", "Scavenger Hunt", "Scene in Canada -- Logging at Bear Creek", "Scene in Canada -- Spearing Salmon in a Mountain Stream", "Scene of the Crime", "Scenes from a Mall", "Scenes from the Class Struggle in Beverly Hills", "Scenic Route", "Scent of a Woman", "Scent of Mystery", "Scent-imental Over You", "Scheming Schemers", "Schindler's List", "Schizoid", "Schizopolis", "School Dance", "School Daze", "School for Girls", "School for Scoundrels", "School for Wives", "School of Rock", "School Ties", "Scissors", "Scooby-Doo 2: Monsters Unleashed", "Scooby-Doo and the Ghoul School", "Scooby-Doo and the Reluctant Werewolf", "Scooby-Doo in Arabian Nights", "Scooby-Doo Meets the Boo Brothers", "Scooby-Doo", "Scoop", "Scorchers", "Scorchy", "Score", "Scorned", "Scorpio Rising", "Scorpio", "Scorpion", "Scotland Yard Investigator", "Scotland Yard", "Scotland, Pennsylvania", "Scott Joplin", "Scott Pilgrim vs. the World", "Scott Walker: 30 Century Man", "Scottsboro: An American Tragedy", "Scrambled Brains", "Scrap Happy Daffy", "Scrapper", "Scratch", "Scream 2", "Scream 3", "Scream 4", "Scream Blacula Scream", "Scream", "Screamers", "Screaming Eagles", "Screaming Mimi", "Screwed", "Scrooged", "Scrub Me Mama with a Boogie Beat", "Scruples", "Scudda Hoo! Scudda Hay!", "Sea Devils", "Sea Horses", "Sea Legs", "Sea of Lost Ships", "Sea of Love", "Sea Racketeers", "Sea Raiders", "Sea Shore Shapes", "Sea Spoilers", "Sea Tiger", "Seabiscuit", "Sealed Cargo", "Sealed Hearts", "Sealed Lips", "Sealed Verdict", "Seance: The Summoning", "Search for Beauty", "Search for Danger", "Searching for Bobby Fischer", "Searching Ruins on Broadway, Galveston, for Dead Bodies", "Searching", "Seas Beneath", "Season of Miracles", "Season of the Witch", "Second Act", "Second Chance", "Second Choice", "Second Chorus", "Second Fiddle", "Second Hand Love", "Second Hand Rose", "Second Hand Wife", "Second Honeymoon", "Second Sight", "Second Wife", "Second Youth", "Second-Hand Hearts", "Secondhand Lions", "Seconds", "Secret Admirer", "Secret Agent of Japan", "Secret Agent X-9", "Secret Beyond the Door...", "Secret Command", "Secret Enemies", "Secret Honor", "Secret Love", "Secret of the Blue Room", "Secret of the Cave", "Secret of the Chateau", "Secret of the Incas", "Secret of Treasure Mountain", "Secret Orders", "Secret Patrol", "Secret Service in Darkest Africa", "Secret Service Investigator", "Secret Service of the Air", "Secret Service", "Secret Valley", "Secret Window", "Secretariat", "Secretary", "Secrets of a Nurse", "Secrets of a Secretary", "Secrets of a Sorority Girl", "Secrets of an Actress", "Secrets of Monte Carlo", "Secrets of Paris", "Secrets of Scotland Yard", "Secrets of the French Police", "Secrets of the Gods", "Secrets of the Lone Wolf", "Secrets of the Night", "Secrets", "Security Risk", "See America Thirst", "See Here, Private Hargrove", "See My Lawyer", "See No Evil", "See No Evil, Hear No Evil", "See Spot Run", "See You in the Morning", "Seed of Chucky", "Seed People", "Seed", "Seeing's Believing", "Seeking a Friend for the End of the World", "Seems Like Old Times", "Seize the Day", "Seizure", "Selena", "Self Defense", "Self-Made Maids", "Seminole Uprising", "Seminole", "Semi-Pro", "Semi-Tough", "Send Me No Flowers", "Senior Prom", "Señor Americano", "Senor Daredevil", "Senorita from the West", "Senorita", "Sensation Hunters", "Sensational Hurdle Race", "Sensations of 1945", "Sense and Sensibility", "Senseless", "Sentimental Journey", "Sentimental Tommy", "Separate Tables", "September 30, 1955", "September Affair", "September Storm", "September", "Sequel to the Diamond from the Sky", "Sequoia", "Seraphim Falls", "Serena", "Serenade", "Serendipity", "Serenity", "Sergeant Deadhead", "Sergeant Madden", "Sergeant Mike", "Sergeant Murphy", "Sergeant Rutledge", "Sergeant Ryker", "Sergeant York", "Sergeants 3", "Serial Mom", "Serial", "Serpent of the Nile", "Serpico", "Servants' Entrance", "Served Like A Girl", "Service de Luxe", "Service with the Colors", "Serving Sara", "Sesame Street Jam: A Musical Celebration", "Sesame Street Presents Follow That Bird", "Sesame Street's 25th Birthday: A Musical Celebration!", "Session 9", "Set Free", "Set It Off", "Seven Angry Men", "Seven Brides for Seven Brothers", "Seven Chances", "Seven Cities of Gold", "Seven Days Ashore", "Seven Days in May", "Seven Days in Utopia", "Seven Days Leave", "Seven Days' Leave", "Seven Days", "Seven Doors to Death", "Seven Faces", "Seven Footprints to Satan", "Seven Hills of Rome", "Seven Keys to Baldpate", "Seven Men from Now", "Seven Miles from Alcatraz", "Seven Minutes in Heaven", "Seven Pounds", "Seven Sinners", "Seven Sweethearts", "Seven Thieves", "Seven Waves Away", "Seven Ways from Sundown", "Seven Were Saved", "Seven Women from Hell", "Seven Wonders of the World", "Seven Years Bad Luck", "Seven Years in Tibet", "Seven", "Seventeen Again", "Seventeen", "Seventh Heaven", "Seventh Son", "Sex and the City 2", "Sex and the City", "Sex and the College Girl", "Sex and the Single Girl", "Sex Boss", "Sex Drive", "Sex Hygiene", "Sex Kittens Go to College", "Sex Tape", "Sex", "Sex, Lies, and Videotape", "Sextette", "Sexy Beast", "Sexy Evil Genius", "Sgt. Bilko", "Sgt. Kabukiman N.Y.P.D.", "Sgt. Pepper's Lonely Hearts Club Band", "Sgt. Stubby: An American Hero", "Sh! The Octopus", "Shack Out on 101", "Shackles of Gold", "Shad Fishing at Gloucester, N.J.", "Shadow Conspiracy", "Shadow in the Sky", "Shadow Man", "Shadow of a Doubt", "Shadow of a Woman", "Shadow of Doubt", "Shadow of Suspicion", "Shadow of Terror", "Shadow of the Law", "Shadow of the Thin Man", "Shadow of the Vampire", "Shadow on the Wall", "Shadow People", "Shadow Puppets", "Shadow Ranch", "Shadow Valley", "Shadowboxer", "Shadowed", "Shadows and Fog", "Shadows in the Night", "Shadows in the Storm", "Shadows of Death", "Shadows of Paris", "Shadows of Sing Sing", "Shadows of Suspicion", "Shadows of the Night", "Shadows of the North", "Shadows of the Past", "Shadows of the Sea", "Shadows of Tombstone", "Shadows on the Range", "Shadows Over Chinatown", "Shadows Over Shanghai", "Shadows", "Shadrach", "Shady Lady", "Shaft in Africa", "Shaft", "Shaft's Big Score", "Shag", "Shaggy", "Shake Hands with Murder", "Shake Hands with the Devil", "Shake! Otis at Monterey", "Shake, Rattle & Rock!", "Shakedown", "Shakes the Clown", "Shakespeare in Love", "Shalako", "Shall We Dance", "Shall We Dance?", "Shallow Hal", "Shameful Behavior?", "Shampoo", "Shamus", "Shane", "Shanghai Chest", "Shanghai Express", "Shanghai Knights", "Shanghai Lady", "Shanghai Madness", "Shanghai Noon", "Shanghai Surprise", "Shanghai", "Shanghaied Love", "Shanghaied", "Shantytown", "Shark Bait", "Shark Night 3D", "Shark River", "Shark Tale", "Shark!", "Sharky's Machine", "Sharp Shooters", "Sharpshooters", "Shatter Dead", "Shattered Dreams", "Shattered Glass", "Shattered Idols", "Shattered", "She Asked for It", "She Couldn't Say No", "She Couldn't Take It", "She Creature", "She Cried No", "She Demons", "She Devil", "She Done Him Right", "She Done Him Wrong", "She Gets Her Man", "She Gods of Shark Reef", "She Goes to War", "She Got What She Wanted", "She Had to Choose", "She Had to Eat", "She Had to Say Yes", "She Has What It Takes", "She Hate Me", "She Knew All the Answers", "She Learned About Sailors", "She Love Me Not", "She Loved a Fireman", "She Loves Me Not", "She Made Her Bed", "She Married a Cop", "She Married an Artist", "She Married Her Boss", "She Shoulda Said 'No'!", "She Walketh Alone", "She Wanted a Millionaire", "She Wants Me", "She Was a Lady", "She Went to the Races", "She Wolves", "She Wore a Yellow Ribbon", "She Wouldn't Say Yes", "She Wrote the Book", "She", "Sheba, Baby", "Shed No Tears", "She-Devil", "She-Devils on Wheels", "Sheena", "Shell Shock", "Sheltered Daughters", "Shenandoah", "Shep Comes Home", "Sheriff of Cimarron", "Sheriff of Las Vegas", "Sheriff of Redwood Valley", "Sheriff of Sundown", "Sheriff of Wichita", "Sherlock Brown", "Sherlock Gnomes", "Sherlock Holmes and the House of Fear", "Sherlock Holmes and the Secret Weapon", "Sherlock Holmes and the Voice of Terror", "Sherlock Holmes Faces Death", "Sherlock Holmes in Washington", "Sherlock Holmes", "Sherlock Holmes: A Game of Shadows", "Sherlock, Jr.", "Sherman's March", "Sherrybaby", "She's a Sheik", "She's a Soldier Too", "She's a Sweetheart", "She's All That", "She's Back on Broadway", "She's Dangerous", "She's for Me", "She's Funny That Way", "She's Got Everything", "She's Gotta Have It", "She's Having a Baby", "She's My Weakness", "She's No Lady", "She's Out of Control", "She's Out of My League", "She's So Lovely", "She's the Man", "She's the One", "She's Working Her Way Through College", "She-Wolf of London", "Shield for Murder", "Shiloh", "Shine On, Harvest Moon", "Shining Through", "Shining Victory", "Ship Ahoy", "Ship Cafe", "Ship of Fools", "Shipmates Forever", "Shipmates", "Ships of the Night", "Shipwrecked Among Cannibals", "Shipwrecked", "Shirley of the Circus", "Shock and Awe", "Shock Corridor", "Shock Treatment", "Shock", "Shocker", "Shockproof", "Shoot 'Em Up", "Shoot Out", "Shoot the Moon", "Shoot the Works", "Shoot to Kill", "Shoot", "Shooter", "Shootin' for Love", "Shooting High", "Shooting Straight", "Shoot-Out at Medicine Bend", "Shop Angel", "Shopgirl", "Shopworn", "Shore Acres", "Shore Leave", "Short Circuit 2", "Short Circuit", "Short Cut to Hell", "Short Cuts", "Short Eyes", "Short Grass", "Short Term 12", "Short Time", "Shortbus", "Shorts", "Shotgun Jones", "Shotgun Pass", "Shotgun Wedding", "Shotgun", "Shottas", "Should a Husband Forgive?", "Should a Wife Forgive?", "Should a Woman Divorce?", "Should a Woman Tell?", "Should Ladies Behave", "Shoulder Arms", "Shout at the Devil", "Shout", "Show Boat", "Show Business at War", "Show Business", "Show Dogs", "Show Folks", "Show Girl in Hollywood", "Show Girl", "Show People", "Show Them No Mercy!", "Showboy", "ShowBusiness: The Road to Broadway", "Showdown at Abilene", "Showdown at Boot Hill", "Showdown in Little Tokyo", "Showdown", "Showgirls", "Showtime", "Shreck", "Shrek 2", "Shrek Forever After", "Shrek the Third", "Shrek", "Shriek If You Know What I Did Last Friday the Thirteenth", "Shrieker", "Shrink", "Shutter Island", "Shutter", "Shy People", "Si, Senor", "Siberia", "Sibling Rivalry", "Sic 'Em, Towser", "Sicario: Day of the Soldado", "Sick: The Life and Death of Bob Flanagan, Supermasochist", "Sicko", "Side Effects", "Side Out", "Side Show", "Side Street", "Side Streets", "Sidekicks", "Sideshow", "Sidewalk Stories", "Sidewalks of New York", "Sideways", "Siege at Red River", "Siege", "Sierra Baron", "Sierra Passage", "Sierra Stranger", "Sierra Sue", "Sierra", "Siesta", "Sign o' the Times", "Sign of the Pagan", "Signed Judgment", "Signpost to Murder", "Signs of Life", "Signs", "Silas Marner", "Silence", "Silent Conflict", "Silent Fall", "Silent Hill", "Silent Hill: Revelation 3D", "Silent House", "Silent Madness", "Silent Men", "Silent Movie", "Silent Night, Deadly Night 3: Better Watch Out!", "Silent Night, Deadly Night 5: The Toy Maker", "Silent Night, Deadly Night Part 2", "Silent Night, Deadly Night", "Silent Partner", "Silent Rage", "Silent Raiders", "Silent Running", "Silent Sanderson", "Silent Scream", "Silent Sentinel", "Silent Sheldon", "Silent Strength", "Silent Tongue", "Silent Trigger", "Silent Valley", "Silent Witness", "Silk Hat Kid", "Silk Hosiery", "Silk Husbands and Calico Wives", "Silk Stocking Sal", "Silk Stockings", "Silken Shackles", "Silks and Saddles", "Silkwood", "Silly Billies", "Silver Bullet", "Silver Canyon", "Silver City Bonanza", "Silver City Kid", "Silver City Raiders", "Silver City", "Silver Dollar", "Silver Linings Playbook", "Silver Lode", "Silver on the Sage", "Silver Queen", "Silver Raiders", "Silver Range", "Silver River", "Silver Skates", "Silver Spurs", "Silver Streak", "Silver Trails", "Silver Wings", "Silverado", "Simon Birch", "Simon Says", "Simon Sez", "Simon the Jester", "Simon", "Simon, King of the Witches", "Simple Men", "Simply Irresistible", "Sin Cargo", "Sin City", "Sin City: A Dame to Kill For", "Sin Takes a Holiday", "Sin Town", "Sinatra", "Sinbad and the Eye of the Tiger", "Sinbad of the Seven Seas", "Sinbad the Sailor", "Sinbad", "Sinbad: Legend of the Seven Seas", "Since You Went Away", "Since You've Been Gone", "Sincerely Yours", "Sing a Jingle", "Sing a Song of Six Pants", "Sing and Be Happy", "Sing and Like It", "Sing Another Chorus", "Sing Me a Love Song", "Sing Me a Song of Texas", "Sing Sing Nights", "Sing While You Dance", "Sing You Sinners", "Sing Your Way Home", "Sing Your Worries Away", "Sing", "Sing, Baby, Sing", "Sing, Boy, Sing", "Sing, Dance, Plenty Hot", "Sing, Neighbor, Sing", "Singapore Woman", "Singapore", "Singed Wings", "Singed", "Singer Jim McKee", "Singin' in the Corn", "Singin' in the Rain", "Singin' Spurs", "Singing Guns", "Singing in the Dark", "Singing on the Trail", "Single Handed", "Single Room Furnished", "Single White Female", "Single Wives", "Singles", "Sinister Hands", "Sinister Journey", "Sinister", "Sinner or Saint", "Sinner Take All", "Sinner", "Sinners' Holiday", "Sinners in Heaven", "Sinners in Love", "Sinners in Paradise", "Sinners in Silk", "Sinners in the Sun", "Sinner's Parade", "Sinners", "Sins of Jezebel", "Sins of Man", "Sins of the Fathers", "Sioux City Sue", "Sioux City", "Sir Galahad of Twilight", "Sir Lumberjack", "Siren of Atlantis", "Siren of Bagdad", "Sirocco", "Sis Hopkins", "Sister Act 2: Back in the Habit", "Sister Act", "Sister Kenny", "Sister, Sister", "Sisters of Eve", "Sisters Under the Skin", "Sisters", "Sit Tight", "Sitting Bull", "Sitting on the Moon", "Sitting Pretty", "Sitting Target", "Situation Hopeless... But Not Serious", "Six Black Horses", "Six Bridges to Cross", "Six Cylinder Love", "Six Days Seven Nights", "Six Days", "Six Degrees of Separation", "Six Feet Four", "Six Gun Gold", "Six Gun Gospel", "Six Gun Man", "Six Gun Mesa", "Six Hours to Live", "Six Lessons from Madame La Zonga", "Six of a Kind", "Six Pack", "Six Shooter Andy", "Six Ways to Sunday", "Six Weeks", "Six: The Mark Unleashed", "Six-Gun Law", "Six-Gun Serenade", "Six-String Samurai", "Sixteen Candles", "Sixteen Fathoms Deep", "Sixty Cents an Hour", "Skateland", "Skatetown, U.S.A.", "Ski Party", "Ski Patrol", "Skid Proof", "Skidoo", "Skin & Bone", "Skin Deep", "Skin Game", "Skin", "Skinner Steps Out", "Skinner's Big Idea", "Skinner's Dress Suit", "Skinwalker Ranch", "Skipalong Rosenbloom", "Skippy", "Skirts Ahoy!", "Skullduggery", "Sky Blue", "Sky Bride", "Sky Captain and the World of Tomorrow", "Sky Commando", "Sky Devils", "Sky Dragon", "Sky Full of Moon", "Sky Giant", "Sky High Corral", "Sky High", "Sky Murder", "Sky Racket", "Sky Raiders", "Sky Riders", "Skyjacked", "Skylark", "Skyline", "Skyscraper Souls", "Skyscraper", "Skyway", "Slacker", "Slackers", "Slam Dance", "Slam Dunk Ernest", "Slam", "Slander the Woman", "Slander", "Slap Shot", "Slaphappy Sleuths", "Slappy and the Stinkers", "Slapstick of Another Kind", "Slattery's Hurricane", "Slaughter of the Innocents", "Slaughter on Tenth Avenue", "Slaughter Trail", "Slaughter", "Slaughterhouse Rock", "Slaughterhouse-Five", "Slaughter's Big Rip-Off", "Slave Girl", "Slave of Desire", "Slave of Dreams", "Slave Ship", "Slaves in Bondage", "Slaves of Babylon", "Slaves of New York", "Slaves", "Sleep with Me", "Sleep", "Sleep, My Love", "Sleepaway Camp II: Unhappy Campers", "Sleepaway Camp III: Teenage Wasteland", "Sleepaway Camp", "Sleeper", "Sleepers East", "Sleepers", "Sleeping Beauty", "Sleeping Dogs Lie", "Sleeping with the Enemy", "Sleepless in Seattle", "Sleepless", "Sleepover", "Sleepstalker", "Sleepwalk with Me", "Sleepwalkers", "Sleepwalking", "Sleepy Hollow", "Sleepy Lagoon", "Sleepy-Time Tom", "Sleight", "Slender Man", "Sleuth", "Slick Hare", "Slicked-up Pup", "Slide, Kelly, Slide", "Slightly Dangerous", "Slightly French", "Slightly Married", "Slightly Scandalous", "Slightly Scarlet", "Slightly Terrific", "Slim Carter", "Slim Fingers", "Slim Shoulders", "Slim", "Sling Blade", "Slippy McGee", "Slipstream", "Slither", "Sliver", "Slow Dancing in the Big City", "Slugs", "Slumber Party Massacre 3", "Slumber Party Massacre II", "Slumber Party Massacre", "Slums of Beverly Hills", "Small Sacrifices", "Small Soldiers", "Small Time Crooks", "Small Town Boy", "Small Town Gay Bar", "Small Town Girl", "Small Town Saturday Night", "Smallfoot", "Smart Blonde", "Smart Girl", "Smart Girls Don't Talk", "Smart Guy", "Smart Money", "Smart People", "Smart Politics", "Smart Woman", "Smartest Girl in Town", "Smarty Cat", "Smarty", "Smashed", "Smashing the Money Ring", "Smashing the Rackets", "Smashing the Spy Ring", "Smash-Up, the Story of a Woman", "Smile", "Smiles Are Trumps", "Smiles", "Smiley Face", "Smilin' at Trouble", "Smilin' Guns", "Smilin' Through", "Smiling Irish Eyes", "Smitten Kitten", "Smoke Lightning", "Smoke Signal", "Smoke Signals", "Smoke Tree Range", "Smoke", "Smokey and the Bandit II", "Smokey and the Bandit Part 3", "Smokey and the Bandit", "Smokin' Aces", "Smoking Guns", "Smoky Canyon", "Smoky Mountain Melody", "Smoky River Serenade", "Smoky", "Smooth as Satin", "Smooth as Silk", "Smooth Talk", "Smouldering Fires", "Smudge", "Smugglers' Cove", "Smuggler's Gold", "Smuggler's Island", "Smurfs: The Lost Village", "Snafu", "Snake Eater II: The Drug Buster", "Snake Eater", "Snake Eyes", "Snake River Desperadoes", "Snakes on a Plane", "Snares of Paris", "Snatch", "Snatched", "Sneakers", "Sniper", "Snitch", "Snoopy, Come Home", "Snow Blind", "Snow Day", "Snow Dog", "Snow Dogs", "Snow Falling on Cedars", "Snow Flower and the Secret Fan", "Snow White & the Huntsman", "Snow White and the Seven Dwarfs", "Snow White and the Three Stooges", "Snow White", "Snow White: A Tale of Terror", "Snowball Express", "Snowboard Academy", "Snowbound", "Snowden", "Snowdrift", "Snowed Under", "Snuff", "So Big!", "So Big", "So Dark the Night", "So Dear to My Heart", "So Ends Our Night", "So Evil My Love", "So Goes My Love", "So I Married an Axe Murderer", "So Long Letty", "So Long Mr. Chumps", "So Much So Fast", "So Proudly We Hail!", "So Red the Rose", "So This Is Africa", "So This Is London", "So This Is Love", "So This Is Love?", "So This Is Marriage?", "So This Is New York", "So This Is Paris", "So This Is Washington", "So You Won't Talk", "So Young, So Bad", "Soak the Rich", "Soapdish", "Sob Sister", "Social Animals", "Social Briars", "Social Error", "Social Register", "Society Doctor", "Society Fever", "Society Girl", "Society Lawyer", "Society Secrets", "Society", "Socket", "Sodom and Gomorrah", "Sofia", "Soft Boiled", "Soft Cushions", "Soft Living", "Soft Money", "Soft Shoes", "Soiled", "Sol Madrid", "Solar Crisis", "Solarbabies", "Solaris", "Soldier Blue", "Soldier Boyz", "Soldier in the Rain", "Soldier of Fortune", "Soldier", "Soldiers and Women", "Soldiers of Fortune", "Soldiers of the Storm", "Soldiers Three", "Solid Serenade", "Solitary Man", "Solo", "Solo: A Star Wars Story", "Solomon and Sheba", "Sombrero", "Some Baby", "Some Bride", "Some Call It Loving", "Some Came Running", "Some Girls", "Some Kind of a Nut", "Some Kind of Hero", "Some Kind of Monster", "Some Kind of Wonderful", "Some Liar", "Some Like It Hot", "Some More of Samoa", "Some Pun'kins", "Some Velvet Morning", "Somebody has to Shoot the Picture", "Somebody Killed Her Husband", "Somebody Loves Me", "Somebody Up There Likes Me", "Somebody's Mother", "Someone Like You", "Someone Marry Barry", "Someone to Love", "Someone to Remember", "Someone to Watch Over Me", "Something Always Happens", "Something Big", "Something Borrowed", "Something for Everyone", "Something for the Birds", "Something for the Boys", "Something in the Wind", "Something New", "Something of Value", "Something Short of Paradise", "Something to Do", "Something to Live For", "Something to Shout About", "Something to Sing About", "Something to Talk About", "Something to Think About", "Something Wicked This Way Comes", "Something Wild", "Something's Got to Give", "Something's Gotta Give", "Sometimes a Great Notion", "Sometimes They Come Back", "Sometimes They Come Back... Again", "Somewhere I'll Find You", "Somewhere in the City", "Somewhere in the Night", "Somewhere in Time", "Somewhere in Turkey", "Somewhere Slow", "Somewhere", "Sommersby", "Son in Law", "Son of a Sailor", "Son of Ali Baba", "Son of Belle Starr", "Son of Dracula", "Son of Flubber", "Son of Fury: The Story of Benjamin Blake", "Son of Geronimo", "Son of God", "Son of God's Country", "Son of India", "Son of Ingagi", "Son of Kong", "Son of Lassie", "Son of Paleface", "Son of Sinbad", "Son of the Border", "Son of the Gods", "Son of the Golden West", "Son of the Mask", "Son of the Navy", "Son of the Pink Panther", "Son of Zorro", "Song and Dance Man", "Song o' My Heart", "Song of Arizona", "Song of Idaho", "Song of India", "Song of Love", "Song of Mexico", "Song of My Heart", "Song of Nevada", "Song of Old Wyoming", "Song of Russia", "Song of Scheherazade", "Song of Surrender", "Song of Texas", "Song of the Caballero", "Song of the City", "Song of the Drifter", "Song of the Eagle", "Song of the Flame", "Song of the Gringo", "Song of the Islands", "Song of the Open Road", "Song of the Prairie", "Song of the Range", "Song of the Saddle", "Song of the Sarong", "Song of the Sierras", "Song of the South", "Song of the Thin Man", "Song of the Trail", "Song of the Wasteland", "Song of the West", "Song One", "Song to Song", "Song Without End", "Songcatcher", "Songwriter", "Sonhos de Peixe", "Sonny", "Sonora Stagecoach", "Sons o' Guns", "Sons of Adventure", "Sons of New Mexico", "Sons of Steel", "Sons of the Desert", "Sons of the Legion", "Sons of the Pioneers", "Sons of the Saddle", "Sooky", "Sophie and the Rising Sun", "Sophie Lang Goes West", "Sophie's Choice", "Sorcerer", "Sorority Babes in the Slimeball Bowl-O-Rama", "Sorority Boys", "Sorority Girl", "Sorority House Massacre 2", "Sorority House Massacre", "Sorority House", "Sorority Row", "Sorrell and Son", "Sorrowful Jones", "Sorry to Bother You", "Sorry, Wrong Number", "So's Your Aunt Emma", "So's Your Old Man", "So's Your Uncle", "Soul Food", "Soul Man", "Soul Mates", "Soul Men", "Soul of the Beast", "Soul Plane", "Soul Surfer", "Soul Survivors", "Soul to Soul", "Soul-Fire", "Souls at Sea", "Souls for Sables", "Souls for Sale", "Souls in Pawn", "Souls Triumphant", "Soultaker", "Sound and Fury", "Sound City", "Sound of My Voice", "Sound Off", "Sounder", "Soup for One", "Soup to Nuts", "Sour Grapes", "Source Code", "South Central", "South of 8", "South of Algiers", "South of Arizona", "South of Caliente", "South of Death Valley", "South of Dixie", "South of Monterey", "South of Pago Pago", "South of Rio", "South of Santa Fe", "South of St. Louis", "South of Suez", "South of Suva", "South of Tahiti", "South of the Chisholm Trail", "South of the Rio Grande", "South Pacific Trail", "South Pacific", "South Park: Bigger, Longer & Uncut", "South Sea Love", "South Sea Rose", "South Sea Sinner", "South Sea Woman", "Southbound Duckling", "Southern Comfort", "Southern Fried Rabbit", "Southlander", "Southside 1-1000", "Southwest Passage", "Soylent Green", "Space Chimps", "Space Cowboys", "Space Invasion of Lapland", "Space Is the Place", "Space Jam", "Space Marines", "Space Master X-7", "Space Raiders", "Space Ship Sappy", "Space Truckers", "Spaceballs", "SpaceCamp", "Spaced Invaders", "Spacehunter: Adventures in the Forbidden Zone", "Spaceman", "Spaceways", "Spalding Gray: Terrors of Pleasure", "Spanglish", "Spanish Affair", "Spanking the Monkey", "Spare Parts", "Spark", "Sparkle", "Sparrow of the Circus", "Sparrows", "Spartacus", "Spartan", "Spawn of the North", "Spawn", "Speak Easily", "Speak", "Speakeasy", "Special Agent", "Special Delivery", "Special Effects: Anything Can Happen", "Special Inspector", "Special Investigator", "Special", "Species II", "Species", "Specter of the Rose", "Speechless", "Speed 2: Cruise Control", "Speed Cop", "Speed Crazed", "Speed Demon", "Speed Limited", "Speed Mad", "Speed Racer", "Speed to Burn", "Speed to Spare", "Speed Wild", "Speed Wings", "Speed Zone", "Speed", "Speedtrap", "Speedway Junky", "Speedway", "Speedy Meade", "Speedy Spurs", "Speedy", "Spellbinder", "Spellbound", "Spellcaster", "Spencer's Mountain", "Spendthrift", "Sphere", "Sphinx", "Spice World", "Spider-Man 2", "Spider-Man 3", "Spider-Man", "Spider-Man: Homecoming", "Spider-Man: Into the Spider-Verse", "Spiders 3D", "Spies Like Us", "Spike of Bensonhurst", "Spike", "Spin", "Spinout", "Spiral", "Spirit of '76", "Spirit of the Marathon II", "Spirit of Youth", "Spirit: Stallion of the Cimarron", "Spirits of the Dead", "Spit-Ball Sadie", "Spite Marriage", "Spitfire", "Splash", "Splash, Too", "Splendor in the Grass", "Splendor", "Splice", "Split Decisions", "Split Second", "Split", "Spoilers of the Forest", "Spoilers of the North", "Spoilers of the Plains", "Spoilers of the West", "Spontaneous Combustion", "Spook Busters", "Spook Chasers", "Spook Louder", "Spook Ranch", "Spook Town", "Spooks Run Wild", "Spooks!", "Sport of Kings", "Sporting Blood", "Sporting Goods", "Sporting Youth", "Spotlight Sadie", "Spotlight Scandals", "Spread", "Spring Break", "Spring Breakers", "Spring Fever", "Spring Is Here", "Spring Madness", "Spring Parade", "Spring Reunion", "Spring Tonic", "Springfield Rifle", "Springtime for Henry", "Springtime for Thomas", "Springtime in Texas", "Springtime in the Rockies", "Springtime in the Sierras", "Sprung", "Spun", "Spurs", "Spy Chasers", "Spy Game", "Spy Hard", "Spy Hunt", "Spy Kids 2: The Island of Lost Dreams", "Spy Kids 3-D: Game Over", "Spy Kids", "Spy Kids: All the Time in the World", "Spy School", "Spy Ship", "Spy Smasher", "Spy Train", "Spy", "Spymaker: The Secret Life of Ian Fleming", "Squad Car", "Squadron of Honor", "Squanto: A Warrior's Tale", "Square Crooks", "Square Dance Jubilee", "Square Dance Katy", "Square Dance", "Square Deal Sanderson", "Square Shooter", "Squirm", "Sssssss", "St. Benny the Dip", "St. Elmo", "St. Elmo's Fire", "St. Ives", "St. Louis Blues", "St. Louis Woman", "Stablemates", "Stage Beauty", "Stage Door Canteen", "Stage Door Cartoon", "Stage Door", "Stage Fright", "Stage Mother", "Stage Struck", "Stage to Blue River", "Stage to Chino", "Stage to Mesa City", "Stage to Thunder Rock", "Stage to Tucson", "Stagecoach Days", "Stagecoach Driver", "Stagecoach Kid", "Stagecoach Outlaws", "Stagecoach to Dancers' Rock", "Stagecoach to Denver", "Stagecoach to Fury", "Stagecoach to Monterey", "Stagecoach", "Stakeout on Dope Street", "Stakeout", "Stalag 17", "Stalking Santa", "Stallion Road", "Stamboul Quest", "Stamp Day for Superman", "Stampede in the Night", "Stampede Thunder", "Stampede", "Stand and Deliver", "Stand By for Action", "Stand by Me", "Stand Up and Cheer!", "Stand Up and Fight", "Stand Up Guys", "Stand-In", "Standing Room Only", "Standing Up", "Stanley & Iris", "Stanley and Livingstone", "Star 80", "Star Dust Trail", "Star Dust", "Star for a Night", "Star in the Dust", "Star Kid", "Star of Midnight", "Star of Texas", "Star Spangled Girl", "Star Spangled Rhythm", "Star Time", "Star Trek Beyond", "Star Trek Generations", "Star Trek II: The Wrath of Khan", "Star Trek III: The Search for Spock", "Star Trek Into Darkness", "Star Trek IV: The Voyage Home", "Star Trek Nemesis", "Star Trek V: The Final Frontier", "Star Trek VI: The Undiscovered Country", "Star Trek", "Star Trek: First Contact", "Star Trek: Insurrection", "Star Trek: The Motion Picture", "Star Wars Episode IV: A New Hope (aka Star Wars)", "Star Wars: Episode I – The Phantom Menace 3D", "Star Wars: Episode I – The Phantom Menace", "Star Wars: Episode II – Attack of the Clones", "Star Wars: Episode III – Revenge of the Sith", "Star Wars: The Clone Wars", "Star Wars: The Last Jedi", "Star!", "Starchaser: The Legend of Orin", "Stardust Memories", "Stardust", "Stargate", "Stark Fear", "Stark Mad", "Starlift", "Starlight, the Untamed", "Starman", "Stars and Bars", "Stars and Stripes Forever", "Stars In My Crown", "Stars on Parade", "Stars Over Broadway", "Stars Over Texas", "Starship Troopers", "Starsky & Hutch", "Start Cheering", "Start the Revolution Without Me", "Starter for 10", "Starting Out in the Evening", "Starting Over", "State Department: File 649", "State Fair", "State of Grace", "State of Play", "State of the Union", "State Penitentiary", "State Police", "State Property", "State Street Sadie", "State Trooper", "State's Attorney", "Stateside", "Station West", "Stay Alive", "Stay Away, Joe", "Stay Hungry", "Stay Tuned", "Stay", "Staying Alive", "Staying Together", "Steady Company", "Steal This Movie!", "Stealing Beauty", "Stealing Harvard", "Stealing Home", "Stealth", "Steamboat Bill, Jr.", "Steamboat Round the Bend", "Steamboat Willie", "Steel Against the Sky", "Steel Dawn", "Steel Magnolias", "Steel Preferred", "Steel Town", "Steel", "Steele of the Royal Mounted", "Steelyard Blues", "Stella Dallas", "Stella Maris", "Stella", "Step Brothers", "Step by Step", "Step Down to Terror", "Step Lively", "Step Lively, Jeeves!", "Step on It!", "Step on It", "Step Up 2: The Streets", "Step Up 3D", "Step Up Revolution", "Step Up", "Step Up: All In", "Step", "Stepchild", "Stepfather II", "Stephanie Daley", "Stephen Steps Out", "Stepmom", "Steppin' in Society", "Steppin' Out", "Stepping Along", "Stepping Fast", "Stepping Lively", "Stepping Out", "Stepping Sisters", "Stevie", "Stick It", "Stick to Your Guns", "Stick to Your Story", "Stick", "Sticks and Bones", "Stigma", "Stigmata", "Stiletto", "Still Crazy Like a Fox", "Still Green", "Still of the Night", "Still Smokin", "Stingaree", "Stir Crazy", "Stir of Echoes", "Stitches", "Stocks and Blondes", "Stoker", "Stolen Harmony", "Stolen Heaven", "Stolen Holiday", "Stolen Hours", "Stolen Kisses", "Stolen Love", "Stolen Memories: Secrets from the Rose Garden", "Stolen Moments", "Stolen Pleasures", "Stolen Secrets", "Stolen Summer", "Stolen Sweets", "Stolen", "Stomp the Yard", "Stone Cold", "Stone of Silver Creek", "Stone", "Stonewall", "Stoogemania", "Stool Pigeon", "Stop Flirting", "Stop Making Sense", "Stop That Cab", "Stop That Man!", "Stop! Luke! Listen!", "Stop! Or My Mom Will Shoot", "Stop!, Look and Laugh", "Stop, Look and Laugh", "Stop, Look and Listen", "Stop, You're Killing Me", "Stop-Loss", "Stopover Tokyo", "Stork Bites Man", "Storks", "Storm at Daybreak", "Storm Center", "Storm Fear", "Storm Over Bengal", "Storm Over Lisbon", "Storm Over the Andes", "Storm Over Tibet", "Storm over Wyoming", "Storm Warning", "Storm", "Stormbreaker", "Stormswept", "Stormy Monday", "Stormy Weather", "Stormy Weathers", "Stormy", "Storytelling Giant", "Storytelling", "Storyville", "Stowaway", "Straight A's", "Straight from the Heart", "Straight from the Shoulder", "Straight Is the Way", "Straight Out of Brooklyn", "Straight Place and Show", "Straight Shootin'", "Straight Shooting", "Straight Talk", "Straight Through", "Straight Time", "Straight to Hell", "Straightaway", "Straight-Jacket", "Strait-Jacket", "Stranded in Paris", "Stranded", "Strange Affair", "Strange Alibi", "Strange Bargain", "Strange Bedfellows", "Strange Brew", "Strange Cargo", "Strange Case of Dr. Meade", "Strange Confession", "Strange Conquest", "Strange Culture", "Strange Days", "Strange Faces", "Strange Fascination", "Strange Gamble", "Strange Holiday", "Strange Idols", "Strange Illusion", "Strange Impersonation", "Strange Interlude", "Strange Intruder", "Strange Invaders", "Strange Journey", "Strange Justice", "Strange Lady in Town", "Strange Magic", "Strange People", "Strange Triangle", "Strange Voyage", "Strange Wilderness", "Strange Wives", "Strangeland", "Stranger at My Door", "Stranger from Santa Fe", "Stranger in the House", "Stranger on Horseback", "Stranger on the Third Floor", "Stranger Than Fiction", "Stranger Than Paradise", "Strangers All", "Strangers in Love", "Strangers in the Night", "Strangers May Kiss", "Strangers of the Evening", "Strangers of the Night", "Strangers on a Train", "Strangers When We Meet", "Strangler of the Swamp", "Strapless", "Strategic Air Command", "Strategic Command", "Straw Dogs", "Strawberry Fields", "Strawberry Shortcake: The Sweet Dreams Movie", "Streamers", "Streamline Express", "Street Angel", "Street Bandits", "Street Car Chivalry", "Street Corner", "Street Fight", "Street Fighter", "Street Fighter: The Legend of Chun-Li", "Street Girl", "Street Kings", "Street of Chance", "Street of Darkness", "Street of Sinners", "Street of Women", "Street Scene", "Street Scenes", "Street Smart", "Street Trash", "Streets of Fire", "Streets of Ghost Town", "Streets of Gold", "Streets of Laredo", "Streets of San Francisco", "Streets of Shanghai", "Strictly Business", "Strictly Confidential", "Strictly Dishonorable", "Strictly Dynamite", "Strictly in the Groove", "Strictly Modern", "Strictly Personal", "Strictly Unconventional", "Strife with Father", "Strike It Rich", "Strike Me Pink", "Strike Up the Band", "Striking Distance", "Stripes", "Striptease", "Stroker Ace", "Strong Boy", "Stronger Than Death", "Stronger Than Desire", "Stronger", "Stronghold", "Struck by Lightning", "Stuart Little 2", "Stuart Little", "Stuart Saves His Family", "Stuck on You", "Stuck", "Student Bodies", "Student Tour", "Studio Stoops", "Studs Lonigan", "Stupid, But Brave", "Subject Two", "Submarine Alert", "Submarine Base", "Submarine Command", "Submarine D-1", "Submarine Patrol", "Submarine Raider", "Submarine", "Submit the Documentary", "Suburban Commando", "Suburban Girl", "Suburbia", "Suburbicon", "Subway Express", "Subway Sadie", "Success at Any Price", "Succumbs", "Such a Little Queen", "Such Good Friends", "Such Men Are Dangerous", "Such Women Are Dangerous", "Sucker Punch", "Sudan", "Sudden Danger", "Sudden Death", "Sudden Fear", "Sudden Impact", "Sudden Money", "Suddenly", "Suddenly, It's Spring", "Suddenly, Last Summer", "Suds", "Sue of the South", "Suez", "Sufferin' Cats!", "Suffering Man's Charity", "Sugar & Spice", "Sugar Chile Robinson", "Sugar Hill", "Sugarfoot", "Suggestion Box", "Suicide Battalion", "Suicide Fleet", "Suicide Killers", "Suicide Kings", "Suicide Squad", "Sullivan's Travels", "Sully", "Summer and Smoke", "Summer Bachelors", "Summer Catch", "Summer Heat", "Summer Holiday", "Summer Love", "Summer Lovers", "Summer Magic", "Summer of '42", "Summer of Sam", "Summer of the Seventeenth Doll", "Summer Rental", "Summer School", "Summer Stock", "Summer Storm", "Summer Wishes, Winter Dreams", "Summertime", "Summertree", "Sun Valley Cyclone", "Sun Valley Serenade", "Sunbonnet Sue", "Sunburn", "Sunday Bloody Sunday", "Sunday Dinner for a Soldier", "Sunday in New York", "Sunday Lovers", "Sunday", "Sundown in Santa Fe", "Sundown Jim", "Sundown Rider", "Sundown Trail", "Sundown Valley", "Sundown", "Sunlight Jr.", "Sunny Side of the Street", "Sunny Side Up", "Sunny Skies", "Sunny", "Sunnyside", "Sunrise at Campobello", "Sunrise: A Song of Two Humans", "Sunset Boulevard", "Sunset Carson Rides Again", "Sunset Grill", "Sunset in El Dorado", "Sunset in the West", "Sunset Murder Case", "Sunset of Power", "Sunset Park", "Sunset Pass", "Sunset Range", "Sunset", "Sunshine of Paradise Alley", "Sunshine State", "Sunshine", "Sun-Up", "Super 8", "Super Buddies", "Super Fly", "Super Fuzz", "Super Mario Bros.", "Super Size Me", "Super Speed", "Super Speedway", "Super Troopers 2", "Super Troopers", "Super", "SuperBabies: Baby Geniuses 2", "Superbad", "Supercross", "Superdad", "Superfly", "Supergirl", "Superhero Movie", "Superman and the Mole Men", "Superman II", "Superman III", "Superman IV: The Quest for Peace", "Superman Returns", "Superman", "Superman: Unbound", "Supernatural", "Supernova", "Super-Rabbit", "Super-Sleuth", "Super-Speed", "Superstar", "Superstar: The Karen Carpenter Story", "Superstition", "Supervixens", "Support Your Local Gunfighter!", "Support Your Local Sheriff", "Suppose They Gave a War and Nobody Came", "Sure Fire", "Surf II", "Surf Nazis Must Die", "Surf Party", "Surf's Up", "Surprise Package", "Surrender - Hell!", "Surrender", "Surrogates", "Surviving Christmas", "Surviving Picasso", "Surviving the Game", "Susan and God", "Susan Lenox (Her Fall and Rise)", "Susan Slade", "Susan Slept Here", "Susanna Pass", "Susannah of the Mounties", "Susie Q", "Susie Steps Out", "Susie the Little Blue Coupe", "Suspect Zero", "Suspect", "Suspense", "Suspicion", "Suspiria", "Sutter's Gold", "Suture", "Suzanna", "Suzy", "Svengali", "Swamp Fire", "Swamp Thing", "Swamp Water", "Swamp Women", "Swanee River", "Swarm of the Snakehead", "Swashbuckler", "Swat the Crook", "Sweater Girl", "Sweeney Todd: The Demon Barber of Fleet Street", "Sweepings", "Sweepstake Annie", "Sweepstakes Winner", "Sweepstakes", "Sweet Adeline", "Sweet and Hot", "Sweet and Low", "Sweet and Lowdown", "Sweet and Low-Down", "Sweet Bird of Youth", "Sweet Charity", "Sweet Country", "Sweet Daddies", "Sweet Dreams", "Sweet Genevieve", "Sweet Hearts Dance", "Sweet Home Alabama", "Sweet Insanity", "Sweet Kill", "Sweet Kitty Bellairs", "Sweet Liberty", "Sweet Lies", "Sweet Mama", "Sweet Memories", "Sweet Music", "Sweet November", "Sweet Revenge", "Sweet Rosie O'Grady", "Sweet Smell of Success", "Sweet Surrender", "Sweet Sweetback's Baadasssss Song", "Sweet Temptation", "Sweetheart of Sigma Chi", "Sweethearts and Wives", "Sweethearts of the U.S.A.", "Sweethearts on Parade", "Sweethearts", "Sweetwater", "Swell Guy", "Swellhead", "Swept Away", "Swept from the Sea", "Swim Girl, Swim", "Swimfan", "Swimming to Cambodia", "Swimming with Sharks", "Swing Fever", "Swing High, Swing Low", "Swing Hostess", "Swing in the Saddle", "Swing It, Sailor!", "Swing Kids", "Swing Out the Blues", "Swing Out, Sister", "Swing Parade of 1946", "Swing Shift Cinderella", "Swing Shift Maisie", "Swing Shift", "Swing That Cheer", "Swing the Western Way", "Swing Time", "Swing Vote", "Swing Your Lady", "Swing Your Partner", "Swing Your Partners", "Swing!", "Swing, Cowboy, Swing", "Swing, Sister, Swing", "Swingers", "Swingin' Along", "Swingin' on a Rainbow", "Swingtime Johnny", "Swiss Family Robinson", "Swiss Miss", "Swiss Tour", "Switch", "Switchback", "Switchblade Sisters", "Switchin' Kitten", "Switching Channels", "Swoon", "Sword in the Desert", "Sword of the Avenger", "Sword of Venus", "Swordfish", "Sworn Enemy", "Sybil", "Sylvester", "Sylvia Scarlett", "Sylvia", "Symphony of Living", "Symphony of Six Million", "Synanon", "Synchronicity", "Syncopating Sue", "Syncopation", "Synthetic Sin", "Syriana", "T.R. Baskin", "Table 19", "Table for Five", "Tabu", "Tacoma Narrows Bridge Collapse (Galloping Gertie)", "Tadpole", "Tad's Swimming Hole", "Tag Day", "Tag", "Taggart", "Tahiti Honey", "Tahiti Nights", "Tail Spin", "'Taint Legal", "Tainted Money", "Tai-Pan", "Take a Chance", "Take a Giant Step", "Take a Hard Ride", "Take a Letter, Darling", "Take Care of My Little Girl", "Take Down", "Take Her, She's Mine", "Take It Big", "Take It from Me", "Take It or Leave It", "Take Me Home Tonight", "Take Me Home", "Take Me Out to the Ball Game", "Take Me to Town", "Take One False Step", "Take Shelter", "Take the High Ground!", "Take the Lead", "Take the Money and Run", "Take the Stand", "Take This Job and Shove It", "Taken 2", "Taken 3", "Taken", "Takers", "Taking a Chance", "Taking Care of Business", "Taking Lives", "Taking Off", "Taking Woodstock", "Talent for the Game", "Talent Scout", "Tales from the Darkside: The Movie", "Tales from the Hood", "Tales of Erotica", "Tales of Manhattan", "Tales of Robin Hood", "Tales of Terror", "Talk About a Lady", "Talk About a Stranger", "Talk Radio", "Talk to Me", "Talkin' Dirty After Dark", "Tall in the Saddle", "Tall Man Riding", "Tall Story", "Tall Tale", "Tall, Dark and Handsome", "Talladega Nights: The Ballad of Ricky Bobby", "Taming Sutton's Gal", "Tammy and the Bachelor", "Tammy and the Doctor", "Tammy Tell Me True", "Tammy", "Tampico", "Tanganyika", "Tangier Incident", "Tangier", "Tangled Threads", "Tangled", "Tango & Cash", "Tango Tangles", "Tango", "Tank Girl", "Tank", "Tanned Legs", "Tap Roots", "Tap", "Tape", "Tapeheads", "Taps", "Tarantula", "Taras Bulba", "Tarawa Beachhead", "Target Earth", "Target for Today", "Target Hong Kong", "Target Invisible", "Target of an Assassin", "Target Unknown", "Target Zero II", "Target Zero", "Target", "Targets", "Tarnationthe", "Tarnish", "Tarnished Angel", "Tarnished Lady", "Tarnished", "Tars and Spars", "Tarzan and His Mate", "Tarzan and the Amazons", "Tarzan and the Huntress", "Tarzan and the Leopard Woman", "Tarzan and the Lost City", "Tarzan and the Lost Safari", "Tarzan and the Mermaids", "Tarzan and the She-Devil", "Tarzan and the Slave Girl", "Tarzan and the Valley of Gold", "Tarzan Escapes", "Tarzan Finds a Son!", "Tarzan Goes to India", "Tarzan of the Apes", "Tarzan the Ape Man", "Tarzan the Magnificent", "Tarzan Triumphs", "Tarzan", "Tarzan, the Ape Man", "Tarzan's Desert Mystery", "Tarzan's Fight for Life", "Tarzan's Greatest Adventure", "Tarzan's Hidden Jungle", "Tarzan's Magic Fountain", "Tarzan's New York Adventure", "Tarzan's Peril", "Tarzan's Revenge", "Tarzan's Savage Fury", "Tarzan's Secret Treasure", "Tarzan's Three Challenges", "Task Force", "Tattoo", "Taxi 13", "Taxi Driver", "Taxi to the Dark Side", "Taxi!", "Taxi", "Taxi, Mister", "Taza, Son of Cochise", "Tea and Sympathy", "Tea for Three", "Tea for Two", "Tea: With a Kick!", "Teacher's Pet", "Teachers", "Teaching Mrs. Tingle", "Team America: World Police", "Tear Gas Squad", "Tearin' Loose", "Tearing Through", "Tears of the Sun", "Technicolor for Industrial Films", "Ted 2", "Ted", "Teddy at the Throttle", "Teddy the Rough Rider", "Teddy's Goat", "Tee for Two", "Teen Titans Go! To the Movies", "Teen Witch", "Teen Wolf Too", "Teen Wolf", "Teenage Cave Man", "Teen-Age Crime Wave", "Teenage Doll", "Teenage Millionaire", "Teenage Mutant Nina Turtles", "Teenage Mutant Ninja Turtles II: The Secret of the Ooze", "Teenage Mutant Ninja Turtles III", "Teenage Mutant Ninja Turtles", "Teenage Rebel", "Teen-Age Strangler", "Teenage Zombies", "Teenagers from Outer Space", "Teeth", "Tekken", "Telefon", "Telephone Operator", "Television Spy", "Tell It to a Star", "Tell It to the Judge", "Tell Me a Riddle", "Tell Me That You Love Me, Junie Moon", "Tell No Tales", "Tell Them Willie Boy Is Here", "Telling Lies in America", "Telling the World", "Tempest", "Temple Tower", "Temporary Marriage", "Temptation", "Temptation: Confessions of a Marriage Counselor", "Ten Cents a Dance", "Ten Days to Tulara", "Ten Gentlemen from West Point", "Ten Little Indians", "Ten Nights in a Barroom", "Ten North Frederick", "Ten Seconds to Hell", "Ten Tall Men", "Ten Thousand Bedrooms", "Ten Wanted Men", "Tenacious D in The Pick of Destiny", "Tender Comrade", "Tender Fictions", "Tender Is the Night", "Tender Mercies", "Tenderloin", "Tennessee Champ", "Tennessee Johnson", "Tennessee", "Tennessee's Partner", "Tennis Chumps", "Tension at Table Rock", "Tension", "Tentacles of the North", "Tentacles", "Tenth Avenue Angel", "Tenth Avenue Kid", "Tenting Tonight on the Old Camp Ground", "Tequila Sunrise", "Teresa", "Teresa's Tattoo", "Terminal City Ricochet", "Terminal Island", "Terminal Station", "Terminal Velocity", "Terminal", "Terminator 2: Judgment Day", "Terminator 3: Rise of the Machines", "Terminator Genisys", "Terminator Salvation", "Terminus", "Terms of Endearment", "Terrible Ted", "Terrible Teddy, the Grizzly King", "Terribly Stuck Up", "Terror Aboard", "Terror at Midnight", "Terror by Night", "Terror from the Year 5000", "Terror in a Texas Town", "Terror in the Aisles", "Terror Is a Man", "Terror Mountain", "Terror of the Plains", "Terror Squad", "Terror Street", "Terror Tract", "Terror Trail", "Terror Train", "Terrors on Horseback", "TerrorVision", "Terry and the Pirates", "Tess of the d'Urbervilles", "Tess of the Storm Country", "Tessie", "Test Pilot", "Test Tube Babies", "Test", "Testament", "Testosterone", "Tetro", "Tex Granger", "Tex", "Texans Never Cry", "Texas Across the River", "Texas Bad Man", "Texas Carnival", "Texas Chainsaw 3D", "Texas Chainsaw Massacre: The Next Generation", "Texas City", "Texas Cyclone", "Texas Dynamo", "Texas Gun Fighter", "Texas Jack", "Texas Killing Fields", "Texas Lady", "Texas Lawmen", "Texas Masquerade", "Texas Panhandle", "Texas Rangers Ride Again", "Texas Terror", "Texas Terrors", "Texas Tom", "Texas Trail", "Texas", "Texas, Brooklyn and Heaven", "Texasville", "Thank God It's Friday", "Thank You for Smoking", "Thank You for Your Service", "Thank You", "Thank You, Jeeves!", "Thank Your Lucky Stars", "Thanks a Million", "Thanks for Everything", "Thanks for the Buggy Ride", "Thanks for the Memory", "That Awkward Moment", "That Brennan Girl", "That Certain Age", "That Certain Feeling", "That Certain Thing", "That Certain Woman", "That Championship Season", "That Cold Day in the Park", "That Darn Cat!", "That Darn Cat", "That Devil Quemado", "That Forsyte Woman", "That French Lady", "That Funny Feeling", "That Gang of Mine", "That Girl from Paris", "That Hagen Girl", "That Hamilton Woman", "That I May Live", "That Justice Be Done", "That Kind of Woman", "That Lady in Ermine", "That Lucky Touch", "That Man from Tangier", "That Man Jack!", "That Man's Here Again", "That Midnight Kiss", "That Model from Paris", "That Navy Spirit", "That Nazty Nuisance", "That Night in Rio", "That Night with You", "That Night!", "That Night", "That Old Feeling", "That Other Woman", "That Royle Girl", "That Texas Jamboree", "That Thing You Do!", "That Touch of Mink", "That Uncertain Feeling", "That Was Then... This Is Now", "That Way with Women", "That Wonderful Urge", "That's Black Entertainment", "That's Dancing!", "That's Entertainment! III", "That's Entertainment!", "That's Entertainment, Part II", "That's Good", "That's Gratitude", "That's Happiness", "That's Him", "That's Life!", "That's My Baby!", "That's My Baby", "That's My Boy", "That's My Daddy", "That's My Gal", "That's My Man", "That's My Mommy", "That's Right - You're Wrong", "That's the Spirit", "That's What I Am", "The $1,000,000 Reward", "The 11th Hour", "The 13th Letter", "The 13th Man", "The 13th Warrior", "The 15:17 to Paris", "The 27th Day", "The 30 Foot Bride of Candy Rock", "The 300 Spartans", "The 3rd Voice", "The 40-Year-Old Virgin", "The 49th Man", "The 4th Floor", "The 5,000 Fingers of Dr. T", "The 5th Wave", "The 6th Day", "The 6th Man", "The 8th Plague", "The 957th Day", "The A.B.C. of Love", "The Abandonment", "The ABCs of Death", "The Abductors", "The Absent-Minded Professor", "The Abyss", "The Accident Attorney", "The Accidental Husband", "The Accidental Tourist", "The Accountant", "The Accused", "The Accusing Finger", "The Accusing Toe", "The Ace of Clubs", "The Ace of Hearts", "The Acquittal", "The Actress", "The Addams Family", "The Addiction", "The Adjustment Bureau", "The Admiral Was a Lady", "The Adorable Cheat", "The Adorable Deceiver", "The Adventure of Sherlock Holmes' Smarter Brother", "The Adventure Shop", "The Adventurer", "The Adventurers", "The Adventures of a Rookie", "The Adventures of Buckaroo Banzai", "The Adventures of Captain Africa", "The Adventures of Dollie", "The Adventures of Don Coyote", "The Adventures of Elmo in Grouchland", "The Adventures of Ford Fairlane", "The Adventures of Hajji Baba", "The Adventures of Huck Finn", "The Adventures of Huckleberry Finn", "The Adventures of Ichabod and Mr. Toad", "The Adventures of Jane Arden", "The Adventures of Kathlyn", "The Adventures of Marco Polo", "The Adventures of Mark Twain", "The Adventures of Martin Eden", "The Adventures of Pinocchio", "The Adventures of Pluto Nash", "The Adventures of Quentin Durward", "The Adventures of Robin Hood", "The Adventures of Robinson Crusoe", "The Adventures of Rocky and Bullwinkle", "The Adventures of Sebastian Cole", "The Adventures of Sharkboy and Lavagirl in 3-D", "The Adventures of Sherlock Holmes", "The Adventures of Smilin' Jack", "The Adventures of Tarzan", "The Adventures of the American Rabbit", "The Adventures of the Wilderness Family", "The Adventures of Tintin: The Secret of the Unicorn", "The Adventures of Tom Sawyer", "The Adventurous Blonde", "The Adventurous Sex", "The Advisor", "The Affair of Susan", "The Affair of the Necklace", "The Affairs of Anatol", "The Affairs of Annabel", "The Affairs of Cellini", "The Affairs of Dobie Gillis", "The Affairs of Jimmy Valentine", "The Affairs of Martha", "The Affairs of Susan", "The African Queen", "The Age for Love", "The Age of Adaline", "The Age of Consent", "The Age of Desire", "The Age of Innocence", "The Aggression Scale", "The Agony and the Ecstasy", "The Air Circus", "The Air Hawk", "The Air Legion", "The Air Mail", "The Air Patrol", "The Air Up There", "The Alamo", "The Alamo: 13 Days to Glory", "The Alarm", "The Alarmist", "The Alaskan", "The Alibi", "The Alien Encounters", "The All American", "The All-American Boy", "The Alligator People", "The Allnighter", "The Alpha Caper", "The Altar Stairs", "The Amateur Adventuress", "The Amateur Detective", "The Amateur", "The Amati Girls", "The Amazing Adventures of the Living Corpse", "The Amazing Colossal Man", "The Amazing Dr. Clitterhouse", "The Amazing Impostor", "The Amazing Mr. Bickford", "The Amazing Mr. Williams", "The Amazing Mr. X", "The Amazing Mrs. Holliday", "The Amazing Panda Adventure", "The Amazing Spider-Man 2", "The Amazing Spider-Man", "The Amazing Transparent Man", "The Amazing Wife", "The Ambassador", "The Ambassador's Daughter", "The Ambulance", "The Ambushers", "The American Astronaut", "The American President", "The American", "The Americanization of Emily", "The Americano", "The Amityville Horror", "The Amorous Adventures of Moll Flanders", "The Ancient Highway", "The Ancient Mariner", "The Anderson Tapes", "The Andromeda Strain", "The Angel Levine", "The Angel Wore Red", "The Angels Wash Their Faces", "The Angriest Man in Brooklyn", "The Angry Birds Movie", "The Angry Hills", "The Angry Red Planet", "The Animal Kingdom", "The Animal", "The Anniversary Party", "The Answer Man", "The Ant Bully", "The Apache", "The Apartment", "The Ape Man", "The Ape", "The Apostle", "The Appaloosa", "The Apparition", "The Apple Dumpling Gang Rides Again", "The Apple Dumpling Gang", "The Appointment", "The Appointments of Dennis Jennings", "The April Fools", "The Arab", "The Archeologist", "The Architect", "The Arena", "The Argyle Case", "The Argyle Secrets", "The Aristocats", "The Aristocrats", "The Arizona Cowboy", "The Arizona Cyclone", "The Arizona Express", "The Arizona Kid", "The Arizona Raiders", "The Arizona Ranger", "The Arizona Romeo", "The Arizona Streak", "The Arizona Wildcat", "The Arizonian", "The Arkansas Swing", "The Arkansas Traveler", "The Arm Behind the Army", "The Arnelo Affair", "The Arrangement", "The Arrival", "The Art of Getting By", "The Art of Love", "The Art of Self Defense", "The Art of War", "The Artist's Dilemma", "The Aryan", "The Ascent of Mont Blanc", "The Asphalt Jungle", "The Assassination of Jesse James by the Coward Robert Ford", "The Assassination of Richard Nixon", "The Assayer of Lone Gap", "The Assignment", "The Associate", "The Astounding She-Monster", "The Astronaut Farmer", "The Astronaut's Wife", "The Astro-Zombies", "The A-Team", "The Atomic Cafe", "The Atomic City", "The Atomic Kid", "The Atomic Submarine", "The Auction Block", "The Auctioneer", "The Automobile Race", "The Automobile Thieves", "The Avalanche", "The Avenger", "The Avengers", "The Avenging Arrow", "The Avenging Conscience", "The Avenging Rider", "The Average Woman", "The Aviator", "The Awakening of Jim Burke", "The Awakening", "The Awful Orphan", "The Awful Truth", "The Babe Ruth Story", "The Babe", "The Babes in the Woods", "The Baby Cyclone", "The Baby Maker", "The Baby", "The Babymakers", "The Baby-Sitters Club", "The Bachelor and the Bobby-Soxer", "The Bachelor Daddy", "The Bachelor Father", "The Bachelor Girl", "The Bachelor Party", "The Bachelor", "The Bachelor's Daughters", "The Back Trail", "The Back-up Plan", "The Bad and the Beautiful", "The Bad Batch", "The Bad Boy", "The Bad Lands", "The Bad Lieutenant: Port of Call New Orleans", "The Bad Man of Brimstone", "The Bad Man", "The Bad News Bears Go to Japan", "The Bad News Bears in Breaking Training", "The Bad News Bears", "The Bad One", "The Bad Seed", "The Badlanders", "The Bag Man", "The Baggage Smasher", "The Baited Trap", "The Balcony", "The Ballad of Cable Hogue", "The Ballad of Jack and Rose", "The Ballad of Josie", "The Ballad of Little Jo", "The Ballad of Ramblin' Jack", "The Baltimore Bullet", "The Bamboo Blonde", "The Bamboo Prison", "The Bamboo Saucer", "The Band Plays On", "The Band Wagon", "The Bandbox", "The Bandit Buster", "The Bandit of Sherwood Forest", "The Bandit of Tropico", "The Bandit Queen", "The Bandit Tamer", "The Bandit's Baby", "The Bandits of Corsica", "The Bandit's Waterloo", "The Bandolero", "The Banger Sisters", "The Bank Dick", "The Barbarian and the Geisha", "The Barbarian", "The Barber of Seville", "The Barefoot Boy", "The Barefoot Contessa", "The Barefoot Executive", "The Barefoot Mailman", "The Bargain", "The Barker", "The Barkleys of Broadway", "The Barnstormer", "The Baron of Arizona", "The Baroness and the Butler", "The Barren Gain", "The Barrens", "The Barrets of Wimpole Street", "The Barrier", "The Bashful Bachelor", "The Basketball Diaries", "The Basketball Fix", "The Bat Whispers", "The Bat", "The Battery", "The Battle at Apache Pass", "The Battle of Britain", "The Battle of China", "The Battle of Gettysburg", "The Battle of Love's Return", "The Battle of Midway", "The Battle of Rogue River", "The Battle of Russia", "The Battle of San Pietro", "The Battle of Shaker Heights", "The Battle of the Sexes", "The Baxter", "The Bay", "The Baytown Outlaws", "The Beach Girls", "The Beach Party at the Threshold of Hell", "The Beach", "The Beachcomber", "The Bearcat", "The Beast from 20,000 Fathoms", "The Beast of Borneo", "The Beast of Hollow Mountain", "The Beast of the City", "The Beast of Yucca Flats", "The Beast with Five Fingers", "The Beast Within", "The Beast", "The Beastmaster", "The Beat Generation", "The Beautician and the Beast", "The Beautiful and Damned", "The Beautiful Blonde from Bashful Bend", "The Beautiful Cheat", "The Beautiful City", "The Beautiful Sinner", "The Beauty Inside", "The Beauty Market", "The Beauty Prize", "The Beauty Shop", "The Beauty Shoppers", "The Beaver", "The Bedford Incident", "The Bedroom Window", "The Beggar Child", "The Beginning or the End", "The Beguiled", "The Believer", "The Believers", "The Belko Experiment", "The Bell Boy", "The Bell Jar", "The Bellboy and the Playgirls", "The Bellboy", "The Belle of Bar-Z Ranch", "The Belle of Broadway", "The Belle of New York", "The Bells of St. Mary's", "The Bells", "The Beloved Bachelor", "The Beloved Brat", "The Beloved Brute", "The Beloved Rogue", "The Benchwarmers", "The Benefactor", "The Benny Goodman Story", "The Benson Murder Case", "The Best Bad Man", "The Best Bad Thing", "The Best is Yet to Come", "The Best Little Whorehouse in Texas", "The Best Man Holiday", "The Best Man Wins", "The Best Man", "The Best of Everything", "The Best of Times", "The Best People", "The Best Thief in the World", "The Best Things in Life Are Free", "The Best Two Years", "The Best Years of Our Lives", "The Betrayal - Nerakhoon", "The Betsy", "The Bette Midler Show", "The Better Angels", "The Better Man", "The Better 'Ole", "The Better Way", "The Better Wife", "The Beverly Hillbillies", "The BFG", "The Bible: In the Beginning", "The Big Bad Swim", "The Big Bang", "The Big Bankroll", "The Big Beat", "The Big Bird Cage", "The Big Blue", "The Big Bluff", "The Big Bonanza", "The Big Boodle", "The Big Bounce", "The Big Brain", "The Big Brawl", "The Big Broadcast of 1936", "The Big Broadcast of 1937", "The Big Broadcast of 1938", "The Big Broadcast", "The Big Bus", "The Big Cage", "The Big Caper", "The Big Cat", "The Big Catch", "The Big Chance", "The Big Chase", "The Big Chill", "The Big Circus", "The Big City", "The Big Clock", "The Big Combo", "The Big Country", "The Big Cube", "The Big Diamond Robbery", "The Big Doll House", "The Big Easy", "The Big Fisherman", "The Big Fix", "The Big Gamble", "The Big Game", "The Big Green", "The Big Gusher", "The Big Guy", "The Big Hangover", "The Big Heat", "The Big Hit", "The Big House", "The Big Idea", "The Big Kahuna", "The Big Killing", "The Big Knife", "The Big Land", "The Big Lebowski", "The Big Lie", "The Big Lift", "The Big Little Person", "The Big Mouth", "The Big Night", "The Big Noise", "The Big One", "The Big Operator", "The Big Parade", "The Big Party", "The Big Picture", "The Big Pond", "The Big Punch", "The Big Red One", "The Big Score", "The Big Shakedown", "The Big Shot", "The Big Show", "The Big Show-Off", "The Big Sick", "The Big Sky", "The Big Sleep", "The Big Sombrero", "The Big Stampede", "The Big Steal", "The Big Store", "The Big Street", "The Big Stunt", "The Big Tease", "The Big Timer", "The Big Town", "The Big Trail", "The Big Trees", "The Big Wedding", "The Big Wheel", "The Big Year", "The Bigamist", "The Biggest Bundle of Them All", "The Billion Dollar Hobo", "The Billion Dollar Scandal", "The Bingo Long Traveling All-Stars & Motor Kings", "The Birdcage", "The Birds and the Bees", "The Birds II: Land's End", "The Birds", "The Birth of a Nation", "The Biscuit Eater", "The Bishop Misbehaves", "The Bishop Murder Case", "The Bishop of the Ozarks", "The Bishop's Emeralds", "The Bishop's Wife", "The Bitter Tea of General Yen", "The Black Arrow", "The Black Arrow: A Tale of the Two Roses", "The Black Bag", "The Black Bird", "The Black Camel", "The Black Castle", "The Black Cat", "The Black Circle", "The Black Dahlia", "The Black Dakotas", "The Black Doll", "The Black Gate", "The Black Godfather", "The Black Hills Express", "The Black Hole", "The Black Knight", "The Black Lash", "The Black Marble", "The Black Orchid", "The Black Parachute", "The Black Pearl", "The Black Pirate", "The Black Pirates", "The Black Raven", "The Black Room", "The Black Rose", "The Black Scorpion", "The Black Shield of Falworth", "The Black Sleep", "The Black Stallion Returns", "The Black Stallion", "The Black Swan", "The Black Viper", "The Black Watch", "The Black Whip", "The Black Widow", "The Black Windmill", "The Blackbird", "The Blackcoat's Daughter", "The Blacksmith", "The Blair Witch Project", "The Blank Generation", "The Blazing Forest", "The Blazing Sun", "The Blazing Trail", "The Blind Adventure", "The Blind Goddess", "The Blind Side", "The Blinding Trail", "The Bling Ring", "The Blob", "The Blocked Trail", "The Blonde Bandit", "The Blonde from Singapore", "The Blonde Saint", "The Blood of Jesus", "The Blood Oranges", "The Bloodhound", "The Blot on the Shield", "The Blot", "The Blue Angel", "The Blue Bandanna", "The Blue Bird", "The Blue Butterfly", "The Blue Dahlia", "The Blue Danube", "The Blue Eagle", "The Blue Gardenia", "The Blue Hour", "The Blue Iguana", "The Blue Lagoon", "The Blue Streak", "The Blue Veil", "The Blues Brothers", "The Bluffer", "The Bluffers", "The Boat", "The Boatniks", "The Bob Mathias Story", "The Body in the Trunk", "The Body Punch", "The Body Snatcher", "The Bodyguard", "The Bohemian Girl", "The Boiling Point", "The Bold and the Brave", "The Bold Caballero", "The Bold Frontiersman", "The Bolted Door", "The Bonanza Buckaroo", "The Bond Boy", "The Bond", "The Bondage of Barbara", "The Bonded Woman", "The Bondman", "The Bone Collector", "The Boneyard", "The Bonfire of the Vanities", "The Boob", "The Boogeyman", "The Boogie Man Will Get You", "The Book of Eli", "The Book of Esther", "The Book of Henry", "The Book of Life", "The Book of Love", "The Book Thief", "The Boomerang", "The Boondock Saints II: All Saints Day", "The Boondock Saints", "The Boost", "The Bootlegger's Daughter", "The Bootleggers", "The Border Cavalier", "The Border Legion", "The Border Patrol", "The Border Patrolman", "The Border Sheriff", "The Border Whirlwind", "The Border", "The Born Losers", "The Borrower", "The Boss Baby", "The Boss of Big Town", "The Boss of Camp Four", "The Boss of Rustler's Roost", "The Boss Rider of Gun Creek", "The Boss", "The Boston Strangler", "The Bostonians", "The Bottom of the Bottle", "The Boudoir Diplomat", "The Bounty Hunter", "The Bounty Killer", "The Bounty", "The Bourne Identity", "The Bourne Legacy", "The Bourne Supremacy", "The Bourne Ultimatum", "The Bowery Bishop", "The Bowery Boys Meet the Monsters", "The Bowery", "The Bowling Alley Cat", "The Box", "The Boxer", "The Boxtrolls", "The Boy and the Pirates", "The Boy Friend", "The Boy from Indiana", "The Boy from Oklahoma", "The Boy from Stalingrad", "The Boy in the Striped Pyjamas", "The Boy Next Door", "The Boy Who Could Fly", "The Boy Who Cried Werewolf", "The Boy with Green Hair", "The Boy", "The Boys from Brazil", "The Boys from Syracuse", "The Boys in Company C", "The Boys in the Band", "The Boys Next Door", "The Brady Bunch Movie", "The Brahma Diamond", "The Brain Eaters", "The Brain from Planet Arous", "The Brain That Wouldn't Die", "The Bramble Bush", "The Brand Blotter", "The Brand", "The Branded Sombrero", "The Brasher Doubloon", "The Brass Bottle", "The Brass Bowl", "The Brass Legend", "The Brass Teapot", "The Brat", "The Bravados", "The Brave Bulls", "The Brave Engineer", "The Brave Little Toaster", "The Brave One", "The Brave", "The Breakfast Club", "The Breaking Point", "The Break-Up", "The Breath of Scandal", "The Breathless Moment", "The Breed", "The Bribe", "The Bride Came C.O.D.", "The Bride Comes Home", "The Bride Goes Wild", "The Bride of Fear", "The Bride Walks Out", "The Bride Wore Boots", "The Bride Wore Crutches", "The Bride Wore Red", "The Bride", "The Bride's Awakening", "The Bride's Play", "The Bride's Silence", "The Bridge at Remagen", "The Bridge of San Luis Rey", "The Bridge of Sighs", "The Bridge", "The Bridges at Toko-Ri", "The Bridges of Madison County", "The Brigand", "The Bright Shawl", "The Brighton Strangler", "The Brink's Job", "The Broad Road", "The Broadway Boob", "The Broadway Gallant", "The Broadway Hoofer", "The Broadway Madonna", "The Broadway Melody", "The Broadway Peacock", "The Broken Butterfly", "The Broken Commandments", "The Broken Cross", "The Broken Hearts Club: A Romantic Comedy", "The Broken Mask", "The Broken Melody", "The Broken Star", "The Broken Wing", "The Bronc Stomper", "The Broncho Kid", "The Bronze Buckaroo", "The Bronze", "The Brother from Another Planet", "The Brotherhood", "The Brothers Bloom", "The Brothers Grimm", "The Brothers Karamazov", "The Brothers McMullen", "The Brothers Rico", "The Brothers Solomon", "The Brothers", "The Brown Bunny", "The Brown Derby", "The Bruiser", "The Brute Breaker", "The Brute Man", "The Buccaneer", "The Buckaroo Kid", "The Bucket List", "The Buckskin Lady", "The Buddy Holly Story", "The Buddy System", "The Bugle Sounds", "The Bugs Bunny/Road Runner Movie", "The Bullfighters", "The Bund, Shanghai", "The 'Burbs", "The Burden of Proof", "The Burglar", "The Burial Society", "The Burlesque Suicide, No. 2", "The Burning Cross", "The Burning Hills", "The Burning Trail", "The Burning", "The Bushbaby", "The Busher", "The Bushranger", "The Bushwackers", "The Business of Love", "The Business of Strangers", "The Buster Keaton Story", "The Buster", "The Busy Body", "The Butcher Boy", "The Butcher's Wife", "The Butler", "The Butter and Egg Man", "The Butterfly Effect", "The Butterfly", "The Bye Bye Man", "The Cabin in the Cotton", "The Cabin in the Woods", "The Cable Guy", "The Cactus Kid", "The Caddy", "The Caged Bird", "The Caine Mutiny", "The Calgary Stampede", "The California Mail", "The California Trail", "The Californian", "The Call of Courage", "The Call of Home", "The Call of the Canyon", "The Call of the Circus", "The Call of the Heart", "The Call of the Klondike", "The Call of the North", "The Call of the Soul", "The Call of the Traumerei", "The Call of the Wild", "The Call of the Wilderness", "The Call of Youth", "The Call", "The Calling of Dan Matthews", "The Cambric Mask", "The Cameo of the Yellowstone", "The Cameraman", "The Campaign", "The Campus Flirt", "The Canary Murder Case", "The Cancelled Debt", "The Candidate", "The Candy Snatchers", "The Candy Tangerine Man", "The Cannonball Run", "The Canterville Ghost", "The Canyon of Light", "The Canyons", "The Cape Canaveral Monsters", "The Cape Town Affair", "The Caper of the Golden Bulls", "The Captain from Köpenick", "The Captain Hates the Sea", "The Captain's Captain", "The Captain's Kid", "The Captive City", "The Captive", "The Capture", "The Car", "The Caravan Trail", "The Cardboard Lover", "The Cardinal", "The Career of Katherine Bush", "The Careless Age", "The Careless Years", "The Caretakers", "The Carey Treatment", "The Caribbean Mystery", "The Cariboo Trail", "The Carnival Girl", "The Carnival Man", "The Carpetbaggers", "The Carson City Kid", "The Case Against Brooklyn", "The Case Against Mrs. Ames", "The Case for Christ", "The Case of Lena Smith", "The Case of Sergeant Grischa", "The Case of the Black Cat", "The Case of the Black Parrot", "The Case of the Curious Bride", "The Case of the Howling Dog", "The Case of the Lucky Legs", "The Case of the Stuttering Bishop", "The Case of the Velvet Claws", "The Casino Murder Case", "The Castaway Cowboy", "The Castaways", "The Cat and the Canary", "The Cat and the Fiddle", "The Cat and the Mermouse", "The Cat Concerto", "The Cat Creeps", "The Cat from Outer Space", "The Cat in the Hat", "The Catered Affair", "The Catman of Paris", "The Cat's Meow", "The Cats of Mirikitani", "The Cat's Pajamas", "The Cat's-Paw", "The Cattle Thief", "The Cavalier", "The Cave", "The Caveman", "The Caveman's Valentine", "The Cavern", "The Cell", "The Celluloid Closet", "The Cemetery Club", "The Center of the World", "The Central Park Five", "The Certainty of Man", "The Chairman", "The Challenge", "The Chamber", "The Champ", "The Champion Liar", "The Champion of Lost Causes", "The Champion", "The Chance of a Lifetime", "The Changeling", "The Change-Up", "The Chaos Experiment", "The Chaperone", "The Chapman Report", "The Charge at Feather River", "The Charge of the Gauchos", "The Charge of the Light Brigade", "The Charlatan", "The Charles Bukowski Tapes", "The Charmer", "The Chase", "The Chaser", "The Cheap Detective", "The Cheat", "The Cheaters", "The Checkered Coat", "The Checkered Flag", "The Cheerful Fraud", "The Cherokee Flash", "The Cherokee Kid", "The Cherokee Strip", "The Cheyenne Kid", "The Cheyenne Social Club", "The Chicago Fire", "The Chicago Kid", "The Chicken from Outer Space", "The Chicken of Tomorrow", "The Chief", "The Child Stealers", "The Children Act", "The Children in the House", "The Children of Sanchez", "The Children", "The Children's Hour", "The Chilling", "The China Lake Murders", "The China Syndrome", "The Chinese Parrot", "The Chinese Ring", "The Chipmunk Adventure", "The Chocolate of the Gang", "The Chocolate Soldier", "The Chocolate War", "The Choice", "The Choirboys", "The Chorus Lady", "The Chosen", "The Christian", "The Christine Jorgensen Story", "The Christmas Kid", "The Chronicles of Narnia: Prince Caspian", "The Chronicles of Narnia: The Lion, the Witch and the Wardrobe", "The Chronicles of Narnia: The Voyage of the Dawn Treader", "The Chronicles of Riddick", "The Chumscrubber", "The Cider House Rules", "The Cimarron Kid", "The Cincinnati Kid", "The Cinema Murder", "The Circle", "The Circus Clown", "The Circus Cowboy", "The Circus Cyclone", "The Circus Kid", "The Circus Queen Murder", "The Circus", "The Cisco Kid and the Lady", "The Cisco Kid Returns", "The Cisco Kid", "The Citizen", "The City of Comrades", "The City of Dim Faces", "The City Slicker", "The City That Never Sleeps", "The City", "The Clan of the Cave Bear", "The Clash of the Wolves", "The Class of Miss MacMichael", "The Clay Pigeon", "The Clean Heart", "The Clean Up", "The Cleanse", "The Clean-Up Man", "The Clean-Up", "The Clearing", "The Client", "The Climax", "The Climbers", "The Clinging Vine", "The Clock", "The Cloth", "The Cloud Rider", "The Cloverfield Paradox", "The Clown", "The Coast of Folly", "The Cobbler", "The Cobra Strikes", "The Cobweb", "The Coca-Cola Kid", "The Cockettes", "The Cockeyed Miracle", "The Cock-Eyed World", "The Cocoanuts", "The Cocoon and the Butterfly", "The Code of the Scarlet", "The Coffin Ship", "The Cohens and Kellys in Africa", "The Cohens and Kellys in Hollywood", "The Cohens and Kellys in Trouble", "The Cohens and the Kellys in Atlantic City", "The Cohens and the Kellys in Paris", "The Cohens and the Kellys in Scotland", "The Cold Light of Day", "The Collection", "The Collector", "The College Boob", "The College Coquette", "The College Hero", "The Colonel and the King", "The Color of Money", "The Color of Time", "The Colorado Kid", "The Colossus of New York", "The Comancheros", "The Combat", "The Combination of the Safe", "The Come On", "The Comebacks", "The Comedians", "The Comedy of Terrors", "The Comedy", "The Comic", "The Coming of Amos", "The Coming of the Law", "The Coming of the Padres", "The Command", "The Commies Are Coming, the Commies Are Coming", "The Commitments", "The Common Cause", "The Common Law", "The Common Sin", "The Commuter", "The Commuters", "The Company Men", "The Company of Wolves", "The Company She Keeps", "The Company You Keep", "The Company", "The Competition", "The Computer Wore Tennis Shoes", "The Concentratin' Kid", "The Concorde ... Airport '79", "The Concrete Jungle", "The Condemned", "The Confessions of Amans", "The Confidence Man", "The Conjuring 2", "The Conjuring", "The Conquering Horde", "The Conquering Power", "The Conqueror", "The Conquerors", "The Conspirator", "The Conspirators", "The Constant Nymph", "The Constant Woman", "The Contender", "The Contract", "The Contractor", "The Conversation", "The Cook", "The Cookout", "The Cool and the Crazy", "The Cool Surface", "The Cool World", "The Cooler", "The Cop", "The Copperhead", "The Cord of Life[1]", "The Core", "The Corn Is Green", "The Corpse Came C.O.D.", "The Corpse Vanishes", "The Corruptor", "The Corsican Brothers", "The Cosmic Man", "The Cossacks", "The Cotton Club", "The Couch Trip", "The Counselor", "The Count of Luxembourg", "The Count of Monte Cristo", "The Count of Ten", "The Count", "The Counterfeit Traitor", "The Counterfeiters", "The Countess of Monte Cristo", "The Country Bears", "The Country Beyond", "The Country Cousin", "The Country Doctor", "The Country Flapper", "The Country Girl", "The Country Kid", "The County Chairman", "The County Fair", "The Courageous Coward", "The Court Jester", "The Court-Martial of Billy Mitchell", "The Courtship of Andy Hardy", "The Courtship of Eddie's Father", "The Courtship of Miles Standish", "The Cove", "The Covenant", "The Covered Wagon", "The Cowboy and the Countess", "The Cowboy and the Flapper", "The Cowboy and the Indians", "The Cowboy and the Kid", "The Cowboy and the Lady", "The Cowboy Cop", "The Cowboy Kid", "The Cowboy Millionaire", "The Cowboy Musketeer", "The Cowboy Quarterback", "The Cowboy Star", "The Cowboy Way", "The Cowboys", "The Cracker's Bride", "The Cradle", "The Craft", "The Crash", "The Craving", "The Crawling Hand", "The Crazies", "The Crazy World of Julius Vrooder", "The Creation of the Humanoids", "The Creature Walks Among Us", "The Creeper", "The Creeping Terror", "The Cremaster Cycle", "The Crew", "The Cricket on the Hearth", "The Crime Doctor", "The Crime Doctor's Courage", "The Crime Doctor's Diary", "The Crime Doctor's Gamble", "The Crime Doctor's Strangest Case", "The Crime Nobody Saw", "The Crime of Doctor Hallet", "The Crime of Dr. Crespi", "The Crime of Dr. Forbes", "The Crime of Helen Stanley", "The Crime of Korea", "The Crime of the Century", "The Criminal Code", "The Criminal Hypnotist", "The Crimson Canary", "The Crimson Canyon", "The Crimson Challenge", "The Crimson City", "The Crimson Code", "The Crimson Gardenia", "The Crimson Ghost", "The Crimson Key", "The Crimson Kimono", "The Crimson Pirate", "The Crimson Runner", "The Crimson Trail", "The Crocodile Hunter: Collision Course", "The Croods", "The Crook of Dreams", "The Crooked Circle", "The Crooked Road", "The Crooked Way", "The Crooked Web", "The Crosby Case", "The Cross of Lorraine", "The Cross", "The Crossing Guard", "The Crossing", "The Crossroads of New York", "The Crow", "The Crow: City of Angels", "The Crow: Wicked Prayer", "The Crowd Roars", "The Crowd", "The Crowded Hour", "The Crowded Sky", "The Crown of Lies", "The Crucible", "The Cruel Path", "The Cruel Truth", "The Crusader", "The Crusades", "The Crush", "The Cry Baby Killer", "The Cry of the Children", "The Cry of the Weak", "The Crystal Ball", "The Cub Reporter's Temptation", "The Cuban Love Song", "The Cuckoos", "The Culpepper Cattle Co.", "The Cup of Life", "The Cure for Insomnia", "The Cure", "The Cured", "The Curiosity of Chance", "The Curious Case of Benjamin Button", "The Curse of Quon Gwon", "The Curse of the Cat People", "The Curse of the Jade Scorpion", "The Curtain Falls", "The Curtain Pole[1]", "The Curve", "The Custard Cup", "The Cutting Edge", "The Cycle Savages", "The Cyclone Rider", "The Cyclops", "The Czar of Black Hollywood", "The Czar of Broadway", "The D Train", "The D.I.", "The Da Vinci Code", "The Dain Curse", "The Dakota Kid", "The Dallas Connection", "The Dalton Girls", "The Daltons Ride Again", "The Daltons' Women", "The Damned Don't Cry!", "The Dance of Life", "The Dancer of Paris", "The Dancer of the Nile", "The Dancer Upstairs", "The Dancers", "The Dancing Masters", "The Danger Girl", "The Danger Line", "The Danger Rider", "The Danger Signal", "The Dangerous Age", "The Dangerous Blonde", "The Dangerous Coward", "The Dangerous Dub", "The Dangerous Dude", "The Dangerous Flirt", "The Dangerous Little Demon", "The Dangerous Lives of Altar Boys", "The Dangerous Maid", "The Dangerous Paradise", "The Daring Caballero", "The Daring Years", "The Daring Young Man", "The Dark Angel", "The Dark at the Top of the Stairs", "The Dark Avenger", "The Dark Backward", "The Dark Corner", "The Dark Crystal", "The Dark Half", "The Dark Horse", "The Dark Hour", "The Dark Knight Rises", "The Dark Knight", "The Dark Mirror", "The Dark Past", "The Dark Side of the Moon", "The Dark Star", "The Dark Swan", "The Dark Tower", "The Dark Wind", "The Darkest Hour", "The Darkest Minds", "The Darkness", "The Darling of New York", "The Darling of the Rich", "The Darwin Awards", "The Daughter of Dr. Jekyll", "The Daughter of Rosie O'Grady", "The Dawn of a Tomorrow", "The Dawn Patrol", "The Dawn Rider", "The Dawn Trail", "The Day After Tomorrow", "The Day After", "The Day of Faith", "The Day of Reckoning", "The Day of the Animals", "The Day of the Dolphin", "The Day of the Locust", "The Day She Paid", "The Day the Bookies Wept", "The Day the Earth Stood Still", "The Daydreamer", "The Daytrippers", "The Deacon's Love Letters", "The Dead Don't Dream", "The Dead Girl", "The Dead Line", "The Dead Next Door", "The Dead Pit", "The Dead Pool", "The Dead Zone", "The Dead", "The Deadly Companions", "The Deadly Game", "The Deadly Mantis", "The Deadly Trap", "The Deadwood Coach", "The Deal", "The Death Kiss", "The Death of Poe", "The Debt", "The Deceiver", "The Deception", "The Deciding Kiss", "The Decision of Christopher Blake", "The Decks Ran Red", "The Decline of Western Civilization II", "The Decline of Western Civilization", "The Decoy", "The Deep End of the Ocean", "The Deep End", "The Deep Six", "The Deep", "The Deer Hunter", "The Deerslayer", "The Defense Rests", "The Defiant Ones", "The Delhi Camp Railway", "The Delicate Delinquent", "The Delicious Little Devil", "The Delightful Rogue", "The Delinquents", "The Delta Factor", "The Delta Force", "The Demi-Bride", "The Demon Rider", "The Demon", "The Denial", "The Dentist 2", "The Dentist", "The Denver Kid", "The Departed", "The Derby Stallion", "The Descendants", "The Desert Bride", "The Desert Flower", "The Desert Fox", "The Desert Hawk", "The Desert Horseman", "The Desert Outlaw", "The Desert Rats", "The Desert Rider", "The Desert Sheik", "The Desert Song", "The Desert Trail", "The Deserter", "The Desert's Price", "The Desert's Toll", "The Desired Woman", "The Desperado", "The Desperadoes", "The Desperados Are in Town", "The Desperate Game", "The Desperate Hero", "The Desperate Hours", "The Destroying Angel", "The Detective", "The Deuce of Spades", "The Devil and Daniel Johnston", "The Devil and Daniel Webster", "The Devil and Max Devlin", "The Devil and Miss Jones", "The Devil at 4 O'Clock", "The Devil Commands", "The Devil Dancer", "The Devil Horse", "The Devil in Miss Jones", "The Devil Inside", "The Devil Is a Sissy", "The Devil is a Woman", "The Devil Is Driving", "The Devil Makes Three", "The Devil on Horseback", "The Devil on Wheels", "The Devil Pays Off", "The Devil Plays", "The Devil Thumbs a Ride", "The Devil to Pay!", "The Devil Wears Prada", "The Devil-Doll", "The Devil's 8", "The Devil's Advocate", "The Devil's Apple Tree", "The Devil's Brigade", "The Devil's Brother", "The Devil's Candy", "The Devil's Cargo", "The Devil's Circus", "The Devil's Disciple", "The Devil's Double", "The Devil's Gulch", "The Devil's Hairpin", "The Devil's Henchman", "The Devil's Holiday", "The Devil's in Love", "The Devil's Mask", "The Devil's Mate", "The Devil's Party", "The Devil's Pass Key", "The Devil's Playground", "The Devil's Rain", "The Devil's Rejects", "The Devil's Saddle Legion", "The Devil's Skipper", "The Devil's Trademark", "The Devil-Stone", "The Diamond Bandit", "The Diamond Queen", "The Diary of a Chambermaid", "The Diary of Anne Frank", "The Dice Woman", "The Dictator", "The Dilemma", "The Dirty Dozen", "The Dirty Game", "The Disappointments Room", "The Disaster Artist", "The Disco Godfather", "The Discovery", "The Disorderly Orderly", "The Distinguished Gentleman", "The Divergent Series: Allegiant", "The Divergent Series: Insurgent", "The Divine Lady", "The Divine Sinner", "The Divine Woman", "The Diving Bell and the Butterfly", "The Divorce Racket", "The Divorce Trap", "The Divorcee", "The Dixie Flyer", "The Dixie Handicap", "The Dixie Merchant", "The Doberman Gang", "The Docks of New York", "The Doctor and the Girl", "The Doctor Takes a Wife", "The Doctor", "The Do-Deca-Pentathlon", "The Dog House", "The Dog Problem", "The Dogs of War", "The Doll Squad", "The Dolly Sisters", "The Domestics", "The Domino Principle", "The Don Is Dead", "The Donkey Party", "The Donovan Affair", "The Doolins of Oklahoma", "The Doom Generation", "The Door in the Floor", "The Doors", "The Doorway to Hell", "The Double Man", "The Double", "The Doughgirls", "The Dove", "The Dover Boys", "The Dover Road", "The Draft Horse", "The Drag Net", "The Drag-Net", "The Dragon Murder Case", "The Dragon Painter", "The Drake Case", "The Dramatic Life of Abraham Lincoln", "The Dream Girl", "The Dream Team", "The Dressmaker from Paris", "The Drifter", "The Drifters", "The Driftin' Kid", "The Driller Killer", "The Driver", "The Drop Kick", "The Drowning Pool", "The Drug Store Cowboy", "The Drums of Jeopardy", "The Du Pont Story", "The Dub", "The Duchess and the Dirtwater Fox", "The Duchess of Buffalo", "The Duck Doctor", "The Ducksters", "The Dude Cowboy", "The Dude Goes West", "The Dude Ranger", "The Dude Wrangler", "The Duel at Silver Creek", "The Duff", "The Duke Comes Back", "The Duke of West Point", "The Dukes of Hazzard", "The Dunwich Horror", "The Dutiful Dub", "The Dying Gaul", "The Dying Rooms", "The Eagle and the Hawk", "The Eagle Has Landed", "The Eagle", "The Eagle's Brood", "The Eagle's Claw", "The Eagle's Feather", "The Earl of Chicago", "The Earth Woman", "The Earthling", "The Easiest Way", "The Echo of Youth", "The Eddie Cantor Story", "The Eddy Duchin Story", "The Edge", "The Effect of Gamma Rays on Man-in-the-Moon Marigolds", "The Effects of a Trolley Car Collision", "The Egg and I", "The Egg and Jerry", "The Egg Crate Wallop", "The Egyptian", "The Eiger Sanction", "The Eighteenth Angel", "The El Paso Kid", "The Electric Horseman", "The Electric House", "The Elephant Man", "The Eleventh Commandment", "The Eleventh Hour", "The Embarrassment of Riches", "The Emoji Movie", "The Emperor Jones", "The Emperor Waltz", "The Emperor's Candlesticks", "The Emperor's Club", "The Emperor's New Groove", "The Empire Strikes Back", "The Empty Mirror", "The Empty Saddle", "The Enchanted Barn", "The Enchanted Cottage", "The Enchanted Drawing", "The Enchanted Forest", "The Enchanted Hill", "The Enchanted Valley", "The End of Innocence", "The End of Love", "The End of the Affair", "The End of the Game", "The End of Violence", "The End", "The Endless Summer", "The Enemy Below", "The Enemy Sex", "The Enemy", "The Energetic Street Cleaner", "The Enforcer", "The English Patient", "The English Teacher", "The Englishman and the Girl", "The Envoy Extraordinary", "The Equalizer 2", "The Equalizer", "The Errand Boy", "The Escape Artist", "The Escape", "The Eternal City", "The Eternal Flame", "The Eternal Magdalene", "The Eternal Mother", "The Eternal Sea", "The Eternal Struggle", "The Eternal Three", "The Europeans", "The Eve of St. Mark", "The Evening Star", "The Everlasting Whisper", "The Evidence of the Film", "The Evil Below", "The Evil Dead", "The Evil That Men Do", "The Ex", "The Exalted Flapper", "The Exciters", "The Exile of Bar-K Ranch", "The Exile", "The Exiles", "The Ex-Mrs. Bradford", "The Exorcism of Emily Rose", "The Exorcist III", "The Exorcist", "The Expendables 2", "The Expendables 3", "The Expendables", "The Expert", "The Experts", "The Exploits of Elaine", "The Explosive Generation", "The Express", "The Exquisite Sinner", "The Exquisite Thief", "The Exterminator", "The Extra Girl", "The Extra Man", "The Extraordinary Seaman", "The Eye", "The Eyes of Annie Jones", "The Eyes of Julia Deep", "The Eyes of Mystery", "The Eyes of Tammy Faye", "The Eyes of the World", "The Fabulous Baker Boys", "The Fabulous Dorseys", "The Fabulous Joe", "The Fabulous Senorita", "The Fabulous Suzanne", "The Fabulous Texan", "The Face at the Window", "The Face Behind the Mask", "The Face Between", "The Face in the Fog", "The Face of Love", "The Face of Marble", "The Face on the Bar Room Floor", "The Face on the Barroom Floor", "The Face on the Bar-Room Floor", "The Face", "The Facts of Life", "The Faculty", "The Fair Cheat", "The Fair Co-Ed", "The Fairylogue and Radio-Plays", "The Faith of the Strong", "The Faker", "The Falcon and the Co-eds", "The Falcon in Danger", "The Falcon in Hollywood", "The Falcon in Mexico", "The Falcon in San Francisco", "The Falcon Out West", "The Falcon Strikes Back", "The Falcon's Adventure", "The Falcon's Alibi", "The Falcon's Brother", "The Fall Guy", "The Fall of Eve", "The Fall of the House of Usher", "The Fall of the Roman Empire", "The Fall", "The Fallbrook Story", "The Fallen Sparrow", "The False Alarm", "The False Faces", "The False Madonna", "The False Road", "The Family Honor", "The Family Jewels", "The Family Man", "The Family Secret", "The Family Stone", "The Family That Preys", "The Family Upstairs", "The Famous Ferguson Case", "The Famous Mrs. Fair", "The Fan", "The Fantastic Four", "The Fantastic Plastic Machine", "The Far Call", "The Far Country", "The Far Frontier", "The Far Horizons", "The Fargo Kid", "The Farm: Angola, USA", "The Farmer in the Dell", "The Farmer Takes a Wife", "The Farmer's Daughter", "The Fascinating Mrs. Francis", "The Fast and the Furious", "The Fast and the Furious: Tokyo Drift", "The Fast Freight", "The Fast Mail", "The Fast Set", "The Fast Worker", "The Fastest Guitar Alive", "The Fastest Gun Alive", "The Fat Man", "The Fat Spy", "The Fatal Glass of Beer", "The Fatal Hour", "The Fatal Mallet", "The Fatal Mistake", "The Fatal Witness", "The Fate of a Flirt", "The Fate of the Dolphin", "The Fate of the Furious", "The Fault in Our Stars", "The Favor", "The Favorite", "The Favourite", "The FBI Story", "The Fear Fighter", "The Fear Woman", "The Fearless Lover", "The Fearless Rider", "The Fearless Vampire Killers", "The Fearmakers", "The Feathered Serpent", "The Female Animal", "The Female Bunch", "The Female", "The Feminine Touch", "The Feud Maker", "The Feud", "The Fiddlin' Buckaroo", "The Fiend Who Walked the West", "The Fiendish Plot of Dr. Fu Manchu", "The Fiercest Heart", "The Fifth Element", "The Fifth Estate", "The Fifth Floor", "The Fifty-Fifty Girl", "The Fight for Freedom", "The Fight for Life", "The Fighter", "The Fightin' Fury", "The Fightin' Redhead", "The Fightin' Terror", "The Fighting 69th", "The Fighting Adventurer", "The Fighting Blade", "The Fighting Boob", "The Fighting Buckaroo", "The Fighting Chance", "The Fighting Cheat", "The Fighting Code", "The Fighting Coward", "The Fighting Demon", "The Fighting Edge", "The Fighting Fool", "The Fighting Frontiersman", "The Fighting Gentleman", "The Fighting Gringo", "The Fighting Guardsman", "The Fighting Guide", "The Fighting Heart", "The Fighting Kentuckian", "The Fighting Lady", "The Fighting Legion", "The Fighting Line", "The Fighting Marshal", "The Fighting O'Flynn", "The Fighting Peacemaker", "The Fighting Pilot", "The Fighting Ranger", "The Fighting Sap", "The Fighting Seabees", "The Fighting Seventh", "The Fighting Sheriff", "The Fighting Smile", "The Fighting Stallion", "The Fighting Streak", "The Fighting Sullivans", "The Fighting Temptations", "The Fighting Vigilantes", "The File on Thelma Jordon", "The Final Close-Up", "The Final Comedown", "The Final Conflict", "The Final Countdown", "The Final Cut", "The Final Days", "The Final Destination", "The Final Hour", "The Final Impulse", "The Final Sanction", "The Finest Hours", "The Finger Points", "The Finish of Bridget McKeen", "The Fire Bride", "The Fire Coward", "The Fire Eater", "The Fire Flingers", "The Fire Patrol", "The Fireball", "The Firefly", "The Fireman", "The Firing Line", "The Firm", "The First $20 Million Is Always the Hardest", "The First Auto", "The First Baby", "The First Deadly Sin", "The First Degree", "The First Hundred Years", "The First Kiss", "The First Legion", "The First Nudie Musical", "The First Power", "The First Purge", "The First Texan", "The First Time", "The First Traveling Saleslady", "The First Wives Club", "The First Year", "The Fish That Saved Pittsburgh", "The Fisher King", "The Fitzgerald Family Christmas", "The Five Dollar Baby", "The Five Heartbeats", "The Five O'Clock Girl", "The Five Pennies", "The Five-Year Engagement", "The Flame and the Arrow", "The Flame Barrier", "The Flame of Life", "The Flame of New Orleans", "The Flame of the Yukon", "The Flame Within", "The Flame", "The Flaming Forest", "The Flaming Forties", "The Flaming Hour", "The Flaming Urge", "The Flamingo Kid", "The Fleet's In", "The Flesh Eaters", "The Flesh Is Weak", "The Flight of Dragons", "The Flight of the Phoenix", "The Flight That Disappeared", "The Flim-Flam Man", "The Flintstones in Viva Rock Vegas", "The Flintstones", "The Flirt and the Bandit", "The Flirt", "The Flirting Widow", "The Flock", "The Floor Above", "The Floor Below", "The Floorwalker", "The Florentine Dagger", "The Florentine", "The Florida Project", "The Florodora Girl", "The Flower of Doom", "The Fluffer", "The Fly II", "The Fly", "The Flying Cat", "The Flying Deuces", "The Flying Dutchman", "The Flying Fleet", "The Flying Fontaines", "The Flying Fool", "The Flying Horseman", "The Flying Irishman", "The Flying Mail", "The Flying Marine", "The Flying Missile", "The Flying Saucer", "The Flying Serpent", "The Flying Sorceress", "The Fog of War", "The Fog", "The Follies Girl", "The Folly of Vanity", "The Food of the Gods", "The Fool", "The Foolish Age", "The Foolish Matrons", "The Foolish Virgin", "The Foot Shooting Party", "The Footlight Ranger", "The Footloose Heiress", "The Forbidden City", "The Forbidden Dance", "The Forbidden Kingdom", "The Forbidden Room", "The Forbidden Street", "The Forbidden Thing", "The Force Beyond", "The Force", "The Foreign Legion", "The Forest Rangers", "The Forest", "The Forfeit", "The Forger", "The Forgotten aka Don't Look in the Basement", "The Forgotten Law", "The Forgotten", "The Formula", "The Forsaken", "The Fortune Cookie", "The Fortune Hunter", "The Fortune", "The Forty-Niners", "The Forward Pass", "The Founder", "The Fountain", "The Fountainhead", "The Four Feathers", "The Four Flusher", "The Four from Nowhere", "The Four Horsemen of the Apocalypse", "The Four Poster", "The Four Seasons", "The Four Skulls of Jonathan Drake", "The Four-Bit Man", "The Fourflusher", "The Four-Footed Ranger", "The Fourteenth Lover", "The Fourth Horseman", "The Fourth Musketeer", "The Fourth War", "The Fox and the Hound", "The Fox", "The Foxes of Harrow", "The Framed Cat", "The Frame-Up", "The Frankenstein Theory", "The Fraudulent Beggar", "The French Connection", "The French Doll", "The French Key", "The French Line", "The Freshman", "The Friendly Ghost", "The Friends of Eddie Coyle", "The Frighteners", "The Frisco Kid", "The Frogmen", "The Front Page", "The Front Runner", "The Front", "The Frontier Phantom", "The Frontier Trail", "The Frontiersmen", "The Frozen Ghost", "The Frozen Ground", "The Frozen North", "The Fugitive Kind", "The Fugitive Sheriff", "The Fugitive", "The Fuller Brush Girl", "The Fuller Brush Man", "The Funeral", "The Funhouse", "The Furies", "The Fury", "The Future", "The Fuzzy Pink Nightgown", "The Gaiety Girl", "The Gal Who Took the West", "The Gallant Blade", "The Gallant Fool", "The Gallant Hours", "The Gallant Legion", "The Galloping Ace", "The Galloping Fish", "The Galloping Kid", "The Gamble", "The Gambler from Natchez", "The Gambler", "The Gamblers", "The Gambling Fool", "The Gambling Terror", "The Game of Their Lives", "The Game Plan", "The Game That Kills", "The Game Warden", "The Game", "The Game's Up", "The Gamma People", "The Gang Buster", "The Gang That Couldn't Shoot Straight", "The Gang's All Here", "The Gangster", "The Garage", "The Garden Murder Case", "The Garden of Allah", "The Garden of Eden", "The Garden of Weeds", "The Garden", "The Garment Jungle", "The Gate Crasher", "The Gate of Heavenly Peace", "The Gate", "The Gateway of the Moon", "The Gaucho", "The Gauntlet", "The Gay Amigo", "The Gay Bride", "The Gay Buckaroo", "The Gay Caballero", "The Gay Cavalier", "The Gay Deceiver", "The Gay Deceivers", "The Gay Deception", "The Gay Desperado", "The Gay Diplomat", "The Gay Divorcee", "The Gay Falcon", "The Gay Intruders", "The Gay Lord Quex", "The Gay Ranchero", "The Gay Senorita", "The Gay Sisters", "The Gay Vagabond", "The Gazebo", "The Geisha Boy", "The Gene Generation", "The Gene Krupa Story", "The General Died at Dawn", "The General", "The General's Daughter", "The Genius Club", "The Gentle Art of Burglary", "The Gentle Cyclone", "The Gentleman from America", "The Gentleman from Louisiana", "The Gentleman from Nowhere", "The Gentleman from Texas", "The Gentleman Misbehaves", "The George Raft Story", "The Getaway", "The Get-Away", "The Ghost and Mr. Chicken", "The Ghost and Mrs. Muir", "The Ghost and the Darkness", "The Ghost and the Guest", "The Ghost Army", "The Ghost Breaker", "The Ghost Breakers", "The Ghost Comes Home", "The Ghost Goes Wild", "The Ghost in the Invisible Bikini", "The Ghost of Frankenstein", "The Ghost of Slumber Mountain", "The Ghost Patrol", "The Ghost Rider", "The Ghost Ship", "The Ghost Talks", "The Ghost That Walks Alone", "The Ghost Walks", "The Giant Claw", "The Giant Gila Monster", "The Giant Spider Invasion", "The Gift of Love", "The Gift", "The Gilded Butterfly", "The Gilded Highway", "The Gilded Lily", "The Gingerbread Man", "The Girl and the Gambler", "The Girl and the Greaser", "The Girl Can't Help It", "The Girl Dodger", "The Girl Downstairs", "The Girl Friend", "The Girl from 10th Avenue", "The Girl from Alaska", "The Girl from Calgary", "The Girl from Chicago", "The Girl from Havana", "The Girl from Jones Beach", "The Girl from Mandalay", "The Girl from Manhattan", "The Girl from Mexico", "The Girl from Missouri", "The Girl from Monterrey", "The Girl from Montmartre", "The Girl from Outside", "The Girl from Petrovka", "The Girl from San Lorenzo", "The Girl from Scotland Yard", "The Girl from Woolworth's", "The Girl Habit", "The Girl He Didn't Buy", "The Girl He Left Behind", "The Girl Hunters", "The Girl I Loved", "The Girl in 419", "The Girl in Black Stockings", "The Girl in His Room", "The Girl in Number 29", "The Girl in the Glass Cage", "The Girl in the Kremlin", "The Girl in the Limousine", "The Girl in the Park", "The Girl in the Red Velvet Swing", "The Girl in the Shack", "The Girl in the Spider's Web", "The Girl in White", "The Girl Most Likely", "The Girl Next Door", "The Girl of Gold", "The Girl of the Golden West", "The Girl of the Limberlost", "The Girl on the Bridge", "The Girl on the Front Page", "The Girl on the Stairs", "The Girl on the Train", "The Girl Problem", "The Girl Rush", "The Girl Said No", "The Girl Who Came Back", "The Girl Who Dared", "The Girl Who Had Everything", "The Girl Who Knew Too Much", "The Girl Who Ran Wild", "The Girl Who Returned", "The Girl Who Stayed at Home", "The Girl Who Wouldn't Work", "The Girl with All the Gifts", "The Girl with No Regrets", "The Girl with the Dragon Tattoo", "The Girl with the Hungry Eyes", "The Girl", "The Girlfriend Experience", "The Girlie Show - Live Down Under", "The Girls and Daddy[1]", "The Girls of Pleasure Island", "The Girl-Woman", "The Giver", "The Gladiator", "The Glass Alibi", "The Glass Bottom Boat", "The Glass Castle", "The Glass House", "The Glass Key", "The Glass Menagerie", "The Glass Shield", "The Glass Slipper", "The Glass Wall", "The Glass Web", "The Glenn Miller Story", "The Glimmer Man", "The Glimpses of the Moon", "The Glorious Fool", "The Glorious Lady", "The Glory Brigade", "The Glory Guys", "The Glory of Clementina", "The Glove", "The Gnome-Mobile", "The Go Getter", "The Goat", "The Goddess of Lost Lake", "The Goddess", "The Godfather Part II", "The Godfather Part III", "The Godfather", "The Godless Girl", "The Godson", "The Go-Getter", "The Gold Cure", "The Gold Diggers", "The Gold Racket", "The Gold Rush", "The Goldbergs", "The Golden Arrow", "The Golden Bed", "The Golden Blade", "The Golden Bowl", "The Golden Calf", "The Golden Chance", "The Golden Child", "The Golden Cocoon", "The Golden Compass", "The Golden Eye", "The Golden Fleecing", "The Golden Gallows", "The Golden Gift", "The Golden Gloves Story", "The Golden Hawk", "The Golden Head", "The Golden Horde", "The Golden Idol", "The Golden Louis", "The Golden Mistress", "The Golden Princess", "The Golden Shower", "The Golden Stallion", "The Golden Strain", "The Golden Voyage of Sinbad", "The Golden West", "The Goldfish", "The Goldwyn Follies", "The Gong Show Movie", "The Good Bad Man", "The Good Catholic", "The Good Dinosaur", "The Good Doctor", "The Good Earth", "The Good Fairy", "The Good Fellows", "The Good German", "The Good Girl", "The Good Guy", "The Good Humor Man", "The Good Lie", "The Good Mother", "The Good Night", "The Good Old Soak", "The Good Provider", "The Good Shepherd", "The Good Son", "The Good, the Bad, and Huckleberry Hound", "The Goodbye Girl", "The Good-Bye Kiss", "The Goods: Live Hard, Sell Hard", "The Goose and the Gander", "The Goose Hangs High", "The Goose Takes a Trolley Ride", "The Goose Woman", "The Gordon Sisters Boxing", "The Gorgeous Hussy", "The Gorilla Man", "The Gorilla", "The Gospel", "The Governor's Lady", "The Gracie Allen Murder Case", "The Graduate", "The Grail", "The Grain of Dust", "The Grand Duchess and the Waiter", "The Grand Parade", "The Grapes of Wrath", "The Grass Is Greener", "The Grasshopper", "The Grateful Dead Movie", "The Grave", "The Gravy Train", "The Gray Horizon", "The Gray Towers Mystery", "The Gray Wolf's Ghost", "The Great Air Robbery", "The Great Alaskan Mystery", "The Great American Broadcast", "The Great American Pastime", "The Great Awakening", "The Great Baltimore Fire", "The Great Buck Howard", "The Great Caruso", "The Great Circus Mystery", "The Great Commandment", "The Great Dan Patch", "The Great Debaters", "The Great Deception", "The Great Diamond Robbery", "The Great Dictator", "The Great Divide", "The Great Escape II: The Untold Story", "The Great Escape", "The Great Flamarion", "The Great Flirtation", "The Great Gabbo", "The Great Gambini", "The Great Garrick", "The Great Gatsby", "The Great Gildersleeve", "The Great Hospital Mystery", "The Great Hotel Murder", "The Great Impersonation", "The Great Impostor", "The Great Jasper", "The Great Jesse James Raid", "The Great Jewel Mystery", "The Great Jewel Robber", "The Great John L.", "The Great K & A Train Robbery", "The Great Lie", "The Great Locomotive Chase", "The Great Love", "The Great Lover", "The Great Man Votes", "The Great Man", "The Great Man's Lady", "The Great McGinty", "The Great Meadow", "The Great Mike", "The Great Missouri Raid", "The Great Mom Swap", "The Great Moment", "The Great Morgan", "The Great Mouse Detective", "The Great Mr. Nobody", "The Great Muppet Caper", "The Great Night", "The Great Northfield Minnesota Raid", "The Great O'Malley", "The Great Outdoors", "The Great Plane Robbery", "The Great Power", "The Great Race", "The Great Raid", "The Great Redeemer", "The Great Romance", "The Great Rupert", "The Great Santini", "The Great Scout & Cathouse Thursday", "The Great Sensation", "The Great Sinner", "The Great Sioux Massacre", "The Great Sioux Uprising", "The Great Smokey Roadblock", "The Great St. Louis Bank Robbery", "The Great Train Robbery", "The Great Victor Herbert", "The Great Victory", "The Great Waldo Pepper", "The Great Wall", "The Great Waltz", "The Great Warming", "The Great White Hope", "The Great White Hype", "The Great White Way", "The Great Ziegfeld", "The Greater Claim", "The Greater Love", "The Greatest Game Ever Played", "The Greatest Movie Ever Sold", "The Greatest Question", "The Greatest Show on Earth", "The Greatest Showman", "The Greatest Story Ever Told", "The Greatest Thing in Life", "The Greatest", "The Greek Tycoon", "The Greeks Had a Word for Them", "The Green Archer", "The Green Berets", "The Green Glove", "The Green Goddess", "The Green Hornet Strikes Again", "The Green Hornet", "The Green Inferno", "The Green Mile", "The Green Pastures", "The Green Promise", "The Green Temptation", "The Green Years", "The Green-Eyed Blonde", "The Green-Eyed Devil", "The Grey Devil", "The Grey Sisterhood", "The Grey Zone", "The Grey", "The Greyhound Limited", "The Grifters", "The Grim Comedian", "The Grim Game", "The Grinch", "The Grinning Granger", "The Grissom Gang", "The Grocery Clerk", "The Groom Wore Spurs", "The Groomsmen", "The Groove Tube", "The Group", "The Grudge 2", "The Grudge 3", "The Grudge", "The Guardian", "The Guardsman", "The Guatemalan Handshake", "The Guest", "The Guilt of Janet Ames", "The Guilt Trip", "The Guilty Generation", "The Guilty One", "The Guilty", "The Gulf Between", "The Gumball Rally", "The Gun in Betty Lou's Handbag", "The Gun Ranger", "The Gun Runners", "The Gun That Won the West", "The Gunfight at Dodge City", "The Gunfighter", "The Gunman", "The Guns of Fort Petticoat", "The Guru", "The Guttersnipe", "The Guy Who Came Back", "The Guys", "The Guyver", "The Gypsy Moths", "The Habit of Happiness", "The Hairy Ape", "The Hairy Bird", "The Half Breed", "The Half-Breed", "The Half-Breed's Way", "The Half-Naked Truth", "The Half-Way Girl", "The Hallelujah Trail", "The Halliday Brand", "The Hamiltons", "The Hammer", "The Hand That Rocks the Cradle", "The Hand", "The Handmaid's Tale", "The Hands of Nara", "The Handsome Brute", "The Hanging Tree", "The Hangman", "The Hangover Part III", "The Hangover", "The Hangover: Part II", "The Hanoi Hilton", "The Hansom Cabman", "The Happening", "The Happiest Millionaire", "The Happy Ending", "The Happy Hooker Goes Hollywood", "The Happy Hooker", "The Happy Road", "The Happy Thieves", "The Happy Time", "The Happy Warrior", "The Happy Years", "The Happytime Murders", "The Hard Corps", "The Hard Man", "The Hard Ride", "The Hard Way", "The Hard-Boiled Canary", "The Harder They Fall", "The Hardys Ride High", "The Harlem Globetrotters", "The Harrad Experiment", "The Harvest of Hate", "The Harvester", "The Harvey Girls", "The Hasty Hare", "The Hasty Heart", "The Hatchet Man", "The Hate U Give", "The Haunted Bedroom", "The Haunted House", "The Haunted Lounge", "The Haunted Mansion", "The Haunted Mine", "The Haunted Palace", "The Haunting in Connecticut 2: Ghosts of Georgia", "The Haunting in Connecticut", "The Haunting of Molly Hartley", "The Haunting", "The Hawaiians", "The Hawk Is Dying", "The Hawk of Powder River", "The Hawk of Wild River", "The Hawk's Nest", "The Hazards of Helen", "The Head Man", "The Headless Horseman", "The Headline Woman", "The Healer", "The Heart Bandit", "The Heart Buster", "The Heart Is a Lonely Hunter", "The Heart of a Follies Girl", "The Heart of Broadway", "The Heart of Humanity", "The Heart of Maryland", "The Heart of New York", "The Heart of Nora Flynn", "The Heart of Steel", "The Heart of the Game", "The Heart of Wetona", "The Heart of Youth", "The Heart Raider", "The Heart Specialist", "The Heartbreak Kid", "The Heat", "The Heat's On", "The Heavenly Body", "The Heckling Hare", "The Heiress", "The Helen Morgan Story", "The Hell Cat", "The Hell with Heroes", "The Hellcats", "The Hellion", "The Hellstrom Chronicle", "The Help", "The Heritage of the Desert", "The Hero", "The Heroes of Desert Storm", "The Heroes of Telemark", "The Heroic Lover", "The Hidden City", "The Hidden Eye", "The Hidden Hand", "The Hidden II", "The Hidden Way", "The Hidden", "The Hiding Place", "The Higgins Family", "The High and the Mighty", "The High Cost of Loving", "The High Flyer", "The High Powered Rifle", "The High Sign", "The Highbinders", "The Higher Law", "The Highest Bid", "The Highest Trump", "The Highwayman", "The Hi-Line", "The Hill Billy", "The Hill", "The Hills Have Eyes 2", "The Hills Have Eyes III", "The Hills Have Eyes", "The Hills of Utah", "The Hi-Lo Country", "The Hindenburg", "The Hindoo Dagger", "The Hired Gun", "The Hired Hand", "The Hitcher", "The Hitch-Hiker", "The Hitchhiker's Guide to the Galaxy", "The Hitler Gang", "The Hitman", "The Hitman's Bodyguard", "The Hoax", "The Hobbit: The Desolation of Smaug", "The Holcroft Covenant", "The Hole in the Wall", "The Hole", "The Holiday", "The Hollywood Knights", "The Hollywood Revue of 1929", "The Hollywood Ten", "The Holy Terror", "The Home Maker", "The Home Town Girl", "The Home Towners", "The Homebreaker", "The Homesman", "The Homesteaders", "The Homestretch", "The Homicide Squad", "The Honey Pot", "The Honeymoon Express", "The Honeymoon Killers", "The Honeymoon Machine", "The Honeymooners", "The Honeymoon's Over", "The Honor of the District Attorney", "The Honor of the Slums", "The Honor of Thieves", "The Honorable Mr. Buggs", "The Hoodlum Saint", "The Hoodlum", "The Hook", "The Hoosier Schoolmaster", "The Hopes of Blind Alley", "The Horizontal Lieutenant", "The Horn Blows at Midnight", "The Horror of Party Beach", "The Horror Show", "The Horror", "The Horse in the Gray Flannel Suit", "The Horse Soldiers", "The Horse Whisperer", "The Horse with the Flying Tail", "The Horsemen", "The Hospital", "The Host", "The Hot Angel", "The Hot Chick", "The Hot Flashes", "The Hot Heiress", "The Hot Rock", "The Hot Spot", "The Hotel New Hampshire", "The Hottentot", "The Hottest State", "The Hottie and the Nottie", "The Hound of Silver Creek", "The Hound of the Baskervilles", "The Hour Before the Dawn", "The Hours and Times", "The Hours", "The House Across the Bay", "The House Across the Street", "The House Bunny", "The House I Live In", "The House in the Middle", "The House of a Thousand Candles", "The House of Fear", "The House of Intrigue", "The House of Mirth", "The House of Rothschild", "The House of the Seven Gables", "The House of the Spirits", "The House of Tomorrow", "The House of Yes", "The House of Youth", "The House on 56th Street", "The House on 92nd Street", "The House on Carroll Street", "The House on Skull Mountain", "The House on Sorority Row", "The House on Telegraph Hill", "The House with a Clock in Its Walls", "The House with Closed Shutters", "The House", "The Housekeeper's Daughter", "The Houston Story", "The Howards of Virginia", "The Howling", "The Hucksters", "The Hudsucker Proxy", "The Human Comedy", "The Human Duplicators", "The Human Jungle", "The Human Side", "The Human Stain", "The Human Tornado", "The Humbling", "The Humming Bird", "The Hunch", "The Hunchback of Notre Dame", "The Hunchback", "The Hundred-Foot Journey", "The Hunger Games", "The Hunger Games: Catching Fire", "The Hunger Games: Mockingjay – Part 1", "The Hunger", "The Hungry Actors", "The Hunt for Red October", "The Hunted Woman", "The Hunted", "The Hunter", "The Hunters", "The Hunting of the President", "The Hunting Party", "The Huntress", "The Huntsman: Winter's War", "The Hurricane Heist", "The Hurricane Horseman", "The Hurricane Kid", "The Hurricane", "The Hurt Locker", "The Hushed Hour", "The Hustler", "The Hypnotic Eye", "The I Don't Care Girl", "The Ice Flood", "The Ice Follies of 1939", "The Ice Harvest", "The Ice Pirates", "The Ice Storm", "The Iceman Cometh", "The Iceman", "The Ides of March", "The Idle Class", "The Idol Dancer", "The Idol", "The Idolmaker", "The Illusionist", "The Illustrated Man", "The Illustrious Prince", "The Imaginarium of Doctor Parnassus", "The Immigrant", "The Immoral Mr. Teas", "The Immortals", "The Imp", "The Impatient Years", "The Imperfect Lady", "The Impossible Mrs. Bellew", "The Impossible Years", "The Impostor", "The Impostors", "The In Crowd", "The Incident", "The Incredible 2-Headed Transplant", "The Incredible Burt Wonderstone", "The Incredible Hulk Returns", "The Incredible Hulk", "The Incredible Journey", "The Incredible Melting Man", "The Incredible Mr. Limpet", "The Incredible Petrified World", "The Incredible Shrinking Man", "The Incredible Shrinking Woman", "The Incredibles", "The Incredibly Strange Creatures Who Stopped Living and Became Mixed-Up Zombies", "The Incredibly True Adventure of Two Girls in Love", "The Indestructible Wife", "The Indian Fighter", "The Indian in the Cupboard", "The Indian Runner", "The Inevitable Defeat of Mister & Pete", "The Infidel", "The Infiltrator", "The Informant!", "The Informant", "The Informer", "The Informers", "The Inkwell", "The In-Laws", "The Inn of the Sixth Happiness", "The Inner Circle", "The Innocent Lie", "The Inside Story", "The Insider", "The Inspector General", "The Inspector", "The Insult", "The Insurgents", "The International", "The Interns", "The Internship", "The Interpreter", "The Interrupted Bathers", "The Interview", "The Intimate Stranger", "The Intruder", "The Intrusion of Isabel", "The Invasion", "The Invention of Lying", "The Invincible Six", "The Invisible Bond", "The Invisible Boy", "The Invisible Informer", "The Invisible Man Returns", "The Invisible Man", "The Invisible Man's Revenge", "The Invisible Menace", "The Invisible Monster", "The Invisible Mouse", "The Invisible Ray", "The Invisible Wall", "The Invisible Woman", "The Invisible", "The Invitation", "The Irish in Us", "The Iron Curtain", "The Iron Giant", "The Iron Glove", "The Iron Horse", "The Iron Major", "The Iron Mask", "The Iron Mistress", "The Iron Petticoat", "The Iron Sheriff", "The Iroquois Trail", "The Island at the Top of the World", "The Island of Dr. Moreau", "The Island of Intrigue", "The Island", "The Isle of Conquest", "The Isle of Hope", "The Isle of Lost Ships", "The Isle of Retribution", "The Italian Barber", "The Italian Job", "The Italian", "The Ivory-Handled Gun", "The Jack of Hearts", "The Jackal", "The Jacket", "The Jackie Robinson Story", "The Jack-Knife Man", "The Jackpot", "The Jade Cup", "The Jade Mask", "The James Brothers of Missouri", "The James Dean Story", "The January Man", "The Jay Bird", "The Jayhawkers!", "The Jazz Age", "The Jazz Cinderella", "The Jazz Girl", "The Jazz Singer", "The Jerk", "The Jerky Boys: The Movie", "The Jesse Owens Story", "The Jet Cage", "The Jetsons Meet the Flintstones", "The Jilt", "The Jimmy Show", "The Joe Louis Story", "The Johnstown Flood", "The Joker Is Wild", "The Jolson Story", "The Jones Family in Hollywood", "The Joneses Have Amateur Theatricals", "The Joneses", "The Journey of Natty Gann", "The Journey", "The Joy Girl", "The Joy Luck Club", "The Joyous Liar", "The Judge Steps Out", "The Judge", "The Judgement Book", "The Judgment of the Storm", "The Juggler", "The Jungle Book 2", "The Jungle Book", "The Jungle Captive", "The Jungle Princess", "The Jungle Trail", "The Jungle", "The Juror", "The Jury of Fate", "The Jury's Secret", "The Kangaroo Kid", "The Kansan", "The Kansas Terrors", "The Karate Kid", "The Karate Kid, Part II", "The Karate Kid, Part III", "The Keep", "The Keeper of the Bees", "The Kennel Murder Case", "The Kentuckian", "The Kentucky Colonel", "The Kentucky Derby", "The Kentucky Fried Movie", "The Kettles in the Ozarks", "The Kettles on Old MacDonald's Farm", "The Key", "The Keyhole", "The Keys of the Kingdom", "The Kibitzer", "The Kickback", "The Kick-Off", "The Kid and the Cowboy", "The Kid Brother", "The Kid from Amarillo", "The Kid from Broken Gun", "The Kid from Brooklyn", "The Kid from Cleveland", "The Kid from Kansas", "The Kid from Kokomo", "The Kid from Left Field", "The Kid from Spain", "The Kid from Texas", "The Kid Ranger", "The Kid Rides Again", "The Kid Sister", "The Kid Stays in the Picture", "The Kid", "The Kidnapping of the President", "The Kids Are All Right", "The Kid's Clever", "The Kid's Pal", "The Killer Elite", "The Killer Inside Me", "The Killer Is Loose", "The Killer Shrews", "The Killer That Stalked New York", "The Killers", "The Killing of a Chinese Bookie", "The Killing of a Sacred Deer", "The Killing of Sister George", "The Killing", "The Kill-Off", "The King and Four Queens", "The King and I", "The King and the Chorus Girl", "The King Murder", "The King of Comedy", "The King of Kings", "The King of Kong: A Fistful of Quarters", "The King of Marvin Gardens", "The King of the Turf", "The King of the Wild Horses", "The King on Main Street", "The King Steps Out", "The Kingdom of Youth", "The Kingdom", "The Kings of Summer", "The King's Pirate", "The King's Thief", "The King's Vacation", "The Kiss Barrier", "The Kiss Before the Mirror", "The Kiss", "The Kissing Bandit", "The Kite Runner", "The Klansman", "The Kleptomaniac", "The Knickerbocker Buckaroo", "The Knockout Kid", "The Knockout", "The Kremlin Letter", "The L.A. Riot Spectacular", "The Lad and the Lion", "The Ladder Jinx", "The Ladies Man", "The Ladies of the House", "The Lady and the Monster", "The Lady Confesses", "The Lady Consents", "The Lady Escapes", "The Lady Eve", "The Lady Fights Back", "The Lady from Boston", "The Lady from Cheyenne", "The Lady from Hell", "The Lady from Nowhere", "The Lady from Shanghai", "The Lady from Texas", "The Lady Gambles", "The Lady Has Plans", "The Lady in Ermine", "The Lady in Question", "The Lady in Scarlet", "The Lady in the Car with Glasses and a Gun", "The Lady in the Morgue", "The Lady Is Willing", "The Lady Lies", "The Lady Objects", "The Lady of Red Butte", "The Lady of Scandal", "The Lady of the Dug-Out", "The Lady of the Harem", "The Lady Pays Off", "The Lady Refuses", "The Lady Says No", "The Lady Takes a Flyer", "The Lady Takes a Sailor", "The Lady Vanishes", "The Lady Wants Mink", "The Lady Who Dared", "The Lady Who Lied", "The Lady", "The Ladykillers", "The Lady's from Kentucky", "The Lake House", "The Lamb", "The Land Before Time II: The Great Valley Adventure", "The Land Before Time III: The Time of the Great Giving", "The Land Before Time IV: Journey Through the Mists", "The Land Before Time V: The Mysterious Island", "The Land Before Time", "The Land Beyond the Sunset", "The Land Has Eyes", "The Land of the Long Shadows", "The Land That Time Forgot", "The Land Unknown", "The Landlord", "The Lane That Had No Turning", "The Laramie Kid", "The Laramie Project", "The Laramie Trail", "The Lariat Kid", "The Las Vegas Story", "The Lash", "The Last 5 Years", "The Last Airbender", "The Last Alarm", "The Last American Hero", "The Last American Virgin", "The Last Angel of History", "The Last Angry Man", "The Last Bandit", "The Last Blitzkrieg", "The Last Bomb", "The Last Boy Scout", "The Last Castle", "The Last Chance", "The Last Chase", "The Last Command", "The Last Crooked Mile", "The Last Days of Disco", "The Last Days of Frankie the Fly", "The Last Days of Pompeii", "The Last Days", "The Last Detail", "The Last Edition", "The Last Exorcism Part II", "The Last Exorcism", "The Last Express", "The Last Flight of Noah's Ark", "The Last Flight", "The Last Frontier", "The Last Gangster", "The Last Gentleman", "The Last Hard Men", "The Last Horror Film", "The Last Horseman", "The Last Hour", "The Last House on the Left", "The Last Hunt", "The Last Hurrah", "The Last Kiss", "The Last Man on Earth", "The Last Married Couple in America", "The Last Mile", "The Last Mimzy", "The Last Moment", "The Last Movie Star", "The Last Movie", "The Last Musketeer", "The Last of His People", "The Last of Mrs. Cheyney", "The Last of Robin Hood", "The Last of Sheila", "The Last of the Duanes", "The Last of the Fast Guns", "The Last of the Finest", "The Last of the Mohicans", "The Last of the Secret Agents?", "The Last Outlaw", "The Last Outpost", "The Last Picture Show", "The Last Posse", "The Last Producer", "The Last Remake of Beau Geste", "The Last Ride", "The Last Round-Up", "The Last Run", "The Last Samurai", "The Last Seduction", "The Last Shot", "The Last Sin Eater", "The Last Song", "The Last Stagecoach West", "The Last Stand", "The Last Starfighter", "The Last Sunset", "The Last Supper", "The Last Temptation of Christ", "The Last Time I Committed Suicide", "The Last Time I Saw Archie", "The Last Time I Saw Paris", "The Last Trail", "The Last Train from Madrid", "The Last Tycoon", "The Last Unicorn", "The Last Voyage", "The Last Wagon", "The Last Waltz", "The Last Warning", "The Last Warrior", "The Last Winter", "The Last Word", "The Late George Apley", "The Late Shift", "The Late Show", "The Latest from Paris", "The Lathe of Heaven", "The Laughing Policeman", "The Lavender Bath Lady", "The Law and Jake Wade", "The Law and the Lady", "The Law and the Woman", "The Law Comes to Gunsight", "The Law Forbids", "The Law in Her Hands", "The Law of Men", "The Law of the Range", "The Law of the Sea", "The Law Rides Again", "The Law vs. Billy the Kid", "The Law West of Tombstone", "The Lawbreakers", "The Lawful Cheater", "The Lawless Breed", "The Lawless Eighties", "The Lawless Frontier", "The Lawless Legion", "The Lawless Nineties", "The Lawless Rider", "The Lawless Woman", "The Lawless", "The Lawnmower Man 2: Beyond Cyberspace", "The Lawnmower Man", "The Law's Lash", "The Lawyer's Secret", "The Lazarus Effect", "The League of Extraordinary Gentlemen", "The League of Frightened Men", "The League of Gentlemen's Apocalypse", "The League of the Future", "The Learning Tree", "The Leather Burners", "The Leather Saint", "The Leathernecks Have Landed", "The Leavenworth Case", "The Leech Woman", "The Left Hand of God", "The Left Handed Gun", "The Legacy", "The Legend of Alfred Packer", "The Legend of Bagger Vance", "The Legend of Billie Jean", "The Legend of Boggy Creek", "The Legend of Hercules", "The Legend of Jimi Lazer", "The Legend of Lylah Clare", "The Legend of Nigger Charley aka Legend of Black Charley", "The Legend of Simon Conjurer", "The Legend of Tarzan", "The Legend of the Lone Ranger", "The Legend of the White Horse", "The Legend of Tom Dooley", "The Legend of Zorro", "The Legion of Death", "The Legion of Missing Men", "The Legion of the Condemned", "The Lego Batman Movie", "The Lego Ninjago Movie", "The Lemon Drop Kid", "The Lemon Sisters", "The Leopard Man", "The Leopard Woman", "The Leopardess", "The Letter", "The Liberation of L.B. Jones", "The Lieutenant Wore Skirts", "The Life and Death of Peter Sellers", "The Life and Times of Grizzly Adams", "The Life and Times of Hank Greenberg", "The Life and Times of Judge Roy Bean", "The Life and Times of Rosie the Riveter", "The Life Aquatic with Steve Zissou", "The Life Before Her Eyes", "The Life Line", "The Life of a Fireman", "The Life of David Gale", "The Life of Émile Zola", "The Life of General Villa", "The Life of Jimmy Dolan", "The Life of Napoleon", "The Life of Riley", "The Life of the Party", "The Life of Vergie Winters", "The Lifeguard", "The Light at the Edge of the World", "The Light Between Oceans", "The Light in the Dark", "The Light in the Forest", "The Light of the Western Stars", "The Light of Victory", "The Light of Western Stars", "The Light That Failed", "The Light Touch", "The Light", "The Lighthouse by the Sea", "The Lightkeepers", "The Lightning Rider", "The Lights of New York", "The Lily", "The Limey", "The Limited Mail", "The Limits of Control", "The Lincoln Conspiracy", "The Lincoln Highwayman", "The Lincoln Lawyer", "The Lineup", "The Line-Up", "The Linguini Incident", "The Lion and the Horse", "The Lion and the Lamb", "The Lion and the Mouse", "The Lion Hunters", "The Lion King II: Simba's Pride", "The Lion King", "The Lion Man", "The Lion", "The Lion's Den", "The List of Adrian Messenger", "The List", "The Little Accident", "The Little Adventuress", "The Little American", "The Little Buckaroo", "The Little Colonel", "The Little Diplomat", "The Little Drummer Girl", "The Little Firebrand", "The Little Fool", "The Little Foxes", "The Little French Girl", "The Little Giant", "The Little Girl Next Door", "The Little Girl Who Lives Down the Lane", "The Little Hours", "The Little Hut", "The Little Irish Girl", "The Little Lady Next Door", "The Little Mermaid", "The Little Minister", "The Little Orphan", "The Little Princess", "The Little Rascals", "The Little Red Schoolhouse", "The Little Riders", "The Little Savage", "The Little Shepherd of Kingdom Come", "The Little Shop of Horrors", "The Little Snob", "The Little Train Robbery", "The Little Vampire", "The Little Whirlwind", "The Little White Savage", "The Little Wild Girl", "The Little Wildcat", "The Little Yellow House", "The Littlest Rebel", "The Live Wire", "The Lives of a Bengal Lancer", "The Livid Flame", "The Living Desert", "The Living Ghost", "The Living Idol", "The Living Sea", "The Lizzie McGuire Movie", "The Loaded Door", "The Local Bad Man", "The Locked Door", "The Locket", "The Lodge in the Wilderness", "The Lodger", "The Lodgers", "The Loft", "The Lone Chance", "The Lone Gun", "The Lone Hand Texan", "The Lone Hand", "The Lone Ranger and the Lost City of Gold", "The Lone Ranger", "The Lone Star Ranger", "The Lone Star Trail", "The Lone Wolf and His Lady", "The Lone Wolf in London'", "The Lone Wolf in Mexico", "The Lone Wolf in Paris", "The Lone Wolf Keeps a Date", "The Lone Wolf Meets a Lady", "The Lone Wolf Returns", "The Lone Wolf Spy Hunt", "The Lone Wolf", "The Lone Wolf's Daughter", "The Loneliest Planet", "The Lonely Guy", "The Lonely Lady", "The Lonely Man", "The Lonely Road", "The Lonely Trail", "The Lonesome Mouse", "The Long Arm of Mannister", "The Long Chance", "The Long Goodbye", "The Long Gray Line", "The Long Kiss Goodnight", "The Long Lane's Turning", "The Long Long Trail", "The Long Night", "The Long Riders", "The Long Ships", "The Long Voyage Home", "The Long Wait", "The Long Walk Home", "The Long Weekend", "The Long, Hot Summer", "The Long, Long Trailer", "The Longest Day", "The Longest Ride", "The Longest Yard", "The Longhorn", "The Longshots", "The Lookout Girl", "The Lookout", "The Looney Looney Looney Bugs Bunny Movie", "The Looters", "The Lord of the Rings", "The Lord of the Rings: The Fellowship of the Ring", "The Lord of the Rings: The Return of the King", "The Lord of the Rings: The Two Towers", "The Lords of Discipline", "The Lords of Flatbush", "The Lords of Salem", "The Losers", "The Lost Battalion", "The Lost Boys", "The Lost Chord", "The Lost City of Z", "The Lost City", "The Lost Express", "The Lost Lie", "The Lost Man", "The Lost Medallion: The Adventures of Billy Stone", "The Lost Missile", "The Lost Moment", "The Lost Patrol", "The Lost Planet", "The Lost Princess", "The Lost Sermon", "The Lost Skeleton of Cadavra", "The Lost Squadron", "The Lost Trail", "The Lost Tribe", "The Lost Volcano", "The Lost Weekend", "The Lost World", "The Lost World: Jurassic Park", "The Lost Zeppelin", "The Lottery Bride", "The Lottery Man", "The Lotus Eater", "The Love Auction", "The Love Bandit", "The Love Brand", "The Love Bug", "The Love Burglar", "The Love Captive", "The Love Charm", "The Love Cheat", "The Love Doctor", "The Love Flower", "The Love Gamble", "The Love Gambler", "The Love Girl", "The Love God?", "The Love Guru", "The Love Hermit", "The Love Hour", "The Love Letter", "The Love Light", "The Love Machine", "The Love Master", "The Love of Sunya", "The Love of the Pasha's Son: A Turkish Romance", "The Love Parade", "The Love Piker", "The Love Pirate", "The Love Racket", "The Love That Dares", "The Love Thief", "The Love Toy", "The Love Trader", "The Love Trap", "The Loved One", "The Lovely Bones", "The Lover of Camille", "The Lover's Gift", "The Lover's Ruse", "The Lovers", "The Loves of Carmen", "The Loves of Edgar Allan Poe", "The Loves of Letty", "The Luck o' the Foolish", "The Luck of the Irish", "The Luckiest Girl in the World", "The Lucky Devil", "The Lucky Dog", "The Lucky Horseshoe", "The Lucky Lady", "The Lucky One", "The Lucky Ones", "The Lucky Stiff", "The Lucky Texan", "The Lullaby", "The Lure of Ambition", "The Lure of the Gown", "The Lure of the Sawdust", "The Lure of the Wild", "The Lure of Youth", "The Lurking Fear", "The Lusty Men", "The Lying Truth", "The Mack", "The Mackintosh Man", "The Macomber Affair", "The Mad Doctor of Market Street", "The Mad Doctor", "The Mad Empress", "The Mad Game", "The Mad Genius", "The Mad Ghoul", "The Mad Magician", "The Mad Marriage", "The Mad Martindales", "The Mad Miner", "The Mad Miss Manton", "The Mad Monster", "The Mad Whirl", "The Maddening", "The Madonna's Secret", "The Madwoman of Chaillot", "The Magic Carpet", "The Magic Cloak of Oz", "The Magic Cup", "The Magic Face", "The Magic Flame", "The Magic of Belle Isle", "The Magic of Lassie", "The Magician of Lublin", "The Magician", "The Magnetic Monster", "The Magnificent Ambersons", "The Magnificent Dope", "The Magnificent Flirt", "The Magnificent Fraud", "The Magnificent Lie", "The Magnificent Matador", "The Magnificent Rogue", "The Magnificent Seven Ride", "The Magnificent Seven", "The Magnificent Yankee", "The Magus", "The Maiden Heist", "The Mailman", "The Main Attraction", "The Main Event", "The Main Street Kid", "The Majestic", "The Major and the Minor", "The Making of Me", "The Making of O'Malley", "The Male Animal", "The Maltese Falcon", "The Mambo Kings", "The Man and the Moment", "The Man Behind the Gun", "The Man Beneath", "The Man Between", "The Man Called Back", "The Man Called Flintstone", "The Man from Beyond", "The Man from Bitter Ridge", "The Man from Blankley's", "The Man from Brodney's", "The Man from Button Willow", "The Man from Cairo", "The Man from Colorado", "The Man from Dakota", "The Man from Down Under", "The Man from Downing Street", "The Man from Elysian Fields", "The Man from Headquarters", "The Man from Home", "The Man from Laramie", "The Man from Monterey", "The Man from Music Mountain", "The Man from Oklahoma", "The Man from Planet X", "The Man from Red Gulch", "The Man from Snowy River II", "The Man from Texas", "The Man from the Alamo", "The Man from the Diner's Club", "The Man from the Rio Grande", "The Man from the West", "The Man from Thunder River", "The Man from Utah", "The Man from Wyoming", "The Man from Yesterday", "The Man Hunter", "The Man I Love", "The Man I Married", "The Man I Marry", "The Man in Blue", "The Man in Half Moon Street", "The Man in Possession", "The Man in the Glass Booth", "The Man in the Gray Flannel Suit", "The Man in the Iron Mask", "The Man in the Moon", "The Man in the Moonlight", "The Man in the Net", "The Man in the Saddle", "The Man in the Sombrero", "The Man in the Trunk", "The Man Is Armed", "The Man Life Passed By", "The Man Next Door", "The Man on the Box", "The Man on the Eiffel Tower", "The Man Trailer", "The Man Unconquerable", "The Man Upstairs", "The Man Who Broke the Bank at Monte Carlo", "The Man Who Came Back", "The Man Who Came to Dinner", "The Man Who Captured Eichmann", "The Man Who Cheated Himself", "The Man Who Cried Wolf", "The Man Who Dared", "The Man Who Died Twice", "The Man Who Fights Alone", "The Man Who Found Himself", "The Man Who Had Everything", "The Man Who Invented Christmas", "The Man Who Knew Too Little", "The Man Who Knew Too Much", "The Man Who Laughs", "The Man Who Lived Twice", "The Man Who Lost Himself", "The Man Who Loved Women", "The Man Who Married His Own Wife", "The Man Who Never Was", "The Man Who Paid", "The Man Who Played God", "The Man Who Played Square", "The Man Who Reclaimed His Head", "The Man Who Returned to Life", "The Man Who Saw Tomorrow", "The Man Who Shot Liberty Valance", "The Man Who Stayed at Home", "The Man Who Turned to Stone", "The Man Who Turned White", "The Man Who Understood Women", "The Man Who Walked Alone", "The Man Who Wasn't There", "The Man Who Woke Up", "The Man Who Won", "The Man Who Would Be King", "The Man Who Would Not Die", "The Man Who Wouldn't Talk", "The Man with a Cloak", "The Man with Bogart's Face", "The Man with My Face", "The Man with One Red Shoe", "The Man with the Golden Arm", "The Man with the Iron Fists", "The Man with the Punch", "The Man with Two Brains", "The Man with Two Faces", "The Man Without a Conscience", "The Man Without a Country", "The Man Without a Face", "The Man Without a Heart", "The Man", "The Manchurian Candidate", "The Mandarin Mystery", "The Mangler", "The Manhattan Project", "The Maniac Cook", "The Manicure Girl", "The Manitou", "The Mansion of Aching Hearts", "The Manster", "The Mantrap", "The Many Adventures of Winnie the Pooh", "The Marathon", "The Marauders", "The Marine 3: Homefront", "The Marine", "The Marines Are Coming", "The Marines are Here", "The Marines Fly High", "The Mark of the Renegade", "The Mark of the Whistler", "The Mark of Zorro", "The Mark", "The Market of Souls", "The Marksman", "The Marriage Cheat", "The Marriage Circle", "The Marriage Maker", "The Marriage Market", "The Marriage of a Young Stockbroker", "The Marriage Price", "The Marriage Ring", "The Marriage Whirl", "The Marriage-Go-Round", "The Married Flapper", "The Marrying Kind", "The Marrying Man", "The Marshal of Mesa City", "The Marshal's Daughter", "The Martian", "The Martyred Presidents", "The Mask of Diijon", "The Mask of Dimitrios", "The Mask of Fu Manchu", "The Mask of Lopez", "The Mask of Zorro", "The Mask", "The Masked Bride", "The Masked Dancer", "The Masked Marvel", "The Masked Woman", "The Masks of the Devil", "The Masque of the Red Death", "The Masquerade Bandit", "The Masquerader", "The Master Gunfighter", "The Master Key", "The Master Mind", "The Master of Disguise", "The Master Race", "The Master", "The Matador", "The Match King", "The Matchmaker", "The Mate of the Sally Ann", "The Matinee Idol", "The Mating Call", "The Mating Game", "The Mating Habits of the Earthbound Human", "The Mating of Millie", "The Mating Season", "The Matrimonial Bed", "The Matrix Reloaded", "The Matrix Revolutions", "The Matrix", "The Maverick Queen", "The Maverick", "The Mayor of 44th Street", "The Mayor of Filbert", "The Mayor of Hell", "The Maze Runner", "The Maze", "The McConnell Story", "The McGuerins from Brooklyn", "The Mean Season", "The Meanest Gal in Town", "The Meanest Man in the World", "The Measure of a Man", "The Mechanic", "The Medallion", "The Meddler", "The Medicine Bottle", "The Medicine Man", "The Medico of Painted Springs", "The Medusa Touch", "The Meg", "The Melody Lingers On", "The Melody Man", "The Member of the Wedding", "The Memory of Justice", "The Men in Her Life", "The Men of Zanzibar", "The Men Who Lost China", "The Men Who Stare at Goats", "The Men", "The Menace", "The Men's Club", "The Mephisto Waltz", "The Merchant of Venice", "The Merry Cavalier", "The Merry Frinks", "The Merry Gentleman", "The Merry Monahans", "The Merry Widow", "The Merry-Go-Round", "The Messenger", "The Messenger: The Story of Joan of Arc", "The Messengers", "The Meteor Man", "The Mexican Spitfire's Baby", "The Mexican", "The Miami Story", "The Michigan Kid", "The Microbe", "The Midas Touch", "The Midnight Alarm", "The Midnight Express", "The Midnight Flyer", "The Midnight Girl", "The Midnight Guest", "The Midnight Kiss", "The Midnight Lady", "The Midnight Limited", "The Midnight Man", "The Midnight Meat Train", "The Midnight Message", "The Midnight Patrol", "The Midnight Snack", "The Midnight Special", "The Midnight Story", "The Midnight Sun", "The Midnight Taxi", "The Midnight Watch", "The Midshipman", "The Mighty Barnum", "The Mighty Ducks", "The Mighty Kong", "The Mighty McGurk", "The Mighty Quinn", "The Mighty Treve", "The Mighty", "The Milagro Beanfield War", "The Milkman", "The Milky Waif", "The Milky Way", "The Millerson Case", "The Million Dollar Cat", "The Million Dollar Collar", "The Million Dollar Duck", "The Million Dollar Handicap", "The Million Dollar Hotel", "The Millionaire Cowboy", "The Millionaire Kid", "The Millionaire Pirate", "The Millionaire", "The Mind Reader", "The Mind-the-Paint Girl", "The Mine with the Iron Door", "The Mini-Skirt Mob", "The Miniver Story", "The Mints of Hell", "The Miracle Baby", "The Miracle Makers", "The Miracle Man", "The Miracle of Life", "The Miracle of Love", "The Miracle of Morgan's Creek", "The Miracle of Our Lady of Fatima", "The Miracle of the Bells", "The Miracle of the Hills", "The Miracle Season", "The Miracle Woman", "The Miracle Worker", "The Miracle", "The Mirage", "The Mirror Crack'd", "The Mirror Has Two Faces", "The Mirror", "The Misadventures of Merlin Jones", "The Misbehavers", "The Miseducation of Cameron Post", "The Misfits", "The Misleading Widow", "The Missiles of October", "The Missing Corpse", "The Missing Guest", "The Missing Juror", "The Missing Lady", "The Missing Link", "The Missing Links", "The Missing Mouse", "The Missing", "The Mission", "The Mississippi Gambler", "The Missouri Breaks", "The Missouri Traveler", "The Missourians", "The Mist", "The Mistress of Shenstone", "The Mob", "The Mod Squad", "The Model and the Marriage Broker", "The Moderns", "The Mole People", "The Molly Maguires", "The Mollycoddle", "The Money Corral", "The Money Jungle", "The Money Pit", "The Money Trap", "The Monitors", "The Monkey Bicyclist", "The Monkey Hustle", "The Monkey's Paw", "The Monkey's Uncle", "The Monolith Monsters", "The Monster and the Girl", "The Monster Club", "The Monster Maker", "The Monster of Phantom Lake", "The Monster Squad", "The Monster That Challenged the World", "The Monster Walks", "The Monster", "The Montana Kid", "The Monuments Men", "The Moon and Sixpence", "The Moon Is Blue", "The Moon Is Down", "The Moonlighter", "The Moon's Our Home", "The Moonshine Trail", "The Moonshine War", "The Moonshiners", "The Moon-Spinners", "The Moral Deadline", "The Moral Sinner", "The More the Merrier", "The Morning After", "The Mortal Instruments: City of Bones", "The Mortal Storm", "The Mosquito Coast", "The Most Dangerous Game", "The Most Precious Thing in Life", "The Mostly Unfabulous Social Life of Ethan Green", "The Mother of Tears", "The Mothman Prophecies", "The Motorcycle Diaries", "The Mountain Between Us", "The Mountain Men", "The Mountain Road", "The Mountain", "The Mounted Stranger", "The Mouse and His Child", "The Mouse Comes to Dinner", "The Mouthpiece", "The Movie Orgy", "The Movie Trail", "The Mudlark", "The Mugger", "The Mule", "The Mummy Returns", "The Mummy", "The Mummy: Tomb of the Dragon Emperor", "The Mummy's Curse", "The Mummy's Ghost", "The Mummy's Hand", "The Mummy's Tomb", "The Muppet Christmas Carol", "The Muppet Movie", "The Muppets Take Manhattan", "The Muppets", "The Murder Man", "The Murder of Dr. Harrigan", "The Murder of Mary Phagan", "The Muse", "The Music Box Kid", "The Music Goes 'Round", "The Music Man", "The Music Master", "The Music Never Stopped", "The Music of Chance", "The Musketeer", "The Musketeers of Pig Alley", "The Mutineers", "The Mysterious Avenger", "The Mysterious Desperado", "The Mysterious Doctor", "The Mysterious Dr. Fu Manchu", "The Mysterious Island", "The Mysterious Lady", "The Mysterious Miss X", "The Mysterious Monsters", "The Mysterious Mr. M", "The Mysterious Mr. Valentine", "The Mysterious Mr. Wong", "The Mysterious Rider", "The Mysterious Stranger", "The Mysterious Witness", "The Mystery Club", "The Mystery Man", "The Mystery of Edwin Drood", "The Mystery of Marie Roget", "The Mystery of Mr. Wong", "The Mystery of Mr. X", "The Mystery of the Hindu Image", "The Mystic Circle Murders", "The Mystic Hour", "The Mystic", "The Myth of Fingerprints", "The Naked and the Dead", "The Naked City", "The Naked Dawn", "The Naked Earth", "The Naked Edge", "The Naked Face", "The Naked Gun 2½: The Smell of Fear", "The Naked Gun: From the Files of Police Squad!", "The Naked Jungle", "The Naked Kiss", "The Naked Maja", "The Naked Man", "The Naked Prey", "The Naked Spur", "The Naked Street", "The Nanny Diaries", "The Narrow Corner", "The Narrow Margin", "The Narrow Street", "The National Barn Dance", "The Nativity Story", "The Natural", "The Nature of the Beast", "The Naughty Duchess", "The Naughty Flirt", "The Naughty Nineties", "The Navajo Trail", "The Navigator", "The Navy Aviator", "The Navy Comes Through", "The Navy vs. the Night Monsters", "The Navy Way", "The Nazi Plan", "The Neanderthal Man", "The Near Lady", "The Nebraskan", "The Necessary Evil", "The Ne'er Do-Well", "The Negotiator", "The Negro Soldier", "The Neon Demon", "The Neptune Factor", "The Nervous Wreck", "The Nesting", "The Net", "The Nevada Buckaroo", "The Nevadan", "The NeverEnding Story II: The Next Chapter", "The NeverEnding Story III: Escape from Fantasia", "The NeverEnding Story", "The New Adventures of Pippi Longstocking", "The New Centurions", "The New Champion", "The New Commandment", "The New Frontier", "The New Guy", "The New Interns", "The New Janitor", "The New Klondike", "The New Moon", "The New School Teacher", "The New Spirit", "The New Superintendent", "The New Teacher", "The New World", "The New York Hat", "The Newer Way", "The News Parade of the Year 1942", "The Newsboy", "The Newton Boys", "The Next Best Thing", "The Next Corner", "The Next Karate Kid", "The Next Man", "The Next Three Days", "The Next Voice You Hear...", "The Nice Guys", "The Nickel Ride", "The Nifty Nineties", "The Night and the Moment", "The Night Angel", "The Night Before Christmas", "The Night Before the Divorce", "The Night Before", "The Night Bird", "The Night Club Lady", "The Night Club", "The Night Cry", "The Night Flier", "The Night God Screamed", "The Night Hawk", "The Night Holds Terror", "The Night Hunter", "The Night Is Young", "The Night Listener", "The Night Mayor", "The Night Message", "The Night of January 16th", "The Night of June 13", "The Night of Nights", "The Night of the Following Day", "The Night of the Grizzly", "The Night of the Hunter", "The Night of the Iguana", "The Night of the White Pants", "The Night Owl", "The Night Patrol", "The Night Rider", "The Night Riders", "The Night Runner", "The Night the Lights Went Out in Georgia", "The Night the World Exploded", "The Night They Raided Minsky's", "The Night We Called it a Day", "The Nightmare Before Christmas", "The Nihilists", "The Nine Lives of Fritz the Cat", "The Ninety and Nine", "The Ninth Configuration", "The Ninth Gate", "The Ninth Guest", "The Nitwits", "The No Mercy Man", "The Noah", "The No-Gun Man", "The Nona Tapes", "The Non-Stop Flight", "The Non-Stop Kid", "The Noose Hangs High", "The Noose", "The Norseman", "The North Avenue Irregulars", "The North Star", "The Notebook", "The Notorious Landlady", "The Notorious Lone Wolf", "The Notorious Mr. Monks", "The Notorious Sophie Lang", "The Nth Commandment", "The Nude Bomb", "The Nuisance", "The Number 23", "The Nun and the Sergeant", "The Nun", "The Nun's Story", "The Nurse from Brooklyn", "The Nurse's Secret", "The Nut Farm", "The Nut Job 2: Nutty by Nature", "The Nut Job", "The Nut", "The Nutcracker and the Four Realms", "The Nutcracker Prince", "The Nutty Professor", "The Oakdale Affair", "The Oath", "The Object of Beauty", "The Object of My Affection", "The Octagon", "The Odd Couple II", "The Odd Couple", "The Odd Life of Timothy Green", "The Odyssey", "The Offence", "The Offenders", "The Office Wife", "The Oh in Ohio", "The Oklahoma Kid", "The Oklahoma Woman", "The Oklahoman", "The Old Barn Dance", "The Old Bookkeeper", "The Old Code", "The Old Corral", "The Old Dark House", "The Old Fashioned Way", "The Old Frontier", "The Old Grey Hare", "The Old Homestead", "The Old Maid Having Her Picture Taken", "The Old Maid", "The Old Man & the Gun", "The Old Man and the Sea", "The Old Man Who Cried Wolf", "The Old Monk's Tale", "The Old Soldier's Story", "The Old Swimmin' Hole", "The Old Texas Trail", "The Old West", "The Old Wyoming Trail", "The Omega Code", "The Omega Man", "The Omen", "The One and Only", "The One and Only, Genuine, Original Family Band", "The One I Love", "The One Percent", "The One That Got Away", "The One Way Trail", "The One Woman Idea", "The One", "The Onion Field", "The Only Game in Town", "The Only Living Boy in New York", "The Only Son", "The Only Thing", "The Only Woman", "The Oogieloves in the Big Balloon Adventure", "The Open Door", "The Open Switch", "The Opening of Misty Beethoven", "The Opportunists", "The Opposite of Sex", "The Opposite Sex", "The Oranges", "The Ordeal of Rosetta", "The Ordeal", "The Order", "The Oregon Trail", "The Original Kings of Comedy", "The Oscar", "The Osterman Weekend", "The Other Boleyn Girl", "The Other Guys", "The Other Half", "The Other Kind of Love", "The Other Love", "The Other Side of Midnight", "The Other Side of the Door", "The Other Side of the Mountain", "The Other Sister", "The Other Tomorrow", "The Other Woman", "The Other Woman's Story", "The Other", "The Others", "The Outcast", "The Outcasts of Poker Flat", "The Outcasts", "The Outer Gate", "The Outfit", "The Outlaw Express", "The Outlaw Josey Wales", "The Outlaw Stallion", "The Outlaw", "The Outlaw's Daughter", "The Out-of-Towners", "The Outrage", "The Outriders", "The Outsider", "The Outsiders", "The Overland Express", "The Overland Limited", "The Owl and the Pussycat", "The Ox-Bow Incident", "The Pace That Kills", "The Pace That Thrills", "The Pacifier", "The Package", "The Pact", "The Pad and How to Use It", "The Pagan God", "The Pagan Lady", "The Pagan", "The Pagemaster", "The Painted Angel", "The Painted Desert", "The Painted Flapper", "The Painted Hills", "The Painted Lady", "The Painted Stallion", "The Painted Trail", "The Painted Veil", "The Painted Woman", "The Painted World", "The Pajama Game", "The Palace of Pleasure", "The Paleface", "The Pallbearer", "The Palm Beach Girl", "The Palm Beach Story", "The Palomino", "The Panic in Needle Park", "The Paper Chase", "The Paper", "The Paperboy", "The Paradine Case", "The Parallax View", "The Parasite", "The Pardon", "The Parent Trap III", "The Parent Trap", "The Parisian Tigress", "The Parson and the Outlaw", "The Part Time Wife", "The Party Crashers", "The Party", "The Party's Over", "The Pasha's Daughter", "The Passing of Wolf MacLean", "The Passion of Ayn Rand", "The Passion of Darkly Noon", "The Passion of the Christ", "The Passionate Plumber", "The Passionate Quest", "The Past of Mary Holmes", "The Patchwork Girl of Oz", "The Patent Leather Kid", "The Patent Leather Pug", "The Path She Chose", "The Pathfinder", "The Patient in Room 18", "The Patriot", "The Patsy", "The Pawnbroker", "The Pawnshop", "The Pay Off", "The Payoff", "The Pay-Off", "The Peace of Roaring River", "The Peacemaker", "The Peacock Fan", "The Peanuts Movie", "The Pearl of Death", "The Pebble and the Penguin", "The Pelican Brief", "The Pell Street Mystery", "The Penalty", "The Penguin Pool Murder", "The Pentagon Wars", "The People Against O'Hara", "The People Next Door", "The People That Time Forgot", "The People Under the Stairs", "The People vs. Dr. Kildare", "The People vs. Larry Flynt", "The People vs. Nancy Preston", "The People's Enemy", "The Perez Family", "The Perfect Clown", "The Perfect Clue", "The Perfect Crime", "The Perfect Flapper", "The Perfect Furlough", "The Perfect Game", "The Perfect Gentleman", "The Perfect Holiday", "The Perfect Host", "The Perfect Lover", "The Perfect Man", "The Perfect Marriage", "The Perfect Match", "The Perfect Score", "The Perfect Specimen", "The Perfect Storm", "The Perfect Weapon", "The Perils of Gwendoline in the Land of the Yik-Yak", "The Perils of Pauline", "The Peripheral", "The Perks of Being a Wallflower", "The Personality Kid", "The Persuader", "The Pest", "The Petrified Forest", "The Petty Girl", "The Phantom Broadcast", "The Phantom Bullet", "The Phantom City", "The Phantom Express", "The Phantom Flyer", "The Phantom from 10,000 Leagues", "The Phantom Horseman", "The Phantom in the House", "The Phantom of 42nd Street", "The Phantom of Crestwood", "The Phantom of Paris", "The Phantom of the Opera", "The Phantom of the Opera: The Motion Picture", "The Phantom of the Range", "The Phantom Planet", "The Phantom President", "The Phantom Rider", "The Phantom Speaks", "The Phantom Stagecoach", "The Phantom Thief", "The Phantom Tollbooth", "The Phantom", "The Phenix City Story", "The Philadelphia Experiment", "The Philadelphia Story", "The Phobic", "The Phynx", "The Piano", "The Pick-up Artist", "The Picture of Dorian Gray", "The Pied Piper of Cleveland", "The Pied Piper of Hamelin", "The Pied Piper", "The Pigeon That Took Rome", "The Pigeons, Place St. Marc, Venice", "The Pilgrim Lady", "The Pilgrim", "The Pilot", "The Pinch Hitter", "The Pink Jungle", "The Pink Panther 2", "The Pink Panther", "The Pink Phink", "The Pinto Bandit", "The Pinto Kid", "The Pioneer Scout", "The Pirate", "The Pirates of Capri", "The Pirates of Penzance", "The Pirates Who Don't Do Anything: A VeggieTales Movie", "The Pit and the Pendulum", "The Pittsburgh Kid", "The Place Beyond the Pines", "The Plainsman", "The Plastic Age", "The Play Girl", "The Playboys", "The Player", "The Players Club", "The Playhouse", "The Pleasant Devil", "The Pleasure Garden", "The Pleasure of His Company", "The Pleasure Seekers", "The Pledge", "The Plot Thickens", "The Plough and the Stars", "The Plow Girl", "The Plunderer", "The Plunderers", "The Pocatello Kid", "The Poet of the Peaks", "The Pointing Finger", "The Polar Express", "The Police Patrol", "The Politician's Love Story", "The Pompatus of Love", "The Pony Express", "The Poor Boob", "The Poor Little Rich Girl", "The Poor Musician", "The Poor Rich", "The Pope of Greenwich Village", "The Poppy Girl's Husband", "The Poppy Is Also a Flower", "The Popular Sin", "The Port of 40 Thieves", "The Port of Missing Girls", "The Portrait of a Lady", "The Poseidon Adventure", "The Possession of Joel Delaney", "The Possession", "The Post", "The Postman Always Rings Twice", "The Postman Didn't Ring", "The Postman", "The Potters", "The Poughkeepsie Tapes", "The Power and the Glory", "The Power and the Prize", "The Power Inside", "The Power of Community: How Cuba Survived Peak Oil", "The Power of Light", "The Power of Melody", "The Power of One", "The Power of the Press", "The Power of the Whistler", "The Power Within", "The Power", "The Powerpuff Girls Movie", "The Powers Girl", "The Prairie Pirate", "The Prairie Wife", "The Prairie", "The Preacher's Wife", "The Predator", "The Preppie Murder", "The Prescott Kid", "The President Vanishes", "The President's Analyst", "The President's Lady", "The President's Mystery", "The Presidio", "The Prestige", "The Pretender", "The Pretty One", "The Pretty Sister of Jose", "The Preview Murder Mystery", "The Price of a Party", "The Price of Fear", "The Price of Innocence", "The Price of Pleasure", "The Price of Rendova", "The Price of Success", "The Price She Paid", "The Pride and the Passion", "The Pride of Palomar", "The Pride of St. Louis", "The Pride of the Force", "The Pride of the Yankees", "The Primitive Lover", "The Primrose Path", "The Primrose Ring", "The Prince and Betty", "The Prince and Me", "The Prince and the Pauper", "The Prince and the Showgirl", "The Prince of Egypt", "The Prince of Pennsylvania", "The Prince of Pep", "The Prince of Pilsen", "The Prince of Thieves", "The Prince of Tides", "The Prince Who Was a Thief", "The Prince", "The Princess and the Frog", "The Princess and the Pirate", "The Princess and the Plumber", "The Princess Bride", "The Princess Comes Across", "The Princess Diaries 2: Royal Engagement", "The Princess Diaries", "The Princess from Hoboken", "The Princess of Patches", "The Principal", "The Printer's Devil", "The Prisoner of Second Avenue", "The Prisoner of Shark Island", "The Prisoner of Zenda", "The Prisoner or: How I Planned to Kill Tony Blair", "The Prisoner", "The Private Affairs of Bel Ami", "The Private Afternoons of Pamela Mann", "The Private Eyes", "The Private Life of Helen of Troy", "The Private Lives of Adam and Eve", "The Private Lives of Elizabeth and Essex", "The Private Lives of Pippa Lee", "The Private Navy of Sgt. O'Farrell", "The Private War of Major Benson", "The Prize Fighter", "The Prize Pest", "The Prize Winner of Defiance, Ohio", "The Prize", "The Prizefighter and the Lady", "The Probation Wife", "The Prodigal Judge", "The Prodigal Liar", "The Prodigal", "The Producers", "The Professionals", "The Profiteers", "The Program", "The Promise", "The Promotion", "The Proof of the Man", "The Property Man", "The Prophecy", "The Prophet's Paradise", "The Proposal", "The Proprietor", "The Proud and Profane", "The Proud Ones", "The Proud Rebel", "The Prowler", "The Prussian Spy", "The Psychotronic Man", "The Public Defender", "The Public Enemy", "The Public Eye", "The Public Menace", "The Punisher", "The Punk Rock Movie", "The Punk Singer", "The Puppet Masters", "The Purchase Price", "The Purge", "The Purge: Anarchy", "The Purge: Election Year", "The Purple Gang", "The Purple Heart", "The Purple Highway", "The Purple Mask", "The Purple Monster Strikes", "The Purple V", "The Purple Vigilantes", "The Pursuit of D. B. Cooper", "The Pursuit of Happiness", "The Pursuit of Happyness", "The Pusher", "The Puzzled Bather and His Animated Clothes", "The Quakeress", "The Quarterback", "The Queen of Sheba", "The Queen of Versailles", "The Queen's Funeral", "The Quest", "The Quick and the Dead", "The Quick and the Undead", "The Quick Gun", "The Quickening Flame", "The Quiet American", "The Quiet Gun", "The Quiet Man", "The Quitter", "The Rabbit Trap", "The Racers", "The Rack", "The Racket Man", "The Racket", "The Rag Man", "The Rage of Paris", "The Rage: Carrie 2", "The Ragged Edge", "The Ragged Heiress", "The Raging Tide", "The Raid", "The Raiders", "The Railroad Builder", "The Rain People", "The Rainbow Man", "The Rainbow Trail", "The Rainbow", "The Rainmaker", "The Rainmakers", "The Rains Came", "The Rains of Ranchipur", "The Rajah", "The Ramblin' Kid", "The Range Feud", "The Range Terror", "The Ranger and the Lady", "The Rangers Ride", "The Rangers Step In", "The Rape of the Sabine Women", "The Rapture", "The Rare Breed", "The Rat Pack", "The Rat Race", "The Rat Trap Pickpocket Detector", "The Rattler's Hiss", "The Raven", "The Rawhide Kid", "The Rawhide Years", "The Razor's Edge", "The Reader", "The Real Cancun", "The Real Glory", "The Real McCoy", "The Reaping", "The Rebel Rousers", "The Rebel Set", "The Rebellious Bride", "The Rebound", "The Reckless Age", "The Reckless Hour", "The Reckless Moment", "The Reckless Sex", "The Reckoning", "The Recoil", "The Re-Creation of Brian Kent", "The Recruit", "The Red Badge of Courage", "The Red Beret", "The Red Blood of Courage", "The Red Dance", "The Red Danube", "The Red Dragon", "The Red House", "The Red Kimona", "The Red Lantern", "The Red Lily", "The Red Man and the Child", "The Red Menace", "The Red Mill", "The Red Pony", "The Red Rider", "The Red Robin", "The Red Rope", "The Red Stallion", "The Red Viper", "The Red Warning", "The Redeeming Sin", "The Redemption of a Pal", "The Redhead and the Cowboy", "The Redhead from Wyoming", "The Redhead", "The Ref", "The Referee", "The Reflecting Skin", "The Reformer and the Redhead", "The Reincarnation of Peter Proud", "The Reivers", "The Rejected Woman", "The Release of Dan Forbes", "The Relic", "The Reluctant Astronaut", "The Reluctant Debutante", "The Reluctant Dragon", "The Reluctant Fundamentalist", "The Reluctant Saint", "The Remains of the Day", "The Remarkable Andrew", "The Remarkable Mr. Pennypacker", "The Remittance Woman", "The Rendezvous", "The Renegade Ranger", "The Renegade", "The Replacement Killers", "The Replacements", "The Rescue", "The Rescuers Down Under", "The Rescuers", "The Rescuing Angel", "The Restless Breed", "The Restless Sex", "The Restless Spirit", "The Restless Years", "The Resurrected", "The Resurrection of Gavin Stone", "The Resurrection of Zachary Wheeler", "The Return of a Man Called Horse", "The Return of Charlie Chan", "The Return of Count Yorga", "The Return of Doctor X", "The Return of Dr. Fu Manchu", "The Return of Dracula", "The Return of Frank James", "The Return of Jesse James", "The Return of Jimmy Valentine", "The Return of Monte Cristo", "The Return of October", "The Return of Peter Grimm", "The Return of Rin Tin Tin", "The Return of Rusty", "The Return of Sophie Lang", "The Return of Swamp Thing", "The Return of the Durango Kid", "The Return of the King", "The Return of the Rangers", "The Return of the Vampire", "The Return of the Whistler", "The Return of Wildfire", "The Return", "The Reunion", "The Revenant", "The Revenge Rider", "The Revengers", "The Revolt of Mamie Stover", "The Revolutionary", "The Reward of the Faithless", "The Rich Are Always with Us", "The Rich Man's Wife", "The Richest Girl in the World", "The Richest Girl", "The Riddle Rider", "The Ride Back", "The Rider of Death Valley", "The Rider", "The Ridin' Demon", "The Ridin' Kid from Powder River", "The Ridin' Rascal", "The Riding Avenger", "The Riding Comet", "The Riding Renegade", "The Riding Tornado", "The Right Approach", "The Right of the Strongest", "The Right of Way", "The Right Stuff", "The Right That Failed", "The Right to Happiness", "The Right to Lie", "The Right to Live", "The Right to Love", "The Right to Romance", "The Ring of Destiny", "The Ring of Terror", "The Ring Two", "The Ring", "The Ringer", "The Rink", "The Rise and Fall of Legs Diamond", "The Rite", "The Ritz", "The River Pirate", "The River Wild", "The River", "The River's Edge", "The Road Agent", "The Road Agents", "The Road Back", "The Road to Denver", "The Road to Divorce", "The Road to El Dorado", "The Road to Glory", "The Road to Hong Kong", "The Road to Mandalay", "The Road to Reno", "The Road to Romance", "The Road to Ruin", "The Road to Singapore", "The Road to the Heart", "The Road to Wellville", "The Road to Yesterday", "The Road Within", "The Road", "The Roadhouse Murder", "The Roaring Rider", "The Roaring Road", "The Roaring Twenties", "The Robe", "The Rock", "The Rocker", "The Rocket Man", "The Rocketeer", "The Rocky Horror Picture Show", "The Rocky Road", "The Rogue Song", "The Rogues Tavern", "The Roman Spring of Mrs. Stone", "The Romance of a Million Dollars", "The Romance of Rosy Ridge", "The Romance of Tarzan", "The Romantic Englishwoman", "The Romantics", "The Rookie", "The Room", "The Roommate", "The Roommates", "The Roots of Heaven", "The Rosary Murders", "The Rosary", "The Rose Bowl Story", "The Rose Bush of Memories", "The Rose of Paris", "The Rose of San Juan", "The Rose Tattoo", "The Rose", "The Rosebud Beach Hotel", "The Rough House", "The Rough Riders", "The Rough, Tough West", "The Roughneck", "The Round Up", "The Rounders", "The Round-Up", "The Royal African Rifles", "The Royal Bed", "The Royal Hunt of the Sun", "The Royal Mounted Rides Again", "The Royal Rider", "The Royal Tenenbaums", "The Rugrats Movie", "The Ruins", "The Rules of Attraction", "The Ruling Passion", "The Ruling Voice", "The Rum Diary", "The Runaround", "The Runaway Bride", "The Runaway Express", "The Runaway Match, or Marriage by Motor", "The Runaway", "The Runaways", "The Rundown", "The Runner", "The Running Man", "The Rush Hour", "The Russia House", "The Russians Are Coming, the Russians Are Coming", "The Rustle of Silk", "The Sacred Flame", "The Sacrifice", "The Sad Horse", "The Sad Sack", "The Saddle Buster", "The Saddle King", "The Sadist", "The Safety Curtain", "The Safety of Objects", "The Saga of Hemp Brown", "The Sagebrush Troubadour", "The Sailor Takes a Wife", "The Sailor Who Fell from Grace with the Sea", "The Saint in London", "The Saint in New York", "The Saint in Palm Springs", "The Saint Meets the Tiger", "The Saint of Fort Washington", "The Saint Strikes Back", "The Saint Takes Over", "The Saint", "The Sainted Sisters", "The Saint's Double Trouble", "The Saint's Vacation", "The Salamander", "The Salton Sea", "The Salvation Army Lass", "The Salvation Hunters", "The Salzburg Connection", "The San Antonio Kid", "The San Francisco Story", "The Sand Pebbles", "The Sandlot", "The Sandpiper", "The Sandpit Generals", "The Sanitarium", "The Santa Clause 2", "The Santa Clause 3: The Escape Clause", "The Santa Clause", "The Santa Fe Trail", "The Sap from Syracuse", "The Sap", "The Saphead", "The Saracen Blade", "The Saratov Approach", "The Sasquatch Gang", "The Satan Bug", "The Satin Girl", "The Saturday Night Kid", "The Savage Girl", "The Savage Horde", "The Savage Innocents", "The Savage Is Loose", "The Savage Seven", "The Savage", "The Savages", "The Sawdust Paradise", "The Sawdust Trail", "The Saxon Charm", "The Scalphunters", "The Scapegoat", "The Scar of Shame", "The Scarecrow", "The Scarf", "The Scarlet Car", "The Scarlet Claw", "The Scarlet Clue", "The Scarlet Coat", "The Scarlet Dove", "The Scarlet Drop", "The Scarlet Empress", "The Scarlet Honeymoon", "The Scarlet Horseman", "The Scarlet Hour", "The Scarlet Lady", "The Scarlet Letter", "The Scarlet Lily", "The Scarlet Pumpernickel", "The Scarlet Shadow", "The Scarlet West", "The Scenic Route", "The Score", "The Scorpion King", "The Scoundrel", "The Scout", "The Scrapper", "The Scrappin' Kid", "The Screaming Skull", "The Scribbler", "The Sea Bat", "The Sea Beast", "The Sea Chase", "The Sea Ghost", "The Sea God", "The Sea Gull", "The Sea Hawk", "The Sea Hornet", "The Sea Lion", "The Sea of Grass", "The Sea Wolf", "The Sea Wolves", "The Seafarers", "The Seagull", "The Sealed Envelope", "The Search for Bridey Murphy", "The Search for One-eye Jimmy", "The Search for Signs of Intelligent Life in the Universe", "The Search", "The Searchers", "The Searching Wind", "The Second Arrival", "The Second Best Exotic Marigold Hotel", "The Second Chance", "The Second Face", "The Second Floor Mystery", "The Second Greatest Sex", "The Second Jungle Book: Mowgli & Baloo", "The Second Time Around", "The Second Woman", "The Secret Bride", "The Secret Call", "The Secret Fury", "The Secret Garden", "The Secret Heart", "The Secret Hour", "The Secret Invasion", "The Secret Life of an American Wife", "The Secret Life of Bees", "The Secret Life of Pets", "The Secret Life of Walter Mitty", "The Secret Lives of Dentists", "The Secret of Convict Lake", "The Secret of Dr. Kildare", "The Secret of Madame Blanche", "The Secret of My Succe$s", "The Secret of My Success", "The Secret of NIMH", "The Secret of Roan Inish", "The Secret of Santa Vittoria", "The Secret of St. Ives", "The Secret of the Purple Reef", "The Secret of the Sword", "The Secret of the Whistler", "The Secret Sex Lives of Romeo and Juliet", "The Secret Six", "The Secret War of Harry Frigg", "The Secret Ways", "The Secret Wire", "The Secret Witness", "The Secrets of Wu Sin", "The Seduction of Joe Tynan", "The Seduction", "The Seeker", "The Self-Made Wife", "The Sell Out", "The Sellout", "The Senator Was Indiscreet", "The Sender", "The Sentinel", "The Sergeant Was a Lady", "The Sergeant", "The Serpent and the Rainbow", "The Serpent's Egg", "The Servant Girl Problem", "The Sessions", "The Setting Sun", "The Set-Up", "The Seven Ages", "The Seven Little Foys", "The Seven Minutes", "The Seven Year Itch", "The Seven-Per-Cent Solution", "The Seventh Cross", "The Seventh Day", "The Seventh Sign", "The Seventh Sin", "The Seventh Victim", "The Seventh Voyage of Sinbad", "The Seven-Ups", "The Shack", "The Shadow of Silk Lennox", "The Shadow of the Desert", "The Shadow on the Wall", "The Shadow on the Window", "The Shadow Returns", "The Shadow", "The Shady Lady", "The Shaggy D.A.", "The Shaggy Dog", "The Shakedown", "The Shakiest Gun in the West", "The Shallows", "The Shamrock Handicap", "The Shanghai Cobra", "The Shanghai Gesture", "The Shanghai Story", "The Shannons of Broadway", "The Shape of Water", "The Sharkfighters", "The Shawshank Redemption", "The She-Creature", "The Sheepman", "The Sheik Steps Out", "The Sheik", "The Shepherd King", "The Shepherd of the Hills", "The Sheriff of Medicine Bow", "The Sheriff's Oath", "The Sheriff's Son", "The She-Wolf", "The Shield of Honor", "The Shield of Silence", "The Shining Hour", "The Shining", "The Ship from Shanghai", "The Ship of Souls", "The Ship That Died", "The Shipping News", "The Shock Punch", "The Shock", "The Shocking Miss Pilgrim", "The Shoemaker and the Doll", "The Shoes of the Fisherman", "The Shootin' Fool", "The Shootin' Kid", "The Shooting of Dan McGrew", "The Shooting Party", "The Shooting", "The Shootist", "The Shop Around the Corner", "The Shopworn Angel", "The Show of Shows", "The Show", "The Showdown", "The Show-Off", "The Shriek of Araby", "The Shrike", "The Shrimp on the Barbie", "The Shrink Is In", "The Shuttered Room", "The Sicilian", "The Side Show of Life", "The Sidehackers", "The Sideshow", "The Siege", "The Sign of the Cactus", "The Sign of the Cross", "The Sign of the Ram", "The Signal Tower", "The Signal", "The Silence of the Hams", "The Silence of the Lambs", "The Silence", "The Silencers", "The Silent Accuser", "The Silent Command", "The Silent Lover", "The Silent Partner", "The Silent Stranger", "The Silent Vow", "The Silent Watcher", "The Silent Witness", "The Silk Express", "The Silk-Lined Burglar", "The Silver Bullet", "The Silver Chalice", "The Silver Cord", "The Silver Girl", "The Silver Horde", "The Silver King", "The Silver Lining", "The Silver Streak", "The Silver Trail", "The Silver Treasure", "The Silver Whip", "The Simple Things", "The Simpsons Movie", "The Sin Flood", "The Sin of Harold Diddlebock", "The Sin of Madelon Claudet", "The Sin of Nora Moran", "The Sin Ship", "The Sin Sister", "The Singing Blacksmith", "The Singing Cowboy", "The Singing Detective", "The Singing Fool", "The Singing Hill", "The Singing Kid", "The Singing Marine", "The Singing Nun", "The Singing Outlaw", "The Singing Sheriff", "The Singing Vagabond", "The Single Moms Club", "The Single Sin", "The Single Standard", "The Singles Ward", "The Sinister Urge", "The Sinking of the Lusitania", "The Sins of Rachel Cade", "The Sins of the Children", "The Siren Call", "The Siren of Seville", "The Siren's Song", "The Sisterhood of the Traveling Pants 2", "The Sisterhood of the Traveling Pants", "The Sisters Brothers", "The Sisters", "The Sitter", "The Six-Fifty", "The Sixth Commandment", "The Sixth Sense", "The Skeleton Dance", "The Skeleton Key", "The Skeleton Twins", "The Skipper Surprised His Wife", "The Skulls", "The Sky Hawk", "The Sky Parade", "The Sky Pilot", "The Sky Raider", "The Sky Rider", "The Sky Skidder", "The Skydivers", "The Sky's the Limit", "The Skywayman", "The Slammin' Salmon", "The Slanderers", "The Slayer", "The Sleeping City", "The Sleeping Lion", "The Sleeping Sentinel", "The Sleepwalker", "The Slender Thread", "The Slim Princess", "The Slime People", "The Slugger's Wife", "The Small Bachelor", "The Smart Set", "The Smilin' Kid", "The Smiling Ghost", "The Smiling Lieutenant", "The Smiling Terror", "The Smoke Eaters", "The Smokers", "The Smuggler", "The Smuggler's Cave", "The Smugglers of Santa Cruz", "The Smurfs 2", "The Smurfs", "The Smurfs: The Legend of Smurfy Hollow", "The Snake Pit", "The Sneak", "The Sniper", "The Snob Buster", "The Snob", "The Snow Bride", "The Snow Creature", "The Snows of Kilimanjaro", "The Snowshoe Trail", "The Social Code", "The Social Highwayman", "The Social Lion", "The Social Network", "The Soldier and the Lady", "The Solid Gold Cadillac", "The Solitaire Man", "The Soloist", "The Solution to the Mystery", "The Son of Dr. Jekyll", "The Son of Frankenstein", "The Son of Monte Cristo", "The Son of No One", "The Son of Robin Hood", "The Son of Rusty", "The Son of the Sheik", "The Son of the Wolf", "The Son of Thomas Gray", "The Son-Daughter", "The Song and Dance Man", "The Song of Bernadette", "The Song of Life", "The Song of Love", "The Song of Songs", "The Song of the Canary", "The Song of the Sea Shell", "The Song Remains the Same", "The Song", "The Sons of Katie Elder", "The Sorcerer's Apprentice", "The Sorrows of Satan", "The Soul of a Monster", "The Soul of Broadway", "The Soul's Cycle", "The Sound and the Fury", "The Sound of Fury", "The Sound of Music", "The Southerner", "The Sower Reaps", "The Space Between Us", "The Space Children", "The Spaniard", "The Spanish Cape Mystery", "The Spanish Dancer", "The Spanish Main", "The Spanish Prisoner", "The Spark Divine", "The Specialist", "The Specials", "The Spectacular Now", "The Speed Classic", "The Speed Demon", "The Speed Maniac", "The Speed Reporter", "The Speed Spook", "The Speeding Venus", "The Spender", "The Sphinx", "The Spider Woman Strikes Back", "The Spider Woman", "The Spider", "The Spiderwick Chronicles", "The Spikes Gang", "The Spiral Road", "The Spiral Staircase", "The Spirit Is Willing", "The Spirit of Adventure", "The Spirit of Christmas", "The Spirit of Notre Dame", "The Spirit of St. Louis", "The Spirit of the USA", "The Spirit of West Point", "The Spirit", "The Spite Bride", "The Spitfire Grill", "The Spitfire of Seville", "The Spitfire", "The Splendid Crime", "The Splendid Road", "The Splendid Sin", "The Split", "The Spoilers", "The SpongeBob Movie: Sponge Out of Water", "The SpongeBob SquarePants Movie", "The Spook Who Sat By the Door", "The Sport Parade", "The Sporting Age", "The Sporting Chance", "The Sporting Life", "The Sporting Lover", "The Sporting Venus", "The Spotlight", "The Spy Next Door", "The Spy Ring", "The Spy Who Came in from the Cold", "The Spy Who Dumped Me", "The Spy", "The Squall", "The Square Jungle", "The Square", "The Squaw Man", "The Squawkin' Hawk", "The Squeeze", "The Squid and the Whale", "The St. Louis Kid", "The St. Valentine's Day Massacre", "The Stairs", "The Stalking Moon", "The Stand at Apache River", "The Stand", "The Star Boarder", "The Star Chamber", "The Star Maker", "The Star of Bethlehem", "The Star Packer", "The Star Witness", "The Star", "The Starfighters", "The Stars Are Singing", "The Stars Fell on Henrietta", "The Starving Games", "The Station Agent", "The Steadfast Heart", "The Steel Cage", "The Steel Claw", "The Steel Fist", "The Steel Helmet", "The Steel Jungle", "The Steel King", "The Steel Lady", "The Steel Trap", "The Stepfather", "The Stepford Wives", "The Stepping Stone", "The Sterile Cuckoo", "The Still Alarm", "The Stilwell Road", "The Sting II", "The Sting of the Lash", "The Sting", "The Stoker", "The Stolen Bride", "The Stolen Jools", "The Stolen Ranch", "The Stone Killer", "The Stoned Age", "The Stooge", "The Stork Club", "The Storm Breaker", "The Storm Daughter", "The Storm Rider", "The Storm", "The Story of Dr. Wassell", "The Story of Esther Costello", "The Story of G.I. Joe", "The Story of Louis Pasteur", "The Story of Mankind", "The Story of Molly X", "The Story of Robin Hood and His Merrie Men", "The Story of Ruth", "The Story of Seabiscuit", "The Story of Temple Drake", "The Story of the 14th Air Force", "The Story of the Olive", "The Story of Three Loves", "The Story of Us", "The Story of Vernon and Irene Castle", "The Story of Will Rogers", "The Story Without a Name", "The Straight Story", "The Strange Affair of Uncle Harry", "The Strange Boarder", "The Strange Case of Clara Deane", "The Strange Case of Doctor Rx", "The Strange Death of Adolf Hitler", "The Strange Door", "The Strange Love of Martha Ivers", "The Strange Love of Molly Louvain", "The Strange Mr. Gregory", "The Strange Mrs. Crane", "The Strange One", "The Strange Ones", "The Strange Woman", "The Strange World of Planet X", "The Stranger from Arizona", "The Stranger from Pecos", "The Stranger from Ponca City", "The Stranger from Texas", "The Stranger Wore a Gun", "The Stranger", "The Strangers' Banquet", "The Stranger's Return", "The Strangers", "The Strangers: Prey at Night", "The Strangler", "The Stratton Story", "The Strawberry Blonde", "The Strawberry Roan", "The Strawberry Statement", "The Street of Forgotten Men", "The Street of Illusion", "The Street of Sin", "The Street of Tears", "The Street Singer", "The Street with No Name", "The Strength o' Ten", "The Strength of Donald McKenzie", "The Strength of the Pines", "The Strenuous Life; or, Anti-Race Suicide", "The Strip", "The Stripper", "The Strong Man", "The Stronger Vow", "The Strongest Man in the World", "The Struggle", "The Student Prince in Old Heidelberg", "The Student Prince", "The Stuff", "The Stunt Man", "The Stupids", "The Subject Was Roses", "The Substance of Fire", "The Substitute Minister", "The Substitute Wife", "The Substitute", "The Subterraneans", "The Suburbanite", "The Suckling", "The Sugarland Express", "The Sultan's Daughter", "The Sum of All Fears", "The Summertime Killer", "The Sun Also Rises", "The Sun Comes Up", "The Sun Never Sets", "The Sun Sets at Dawn", "The Sun Shines Bright", "The Sunchaser", "The Sundown Trail", "The Sundowner", "The Sundowners", "The Sunset Legion", "The Sunset Trail", "The Sunshine Boys", "The Sunshine Trail", "The Super Cops", "The Super", "The Sure Thing", "The Surrogate", "The Survivors", "The Suspect", "The Swan Princess Christmas", "The Swan Princess II: Escape from Castle Mountain", "The Swan Princess", "The Swan", "The Swarm", "The Sweet Ride", "The Sweetest Thing", "The Sweetheart of Sigma Chi", "The Swellhead", "The Swimmer", "The Swinger", "The Swiss Conspiracy", "The Switch", "The Sword and the Rose", "The Sword and the Sorcerer", "The Sword in the Stone", "The Sword of Monte Cristo", "The Swordsman", "The System", "The Tailor of Panama", "The Taking of Beverly Hills", "The Taking of Pelham 123", "The Taking of Pelham One Two Three", "The Tale of Despereaux", "The Tale of Tiffany Lust", "The Talented Mr. Ripley", "The Talk of the Town", "The Talker", "The Tall Man", "The Tall Men", "The Tall Stranger", "The Tall T", "The Tall Target", "The Tall Texan", "The Tamarind Seed", "The Taming of Sunnybrook Nell", "The Taming of the Shrew", "The Taming of the West", "The Tanks are Coming", "The Tao of Steve", "The Tarnished Angels", "The Tartars", "The Tattered Dress", "The Tattooed Stranger", "The Tavern Keeper's Daughter", "The Teahouse of the August Moon", "The Teaser", "The Teddy Bear Master", "The Teeth of the Tiger", "The Telephone Girl and the Lady", "The Tell Tale Wire", "The Tell-Tale Heart", "The Telltale Knife", "The Tell-Tale Message", "The Temp", "The Tempest", "The Temple of Venus", "The Temptress", "The Ten Commandments", "The Ten of Spades", "The Ten", "The Tender Trap", "The Tender Years", "The Tenderfoot", "The Tenth Woman", "The Terminal Man", "The Terminal", "The Terminator", "The Terror of Tiny Town", "The Terror Within", "The Terror", "The Test of Donald Norton", "The Test of Honor", "The Texan Meets Calamity Jane", "The Texan", "The Texans", "The Texas Bad Man", "The Texas Bearcat", "The Texas Chain Saw Massacre", "The Texas Chainsaw Massacre 2", "The Texas Chainsaw Massacre", "The Texas Chainsaw Massacre: The Beginning", "The Texas Kid", "The Texas Ranger", "The Texas Rangers", "The Texas Streak", "The Texas Tornado", "The Texas Trail", "The Thief and the Cobbler", "The Thief of Bagdad", "The Thief Who Came to Dinner", "The Thief", "The Thin Blue Line", "The Thin Man Goes Home", "The Thin Man", "The Thin Red Line", "The Thing About My Folks", "The Thing Called Love", "The Thing from Another World", "The Thing That Couldn't Die", "The Thing with Two Heads", "The Thing", "The Third Alarm", "The Third Day", "The Third Degree", "The Third Kiss", "The Third Secret", "The Thirteenth Chair", "The Thirteenth Floor", "The Thirteenth Guest", "The Thirteenth Hour", "The Thirteenth Year", "The Thomas Crown Affair", "The Thoroughbred", "The Threat", "The Three Burials of Melquiades Estrada", "The Three Caballeros", "The Three Faces of Eve", "The Three Lives of Thomasina", "The Three Mesquiteers", "The Three Musketeers", "The Three Must-Get-Theres", "The Three Sisters", "The Three Stooges Go Around the World in a Daze", "The Three Stooges in Orbit", "The Three Stooges Meet Hercules", "The Three Stooges", "The Three Troubledoers", "The Three Wise Guys", "The Three Worlds of Gulliver", "The Thrill Chaser", "The Thrill Hunter", "The Thrill of Brazil", "The Thrill of It All", "The Throwback", "The Thunderbolt", "The Thundering Herd", "The Thundering Trail", "The Tie That Binds", "The Tiger Lily", "The Tiger Makes Out", "The Tiger Woman", "The Tiger's Claw", "The Tigger Movie", "The Tijuana Story", "The Timber Queen", "The Timber Trail", "The Timber Wolf", "The Time Machine", "The Time of Their Lives", "The Time of Your Life", "The Time Traveler's Wife", "The Time, the Place and the Girl", "The Timid Terror", "The Tin Star", "The Tingler", "The Tioga Kid", "The Tip Off", "The Tip", "The Tip-Off", "The Tired Tailor's Dream", "The Titan: Story of Michelangelo", "The Toast of Death", "The Toast of New Orleans", "The Toast of New York", "The Toilers and the Wayfarers", "The Toll of the Sea", "The Tomb of Ligeia", "The Tomboy", "The Tong Man", "The Toolbox Murders", "The Tooth Will Out", "The Top of New York", "The Top of the World", "The Topeka Terror", "The Torch Bearer", "The Torch", "The Tornado", "The Touch of Satan", "The Touch", "The Tough Guy", "The Tougher They Come", "The Tourist", "The Tower of Lies", "The Towering Inferno", "The Town of Nazareth", "The Town Scandal", "The Town That Dreaded Sundown", "The Town That Forgot God", "The Town Went Wild", "The Town", "The Toxic Avenger Part II", "The Toxic Avenger Part III: The Last Temptation of Toxie", "The Toxic Avenger", "The Toy Tiger", "The Toy Wife", "The Toy", "The Traffic Cop", "The Tragical Tale of a Belated Letter", "The Trail Beyond", "The Trail of '98", "The Trail of the Hound", "The Trail of the Law", "The Trail of the Lonesome Pine", "The Trail Rider", "The Train Robbers", "The Train Wreckers", "The Train", "The Tramp", "The Tramp's Dream", "The Tramp's First Bath", "The Transformers: The Movie", "The Transporter", "The Trap", "The Traveling Executioner", "The Traveling Saleswoman", "The Treasure of Lost Canyon", "The Treasure of Pancho Villa", "The Treasure of the Sierra Madre", "The Tree of Life", "The Trembling Hour", "The Trespasser", "The Trial of Billy Jack", "The Trial of Mary Dugan", "The Trial of the Incredible Hulk", "The Trial of Vivienne Ware", "The Trial", "The Trials of Henry Kissinger", "The Tribulations of an Amateur Photographer", "The Triflers", "The Trigger Effect", "The Trigger Trio", "The Trip to Bountiful", "The Trip", "The Trojan Women", "The Trouble Shooter", "The Trouble with Angels", "The Trouble with Girls", "The Trouble with Harry", "The Trouble with Spies", "The Trouble with Wives", "The Trouble with Women", "The Trouper", "The Truce Hurts", "The True Glory", "The True Story of Frank Zappa's 200 Motels", "The True Story of Jesse James", "The True Story of Lynn Stuart", "The Truman Show", "The Trumpet Blows", "The Trunk Mystery", "The Trusted Outlaw", "The Truth About Cats & Dogs", "The Truth About Charlie", "The Truth About Murder", "The Truth About Spring", "The Truth About Wives", "The Truth About Women", "The Truth About Youth", "The Truthful Liar", "The Truthful Sex", "The Tulsa Kid", "The Tune", "The Tunnel of Love", "The Turmoil", "The Turn in the Road", "The Turning Point", "The Turning", "The Tuttles of Tahiti", "The Tuxedo", "The Twelfth Juror", "The Twelve Chairs", "The Twilight of the Golds", "The Twilight Saga: Breaking Dawn - Part 1", "The Twilight Saga: Breaking Dawn - Part 2", "The Twilight Saga: Eclipse", "The Twin Pawns", "The Twinkle in God's Eye", "The Twinkler", "The Two Brides", "The Two Brothers", "The Two Faces of January", "The Two Jakes", "The Two Mouseketeers", "The Two Mrs. Carrolls", "The Two Orphans", "The Two Paths", "The Two-Fisted Lover", "The Two-Gun Man", "The Twonky", "The U.S. vs. John Lennon", "The Ugly American", "The Ugly Dachshund", "The Ugly Truth", "The Ultimate Thrill", "The Ultimate Warrior", "The Unafraid", "The Unbearable Lightness of Being", "The Unbelievable Truth", "The Unborn", "The Unchanging Sea", "The Unchastened Woman", "The Undead", "The Undefeated", "The Undercover Man", "The Undercover Woman", "The Underdog", "The Understudy", "The Understudy: Graveyard Shift II", "The Undertaker and His Pals", "The Undertow", "The Underworld Story", "The Undying Monster", "The Unearthly", "The Unexpected Bath", "The Unexpected Father", "The Unfair Sex", "The Unfaithful", "The Unfinished Dance", "The Unforgiven", "The Unguarded Hour", "The Unguarded Moment", "The Unholy Garden", "The Unholy Three", "The Unholy Wife", "The Uninvited Guest", "The Uninvited", "The Unkissed Bride", "The Unknown Cavalier", "The Unknown Guest", "The Unknown Known", "The Unknown Lover", "The Unknown Man", "The Unknown Purple", "The Unknown Quantity", "The Unknown Ranger", "The Unknown Soldier", "The Unknown Terror", "The Unknown", "The Unlawful Trade", "The Unmasking", "The Unnamed Woman", "The Unpainted Woman", "The Unpardonable Sin", "The Unseen", "The Unsinkable Molly Brown", "The Unspeakable Act", "The Unsuspected", "The Untameable", "The Untamed Breed", "The Untamed Lady", "The Untouchables", "The Unwelcome Stranger", "The Unwritten Code", "The Unwritten Law", "The Uplifters", "The Upper Footage", "The Upside of Anger", "The Usual Suspects", "The Usurper", "The Utah Kid", "The V.I.P.s", "The Vagabond King", "The Vagabond Lover", "The Vagabond Trail", "The Vagabond", "The Valiant Hombre", "The Valiant", "The Valley of Bravery", "The Valley of Decision", "The Valley of Gwangi", "The Valley of Hunted Men", "The Valley of Silent Men", "The Valley of the Giants", "The Vampire (1915 film)", "The Vampire Bat", "The Vampire", "The Vampire's Ghost", "The Van", "The Vanderbilt Auto Race", "The Vanishing American", "The Vanishing Duck", "The Vanishing Frontier", "The Vanishing of Sidney Hall", "The Vanishing Outpost", "The Vanishing Pioneer", "The Vanishing Prairie", "The Vanishing Virginian", "The Vanishing Westerner", "The Vanishing", "The Vanquished", "The Vatican Tapes", "The Veiled Adventure", "The Veiled Woman", "The Veils of Bagdad", "The Velvet Touch", "The Venetian Affair", "The Vengeance of Durand", "The Venus Model", "The Verdict", "The Vermilion Pencil", "The Very Idea", "The Very Thought of You", "The Vice Squad", "The Vicious Circle", "The Victor", "The Victors", "The Video Dead", "The View from Pompey's Head", "The Vigilante", "The Vigilantes Return", "The Vigilantes Ride", "The Viking", "The Vikings", "The Village Blacksmith", "The Village", "The Villain Still Pursued Her", "The Villain", "The Vintage", "The Violators", "The Violent Men", "The Violent Years", "The Virgin of Stamboul", "The Virgin Queen", "The Virgin Suicides", "The Virgin", "The Virginia Judge", "The Virginian", "The Virtuous Husband", "The Virtuous Model", "The Virtuous Sin", "The Virtuous Thief", "The Visit", "The Visitor", "The Visitors", "The Visual Bible: Acts", "The Voice from the Minaret", "The Voice of Bugle Ann", "The Voice of Conscience", "The Voice of the Child", "The Voice of the Turtle", "The Voice of the Violin", "The Voices", "The Volga Boatman", "The Vow", "The Voyage", "The Wabbit Who Came to Supper", "The WAC from Walla Walla", "The Wackiest Ship In the Army", "The Wackness", "The Wagon Master", "The Wagon Show", "The Wagons Roll at Night", "The Wait", "The Waiting Game", "The Walking Dead", "The Walking Deceased", "The Walking Hills", "The Walking Target", "The Wall Flower", "The Wall St. Whizz", "The Wall", "The Walls Came Tumbling Down", "The Walls of Jericho", "The Wanderer", "The Wanderers", "The Waning Sex", "The Wanters", "The War Against Mrs. Hadley", "The War at Home", "The War Between Men and Women", "The War Lord", "The War Lover", "The War of the Roses", "The War of the Worlds", "The War Room", "The War Tapes", "The War Wagon", "The War Within", "The War", "The Warrens of Virginia", "The Warrior and the Sorceress", "The Warrior's Husband", "The Warriors", "The Wash", "The Washington Masquerade", "The Wasp Woman", "The Wasp", "The Watch", "The Watcher in the Woods", "The Watcher", "The Water Hole", "The Water Horse: Legend of the Deep", "The Water Lily", "The Waterboy", "The Waterdance", "The Watermelon Woman", "The Way of a Girl", "The Way of a Woman", "The Way of All Flesh", "The Way of All Men", "The Way of the Gun", "The Way of the Strong", "The Way to Love", "The Way to the Gold", "The Way We Were", "The Way West", "The Way", "The Way, Way Back", "The Wayward Bus", "The Wayward Girl", "The Weaker Vessel", "The Wearing of the Grin", "The Weather Man", "The Weavers: Wasn't That a Time!", "The Web of Chance", "The Web", "The Wedding Banquet", "The Wedding Date", "The Wedding March", "The Wedding Night", "The Wedding Party", "The Wedding Planner", "The Wedding Ringer", "The Wedding Singer", "The Wedding Song", "The Welcome Burglar[1]", "The Well", "The Well-Groomed Bride", "The Werewolf of Washington", "The Werewolf of Woodstock", "The Werewolf", "The West Point Story", "The West Side Kid", "The West Side Waltz", "The West~Bound Limited", "The Western Code", "The Western Wallop", "The Westerner", "The Westerners", "The Westward Trail", "The Wet Parade", "The Whales of August", "The Wheel", "The Wheeler Dealers", "The Whip Hand", "The Whip Woman", "The Whip", "The Whirlwind of Youth", "The Whirlwind", "The Whispered Name", "The Whispering Chorus", "The Whispering Skull", "The Whistle at Eaton Falls", "The Whistler", "The White Angel", "The White Black Sheep", "The White Buffalo", "The White Cliffs of Dover", "The White Cockatoo", "The White Dawn", "The White Desert", "The White Dove", "The White Flower", "The White Heather", "The White Monkey", "The White Moth", "The White Orchid", "The White Outlaw", "The White Parade", "The White Rose", "The White Rosette", "The White Sheep", "The White Sin", "The White Sister", "The White Squaw", "The White Tower", "The Whole Dam Family and the Dam Dog", "The Whole Nine Yards", "The Whole Ten Yards", "The Whole Town's Talking", "The Whole Truth", "The Whole Wide World", "The Wicked Darling", "The Wicked Dreams of Paula Schultz", "The Wicked", "The Wicker Man", "The Widow From Chicago", "The Widow from Monte Carlo", "The Widow in Scarlet", "The Widow's Investment", "The Wife of Monte Cristo", "The Wife of the Centaur", "The Wife Takes a Flyer", "The Wife Who Wasn't Wanted", "The Wife", "The Wife's Relations", "The Wild and the Innocent", "The Wild Angels", "The Wild Blue Yonder", "The Wild Bull's Lair", "The Wild Bunch", "The Wild Country", "The Wild Frontier", "The Wild Girl", "The Wild Goose Chase", "The Wild Heart", "The Wild Horse Stampede", "The Wild Life", "The Wild Man of Borneo", "The Wild North", "The Wild One", "The Wild Party", "The Wild Racers", "The Wild Ride", "The Wild Scene", "The Wild Thornberrys Movie", "The Wild West Show", "The Wild Westerners", "The Wild Wild West", "The Wild", "The Wild, Wild World of Jayne Mansfield", "The Wildcatter", "The Wilderness Trail", "The Winchester Woman", "The Wind and the Lion", "The Wind", "The Winding Stair", "The Window", "The Winged Horseman", "The Wings of Eagles", "The Wings of the Dove", "The Winner", "The Winner's Circle", "The Winning Girl", "The Winning of Barbara Worth", "The Winning Stroke", "The Winning Team", "The Winning Ticket", "The Winter Guest", "The Wise Guy", "The Wise Kid", "The Wise Virgin", "The Wiser Sex", "The Wishing Ring Man", "The Wishing Ring", "The Wistful Widow of Wagon Gap", "The Witch", "The Witches of Eastwick", "The Witches", "The Witching Hour", "The Witness Chair", "The Witness for the Defense", "The Witness", "The Wiz", "The Wizard of Baghdad", "The Wizard of Gore", "The Wizard of Oz (TV special)", "The Wizard of Oz", "The Wizard of Speed and Time", "The Wizard", "The Wolf Hunters", "The Wolf Man", "The Wolf of Wall Street", "The Wolf Song", "The Wolf", "The Wolfman", "The Wolverine", "The Woman Accused", "The Woman Between", "The Woman Chaser", "The Woman Condemned", "The Woman Conquers", "The Woman Disputed", "The Woman from Hell", "The Woman from Mellon's", "The Woman from Monte Carlo", "The Woman from Tangier", "The Woman God Forgot", "The Woman Hater", "The Woman He Married", "The Woman Hunter", "The Woman I Love", "The Woman I Stole", "The Woman in Black 2: Angel of Death", "The Woman in Chains", "The Woman in Green", "The Woman in Red", "The Woman in Room 13", "The Woman in the Suitcase", "The Woman in the Window", "The Woman in White", "The Woman Inside", "The Woman Michael Married", "The Woman Next Door", "The Woman of Bronze", "The Woman of the Town", "The Woman on the Beach", "The Woman on the Index", "The Woman on the Jury", "The Woman on Trial", "The Woman Racket", "The Woman Thou Gavest Me", "The Woman Under Cover", "The Woman Who Came Back", "The Woman Who Walked Alone", "The Woman with Four Faces", "The Woman's Side", "The Women in His Life", "The Women Men Marry", "The Women of Brewster Place", "The Women of Pitcairn Island", "The Women", "The Wonder, Ching Ling Foo", "The Wonderful Country", "The Wonderful Land of Oz", "The Wonderful Thing", "The Wonderful Wizard of Oz", "The Wonderful World of the Brothers Grimm", "The Wood", "The Wooden Leg", "The Woodlanders", "The Woods", "The Woodsman", "The Words", "The Work and the Glory", "The Working Man", "The Workman's Paradise", "The World According to Garp", "The World Accuses", "The World Aflame", "The World and Its Woman", "The World and the Flesh", "The World at War", "The World Changes", "The World in His Arms", "The World Moves On", "The World of Abbott and Costello", "The World of Henry Orient", "The World of Suzie Wong", "The World to Live In", "The World Was His Jury", "The World, the Flesh and the Devil", "The World's Applause", "The World's Champion", "The World's Fastest Indian", "The World's Greatest Athlete", "The World's Greatest Lover", "The World's Greatest Sinner", "The Worst of Faces of Death", "The Worst Woman in Paris?", "The Wraith", "The Wrath of God", "The Wrath of the Gods", "The Wreck of the Hesperus", "The Wreck of the Mary Deare", "The Wreck of the Singapore", "The Wrecker", "The Wrecking Crew", "The Wrestler", "The Wright Idea", "The Wrong Birds", "The Wrong Guy", "The Wrong Man", "The Wrong Road", "The Wrong Woman", "The Wrongdoers", "The Wyoming Bandit", "The Wyoming Wildcat", "The X-Files", "The X-Files: I Want to Believe", "The Yakuza", "The Yankee Clipper", "The Yankee Consul", "The Yankee Doodle Mouse", "The Yankee Señor", "The Yanks Are Coming", "The Yards", "The Year of the Comet", "The Year of the Yao", "The Yearling", "The Yellow Back", "The Yellow Cab Man", "The Yellow Canary", "The Yellow Mountain", "The Yellow Rose of Texas", "The Yellow Stain", "The Yellow Ticket", "The Yellow Tomahawk", "The Yes Men", "The Yin and the Yang of Mr. Go", "The Yoke's on Me", "The Yosemite Trail", "The Young and the Brave", "The Young Black Stallion", "The Young Captives", "The Young Diana", "The Young Doctors", "The Young Don't Cry", "The Young Guns", "The Young in Heart", "The Young Land", "The Young Lions", "The Young Lovers", "The Young Messiah", "The Young Millionaire", "The Young One", "The Young Philadelphians", "The Young Rajah", "The Young Savages", "The Young Swingers", "The Young Victoria", "The Younger Brothers", "The Younger Generation", "The Youngest Profession", "The Zero Hour", "The Zeros", "The Zodiac Killer", "The Zookeeper's Wife", "The Zoot Cat", "Their Big Moment", "Their Hour", "Their Mad Moment", "Their Social Splash", "Their Worldly Goods", "Thelma & Louise", "Thelma", "Them Was the Happy Days!", "Them!", "Theodora Goes Wild", "Theodore Rex", "There Goes Kelly", "There Goes My Girl", "There Goes My Heart", "There Goes the Groom", "There Goes the Neighborhood", "There Was a Crooked Man...", "There Will Be Blood", "There You Are!", "Theremin: An Electronic Odyssey", "There's Always a Woman", "There's Always Tomorrow", "There's Always Vanilla", "There's No Business Like Show Business", "There's Nothing Out There", "There's One Born Every Minute", "There's Something About a Soldier", "There's Something About Mary", "These Glamour Girls", "These Thousand Hills", "These Three", "These Wilder Years", "They All Come Out", "They All Kissed the Bride", "They All Laughed", "They Call It Sin", "They Call Me Bruce?", "They Call Me MISTER Tibbs!", "They Came to Blow Up America", "They Came to Cordura", "They Came to Rob Las Vegas", "They Came Together", "They Dare Not Love", "They Died with Their Boots On", "They Drive by Night", "They Gave Him a Gun", "They Got Me Covered", "They Had to See Paris", "They Just Had to Get Married", "They Knew What They Wanted", "They Learned About Women", "They Like 'Em Rough", "They Live by Night", "They Live in Fear", "They Live", "They Made Me a Criminal", "They Made Me a Killer", "They Met in a Taxi", "They Met in Argentina", "They Met in Bombay", "They Might Be Giants", "They Never Come Back", "They Only Kill Their Masters", "They Raid by Night", "They Rode West", "They Saved Hitler's Brain", "They Shall Have Music", "They Shoot Horses, Don't They?", "They Still Call Me Bruce", "They Stooge to Conga", "They Wanted to Marry", "They Were Expendable", "They Were So Young", "They Won't Believe Me", "They Won't Forget", "They", "Thick as Thieves", "Thicker than Water", "Thief in the Dark", "Thief of Damascus", "Thief of Hearts", "Thief", "Thieves' Clothes", "Thieves Fall Out", "Thieves' Highway", "Thieves Like Us", "Thieves", "Thin Ice", "Things Are Tough All Over", "Things to Do in Denver When You're Dead", "Things We Lost in the Fire", "Things You Can Tell Just by Looking at Her", "Think Fast, Mr. Moto", "Think Like a Man Too", "Think Like a Man", "Thinner", "Third Class Male", "Third Finger, Left Hand", "Third Man on the Mountain", "Thirteen Conversations About One Thing", "Thirteen Days", "Thirteen Ghosts", "Thirteen Hours by Air", "Thirteen Women", "Thirteen", "Thirty Day Princess", "Thirty Days", "Thirty Seconds over Tokyo", "This Above All", "This Ain't Bebop", "This Angry Age", "This Boy's Life", "This Could Be the Night", "This Day and Age", "This Earth Is Mine", "This Film Is Not Yet Rated", "This Girl's Life", "This Gun for Hire", "This Happy Feeling", "This Hero Stuff", "This Is 40", "This Is America", "This Is Cinerama", "This Is Elvis", "This Is Heaven", "This Is My Affair", "This Is My Life", "This Is My Love", "This Is Spinal Tap", "This Is th' Life", "This Is the Army", "This Is the End", "This Is the Life", "This Is the Night", "This Is Where I Leave You", "This Island Earth", "This Land Is Mine", "This Love of Ours", "This Mad World", "This Man Is Mine", "This Man's Navy", "This Marriage Business", "This Means War", "This Modern Age", "This One's for the Ladies", "This Property Is Condemned", "This Rebel Breed", "This Reckless Age", "This Side of Heaven", "This Side of the Law", "This Sporting Age", "This Thing Called Love", "This Thing of Ours", "This Time for Keeps", "This Way Please", "This Woman is Dangerous", "This Woman is Mine", "This Woman", "This World, Then the Fireworks", "Thomas & the Magic Railroad", "Thomasine & Bushrod", "Thor", "Thor: Ragnarok", "Thor: The Dark World", "Thorns and Orange Blossoms", "Thoroughbreds Don't Cry", "Thoroughbreds", "Thoroughly Modern Millie", "Those Awful Hats", "Those Boys!", "Those Calloways", "Those Endearing Young Charms", "Those High Grey Walls", "Those Lips, Those Eyes", "Those Love Pangs", "Those Redheads from Seattle", "Those She Left Behind", "Those Three French Girls", "Those We Love", "Those Were the Days!", "Those Who Dance", "Those Who Dare", "Those Who Judge", "Thou Shalt Not", "Thousands Cheer", "Threat", "Three Ages", "Three Amigos", "Three Arabian Nuts", "Three Bad Sisters", "Three Billboards Outside Ebbing, Missouri", "Three Bites of the Apple", "Three Blind Mice", "Three Blondes in His Life", "Three Brave Men", "Three Came Home", "Three Came to Kill", "Three Cheers for Love", "Three Cheers for the Irish", "Three Coins in the Fountain", "Three Comrades", "Three Daring Daughters", "Three Days of the Condor", "Three Desperate Men", "Three Faces East", "Three Faces West", "Three Fingered Jenny", "Three for Bedroom \"C\"", "Three for the Road", "Three for the Show", "Three Friends", "Three Fugitives", "Three Girls About Town", "Three Girls Lost", "Three Godfathers", "Three Guys Named Mike", "Three Hams on Rye", "Three Hearts for Julia", "Three Hours to Kill", "Three Husbands", "Three in Exile", "Three in the Attic", "Three in the Saddle", "Three Is a Family", "Three Jumps Ahead", "Three Kids and a Queen", "Three Kings", "Three Little Bops", "Three Little Girls in Blue", "Three Little Pigs", "Three Little Pirates", "Three Little Sew and Sews", "Three Little Sisters", "Three Little Twirps", "Three Little Words", "Three Live Ghosts", "Three Loan Wolves", "Three Loves Has Nancy", "Three Married Men", "Three Men and a Baby", "Three Men and a Girl", "Three Men and a Little Lady", "Three Men on a Horse", "Three Miles Out", "Three O'Clock High", "Three O'Clock in the Morning", "Three of a Kind", "Three of Hearts", "Three on a Couch", "Three on a Match", "Three on a Ticket", "Three on the Trail", "Three Pests in a Mess", "Three Russian Girls", "Three Sailors and a Girl", "Three Sappy People", "Three Secrets", "Three Sinners", "Three Smart Girls Grow Up", "Three Smart Girls", "Three Sons o' Guns", "Three Sons", "Three Steps North", "Three Strangers", "Three Stripes in the Sun", "Three Texas Steers", "Three the Hard Way", "Three to Tango", "Three Tough Guys", "Three Violent People", "Three Weekends", "Three Weeks in Paris", "Three Weeks", "Three Who Loved", "Three Who Paid", "Three Wise Fools", "Three Wise Girls", "Three Wishes", "Three Women", "Three Young Texans", "Three-Cornered Moon", "Three-Ring Marriage", "Three's a Crowd", "Threesome", "Thrill of a Lifetime", "Thrill of a Romance", "Thrill of Youth", "Thrilling Youth", "Through a Glass Window", "Through the Back Door", "Through the Breakers", "Through the Dark", "Through the Wrong Door", "Throw a Saddle on a Star", "Throw Momma from the Train", "Thru Different Eyes", "Thru the Moebius Strip", "Thumb Tripping", "Thumbelina", "Thumbs Down", "Thumbs Up", "Thumbsucker", "Thunder Afloat", "Thunder Alley", "Thunder and Lightning", "Thunder Bay", "Thunder Below", "Thunder Birds", "Thunder in Carolina", "Thunder in God's Country", "Thunder in the Desert", "Thunder in the East", "Thunder in the Night", "Thunder in the Pines", "Thunder in the Sun", "Thunder in the Valley", "Thunder Island", "Thunder Mountain", "Thunder on the Hill", "Thunder Over Arizona", "Thunder Over Texas", "Thunder Over the Plains", "Thunder Prince", "Thunder Riders", "Thunder Road", "Thunder Town", "Thunder Trail", "Thunder", "Thunderbirds", "Thunderbolt and Lightfoot", "Thunderbolt", "Thunderbolts of Fate", "Thundercrack!", "Thundergate", "Thunderhead, Son of Flicka", "Thunderheart", "Thunderhoof", "Thundering Caravans", "Thundering Dawn", "Thundering Gun Slingers", "Thundering Hoofs", "Thundering Jets", "Thundering Romance", "Thundering Trails", "Thunderstruck", "Thus is Life", "THX 1138", "Thy Name Is Woman", "Thy Neighbor's Wife", "Tick Tock Tuckered", "Ticket to New Year's", "Ticket to Paradise", "Tickle Me", "Tide of Empire", "Tides of Passion", "Tiger Fangs", "Tiger Love", "Tiger Orange", "Tiger Rose", "Tiger Shark", "Tiger Thompson", "Tigerland", "Tigershark", "Tight Shoes", "Tight Spot", "Tightrope", "'Til There Was You", "'Til We Meet Again", "Till I Come Back to You", "Till the Clouds Roll By", "Till the End of Time", "Till There Was You", "Till We Meet Again", "Tillie and Gus", "Tillie", "Tillie's Punctured Romance", "Tilt", "Timber Fury", "Timber Queen", "Timber", "Timberjack", "Timbuktu", "Time After Time", "Time and Tide", "Time Bomb", "Time Changer", "Time Chasers", "Time Is Money", "Time Limit", "Time of Love", "Time Out for Murder", "Time Out for Rhythm", "Time Out for Romance", "Time Out of Mind", "Time Table", "Time to Kill", "Time to Love", "Time Travelers", "Time Walker", "Time, the Comedian", "Timecode", "Timecop", "Timeline", "Timequest", "Timerider: The Adventure of Lyle Swann", "Times Have Changed", "Times Square Lady", "Times Square Playboy", "Times Square", "Timid Tabby", "Timothy's Quest", "Tin Cup", "Tin Gods", "Tin Hats", "Tin Men", "Tin Pan Alley", "Tin Toy", "Tinkering with Trouble", "Tiny Toon Adventures: How I Spent My Vacation", "Tip on a Dead Jockey", "Tip-Off Girls", "Tipped Off", "Tish", "Titan A.E.", "Titanic 3D", "Titanic", "Titicut Follies", "Titus", "T-Men", "TMNT 2: Out of the Shadows", "TMNT", "To Be Called For", "To Be or Not to Be", "To Beat the Band", "To Beep or Not to Beep", "To Brave Alaska", "To Catch a Thief", "To Die For", "To Each His Own", "To End All Wars", "To Find a Man", "To Fly!", "To Gillian on Her 37th Birthday", "To Grandmother's House We Go", "To Have and Have Not", "To Have and to Hold", "To Hell and Back", "To Kill a Mockingbird", "To Live and Die in L.A.", "To Mary With Love", "To Melody a Soul Responds", "To Please a Lady", "To Rent Furnished", "To Sir, with Love II", "To Sleep with Anger", "To the Ends of the Earth", "To the Ladies", "To the Last Man", "To the Limit", "To the People of the United States", "To the Shores of Iwo Jima", "To the Shores of Tripoli", "To the Victor", "To Trap a Spy", "To Wong Foo, Thanks for Everything! Julie Newmar", "To Young to Marry", "Toad Warrior", "Tobacco Road", "Tobor the Great", "Tobruk", "Toby Tyler", "Toby's Bow", "Toccata for Toy Trains", "Today I Hang", "Today We Live", "Today", "Together Again", "Together Alone", "Together We Live", "Toilers of the Sea", "Tokyo After Dark", "Tokyo File 212", "Tokyo Joe", "Tokyo Rose", "Tol'able David", "Told in the Hills", "Tom & Viv", "Tom and Chérie", "Tom and His Pals", "Tom and Huck", "Tom and Jerry in the Hollywood Bowl", "Tom and Jerry: The Movie", "Tom Brown of Culver", "Tom Brown's School Days", "Tom Horn", "Tom Mix in Arabia", "Tom Sawyer", "Tom Sawyer, Detective", "Tom Thumb", "Tom, Dick and Harry", "Tom, Tom, the Piper's Son", "Tomahawk Trail", "Tomahawk", "Tomb Raider", "Tomboy", "Tombstone", "Tombstone, the Town Too Tough to Die", "Tomcats", "Tommy Boy", "Tommy Tricker and the Stamp Traveller", "Tomorrow and Tomorrow", "Tomorrow at Seven", "Tomorrow Is Another Day", "Tomorrow Is Forever", "Tomorrow Is Today", "Tomorrow You're Gone", "Tomorrow", "Tomorrow, the World!", "Tomorrowland", "Tomorrow's Love", "Tomorrow's Youth", "Tom's Photo Finish", "Tongues of Flame", "Tongues Untied", "Tonight and Every Night", "Tonight at Twelve", "Tonight for Sure", "Tonight Is Ours", "Tonight or Never", "Tonight We Raid Calais", "Tonight We Sing", "Tonka", "Tony Rome", "Tony Runs Wild", "Too Busy to Work", "Too Good to Be True", "Too Hot to Handle", "Too Late Blues", "Too Late for Tears", "Too Late the Hero", "Too Many Blondes", "Too Many Cooks", "Too Many Crooks", "Too Many Girls", "Too Many Husbands", "Too Many Kisses", "Too Many Parents", "Too Many Winners", "Too Many Wives", "Too Many Women", "Too Much Beef", "Too Much Business", "Too Much Harmony", "Too Much Johnson", "Too Much Money", "Too Much Sun", "Too Much Wife", "Too Much, Too Soon", "Too Tough to Kill", "Too Young to Kiss", "Too Young to Know", "Toot, Whistle, Plunk and Boom", "Tooth Fairy", "Toothless", "Tootsie", "Top Banana", "Top Dog", "Top Five", "Top Gun", "Top Hat", "Top Man", "Top o' the Morning", "Top of the Town", "Top of the World", "Top Secret Affair", "Top Secret!", "Top Sergeant", "Top Speed", "Topaz", "Topaze", "Topeka", "Topkapi", "Topper Returns", "Topper Takes a Trip", "Topper", "Tops with Pops", "Topsy and Bunker: The Cat Killers", "Topsy and Eva", "Tora! Tora! Tora!", "Torch Singer", "Torch Song Trilogy", "Torch Song", "Torchy Blane in Chinatown", "Torchy Blane in Panama", "Torchy Blane... Playing with Dynamite", "Torchy Gets Her Man", "Torchy Runs for Mayor", "Torment", "Tormented", "Torn Curtain", "Tornado Range", "Tornado!", "Tornado", "Torpedo Alley", "Torpedo Boat", "Torpedo Run", "Torque", "Torrent", "Torrid Zone", "Tortilla Flaps", "Tortilla Flat", "Tortilla Soup", "Tortoise Beats Hare", "Torture Garden", "Tot Watchers", "Total Recall", "Totally F***ed Up", "Toton the Apache", "Touch and Go", "Touch Me", "Touch of Evil", "Touch", "Touchback", "Touchdown", "Touchdown, Army", "Tough Assignment", "Tough Enough", "Tough Guy", "Tough Guys Don't Dance", "Tough Guys", "Tougher Than Leather", "Toughest Gun in Tombstone", "Toughest Man Alive", "Toughest Man in Arizona", "Tourist Trap", "Tovarich", "Toward the Unknown", "Tower Heist", "Tower of London", "Tower of Terror", "Town & Country", "Town Without Pity", "Toy Soldiers", "Toy Story 2", "Toy Story 3", "Toy Story", "Toys in the Attic", "Toys", "Tracers", "Traces of Red", "Traci Townsend", "Track 29", "Track of the Cat", "Track of the Moon Beast", "Track of Thunder", "Tracked by the Police", "Tracked in the Snow Country", "Tracked to Earth", "Tracked", "Tracks", "Trade Winds", "Trader Horn", "Trader Tom of the China Seas", "Trading Places", "Traffic in Crime", "Traffic in Hearts", "Traffic", "Traffik", "Tragic Love", "Trail Dust", "Trail Guide", "Trail of Kit Carson", "Trail of Robin Hood", "Trail of Terror", "Trail of the Mounties", "Trail of the Rustlers", "Trail of the Vigilantes", "Trail of the Yukon", "Trail of Vengeance", "Trail Riders", "Trail Street", "Trail to Gunsight", "Trail to Laredo", "Trail to Mexico", "Trail to San Antone", "Trail to Vengeance", "Trailin' Trouble", "Trailin' West", "Trailing Danger", "Trailing Trouble", "Trails End", "Train to Alcatraz", "Train to Tombstone", "Training Day", "Trainwreck", "Traitor", "Tramp, Tramp, Tramp", "Tramp's Nap Interrupted", "Trance and Dance in Bali", "Trancers", "Transamerica", "Transatlantic Merry-Go-Round", "Transatlantic", "Transcendence", "Transcontinental Limited", "Transformers", "Transformers: Age of Extinction", "Transformers: Dark of the Moon", "Transformers: Revenge of the Fallen", "Transformers: The Last Knight", "Transgression", "Transient Lady", "Transporter 2", "Transylvania 6-5000", "Trantasia", "Trap Happy", "Trapeze Disrobing Act", "Trapeze", "Trapped by Boston Blackie", "Trapped by G-Men", "Trapped by Television", "Trapped in Paradise", "Trapped", "Trash", "Travelin' On", "Traveling Husbands", "Traveling Saleslady", "Traveller", "Travels with My Aunt", "Treachery Rides the Range", "Treason", "Treasure Island", "Treasure of Matecumbe", "Treasure of Monte Cristo", "Treasure of Ruby Hills", "Treasure of the Golden Condor", "Treasure Planet", "Treat 'Em Rough", "Trees Lounge", "Trekkies", "Tremors 2: Aftershocks", "Tremors", "Trenchcoat", "Trent's Last Case", "Trespass Against Us", "Trespass", "Trial and Error", "Trial by Jury", "Trial Without Jury", "Trial", "Tribes", "Tribute to a Bad Man", "Trick for Trick", "Trick or Treat", "Trick 'r Treat", "Trifling with Honor", "Trifling Women", "Trigger Fingers", "Trigger Law", "Trigger Trail", "Trigger Tricks", "Trigger, Jr.", "Triggerman", "Trilby", "Trilogy of Terror II", "Trilogy", "Trimmed in Scarlet", "Trimmed", "Trinity and Beyond", "Tripfall", "Triple 9", "Triple Action", "Triple Bogey on a Par Five Hole", "Triple Crossed", "Triple Justice", "Triple Threat", "Triple Trouble", "Triplet Trouble", "Tripoli", "Trippin'", "Tristan & Isolde", "Triumph of the Spirit", "Triumph", "Trixie from Broadway", "Trocadero", "Trojan War", "Troll 2", "Troll 3", "Troll", "Tromeo and Juliet", "Tron", "Tron: Legacy", "Troop Beverly Hills", "Troop Train", "Trooper 77", "Trooper Hook", "Trooper O'Neill", "Troopers Three", "Tropic Fury", "Tropic Holiday", "Tropic Madness", "Tropic Thunder", "Tropic Zone", "Tropical Heat Wave", "Tropical Nights", "Tropical Snow", "Trouble Along the Way", "Trouble Chasers", "Trouble for Two", "Trouble in Hogan's Alley", "Trouble in Mind", "Trouble in Morocco", "Trouble in Paradise", "Trouble in Sundown", "Trouble Makers", "Trouble Man", "Trouble Preferred", "Trouble the Water", "Trouble with the Curve", "Trouble", "Troublemakers", "Troubles of a Bride", "Trouping with Ellen", "Trout Fishing, Landing Three Pounder", "Troy", "Truck Busters", "Truck Turner", "Trucker's Woman", "True Adolescents", "True as Steel", "True Believer", "True Colors", "True Confession", "True Confessions", "True Crime", "True Grit", "True Heart Susie", "True Heaven", "True Identity", "True Lies", "True Love Never Runs Smooth", "True Love", "True Nobility", "True Romance", "True Stories", "True Story", "True to Life", "True to the Army", "True to the Navy", "True Western Hearts", "Truman", "Trumpin' Trouble", "Trunk to Cairo", "Trust the Man", "Trust", "Truth or Consequences, N.M.", "Truth or Dare", "'Truxton King", "Try and Get It", "Trying to Get Arrested", "Tubby the Tuba", "Tuck Everlasting", "Tucker: The Man and His Dream", "Tucson (film)", "Tucson Raiders", "Tuff Turf", "Tugboat Annie Sails Again", "Tugboat Annie", "Tugboat Princess", "Tulip Fever", "Tulips Shall Grow", "Tully", "Tulsa", "Tumbleweed Trail", "Tumbleweed", "Tumbleweeds", "Tumbling River", "Tumbling Tumbleweeds", "Tummy Trouble", "Tuna Clipper", "Tune in Tomorrow", "Tunisian Victory", "Tunnel Vision", "Tupac: Resurrection", "Turbo", "Turbo: A Power Rangers Movie", "Turbulence", "Turistas", "Turk 182", "Turkish Dance", "Turkish Delight", "Turn Back the Clock", "Turn It Up", "Turn Off the Moon", "Turn to the Right", "Turnabout", "Turner & Hooch", "Turning the Tables", "Turtle Diary", "Tusk", "Tuxedo Junction", "Tweetie Pie", "Tweety and the Beanstalk", "Twelve Hours to Kill", "Twelve in a Barrel", "Twelve Miles Out", "Twelve Monkeys", "Twelve O'Clock High", "Twelve", "Twentieth Century", "Twenty Bucks", "Twenty Dollars a Week", "Twenty Million Sweethearts", "Twenty Minutes of Love", "Twenty Plus Two", "Twenty-One", "Twice Blessed", "Twice in a Lifetime", "Twice Upon a Time", "Twice-Told Tales", "Twilight for the Gods", "Twilight in the Sierras", "Twilight of Honor", "Twilight on the Prairie", "Twilight on the Rio Grande", "Twilight on the Trail", "Twilight Zone: The Movie", "Twilight", "Twilight's Last Gleaming", "Twin Beds", "Twin Falls Idaho", "Twin Peaks: Fire Walk with Me", "Twins of Suffering Creek", "Twins", "Twist Around the Clock", "Twisted Desire", "Twisted Obsession", "Twisted Triggers", "Twisted", "Twister", "Two Against the World", "Two Alone", "Two Arabian Knights", "Two Bits", "Two Blondes and a Redhead", "Two Can Play That Game", "Two Can Play", "Two Days", "Two Evil Eyes", "Two Fisted Justice", "Two Flags West", "Two for the Money", "Two for the Seesaw", "Two for Tonight", "Two Gals and a Guy", "Two Girls and a Sailor", "Two Girls on Broadway", "Two Girls Wanted", "Two Gun Law", "Two Guns and a Badge", "Two Guys from Milwaukee", "Two Guys from Texas", "Two Heads on a Pillow", "Two in a Crowd", "Two in a Taxi", "Two in Revolt", "Two in the Dark", "Two Kinds of Women", "Two Latins from Manhattan", "Two Little Indians", "Two Little Vagabonds; or, The Pugilistic Parson", "Two Lost Worlds", "Two Lovers", "Two Loves", "Two Men and a Maid", "Two Minutes to Play", "Two Moon Junction", "Two Much", "Two Mules for Sister Sara", "Two O'Clock Courage", "Two of a Kind", "Two Old Sparks", "Two on a Guillotine", "Two Outlaws", "Two People", "Two Rode Together", "Two Rubes at the Theatre", "Two Scrambled", "Two Seats at the Opera", "Two Seconds", "Two Senoritas from Chicago", "Two Shall Be Born", "Two Sinners", "Two Sisters from Boston", "Two Sisters", "Two Smart People", "Two Solitudes", "Two Thousand Maniacs!", "Two Tickets to Broadway", "Two Tickets to London", "Two Weeks in Another Town", "Two Weeks Notice", "Two Weeks Off", "Two Weeks to Live", "Two Weeks with Love", "Two Weeks", "Two Wise Maids", "Two Women", "Two Yanks in Trinidad", "Two Years Before the Mast", "Two-Dollar Bettor", "Two-Faced Woman", "Two-Fisted Gentleman", "Two-Fisted Jones", "Two-Fisted Law", "Two-Fisted Sheriff", "Two-Fisted Stranger", "Two-Fisted", "Two-Gun Gussie", "Two-Gun Man from Harlem", "Two-Lane Blacktop", "Two-Man Submarine", "Two-Minute Warning", "Tycoon", "Tying the Knot", "Tyler Perry Presents Peeples", "Tyler Perry's Acrimony", "Typhoon", "Tyrant of Red Gulch", "Tyrant of the Sea", "Tyson", "U Turn", "U.S. Marshals", "U2 3D", "U-571", "U-Boat Prisoner", "UFO: Target Earth", "UHF", "Ulee's Gold", "Ultraviolet", "Ulzana's Raid", "Unaccompanied Minors", "Unashamed", "Unbreakable", "Unbroken", "Unbroken: Path to Redemption", "Uncertain Glory", "Uncertain Lady", "Unchained", "Uncharted Channels", "Uncharted Seas", "Uncivil War Birds", "Uncle Buck", "Uncle Drew", "Uncle Joe Shannon", "Uncle Joe", "Uncle Meat", "Uncle Nino", "Uncle Tom's Cabin (1914 film)", "Uncommon Valor", "Unconquered Bandit", "Unconquered", "Undefeatable", "Under a Texas Moon", "Under Age", "Under Arizona Skies", "Under California Stars", "Under Colorado Skies", "Under Cover of Night", "Under Crimson Skies", "Under Eighteen", "Under Fire", "Under Mexicali Stars", "Under My Skin", "Under Nevada Skies", "Under Oath", "Under Pressure", "Under Siege 2: Dark Territory", "Under Siege", "Under Suspicion", "Under the Big Top", "Under the Black Eagle", "Under the Cherry Moon", "Under the Greenwood Tree", "Under the Pampas Moon", "Under the Rainbow", "Under the Red Robe", "Under the Rouge", "Under the Silver Lake", "Under the Southern Cross", "Under the Tonto Rim", "Under the Top", "Under the Tuscan Sun", "Under the Volcano", "Under the Yoke", "Under the Yum Yum Tree", "Under Two Flags", "Under Western Skies", "Under Western Stars", "Under Wraps", "Under Your Spell", "Underclassman", "Undercover Blues", "Undercover Brother", "Undercover Doctor", "Undercover Girl", "Undercover Maisie", "Undercover Man", "Under-Cover Man", "Undercurrent", "Underdog", "Underground", "Underneath", "Undertow", "Underwater Warrior", "Underwater!", "Underworld U.S.A.", "Underworld", "Underworld: Awakening", "Underworld: Blood Wars", "Underworld: Evolution", "Underworld: Rise of the Lycans", "Undiscovered", "Undisputed", "Undoing", "Undressed", "Unexpected Father", "Unexpected Guest", "Unexpected Uncle", "Unfaithful", "Unfaithfully Yours", "Unfinished Business", "Unforgettable", "Unforgiven", "Unfriended", "Unfriended: Dark Web", "Unguarded Women", "Unholy Love", "Unholy Partners", "Unhook the Stars", "UnHung Hero", "Unidentified", "Uninvited (1988 film)", "Union Depot", "Union Pacific", "Union Station", "United 93", "Universal Soldier", "Universal Soldier: The Return", "University Heights", "Unknown Island", "Unknown Love", "Unknown Valley", "Unknown Woman", "Unknown World", "Unknown", "Unlawful Entry", "Unlikely Angel", "Unlocked", "Unmarried Wives", "Unmarried", "Unmasked", "Unrest", "Unsane", "Unseeing Eyes", "Unseen Enemy", "Unseen Forces", "Unseen Hands", "Unstoppable", "Unsupersize Me", "Untamed Frontier", "Untamed Fury", "Untamed Heart", "Untamed Heiress", "Untamed Women", "Untamed Youth", "Untamed", "Until September", "Until They Sail", "Unto the Third Generation", "Unto the Weak", "Untraceable", "Unzipped", "Up and at 'Em", "Up and Going", "Up at the Villa", "Up Close & Personal", "Up for Murder", "Up from the Beach", "Up Front", "Up Goes Maisie", "Up in Arms", "Up in Central Park", "Up in Mabel's Room", "Up in Smoke", "Up in the Air", "Up in the Cellar", "Up Periscope", "Up Pops the Devil", "Up the Academy", "Up the Creek", "Up the Down Staircase", "Up the Ladder", "Up the River", "Up the Sandbox", "Up!", "Up", "Upgrade", "Upper Falls of the Yellowstone", "Upper World", "Uprising", "Upstage", "Upstairs and Down", "Upstairs", "Upstream Color", "Upstream", "Uptight", "Uptown Girls", "Uptown New York", "Uptown Saturday Night", "Uranium Boom", "Urban Cowboy", "Urban Legend", "Urban Legends: Final Cut", "Used Cars", "Used People", "Utah Blaine", "Utah Wagon Train", "Utah", "V for Vendetta", "V/H/S", "V/H/S/2", "Vacancy", "Vacation Days", "Vacation from Love", "Vacation in Reno", "Vacationland", "Vagabond Lady", "Vagabond Luck", "Valdez Is Coming", "Valencia", "Valentine", "Valentine's Day", "Valentino", "Valerian and the City of a Thousand Planets", "Valerie", "Valiant Is the Word for Carrie", "Valkyrie", "Valley Girl", "Valley of Fear", "Valley of Fire", "Valley of Hunted Men", "Valley of the Dolls", "Valley of the Giants", "Valley of the Head Hunters", "Valley of the Kings", "Valley of the Redwoods", "Valley of the Zombies", "Valley of Vengeance", "Valmont", "Vamp", "Vamping Venus", "Vampire Academy", "Vampire in Brooklyn", "Vampire Journals", "Vampire's Kiss", "Vampires Suck", "Vampires", "Van Helsing", "Vanaja", "Vanessa: Her Love Story", "Vanilla Sky", "Vanishing Point", "Vanity Fair", "Vanity Street", "Vanity", "Vanity's Price", "Vantage Point", "Vanya on 42nd Street", "Varieties on Parade", "Variety Girl", "Varsity Blues", "Varsity Show", "Varsity", "V-Day: Until the Violence Stops", "Vegas Vacation", "Vehicle 19", "Velocity Trap", "Velvet Smooth", "Vendetta", "Vengeance of Rannah", "Vengeance Valley", "Venom", "Venus in the East", "Venus Makes Trouble", "Venus of the South Seas", "Vera Cruz", "Verboten!", "Vernon, Florida", "Veronica Guerin", "Veronica Mars", "Veronika Decides to Die", "Vertical Limit", "Vertigo", "Very Bad Things", "Very Nice, Very Nice", "Very Truly Yours", "Vibes", "Vice Raid", "Vice Squad", "Vice Versa", "Vice", "Vicki", "Vicky Cristina Barcelona", "Victim of Love", "Victim of the Brain", "Victor Frankenstein", "Victor Victoria", "Victor", "Victoria & Abdul", "Victory at Entebbe", "Victory Through Air Power", "Victory", "Video from Hell", "Videodrome", "Viennese Nights", "View from the Top", "Vigil in the Night", "Vigilante Force", "Vigilante Hideout", "Vigilante Terror", "Vigilante", "Vigilantes of Boomtown", "Vigilantes of Dodge City", "Villa Rides", "Villa!!", "Village of the Damned", "Village Tale", "Villain", "Vincent & Theo", "Violated Paradise", "Violence", "Violent Road", "Violent Saturday", "Violet & Daisy", "Violets Are Blue", "Virgin Lips", "Virginia City", "Virginia", "Virginian Outcast", "Virtue", "Virtuosity", "Virtuous Liars", "Virus", "Vision Quest", "Visioneers", "Visions of Eight", "Visions of Light", "Visit to a Small Planet", "Visiting Hours", "Viva Cisco Kid", "Viva Knievel!", "Viva Las Vegas", "Viva Max!", "Viva Villa!", "Viva Zapata!", "Vivacious Lady", "Viviana", "Vixen!", "Voces inocentes", "Voice in the Mirror", "Voice in the Night", "Voice in the Wind", "Voice of the Whistler", "Voices of Iraq", "Volcano!", "Volcano", "Voltaire", "Volunteers", "Von Richthofen and Brown", "Von Ryan's Express", "Voodoo Dawn", "Voodoo Island", "Voodoo Man", "Voodoo Tiger", "Voodoo Woman", "Voyage to the Bottom of the Sea", "Voyage to the Planet of Prehistoric Women", "W", "W.", "W.C. Fields and Me", "W.W. and the Dixie Dancekings", "Wabash Avenue", "Wabbit Twouble", "Wackiki Wabbit", "Waco", "Waco: The Rules of Engagement", "Wag the Dog", "Wages for Wives", "Wages of Virtue", "Wagon Master", "Wagon Team", "Wagon Tracks West", "Wagon Tracks", "Wagon Wheels Westward", "Wagon Wheels", "Wagons East!", "Wagons West", "Wagons Westward", "Wah Do Dem", "Waikiki Wedding", "Waist Deep", "Wait Till Jack Comes Home", "Wait till the Sun Shines, Nellie", "Wait Until Dark", "Wait Until Spring, Bandini", "Waiting at the Church", "Waiting for Forever", "Waiting for Guffman", "Waiting for Superman", "Waiting for the Moon", "Waiting to Exhale", "Waiting...", "Waitress!", "Waitress", "Wake Island", "Wake Me When It's Over", "Wake of Death", "Wake of the Red Witch", "Wake Up and Dream", "Wake Up and Live", "Wakefield", "Waking Life", "Waking the Dead", "Waking Up in Reno", "Waking Up the Town", "Wakko's Wish", "Walk a Crooked Mile", "Walk Don't Run", "Walk East on Beacon", "Walk Hard: The Dewey Cox Story", "Walk Like a Dragon", "Walk of Fame", "Walk of Shame", "Walk on the Wild Side", "Walk Softly, Stranger", "Walk Tall", "Walk the Angry Beach", "Walk the Line", "Walk the Proud Land", "Walk the Walk", "Walker", "Walking Across Egypt", "Walking and Talking", "Walking Back", "Walking Down Broadway", "Walking My Baby Back Home", "Walking on Air", "Walking Tall Part 2", "Walking Tall", "Walking the Edge", "Walking with Dinosaurs", "Walking with the Enemy", "Walky Talky Hawky", "Wall of Noise", "Wall Street", "Wall Street: Money Never Sleeps", "WALL-E", "Wallflower", "Wallflowers", "Walls of Gold", "Waltz with Bashir", "Wanda Nevada", "Wanderer of the Wasteland", "Wandering Daughters", "Wandering Fires", "Wandering Footsteps", "Wandering Girls", "Wandering Husbands", "Wanderlust", "Wanted - $5,000", "Wanted! Jane Turner", "Wanted", "Wanted: A Husband", "Wanted: Dead or Alive", "War and Peace", "War and Remembrance", "War Arrow", "War Babies", "War Comes to America", "War Correspondent", "War Dogs", "War Drums", "War for the Planet of the Apes", "War Hunt", "War Is Hell", "War Machine", "War Nurse", "War of the Colossal Beast", "War of the Satellites", "War of the Wildcats", "War of the Worlds", "War Paint", "War Party", "War Pigs", "War", "War, Inc.", "War/Dance", "Warcraft", "WarGames", "Warlock", "Warlock: The Armageddon", "Warm Bodies", "Warning Shot", "Warpath", "Warrior of the Lost World", "Warrior", "Warriors of Virtue", "Wartime Nutrition", "Washing Gold on 20 Above Hunker, Klondike", "Washington Melodrama", "Washington Merry-Go-Round", "Washington Square", "Washington Story", "Wasted Lives", "Watch It", "Watch on the Rhine", "Watch the Birdie", "Watch Your Step", "Watch Your Wife", "Watchers", "Watchmen", "Water for Elephants", "Water, Water Every Hare", "Waterfront at Midnight", "Waterfront Lady", "Waterfront", "Waterhole No. 3", "Waterloo Bridge", "Watermarks", "Watermelon Contest", "Watermelon Man", "Watermelon Patch", "Waterworld", "Watusi", "Wavelength", "Waxwork II: Lost in Time", "Waxwork", "Way Back Home", "Way Down East", "Way Down South", "Way for a Sailor", "Way of a Gaucho", "Way Out West", "Way...Way Out", "Wayne's World 2", "Wayne's World", "Wayward", "We Americans", "We Are Marshall", "We Are Not Alone", "We Are the Marines", "We Are the Strange", "We Are What We Are", "We Bought a Zoo", "We Can't Have Everything", "We Don't Live Here Anymore", "We Faw Down", "We Go Fast", "We Have Our Moments", "We Live Again", "We Live in Public", "We Moderns", "We Never Sleep", "We Own the Night", "We Steal Secrets: The Story of WikiLeaks", "We Want Our Mummy", "We Went to College", "We Were Dancing", "We Were Soldiers", "We Were Strangers", "We Who Are About to Die", "We Who Are Young", "Weapons of Mass Distraction", "Weary River", "Web of Danger", "Webs of Steel", "Wedding Bell Blues", "Wedding Crashers", "Wedding Present", "Wedding Procession in Cairo", "Wedding Rings", "Weddings and Babies", "Wednesday's Child", "Wee Willie Winkie", "Weeds", "Week End Husbands", "Week Ends Only", "Weekend at Bernie's II", "Weekend at Bernie's", "Week-End at the Waldorf", "Weekend for Three", "Week-End in Havana", "Week-End Marriage", "Week-End Pass", "Week-End with Father", "Weird Science", "Weird Woman", "Welcome Danger", "Welcome Home Roscoe Jenkins", "Welcome Home", "Welcome Home, Roxy Carmichael", "Welcome Stranger", "Welcome to Collinwood", "Welcome to Hard Times", "Welcome to L.A.", "Welcome to Marwen", "Welcome to Mooseport", "Welcome to Nollywood", "Welcome to Sarajevo", "Welcome to the Dollhouse", "Welcome to the Jungle", "Welcome to the Rileys", "We'll Never Have Paris", "Wells Fargo Gunmaster", "Wells Fargo", "We're Back! A Dinosaur's Story", "We're in the Legion Now!", "We're in the Money", "We're in the Navy Now", "We're No Angels", "We're Not Dressing", "We're Not Married!", "We're on the Jury", "We're Only Human", "We're Rich Again", "We're the Millers", "Werewolf of London", "Werewolf", "Werewolves on Wheels", "Wes Craven's New Nightmare", "West 47th Street", "West Bound Limited", "West Is Best", "West of Arizona", "West of Broadway", "West of Cheyenne", "West of Chicago", "West of Dodge City", "West of El Dorado", "West of Nevada", "West of Santa Fe", "West of Shanghai", "West of Singapore", "West of Sonora", "West of Texas", "West of the Alamo", "West of the Brazos", "West of the Divide", "West of the Law", "West of the Pecos", "West of the Rainbow's End", "West of the Rio Grande", "West of the Santa Fe", "West of the Water Tower", "West of Tombstone", "West of Wyoming", "West of Zanzibar", "West Point of the Air", "West Point Widow", "West Point", "West Side Story", "West to Glory", "Westbound Mail", "Westbound", "Western Courage", "Western Cyclone", "Western Feuds", "Western Frontier", "Western Gold", "Western Heritage", "Western Jamboree", "Western Luck", "Western Mail", "Western Pacific Agent", "Western Pluck", "Western Renegades", "Western Speed", "Western Trails", "Western Union", "Western Vengeance", "Western Yesterdays", "Westinghouse Works, 1904", "Westward Bound", "Westward Ho the Wagons!", "Westward Ho", "Westward Passage", "Westward the Women", "Westworld", "Wet Hot American Summer", "Wet Paint", "We've Never Been Licked", "Wharf Angel", "What a Blonde", "What a Girl Wants", "What a Life", "What a Man!", "What a Night!", "What a Way to Go!", "What a Widow!", "What a Wife Learned", "What a Woman!", "What About Bob?", "What Am I Bid?", "What Did You Do in the War, Daddy?", "What Do You Say to a Naked Lady?", "What Doesn't Kill You", "What Dreams May Come", "What Ever Happened to Aunt Alice?", "What Ever Happened to Baby Jane?", "What Every Woman Knows", "What Every Woman Learns", "What Every Woman Wants", "What Fools Men Are", "What Fools Men", "What Goes Up", "What Happened in the Tunnel", "What Happened to Jones", "What Happened to Mary?", "What Happened Was", "What Happens in Vegas", "What If...", "What Just Happened", "What Lies Beneath", "What Love Is", "What Makes a Battle", "What Men Want", "What Next, Corporal Hargrove?", "What Planet Are You From?", "What Price Beauty?", "What Price Crime", "What Price Decency", "What Price Glory?", "What Price Hollywood?", "What Price Innocence?", "What the Daisy Said", "What the Deaf Man Heard", "What They Had", "What to Expect When You're Expecting", "What We Do Is Secret", "What Wives Want", "What Women Want", "What You Mean We?", "What! No Beer?", "Whatever It Takes", "Whatever Works", "What's a Nice Girl Like You Doing in a Place Like This?", "What's Buzzin', Cousin?", "What's Cookin' Doc?", "What's Cookin'?", "What's Eating Gilbert Grape", "What's His Name", "What's Love Got to Do with It", "What's New Pussycat?", "What's Opera, Doc?", "What's So Bad About Feeling Good?", "What's the Matter with Helen?", "What's the Worst That Could Happen?", "What's Up, Doc?", "What's Up, Tiger Lily?", "What's Wrong with the Women?", "What's Wrong with Virginia", "What's Your Number?", "What's Your Reputation Worth?", "Wheel of Chance", "When a Fellow Needs a Friend", "When a Girl Loves", "When a Girl's Beautiful", "When a Man Loves a Woman", "When a Man Loves", "When a Man Rides Alone", "When a Man Sees Red", "When a Man's a Man", "When a Stranger Calls", "When a Woman Waits", "When Danger Smiles", "When Do We Eat?", "When Doctors Disagree", "When Eagles Strike", "When Fate Decides", "When Gangland Strikes", "When G-Men Step In", "When Harry Met Sally...", "When Hell Broke Loose", "When Husbands Flirt", "When I Came Home", "When I Grow Up", "When in Rome", "When Knighthood Was in Flower", "When Ladies Meet", "When Love Comes", "When Love Grows Cold", "When Love Is Young", "When Marnie Was There", "When Men Desire", "When My Baby Smiles at Me", "When Odds Are Even", "When Romance Rides", "When Strangers Appear", "When Strangers Marry", "When Strangers Meet", "When the Bough Breaks", "When the Clouds Roll By", "When the Daltons Rode", "When the Day Breaks", "When the Door Opened", "When the Heart Calls", "When the Law Rides", "When the Legends Die", "When the Lights Go on Again", "When the Redskins Rode", "When the Wife's Away", "When Time Ran Out", "When Tomorrow Comes", "When We Were Kings", "When We Were Twenty-One", "When Were You Born", "When Will I Be Loved", "When Willie Comes Marching Home", "When Worlds Collide", "When You Comin' Back, Red Ryder?", "When You Remember Me", "When You're in Love", "When You're Smiling", "When's Your Birthday?", "Where Angels Go, Trouble Follows", "Where Are My Children?", "Where Are Your Children?", "Where Danger Lives", "Where Did You Get That Girl?", "Where Do We Go from Here?", "Where Eagles Dare", "Where East is East", "Where Is My Wandering Boy Tonight?", "Where Is This West?", "Where It's At", "Where Love Has Gone", "Where the Boys Are '84", "Where the Boys Are", "Where the Buffalo Roam", "Where the Day Takes You", "Where the Heart Is", "Where the Lilies Bloom", "Where the Money Is", "Where the North Begins", "Where the Pavement Ends", "Where the Red Fern Grows", "Where the Sidewalk Ends", "Where the Toys Come From", "Where the Truth Lies", "Where the West Begins", "Where the Wild Things Are", "Where There's Life", "Where Was I?", "Where Were You When the Lights Went Out?", "Where's God When I'm S-Scared?", "Where's Poppa?", "Which Is Witch", "Which Way Is Up?", "Which Way to the Front?", "Which Woman?", "While Justice Waits", "While London Sleeps", "While Paris Sleeps", "While Satan Sleeps", "While the City Sleeps", "While the Patient Slept", "While There's Life", "While We're Young", "While You Were Sleeping", "Whip It", "Whiplash", "Whipped", "Whipsaw", "Whirlpool", "Whirlwind Raiders", "Whirlwind", "Whiskey Tango Foxtrot", "Whisper", "Whispering City", "Whispering Footsteps", "Whispering Ghosts", "Whispering Smith Speaks", "Whispering Smith", "Whispering Winds", "Whispering Wires", "Whispers in the Dark", "Whistle Stop", "Whistlin' Dan", "Whistling Hills", "Whistling in Brooklyn", "Whistling in the Dark", "White and Unmarried", "White Banners", "White Bondage", "White Boy Rick", "White Cargo", "White Chicks", "White Christmas", "White Comanche", "White Dog", "White Eagle", "White Fang 2: Myth of the White Wolf", "White Fang", "White Feather", "White Flannels", "White Gold", "White Hands", "White Heat", "White House Down", "White Hunter Black Heart", "White Hunter", "White Legion", "White Lies", "White Lightning", "White Line Fever", "White Man", "White Man's Burden", "White Men Can't Jump", "White Mice", "White Nights", "White Noise", "White Noise: The Light", "White of the Eye", "White Oleander", "White Palace", "White Pongo", "White Reindeer", "White Room", "White Sands", "White Savage", "White Shadows in the South Seas", "White Shoulders", "White Squall", "White Thunder", "White Tie and Tails", "White Tiger", "White Water Summer", "White Wilderness", "White Witch Doctor", "White Wolves: A Cry in the Wild II", "White Woman", "White Zombie", "Whiteboyz", "Whiteout", "Whitewashed Walls", "Whitney", "Who Are My Parents?", "Who Cares", "Who Cares?", "Who Do I Gotta Kill?", "Who Done It?", "Who Framed Roger Rabbit", "Who Is Harry Kellerman and Why Is He Saying Those Terrible Things About Me?", "Who Is Hope Schuyler?", "Who Is Killing the Great Chefs of Europe?", "Who Killed Aunt Maggie?", "Who Killed Doc Robbin", "Who Killed Gail Preston?", "Who Killed Mary What's 'Er Name?", "Who Killed the Electric Car?", "Who Killed Who?", "Who Said Watermelon?", "Who Was That Lady?", "Who Was the Man?", "Who Will Marry Me?", "Who'll Stop the Rain", "Wholly Moses", "Whom the Gods Destroy", "Whom the Gods Would Destroy", "Whoopee!", "Whore II", "Whore", "Who's Afraid of Virginia Woolf?", "Who's Been Sleeping in My Bed?", "Who's Got the Action?", "Who's Guilty?", "Who's Harry Crumb?", "Who's Minding the Mint?", "Who's Minding the Store?", "Who's That Girl", "Who's That Knocking at My Door", "Who's the Man?", "Who's Your Caddy?", "Who's Your Friend", "Whose Life Is It Anyway?", "Why Announce Your Marriage?", "Why Be Good?", "Why Change Your Wife?", "Why Did I Get Married Too?", "Why Did I Get Married?", "Why Do Fools Fall in Love", "Why Girls Go Back Home", "Why Girls Leave Home", "Why Girls Love Sailors", "Why Korea?", "Why Leave Home?", "Why Me?", "Why Men Leave Home", "Why Mr. Nation Wants a Divorce", "Why Must I Die?", "Why Pick on Me?", "Why Sailors Go Wrong", "Why Smith Left Home", "Why We Fight", "Why Women Love", "Why Worry?", "Why Would I Lie?", "Wichita", "Wicked City", "Wicked Little Things", "Wicked Stepmother", "Wicked Woman", "Wicked", "Wicked, Wicked", "Wickedness Preferred", "Wicker Park", "Wide Awake", "Wide Open Faces", "Wide Open", "Wideo Wabbit", "Widow by Proxy", "Widows' Peak", "Widows", "Wife in Name Only", "Wife Savers", "Wife vs. Secretary", "Wife Wanted", "Wife, Doctor and Nurse", "Wife, Husband and Friend", "Wigstock: The Movie", "Wild 90", "Wild America", "Wild and Wonderful", "Wild and Woolly", "Wild at Heart", "Wild Beauty", "Wild Bill Hickok Rides", "Wild Bill Hickok", "Wild Bill", "Wild Blood", "Wild Boys of the Road", "Wild Brian Kent", "Wild Card", "Wild Cargo", "Wild Company", "Wild Country", "Wild Geese Calling", "Wild Girl", "Wild Gold", "Wild Guitar", "Wild Harvest", "Wild Hearts Can't Be Broken", "Wild Heritage", "Wild Hogs", "Wild Honey", "Wild Horse Ambush", "Wild Horse Canyon", "Wild Horse Mesa", "Wild Horse Phantom", "Wild Horse Rodeo", "Wild Horse Round-Up", "Wild Horse Rustlers", "Wild Horse Stampede", "Wild Horse", "Wild in the Country", "Wild in the Streets", "Wild Is the Wind", "Wild Justice", "Wild Money", "Wild Oats Lane", "Wild on the Beach", "Wild Oranges", "Wild Orchid", "Wild Orchids", "Wild River", "Wild Rovers", "Wild Seed", "Wild Side", "Wild Stallion", "Wild Thing", "Wild Things", "Wild to Go", "Wild West Romance", "Wild West", "Wild Wild West", "Wild Women of Wongo", "Wild", "Wild, Wild Susan", "Wildcat Bus", "Wildcat Trooper", "Wildcats", "Wilder Napalm", "Wildfire", "Wildflower", "Wildlife", "Wildness of Youth", "Will Penny", "Will Success Spoil Rock Hunter?", "Willard", "Willie & Phil", "Willie Dynamite", "Willie Runs the Park", "Willoughby's Magic Hat", "Willow", "Willy McBean and his Magic Machine", "Willy the Sparrow", "Willy Wonka & the Chocolate Factory", "Willy/Milly", "Wilma", "Wilson", "Win a Date with Tad Hamilton!", "Win That Girl", "Win Win", "Winchester '73", "Winchester", "Wind Across the Everglades", "Wind Chill", "Wind River", "Wind", "Windjammer", "Window Water Baby Moving", "Windows", "Winds of Chance", "Winds of the Wasteland", "Winds of War", "Windsor Protocol", "Windtalkers", "Wine of Youth", "Wine", "Wine, Women and Horses", "Wine, Women and Song", "Wing and a Prayer", "Wing Commander", "Winged Victory", "Wings for the Eagle", "Wings for This Man", "Wings in the Dark", "Wings of Courage", "Wings of Danger", "Wings of the Hawk", "Wings of the Morning", "Wings of the Navy", "Wings of the Storm", "Wings of Youth", "Wings over Honolulu", "Wings Over the Pacific", "Wings", "Wink of an Eye", "Winnebago Man", "Winner Take All", "Winners of the West", "Winners of the Wilderness", "Winnie the Pooh and a Day for Eeyore", "Winnie the Pooh and the Honey Tree", "Winnie the Pooh", "Winnie the Pooh: Seasons of Giving", "Winning a Woman", "Winning of the West", "Winning the Futurity", "Winning with Wits", "Winning Your Wings", "Winning", "Winter Kills", "Winter Meeting", "Winter People", "Winter Soldier", "Winter Solstice", "Winter Wonderland", "Winter's Bone", "Winterset", "Wintertime", "Wired", "Wiretapper", "Wiring Pike in a Mill Stream", "Wisconsin Death Trip", "Wisdom", "Wise Blood", "Wise Girl", "Wise Guys", "Wish I Was Here", "Wish Upon a Star", "Wish Upon", "Wishmaster", "Witchboard", "Witchcraft II: The Temptress", "Witchcraft", "Witches' Brew", "Witching Hour", "With a Song in My Heart", "With All Deliberate Speed", "With Davy Crockett at the Fall of the Alamo", "With Honors", "With Love and Kisses", "With Six You Get Eggroll", "With the Marines at Tarawa", "With the Mounted Police", "With These Hands", "With This Ring", "Within Our Gates", "Within the Law", "Within These Walls", "Without a Paddle", "Without a Trace", "Without Children", "Without Compromise", "Without Evidence", "Without Fear", "Without Honor", "Without Limits", "Without Love", "Without Mercy", "Without Orders", "Without Regret", "Without Reservations", "Without Warning!", "Without Warning", "Without You I'm Nothing", "Witless Protection", "Witness for the Prosecution", "Witness to Murder", "Witness to the Execution", "Witness", "Wives and Lovers", "Wives Never Know", "Wives Under Suspicion", "Wizard of Oz", "Wizard of the Saddle", "Wizards", "Wolf Blood", "Wolf Dog", "Wolf Fangs", "Wolf Lake", "Wolf Larsen", "Wolf Law", "Wolf Riders", "Wolf Tracks", "Wolf", "Wolfen", "Wolfman", "Wolf's Clothing", "Wolves of the City", "Wolves of the Desert", "Wolves of the Night", "Wolves of the Range", "Woman Against the World", "Woman Against Woman", "Woman and the Hunter", "Woman Chases Man", "Woman Hungry", "Woman in Distress", "Woman in Gold", "Woman in Hiding", "Woman in the Dark", "Woman Obsessed", "Woman of Desire", "Woman of the North Country", "Woman of the Year", "Woman on the Run", "Woman on Top", "Woman They Almost Lynched", "Woman Thou Art Loosed", "Woman Times Seven", "Woman Trap", "Woman Walks Ahead", "Woman Wanted", "Woman Wise", "Woman, Woman!", "Womanhandled", "Womanpower", "Woman-Proof", "Woman's Honor", "Woman's Place", "Woman's World", "Woman-Wise", "Women and Gold", "Women Are Like That", "Women Are Trouble", "Women Everywhere", "Women First", "Women from Headquarters", "Women Go on Forever", "Women in Bondage", "Women in Cages", "Women in Prison", "Women in the Night", "Women in the Wind", "Women in War", "Women Love Once", "Women Must Dress", "Women of All Nations", "Women of Glamour", "Women of the Prehistoric Planet", "Women They Talk About", "Women Who Give", "Women Without Men", "Women Without Names", "Women Won't Tell", "Women's Prison", "Won by Wireless", "Won in the Clouds", "Won Ton Ton, the Dog Who Saved Hollywood", "Wonder Bar", "Wonder Boys", "Wonder Man", "Wonder of Women", "Wonder Wheel", "Wonder Woman", "Wonder", "Wonderful Trick Donkey, The", "Wonderful World", "Wonderland", "Wonderstruck", "Won't Back Down", "Won't You Be My Neighbor?", "Woo", "Wood for War", "Woodshock", "Woodstock", "Word Is Out", "Wordplay", "Words and Music by-", "Words and Music", "Work", "Working for Peanuts", "Working Girl", "Working Girls", "World and Time Enough", "World for Ransom", "World Gone Wild", "World in My Corner", "World Trade Center", "World War Z", "World Without End", "Worldly Goods", "World's Greatest Dad", "Worth Winning", "Wrath of the Titans", "Wrecking Crew", "Wreck-It Ralph", "Wrestling Ernest Hemingway", "Wristcutters: A Love Story", "Written on the Wind", "Wrong Cops", "Wrong Is Right", "Wrong Turn", "Wrong", "Wrongfully Accused", "WUSA", "Wuthering Heights", "Wyatt Earp", "Wyoming Hurricane", "Wyoming Mail", "Wyoming Outlaw", "Wyoming Renegades", "Wyoming Roundup", "Wyoming Wildcat", "Wyoming", "X Games 3D: The Movie", "X Marks the Spot", "X the Unknown", "X: The Man with the X-ray Eyes", "X-15", "X2: X-Men United", "Xanadu", "X-Men Origins: Wolverine", "X-Men", "X-Men: Apocalypse", "X-Men: Days of Future Past", "X-Men: First Class", "X-Men: The Last Stand", "XX", "xXx", "xXx: Return of Xander Cage", "xXx: State of the Union", "Yale vs. Harvard", "Yankee Buccaneer", "Yankee Doodle Daffy", "Yankee Doodle Dandy", "Yankee Doodle in Berlin", "Yankee Fakir", "Yankee Madness", "Yankee Pasha", "Yanks Ahoy", "Yanks", "Yaqui Drums", "Year of the Dog", "Year of the Dragon", "Year of the Fish", "Year of the Gun", "Year of the Horse", "Year One", "Yellow Cargo", "Yellow Dust", "Yellow Fin", "Yellow Fingers", "Yellow Jack", "Yellow Lily", "Yellow Men and Gold", "Yellow Sky", "Yellow", "Yellowneck", "Yellowstone Kelly", "Yellowstone", "Yentl", "Yes Man", "Yes Sir, That's My Baby", "Yes, Giorgio", "Yes, My Darling Daughter", "Yes, We Have No Bonanza", "Yesterday's Wife", "Yeti: A Love Story", "Yiddle with His Fiddle", "Yodelin' Kid from Pine Ridge", "Yogi Bear", "Yogi's Great Escape", "Yokel Boy", "Yolanda and the Thief", "Yolanda", "You Again", "You and Me", "You Are Here", "You Belong to Me", "You Came Along", "You Can Count on Me", "You Can't Beat Love", "You Can't Beat the Law", "You Can't Buy Everything", "You Can't Buy Luck", "You Can't Cheat an Honest Man", "You Can't Escape Forever", "You Can't Fool Your Wife", "You Can't Get Away with It", "You Can't Get Away with Murder", "You Can't Have Everything", "You Can't Lose Your Mother-in-Law", "You Can't Ration Love", "You Can't Run Away from It", "You Can't Take It with You", "You Can't Win 'Em All", "You Don't Mess with the Zohan", "You for Me", "You Got Served", "You Gotta Stay Happy", "You Have to Run Fast", "You Kill Me", "You Light Up My Life", "You May Be Next", "You Never Can Tell", "You Never Know Women", "You Never Know", "You Never Saw Such a Girl", "You Only Live Once", "You Ought to Be in Pictures", "You Ruined My Life", "You Said a Mouthful", "You So Crazy", "You Were Meant for Me", "You Were Never Lovelier", "You Were Never Really Here", "You Will Meet a Tall Dark Stranger", "You, John Jones!", "You, Me and Dupree", "You'd Be Surprised", "You'll Find Out", "You'll Like My Mother", "You'll Never Get Rich", "Young Adult", "Young America", "Young and Beautiful", "Young and Dangerous 4", "Young and Dangerous", "Young and Wild", "Young and Willing", "Young April", "Young as You Feel", "Young at Heart", "Young Bess", "Young Bill Hickok", "Young Billy Young", "Young Blood", "Young Bride", "Young Buffalo Bill", "Young Daniel Boone", "Young Desire", "Young Doctors in Love", "Young Donovan's Kid", "Young Dr. Kildare", "Young Eagles", "Young Frankenstein", "Young Fugitives", "Young Fury", "Young Guns II", "Young Guns", "Young Ideas", "Young Jesse James", "Young Man of Manhattan", "Young Man with a Horn", "Young Man with Ideas", "Young Mr. Jazz", "Young Mr. Lincoln", "Young Nowheres", "Young Ones", "Young People", "Young Sherlock Holmes", "Young Sinners", "Young Thugs: Innocent Blood", "Young Tom Edison", "Young Whirlwind", "Young Widow", "Youngblood Hawke", "Youngblood", "Younger and Younger", "Your Best Friend", "Your Cheatin' Heart", "Your Friend and Mine", "Your Friends & Neighbors", "Your Highness", "Your Job In Germany", "Your Studio and You", "Your Uncle Dudley", "You're a Lucky Fellow, Mr. Smith", "You're a Sap, Mr. Jap", "You're Fired", "You're in the Army Now", "You're in the Navy Now", "You're My Everything", "You're Never Too Young", "You're Next", "You're Not So Tough", "You're Not You", "You're Only Young Once", "You're Telling Me!", "Yours for the Asking", "Yours, Mine and Ours", "Youth and Adventure", "Youth for Sale", "Youth in Oregon", "Youth in Revolt", "Youth Must Have Love", "Youth on Parade", "Youth on Parole", "Youth on Trial", "Youth Runs Wild", "Youth Takes a Fling", "Youth to Youth", "Youth Without Youth", "Youth's Endearing Charm", "Youth's Gamble", "You've Got Mail", "You've Got to Walk It Like You Talk It or You'll Lose That Beat", "Yukon Flight", "Yukon Gold", "Yukon Manhunt", "Yukon Vengeance", "Yvonne from Paris", "Zabriskie Point", "Zachariah", "Zack and Miri Make a Porno", "Zander the Great", "Zandy's Bride", "Zapped Again!", "Zapped!", "Zarkorr! The Invader", "Zathura", "Zaza", "Zebra in the Kitchen", "Zebrahead", "Zelig", "Zenobia", "Zero Charisma", "Zero Dark Thirty", "Zero Day", "Zero Effect", "Zero Hour!", "Zero to Sixty", "Zeus and Roxanne", "Ziegfeld Follies", "Ziegfeld Girl", "Zig Zag", "Zipperface", "Zodiac", "Zombie High", "Zombie Hunter", "Zombieland", "Zombies of Mora Tau", "Zombies of the Stratosphere", "Zombies on Broadway", "Zoo in Budapest", "Zookeeper", "Zoolander 2", "Zoolander", "Zoom", "Zoot Suit", "Zootopia", "Zorro, the Gay Blade", "Zorro's Black Whip", "Zorro's Fighting Legion", "Zotz!", "Zyzzyx Road" ]; ================================================ FILE: dist/db/clickhouse/index.cjs ================================================ 'use strict'; var clickhouse = require('clickhouse'); /** * @param {*} value * @param {*} default_value * @param {*=} merge_value * @return {*} */ function concat(arrays){ return [].concat.apply([], arrays); } /** * @param {Map|Set} val * @param {boolean=} stringify * @return {Array} */ function toArray(val, stringify){ const result = []; for(const key of val.keys()){ result.push(key); } return result; } const defaults = { host: "http://localhost", port: "8123", debug: false, basicAuth: null, isUseGzip: false, trimQuery: false, usePost: false, format: "json", raw: false, config: { output_format_json_quote_64bit_integers: 0, enable_http_compression: 0, database: "default" } }; const fields = ["map", "ctx", "tag", "reg", "cfg"]; const types = { "text": "String", "char": "String", "varchar": "String", "string": "String", "number": "Int32", "numeric": "Int32", "integer": "Int32", "smallint": "Int16", "tinyint": "Int8", "mediumint": "Int32", "int": "Int32", "int8": "Int8", "uint8": "UInt8", "int16": "Int16", "uint16": "UInt16", "int32": "Int32", "uint32": "UInt32", "int64": "Int64", "uint64": "UInt64", "bigint": "Int64" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } let Index; /** * @constructor * @implements StorageInterface */ function ClickhouseDB(name, config = {}){ if(!this || this.constructor !== ClickhouseDB){ return new ClickhouseDB(name, config); } if(typeof name === "object"){ config = name; name = name.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } this.id = "flexsearch" + (name ? "_" + sanitize(name) : ""); this.field = config.field ? "_" + sanitize(config.field) : ""; this.type = config.type ? types[config.type.toLowerCase()] : "String"; if(!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); this.support_tag_search = true; this.db = Index || (Index = config.db || null); Object.assign(defaults, config); config.database && (defaults.config.database = config.database); this.db && delete defaults.db; } ClickhouseDB.prototype.mount = function(flexsearch){ if(flexsearch.index){ return flexsearch.mount(this); } defaults.resolution = Math.max(flexsearch.resolution, flexsearch.resolution_ctx); flexsearch.db = this; return this.open(); }; ClickhouseDB.prototype.open = async function(){ if(!this.db) { this.db = Index || ( Index = new clickhouse.ClickHouse(defaults) ); } const exists = await this.db.query(` SELECT 1 FROM system.databases WHERE name = '${this.id}'; `).toPromise(); if(!exists || !exists.length){ await this.db.query(` CREATE DATABASE IF NOT EXISTS ${this.id}; `).toPromise(); } for(let i = 0; i < fields.length; i++){ switch(fields[i]){ case "map": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.map${this.field}( key String, res ${defaults.resolution <= 255 ? "UInt8" : "UInt16"}, id ${this.type} ) ENGINE = MergeTree ORDER BY (key, id); `, { params: { name: this.id + ".map" + this.field }}).toPromise(); break; case "ctx": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.ctx${this.field}( ctx String, key String, res ${defaults.resolution <= 255 ? "UInt8" : "UInt16"}, id ${this.type} ) ENGINE = MergeTree ORDER BY (ctx, key, id); `).toPromise(); break; case "tag": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.tag${this.field}( tag String, id ${this.type} ) ENGINE = MergeTree ORDER BY (tag, id); `).toPromise(); break; case "reg": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.reg( id ${this.type}, doc Nullable(String) ) ENGINE = MergeTree ORDER BY (id); `).toPromise(); break; case "cfg": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.cfg${this.field}( cfg String ) ENGINE = TinyLog; `).toPromise(); break; } } return this.db; }; ClickhouseDB.prototype.close = function(){ this.db = Index = null; return this; }; ClickhouseDB.prototype.destroy = function(){ return Promise.all([ this.db.query(`DROP TABLE ${this.id}.map${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.ctx${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.tag${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.cfg${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.reg;`).toPromise() ]); }; ClickhouseDB.prototype.clear = function(){ return Promise.all([ this.db.query(`TRUNCATE TABLE ${this.id}.map${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.ctx${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.tag${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.cfg${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.reg;`).toPromise() ]); }; function create_result(rows, resolve, enrich){ if(resolve){ for(let i = 0; i < rows.length; i++){ if(enrich){ if(rows[i].doc){ rows[i].doc = JSON.parse(rows[i].doc); } } else { rows[i] = rows[i].id; } } return rows; } else { const arr = []; for(let i = 0, row; i < rows.length; i++){ row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id ); } return arr; } } ClickhouseDB.prototype.get = function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false, tags){ let rows; let stmt = ''; let params = ctx ? { ctx, key } : { key }; let table = this.id + (ctx ? ".ctx" : ".map") + this.field; if(tags){ for(let i = 0, count = 1; i < tags.length; i+=2){ stmt += ` AND ${ table }.id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = {tag${ count }:String})`; params["tag" + count] = tags[i + 1]; count++; } } if(ctx){ rows = this.db.query(` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT OUTER JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE ctx = {ctx:String} AND key = {key:String} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, { params } ).toPromise(); } else { rows = this.db.query(` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT OUTER JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE key = {key:String} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, { params } ).toPromise(); } return rows.then(function(rows){ return create_result(rows, resolve, enrich); }); }; ClickhouseDB.prototype.tag = function(tag, limit = 0, offset = 0, enrich = false){ const table = this.id + ".tag" + this.field; const promise = this.db.query(` SELECT ${ table }.id ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT OUTER JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE tag = {tag:String} ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, { params: { tag } } ).toPromise(); enrich || promise.then(function(rows){ return create_result(rows, true, false); }); return promise; }; ClickhouseDB.prototype.enrich = async function(ids){ let MAXIMUM_QUERY_VARS = 1e5; let result = []; if(typeof ids !== "object"){ ids = [ids]; } for(let count = 0; count < ids.length;){ const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; let params = {}; let stmt = ""; for(let i = 0; i < chunk.length; i++){ stmt += (stmt ? "," : "") + "{id" + (i + 1) + ":String}"; params["id" + (i + 1)] = chunk[i]; } const res = await this.db.query(` SELECT id, doc FROM ${ this.id }.reg WHERE id IN (${ stmt })`, { params } ).toPromise(); if(res && res.length){ for(let i = 0, doc; i < res.length; i++){ if((doc = res[i].doc)){ res[i].doc = JSON.parse(doc); } } result.push(res); } } return result.length === 1 ? result[0] : result.length > 1 ? concat(result) : result; }; ClickhouseDB.prototype.has = async function(id){ const result = await this.db.query(` SELECT 1 FROM ${this.id}.reg WHERE id = {id:${this.type }} LIMIT 1`, { params: { id }} ).toPromise(); return !!(result && result[0] && result[0]["1"]); }; ClickhouseDB.prototype.search = function(flexsearch, query, limit = 100, offset = 0, suggest = false, resolve = true, enrich = false, tags){ let rows; if(query.length > 1 && flexsearch.depth){ let where = ""; let params = {}; let keyword = query[0]; let term; for(let i = 1; i < query.length; i++){ term = query[i]; const swap = flexsearch.bidirectional && (term > keyword); where += (where ? " OR " : "") + `(ctx = {ctx${ i }:String} AND key = {key${ i }:String})`; params["ctx" + i] = swap ? term : keyword; params["key" + i] = swap ? keyword : term; keyword = term; } if(tags){ where = "(" + where + ")"; for(let i = 0, count = 1; i < tags.length; i+=2){ where += ` AND id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = {tag${ count }:String})`; params["tag" + count] = tags[i + 1]; count++; } } rows = this.db.query(` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" }(res) as res FROM ${ this.id }.ctx${ this.field } WHERE ${ where } GROUP BY id ) as r ${ enrich ? ` LEFT OUTER JOIN ${ this.id }.reg ON ${ this.id }.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + (query.length - 1) } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, { params }).toPromise(); } else { let where = ""; let params = {}; for(let i = 0; i < query.length; i++){ where += (where ? "," : "") + `{key${i}:String}`; params["key" + i] = query[i]; } where = "key " + (query.length > 1 ? "IN (" + where + ")" : "= " + where ); if(tags){ where = "(" + where + ")"; for(let i = 0, count = 1; i < tags.length; i+=2){ where += ` AND id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = {tag${ count }:String})`; params["tag" + count] = tags[i + 1]; count++; } } rows = this.db.query(` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" }(res) as res FROM ${ this.id }.map${ this.field } WHERE ${ where } GROUP BY id ) as r ${ enrich ? ` LEFT OUTER JOIN ${ this.id }.reg ON ${ this.id }.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + query.length } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, { params }).toPromise(); } return rows.then(function(rows){ return create_result(rows, resolve, enrich); }); }; ClickhouseDB.prototype.info = function(){ }; ClickhouseDB.prototype.transaction = function(task){ return task.call(this); }; ClickhouseDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ /** @dict */ task = tasks[i]; if(task["del"]){ removals.push(task["del"]); } else if(task["ins"]); } if(removals.length){ await this.remove(removals); } if(!flexsearch.reg.size){ return; } const promises = []; if(flexsearch.map.size){ let data = []; for(const item of flexsearch.map){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ for(let j = 0; j < ids.length; j++){ data.push({ key: key, res: i, id: /*this.type === "number" ? parseInt(ids[j], 10) :*/ ids[j] }); } } } } if(data.length){ promises.push(this.db.insert( `INSERT INTO ${ this.id }.map${ this.field } (key, res, id)`, data ).toPromise()); } } if(flexsearch.ctx.size){ let data = []; for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ for(let j = 0; j < ids.length; j++){ data.push({ ctx: ctx_key, key: key, res: i, id: /*this.type === "number" ? parseInt(ids[j], 10) :*/ ids[j] }); } } } } } if(data.length){ promises.push(this.db.insert( `INSERT INTO ${ this.id }.ctx${ this.field } (ctx, key, res, id)`, data ).toPromise()); } } if(flexsearch.tag){ let data = []; for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; for(let j = 0; j < ids.length; j++){ data.push({ tag, id: ids[j] }); } } if(data.length){ promises.push(this.db.insert( `INSERT INTO ${this.id}.tag${ this.field } (tag, id)`, data ).toPromise()); } } if(flexsearch.store){ let data = []; for(const item of flexsearch.store.entries()){ const id = item[0]; const doc = item[1]; data.push({ id, doc: doc && JSON.stringify(doc) }); } if(data.length){ promises.push(this.db.insert( `INSERT INTO ${this.id}.reg (id, doc)`, data ).toPromise()); } } else if(!flexsearch.bypass){ let data = toArray(flexsearch.reg); for(let i = 0; i < data.length; i++){ data[i] = { id: data[i] }; } if(data.length){ promises.push(this.db.insert( `INSERT INTO ${this.id}.reg (id)`, data ).toPromise()); } } promises.length && await Promise.all(promises); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); await Promise.all([ this.db.query(`OPTIMIZE TABLE ${this.id}.map${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.ctx${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.tag${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.reg FINAL`).toPromise() ]); }; ClickhouseDB.prototype.remove = async function(ids){ if(typeof ids !== "object"){ ids = [ids]; } while(ids.length){ let chunk = ids.slice(0, 1e5); ids = ids.slice(1e5); chunk = this.type === "String" ? "'" + chunk.join("','") + "'" : chunk.join(","); await Promise.all([ this.db.query(` ALTER TABLE ${this.id}.map${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;` ).toPromise(), this.db.query(` ALTER TABLE ${this.id}.ctx${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;` ).toPromise(), this.db.query(` ALTER TABLE ${this.id}.tag${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;` ).toPromise(), this.db.query(` ALTER TABLE ${this.id}.reg DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;` ).toPromise() ]); } }; module.exports = ClickhouseDB; ================================================ FILE: dist/db/indexeddb/index.cjs ================================================ 'use strict'; /** * @param {*} value * @param {*} default_value * @param {*=} merge_value * @return {*} */ function create_object(){ return Object.create(null); } const VERSION = 1; const IndexedDB = typeof window !== "undefined" && ( window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB ); const fields = ["map", "ctx", "tag", "reg", "cfg"]; /** * @param {!string} str * @return {string} */ function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } const Index = create_object(); /** * @param {string|PersistentOptions=} name * @param {PersistentOptions=} config * @constructor * @implements StorageInterface */ function IdxDB(name, config = {}){ if(!this || this.constructor !== IdxDB){ return new IdxDB(name, config); } if(typeof name === "object"){ config = /** @type PersistentOptions */ (name); name = name.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } this.id = "flexsearch" + (name ? ":" + sanitize(name) : ""); this.field = config.field ? sanitize(config.field) : ""; this.type = config.type; this.support_tag_search = false; this.fastupdate = false; this.db = null; this.trx = {}; } IdxDB.prototype.mount = function(flexsearch){ if(flexsearch.index){ return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; IdxDB.prototype.open = function(){ if(this.db) return this.db; let self = this; navigator.storage && navigator.storage.persist && navigator.storage.persist(); Index[self.id] || (Index[self.id] = []); Index[self.id].push(self.field); const req = IndexedDB.open(self.id, VERSION); /** @this {IDBOpenDBRequest} */ req.onupgradeneeded = function(event){ const db = self.db = this.result; for(let i = 0, ref; i < fields.length; i++){ ref = fields[i]; for(let j = 0, field; j < Index[self.id].length; j++){ field = Index[self.id][j]; db.objectStoreNames.contains(ref + (ref !== "reg" ? (field ? ":" + field : "") : "")) || db.createObjectStore(ref + (ref !== "reg" ? (field ? ":" + field : "") : "")); } } }; return self.db = promisfy(req, function(result){ self.db = result; self.db.onversionchange = function(){ self.close(); }; }); }; IdxDB.prototype.close = function(){ this.db && this.db.close(); this.db = null; }; /** * @return {!Promise} */ IdxDB.prototype.destroy = function(){ const req = IndexedDB.deleteDatabase(this.id); return promisfy(req); }; /** * @return {!Promise} */ IdxDB.prototype.clear = function(){ const stores = []; for(let i = 0, ref; i < fields.length; i++){ ref = fields[i]; for(let j = 0, field; j < Index[this.id].length; j++){ field = Index[this.id][j]; stores.push(ref + (ref !== "reg" ? (field ? ":" + field : "") : "")); } } const transaction = this.db.transaction(stores, "readwrite"); for(let i = 0; i < stores.length; i++){ transaction.objectStore(stores[i]).clear(); } return promisfy(transaction); }; /** * @param {!string} key * @param {string=} ctx * @param {number=} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @return {!Promise} */ IdxDB.prototype.get = function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false){ const transaction = this.db.transaction((ctx ? "ctx" : "map") + (this.field ? ":" + this.field : ""), "readonly"); const map = transaction.objectStore((ctx ? "ctx" : "map") + (this.field ? ":" + this.field : "")); const req = map.get(ctx ? ctx + ":" + key : key); const self = this; return promisfy(req).then(function(res){ let result = []; if(!res || !res.length) return result; if(resolve){ if(!limit && !offset && res.length === 1){ return res[0]; } for(let i = 0, arr; i < res.length; i++){ if((arr = res[i]) && arr.length){ if(offset >= arr.length){ offset -= arr.length; continue; } const end = limit ? offset + Math.min(arr.length - offset, limit) : arr.length; for(let j = offset; j < end; j++){ result.push(arr[j]); } offset = 0; if(result.length === limit){ break; } } } return SUPPORT_STORE && enrich ? self.enrich(result) : result; } else { return res; } }); }; if(SUPPORT_TAGS){ /** * @param {!string} tag * @param {number=} limit * @param {number=} offset * @param {boolean=} enrich * @return {!Promise} */ IdxDB.prototype.tag = function(tag, limit = 0, offset = 0, enrich = false){ const transaction = this.db.transaction("tag" + (this.field ? ":" + this.field : ""), "readonly"); const map = transaction.objectStore("tag" + (this.field ? ":" + this.field : "")); const req = map.get(tag); const self = this; return promisfy(req).then(function(ids){ if(!ids || !ids.length || offset >= ids.length) return []; if(!limit && !offset) return ids; const result = ids.slice(offset, offset + limit); return SUPPORT_STORE && enrich ? self.enrich(result) : result; }); }; } if(SUPPORT_STORE){ /** * @param {SearchResults} ids * @return {!Promise} */ IdxDB.prototype.enrich = function(ids){ if(typeof ids !== "object"){ ids = [ids]; } const transaction = this.db.transaction("reg", "readonly"); const map = transaction.objectStore("reg"); const promises = []; for(let i = 0; i < ids.length; i++){ promises[i] = promisfy(map.get(ids[i])); } return Promise.all(promises).then(function(docs){ for(let i = 0; i < docs.length; i++){ docs[i] = { "id": ids[i], "doc": docs[i] ? JSON.parse(docs[i]) : null }; } return docs; }); }; } /** * @param {number|string} id * @return {!Promise} */ IdxDB.prototype.has = function(id){ const transaction = this.db.transaction("reg", "readonly"); const map = transaction.objectStore("reg"); const req = map.getKey(id); return promisfy(req).then(function(result){ return !!result; }); }; IdxDB.prototype.search = null; IdxDB.prototype.info = function(){ }; /** * @param {!string} ref * @param {!string} modifier * @param {!Function} task */ IdxDB.prototype.transaction = function(ref, modifier, task){ const key = ref + (ref !== "reg" ? (this.field ? ":" + this.field : "") : ""); /** * @type {IDBObjectStore} */ let store = this.trx[key + ":" + modifier]; if(store) return task.call(this, store); let transaction = this.db.transaction(key, modifier); /** * @type {IDBObjectStore} */ this.trx[key + ":" + modifier] = store = transaction.objectStore(key); const promise = task.call(this, store); this.trx[key + ":" + modifier] = null; return promisfy(transaction).finally(function(){ return promise; }); }; IdxDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ /** @dict */ task = tasks[i]; if(task["del"]){ removals.push(task["del"]); } } if(removals.length){ await this.remove(removals); } if(!flexsearch.reg.size){ return; } await this.transaction("map", "readwrite", function(store){ for(const item of flexsearch.map){ const key = item[0]; const value = item[1]; if(!value.length) continue; store.get(key).onsuccess = function(){ let result = this.result; let changed; if(result && result.length){ const maxlen = Math.max(result.length, value.length); for(let i = 0, res, val; i < maxlen; i++){ val = value[i]; if(val && val.length){ res = result[i]; if(res && res.length){ for(let j = 0; j < val.length; j++){ res.push(val[j]); } changed = 1; } else { result[i] = val; changed = 1; } } } } else { result = value; changed = 1; } changed && store.put(result, key); }; } }); await this.transaction("ctx", "readwrite", function(store){ for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const value = item[1]; if(!value.length) continue; store.get(ctx_key + ":" + key).onsuccess = function(){ let result = this.result; let changed; if(result && result.length){ const maxlen = Math.max(result.length, value.length); for(let i = 0, res, val; i < maxlen; i++){ val = value[i]; if(val && val.length){ res = result[i]; if(res && res.length){ for(let j = 0; j < val.length; j++){ res.push(val[j]); } changed = 1; } else { result[i] = val; changed = 1; } } } } else { result = value; changed = 1; } changed && store.put(result, ctx_key + ":" + key); }; } } }); if(SUPPORT_STORE && flexsearch.store){ await this.transaction("reg", "readwrite", function(store){ for(const item of flexsearch.store){ const id = item[0]; const doc = item[1]; store.put(typeof doc === "object" ? JSON.stringify(doc) : 1 , id); } }); } else if(!flexsearch.bypass){ await this.transaction("reg", "readwrite", function(store){ for(const id of flexsearch.reg.keys()){ store.put(1, id); } }); } if(SUPPORT_TAGS && flexsearch.tag){ await this.transaction("tag", "readwrite", function(store){ for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; store.get(tag).onsuccess = function(){ let result = this.result; result = result && result.length ? result.concat(ids) : ids; store.put(result, tag); }; } }); } flexsearch.map.clear(); flexsearch.ctx.clear(); if(SUPPORT_TAGS){ flexsearch.tag && flexsearch.tag.clear(); } if(SUPPORT_STORE){ flexsearch.store && flexsearch.store.clear(); } flexsearch.document || flexsearch.reg.clear(); }; /** * @param {IDBCursorWithValue} cursor * @param {Array} ids * @param {boolean=} _tag */ function handle(cursor, ids, _tag){ const arr = cursor.value; let changed; let count = 0; for(let x = 0, result; x < arr.length; x++){ if((result = _tag ? arr : arr[x])){ for(let i = 0, pos, id; i < ids.length; i++){ id = ids[i]; pos = result.indexOf(id); if(pos >= 0){ changed = 1; if(result.length > 1){ result.splice(pos, 1); } else { arr[x] = []; break; } } } count += result.length; } if(_tag) break; } if(!count){ cursor.delete(); } else if(changed){ cursor.update(arr); } cursor.continue(); } /** * @param {Array} ids * @return {!Promise} */ IdxDB.prototype.remove = function(ids){ const self = this; if(typeof ids !== "object"){ ids = [ids]; } return /** @type {!Promise} */(Promise.all([ self.transaction("map", "readwrite", function(store){ store.openCursor().onsuccess = function(){ const cursor = this.result; cursor && handle(cursor, ids); }; }), self.transaction("ctx", "readwrite", function(store){ store.openCursor().onsuccess = function(){ const cursor = this.result; cursor && handle(cursor, ids); }; }), SUPPORT_TAGS && self.transaction("tag", "readwrite", function(store){ store.openCursor().onsuccess = function(){ const cursor = this.result; cursor && handle(cursor, ids, true); }; }), self.transaction("reg", "readwrite", function(store){ for(let i = 0; i < ids.length; i++){ store.delete(ids[i]); } }) ])); }; /** * @param {IDBRequest|IDBOpenDBRequest} req * @param {Function=} callback * @return {!Promise} */ function promisfy(req, callback){ return new Promise((resolve, reject) => { /** @this {IDBRequest|IDBOpenDBRequest} */ req.onsuccess = req.oncomplete = function(){ callback && callback(this.result); callback = null; resolve(this.result); }; req.onerror = req.onblocked = reject; req = null; }); } module.exports = IdxDB; ================================================ FILE: dist/db/mongodb/index.cjs ================================================ 'use strict'; var mongodb = require('mongodb'); const defaults = { host: "localhost", port: "27017", user: null, pass: null }; const fields = ["map", "ctx", "tag", "reg", "cfg"]; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } let CLIENT; let Index = Object.create(null); /** * @constructor * @implements StorageInterface */ function MongoDB(name, config = {}){ if(!this || this.constructor !== MongoDB){ return new MongoDB(name, config); } if(typeof name === "object"){ config = name; name = name.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } this.id = "flexsearch" + (name ? "-" + sanitize(name) : ""); this.field = config.field ? "-" + sanitize(config.field) : ""; this.type = config.type || ""; this.db = config.db || Index[this.id] || CLIENT || null; this.trx = false; this.support_tag_search = true; Object.assign(defaults, config); this.db && delete defaults.db; } MongoDB.prototype.mount = function(flexsearch){ if(flexsearch.index){ return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; async function createCollection(db, ref, field){ switch(ref){ case "map": await db.createCollection("map" + field); await db.collection("map" + field).createIndex({ key: 1 }); await db.collection("map" + field).createIndex({ id: 1 }); break; case "ctx": await db.createCollection("ctx" + field); await db.collection("ctx" + field).createIndex({ ctx: 1, key: 1 }); await db.collection("ctx" + field).createIndex({ id: 1 }); break; case "tag": await db.createCollection("tag" + field); await db.collection("tag" + field).createIndex({ tag: 1 }); await db.collection("tag" + field).createIndex({ id: 1 }); break; case "reg": await db.createCollection("reg"); await db.collection("reg").createIndex({ id: 1 }); break; case "cfg": await db.createCollection("cfg" + field); } } MongoDB.prototype.open = async function(){ if(!this.db){ if(!(this.db = Index[this.id])){ if(!(this.db = CLIENT)){ let url = defaults.url; if(!url){ url = defaults.user ? `mongodb://${ defaults.user }:${ defaults.pass }@${ defaults.host }:${ defaults.port }` : `mongodb://${ defaults.host }:${ defaults.port }`; } this.db = CLIENT = new mongodb.MongoClient(url); await this.db.connect(); } } } if(this.db.db){ this.db = Index[this.id] = this.db.db(this.id); } const collections = await this.db.listCollections().toArray(); for(let i = 0; i < fields.length; i++){ let found = false; for(let j = 0; j < collections.length; j++){ if(collections[j].name === fields[i] + (fields[i] !== "reg" ? this.field : "")){ found = true; break; } } if(!found){ await createCollection(this.db, fields[i], this.field); } } return this.db; }; MongoDB.prototype.close = function(){ this.db = CLIENT = null; Index[this.id] = null; return this; }; MongoDB.prototype.destroy = function(){ return Promise.all([ this.db.dropCollection("map" + this.field), this.db.dropCollection("ctx" + this.field), this.db.dropCollection("tag" + this.field), this.db.dropCollection("cfg" + this.field), this.db.dropCollection("reg") ]); }; async function clear(ref){ await this.db.dropCollection(ref); await createCollection(this.db, ref, this.field); } MongoDB.prototype.clear = function(){ return Promise.all([ clear.call(this, "map" + this.field), clear.call(this, "ctx" + this.field), clear.call(this, "tag" + this.field), clear.call(this, "cfg" + this.field), clear.call(this, "reg") ]); }; function create_result(rows, resolve, enrich){ const _id = rows[0] && typeof rows[0]._id !== "undefined"; if(resolve){ if(!enrich || _id) for(let i = 0, row; i < rows.length; i++){ row = rows[i]; if(enrich){ const id = row._id; delete row._id; row.id = id; } else { rows[i] = _id ? row._id : row.id; } } return rows; } else { const arr = []; for(let i = 0, row, res; i < rows.length; i++){ row = rows[i]; res = row.res; (arr[res] || (arr[res] = [])).push( _id ? row._id : row.id ); } return arr; } } MongoDB.prototype.get = async function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false, tags){ let rows; let params = ctx ? { ctx, key } : { key }; if(!enrich && !tags){ const stmt = { projection: { _id: 0, res: 1, id: 1 } }; limit && (stmt.limit = limit); offset && (stmt.skip = offset); rows = await this.db.collection((ctx ? "ctx" : "map") + this.field) .find(params, stmt) .toArray(); } else { const project = { _id: 0, id: 1 }; const stmt = [ { $match: params } ]; if(!resolve){ project["res"] = 1; } if(enrich){ project["doc"] = "$doc.doc"; stmt.push( { $lookup: { from: "reg", localField: "id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: true } } ); } if(tags){ const match = {}; for(let i = 0, count = 1; i < tags.length; i += 2){ project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push( { $lookup: { from: "tag-" + sanitize(tags[i]), localField: "id", foreignField: "id", as: "tag" + count } } ); count++; } stmt.push( { $project: project }, { $match: match }, { $project: { id: 1, doc: 1 } } ); } else { stmt.push( { $project: project } ); } stmt.push( { $sort: { res: 1 } } ); limit && stmt.push( { $limit: limit } ); offset && stmt.push( { $skip: offset} ); rows = []; const result = await this.db.collection((ctx ? "ctx" : "map") + this.field).aggregate(stmt); while(true){ const row = await result.next(); if(row) rows.push(row); else break; } } return create_result(rows, resolve, enrich); }; MongoDB.prototype.tag = async function(tag, limit = 0, offset = 0, enrich = false){ let rows; if(!enrich){ const stmt = { projection: { _id: 0, id: 1 } }; limit && (stmt.limit = limit); offset && (stmt.skip = offset); rows = await this.db.collection("tag" + this.field) .find({ tag }, stmt) .toArray(); } else { const stmt = [ { $match: { tag } } ]; limit && stmt.push( { $limit: limit } ); offset && stmt.push( { $skip: offset} ); stmt.push( { $lookup: { from: "reg", localField: "id", foreignField: "id", as: "doc" } }, { $project: { _id: 0, id: 1, doc: "$doc.doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: true } } ); rows = []; const result = await this.db.collection("tag" + this.field).aggregate(stmt); while(true){ const row = await result.next(); if(row) rows.push(row); else break; } } create_result(rows, true, enrich); }; MongoDB.prototype.enrich = function(ids){ if(typeof ids !== "object"){ ids = [ids]; } return this.db.collection("reg") .find({ id: { $in: ids } }, { projection: { _id: 0, id: 1, doc: 1 } }) .toArray(); }; MongoDB.prototype.has = function(id){ return this.db.collection("reg").countDocuments({ id }, { limit: 1 }).then(function(result){ return !!result; }); }; MongoDB.prototype.search = async function(flexsearch, query, limit = 100, offset = 0, suggest = false, resolve = true, enrich = false, tags){ let result = [], rows; if(query.length > 1 && flexsearch.depth){ let params = []; let keyword = query[0]; let term; for(let i = 1; i < query.length; i++){ term = query[i]; const swap = flexsearch.bidirectional && (term > keyword); params.push({ ctx: swap ? term : keyword, key: swap ? keyword : term }); keyword = term; } const project = { _id: 1 }; if(!resolve) project["res"] = 1; if(enrich) project["doc"] = 1; const stmt = [ { $match: { $or: params } }, { $group: { _id: "$id", count: { $sum: 1 }, res: suggest ? { $sum: "$res" } : { $sum : "$res" } } } ]; suggest || stmt.push( { $match: { count: query.length - 1 } } ); if(enrich){ project["doc"] = "$doc.doc"; stmt.push( { $lookup: { from: "reg", localField: "_id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: true } } ); } if(tags){ const match = {}; for(let i = 0, count = 1; i < tags.length; i += 2){ project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push( { $lookup: { from: "tag-" + sanitize(tags[i]), localField: "_id", foreignField: "id", as: "tag" + count } } ); count++; } stmt.push( { $match: match } ); } stmt.push( { $sort: suggest ? { count: -1, res: 1} : { res: 1 } } ); limit && stmt.push( { $limit: limit } ); offset && stmt.push( { $skip: offset} ); stmt.push( { $project: project } ); rows = await this.db.collection("ctx" + this.field).aggregate(stmt); } else { const project = { _id: 1 }; if(!resolve) project["res"] = 1; if(enrich) project["doc"] = 1; const stmt = [ { $match: { key: { $in: query } } }, { $group: { _id: "$id", count: { $sum: 1 }, res: suggest ? { $sum: "$res" } : { $sum : "$res" } } } ]; suggest || stmt.push( { $match: { count: query.length } } ); if(enrich){ project["doc"] = "$doc.doc"; stmt.push( { $lookup: { from: "reg", localField: "_id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: true } } ); } if(tags){ const match = {}; for(let i = 0, count = 1; i < tags.length; i += 2){ project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push( { $lookup: { from: "tag-" + sanitize(tags[i]), localField: "_id", foreignField: "id", as: "tag" + count } } ); count++; } stmt.push( { $match: match } ); } stmt.push( { $sort: suggest ? { count: -1, res: 1 } : { res: 1 } } ); limit && stmt.push( { $limit: limit } ); offset && stmt.push( { $skip: offset} ); stmt.push( { $project: project } ); rows = await this.db.collection("map" + this.field).aggregate(stmt); } while(true) { const row = await rows.next(); if(row){ if(resolve && enrich){ row.id = row._id; delete row._id; } result.push(row); } else break; } return create_result(result, resolve, enrich); }; MongoDB.prototype.info = function(){ }; MongoDB.prototype.transaction = function(task){ return task.call(this); }; MongoDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ /** @dict */ task = tasks[i]; if(task["del"]){ removals.push(task["del"]); } else if(task["ins"]); } if(removals.length){ await this.remove(removals); } if(!flexsearch.reg.size){ return; } const promises = []; if(flexsearch.map.size){ let data = []; for(const item of flexsearch.map){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ this.type || (this.type = typeof ids[0]); for(let j = 0; j < ids.length; j++){ data.push({ key: key, res: i, id: ids[j] }); } } } } if(data.length){ promises.push( this.db.collection("map" + this.field).insertMany(data) ); } } if(flexsearch.ctx.size){ let data = []; for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ for(let j = 0; j < ids.length; j++){ data.push({ ctx: ctx_key, key: key, res: i, id: ids[j] }); } } } } } if(data.length){ promises.push( this.db.collection("ctx" + this.field).insertMany(data) ); } } if(flexsearch.tag){ let data = []; for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; for(let j = 0; j < ids.length; j++){ data.push({ tag, id: ids[j] }); } } if(data.length){ promises.push( this.db.collection("tag" + this.field).insertMany(data) ); } } let data = []; if(flexsearch.store){ for(const item of flexsearch.store.entries()){ const id = item[0]; const doc = item[1]; data.push({ id, doc }); } } else if(!flexsearch.bypass){ for(const id of flexsearch.reg.keys()){ data.push({ id }); } } if(data.length){ promises.push( this.db.collection("reg").insertMany(data) ); } promises.length && await Promise.all(promises); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; MongoDB.prototype.remove = function(ids){ if(!ids && ids !== 0) return; if(typeof ids !== "object"){ ids = [ids]; } return Promise.all([ this.db.collection("map" + this.field).deleteMany({ "id": { "$in": ids }}), this.db.collection("ctx" + this.field).deleteMany({ "id": { "$in": ids }}), this.db.collection("tag" + this.field).deleteMany({ "id": { "$in": ids }}), this.db.collection("reg").deleteMany({ "id": { "$in": ids }}) ]); }; module.exports = MongoDB; ================================================ FILE: dist/db/postgres/index.cjs ================================================ 'use strict'; var pg_promise = require('pg-promise'); /** * @param {*} value * @param {*} default_value * @param {*=} merge_value * @return {*} */ function concat(arrays){ return [].concat.apply([], arrays); } const defaults = { schema: "flexsearch", user: "postgres", pass: "postgres", name: "postgres", host: "localhost", port: "5432" }; const pgp = pg_promise(); const MAXIMUM_QUERY_VARS = 16000; const fields = ["map", "ctx", "reg", "tag", "cfg"]; const types = { "text": "text", "char": "text", "varchar": "text", "string": "text", "number": "int", "numeric": "int", "integer": "int", "smallint": "int", "tinyint": "int", "mediumint": "int", "int": "int", "int8": "int", "uint8": "int", "int16": "int", "uint16": "int", "int32": "int", "uint32": "bigint", "int64": "bigint", "bigint": "bigint" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } let DB; /** * @constructor * @implements StorageInterface */ function PostgresDB(name, config = {}){ if(!this || this.constructor !== PostgresDB){ return new PostgresDB(name, config); } if(typeof name === "object"){ config = name; name = config.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } this.id = (config.schema ? sanitize(config.schema) : defaults.schema) + (name ? "_" + sanitize(name) : ""); this.field = config.field ? "_" + sanitize(config.field) : ""; this.type = config.type ? types[config.type.toLowerCase()] : "text"; this.support_tag_search = true; if(!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); this.db = DB || (DB = config.db || null); Object.assign(defaults, config); this.db && delete defaults.db; } PostgresDB.prototype.mount = function(flexsearch){ if(flexsearch.index){ return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; PostgresDB.prototype.open = async function(){ if(!this.db) { this.db = DB || ( DB = pgp(`postgres://${defaults.user}:${encodeURIComponent(defaults.pass)}@${defaults.host}:${defaults.port}/${defaults.name}`) ); } const exist = await this.db.oneOrNone(` SELECT EXISTS ( SELECT 1 FROM information_schema.schemata WHERE schema_name = '${this.id}' ); `); if(!exist || !exist.exists){ await this.db.none(`CREATE SCHEMA IF NOT EXISTS ${ this.id };`); } for(let i = 0; i < fields.length; i++){ const exist = await this.db.oneOrNone(` SELECT EXISTS ( SELECT 1 FROM pg_tables WHERE schemaname = '${this.id}' AND tablename = '${fields[i] + (fields[i] !== "reg" ? this.field : "")}' ); `); if(exist && exist.exists) continue; const type = this.type === "text" ? "varchar(128)" : this.type; switch(fields[i]){ case "map": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.map${this.field}( key varchar(128) NOT NULL, res smallint NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_map_index${this.field} ON ${this.id}.map${this.field} (key); CREATE INDEX IF NOT EXISTS ${this.id}_map_id${this.field} ON ${this.id}.map${this.field} (id); `); break; case "ctx": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.ctx${this.field}( ctx varchar(128) NOT NULL, key varchar(128) NOT NULL, res smallint NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_ctx_index${this.field} ON ${this.id}.ctx${this.field} (ctx, key); CREATE INDEX IF NOT EXISTS ${this.id}_ctx_id${this.field} ON ${this.id}.ctx${this.field} (id); `); break; case "tag": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.tag${this.field}( tag varchar(128) NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_tag_index${this.field} ON ${this.id}.tag${this.field} (tag); CREATE INDEX IF NOT EXISTS ${this.id}_tag_id${this.field} ON ${this.id}.tag${this.field} (id); `); break; case "reg": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.reg( id ${type} NOT NULL CONSTRAINT ${this.id}_reg_pk PRIMARY KEY, doc text DEFAULT NULL ); `).catch(e => { }); break; case "cfg": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.cfg${this.field}( cfg text NOT NULL ); `); break; } } return this.db; }; PostgresDB.prototype.close = function(){ this.db = null; return this; }; PostgresDB.prototype.destroy = function(){ return this.db.none(` DROP TABLE IF EXISTS ${this.id}.map${this.field}; DROP TABLE IF EXISTS ${this.id}.ctx${this.field}; DROP TABLE IF EXISTS ${this.id}.tag${this.field}; DROP TABLE IF EXISTS ${this.id}.cfg${this.field}; DROP TABLE IF EXISTS ${this.id}.reg; `); }; PostgresDB.prototype.clear = function(){ return this.db.none(` TRUNCATE TABLE ${this.id}.map${ this.field }; TRUNCATE TABLE ${this.id}.ctx${ this.field }; TRUNCATE TABLE ${this.id}.tag${ this.field }; TRUNCATE TABLE ${this.id}.cfg${ this.field }; TRUNCATE TABLE ${this.id}.reg; `); }; function create_result(rows, resolve, enrich){ if(resolve){ for(let i = 0; i < rows.length; i++){ if(enrich){ if(rows[i].doc){ rows[i].doc = JSON.parse(rows[i].doc); } } else { rows[i] = rows[i].id; } } return rows; } else { const arr = []; for(let i = 0, row; i < rows.length; i++){ row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id ); } return arr; } } PostgresDB.prototype.get = function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false, tags){ let rows; let stmt = ''; let params = ctx ? [ctx, key] : [key]; let table = this.id + (ctx ? ".ctx" : ".map") + this.field; if(tags){ for(let i = 0, count = params.length + 1; i < tags.length; i+=2){ stmt += ` AND ${ table }.id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = $${ count++ })`; params.push(tags[i + 1]); } } if(ctx){ rows = this.db.any(` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE ctx = $1 AND key = $2 ${stmt} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, params ); } else { rows = this.db.any(` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE key = $1 ${stmt} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, params ); } return rows.then(function(rows){ return create_result(rows, resolve, enrich); }); }; PostgresDB.prototype.tag = function(tag, limit = 0, offset = 0, enrich = false){ const table = this.id + ".tag" + this.field; const promise = this.db.any(` SELECT ${ table }.id ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE tag = $1 ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, [tag] ); enrich || promise.then(function(rows){ return create_result(rows, true, false); }); return promise; }; PostgresDB.prototype.enrich = async function(ids){ let result = []; if(typeof ids !== "object"){ ids = [ids]; } for(let count = 0; count < ids.length;){ const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; let stmt = ""; for(let i = 1; i <= chunk.length; i++){ stmt += (stmt ? "," : "") + "$" + i; } const res = await this.db.any(` SELECT id, doc FROM ${ this.id }.reg WHERE id IN (${ stmt })`, ids ); if(res && res.length){ for(let i = 0, doc; i < res.length; i++){ if((doc = res[i].doc)){ res[i].doc = JSON.parse(doc); } } result.push(res); } } return result.length === 1 ? result[0] : result.length > 1 ? concat(result) : result; }; PostgresDB.prototype.has = function(id){ return this.db.oneOrNone("SELECT EXISTS(SELECT 1 FROM " + this.id + ".reg WHERE id = $1)", [id]).then(function(result){ return !!(result && result.exists); }); }; PostgresDB.prototype.search = function(flexsearch, query, limit = 100, offset = 0, suggest = false, resolve = true, enrich = false, tags){ let rows; if(query.length > 1 && flexsearch.depth){ let where = ""; let params = []; let keyword = query[0]; let term; let count = 1; for(let i = 1; i < query.length; i++){ term = query[i]; const swap = flexsearch.bidirectional && (term > keyword); where += (where ? " OR " : "") + `(ctx = $${ count++ } AND key = $${ count++ })`; params.push(swap ? term : keyword, swap ? keyword : term); keyword = term; } if(tags){ where = "(" + where + ")"; for(let i = 0; i < tags.length; i+=2){ where += ` AND id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = $${ count++ })`; params.push(tags[i + 1]); } } rows = this.db.any(` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" }(res) as res FROM ${ this.id }.ctx${ this.field } WHERE ${ where } GROUP BY id ) as r ${ enrich ? ` LEFT JOIN ${ this.id }.reg ON ${ this.id }.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + (query.length - 1) } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params); } else { let where = ""; let count = 1; let query_length = query.length; for(let i = 0; i < query_length; i++){ where += (where ? "," : "") + "$" + count++; } where = "key " + (query_length > 1 ? "IN (" + where + ")" : "= " + where ); if(tags){ where = "(" + where + ")"; for(let i = 0; i < tags.length; i+=2){ where += ` AND id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = $${ count++ })`; query.push(tags[i + 1]); } } rows = this.db.any(` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" }(res) as res FROM ${ this.id }.map${ this.field } WHERE ${ where } GROUP BY id ) as r ${ enrich ? ` LEFT JOIN ${ this.id }.reg ON ${ this.id }.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + query_length } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, query); } return rows.then(function(rows){ return create_result(rows, resolve, enrich); }); }; PostgresDB.prototype.info = function(){ }; PostgresDB.prototype.transaction = function(task){ const self = this; return this.db.tx(function(trx){ return task.call(self, trx); }); }; PostgresDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ /** @dict */ task = tasks[i]; if(task["del"]){ removals.push(task["del"]); } else if(task["ins"]); } if(removals.length){ await this.remove(removals); } if(!flexsearch.reg.size){ return; } await this.transaction(function(trx){ const batch = []; if(flexsearch.store){ let data = []; let stmt = new pgp.helpers.ColumnSet(["id", "doc"],{ table: this.id + ".reg" }); for(const item of flexsearch.store.entries()){ const id = item[0]; const doc = item[1]; data.push({ id, doc: doc && JSON.stringify(doc) }); if(data.length === MAXIMUM_QUERY_VARS){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); data = []; } } if(data.length){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); } } else if(!flexsearch.bypass){ let data = []; let stmt = new pgp.helpers.ColumnSet(["id"],{ table: this.id + ".reg" }); for(const id of flexsearch.reg.keys()){ data.push({ id }); if(data.length === MAXIMUM_QUERY_VARS){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); data = []; } } if(data.length){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); } } if(flexsearch.map.size){ let data = []; let stmt = new pgp.helpers.ColumnSet(["key", "res", "id"], { table: this.id + ".map" + this.field }); for(const item of flexsearch.map){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ for(let j = 0; j < ids.length; j++){ data.push({ key: key, res: i, id: ids[j] }); if(data.length === MAXIMUM_QUERY_VARS){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); data = []; } } } } } if(data.length){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); } } if(flexsearch.ctx.size){ let data = []; let stmt = new pgp.helpers.ColumnSet(["ctx", "key", "res", "id"], { table: this.id + ".ctx" + this.field }); for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ for(let j = 0; j < ids.length; j++){ data.push({ ctx: ctx_key, key: key, res: i, id: ids[j] }); if(data.length === MAXIMUM_QUERY_VARS){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); data = []; } } } } } } if(data.length){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); } } if(flexsearch.tag){ let data = []; let stmt = new pgp.helpers.ColumnSet(["tag", "id"],{ table: this.id + ".tag" + this.field }); for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; for(let j = 0; j < ids.length; j++){ data.push({ tag, id: ids[j] }); if(data.length === MAXIMUM_QUERY_VARS){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); data = []; } } } if(data.length){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); } } if(batch.length){ return trx.batch(batch); } }); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; PostgresDB.prototype.remove = function(ids){ if(!ids && ids !== 0){ return; } if(typeof ids !== "object"){ ids = [ids]; } if(!ids.length){ return; } return this.transaction(function(trx){ ids = [ids]; return trx.batch([ trx.none({ text: "DELETE FROM " + this.id + ".map" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".tag" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".reg WHERE id = ANY ($1)", rowMode: "array" }, ids) ]); }); }; module.exports = PostgresDB; ================================================ FILE: dist/db/redis/index.cjs ================================================ 'use strict'; var redis = require('redis'); /** * @param {*} value * @param {*} default_value * @param {*=} merge_value * @return {*} */ /** * @param {Map|Set} val * @param {boolean=} stringify * @return {Array} */ function toArray(val, stringify){ const result = []; for(const key of val.keys()){ result.push("" + key ); } return result; } const defaults = { host: "localhost", port: "6379", user: null, pass: null }; let DB, TRX; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } /** * @constructor * @implements StorageInterface */ function RedisDB(name, config = {}){ if(!this || this.constructor !== RedisDB){ return new RedisDB(name, config); } if(typeof name === "object"){ config = name; name = name.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } this.id = (name ? sanitize(name) : "flexsearch") + "|"; this.field = config.field ? "-" + sanitize(config.field) : ""; this.type = config.type || ""; this.fastupdate = true; this.db = config.db || DB || null; this.support_tag_search = true; this.resolution = 9; this.resolution_ctx = 9; Object.assign(defaults, config); this.db && delete defaults.db; } RedisDB.prototype.mount = function(flexsearch){ if(flexsearch.index){ return flexsearch.mount(this); } flexsearch.db = this; this.resolution = flexsearch.resolution; this.resolution_ctx = flexsearch.resolution_ctx; return this.open(); }; RedisDB.prototype.open = async function(){ if(this.db){ return this.db } if(DB){ return this.db = DB; } let url = defaults.url; if(!url){ url = defaults.user ? `redis://${defaults.user}:${defaults.pass}@${defaults.host}:${defaults.port}` : `redis://${defaults.host}:${defaults.port}`; } return this.db = DB = await redis.createClient(url) .on("error", err => console.error(err)) .connect(); }; RedisDB.prototype.close = async function(){ DB && await this.db.disconnect(); this.db = DB = null; return this; }; RedisDB.prototype.destroy = function(){ return this.clear(true); }; RedisDB.prototype.clear = function(destroy = false){ if(!this.id) return; const self = this; function unlink(keys){ return keys.length && self.db.unlink(keys); } return Promise.all([ this.db.keys(this.id + "map" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "ctx" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "tag" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "ref" + (destroy ? "" : this.field) + "*").then(unlink), unlink([ this.id + "cfg" + (destroy ? "*" : this.field), this.id + "doc", this.id + "reg" ]) ]); }; function create_result(range, type, resolve, enrich, resolution){ if(resolve){ for(let i = 0, tmp, id; i < range.length; i++){ tmp = range[i]; id = type === "number" ? parseInt(tmp.value || tmp, 10) : tmp.value || tmp; range[i] = enrich ? { id, doc: tmp.doc } : id; } return range; } else { let result = []; for(let i = 0, tmp, id, score; i < range.length; i++){ tmp = range[i]; id = type === "number" ? parseInt(tmp.value || tmp, 10) : tmp.value || tmp; score = Math.max(resolution - tmp.score, 0); result[score] || (result[score] = []); result[score].push(id); } return result; } } RedisDB.prototype.get = function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false, tags){ if(tags){ const flexsearch = { depth: !!ctx }; const query = ctx ? [ctx, key] : [key]; return this.search(flexsearch, query, limit, offset, false, resolve, enrich, tags); } const type = this.type; const self = this; let result; if(ctx){ result = this.db[resolve ? "zRange" : "zRangeWithScores"]( this.id + "ctx" + this.field + ":" + ctx + ":" + key, "" + offset, "" + (offset + limit - 1), { REV: true } ); } else { result = this.db[resolve ? "zRange" : "zRangeWithScores"]( this.id + "map" + this.field + ":" + key, "" + offset, "" + (offset + limit - 1), { REV: true } ); } return result.then(async function(range){ if(!range.length) return range; if(enrich) range = await self.enrich(range); return create_result(range, type, resolve, enrich, ctx ? self.resolution_ctx : self.resolution); }); }; RedisDB.prototype.tag = function(tag, limit = 0, offset = 0, enrich = false){ const self = this; return this.db.sMembers(this.id + "tag" + this.field + ":" + tag).then(function(ids){ if(!ids || !ids.length || offset >= ids.length) return []; if(!limit && !offset) return ids; const result = ids.slice(offset, offset + limit); return enrich ? self.enrich(result) : result; }); }; RedisDB.prototype.enrich = function(ids){ if(typeof ids !== "object"){ ids = [ids]; } return this.db.hmGet(this.id + "doc", this.type === "number" ? ids.map(i => "" + i) : ids).then(function(res){ for(let i = 0; i < res.length; i++){ res[i] = { id: ids[i], doc: res[i] && JSON.parse(res[i]) }; } return res; }); }; RedisDB.prototype.has = function(id){ return this.db.sIsMember(this.id + "reg", "" + id).then(function(res){ return !!res; }); }; RedisDB.prototype.search = function(flexsearch, query, limit = 100, offset = 0, suggest = false, resolve = true, enrich = false, tags){ const ctx = query.length > 1 && flexsearch.depth; let result; let params = []; let weights = []; if(ctx){ const key = this.id + "ctx" + this.field + ":"; let keyword = query[0]; let term; for(let i = 1, swap; i < query.length; i++){ term = query[i]; swap = flexsearch.bidirectional && (term > keyword); params.push(key + (swap ? term : keyword) + ":" + (swap ? keyword : term)); weights.push(1); keyword = term; } } else { const key = this.id + "map" + this.field + ":"; for(let i = 0; i < query.length; i++){ params.push(key + query[i]); weights.push(1); } } query = params; const type = this.type; let key = this.id + "tmp:" + Math.random(); if(suggest){ const multi = this.db.multi(); multi.zInterStore(key, query, { AGGREGATE: "SUM" }); query.push(key); weights.push(query.length); multi.zUnionStore(key, query, { WEIGHTS: weights, AGGREGATE: "SUM" }); { if(tags){ query = [key]; for(let i = 0; i < tags.length; i += 2){ query.push(this.id + "tag-" + sanitize(tags[i]) + ":" + tags[i + 1]); } multi.zInterStore(key, query, { AGGREGATE: "MAX" }); } } result = multi [(resolve ? "zRange" : "zRangeWithScores")]( key, "" + offset, "" + (offset + limit - 1), { REV: true } ) .unlink(key) .exec(); } else { if(tags) for(let i = 0; i < tags.length; i += 2){ query.push(this.id + "tag-" + sanitize(tags[i]) + ":" + tags[i + 1]); } result = this.db.multi() .zInterStore(key, query, { AGGREGATE: "MAX" }) [(resolve ? "zRange" : "zRangeWithScores")]( key, "" + offset, "" + (offset + limit - 1), { REV: true } ) .unlink(key) .exec(); } const self = this; return result.then(async function(range){ range = suggest && tags ? range[3] : range[suggest ? 2 : 1]; if(!range.length) return range; if(enrich) range = await self.enrich(range); return create_result(range, type, resolve, enrich, ctx ? self.resolution_ctx : self.resolution); }); }; RedisDB.prototype.info = function(){ }; RedisDB.prototype.transaction = function(task, callback){ if(TRX){ return task.call(this, TRX); } TRX = this.db.multi(); let promise1 = task.call(this, TRX); let promise2 = TRX.exec(); TRX = null; return Promise.all([promise1, promise2]).then(function(){ callback && callback(); }); }; RedisDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ /** @dict */ task = tasks[i]; if(task["del"]){ removals.push(task["del"]); } else if(task["ins"]); } if(removals.length){ await this.remove(removals); } if(!flexsearch.reg.size){ return; } await this.transaction(function(trx){ let refs = new Map(); for(const item of flexsearch.map){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ let result = []; for(let j = 0; j < ids.length; j++){ result.push({ score: this.resolution - i, value: "" + ids[j] }); } if(typeof ids[0] === "number"){ this.type = "number"; } const ref = this.id + "map" + this.field + ":" + key; trx.zAdd(ref, result); if(this.fastupdate) for(let j = 0, id; j < ids.length; j++){ id = ids[j]; let tmp = refs.get(id); tmp || refs.set(id, tmp = []); tmp.push(ref); } } } } if(this.fastupdate) for(const item of refs){ const key = item[0]; const value = item[1]; trx.sAdd(this.id + "ref" + this.field + ":" + key, value); } refs = new Map(); for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ let result = []; for(let j = 0; j < ids.length; j++){ result.push({ score: this.resolution_ctx - i, value: "" + ids[j] }); } if(typeof ids[0] === "number"){ this.type = "number"; } const ref = this.id + "ctx" + this.field + ":" + ctx_key + ":" + key; trx.zAdd(ref, result); if(this.fastupdate) for(let j = 0, id; j < ids.length; j++){ id = ids[j]; let tmp = refs.get(id); tmp || refs.set(id, tmp = []); tmp.push(ref); } } } } } if(this.fastupdate) for(const item of refs){ const key = item[0]; const value = item[1]; trx.sAdd(this.id + "ref" + this.field + ":" + key, value); } if(flexsearch.store){ for(const item of flexsearch.store.entries()){ const id = item[0]; const doc = item[1]; doc && trx.hSet(this.id + "doc", "" + id, JSON.stringify(doc)); } } if(!flexsearch.bypass){ let ids = toArray(flexsearch.reg); if(ids.length){ trx.sAdd(this.id + "reg", ids); } } if(flexsearch.tag){ for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; let result = []; if(typeof ids[0] === "number"){ for(let i = 0; i < ids.length; i++){ result[i] = "" + ids[i]; } } else { result = ids; } trx.sAdd(this.id + "tag" + this.field + ":" + tag, result); } } }); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; RedisDB.prototype.remove = function(ids){ if(!ids && ids !== 0){ return; } if(typeof ids !== "object"){ ids = [ids]; } if(!ids.length){ return; } return this.transaction(async function(trx){ while(ids.length){ let next; if(ids.length > 10000){ next = ids.slice(10000); ids = ids.slice(0, 10000); } if(typeof ids[0] === "number"){ for(let i = 0; i < ids.length; i++){ ids[i] = "" + ids[i]; } } const check = await this.db.smIsMember(this.id + "reg", ids); for(let i = 0, id; i < ids.length; i++){ if(!check[i]) continue; id = "" + ids[i]; if(this.fastupdate){ const ref = await this.db.sMembers(this.id + "ref" + this.field + ":" + id); if(ref){ for(let j = 0; j < ref.length; j++){ trx.zRem(ref[j], id); } trx.unlink(this.id + "ref" + this.field + ":" + id); } } trx.hDel(this.id + "doc", id); trx.sRem(this.id + "reg", id); } if(next) ids = next; else break; } }); }; module.exports = RedisDB; ================================================ FILE: dist/db/sqlite/index.cjs ================================================ 'use strict'; var sqlite3 = require('sqlite3'); var path = require('path'); /** * @param {*} value * @param {*} default_value * @param {*=} merge_value * @return {*} */ function concat(arrays){ return [].concat.apply([], arrays); } /** * @param {Map|Set} val * @param {boolean=} stringify * @return {Array} */ function toArray(val, stringify){ const result = []; for(const key of val.keys()){ result.push(key); } return result; } const MAXIMUM_QUERY_VARS = 16000; const fields = ["map", "ctx", "reg", "tag", "cfg"]; const types = { "text": "text", "char": "text", "varchar": "text", "string": "text", "number": "int", "numeric": "int", "integer": "int", "smallint": "int", "tinyint": "int", "mediumint": "int", "int": "int", "int8": "int", "uint8": "int", "int16": "int", "uint16": "int", "int32": "int", "uint32": "bigint", "int64": "bigint", "bigint": "bigint" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } const TRX = Object.create(null); const Index = Object.create(null); /** * @constructor * @implements StorageInterface */ function SqliteDB(name, config = {}){ if(!this || this.constructor !== SqliteDB){ return new SqliteDB(name, config); } if(typeof name === "object"){ config = name; name = name.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } this.id = config.path || ( name === ":memory:" ? name : "flexsearch" + (name ? "-" + sanitize(name) : "") + ".sqlite" ); this.field = config.field ? "_" + sanitize(config.field) : ""; this.support_tag_search = true; this.db = config.db || Index[this.id] || null; this.type = config.type ? types[config.type.toLowerCase()] : "string"; if(!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); } SqliteDB.prototype.mount = function(flexsearch){ if(flexsearch.index){ return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; SqliteDB.prototype.open = async function(){ if(!this.db){ if(!(this.db = Index[this.id])){ let filepath = this.id; if(filepath !== ":memory:"){ if(filepath[0] !== "/" && filepath[0] !== "\\"){ const dir = process.cwd(); filepath = path.join(dir, this.id); } } this.db = Index[this.id] = new sqlite3.Database(filepath); } } const db = this.db; for(let i = 0; i < fields.length; i++){ const exist = await this.promisfy({ method: "get", stmt: "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?) as exist", params: [fields[i] + (fields[i] === "reg" ? "" : this.field)] }); if(!exist || !exist.exist){ let stmt, stmt_index; switch(fields[i]){ case "map": stmt = ` CREATE TABLE IF NOT EXISTS main.map${this.field}( key TEXT NOT NULL, res INTEGER NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS map_key_index${this.field} ON map${this.field} (key); CREATE INDEX IF NOT EXISTS map_id_index${this.field} ON map${this.field} (id); `; break; case "ctx": stmt = ` CREATE TABLE IF NOT EXISTS main.ctx${this.field}( ctx TEXT NOT NULL, key TEXT NOT NULL, res INTEGER NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS ctx_key_index${this.field} ON ctx${this.field} (ctx, key); CREATE INDEX IF NOT EXISTS ctx_id_index${this.field} ON ctx${this.field} (id); `; break; case "tag": stmt = ` CREATE TABLE IF NOT EXISTS main.tag${this.field}( tag TEXT NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS tag_index${this.field} ON tag${this.field} (tag); CREATE INDEX IF NOT EXISTS tag_id_index${this.field} ON tag${this.field} (id); `; break; case "reg": stmt = ` CREATE TABLE IF NOT EXISTS main.reg( id ${this.type} NOT NULL CONSTRAINT reg_pk${this.field} PRIMARY KEY, doc TEXT DEFAULT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS reg_index ON reg (id); `; break; case "cfg": stmt = ` CREATE TABLE IF NOT EXISTS main.cfg${this.field} ( cfg TEXT NOT NULL ); `; break; } await new Promise(function(resolve, reject){ db.exec(stmt, function(err, rows){ if(err) return reject(err); stmt_index ? db.exec(stmt_index, function(err, rows){ if(err) return reject(err); resolve(rows); }) : resolve(rows); }); }); } } db.exec("PRAGMA optimize = 0x10002"); return db; }; SqliteDB.prototype.close = function(){ this.db && this.db.close(); this.db = null; Index[this.id] = null; TRX[this.id] = null; return this; }; SqliteDB.prototype.destroy = function(){ return this.transaction(function(){ this.db.run("DROP TABLE IF EXISTS main.map" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.ctx" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.tag" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.cfg" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.reg;"); }); }; SqliteDB.prototype.clear = function(){ return this.transaction(function(){ this.db.run("DELETE FROM main.map" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.ctx" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.tag" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.cfg" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.reg WHERE 1;"); }); }; function create_result(rows, resolve, enrich){ if(resolve){ for(let i = 0; i < rows.length; i++){ if(enrich){ if(rows[i].doc){ rows[i].doc = JSON.parse(rows[i].doc); } } else { rows[i] = rows[i].id; } } return rows; } else { const arr = []; for(let i = 0, row; i < rows.length; i++){ row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id ); } return arr; } } SqliteDB.prototype.get = function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false, tags){ let result; let stmt = ''; let params = ctx ? [ctx, key] : [key]; let table = "main." + (ctx ? "ctx" : "map") + this.field; if(tags){ for(let i = 0; i < tags.length; i+=2){ stmt += ` AND ${ table }.id IN (SELECT id FROM main.tag_${ sanitize(tags[i]) } WHERE tag = ?)`; params.push(tags[i + 1]); } } if(ctx){ result = this.promisfy({ method: "all", stmt: ` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${ table }.id ` : "" } WHERE ctx = ? AND key = ? ${stmt} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params }); } else { result = this.promisfy({ method: "all", stmt: ` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${ table }.id ` : "" } WHERE key = ? ${stmt} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params }); } return result.then(function(rows){ return create_result(rows, resolve, enrich); }); }; SqliteDB.prototype.tag = function(tag, limit = 0, offset = 0, enrich = false){ const table = "main.tag" + this.field; const promise = this.promisfy({ method: "all", stmt: ` SELECT ${ table }.id ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${ table }.id ` : "" } WHERE tag = ? ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params: [tag] }); enrich || promise.then(function(rows){ return create_result(rows, true, false); }); return promise; }; function build_params(length, single_param){ let stmt = single_param ? ",(?)" : ",?"; for(let i = 1; i < length;){ if(i <= (length - i)){ stmt += stmt; i *= 2; } else { stmt += stmt.substring(0, (length - i) * (single_param ? 4 : 2)); break; } } return stmt.substring(1); } SqliteDB.prototype.enrich = function(ids){ const result = []; const promises = []; if(typeof ids !== "object"){ ids = [ids]; } for(let count = 0; count < ids.length;){ const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; const stmt = build_params(chunk.length); count += chunk.length; promises.push(this.promisfy({ method: "all", stmt: `SELECT id, doc FROM main.reg WHERE id IN (${stmt})`, params: chunk })); } return Promise.all(promises).then(function(promises){ for(let i = 0, res; i < promises.length; i++){ res = promises[i]; if(res && res.length){ for(let i = 0, doc; i < res.length; i++){ if((doc = res[i].doc)){ res[i].doc = JSON.parse(doc); } } result.push(res); } } return result.length === 1 ? result[0] : result.length > 1 ? concat(result) : result; }); }; SqliteDB.prototype.has = function(id){ return this.promisfy({ method: "get", stmt: `SELECT EXISTS(SELECT 1 FROM main.reg WHERE id = ?) as exist`, params: [id] }).then(function(result){ return !!(result && result.exist); }); }; SqliteDB.prototype.search = function(flexsearch, query, limit = 100, offset = 0, suggest = false, resolve = true, enrich = false, tags){ let rows; if(query.length > 1 && flexsearch.depth){ let stmt = ""; let params = []; let keyword = query[0]; let term; for(let i = 1; i < query.length; i++){ term = query[i]; const swap = flexsearch.bidirectional && (term > keyword); stmt += (stmt ? " OR " : "") + `(ctx = ? AND key = ?)`; params.push(swap ? term : keyword, swap ? keyword : term); keyword = term; } if(tags){ stmt = "(" + stmt + ")"; for(let i = 0; i < tags.length; i+=2){ stmt += ` AND id IN (SELECT id FROM main.tag_${ sanitize(tags[i]) } WHERE tag = ?)`; params.push(tags[i + 1]); } } rows = this.promisfy({ method: "all", stmt: ` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" }(res) as res FROM main.ctx${ this.field } WHERE ${ stmt } GROUP BY id ) as r ${ enrich ? ` LEFT JOIN main.reg ON main.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + (query.length - 1) } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params }); } else { let stmt = ""; let query_length = query.length; for(let i = 0; i < query_length; i++){ stmt += (stmt ? " OR " : "") + `key = ?`; } if(tags){ stmt = "(" + stmt + ")"; for(let i = 0; i < tags.length; i+=2){ stmt += ` AND id IN (SELECT id FROM main.tag_${sanitize(tags[i])} WHERE tag = ?)`; query.push(tags[i + 1]); } } rows = this.promisfy({ method: "all", stmt: ` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" }(res) as res FROM main.map${ this.field } WHERE ${ stmt } GROUP BY id ) as r ${ enrich ? ` LEFT JOIN main.reg ON main.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + query_length } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params: query }); } return rows.then(function(rows){ return create_result(rows, resolve, enrich); }); }; SqliteDB.prototype.info = function(){ }; SqliteDB.prototype.transaction = async function(task, callback){ if(TRX[this.id]){ return await task.call(this); } const db = this.db; const self = this; return TRX[this.id] = new Promise(function(resolve, reject){ db.exec("PRAGMA optimize"); db.exec('PRAGMA busy_timeout = 5000'); db.exec("BEGIN"); db.parallelize(function(){ task.call(self); }); db.exec("COMMIT", function(err, rows){ TRX[self.id] = null; if(err) return reject(err); callback && callback(rows); resolve(rows); db.exec("PRAGMA shrink_memory"); }); }); }; SqliteDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; let inserts = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ task = tasks[i]; if(typeof task["del"] !== "undefined"){ removals.push(task["del"]); } else if(typeof task["ins"] !== "undefined"){ inserts.push(task["ins"]); } } if(removals.length){ await this.remove(removals); } if(!flexsearch.reg.size){ return; } await this.transaction(function(){ for(const item of flexsearch.map){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ let stmt = ""; let params = []; for(let j = 0; j < ids.length; j++){ stmt += (stmt ? "," : "") + "(?,?,?)"; params.push(key, i, ids[j]); if((j === ids.length - 1) || (params.length + 3 > MAXIMUM_QUERY_VARS)){ this.db.run( "INSERT INTO main.map" + this.field + " (key, res, id) VALUES " + stmt , params); stmt = ""; params = []; } } } } } for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ let stmt = ""; let params = []; for(let j = 0; j < ids.length; j++){ stmt += (stmt ? "," : "") + "(?,?,?,?)"; params.push(ctx_key, key, i, ids[j]); if((j === ids.length - 1) || (params.length + 4 > MAXIMUM_QUERY_VARS)){ this.db.run("INSERT INTO main.ctx" + this.field + " (ctx, key, res, id) VALUES " + stmt, params); stmt = ""; params = []; } } } } } } if(flexsearch.store){ let stmt = ""; let chunk = []; for(const item of flexsearch.store.entries()){ const id = item[0]; const doc = item[1]; stmt += (stmt ? "," : "") + "(?,?)"; chunk.push(id, typeof doc === "object" ? JSON.stringify(doc) : doc || null ); if(chunk.length + 2 > MAXIMUM_QUERY_VARS){ this.db.run("INSERT INTO main.reg (id, doc) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); stmt = ""; chunk = []; } } if(chunk.length){ this.db.run("INSERT INTO main.reg (id, doc) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); } } else if(!flexsearch.bypass){ let ids = toArray(flexsearch.reg); for(let count = 0; count < ids.length;){ const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; const stmt = build_params(chunk.length, true); this.db.run("INSERT INTO main.reg (id) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); } } if(flexsearch.tag){ let stmt = ""; let chunk = []; for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; for(let i = 0; i < ids.length; i++){ stmt += (stmt ? "," : "") + "(?,?)"; chunk.push(tag, ids[i]); } if(chunk.length + 2 > MAXIMUM_QUERY_VARS){ this.db.run("INSERT INTO main.tag" + this.field + " (tag, id) VALUES " + stmt, chunk); stmt = ""; chunk = []; } } if(chunk.length){ this.db.run("INSERT INTO main.tag" + this.field + " (tag, id) VALUES " + stmt, chunk); } } }); if(inserts.length){ await this.cleanup(); } flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; SqliteDB.prototype.remove = function(ids){ if(typeof ids !== "object"){ ids = [ids]; } let next; if(ids.length > MAXIMUM_QUERY_VARS){ next = ids.slice(MAXIMUM_QUERY_VARS); ids = ids.slice(0, MAXIMUM_QUERY_VARS); } const self = this; return this.transaction(function(){ const stmt = build_params(ids.length); this.db.run("DELETE FROM main.map" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.ctx" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.tag" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.reg WHERE id IN (" + stmt + ")", ids); }).then(function(result){ return next ? self.remove(next) : result; }); }; SqliteDB.prototype.cleanup = function(){ return this.transaction(function(){ this.db.run( "DELETE FROM main.map" + this.field + " " + "WHERE ROWID IN (" + "SELECT ROWID FROM (" + "SELECT ROWID, row_number() OVER dupes AS count " + "FROM main.map" + this.field + " _t " + "WINDOW dupes AS (PARTITION BY id, key ORDER BY res) " + ") " + "WHERE count > 1" + ")" ); this.db.run( "DELETE FROM main.ctx" + this.field + " " + "WHERE ROWID IN (" + "SELECT ROWID FROM (" + "SELECT ROWID, row_number() OVER dupes AS count " + "FROM main.ctx" + this.field + " _t " + "WINDOW dupes AS (PARTITION BY id, ctx, key ORDER BY res) " + ") " + "WHERE count > 1" + ")" ); }); }; SqliteDB.prototype.promisfy = function(opt){ const db = this.db; return new Promise(function(resolve, reject){ db[opt.method](opt.stmt, opt.params || [], function(err, rows){ opt.callback && opt.callback(rows); err ? reject(err) : resolve(rows); }); }); }; module.exports = SqliteDB; ================================================ FILE: dist/flexsearch.bundle.debug.js ================================================ /**! * FlexSearch.js v0.8.214 (Bundle/Debug) * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ (function _f(self){'use strict';if(typeof module!=='undefined')self=module;else if(typeof process !== 'undefined')self=process;self._factory=_f; var w; function H(a, c, b) { const e = typeof b, d = typeof a; if (e !== "undefined") { if (d !== "undefined") { if (b) { if (d === "function" && e === d) { return function(k) { return a(b(k)); }; } c = a.constructor; if (c === b.constructor) { if (c === Array) { return b.concat(a); } if (c === Map) { var f = new Map(b); for (var g of a) { f.set(g[0], g[1]); } return f; } if (c === Set) { g = new Set(b); for (f of a.values()) { g.add(f); } return g; } } } return a; } return b; } return d === "undefined" ? c : a; } function aa(a, c) { return typeof a === "undefined" ? c : a; } function I() { return Object.create(null); } function N(a) { return typeof a === "string"; } function ba(a) { return typeof a === "object"; } function ca(a, c) { if (N(c)) { a = a[c]; } else { for (let b = 0; a && b < c.length; b++) { a = a[c[b]]; } } return a; } ;const da = /[^\p{L}\p{N}]+/u, fa = /(\d{3})/g, ha = /(\D)(\d{3})/g, ia = /(\d{3})(\D)/g, ja = /[\u0300-\u036f]/g; function ka(a = {}) { if (!this || this.constructor !== ka) { return new ka(...arguments); } if (arguments.length) { for (a = 0; a < arguments.length; a++) { this.assign(arguments[a]); } } else { this.assign(a); } } w = ka.prototype; w.assign = function(a) { this.normalize = H(a.normalize, !0, this.normalize); let c = a.include, b = c || a.exclude || a.split, e; if (b || b === "") { if (typeof b === "object" && b.constructor !== RegExp) { let d = ""; e = !c; c || (d += "\\p{Z}"); b.letter && (d += "\\p{L}"); b.number && (d += "\\p{N}", e = !!c); b.symbol && (d += "\\p{S}"); b.punctuation && (d += "\\p{P}"); b.control && (d += "\\p{C}"); if (b = b.char) { d += typeof b === "object" ? b.join("") : b; } try { this.split = new RegExp("[" + (c ? "^" : "") + d + "]+", "u"); } catch (f) { console.error("Your split configuration:", b, "is not supported on this platform. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } } else { this.split = b, e = b === !1 || "a1a".split(b).length < 2; } this.numeric = H(a.numeric, e); } else { try { this.split = H(this.split, da); } catch (d) { console.warn("This platform does not support unicode regex. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } this.numeric = H(a.numeric, H(this.numeric, !0)); } this.prepare = H(a.prepare, null, this.prepare); this.finalize = H(a.finalize, null, this.finalize); b = a.filter; this.filter = typeof b === "function" ? b : H(b && new Set(b), null, this.filter); this.dedupe = H(a.dedupe, !0, this.dedupe); this.matcher = H((b = a.matcher) && new Map(b), null, this.matcher); this.mapper = H((b = a.mapper) && new Map(b), null, this.mapper); this.stemmer = H((b = a.stemmer) && new Map(b), null, this.stemmer); this.replacer = H(a.replacer, null, this.replacer); this.minlength = H(a.minlength, 1, this.minlength); this.maxlength = H(a.maxlength, 1024, this.maxlength); this.rtl = H(a.rtl, !1, this.rtl); if (this.cache = b = H(a.cache, !0, this.cache)) { this.F = null, this.L = typeof b === "number" ? b : 2e5, this.B = new Map(), this.D = new Map(), this.I = this.H = 128; } this.h = ""; this.J = null; this.A = ""; this.K = null; if (this.matcher) { for (const d of this.matcher.keys()) { this.h += (this.h ? "|" : "") + d; } } if (this.stemmer) { for (const d of this.stemmer.keys()) { this.A += (this.A ? "|" : "") + d; } } return this; }; w.addStemmer = function(a, c) { this.stemmer || (this.stemmer = new Map()); this.stemmer.set(a, c); this.A += (this.A ? "|" : "") + a; this.K = null; this.cache && la(this); return this; }; w.addFilter = function(a) { typeof a === "function" ? this.filter = a : (this.filter || (this.filter = new Set()), this.filter.add(a)); this.cache && la(this); return this; }; w.addMapper = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length > 1) { return this.addMatcher(a, c); } this.mapper || (this.mapper = new Map()); this.mapper.set(a, c); this.cache && la(this); return this; }; w.addMatcher = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length < 2 && (this.dedupe || this.mapper)) { return this.addMapper(a, c); } this.matcher || (this.matcher = new Map()); this.matcher.set(a, c); this.h += (this.h ? "|" : "") + a; this.J = null; this.cache && la(this); return this; }; w.addReplacer = function(a, c) { if (typeof a === "string") { return this.addMatcher(a, c); } this.replacer || (this.replacer = []); this.replacer.push(a, c); this.cache && la(this); return this; }; w.encode = function(a, c) { if (this.cache && a.length <= this.H) { if (this.F) { if (this.B.has(a)) { return this.B.get(a); } } else { this.F = setTimeout(la, 50, this); } } this.normalize && (typeof this.normalize === "function" ? a = this.normalize(a) : a = ja ? a.normalize("NFKD").replace(ja, "").toLowerCase() : a.toLowerCase()); this.prepare && (a = this.prepare(a)); this.numeric && a.length > 3 && (a = a.replace(ha, "$1 $2").replace(ia, "$1 $2").replace(fa, "$1 ")); const b = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer); let e = [], d = I(), f, g, k = this.split || this.split === "" ? a.split(this.split) : [a]; for (let l = 0, m, p; l < k.length; l++) { if ((m = p = k[l]) && !(m.length < this.minlength || m.length > this.maxlength)) { if (c) { if (d[m]) { continue; } d[m] = 1; } else { if (f === m) { continue; } f = m; } if (b) { e.push(m); } else { if (!this.filter || (typeof this.filter === "function" ? this.filter(m) : !this.filter.has(m))) { if (this.cache && m.length <= this.I) { if (this.F) { var h = this.D.get(m); if (h || h === "") { h && e.push(h); continue; } } else { this.F = setTimeout(la, 50, this); } } if (this.stemmer) { this.K || (this.K = new RegExp("(?!^)(" + this.A + ")$")); let u; for (; u !== m && m.length > 2;) { u = m, m = m.replace(this.K, r => this.stemmer.get(r)); } } if (m && (this.mapper || this.dedupe && m.length > 1)) { h = ""; for (let u = 0, r = "", t, n; u < m.length; u++) { t = m.charAt(u), t === r && this.dedupe || ((n = this.mapper && this.mapper.get(t)) || n === "" ? n === r && this.dedupe || !(r = n) || (h += n) : h += r = t); } m = h; } this.matcher && m.length > 1 && (this.J || (this.J = new RegExp("(" + this.h + ")", "g")), m = m.replace(this.J, u => this.matcher.get(u))); if (m && this.replacer) { for (h = 0; m && h < this.replacer.length; h += 2) { m = m.replace(this.replacer[h], this.replacer[h + 1]); } } this.cache && p.length <= this.I && (this.D.set(p, m), this.D.size > this.L && (this.D.clear(), this.I = this.I / 1.1 | 0)); if (m) { if (m !== p) { if (c) { if (d[m]) { continue; } d[m] = 1; } else { if (g === m) { continue; } g = m; } } e.push(m); } } } } } this.finalize && (e = this.finalize(e) || e); this.cache && a.length <= this.H && (this.B.set(a, e), this.B.size > this.L && (this.B.clear(), this.H = this.H / 1.1 | 0)); return e; }; function la(a) { a.F = null; a.B.clear(); a.D.clear(); } ;function ma(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : b = a); b && (a = b.query || a, c = b.limit || c); let e = "" + (c || 0); b && (e += (b.offset || 0) + !!b.context + !!b.suggest + (b.resolve !== !1) + (b.resolution || this.resolution) + (b.boost || 0)); a = ("" + a).toLowerCase(); this.cache || (this.cache = new na()); let d = this.cache.get(a + e); if (!d) { const f = b && b.cache; f && (b.cache = !1); d = this.search(a, c, b); f && (b.cache = f); this.cache.set(a + e, d); } return d; } function na(a) { this.limit = a && a !== !0 ? a : 1000; this.cache = new Map(); this.h = ""; } na.prototype.set = function(a, c) { this.cache.set(this.h = a, c); this.cache.size > this.limit && this.cache.delete(this.cache.keys().next().value); }; na.prototype.get = function(a) { const c = this.cache.get(a); c && this.h !== a && (this.cache.delete(a), this.cache.set(this.h = a, c)); return c; }; na.prototype.remove = function(a) { for (const c of this.cache) { const b = c[0]; c[1].includes(a) && this.cache.delete(b); } }; na.prototype.clear = function() { this.cache.clear(); this.h = ""; }; const oa = {normalize:!1, numeric:!1, dedupe:!1}; const ra = {}; const sa = new Map([["b", "p"], ["v", "f"], ["w", "f"], ["z", "s"], ["x", "s"], ["d", "t"], ["n", "m"], ["c", "k"], ["g", "k"], ["j", "k"], ["q", "k"], ["i", "e"], ["y", "e"], ["u", "o"]]); const ta = new Map([["ae", "a"], ["oe", "o"], ["sh", "s"], ["kh", "k"], ["th", "t"], ["ph", "f"], ["pf", "f"]]), ua = [/([^aeo])h(.)/g, "$1$2", /([aeo])h([^aeo]|$)/g, "$1$2", /(.)\1+/g, "$1"]; const va = {a:"", e:"", i:"", o:"", u:"", y:"", b:1, f:1, p:1, v:1, c:2, g:2, j:2, k:2, q:2, s:2, x:2, z:2, "\u00df":2, d:3, t:3, l:4, m:5, n:5, r:6}; var wa = {Exact:oa, Default:ra, Normalize:ra, LatinBalance:{mapper:sa}, LatinAdvanced:{mapper:sa, matcher:ta, replacer:ua}, LatinExtra:{mapper:sa, replacer:ua.concat([/(?!^)[aeo]/g, ""]), matcher:ta}, LatinSoundex:{dedupe:!1, include:{letter:!0}, finalize:function(a) { for (let b = 0; b < a.length; b++) { var c = a[b]; let e = c.charAt(0), d = va[e]; for (let f = 1, g; f < c.length && (g = c.charAt(f), g === "h" || g === "w" || !(g = va[g]) || g === d || (e += g, d = g, e.length !== 4)); f++) { } a[b] = e; } }}, CJK:{split:""}, LatinExact:oa, LatinDefault:ra, LatinSimple:ra}; function xa(a, c, b, e) { let d = []; for (let f = 0, g; f < a.index.length; f++) { if (g = a.index[f], c >= g.length) { c -= g.length; } else { c = g[e ? "splice" : "slice"](c, b); const k = c.length; if (k && (d = d.length ? d.concat(c) : c, b -= k, e && (a.length -= k), !b)) { break; } c = 0; } } return d; } function Aa(a) { if (!this || this.constructor !== Aa) { return new Aa(a); } this.index = a ? [a] : []; this.length = a ? a.length : 0; const c = this; return new Proxy([], {get(b, e) { if (e === "length") { return c.length; } if (e === "push") { return function(d) { c.index[c.index.length - 1].push(d); c.length++; }; } if (e === "pop") { return function() { if (c.length) { return c.length--, c.index[c.index.length - 1].pop(); } }; } if (e === "indexOf") { return function(d) { let f = 0; for (let g = 0, k, h; g < c.index.length; g++) { k = c.index[g]; h = k.indexOf(d); if (h >= 0) { return f + h; } f += k.length; } return -1; }; } if (e === "includes") { return function(d) { for (let f = 0; f < c.index.length; f++) { if (c.index[f].includes(d)) { return !0; } } return !1; }; } if (e === "slice") { return function(d, f) { return xa(c, d || 0, f || c.length, !1); }; } if (e === "splice") { return function(d, f) { return xa(c, d || 0, f || c.length, !0); }; } if (e === "constructor") { return Array; } if (typeof e !== "symbol") { return (b = c.index[e / 2 ** 31 | 0]) && b[e]; } }, set(b, e, d) { b = e / 2 ** 31 | 0; (c.index[b] || (c.index[b] = []))[e] = d; c.length++; return !0; }}); } Aa.prototype.clear = function() { this.index.length = 0; }; Aa.prototype.push = function() { }; function Q(a = 8) { if (!this || this.constructor !== Q) { return new Q(a); } this.index = I(); this.h = []; this.size = 0; a > 32 ? (this.B = Ba, this.A = BigInt(a)) : (this.B = Ca, this.A = a); } Q.prototype.get = function(a) { const c = this.index[this.B(a)]; return c && c.get(a); }; Q.prototype.set = function(a, c) { var b = this.B(a); let e = this.index[b]; e ? (b = e.size, e.set(a, c), (b -= e.size) && this.size++) : (this.index[b] = e = new Map([[a, c]]), this.h.push(e), this.size++); }; function R(a = 8) { if (!this || this.constructor !== R) { return new R(a); } this.index = I(); this.h = []; this.size = 0; a > 32 ? (this.B = Ba, this.A = BigInt(a)) : (this.B = Ca, this.A = a); } R.prototype.add = function(a) { var c = this.B(a); let b = this.index[c]; b ? (c = b.size, b.add(a), (c -= b.size) && this.size++) : (this.index[c] = b = new Set([a]), this.h.push(b), this.size++); }; w = Q.prototype; w.has = R.prototype.has = function(a) { const c = this.index[this.B(a)]; return c && c.has(a); }; w.delete = R.prototype.delete = function(a) { const c = this.index[this.B(a)]; c && c.delete(a) && this.size--; }; w.clear = R.prototype.clear = function() { this.index = I(); this.h = []; this.size = 0; }; w.values = R.prototype.values = function*() { for (let a = 0; a < this.h.length; a++) { for (let c of this.h[a].values()) { yield c; } } }; w.keys = R.prototype.keys = function*() { for (let a = 0; a < this.h.length; a++) { for (let c of this.h[a].keys()) { yield c; } } }; w.entries = R.prototype.entries = function*() { for (let a = 0; a < this.h.length; a++) { for (let c of this.h[a].entries()) { yield c; } } }; function Ca(a) { let c = 2 ** this.A - 1; if (typeof a == "number") { return a & c; } let b = 0, e = this.A + 1; for (let d = 0; d < a.length; d++) { b = (b * e ^ a.charCodeAt(d)) & c; } return this.A === 32 ? b + 2 ** 31 : b; } function Ba(a) { let c = BigInt(2) ** this.A - BigInt(1); var b = typeof a; if (b === "bigint") { return a & c; } if (b === "number") { return BigInt(a) & c; } b = BigInt(0); let e = this.A + BigInt(1); for (let d = 0; d < a.length; d++) { b = (b * e ^ BigInt(a.charCodeAt(d))) & c; } return b; } ;let Da, S; async function Ea(a) { a = a.data; var c = a.task; const b = a.id; let e = a.args; switch(c) { case "init": S = a.options || {}; (c = a.factory) ? (Function("return " + c)()(self), Da = new self.FlexSearch.Index(S), delete self.FlexSearch) : Da = new T(S); postMessage({id:b}); break; default: let d; if (c === "export") { if (!S.export || typeof S.export !== "function") { throw Error('Either no extern configuration provided for the Worker-Index or no method was defined on the config property "export".'); } e[1] ? (e[0] = S.export, e[2] = 0, e[3] = 1) : e = null; } if (c === "import") { if (!S.import || typeof S.import !== "function") { throw Error('Either no extern configuration provided for the Worker-Index or no method was defined on the config property "import".'); } e[0] && (a = await S.import.call(Da, e[0]), Da.import(e[0], a)); } else { (d = e && Da[c].apply(Da, e)) && d.then && (d = await d), d && d.await && (d = await d.await), c === "search" && d.result && (d = d.result); } postMessage(c === "search" ? {id:b, msg:d} : {id:b}); } } ;function Fa(a) { Ga.call(a, "add"); Ga.call(a, "append"); Ga.call(a, "search"); Ga.call(a, "update"); Ga.call(a, "remove"); Ga.call(a, "searchCache"); } let Ha, Ia, Ja; function Ka() { Ha = Ja = 0; } function Ga(a) { this[a + "Async"] = function() { const c = arguments; var b = c[c.length - 1]; let e; typeof b === "function" && (e = b, delete c[c.length - 1]); Ha ? Ja || (Ja = Date.now() - Ia >= this.priority * this.priority * 3) : (Ha = setTimeout(Ka, 0), Ia = Date.now()); if (Ja) { const f = this; return new Promise(g => { setTimeout(function() { g(f[a + "Async"].apply(f, c)); }, 0); }); } const d = this[a].apply(this, c); b = d.then ? d : new Promise(f => f(d)); e && b.then(e); return b; }; } ;let V = 0; function La(a = {}, c) { function b(k) { function h(l) { l = l.data || l; const m = l.id, p = m && f.h[m]; p && (p(l.msg), delete f.h[m]); } this.worker = k; this.h = I(); if (this.worker) { d ? this.worker.on("message", h) : this.worker.onmessage = h; if (a.config) { return new Promise(function(l) { V > 1e9 && (V = 0); f.h[++V] = function() { l(f); }; f.worker.postMessage({id:V, task:"init", factory:e, options:a}); }); } this.priority = a.priority || 4; this.encoder = c || null; this.worker.postMessage({task:"init", factory:e, options:a}); return this; } console.warn("Worker is not available on this platform. Please report on Github: https://github.com/nextapps-de/flexsearch/issues"); } if (!this || this.constructor !== La) { return new La(a); } let e = typeof self !== "undefined" ? self._factory : typeof window !== "undefined" ? window._factory : null; e && (e = e.toString()); const d = typeof window === "undefined", f = this, g = Ma(e, d, a.worker); return g.then ? g.then(function(k) { return b.call(f, k); }) : b.call(this, g); } W("add"); W("append"); W("search"); W("update"); W("remove"); W("clear"); W("export"); W("import"); La.prototype.searchCache = ma; Fa(La.prototype); function W(a) { La.prototype[a] = function() { const c = this, b = [].slice.call(arguments); var e = b[b.length - 1]; let d; typeof e === "function" && (d = e, b.pop()); e = new Promise(function(f) { a === "export" && typeof b[0] === "function" && (b[0] = null); V > 1e9 && (V = 0); c.h[++V] = f; c.worker.postMessage({task:a, id:V, args:b}); }); return d ? (e.then(d), this) : e; }; } function Ma(a, c, b) { return c ? typeof module !== "undefined" ? new(require("worker_threads")["Worker"])(__dirname+"/node/node.js") : import("worker_threads").then(function(worker){return new worker["Worker"]((1,eval)("import.meta.dirname")+"/node/node.mjs")}) : a ? new window.Worker(URL.createObjectURL(new Blob(["onmessage=" + Ea.toString()], {type:"text/javascript"}))) : new window.Worker(typeof b === "string" ? b : (0,eval)("import.meta.url").replace("/worker.js", "/worker/worker.js").replace("flexsearch.bundle.module.min.js", "module/worker/worker.js").replace("flexsearch.bundle.module.min.mjs", "module/worker/worker.js"), {type:"module"}); } ;Na.prototype.add = function(a, c, b) { ba(a) && (c = a, a = ca(c, this.key)); if (c && (a || a === 0)) { if (!b && this.reg.has(a)) { return this.update(a, c); } for (let k = 0, h; k < this.field.length; k++) { h = this.B[k]; var e = this.index.get(this.field[k]); if (typeof h === "function") { var d = h(c); d && e.add(a, d, b, !0); } else { if (d = h.G, !d || d(c)) { h.constructor === String ? h = ["" + h] : N(h) && (h = [h]), Oa(c, h, this.D, 0, e, a, h[0], b); } } } if (this.tag) { for (e = 0; e < this.A.length; e++) { var f = this.A[e], g = this.F[e]; d = this.tag.get(g); let k = I(); if (typeof f === "function") { if (f = f(c), !f) { continue; } } else { const h = f.G; if (h && !h(c)) { continue; } f.constructor === String && (f = "" + f); f = ca(c, f); } if (d && f) { N(f) && (f = [f]); for (let h = 0, l, m; h < f.length; h++) { if (l = f[h], !k[l] && (k[l] = 1, (g = d.get(l)) ? m = g : d.set(l, m = []), !b || !m.includes(a))) { if (m.length === 2 ** 31 - 1) { g = new Aa(m); if (this.fastupdate) { for (let p of this.reg.values()) { p.includes(m) && (p[p.indexOf(m)] = g); } } d.set(l, m = g); } m.push(a); this.fastupdate && ((g = this.reg.get(a)) ? g.push(m) : this.reg.set(a, [m])); } } } else { d || console.warn("Tag '" + g + "' was not found"); } } } if (this.store && (!b || !this.store.has(a))) { let k; if (this.h) { k = I(); for (let h = 0, l; h < this.h.length; h++) { l = this.h[h]; if ((b = l.G) && !b(c)) { continue; } let m; if (typeof l === "function") { m = l(c); if (!m) { continue; } l = [l.O]; } else if (N(l) || l.constructor === String) { k[l] = c[l]; continue; } Ra(c, k, l, 0, l[0], m); } } this.store.set(a, k || c); } this.worker && (this.fastupdate || this.reg.add(a)); } return this; }; function Ra(a, c, b, e, d, f) { a = a[d]; if (e === b.length - 1) { c[d] = f || a; } else if (a) { if (a.constructor === Array) { for (c = c[d] = Array(a.length), d = 0; d < a.length; d++) { Ra(a, c, b, e, d); } } else { c = c[d] || (c[d] = I()), d = b[++e], Ra(a, c, b, e, d); } } } function Oa(a, c, b, e, d, f, g, k) { if (a = a[g]) { if (e === c.length - 1) { if (a.constructor === Array) { if (b[e]) { for (c = 0; c < a.length; c++) { d.add(f, a[c], !0, !0); } return; } a = a.join(" "); } d.add(f, a, k, !0); } else { if (a.constructor === Array) { for (g = 0; g < a.length; g++) { Oa(a, c, b, e, d, f, g, k); } } else { g = c[++e], Oa(a, c, b, e, d, f, g, k); } } } } ;function Sa(a, c, b, e) { if (!a.length) { return a; } if (a.length === 1) { return a = a[0], a = b || a.length > c ? a.slice(b, b + c) : a, e ? Ta.call(this, a) : a; } let d = []; for (let f = 0, g, k; f < a.length; f++) { if ((g = a[f]) && (k = g.length)) { if (b) { if (b >= k) { b -= k; continue; } g = g.slice(b, b + c); k = g.length; b = 0; } k > c && (g = g.slice(0, c), k = c); if (!d.length && k >= c) { return e ? Ta.call(this, g) : g; } d.push(g); c -= k; if (!c) { break; } } } d = d.length > 1 ? [].concat.apply([], d) : d[0]; return e ? Ta.call(this, d) : d; } ;function Ua(a, c, b, e) { var d = e[0]; if (d[0] && d[0].query) { return a[c].apply(a, d); } if (!(c !== "and" && c !== "not" || a.result.length || a.await || d.suggest)) { return e.length > 1 && (d = e[e.length - 1]), (e = d.resolve) ? a.await || a.result : a; } let f = [], g = 0, k = 0, h, l, m, p, u; for (c = 0; c < e.length; c++) { if (d = e[c]) { var r = void 0; if (d.constructor === X) { r = d.await || d.result; } else if (d.then || d.constructor === Array) { r = d; } else { g = d.limit || 0; k = d.offset || 0; m = d.suggest; l = d.resolve; h = ((p = d.highlight || a.highlight) || d.enrich) && l; r = d.queue; let t = d.async || r, n = d.index, q = d.query; n ? a.index || (a.index = n) : n = a.index; if (q || d.tag) { if (!n) { throw Error("Resolver can't apply because the corresponding Index was never specified"); } const x = d.field || d.pluck; if (x) { !q || a.query && !p || (a.query = q, a.field = x, a.highlight = p); if (!n.index) { throw Error("Resolver can't apply because the corresponding Document Index was not specified"); } n = n.index.get(x); if (!n) { throw Error("Resolver can't apply because the specified Document Field '" + x + "' was not found"); } } if (r && (u || a.await)) { u = 1; let v; const A = a.C.length, E = new Promise(function(F) { v = F; }); (function(F, B) { E.h = function() { B.index = null; B.resolve = !1; B.enrich = !1; let C = t ? F.searchAsync(B) : F.search(B); if (C.then) { return C.then(function(z) { a.C[A] = z = z.result || z; v(z); return z; }); } C = C.result || C; v(C); return C; }; })(n, Object.assign({}, d)); a.C.push(E); f[c] = E; continue; } else { d.resolve = !1, d.enrich = !1, d.index = null, r = t ? n.searchAsync(d) : n.search(d), d.resolve = l, d.enrich = h, d.index = n; } } else if (d.and) { r = Va(d, "and", n); } else if (d.or) { r = Va(d, "or", n); } else if (d.not) { r = Va(d, "not", n); } else if (d.xor) { r = Va(d, "xor", n); } else { continue; } } r.await ? (u = 1, r = r.await) : r.then ? (u = 1, r = r.then(function(t) { return t.result || t; })) : r = r.result || r; f[c] = r; } } u && !a.await && (a.await = new Promise(function(t) { a.return = t; })); if (u) { const t = Promise.all(f).then(function(n) { for (let q = 0; q < a.C.length; q++) { if (a.C[q] === t) { a.C[q] = function() { return b.call(a, n, g, k, h, l, m, p); }; break; } } Wa(a); }); a.C.push(t); } else if (a.await) { a.C.push(function() { return b.call(a, f, g, k, h, l, m, p); }); } else { return b.call(a, f, g, k, h, l, m, p); } return l ? a.await || a.result : a; } function Va(a, c, b) { a = a[c]; const e = a[0] || a; e.index || (e.index = b); b = new X(e); a.length > 1 && (b = b[c].apply(b, a.slice(1))); return b; } ;X.prototype.or = function() { return Ua(this, "or", Xa, arguments); }; function Xa(a, c, b, e, d, f, g) { a.length && (this.result.length && a.push(this.result), a.length < 2 ? this.result = a[0] : (this.result = Ya(a, c, b, !1, this.h), b = 0)); d && (this.await = null); return d ? this.resolve(c, b, e, g) : this; } ;X.prototype.and = function() { return Ua(this, "and", Za, arguments); }; function Za(a, c, b, e, d, f, g) { if (!f && !this.result.length) { return d ? this.result : this; } let k; if (a.length) { if (this.result.length && a.unshift(this.result), a.length < 2) { this.result = a[0]; } else { let h = 0; for (let l = 0, m, p; l < a.length; l++) { if ((m = a[l]) && (p = m.length)) { h < p && (h = p); } else if (!f) { h = 0; break; } } h ? (this.result = $a(a, h, c, b, f, this.h, d), k = !0) : this.result = []; } } else { f || (this.result = a); } d && (this.await = null); return d ? this.resolve(c, b, e, g, k) : this; } ;X.prototype.xor = function() { return Ua(this, "xor", ab, arguments); }; function ab(a, c, b, e, d, f, g) { if (a.length) { if (this.result.length && a.unshift(this.result), a.length < 2) { this.result = a[0]; } else { a: { f = b; var k = this.h; const h = [], l = I(); let m = 0; for (let p = 0, u; p < a.length; p++) { if (u = a[p]) { m < u.length && (m = u.length); for (let r = 0, t; r < u.length; r++) { if (t = u[r]) { for (let n = 0, q; n < t.length; n++) { q = t[n], l[q] = l[q] ? 2 : 1; } } } } } for (let p = 0, u, r = 0; p < m; p++) { for (let t = 0, n; t < a.length; t++) { if (n = a[t]) { if (u = n[p]) { for (let q = 0, x; q < u.length; q++) { if (x = u[q], l[x] === 1) { if (f) { f--; } else { if (d) { if (h.push(x), h.length === c) { a = h; break a; } } else { const v = p + (t ? k : 0); h[v] || (h[v] = []); h[v].push(x); if (++r === c) { a = h; break a; } } } } } } } } } a = h; } this.result = a; k = !0; } } else { f || (this.result = a); } d && (this.await = null); return d ? this.resolve(c, b, e, g, k) : this; } ;X.prototype.not = function() { return Ua(this, "not", bb, arguments); }; function bb(a, c, b, e, d, f, g) { if (!f && !this.result.length) { return d ? this.result : this; } if (a.length && this.result.length) { a: { f = b; var k = []; a = new Set(a.flat().flat()); for (let h = 0, l, m = 0; h < this.result.length; h++) { if (l = this.result[h]) { for (let p = 0, u; p < l.length; p++) { if (u = l[p], !a.has(u)) { if (f) { f--; } else { if (d) { if (k.push(u), k.length === c) { a = k; break a; } } else { if (k[h] || (k[h] = []), k[h].push(u), ++m === c) { a = k; break a; } } } } } } } a = k; } this.result = a; k = !0; } d && (this.await = null); return d ? this.resolve(c, b, e, g, k) : this; } ;function cb(a, c, b, e, d) { let f, g, k; typeof d === "string" ? (f = d, d = "") : f = d.template; if (!f) { throw Error('No template pattern was specified by the search option "highlight"'); } g = f.indexOf("$1"); if (g === -1) { throw Error('Invalid highlight template. The replacement pattern "$1" was not found in template: ' + f); } k = f.substring(g + 2); g = f.substring(0, g); let h = d && d.boundary, l = !d || d.clip !== !1, m = d && d.merge && k && g && new RegExp(k + " " + g, "g"); d = d && d.ellipsis; var p = 0; if (typeof d === "object") { var u = d.template; p = u.length - 2; d = d.pattern; } typeof d !== "string" && (d = d === !1 ? "" : "..."); p && (d = u.replace("$1", d)); u = d.length - p; let r, t; typeof h === "object" && (r = h.before, r === 0 && (r = -1), t = h.after, t === 0 && (t = -1), h = h.total || 9e5); p = new Map(); for (let Pa = 0, ea, gb, pa; Pa < c.length; Pa++) { let qa; if (e) { qa = c, pa = e; } else { var n = c[Pa]; pa = n.field; if (!pa) { continue; } qa = n.result; } gb = b.get(pa); ea = gb.encoder; n = p.get(ea); typeof n !== "string" && (n = ea.encode(a), p.set(ea, n)); for (let ya = 0; ya < qa.length; ya++) { var q = qa[ya].doc; if (!q) { continue; } q = ca(q, pa); if (!q) { continue; } var x = q.trim().split(/\s+/); if (!x.length) { continue; } q = ""; var v = []; let za = []; var A = -1, E = -1, F = 0; for (var B = 0; B < x.length; B++) { var C = x[B], z = ea.encode(C); z = z.length > 1 ? z.join(" ") : z[0]; let y; if (z && C) { var D = C.length, J = (ea.split ? C.replace(ea.split, "") : C).length - z.length, G = "", L = 0; for (var O = 0; O < n.length; O++) { var P = n[O]; if (P) { var M = P.length; M += J < 0 ? 0 : J; L && M <= L || (P = z.indexOf(P), P > -1 && (G = (P ? C.substring(0, P) : "") + g + C.substring(P, P + M) + k + (P + M < D ? C.substring(P + M) : ""), L = M, y = !0)); } } G && (h && (A < 0 && (A = q.length + (q ? 1 : 0)), E = q.length + (q ? 1 : 0) + G.length, F += D, za.push(v.length), v.push({match:G})), q += (q ? " " : "") + G); } if (!y) { C = x[B], q += (q ? " " : "") + C, h && v.push({text:C}); } else if (h && F >= h) { break; } } F = za.length * (f.length - 2); if (r || t || h && q.length - F > h) { if (F = h + F - u * 2, B = E - A, r > 0 && (B += r), t > 0 && (B += t), B <= F) { x = r ? A - (r > 0 ? r : 0) : A - ((F - B) / 2 | 0), v = t ? E + (t > 0 ? t : 0) : x + F, l || (x > 0 && q.charAt(x) !== " " && q.charAt(x - 1) !== " " && (x = q.indexOf(" ", x), x < 0 && (x = 0)), v < q.length && q.charAt(v - 1) !== " " && q.charAt(v) !== " " && (v = q.lastIndexOf(" ", v), v < E ? v = E : ++v)), q = (x ? d : "") + q.substring(x, v) + (v < q.length ? d : ""); } else { E = []; A = {}; F = {}; B = {}; C = {}; z = {}; G = J = D = 0; for (O = L = 1;;) { var U = void 0; for (let y = 0, K; y < za.length; y++) { K = za[y]; if (G) { if (J !== G) { if (B[y + 1]) { continue; } K += G; if (A[K]) { D -= u; F[y + 1] = 1; B[y + 1] = 1; continue; } if (K >= v.length - 1) { if (K >= v.length) { B[y + 1] = 1; K >= x.length && (F[y + 1] = 1); continue; } D -= u; } q = v[K].text; if (M = t && z[y]) { if (M > 0) { if (q.length > M) { if (B[y + 1] = 1, l) { q = q.substring(0, M); } else { continue; } } (M -= q.length) || (M = -1); z[y] = M; } else { B[y + 1] = 1; continue; } } if (D + q.length + 1 <= h) { q = " " + q, E[y] += q; } else if (l) { U = h - D - 1, U > 0 && (q = " " + q.substring(0, U), E[y] += q), B[y + 1] = 1; } else { B[y + 1] = 1; continue; } } else { if (B[y]) { continue; } K -= J; if (A[K]) { D -= u; B[y] = 1; F[y] = 1; continue; } if (K <= 0) { if (K < 0) { B[y] = 1; F[y] = 1; continue; } D -= u; } q = v[K].text; if (M = r && C[y]) { if (M > 0) { if (q.length > M) { if (B[y] = 1, l) { q = q.substring(q.length - M); } else { continue; } } (M -= q.length) || (M = -1); C[y] = M; } else { B[y] = 1; continue; } } if (D + q.length + 1 <= h) { q += " ", E[y] = q + E[y]; } else if (l) { U = q.length + 1 - (h - D), U >= 0 && U < q.length && (q = q.substring(U) + " ", E[y] = q + E[y]), B[y] = 1; } else { B[y] = 1; continue; } } } else { q = v[K].match; r && (C[y] = r); t && (z[y] = t); y && D++; let Qa; K ? !y && u && (D += u) : (F[y] = 1, B[y] = 1); K >= x.length - 1 ? Qa = 1 : K < v.length - 1 && v[K + 1].match ? Qa = 1 : u && (D += u); D -= f.length - 2; if (!y || D + q.length <= h) { E[y] = q; } else { U = L = O = F[y] = 0; break; } Qa && (F[y + 1] = 1, B[y + 1] = 1); } D += q.length; U = A[K] = 1; } if (U) { J === G ? G++ : J++; } else { J === G ? L = 0 : O = 0; if (!L && !O) { break; } L ? (J++, G = J) : G++; } } q = ""; for (let y = 0, K; y < E.length; y++) { K = (F[y] ? y ? " " : "" : (y && !d ? " " : "") + d) + E[y], q += K; } d && !F[E.length] && (q += d); } } m && (q = q.replace(m, " ")); qa[ya].highlight = q; } if (e) { break; } } return c; } ;function X(a, c) { if (!this || this.constructor !== X) { return new X(a, c); } let b = 0, e, d, f, g, k, h; if (a && a.index) { const l = a; c = l.index; b = l.boost || 0; if (d = l.query) { f = l.field || l.pluck; g = l.highlight; const m = l.resolve; a = l.async || l.queue; l.resolve = !1; l.highlight = ""; l.index = null; a = a ? c.searchAsync(l) : c.search(l); l.resolve = m; l.highlight = g; l.index = c; a = a.result || a; } else { a = []; } } if (a && a.then) { const l = this; a = a.then(function(m) { l.C[0] = l.result = m.result || m; Wa(l); }); e = [a]; a = []; k = new Promise(function(m) { h = m; }); } this.index = c || null; this.result = a || []; this.h = b; this.C = e || []; this.await = k || null; this.return = h || null; this.highlight = g || null; this.query = d || ""; this.field = f || ""; } w = X.prototype; w.limit = function(a) { if (this.await) { const c = this; this.C.push(function() { return c.limit(a).result; }); } else { if (this.result.length) { const c = []; for (let b = 0, e; b < this.result.length; b++) { if (e = this.result[b]) { if (e.length <= a) { if (c[b] = e, a -= e.length, !a) { break; } } else { c[b] = e.slice(0, a); break; } } } this.result = c; } } return this; }; w.offset = function(a) { if (this.await) { const c = this; this.C.push(function() { return c.offset(a).result; }); } else { if (this.result.length) { const c = []; for (let b = 0, e; b < this.result.length; b++) { if (e = this.result[b]) { e.length <= a ? a -= e.length : (c[b] = e.slice(a), a = 0); } } this.result = c; } } return this; }; w.boost = function(a) { if (this.await) { const c = this; this.C.push(function() { return c.boost(a).result; }); } else { this.h += a; } return this; }; function Wa(a, c) { let b = a.result; var e = a.await; a.await = null; for (let d = 0, f; d < a.C.length; d++) { if (f = a.C[d]) { if (typeof f === "function") { b = f(), a.C[d] = b = b.result || b, d--; } else if (f.h) { b = f.h(), a.C[d] = b = b.result || b, d--; } else if (f.then) { return a.await = e; } } } e = a.return; a.C = []; a.return = null; c || e(b); return b; } w.resolve = function(a, c, b, e, d) { let f = this.await ? Wa(this, !0) : this.result; if (f.then) { const g = this; return f.then(function() { return g.resolve(a, c, b, e, d); }); } f.length && (typeof a === "object" ? (e = a.highlight || this.highlight, b = !!e || a.enrich, c = a.offset, a = a.limit) : (e = e || this.highlight, b = !!e || b), f = d ? b ? Ta.call(this.index, f) : f : Sa.call(this.index, f, a || 100, c, b)); return this.finalize(f, e); }; w.finalize = function(a, c) { if (a.then) { const e = this; return a.then(function(d) { return e.finalize(d, c); }); } c && !this.query && console.warn('There was no query specified for highlighting. Please specify a query within the highlight resolver stage like { query: "...", highlight: ... }.'); c && a.length && this.query && (a = cb(this.query, a, this.index.index, this.field, c)); const b = this.return; this.highlight = this.index = this.result = this.C = this.await = this.return = null; this.query = this.field = ""; b && b(a); return a; }; function $a(a, c, b, e, d, f, g) { const k = a.length; let h = [], l, m; l = I(); for (let p = 0, u, r, t, n; p < c; p++) { for (let q = 0; q < k; q++) { if (t = a[q], p < t.length && (u = t[p])) { for (let x = 0; x < u.length; x++) { r = u[x]; (m = l[r]) ? l[r]++ : (m = 0, l[r] = 1); n = h[m] || (h[m] = []); if (!g) { let v = p + (q || !d ? 0 : f || 0); n = n[v] || (n[v] = []); } n.push(r); if (g && b && m === k - 1 && n.length - e === b) { return e ? n.slice(e) : n; } } } } } if (a = h.length) { if (d) { h = h.length > 1 ? Ya(h, b, e, g, f) : (h = h[0]) && b && h.length > b || e ? h.slice(e, b + e) : h; } else { if (a < k) { return []; } h = h[a - 1]; if (b || e) { if (g) { if (h.length > b || e) { h = h.slice(e, b + e); } } else { d = []; for (let p = 0, u; p < h.length; p++) { if (u = h[p]) { if (e && u.length > e) { e -= u.length; } else { if (b && u.length > b || e) { u = u.slice(e, b + e), b -= u.length, e && (e -= u.length); } d.push(u); if (!b) { break; } } } } h = d; } } } } return h; } function Ya(a, c, b, e, d) { const f = [], g = I(); let k; var h = a.length; let l; if (e) { for (d = h - 1; d >= 0; d--) { if (l = (e = a[d]) && e.length) { for (h = 0; h < l; h++) { if (k = e[h], !g[k]) { if (g[k] = 1, b) { b--; } else { if (f.push(k), f.length === c) { return f; } } } } } } } else { for (let m = h - 1, p, u = 0; m >= 0; m--) { p = a[m]; for (let r = 0; r < p.length; r++) { if (l = (e = p[r]) && e.length) { for (let t = 0; t < l; t++) { if (k = e[t], !g[k]) { if (g[k] = 1, b) { b--; } else { let n = (r + (m < h - 1 ? d || 0 : 0)) / (m + 1) | 0; (f[n] || (f[n] = [])).push(k); if (++u === c) { return f; } } } } } } } } return f; } function db(a, c, b, e, d) { const f = I(), g = []; for (let k = 0, h; k < c.length; k++) { h = c[k]; for (let l = 0; l < h.length; l++) { f[h[l]] = 1; } } if (d) { for (let k = 0, h; k < a.length; k++) { if (h = a[k], f[h]) { if (e) { e--; } else { if (g.push(h), f[h] = 0, b && --b === 0) { break; } } } } } else { a = a.result || a; for (let k = 0, h, l; k < a.length; k++) { for (h = a[k], c = 0; c < h.length; c++) { l = h[c], f[l] && ((g[k] || (g[k] = [])).push(l), f[l] = 0); } } } return g; } ;I(); Na.prototype.search = function(a, c, b, e) { b || (!c && ba(a) ? (b = a, a = "") : ba(c) && (b = c, c = 0)); let d = []; var f = []; let g; let k, h, l, m, p; let u = 0, r = !0, t; if (b) { b.constructor === Array && (b = {index:b}); a = b.query || a; g = b.pluck; k = b.merge; l = b.boost; p = g || b.field || (p = b.index) && (p.index ? null : p); var n = this.tag && b.tag; h = b.suggest; r = b.resolve !== !1; m = b.cache; this.store && b.highlight && !r ? console.warn("Highlighting results can only be done within a resolver stage (and/or/not/xor) or when calling .resolve({ highlight: ... })") : this.store && b.enrich && !r && console.warn("Enrich results can only be done on a final resolver task or when calling .resolve({ enrich: true })"); t = r && this.store && b.highlight; var q = !!t || r && this.store && b.enrich; c = b.limit || c; var x = b.offset || 0; c || (c = r ? 100 : 0); if (n && (!this.db || !e)) { n.constructor !== Array && (n = [n]); var v = []; for (let C = 0, z; C < n.length; C++) { z = n[C]; if (N(z)) { throw Error("A tag option can't be a string, instead it needs a { field: tag } format."); } if (z.field && z.tag) { var A = z.tag; if (A.constructor === Array) { for (var E = 0; E < A.length; E++) { v.push(z.field, A[E]); } } else { v.push(z.field, A); } } else { A = Object.keys(z); for (let D = 0, J, G; D < A.length; D++) { if (J = A[D], G = z[J], G.constructor === Array) { for (E = 0; E < G.length; E++) { v.push(J, G[E]); } } else { v.push(J, G); } } } } if (!v.length) { throw Error("Your tag definition within the search options is probably wrong. No valid tags found."); } n = v; if (!a) { f = []; if (v.length) { for (n = 0; n < v.length; n += 2) { if (this.db) { e = this.index.get(v[n]); if (!e) { console.warn("Tag '" + v[n] + ":" + v[n + 1] + "' will be skipped because there is no field '" + v[n] + "'."); continue; } f.push(e = e.db.tag(v[n + 1], c, x, q)); } else { e = eb.call(this, v[n], v[n + 1], c, x, q); } d.push(r ? {field:v[n], tag:v[n + 1], result:e} : [e]); } } if (f.length) { const C = this; return Promise.all(f).then(function(z) { for (let D = 0; D < z.length; D++) { r ? d[D].result = z[D] : d[D] = z[D]; } return r ? d : new X(d.length > 1 ? $a(d, 1, 0, 0, h, l) : d[0], C); }); } return r ? d : new X(d.length > 1 ? $a(d, 1, 0, 0, h, l) : d[0], this); } } if (!r && !g) { if (p = p || this.field) { N(p) ? g = p : (p.constructor === Array && p.length === 1 && (p = p[0]), g = p.field || p.index); } if (!g) { throw Error("Apply resolver on document search requires either the option 'pluck' to be set or just select a single field name in your query."); } } p && p.constructor !== Array && (p = [p]); } p || (p = this.field); let F; v = (this.worker || this.db) && !e && []; for (let C = 0, z, D, J; C < p.length; C++) { D = p[C]; if (this.db && this.tag && !this.B[C]) { continue; } let G; N(D) || (G = D, D = G.field, a = G.query || a, c = aa(G.limit, c), x = aa(G.offset, x), h = aa(G.suggest, h), t = r && this.store && aa(G.highlight, t), q = !!t || r && this.store && aa(G.enrich, q), m = aa(G.cache, m)); if (e) { z = e[C]; } else { A = G || b || {}; E = A.enrich; var B = this.index.get(D); n && (this.db && (A.tag = n, A.field = p, F = B.db.support_tag_search), !F && E && (A.enrich = !1), F || (A.limit = 0, A.offset = 0)); z = m ? B.searchCache(a, n && !F ? 0 : c, A) : B.search(a, n && !F ? 0 : c, A); n && !F && (A.limit = c, A.offset = x); E && (A.enrich = E); if (v) { v[C] = z; continue; } } J = (z = z.result || z) && z.length; if (n && J) { A = []; E = 0; if (this.db && e) { if (!F) { for (B = p.length; B < e.length; B++) { let L = e[B]; if (L && L.length) { E++, A.push(L); } else if (!h) { return r ? d : new X(d, this); } } } } else { for (let L = 0, O, P; L < n.length; L += 2) { O = this.tag.get(n[L]); if (!O) { if (console.warn("Tag '" + n[L] + ":" + n[L + 1] + "' will be skipped because there is no field '" + n[L] + "'."), h) { continue; } else { return r ? d : new X(d, this); } } if (P = (O = O && O.get(n[L + 1])) && O.length) { E++, A.push(O); } else if (!h) { return r ? d : new X(d, this); } } } if (E) { z = db(z, A, c, x, r); J = z.length; if (!J && !h) { return r ? z : new X(z, this); } E--; } } if (J) { f[u] = D, d.push(z), u++; } else if (p.length === 1) { return r ? d : new X(d, this); } } if (v) { if (this.db && n && n.length && !F) { for (q = 0; q < n.length; q += 2) { f = this.index.get(n[q]); if (!f) { if (console.warn("Tag '" + n[q] + ":" + n[q + 1] + "' was not found because there is no field '" + n[q] + "'."), h) { continue; } else { return r ? d : new X(d, this); } } v.push(f.db.tag(n[q + 1], c, x, !1)); } } const C = this; return Promise.all(v).then(function(z) { b && (b.resolve = r); z.length && (z = C.search(a, c, b, z)); return z; }); } if (!u) { return r ? d : new X(d, this); } if (g && (!q || !this.store)) { return d = d[0], r ? d : new X(d, this); } v = []; for (x = 0; x < f.length; x++) { n = d[x]; q && n.length && typeof n[0].doc === "undefined" && (this.db ? v.push(n = this.index.get(this.field[0]).db.enrich(n)) : n = Ta.call(this, n)); if (g) { return r ? t ? cb(a, n, this.index, g, t) : n : new X(n, this); } d[x] = {field:f[x], result:n}; } if (q && this.db && v.length) { const C = this; return Promise.all(v).then(function(z) { for (let D = 0; D < z.length; D++) { d[D].result = z[D]; } t && (d = cb(a, d, C.index, g, t)); return k ? fb(d) : d; }); } t && (d = cb(a, d, this.index, g, t)); return k ? fb(d) : d; }; function fb(a) { const c = [], b = I(), e = I(); for (let d = 0, f, g, k, h, l, m, p; d < a.length; d++) { f = a[d]; g = f.field; k = f.result; for (let u = 0; u < k.length; u++) { if (l = k[u], typeof l !== "object" ? l = {id:h = l} : h = l.id, (m = b[h]) ? m.push(g) : (l.field = b[h] = [g], c.push(l)), p = l.highlight) { m = e[h], m || (e[h] = m = {}, l.highlight = m), m[g] = p; } } } return c; } function eb(a, c, b, e, d) { a = this.tag.get(a); if (!a) { return []; } a = a.get(c); if (!a) { return []; } c = a.length - e; if (c > 0) { if (b && c > b || e) { a = a.slice(e, e + b); } d && (a = Ta.call(this, a)); } return a; } function Ta(a) { if (!this || !this.store) { return a; } if (this.db) { return this.index.get(this.field[0]).db.enrich(a); } const c = Array(a.length); for (let b = 0, e; b < a.length; b++) { e = a[b], c[b] = {id:e, doc:this.store.get(e)}; } return c; } ;function Na(a) { if (!this || this.constructor !== Na) { return new Na(a); } const c = a.document || a.doc || a; let b, e; this.B = []; this.field = []; this.D = []; this.key = (b = c.key || c.id) && hb(b, this.D) || "id"; (e = a.keystore || 0) && (this.keystore = e); this.fastupdate = !!a.fastupdate; this.reg = !this.fastupdate || a.worker || a.db ? e ? new R(e) : new Set() : e ? new Q(e) : new Map(); this.h = (b = c.store || null) && b && b !== !0 && []; this.store = b ? e ? new Q(e) : new Map() : null; this.cache = (b = a.cache || null) && new na(b); a.cache = !1; this.worker = a.worker || !1; this.priority = a.priority || 4; this.index = ib.call(this, a, c); this.tag = null; if (b = c.tag) { if (typeof b === "string" && (b = [b]), b.length) { this.tag = new Map(); this.A = []; this.F = []; for (let d = 0, f, g; d < b.length; d++) { f = b[d]; g = f.field || f; if (!g) { throw Error("The tag field from the document descriptor is undefined."); } f.custom ? this.A[d] = f.custom : (this.A[d] = hb(g, this.D), f.filter && (typeof this.A[d] === "string" && (this.A[d] = new String(this.A[d])), this.A[d].G = f.filter)); this.F[d] = g; this.tag.set(g, new Map()); } } } if (this.worker) { this.fastupdate = !1; a = []; for (const d of this.index.values()) { d.then && a.push(d); } if (a.length) { const d = this; return Promise.all(a).then(function(f) { let g = 0; for (const k of d.index.entries()) { const h = k[0]; let l = k[1]; l.then && (l = f[g], d.index.set(h, l), g++); } return d; }); } } else { a.db && (this.fastupdate = !1, this.mount(a.db)); } } w = Na.prototype; w.mount = function(a) { if (this.worker) { throw Error("You can't use Worker-Indexes on a persistent model. That would be useless, since each of the persistent model acts like Worker-Index by default (Master/Slave)."); } let c = this.field; if (this.tag) { for (let f = 0, g; f < this.F.length; f++) { g = this.F[f]; var b = void 0; this.index.set(g, b = new T({}, this.reg)); c === this.field && (c = c.slice(0)); c.push(g); b.tag = this.tag.get(g); } } b = []; const e = {db:a.db, type:a.type, fastupdate:a.fastupdate}; for (let f = 0, g, k; f < c.length; f++) { e.field = k = c[f]; g = this.index.get(k); const h = new a.constructor(a.id, e); h.id = a.id; b[f] = h.mount(g); g.document = !0; f ? g.bypass = !0 : g.store = this.store; } const d = this; return this.db = Promise.all(b).then(function() { d.db = !0; }); }; w.commit = async function() { const a = []; for (const c of this.index.values()) { a.push(c.commit()); } await Promise.all(a); this.reg.clear(); }; w.destroy = function() { const a = []; for (const c of this.index.values()) { a.push(c.destroy()); } return Promise.all(a); }; function ib(a, c) { const b = new Map(); let e = c.index || c.field || c; N(e) && (e = [e]); for (let f = 0, g, k; f < e.length; f++) { g = e[f]; N(g) || (k = g, g = g.field); k = ba(k) ? Object.assign({}, a, k) : a; if (this.worker) { var d = void 0; d = (d = k.encoder) && d.encode ? d : new ka(typeof d === "string" ? wa[d] : d || {}); d = new La(k, d); b.set(g, d); } this.worker || b.set(g, new T(k, this.reg)); k.custom ? this.B[f] = k.custom : (this.B[f] = hb(g, this.D), k.filter && (typeof this.B[f] === "string" && (this.B[f] = new String(this.B[f])), this.B[f].G = k.filter)); this.field[f] = g; } if (this.h) { a = c.store; N(a) && (a = [a]); for (let f = 0, g, k; f < a.length; f++) { g = a[f], k = g.field || g, g.custom ? (this.h[f] = g.custom, g.custom.O = k) : (this.h[f] = hb(k, this.D), g.filter && (typeof this.h[f] === "string" && (this.h[f] = new String(this.h[f])), this.h[f].G = g.filter)); } } return b; } function hb(a, c) { const b = a.split(":"); let e = 0; for (let d = 0; d < b.length; d++) { a = b[d], a[a.length - 1] === "]" && (a = a.substring(0, a.length - 2)) && (c[e] = !0), a && (b[e++] = a); } e < b.length && (b.length = e); return e > 1 ? b : b[0]; } w.append = function(a, c) { return this.add(a, c, !0); }; w.update = function(a, c) { return this.remove(a).add(a, c); }; w.remove = function(a) { ba(a) && (a = ca(a, this.key)); for (var c of this.index.values()) { c.remove(a, !0); } if (this.reg.has(a)) { if (this.tag && !this.fastupdate) { for (let b of this.tag.values()) { for (let e of b) { c = e[0]; const d = e[1], f = d.indexOf(a); f > -1 && (d.length > 1 ? d.splice(f, 1) : b.delete(c)); } } } this.store && this.store.delete(a); this.reg.delete(a); } this.cache && this.cache.remove(a); return this; }; w.clear = function() { const a = []; for (const c of this.index.values()) { const b = c.clear(); b.then && a.push(b); } if (this.tag) { for (const c of this.tag.values()) { c.clear(); } } this.store && this.store.clear(); this.cache && this.cache.clear(); return a.length ? Promise.all(a) : this; }; w.contain = function(a) { return this.db ? this.index.get(this.field[0]).db.has(a) : this.reg.has(a); }; w.cleanup = function() { for (const a of this.index.values()) { a.cleanup(); } return this; }; w.get = function(a) { return this.db ? this.index.get(this.field[0]).db.enrich(a).then(function(c) { return c[0] && c[0].doc || null; }) : this.store.get(a) || null; }; w.set = function(a, c) { typeof a === "object" && (c = a, a = ca(c, this.key)); this.store.set(a, c); return this; }; w.searchCache = ma; w.export = jb; w.import = kb; Fa(Na.prototype); function lb(a, c = 0) { let b = [], e = []; c && (c = 250000 / c * 5000 | 0); for (const d of a.entries()) { e.push(d), e.length === c && (b.push(e), e = []); } e.length && b.push(e); return b; } function mb(a, c) { c || (c = new Map()); for (let b = 0, e; b < a.length; b++) { e = a[b], c.set(e[0], e[1]); } return c; } function nb(a, c = 0) { let b = [], e = []; c && (c = 250000 / c * 1000 | 0); for (const d of a.entries()) { e.push([d[0], lb(d[1])[0] || []]), e.length === c && (b.push(e), e = []); } e.length && b.push(e); return b; } function ob(a, c) { c || (c = new Map()); for (let b = 0, e, d; b < a.length; b++) { e = a[b], d = c.get(e[0]), c.set(e[0], mb(e[1], d)); } return c; } function pb(a) { let c = [], b = []; for (const e of a.keys()) { b.push(e), b.length === 250000 && (c.push(b), b = []); } b.length && c.push(b); return c; } function qb(a, c) { c || (c = new Set()); for (let b = 0; b < a.length; b++) { c.add(a[b]); } return c; } function rb(a, c, b, e, d, f, g = 0) { const k = e && e.constructor === Array; var h = k ? e.shift() : e; if (!h) { return this.export(a, c, d, f + 1); } if ((h = a((c ? c + "." : "") + (g + 1) + "." + b, JSON.stringify(h))) && h.then) { const l = this; return h.then(function() { return rb.call(l, a, c, b, k ? e : null, d, f, g + 1); }); } return rb.call(this, a, c, b, k ? e : null, d, f, g + 1); } function jb(a, c, b = 0, e = 0) { if (b < this.field.length) { const g = this.field[b]; if ((c = this.index.get(g).export(a, g, b, e = 1)) && c.then) { const k = this; return c.then(function() { return k.export(a, g, b + 1); }); } return this.export(a, g, b + 1); } let d, f; switch(e) { case 0: d = "reg"; f = pb(this.reg); c = null; break; case 1: d = "tag"; f = this.tag && nb(this.tag, this.reg.size); c = null; break; case 2: d = "doc"; f = this.store && lb(this.store); c = null; break; default: return; } return rb.call(this, a, c, d, f || null, b, e); } function kb(a, c) { var b = a.split("."); b[b.length - 1] === "json" && b.pop(); const e = b.length > 2 ? b[0] : ""; b = b.length > 2 ? b[2] : b[1]; if (this.worker && e) { return this.index.get(e).import(a); } if (c) { typeof c === "string" && (c = JSON.parse(c)); if (e) { return this.index.get(e).import(b, c); } switch(b) { case "reg": this.fastupdate = !1; this.reg = qb(c, this.reg); for (let d = 0, f; d < this.field.length; d++) { f = this.index.get(this.field[d]), f.fastupdate = !1, f.reg = this.reg; } if (this.worker) { c = []; for (const d of this.index.values()) { c.push(d.import(a)); } return Promise.all(c); } break; case "tag": this.tag = ob(c, this.tag); break; case "doc": this.store = mb(c, this.store); } } } function sb(a, c) { let b = ""; for (const e of a.entries()) { a = e[0]; const d = e[1]; let f = ""; for (let g = 0, k; g < d.length; g++) { k = d[g] || [""]; let h = ""; for (let l = 0; l < k.length; l++) { h += (h ? "," : "") + (c === "string" ? '"' + k[l] + '"' : k[l]); } h = "[" + h + "]"; f += (f ? "," : "") + h; } f = '["' + a + '",[' + f + "]]"; b += (b ? "," : "") + f; } return b; } ;T.prototype.remove = function(a, c) { const b = this.reg.size && (this.fastupdate ? this.reg.get(a) : this.reg.has(a)); if (b) { if (this.fastupdate) { for (let e = 0, d, f; e < b.length; e++) { if ((d = b[e]) && (f = d.length)) { if (d[f - 1] === a) { d.pop(); } else { const g = d.indexOf(a); g >= 0 && d.splice(g, 1); } } } } else { tb(this.map, a), this.depth && tb(this.ctx, a); } c || this.reg.delete(a); } this.db && (this.commit_task.push({del:a}), this.M && ub(this)); this.cache && this.cache.remove(a); return this; }; function tb(a, c) { let b = 0; var e = typeof c === "undefined"; if (a.constructor === Array) { for (let d = 0, f, g, k; d < a.length; d++) { if ((f = a[d]) && f.length) { if (e) { return 1; } g = f.indexOf(c); if (g >= 0) { if (f.length > 1) { return f.splice(g, 1), 1; } delete a[d]; if (b) { return 1; } k = 1; } else { if (k) { return 1; } b++; } } } } else { for (let d of a.entries()) { e = d[0], tb(d[1], c) ? b++ : a.delete(e); } } return b; } ;const vb = {memory:{resolution:1}, performance:{resolution:3, fastupdate:!0, context:{depth:1, resolution:1}}, match:{tokenize:"full"}, score:{resolution:9, context:{depth:2, resolution:3}}}; T.prototype.add = function(a, c, b, e) { if (c && (a || a === 0)) { if (!e && !b && this.reg.has(a)) { return this.update(a, c); } e = this.depth; c = this.encoder.encode(c, !e); const l = c.length; if (l) { const m = I(), p = I(), u = this.resolution; for (let r = 0; r < l; r++) { let t = c[this.rtl ? l - 1 - r : r]; var d = t.length; if (d && (e || !p[t])) { var f = this.score ? this.score(c, t, r, null, 0) : wb(u, l, r), g = ""; switch(this.tokenize) { case "tolerant": Y(this, p, t, f, a, b); if (d > 2) { for (let n = 1, q, x, v, A; n < d - 1; n++) { q = t.charAt(n), x = t.charAt(n + 1), v = t.substring(0, n) + x, A = t.substring(n + 2), g = v + q + A, Y(this, p, g, f, a, b), g = v + A, Y(this, p, g, f, a, b); } Y(this, p, t.substring(0, t.length - 1), f, a, b); } break; case "full": if (d > 2) { for (let n = 0, q; n < d; n++) { for (f = d; f > n; f--) { g = t.substring(n, f); q = this.rtl ? d - 1 - n : n; var k = this.score ? this.score(c, t, r, g, q) : wb(u, l, r, d, q); Y(this, p, g, k, a, b); } } break; } case "bidirectional": case "reverse": if (d > 1) { for (k = d - 1; k > 0; k--) { g = t[this.rtl ? d - 1 - k : k] + g; var h = this.score ? this.score(c, t, r, g, k) : wb(u, l, r, d, k); Y(this, p, g, h, a, b); } g = ""; } case "forward": if (d > 1) { for (k = 0; k < d; k++) { g += t[this.rtl ? d - 1 - k : k], Y(this, p, g, f, a, b); } break; } default: if (Y(this, p, t, f, a, b), e && l > 1 && r < l - 1) { for (d = this.N, g = t, f = Math.min(e + 1, this.rtl ? r + 1 : l - r), k = 1; k < f; k++) { t = c[this.rtl ? l - 1 - r - k : r + k]; h = this.bidirectional && t > g; const n = this.score ? this.score(c, g, r, t, k - 1) : wb(d + (l / 2 > d ? 0 : 1), l, r, f - 1, k - 1); Y(this, m, h ? g : t, n, a, b, h ? t : g); } } } } } this.fastupdate || this.reg.add(a); } } this.db && (this.commit_task.push(b ? {ins:a} : {del:a}), this.M && ub(this)); return this; }; function Y(a, c, b, e, d, f, g) { let k, h; if (!(k = c[b]) || g && !k[g]) { g ? (c = k || (c[b] = I()), c[g] = 1, h = a.ctx, (k = h.get(g)) ? h = k : h.set(g, h = a.keystore ? new Q(a.keystore) : new Map())) : (h = a.map, c[b] = 1); (k = h.get(b)) ? h = k : h.set(b, h = k = []); if (f) { for (let l = 0, m; l < k.length; l++) { if ((m = k[l]) && m.includes(d)) { if (l <= e) { return; } m.splice(m.indexOf(d), 1); a.fastupdate && (c = a.reg.get(d)) && c.splice(c.indexOf(m), 1); break; } } } h = h[e] || (h[e] = []); h.push(d); if (h.length === 2 ** 31 - 1) { c = new Aa(h); if (a.fastupdate) { for (let l of a.reg.values()) { l.includes(h) && (l[l.indexOf(h)] = c); } } k[e] = h = c; } a.fastupdate && ((e = a.reg.get(d)) ? e.push(h) : a.reg.set(d, [h])); } } function wb(a, c, b, e, d) { return b && a > 1 ? c + (e || 0) <= a ? b + (d || 0) : (a - 1) / (c + (e || 0)) * (b + (d || 0)) + 1 | 0 : 0; } ;T.prototype.search = function(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : (b = a, a = "")); if (b && b.cache) { return b.cache = !1, a = this.searchCache(a, c, b), b.cache = !0, a; } let e = [], d, f, g, k = 0, h, l, m, p, u; b && (a = b.query || a, c = b.limit || c, k = b.offset || 0, f = b.context, g = b.suggest, u = (h = b.resolve) && b.enrich, m = b.boost, p = b.resolution, l = this.db && b.tag); typeof h === "undefined" && (h = this.resolve); f = this.depth && f !== !1; let r = this.encoder.encode(a, !f); d = r.length; c = c || (h ? 100 : 0); if (d === 1) { return xb.call(this, r[0], "", c, k, h, u, l); } if (d === 2 && f && !g) { return xb.call(this, r[1], r[0], c, k, h, u, l); } let t = I(), n = 0, q; f && (q = r[0], n = 1); p || p === 0 || (p = q ? this.N : this.resolution); if (this.db) { if (this.db.search && (b = this.db.search(this, r, c, k, g, h, u, l), b !== !1)) { return b; } const x = this; return async function() { for (let v, A; n < d; n++) { if ((A = r[n]) && !t[A]) { t[A] = 1; v = await yb(x, A, q, 0, 0, !1, !1); if (v = zb(v, e, g, p)) { e = v; break; } q && (g && v && e.length || (q = A)); } g && q && n === d - 1 && !e.length && (p = x.resolution, q = "", n = -1, t = I()); } return Ab(e, p, c, k, g, m, h); }(); } for (let x, v; n < d; n++) { if ((v = r[n]) && !t[v]) { t[v] = 1; x = yb(this, v, q, 0, 0, !1, !1); if (x = zb(x, e, g, p)) { e = x; break; } q && (g && x && e.length || (q = v)); } g && q && n === d - 1 && !e.length && (p = this.resolution, q = "", n = -1, t = I()); } return Ab(e, p, c, k, g, m, h); }; function Ab(a, c, b, e, d, f, g) { let k = a.length, h = a; if (k > 1) { h = $a(a, c, b, e, d, f, g); } else if (k === 1) { return g ? Sa.call(null, a[0], b, e) : new X(a[0], this); } return g ? h : new X(h, this); } function xb(a, c, b, e, d, f, g) { a = yb(this, a, c, b, e, d, f, g); return this.db ? a.then(function(k) { return d ? k || [] : new X(k, this); }) : a && a.length ? d ? Sa.call(this, a, b, e) : new X(a, this) : d ? [] : new X([], this); } function zb(a, c, b, e) { let d = []; if (a && a.length) { if (a.length <= e) { c.push(a); return; } for (let f = 0, g; f < e; f++) { if (g = a[f]) { d[f] = g; } } if (d.length) { c.push(d); return; } } if (!b) { return d; } } function yb(a, c, b, e, d, f, g, k) { let h; b && (h = a.bidirectional && c > b) && (h = b, b = c, c = h); if (a.db) { return a.db.get(c, b, e, d, f, g, k); } a = b ? (a = a.ctx.get(b)) && a.get(c) : a.map.get(c); return a; } ;function T(a, c) { if (!this || this.constructor !== T) { return new T(a); } if (a) { var b = N(a) ? a : a.preset; b && (vb[b] || console.warn("Preset not found: " + b), a = Object.assign({}, vb[b], a)); } else { a = {}; } b = a.context; const e = b === !0 ? {depth:1} : b || {}, d = N(a.encoder) ? wa[a.encoder] : a.encode || a.encoder || {}; this.encoder = d.encode ? d : typeof d === "object" ? new ka(d) : {encode:d}; this.resolution = a.resolution || 9; this.tokenize = b = (b = a.tokenize) && b !== "default" && b !== "exact" && b || "strict"; this.depth = b === "strict" && e.depth || 0; this.bidirectional = e.bidirectional !== !1; this.fastupdate = !!a.fastupdate; this.score = a.score || null; e && e.depth && this.tokenize !== "strict" && console.warn('Context-Search could not applied, because it is just supported when using the tokenizer "strict".'); (b = a.keystore || 0) && (this.keystore = b); this.map = b ? new Q(b) : new Map(); this.ctx = b ? new Q(b) : new Map(); this.reg = c || (this.fastupdate ? b ? new Q(b) : new Map() : b ? new R(b) : new Set()); this.N = e.resolution || 3; this.rtl = d.rtl || a.rtl || !1; this.cache = (b = a.cache || null) && new na(b); this.resolve = a.resolve !== !1; if (b = a.db) { this.db = this.mount(b); } this.M = a.commit !== !1; this.commit_task = []; this.commit_timer = null; this.priority = a.priority || 4; } w = T.prototype; w.mount = function(a) { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return a.mount(this); }; w.commit = function() { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return this.db.commit(this); }; w.destroy = function() { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return this.db.destroy(); }; function ub(a) { a.commit_timer || (a.commit_timer = setTimeout(function() { a.commit_timer = null; a.db.commit(a); }, 1)); } w.clear = function() { this.map.clear(); this.ctx.clear(); this.reg.clear(); this.cache && this.cache.clear(); return this.db ? (this.commit_timer && clearTimeout(this.commit_timer), this.commit_timer = null, this.commit_task = [], this.db.clear()) : this; }; w.append = function(a, c) { return this.add(a, c, !0); }; w.contain = function(a) { return this.db ? this.db.has(a) : this.reg.has(a); }; w.update = function(a, c) { const b = this, e = this.remove(a); return e && e.then ? e.then(() => b.add(a, c)) : this.add(a, c); }; w.cleanup = function() { if (!this.fastupdate) { return console.info('Cleanup the index isn\'t required when not using "fastupdate".'), this; } tb(this.map); this.depth && tb(this.ctx); return this; }; w.searchCache = ma; w.export = function(a, c, b = 0, e = 0) { let d, f; switch(e) { case 0: d = "reg"; f = pb(this.reg); break; case 1: d = "cfg"; f = null; break; case 2: d = "map"; f = lb(this.map, this.reg.size); break; case 3: d = "ctx"; f = nb(this.ctx, this.reg.size); break; default: return; } return rb.call(this, a, c, d, f, b, e); }; w.import = function(a, c) { if (c) { switch(typeof c === "string" && (c = JSON.parse(c)), a = a.split("."), a[a.length - 1] === "json" && a.pop(), a.length === 3 && a.shift(), a = a.length > 1 ? a[1] : a[0], a) { case "reg": this.fastupdate = !1; this.reg = qb(c, this.reg); break; case "map": this.map = mb(c, this.map); break; case "ctx": this.ctx = ob(c, this.ctx); } } }; w.serialize = function(a = !0) { let c = "", b = "", e = ""; if (this.reg.size) { let f; for (var d of this.reg.keys()) { f || (f = typeof d), c += (c ? "," : "") + (f === "string" ? '"' + d + '"' : d); } c = "index.reg=new Set([" + c + "]);"; b = sb(this.map, f); b = "index.map=new Map([" + b + "]);"; for (const g of this.ctx.entries()) { d = g[0]; let k = sb(g[1], f); k = "new Map([" + k + "])"; k = '["' + d + '",' + k + "]"; e += (e ? "," : "") + k; } e = "index.ctx=new Map([" + e + "]);"; } return a ? "function inject(index){" + c + b + e + "}" : c + b + e; }; Fa(T.prototype); const Bb = typeof window !== "undefined" && (window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB), Cb = ["map", "ctx", "tag", "reg", "cfg"], Db = I(); function Eb(a, c = {}) { if (!this || this.constructor !== Eb) { return new Eb(a, c); } typeof a === "object" && (c = a, a = a.name); a || console.info("Default storage space was used, because a name was not passed."); this.id = "flexsearch" + (a ? ":" + a.toLowerCase().replace(/[^a-z0-9_\-]/g, "") : ""); this.field = c.field ? c.field.toLowerCase().replace(/[^a-z0-9_\-]/g, "") : ""; this.type = c.type; this.fastupdate = this.support_tag_search = !1; this.db = null; this.h = {}; } w = Eb.prototype; w.mount = function(a) { if (a.index) { return a.mount(this); } a.db = this; return this.open(); }; w.open = function() { if (this.db) { return this.db; } let a = this; navigator.storage && navigator.storage.persist && navigator.storage.persist(); Db[a.id] || (Db[a.id] = []); Db[a.id].push(a.field); const c = Bb.open(a.id, 1); c.onupgradeneeded = function() { const b = a.db = this.result; for (let e = 0, d; e < Cb.length; e++) { d = Cb[e]; for (let f = 0, g; f < Db[a.id].length; f++) { g = Db[a.id][f], b.objectStoreNames.contains(d + (d !== "reg" ? g ? ":" + g : "" : "")) || b.createObjectStore(d + (d !== "reg" ? g ? ":" + g : "" : "")); } } }; return a.db = Z(c, function(b) { a.db = b; a.db.onversionchange = function() { a.close(); }; }); }; w.close = function() { this.db && this.db.close(); this.db = null; }; w.destroy = function() { const a = Bb.deleteDatabase(this.id); return Z(a); }; w.clear = function() { const a = []; for (let b = 0, e; b < Cb.length; b++) { e = Cb[b]; for (let d = 0, f; d < Db[this.id].length; d++) { f = Db[this.id][d], a.push(e + (e !== "reg" ? f ? ":" + f : "" : "")); } } const c = this.db.transaction(a, "readwrite"); for (let b = 0; b < a.length; b++) { c.objectStore(a[b]).clear(); } return Z(c); }; w.get = function(a, c, b = 0, e = 0, d = !0, f = !1) { a = this.db.transaction((c ? "ctx" : "map") + (this.field ? ":" + this.field : ""), "readonly").objectStore((c ? "ctx" : "map") + (this.field ? ":" + this.field : "")).get(c ? c + ":" + a : a); const g = this; return Z(a).then(function(k) { let h = []; if (!k || !k.length) { return h; } if (d) { if (!b && !e && k.length === 1) { return k[0]; } for (let l = 0, m; l < k.length; l++) { if ((m = k[l]) && m.length) { if (e >= m.length) { e -= m.length; continue; } const p = b ? e + Math.min(m.length - e, b) : m.length; for (let u = e; u < p; u++) { h.push(m[u]); } e = 0; if (h.length === b) { break; } } } return f ? g.enrich(h) : h; } return k; }); }; w.tag = function(a, c = 0, b = 0, e = !1) { a = this.db.transaction("tag" + (this.field ? ":" + this.field : ""), "readonly").objectStore("tag" + (this.field ? ":" + this.field : "")).get(a); const d = this; return Z(a).then(function(f) { if (!f || !f.length || b >= f.length) { return []; } if (!c && !b) { return f; } f = f.slice(b, b + c); return e ? d.enrich(f) : f; }); }; w.enrich = function(a) { typeof a !== "object" && (a = [a]); const c = this.db.transaction("reg", "readonly").objectStore("reg"), b = []; for (let e = 0; e < a.length; e++) { b[e] = Z(c.get(a[e])); } return Promise.all(b).then(function(e) { for (let d = 0; d < e.length; d++) { e[d] = {id:a[d], doc:e[d] ? JSON.parse(e[d]) : null}; } return e; }); }; w.has = function(a) { a = this.db.transaction("reg", "readonly").objectStore("reg").getKey(a); return Z(a).then(function(c) { return !!c; }); }; w.search = null; w.info = function() { }; w.transaction = function(a, c, b) { a += a !== "reg" ? this.field ? ":" + this.field : "" : ""; let e = this.h[a + ":" + c]; if (e) { return b.call(this, e); } let d = this.db.transaction(a, c); this.h[a + ":" + c] = e = d.objectStore(a); const f = b.call(this, e); this.h[a + ":" + c] = null; return Z(d).finally(function() { return f; }); }; w.commit = async function(a) { let c = a.commit_task, b = []; a.commit_task = []; for (let e = 0, d; e < c.length; e++) { d = c[e], d.del && b.push(d.del); } b.length && await this.remove(b); a.reg.size && (await this.transaction("map", "readwrite", function(e) { for (const d of a.map) { const f = d[0], g = d[1]; g.length && (e.get(f).onsuccess = function() { let k = this.result; var h; if (k && k.length) { const l = Math.max(k.length, g.length); for (let m = 0, p, u; m < l; m++) { if ((u = g[m]) && u.length) { if ((p = k[m]) && p.length) { for (h = 0; h < u.length; h++) { p.push(u[h]); } } else { k[m] = u; } h = 1; } } } else { k = g, h = 1; } h && e.put(k, f); }); } }), await this.transaction("ctx", "readwrite", function(e) { for (const d of a.ctx) { const f = d[0], g = d[1]; for (const k of g) { const h = k[0], l = k[1]; l.length && (e.get(f + ":" + h).onsuccess = function() { let m = this.result; var p; if (m && m.length) { const u = Math.max(m.length, l.length); for (let r = 0, t, n; r < u; r++) { if ((n = l[r]) && n.length) { if ((t = m[r]) && t.length) { for (p = 0; p < n.length; p++) { t.push(n[p]); } } else { m[r] = n; } p = 1; } } } else { m = l, p = 1; } p && e.put(m, f + ":" + h); }); } } }), a.store ? await this.transaction("reg", "readwrite", function(e) { for (const d of a.store) { const f = d[0], g = d[1]; e.put(typeof g === "object" ? JSON.stringify(g) : 1, f); } }) : a.bypass || await this.transaction("reg", "readwrite", function(e) { for (const d of a.reg.keys()) { e.put(1, d); } }), a.tag && await this.transaction("tag", "readwrite", function(e) { for (const d of a.tag) { const f = d[0], g = d[1]; g.length && (e.get(f).onsuccess = function() { let k = this.result; k = k && k.length ? k.concat(g) : g; e.put(k, f); }); } }), a.map.clear(), a.ctx.clear(), a.tag && a.tag.clear(), a.store && a.store.clear(), a.document || a.reg.clear()); }; function Fb(a, c, b) { const e = a.value; let d, f = 0; for (let g = 0, k; g < e.length; g++) { if (k = b ? e : e[g]) { for (let h = 0, l, m; h < c.length; h++) { if (m = c[h], l = k.indexOf(m), l >= 0) { if (d = 1, k.length > 1) { k.splice(l, 1); } else { e[g] = []; break; } } } f += k.length; } if (b) { break; } } f ? d && a.update(e) : a.delete(); a.continue(); } w.remove = function(a) { typeof a !== "object" && (a = [a]); return Promise.all([this.transaction("map", "readwrite", function(c) { c.openCursor().onsuccess = function() { const b = this.result; b && Fb(b, a); }; }), this.transaction("ctx", "readwrite", function(c) { c.openCursor().onsuccess = function() { const b = this.result; b && Fb(b, a); }; }), this.transaction("tag", "readwrite", function(c) { c.openCursor().onsuccess = function() { const b = this.result; b && Fb(b, a, !0); }; }), this.transaction("reg", "readwrite", function(c) { for (let b = 0; b < a.length; b++) { c.delete(a[b]); } })]); }; function Z(a, c) { return new Promise((b, e) => { a.onsuccess = a.oncomplete = function() { c && c(this.result); c = null; b(this.result); }; a.onerror = a.onblocked = e; a = null; }); } ;const Gb = {Index:T, Charset:wa, Encoder:ka, Document:Na, Worker:La, Resolver:X, IndexedDB:Eb, Language:{}}, Hb = typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : self; let Ib; (Ib = Hb.define) && Ib.amd ? Ib([], function() { return Gb; }) : typeof Hb.exports === "object" ? Hb.exports = Gb : Hb.FlexSearch = Gb; }(this||self)); ================================================ FILE: dist/flexsearch.bundle.module.debug.js ================================================ /**! * FlexSearch.js v0.8.214 (Bundle/Module/Debug) * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ var w; function H(a, c, b) { const e = typeof b, d = typeof a; if (e !== "undefined") { if (d !== "undefined") { if (b) { if (d === "function" && e === d) { return function(k) { return a(b(k)); }; } c = a.constructor; if (c === b.constructor) { if (c === Array) { return b.concat(a); } if (c === Map) { var f = new Map(b); for (var g of a) { f.set(g[0], g[1]); } return f; } if (c === Set) { g = new Set(b); for (f of a.values()) { g.add(f); } return g; } } } return a; } return b; } return d === "undefined" ? c : a; } function aa(a, c) { return typeof a === "undefined" ? c : a; } function I() { return Object.create(null); } function N(a) { return typeof a === "string"; } function ba(a) { return typeof a === "object"; } function ca(a, c) { if (N(c)) { a = a[c]; } else { for (let b = 0; a && b < c.length; b++) { a = a[c[b]]; } } return a; } ;const da = /[^\p{L}\p{N}]+/u, fa = /(\d{3})/g, ha = /(\D)(\d{3})/g, ia = /(\d{3})(\D)/g, ja = /[\u0300-\u036f]/g; function ka(a = {}) { if (!this || this.constructor !== ka) { return new ka(...arguments); } if (arguments.length) { for (a = 0; a < arguments.length; a++) { this.assign(arguments[a]); } } else { this.assign(a); } } w = ka.prototype; w.assign = function(a) { this.normalize = H(a.normalize, !0, this.normalize); let c = a.include, b = c || a.exclude || a.split, e; if (b || b === "") { if (typeof b === "object" && b.constructor !== RegExp) { let d = ""; e = !c; c || (d += "\\p{Z}"); b.letter && (d += "\\p{L}"); b.number && (d += "\\p{N}", e = !!c); b.symbol && (d += "\\p{S}"); b.punctuation && (d += "\\p{P}"); b.control && (d += "\\p{C}"); if (b = b.char) { d += typeof b === "object" ? b.join("") : b; } try { this.split = new RegExp("[" + (c ? "^" : "") + d + "]+", "u"); } catch (f) { console.error("Your split configuration:", b, "is not supported on this platform. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } } else { this.split = b, e = b === !1 || "a1a".split(b).length < 2; } this.numeric = H(a.numeric, e); } else { try { this.split = H(this.split, da); } catch (d) { console.warn("This platform does not support unicode regex. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } this.numeric = H(a.numeric, H(this.numeric, !0)); } this.prepare = H(a.prepare, null, this.prepare); this.finalize = H(a.finalize, null, this.finalize); b = a.filter; this.filter = typeof b === "function" ? b : H(b && new Set(b), null, this.filter); this.dedupe = H(a.dedupe, !0, this.dedupe); this.matcher = H((b = a.matcher) && new Map(b), null, this.matcher); this.mapper = H((b = a.mapper) && new Map(b), null, this.mapper); this.stemmer = H((b = a.stemmer) && new Map(b), null, this.stemmer); this.replacer = H(a.replacer, null, this.replacer); this.minlength = H(a.minlength, 1, this.minlength); this.maxlength = H(a.maxlength, 1024, this.maxlength); this.rtl = H(a.rtl, !1, this.rtl); if (this.cache = b = H(a.cache, !0, this.cache)) { this.F = null, this.L = typeof b === "number" ? b : 2e5, this.B = new Map(), this.D = new Map(), this.I = this.H = 128; } this.h = ""; this.J = null; this.A = ""; this.K = null; if (this.matcher) { for (const d of this.matcher.keys()) { this.h += (this.h ? "|" : "") + d; } } if (this.stemmer) { for (const d of this.stemmer.keys()) { this.A += (this.A ? "|" : "") + d; } } return this; }; w.addStemmer = function(a, c) { this.stemmer || (this.stemmer = new Map()); this.stemmer.set(a, c); this.A += (this.A ? "|" : "") + a; this.K = null; this.cache && la(this); return this; }; w.addFilter = function(a) { typeof a === "function" ? this.filter = a : (this.filter || (this.filter = new Set()), this.filter.add(a)); this.cache && la(this); return this; }; w.addMapper = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length > 1) { return this.addMatcher(a, c); } this.mapper || (this.mapper = new Map()); this.mapper.set(a, c); this.cache && la(this); return this; }; w.addMatcher = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length < 2 && (this.dedupe || this.mapper)) { return this.addMapper(a, c); } this.matcher || (this.matcher = new Map()); this.matcher.set(a, c); this.h += (this.h ? "|" : "") + a; this.J = null; this.cache && la(this); return this; }; w.addReplacer = function(a, c) { if (typeof a === "string") { return this.addMatcher(a, c); } this.replacer || (this.replacer = []); this.replacer.push(a, c); this.cache && la(this); return this; }; w.encode = function(a, c) { if (this.cache && a.length <= this.H) { if (this.F) { if (this.B.has(a)) { return this.B.get(a); } } else { this.F = setTimeout(la, 50, this); } } this.normalize && (typeof this.normalize === "function" ? a = this.normalize(a) : a = ja ? a.normalize("NFKD").replace(ja, "").toLowerCase() : a.toLowerCase()); this.prepare && (a = this.prepare(a)); this.numeric && a.length > 3 && (a = a.replace(ha, "$1 $2").replace(ia, "$1 $2").replace(fa, "$1 ")); const b = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer); let e = [], d = I(), f, g, k = this.split || this.split === "" ? a.split(this.split) : [a]; for (let l = 0, m, p; l < k.length; l++) { if ((m = p = k[l]) && !(m.length < this.minlength || m.length > this.maxlength)) { if (c) { if (d[m]) { continue; } d[m] = 1; } else { if (f === m) { continue; } f = m; } if (b) { e.push(m); } else { if (!this.filter || (typeof this.filter === "function" ? this.filter(m) : !this.filter.has(m))) { if (this.cache && m.length <= this.I) { if (this.F) { var h = this.D.get(m); if (h || h === "") { h && e.push(h); continue; } } else { this.F = setTimeout(la, 50, this); } } if (this.stemmer) { this.K || (this.K = new RegExp("(?!^)(" + this.A + ")$")); let u; for (; u !== m && m.length > 2;) { u = m, m = m.replace(this.K, r => this.stemmer.get(r)); } } if (m && (this.mapper || this.dedupe && m.length > 1)) { h = ""; for (let u = 0, r = "", t, n; u < m.length; u++) { t = m.charAt(u), t === r && this.dedupe || ((n = this.mapper && this.mapper.get(t)) || n === "" ? n === r && this.dedupe || !(r = n) || (h += n) : h += r = t); } m = h; } this.matcher && m.length > 1 && (this.J || (this.J = new RegExp("(" + this.h + ")", "g")), m = m.replace(this.J, u => this.matcher.get(u))); if (m && this.replacer) { for (h = 0; m && h < this.replacer.length; h += 2) { m = m.replace(this.replacer[h], this.replacer[h + 1]); } } this.cache && p.length <= this.I && (this.D.set(p, m), this.D.size > this.L && (this.D.clear(), this.I = this.I / 1.1 | 0)); if (m) { if (m !== p) { if (c) { if (d[m]) { continue; } d[m] = 1; } else { if (g === m) { continue; } g = m; } } e.push(m); } } } } } this.finalize && (e = this.finalize(e) || e); this.cache && a.length <= this.H && (this.B.set(a, e), this.B.size > this.L && (this.B.clear(), this.H = this.H / 1.1 | 0)); return e; }; function la(a) { a.F = null; a.B.clear(); a.D.clear(); } ;function ma(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : b = a); b && (a = b.query || a, c = b.limit || c); let e = "" + (c || 0); b && (e += (b.offset || 0) + !!b.context + !!b.suggest + (b.resolve !== !1) + (b.resolution || this.resolution) + (b.boost || 0)); a = ("" + a).toLowerCase(); this.cache || (this.cache = new na()); let d = this.cache.get(a + e); if (!d) { const f = b && b.cache; f && (b.cache = !1); d = this.search(a, c, b); f && (b.cache = f); this.cache.set(a + e, d); } return d; } function na(a) { this.limit = a && a !== !0 ? a : 1000; this.cache = new Map(); this.h = ""; } na.prototype.set = function(a, c) { this.cache.set(this.h = a, c); this.cache.size > this.limit && this.cache.delete(this.cache.keys().next().value); }; na.prototype.get = function(a) { const c = this.cache.get(a); c && this.h !== a && (this.cache.delete(a), this.cache.set(this.h = a, c)); return c; }; na.prototype.remove = function(a) { for (const c of this.cache) { const b = c[0]; c[1].includes(a) && this.cache.delete(b); } }; na.prototype.clear = function() { this.cache.clear(); this.h = ""; }; const oa = {normalize:!1, numeric:!1, dedupe:!1}; const ra = {}; const sa = new Map([["b", "p"], ["v", "f"], ["w", "f"], ["z", "s"], ["x", "s"], ["d", "t"], ["n", "m"], ["c", "k"], ["g", "k"], ["j", "k"], ["q", "k"], ["i", "e"], ["y", "e"], ["u", "o"]]); const ta = new Map([["ae", "a"], ["oe", "o"], ["sh", "s"], ["kh", "k"], ["th", "t"], ["ph", "f"], ["pf", "f"]]), ua = [/([^aeo])h(.)/g, "$1$2", /([aeo])h([^aeo]|$)/g, "$1$2", /(.)\1+/g, "$1"]; const va = {a:"", e:"", i:"", o:"", u:"", y:"", b:1, f:1, p:1, v:1, c:2, g:2, j:2, k:2, q:2, s:2, x:2, z:2, "\u00df":2, d:3, t:3, l:4, m:5, n:5, r:6}; var wa = {Exact:oa, Default:ra, Normalize:ra, LatinBalance:{mapper:sa}, LatinAdvanced:{mapper:sa, matcher:ta, replacer:ua}, LatinExtra:{mapper:sa, replacer:ua.concat([/(?!^)[aeo]/g, ""]), matcher:ta}, LatinSoundex:{dedupe:!1, include:{letter:!0}, finalize:function(a) { for (let b = 0; b < a.length; b++) { var c = a[b]; let e = c.charAt(0), d = va[e]; for (let f = 1, g; f < c.length && (g = c.charAt(f), g === "h" || g === "w" || !(g = va[g]) || g === d || (e += g, d = g, e.length !== 4)); f++) { } a[b] = e; } }}, CJK:{split:""}, LatinExact:oa, LatinDefault:ra, LatinSimple:ra}; function xa(a, c, b, e) { let d = []; for (let f = 0, g; f < a.index.length; f++) { if (g = a.index[f], c >= g.length) { c -= g.length; } else { c = g[e ? "splice" : "slice"](c, b); const k = c.length; if (k && (d = d.length ? d.concat(c) : c, b -= k, e && (a.length -= k), !b)) { break; } c = 0; } } return d; } function Aa(a) { if (!this || this.constructor !== Aa) { return new Aa(a); } this.index = a ? [a] : []; this.length = a ? a.length : 0; const c = this; return new Proxy([], {get(b, e) { if (e === "length") { return c.length; } if (e === "push") { return function(d) { c.index[c.index.length - 1].push(d); c.length++; }; } if (e === "pop") { return function() { if (c.length) { return c.length--, c.index[c.index.length - 1].pop(); } }; } if (e === "indexOf") { return function(d) { let f = 0; for (let g = 0, k, h; g < c.index.length; g++) { k = c.index[g]; h = k.indexOf(d); if (h >= 0) { return f + h; } f += k.length; } return -1; }; } if (e === "includes") { return function(d) { for (let f = 0; f < c.index.length; f++) { if (c.index[f].includes(d)) { return !0; } } return !1; }; } if (e === "slice") { return function(d, f) { return xa(c, d || 0, f || c.length, !1); }; } if (e === "splice") { return function(d, f) { return xa(c, d || 0, f || c.length, !0); }; } if (e === "constructor") { return Array; } if (typeof e !== "symbol") { return (b = c.index[e / 2 ** 31 | 0]) && b[e]; } }, set(b, e, d) { b = e / 2 ** 31 | 0; (c.index[b] || (c.index[b] = []))[e] = d; c.length++; return !0; }}); } Aa.prototype.clear = function() { this.index.length = 0; }; Aa.prototype.push = function() { }; function Q(a = 8) { if (!this || this.constructor !== Q) { return new Q(a); } this.index = I(); this.h = []; this.size = 0; a > 32 ? (this.B = Ba, this.A = BigInt(a)) : (this.B = Ca, this.A = a); } Q.prototype.get = function(a) { const c = this.index[this.B(a)]; return c && c.get(a); }; Q.prototype.set = function(a, c) { var b = this.B(a); let e = this.index[b]; e ? (b = e.size, e.set(a, c), (b -= e.size) && this.size++) : (this.index[b] = e = new Map([[a, c]]), this.h.push(e), this.size++); }; function R(a = 8) { if (!this || this.constructor !== R) { return new R(a); } this.index = I(); this.h = []; this.size = 0; a > 32 ? (this.B = Ba, this.A = BigInt(a)) : (this.B = Ca, this.A = a); } R.prototype.add = function(a) { var c = this.B(a); let b = this.index[c]; b ? (c = b.size, b.add(a), (c -= b.size) && this.size++) : (this.index[c] = b = new Set([a]), this.h.push(b), this.size++); }; w = Q.prototype; w.has = R.prototype.has = function(a) { const c = this.index[this.B(a)]; return c && c.has(a); }; w.delete = R.prototype.delete = function(a) { const c = this.index[this.B(a)]; c && c.delete(a) && this.size--; }; w.clear = R.prototype.clear = function() { this.index = I(); this.h = []; this.size = 0; }; w.values = R.prototype.values = function*() { for (let a = 0; a < this.h.length; a++) { for (let c of this.h[a].values()) { yield c; } } }; w.keys = R.prototype.keys = function*() { for (let a = 0; a < this.h.length; a++) { for (let c of this.h[a].keys()) { yield c; } } }; w.entries = R.prototype.entries = function*() { for (let a = 0; a < this.h.length; a++) { for (let c of this.h[a].entries()) { yield c; } } }; function Ca(a) { let c = 2 ** this.A - 1; if (typeof a == "number") { return a & c; } let b = 0, e = this.A + 1; for (let d = 0; d < a.length; d++) { b = (b * e ^ a.charCodeAt(d)) & c; } return this.A === 32 ? b + 2 ** 31 : b; } function Ba(a) { let c = BigInt(2) ** this.A - BigInt(1); var b = typeof a; if (b === "bigint") { return a & c; } if (b === "number") { return BigInt(a) & c; } b = BigInt(0); let e = this.A + BigInt(1); for (let d = 0; d < a.length; d++) { b = (b * e ^ BigInt(a.charCodeAt(d))) & c; } return b; } ;let Da, S; async function Ea(a) { a = a.data; var c = a.task; const b = a.id; let e = a.args; switch(c) { case "init": S = a.options || {}; (c = a.factory) ? (Function("return " + c)()(self), Da = new self.FlexSearch.Index(S), delete self.FlexSearch) : Da = new T(S); postMessage({id:b}); break; default: let d; if (c === "export") { if (!S.export || typeof S.export !== "function") { throw Error('Either no extern configuration provided for the Worker-Index or no method was defined on the config property "export".'); } e[1] ? (e[0] = S.export, e[2] = 0, e[3] = 1) : e = null; } if (c === "import") { if (!S.import || typeof S.import !== "function") { throw Error('Either no extern configuration provided for the Worker-Index or no method was defined on the config property "import".'); } e[0] && (a = await S.import.call(Da, e[0]), Da.import(e[0], a)); } else { (d = e && Da[c].apply(Da, e)) && d.then && (d = await d), d && d.await && (d = await d.await), c === "search" && d.result && (d = d.result); } postMessage(c === "search" ? {id:b, msg:d} : {id:b}); } } ;function Fa(a) { Ga.call(a, "add"); Ga.call(a, "append"); Ga.call(a, "search"); Ga.call(a, "update"); Ga.call(a, "remove"); Ga.call(a, "searchCache"); } let Ha, Ia, Ja; function Ka() { Ha = Ja = 0; } function Ga(a) { this[a + "Async"] = function() { const c = arguments; var b = c[c.length - 1]; let e; typeof b === "function" && (e = b, delete c[c.length - 1]); Ha ? Ja || (Ja = Date.now() - Ia >= this.priority * this.priority * 3) : (Ha = setTimeout(Ka, 0), Ia = Date.now()); if (Ja) { const f = this; return new Promise(g => { setTimeout(function() { g(f[a + "Async"].apply(f, c)); }, 0); }); } const d = this[a].apply(this, c); b = d.then ? d : new Promise(f => f(d)); e && b.then(e); return b; }; } ;let V = 0; function La(a = {}, c) { function b(k) { function h(l) { l = l.data || l; const m = l.id, p = m && f.h[m]; p && (p(l.msg), delete f.h[m]); } this.worker = k; this.h = I(); if (this.worker) { d ? this.worker.on("message", h) : this.worker.onmessage = h; if (a.config) { return new Promise(function(l) { V > 1e9 && (V = 0); f.h[++V] = function() { l(f); }; f.worker.postMessage({id:V, task:"init", factory:e, options:a}); }); } this.priority = a.priority || 4; this.encoder = c || null; this.worker.postMessage({task:"init", factory:e, options:a}); return this; } console.warn("Worker is not available on this platform. Please report on Github: https://github.com/nextapps-de/flexsearch/issues"); } if (!this || this.constructor !== La) { return new La(a); } let e = typeof self !== "undefined" ? self._factory : typeof window !== "undefined" ? window._factory : null; e && (e = e.toString()); const d = typeof window === "undefined", f = this, g = Ma(e, d, a.worker); return g.then ? g.then(function(k) { return b.call(f, k); }) : b.call(this, g); } W("add"); W("append"); W("search"); W("update"); W("remove"); W("clear"); W("export"); W("import"); La.prototype.searchCache = ma; Fa(La.prototype); function W(a) { La.prototype[a] = function() { const c = this, b = [].slice.call(arguments); var e = b[b.length - 1]; let d; typeof e === "function" && (d = e, b.pop()); e = new Promise(function(f) { a === "export" && typeof b[0] === "function" && (b[0] = null); V > 1e9 && (V = 0); c.h[++V] = f; c.worker.postMessage({task:a, id:V, args:b}); }); return d ? (e.then(d), this) : e; }; } function Ma(a, c, b) { return c ? typeof module !== "undefined" ? new(require("worker_threads")["Worker"])(__dirname+"/worker/node.js") : import("worker_threads").then(function(worker){return new worker["Worker"](import.meta.dirname+"/node/node.mjs")}) : a ? new window.Worker(URL.createObjectURL(new Blob(["onmessage=" + Ea.toString()], {type:"text/javascript"}))) : new window.Worker(typeof b === "string" ? b : import.meta.url.replace("/worker.js", "/worker/worker.js").replace("flexsearch.bundle.module.min.js", "module/worker/worker.js").replace("flexsearch.bundle.module.min.mjs", "module/worker/worker.js"), {type:"module"}); } ;Na.prototype.add = function(a, c, b) { ba(a) && (c = a, a = ca(c, this.key)); if (c && (a || a === 0)) { if (!b && this.reg.has(a)) { return this.update(a, c); } for (let k = 0, h; k < this.field.length; k++) { h = this.B[k]; var e = this.index.get(this.field[k]); if (typeof h === "function") { var d = h(c); d && e.add(a, d, b, !0); } else { if (d = h.G, !d || d(c)) { h.constructor === String ? h = ["" + h] : N(h) && (h = [h]), Qa(c, h, this.D, 0, e, a, h[0], b); } } } if (this.tag) { for (e = 0; e < this.A.length; e++) { var f = this.A[e], g = this.F[e]; d = this.tag.get(g); let k = I(); if (typeof f === "function") { if (f = f(c), !f) { continue; } } else { const h = f.G; if (h && !h(c)) { continue; } f.constructor === String && (f = "" + f); f = ca(c, f); } if (d && f) { N(f) && (f = [f]); for (let h = 0, l, m; h < f.length; h++) { if (l = f[h], !k[l] && (k[l] = 1, (g = d.get(l)) ? m = g : d.set(l, m = []), !b || !m.includes(a))) { if (m.length === 2 ** 31 - 1) { g = new Aa(m); if (this.fastupdate) { for (let p of this.reg.values()) { p.includes(m) && (p[p.indexOf(m)] = g); } } d.set(l, m = g); } m.push(a); this.fastupdate && ((g = this.reg.get(a)) ? g.push(m) : this.reg.set(a, [m])); } } } else { d || console.warn("Tag '" + g + "' was not found"); } } } if (this.store && (!b || !this.store.has(a))) { let k; if (this.h) { k = I(); for (let h = 0, l; h < this.h.length; h++) { l = this.h[h]; if ((b = l.G) && !b(c)) { continue; } let m; if (typeof l === "function") { m = l(c); if (!m) { continue; } l = [l.O]; } else if (N(l) || l.constructor === String) { k[l] = c[l]; continue; } Ra(c, k, l, 0, l[0], m); } } this.store.set(a, k || c); } this.worker && (this.fastupdate || this.reg.add(a)); } return this; }; function Ra(a, c, b, e, d, f) { a = a[d]; if (e === b.length - 1) { c[d] = f || a; } else if (a) { if (a.constructor === Array) { for (c = c[d] = Array(a.length), d = 0; d < a.length; d++) { Ra(a, c, b, e, d); } } else { c = c[d] || (c[d] = I()), d = b[++e], Ra(a, c, b, e, d); } } } function Qa(a, c, b, e, d, f, g, k) { if (a = a[g]) { if (e === c.length - 1) { if (a.constructor === Array) { if (b[e]) { for (c = 0; c < a.length; c++) { d.add(f, a[c], !0, !0); } return; } a = a.join(" "); } d.add(f, a, k, !0); } else { if (a.constructor === Array) { for (g = 0; g < a.length; g++) { Qa(a, c, b, e, d, f, g, k); } } else { g = c[++e], Qa(a, c, b, e, d, f, g, k); } } } } ;function Sa(a, c, b, e) { if (!a.length) { return a; } if (a.length === 1) { return a = a[0], a = b || a.length > c ? a.slice(b, b + c) : a, e ? Ta.call(this, a) : a; } let d = []; for (let f = 0, g, k; f < a.length; f++) { if ((g = a[f]) && (k = g.length)) { if (b) { if (b >= k) { b -= k; continue; } g = g.slice(b, b + c); k = g.length; b = 0; } k > c && (g = g.slice(0, c), k = c); if (!d.length && k >= c) { return e ? Ta.call(this, g) : g; } d.push(g); c -= k; if (!c) { break; } } } d = d.length > 1 ? [].concat.apply([], d) : d[0]; return e ? Ta.call(this, d) : d; } ;function Ua(a, c, b, e) { var d = e[0]; if (d[0] && d[0].query) { return a[c].apply(a, d); } if (!(c !== "and" && c !== "not" || a.result.length || a.await || d.suggest)) { return e.length > 1 && (d = e[e.length - 1]), (e = d.resolve) ? a.await || a.result : a; } let f = [], g = 0, k = 0, h, l, m, p, u; for (c = 0; c < e.length; c++) { if (d = e[c]) { var r = void 0; if (d.constructor === X) { r = d.await || d.result; } else if (d.then || d.constructor === Array) { r = d; } else { g = d.limit || 0; k = d.offset || 0; m = d.suggest; l = d.resolve; h = ((p = d.highlight || a.highlight) || d.enrich) && l; r = d.queue; let t = d.async || r, n = d.index, q = d.query; n ? a.index || (a.index = n) : n = a.index; if (q || d.tag) { if (!n) { throw Error("Resolver can't apply because the corresponding Index was never specified"); } const x = d.field || d.pluck; if (x) { !q || a.query && !p || (a.query = q, a.field = x, a.highlight = p); if (!n.index) { throw Error("Resolver can't apply because the corresponding Document Index was not specified"); } n = n.index.get(x); if (!n) { throw Error("Resolver can't apply because the specified Document Field '" + x + "' was not found"); } } if (r && (u || a.await)) { u = 1; let v; const A = a.C.length, E = new Promise(function(F) { v = F; }); (function(F, B) { E.h = function() { B.index = null; B.resolve = !1; B.enrich = !1; let C = t ? F.searchAsync(B) : F.search(B); if (C.then) { return C.then(function(z) { a.C[A] = z = z.result || z; v(z); return z; }); } C = C.result || C; v(C); return C; }; })(n, Object.assign({}, d)); a.C.push(E); f[c] = E; continue; } else { d.resolve = !1, d.enrich = !1, d.index = null, r = t ? n.searchAsync(d) : n.search(d), d.resolve = l, d.enrich = h, d.index = n; } } else if (d.and) { r = Va(d, "and", n); } else if (d.or) { r = Va(d, "or", n); } else if (d.not) { r = Va(d, "not", n); } else if (d.xor) { r = Va(d, "xor", n); } else { continue; } } r.await ? (u = 1, r = r.await) : r.then ? (u = 1, r = r.then(function(t) { return t.result || t; })) : r = r.result || r; f[c] = r; } } u && !a.await && (a.await = new Promise(function(t) { a.return = t; })); if (u) { const t = Promise.all(f).then(function(n) { for (let q = 0; q < a.C.length; q++) { if (a.C[q] === t) { a.C[q] = function() { return b.call(a, n, g, k, h, l, m, p); }; break; } } Wa(a); }); a.C.push(t); } else if (a.await) { a.C.push(function() { return b.call(a, f, g, k, h, l, m, p); }); } else { return b.call(a, f, g, k, h, l, m, p); } return l ? a.await || a.result : a; } function Va(a, c, b) { a = a[c]; const e = a[0] || a; e.index || (e.index = b); b = new X(e); a.length > 1 && (b = b[c].apply(b, a.slice(1))); return b; } ;X.prototype.or = function() { return Ua(this, "or", Xa, arguments); }; function Xa(a, c, b, e, d, f, g) { a.length && (this.result.length && a.push(this.result), a.length < 2 ? this.result = a[0] : (this.result = Ya(a, c, b, !1, this.h), b = 0)); d && (this.await = null); return d ? this.resolve(c, b, e, g) : this; } ;X.prototype.and = function() { return Ua(this, "and", Za, arguments); }; function Za(a, c, b, e, d, f, g) { if (!f && !this.result.length) { return d ? this.result : this; } let k; if (a.length) { if (this.result.length && a.unshift(this.result), a.length < 2) { this.result = a[0]; } else { let h = 0; for (let l = 0, m, p; l < a.length; l++) { if ((m = a[l]) && (p = m.length)) { h < p && (h = p); } else if (!f) { h = 0; break; } } h ? (this.result = $a(a, h, c, b, f, this.h, d), k = !0) : this.result = []; } } else { f || (this.result = a); } d && (this.await = null); return d ? this.resolve(c, b, e, g, k) : this; } ;X.prototype.xor = function() { return Ua(this, "xor", ab, arguments); }; function ab(a, c, b, e, d, f, g) { if (a.length) { if (this.result.length && a.unshift(this.result), a.length < 2) { this.result = a[0]; } else { a: { f = b; var k = this.h; const h = [], l = I(); let m = 0; for (let p = 0, u; p < a.length; p++) { if (u = a[p]) { m < u.length && (m = u.length); for (let r = 0, t; r < u.length; r++) { if (t = u[r]) { for (let n = 0, q; n < t.length; n++) { q = t[n], l[q] = l[q] ? 2 : 1; } } } } } for (let p = 0, u, r = 0; p < m; p++) { for (let t = 0, n; t < a.length; t++) { if (n = a[t]) { if (u = n[p]) { for (let q = 0, x; q < u.length; q++) { if (x = u[q], l[x] === 1) { if (f) { f--; } else { if (d) { if (h.push(x), h.length === c) { a = h; break a; } } else { const v = p + (t ? k : 0); h[v] || (h[v] = []); h[v].push(x); if (++r === c) { a = h; break a; } } } } } } } } } a = h; } this.result = a; k = !0; } } else { f || (this.result = a); } d && (this.await = null); return d ? this.resolve(c, b, e, g, k) : this; } ;X.prototype.not = function() { return Ua(this, "not", bb, arguments); }; function bb(a, c, b, e, d, f, g) { if (!f && !this.result.length) { return d ? this.result : this; } if (a.length && this.result.length) { a: { f = b; var k = []; a = new Set(a.flat().flat()); for (let h = 0, l, m = 0; h < this.result.length; h++) { if (l = this.result[h]) { for (let p = 0, u; p < l.length; p++) { if (u = l[p], !a.has(u)) { if (f) { f--; } else { if (d) { if (k.push(u), k.length === c) { a = k; break a; } } else { if (k[h] || (k[h] = []), k[h].push(u), ++m === c) { a = k; break a; } } } } } } } a = k; } this.result = a; k = !0; } d && (this.await = null); return d ? this.resolve(c, b, e, g, k) : this; } ;function cb(a, c, b, e, d) { let f, g, k; typeof d === "string" ? (f = d, d = "") : f = d.template; if (!f) { throw Error('No template pattern was specified by the search option "highlight"'); } g = f.indexOf("$1"); if (g === -1) { throw Error('Invalid highlight template. The replacement pattern "$1" was not found in template: ' + f); } k = f.substring(g + 2); g = f.substring(0, g); let h = d && d.boundary, l = !d || d.clip !== !1, m = d && d.merge && k && g && new RegExp(k + " " + g, "g"); d = d && d.ellipsis; var p = 0; if (typeof d === "object") { var u = d.template; p = u.length - 2; d = d.pattern; } typeof d !== "string" && (d = d === !1 ? "" : "..."); p && (d = u.replace("$1", d)); u = d.length - p; let r, t; typeof h === "object" && (r = h.before, r === 0 && (r = -1), t = h.after, t === 0 && (t = -1), h = h.total || 9e5); p = new Map(); for (let Oa = 0, ea, db, pa; Oa < c.length; Oa++) { let qa; if (e) { qa = c, pa = e; } else { var n = c[Oa]; pa = n.field; if (!pa) { continue; } qa = n.result; } db = b.get(pa); ea = db.encoder; n = p.get(ea); typeof n !== "string" && (n = ea.encode(a), p.set(ea, n)); for (let ya = 0; ya < qa.length; ya++) { var q = qa[ya].doc; if (!q) { continue; } q = ca(q, pa); if (!q) { continue; } var x = q.trim().split(/\s+/); if (!x.length) { continue; } q = ""; var v = []; let za = []; var A = -1, E = -1, F = 0; for (var B = 0; B < x.length; B++) { var C = x[B], z = ea.encode(C); z = z.length > 1 ? z.join(" ") : z[0]; let y; if (z && C) { var D = C.length, J = (ea.split ? C.replace(ea.split, "") : C).length - z.length, G = "", L = 0; for (var O = 0; O < n.length; O++) { var P = n[O]; if (P) { var M = P.length; M += J < 0 ? 0 : J; L && M <= L || (P = z.indexOf(P), P > -1 && (G = (P ? C.substring(0, P) : "") + g + C.substring(P, P + M) + k + (P + M < D ? C.substring(P + M) : ""), L = M, y = !0)); } } G && (h && (A < 0 && (A = q.length + (q ? 1 : 0)), E = q.length + (q ? 1 : 0) + G.length, F += D, za.push(v.length), v.push({match:G})), q += (q ? " " : "") + G); } if (!y) { C = x[B], q += (q ? " " : "") + C, h && v.push({text:C}); } else if (h && F >= h) { break; } } F = za.length * (f.length - 2); if (r || t || h && q.length - F > h) { if (F = h + F - u * 2, B = E - A, r > 0 && (B += r), t > 0 && (B += t), B <= F) { x = r ? A - (r > 0 ? r : 0) : A - ((F - B) / 2 | 0), v = t ? E + (t > 0 ? t : 0) : x + F, l || (x > 0 && q.charAt(x) !== " " && q.charAt(x - 1) !== " " && (x = q.indexOf(" ", x), x < 0 && (x = 0)), v < q.length && q.charAt(v - 1) !== " " && q.charAt(v) !== " " && (v = q.lastIndexOf(" ", v), v < E ? v = E : ++v)), q = (x ? d : "") + q.substring(x, v) + (v < q.length ? d : ""); } else { E = []; A = {}; F = {}; B = {}; C = {}; z = {}; G = J = D = 0; for (O = L = 1;;) { var U = void 0; for (let y = 0, K; y < za.length; y++) { K = za[y]; if (G) { if (J !== G) { if (B[y + 1]) { continue; } K += G; if (A[K]) { D -= u; F[y + 1] = 1; B[y + 1] = 1; continue; } if (K >= v.length - 1) { if (K >= v.length) { B[y + 1] = 1; K >= x.length && (F[y + 1] = 1); continue; } D -= u; } q = v[K].text; if (M = t && z[y]) { if (M > 0) { if (q.length > M) { if (B[y + 1] = 1, l) { q = q.substring(0, M); } else { continue; } } (M -= q.length) || (M = -1); z[y] = M; } else { B[y + 1] = 1; continue; } } if (D + q.length + 1 <= h) { q = " " + q, E[y] += q; } else if (l) { U = h - D - 1, U > 0 && (q = " " + q.substring(0, U), E[y] += q), B[y + 1] = 1; } else { B[y + 1] = 1; continue; } } else { if (B[y]) { continue; } K -= J; if (A[K]) { D -= u; B[y] = 1; F[y] = 1; continue; } if (K <= 0) { if (K < 0) { B[y] = 1; F[y] = 1; continue; } D -= u; } q = v[K].text; if (M = r && C[y]) { if (M > 0) { if (q.length > M) { if (B[y] = 1, l) { q = q.substring(q.length - M); } else { continue; } } (M -= q.length) || (M = -1); C[y] = M; } else { B[y] = 1; continue; } } if (D + q.length + 1 <= h) { q += " ", E[y] = q + E[y]; } else if (l) { U = q.length + 1 - (h - D), U >= 0 && U < q.length && (q = q.substring(U) + " ", E[y] = q + E[y]), B[y] = 1; } else { B[y] = 1; continue; } } } else { q = v[K].match; r && (C[y] = r); t && (z[y] = t); y && D++; let Pa; K ? !y && u && (D += u) : (F[y] = 1, B[y] = 1); K >= x.length - 1 ? Pa = 1 : K < v.length - 1 && v[K + 1].match ? Pa = 1 : u && (D += u); D -= f.length - 2; if (!y || D + q.length <= h) { E[y] = q; } else { U = L = O = F[y] = 0; break; } Pa && (F[y + 1] = 1, B[y + 1] = 1); } D += q.length; U = A[K] = 1; } if (U) { J === G ? G++ : J++; } else { J === G ? L = 0 : O = 0; if (!L && !O) { break; } L ? (J++, G = J) : G++; } } q = ""; for (let y = 0, K; y < E.length; y++) { K = (F[y] ? y ? " " : "" : (y && !d ? " " : "") + d) + E[y], q += K; } d && !F[E.length] && (q += d); } } m && (q = q.replace(m, " ")); qa[ya].highlight = q; } if (e) { break; } } return c; } ;function X(a, c) { if (!this || this.constructor !== X) { return new X(a, c); } let b = 0, e, d, f, g, k, h; if (a && a.index) { const l = a; c = l.index; b = l.boost || 0; if (d = l.query) { f = l.field || l.pluck; g = l.highlight; const m = l.resolve; a = l.async || l.queue; l.resolve = !1; l.highlight = ""; l.index = null; a = a ? c.searchAsync(l) : c.search(l); l.resolve = m; l.highlight = g; l.index = c; a = a.result || a; } else { a = []; } } if (a && a.then) { const l = this; a = a.then(function(m) { l.C[0] = l.result = m.result || m; Wa(l); }); e = [a]; a = []; k = new Promise(function(m) { h = m; }); } this.index = c || null; this.result = a || []; this.h = b; this.C = e || []; this.await = k || null; this.return = h || null; this.highlight = g || null; this.query = d || ""; this.field = f || ""; } w = X.prototype; w.limit = function(a) { if (this.await) { const c = this; this.C.push(function() { return c.limit(a).result; }); } else { if (this.result.length) { const c = []; for (let b = 0, e; b < this.result.length; b++) { if (e = this.result[b]) { if (e.length <= a) { if (c[b] = e, a -= e.length, !a) { break; } } else { c[b] = e.slice(0, a); break; } } } this.result = c; } } return this; }; w.offset = function(a) { if (this.await) { const c = this; this.C.push(function() { return c.offset(a).result; }); } else { if (this.result.length) { const c = []; for (let b = 0, e; b < this.result.length; b++) { if (e = this.result[b]) { e.length <= a ? a -= e.length : (c[b] = e.slice(a), a = 0); } } this.result = c; } } return this; }; w.boost = function(a) { if (this.await) { const c = this; this.C.push(function() { return c.boost(a).result; }); } else { this.h += a; } return this; }; function Wa(a, c) { let b = a.result; var e = a.await; a.await = null; for (let d = 0, f; d < a.C.length; d++) { if (f = a.C[d]) { if (typeof f === "function") { b = f(), a.C[d] = b = b.result || b, d--; } else if (f.h) { b = f.h(), a.C[d] = b = b.result || b, d--; } else if (f.then) { return a.await = e; } } } e = a.return; a.C = []; a.return = null; c || e(b); return b; } w.resolve = function(a, c, b, e, d) { let f = this.await ? Wa(this, !0) : this.result; if (f.then) { const g = this; return f.then(function() { return g.resolve(a, c, b, e, d); }); } f.length && (typeof a === "object" ? (e = a.highlight || this.highlight, b = !!e || a.enrich, c = a.offset, a = a.limit) : (e = e || this.highlight, b = !!e || b), f = d ? b ? Ta.call(this.index, f) : f : Sa.call(this.index, f, a || 100, c, b)); return this.finalize(f, e); }; w.finalize = function(a, c) { if (a.then) { const e = this; return a.then(function(d) { return e.finalize(d, c); }); } c && !this.query && console.warn('There was no query specified for highlighting. Please specify a query within the highlight resolver stage like { query: "...", highlight: ... }.'); c && a.length && this.query && (a = cb(this.query, a, this.index.index, this.field, c)); const b = this.return; this.highlight = this.index = this.result = this.C = this.await = this.return = null; this.query = this.field = ""; b && b(a); return a; }; function $a(a, c, b, e, d, f, g) { const k = a.length; let h = [], l, m; l = I(); for (let p = 0, u, r, t, n; p < c; p++) { for (let q = 0; q < k; q++) { if (t = a[q], p < t.length && (u = t[p])) { for (let x = 0; x < u.length; x++) { r = u[x]; (m = l[r]) ? l[r]++ : (m = 0, l[r] = 1); n = h[m] || (h[m] = []); if (!g) { let v = p + (q || !d ? 0 : f || 0); n = n[v] || (n[v] = []); } n.push(r); if (g && b && m === k - 1 && n.length - e === b) { return e ? n.slice(e) : n; } } } } } if (a = h.length) { if (d) { h = h.length > 1 ? Ya(h, b, e, g, f) : (h = h[0]) && b && h.length > b || e ? h.slice(e, b + e) : h; } else { if (a < k) { return []; } h = h[a - 1]; if (b || e) { if (g) { if (h.length > b || e) { h = h.slice(e, b + e); } } else { d = []; for (let p = 0, u; p < h.length; p++) { if (u = h[p]) { if (e && u.length > e) { e -= u.length; } else { if (b && u.length > b || e) { u = u.slice(e, b + e), b -= u.length, e && (e -= u.length); } d.push(u); if (!b) { break; } } } } h = d; } } } } return h; } function Ya(a, c, b, e, d) { const f = [], g = I(); let k; var h = a.length; let l; if (e) { for (d = h - 1; d >= 0; d--) { if (l = (e = a[d]) && e.length) { for (h = 0; h < l; h++) { if (k = e[h], !g[k]) { if (g[k] = 1, b) { b--; } else { if (f.push(k), f.length === c) { return f; } } } } } } } else { for (let m = h - 1, p, u = 0; m >= 0; m--) { p = a[m]; for (let r = 0; r < p.length; r++) { if (l = (e = p[r]) && e.length) { for (let t = 0; t < l; t++) { if (k = e[t], !g[k]) { if (g[k] = 1, b) { b--; } else { let n = (r + (m < h - 1 ? d || 0 : 0)) / (m + 1) | 0; (f[n] || (f[n] = [])).push(k); if (++u === c) { return f; } } } } } } } } return f; } function eb(a, c, b, e, d) { const f = I(), g = []; for (let k = 0, h; k < c.length; k++) { h = c[k]; for (let l = 0; l < h.length; l++) { f[h[l]] = 1; } } if (d) { for (let k = 0, h; k < a.length; k++) { if (h = a[k], f[h]) { if (e) { e--; } else { if (g.push(h), f[h] = 0, b && --b === 0) { break; } } } } } else { a = a.result || a; for (let k = 0, h, l; k < a.length; k++) { for (h = a[k], c = 0; c < h.length; c++) { l = h[c], f[l] && ((g[k] || (g[k] = [])).push(l), f[l] = 0); } } } return g; } ;I(); Na.prototype.search = function(a, c, b, e) { b || (!c && ba(a) ? (b = a, a = "") : ba(c) && (b = c, c = 0)); let d = []; var f = []; let g; let k, h, l, m, p; let u = 0, r = !0, t; if (b) { b.constructor === Array && (b = {index:b}); a = b.query || a; g = b.pluck; k = b.merge; l = b.boost; p = g || b.field || (p = b.index) && (p.index ? null : p); var n = this.tag && b.tag; h = b.suggest; r = b.resolve !== !1; m = b.cache; this.store && b.highlight && !r ? console.warn("Highlighting results can only be done within a resolver stage (and/or/not/xor) or when calling .resolve({ highlight: ... })") : this.store && b.enrich && !r && console.warn("Enrich results can only be done on a final resolver task or when calling .resolve({ enrich: true })"); t = r && this.store && b.highlight; var q = !!t || r && this.store && b.enrich; c = b.limit || c; var x = b.offset || 0; c || (c = r ? 100 : 0); if (n && (!this.db || !e)) { n.constructor !== Array && (n = [n]); var v = []; for (let C = 0, z; C < n.length; C++) { z = n[C]; if (N(z)) { throw Error("A tag option can't be a string, instead it needs a { field: tag } format."); } if (z.field && z.tag) { var A = z.tag; if (A.constructor === Array) { for (var E = 0; E < A.length; E++) { v.push(z.field, A[E]); } } else { v.push(z.field, A); } } else { A = Object.keys(z); for (let D = 0, J, G; D < A.length; D++) { if (J = A[D], G = z[J], G.constructor === Array) { for (E = 0; E < G.length; E++) { v.push(J, G[E]); } } else { v.push(J, G); } } } } if (!v.length) { throw Error("Your tag definition within the search options is probably wrong. No valid tags found."); } n = v; if (!a) { f = []; if (v.length) { for (n = 0; n < v.length; n += 2) { if (this.db) { e = this.index.get(v[n]); if (!e) { console.warn("Tag '" + v[n] + ":" + v[n + 1] + "' will be skipped because there is no field '" + v[n] + "'."); continue; } f.push(e = e.db.tag(v[n + 1], c, x, q)); } else { e = fb.call(this, v[n], v[n + 1], c, x, q); } d.push(r ? {field:v[n], tag:v[n + 1], result:e} : [e]); } } if (f.length) { const C = this; return Promise.all(f).then(function(z) { for (let D = 0; D < z.length; D++) { r ? d[D].result = z[D] : d[D] = z[D]; } return r ? d : new X(d.length > 1 ? $a(d, 1, 0, 0, h, l) : d[0], C); }); } return r ? d : new X(d.length > 1 ? $a(d, 1, 0, 0, h, l) : d[0], this); } } if (!r && !g) { if (p = p || this.field) { N(p) ? g = p : (p.constructor === Array && p.length === 1 && (p = p[0]), g = p.field || p.index); } if (!g) { throw Error("Apply resolver on document search requires either the option 'pluck' to be set or just select a single field name in your query."); } } p && p.constructor !== Array && (p = [p]); } p || (p = this.field); let F; v = (this.worker || this.db) && !e && []; for (let C = 0, z, D, J; C < p.length; C++) { D = p[C]; if (this.db && this.tag && !this.B[C]) { continue; } let G; N(D) || (G = D, D = G.field, a = G.query || a, c = aa(G.limit, c), x = aa(G.offset, x), h = aa(G.suggest, h), t = r && this.store && aa(G.highlight, t), q = !!t || r && this.store && aa(G.enrich, q), m = aa(G.cache, m)); if (e) { z = e[C]; } else { A = G || b || {}; E = A.enrich; var B = this.index.get(D); n && (this.db && (A.tag = n, A.field = p, F = B.db.support_tag_search), !F && E && (A.enrich = !1), F || (A.limit = 0, A.offset = 0)); z = m ? B.searchCache(a, n && !F ? 0 : c, A) : B.search(a, n && !F ? 0 : c, A); n && !F && (A.limit = c, A.offset = x); E && (A.enrich = E); if (v) { v[C] = z; continue; } } J = (z = z.result || z) && z.length; if (n && J) { A = []; E = 0; if (this.db && e) { if (!F) { for (B = p.length; B < e.length; B++) { let L = e[B]; if (L && L.length) { E++, A.push(L); } else if (!h) { return r ? d : new X(d, this); } } } } else { for (let L = 0, O, P; L < n.length; L += 2) { O = this.tag.get(n[L]); if (!O) { if (console.warn("Tag '" + n[L] + ":" + n[L + 1] + "' will be skipped because there is no field '" + n[L] + "'."), h) { continue; } else { return r ? d : new X(d, this); } } if (P = (O = O && O.get(n[L + 1])) && O.length) { E++, A.push(O); } else if (!h) { return r ? d : new X(d, this); } } } if (E) { z = eb(z, A, c, x, r); J = z.length; if (!J && !h) { return r ? z : new X(z, this); } E--; } } if (J) { f[u] = D, d.push(z), u++; } else if (p.length === 1) { return r ? d : new X(d, this); } } if (v) { if (this.db && n && n.length && !F) { for (q = 0; q < n.length; q += 2) { f = this.index.get(n[q]); if (!f) { if (console.warn("Tag '" + n[q] + ":" + n[q + 1] + "' was not found because there is no field '" + n[q] + "'."), h) { continue; } else { return r ? d : new X(d, this); } } v.push(f.db.tag(n[q + 1], c, x, !1)); } } const C = this; return Promise.all(v).then(function(z) { b && (b.resolve = r); z.length && (z = C.search(a, c, b, z)); return z; }); } if (!u) { return r ? d : new X(d, this); } if (g && (!q || !this.store)) { return d = d[0], r ? d : new X(d, this); } v = []; for (x = 0; x < f.length; x++) { n = d[x]; q && n.length && typeof n[0].doc === "undefined" && (this.db ? v.push(n = this.index.get(this.field[0]).db.enrich(n)) : n = Ta.call(this, n)); if (g) { return r ? t ? cb(a, n, this.index, g, t) : n : new X(n, this); } d[x] = {field:f[x], result:n}; } if (q && this.db && v.length) { const C = this; return Promise.all(v).then(function(z) { for (let D = 0; D < z.length; D++) { d[D].result = z[D]; } t && (d = cb(a, d, C.index, g, t)); return k ? gb(d) : d; }); } t && (d = cb(a, d, this.index, g, t)); return k ? gb(d) : d; }; function gb(a) { const c = [], b = I(), e = I(); for (let d = 0, f, g, k, h, l, m, p; d < a.length; d++) { f = a[d]; g = f.field; k = f.result; for (let u = 0; u < k.length; u++) { if (l = k[u], typeof l !== "object" ? l = {id:h = l} : h = l.id, (m = b[h]) ? m.push(g) : (l.field = b[h] = [g], c.push(l)), p = l.highlight) { m = e[h], m || (e[h] = m = {}, l.highlight = m), m[g] = p; } } } return c; } function fb(a, c, b, e, d) { a = this.tag.get(a); if (!a) { return []; } a = a.get(c); if (!a) { return []; } c = a.length - e; if (c > 0) { if (b && c > b || e) { a = a.slice(e, e + b); } d && (a = Ta.call(this, a)); } return a; } function Ta(a) { if (!this || !this.store) { return a; } if (this.db) { return this.index.get(this.field[0]).db.enrich(a); } const c = Array(a.length); for (let b = 0, e; b < a.length; b++) { e = a[b], c[b] = {id:e, doc:this.store.get(e)}; } return c; } ;function Na(a) { if (!this || this.constructor !== Na) { return new Na(a); } const c = a.document || a.doc || a; let b, e; this.B = []; this.field = []; this.D = []; this.key = (b = c.key || c.id) && hb(b, this.D) || "id"; (e = a.keystore || 0) && (this.keystore = e); this.fastupdate = !!a.fastupdate; this.reg = !this.fastupdate || a.worker || a.db ? e ? new R(e) : new Set() : e ? new Q(e) : new Map(); this.h = (b = c.store || null) && b && b !== !0 && []; this.store = b ? e ? new Q(e) : new Map() : null; this.cache = (b = a.cache || null) && new na(b); a.cache = !1; this.worker = a.worker || !1; this.priority = a.priority || 4; this.index = ib.call(this, a, c); this.tag = null; if (b = c.tag) { if (typeof b === "string" && (b = [b]), b.length) { this.tag = new Map(); this.A = []; this.F = []; for (let d = 0, f, g; d < b.length; d++) { f = b[d]; g = f.field || f; if (!g) { throw Error("The tag field from the document descriptor is undefined."); } f.custom ? this.A[d] = f.custom : (this.A[d] = hb(g, this.D), f.filter && (typeof this.A[d] === "string" && (this.A[d] = new String(this.A[d])), this.A[d].G = f.filter)); this.F[d] = g; this.tag.set(g, new Map()); } } } if (this.worker) { this.fastupdate = !1; a = []; for (const d of this.index.values()) { d.then && a.push(d); } if (a.length) { const d = this; return Promise.all(a).then(function(f) { let g = 0; for (const k of d.index.entries()) { const h = k[0]; let l = k[1]; l.then && (l = f[g], d.index.set(h, l), g++); } return d; }); } } else { a.db && (this.fastupdate = !1, this.mount(a.db)); } } w = Na.prototype; w.mount = function(a) { if (this.worker) { throw Error("You can't use Worker-Indexes on a persistent model. That would be useless, since each of the persistent model acts like Worker-Index by default (Master/Slave)."); } let c = this.field; if (this.tag) { for (let f = 0, g; f < this.F.length; f++) { g = this.F[f]; var b = void 0; this.index.set(g, b = new T({}, this.reg)); c === this.field && (c = c.slice(0)); c.push(g); b.tag = this.tag.get(g); } } b = []; const e = {db:a.db, type:a.type, fastupdate:a.fastupdate}; for (let f = 0, g, k; f < c.length; f++) { e.field = k = c[f]; g = this.index.get(k); const h = new a.constructor(a.id, e); h.id = a.id; b[f] = h.mount(g); g.document = !0; f ? g.bypass = !0 : g.store = this.store; } const d = this; return this.db = Promise.all(b).then(function() { d.db = !0; }); }; w.commit = async function() { const a = []; for (const c of this.index.values()) { a.push(c.commit()); } await Promise.all(a); this.reg.clear(); }; w.destroy = function() { const a = []; for (const c of this.index.values()) { a.push(c.destroy()); } return Promise.all(a); }; function ib(a, c) { const b = new Map(); let e = c.index || c.field || c; N(e) && (e = [e]); for (let f = 0, g, k; f < e.length; f++) { g = e[f]; N(g) || (k = g, g = g.field); k = ba(k) ? Object.assign({}, a, k) : a; if (this.worker) { var d = void 0; d = (d = k.encoder) && d.encode ? d : new ka(typeof d === "string" ? wa[d] : d || {}); d = new La(k, d); b.set(g, d); } this.worker || b.set(g, new T(k, this.reg)); k.custom ? this.B[f] = k.custom : (this.B[f] = hb(g, this.D), k.filter && (typeof this.B[f] === "string" && (this.B[f] = new String(this.B[f])), this.B[f].G = k.filter)); this.field[f] = g; } if (this.h) { a = c.store; N(a) && (a = [a]); for (let f = 0, g, k; f < a.length; f++) { g = a[f], k = g.field || g, g.custom ? (this.h[f] = g.custom, g.custom.O = k) : (this.h[f] = hb(k, this.D), g.filter && (typeof this.h[f] === "string" && (this.h[f] = new String(this.h[f])), this.h[f].G = g.filter)); } } return b; } function hb(a, c) { const b = a.split(":"); let e = 0; for (let d = 0; d < b.length; d++) { a = b[d], a[a.length - 1] === "]" && (a = a.substring(0, a.length - 2)) && (c[e] = !0), a && (b[e++] = a); } e < b.length && (b.length = e); return e > 1 ? b : b[0]; } w.append = function(a, c) { return this.add(a, c, !0); }; w.update = function(a, c) { return this.remove(a).add(a, c); }; w.remove = function(a) { ba(a) && (a = ca(a, this.key)); for (var c of this.index.values()) { c.remove(a, !0); } if (this.reg.has(a)) { if (this.tag && !this.fastupdate) { for (let b of this.tag.values()) { for (let e of b) { c = e[0]; const d = e[1], f = d.indexOf(a); f > -1 && (d.length > 1 ? d.splice(f, 1) : b.delete(c)); } } } this.store && this.store.delete(a); this.reg.delete(a); } this.cache && this.cache.remove(a); return this; }; w.clear = function() { const a = []; for (const c of this.index.values()) { const b = c.clear(); b.then && a.push(b); } if (this.tag) { for (const c of this.tag.values()) { c.clear(); } } this.store && this.store.clear(); this.cache && this.cache.clear(); return a.length ? Promise.all(a) : this; }; w.contain = function(a) { return this.db ? this.index.get(this.field[0]).db.has(a) : this.reg.has(a); }; w.cleanup = function() { for (const a of this.index.values()) { a.cleanup(); } return this; }; w.get = function(a) { return this.db ? this.index.get(this.field[0]).db.enrich(a).then(function(c) { return c[0] && c[0].doc || null; }) : this.store.get(a) || null; }; w.set = function(a, c) { typeof a === "object" && (c = a, a = ca(c, this.key)); this.store.set(a, c); return this; }; w.searchCache = ma; w.export = jb; w.import = kb; Fa(Na.prototype); function lb(a, c = 0) { let b = [], e = []; c && (c = 250000 / c * 5000 | 0); for (const d of a.entries()) { e.push(d), e.length === c && (b.push(e), e = []); } e.length && b.push(e); return b; } function mb(a, c) { c || (c = new Map()); for (let b = 0, e; b < a.length; b++) { e = a[b], c.set(e[0], e[1]); } return c; } function nb(a, c = 0) { let b = [], e = []; c && (c = 250000 / c * 1000 | 0); for (const d of a.entries()) { e.push([d[0], lb(d[1])[0] || []]), e.length === c && (b.push(e), e = []); } e.length && b.push(e); return b; } function ob(a, c) { c || (c = new Map()); for (let b = 0, e, d; b < a.length; b++) { e = a[b], d = c.get(e[0]), c.set(e[0], mb(e[1], d)); } return c; } function pb(a) { let c = [], b = []; for (const e of a.keys()) { b.push(e), b.length === 250000 && (c.push(b), b = []); } b.length && c.push(b); return c; } function qb(a, c) { c || (c = new Set()); for (let b = 0; b < a.length; b++) { c.add(a[b]); } return c; } function rb(a, c, b, e, d, f, g = 0) { const k = e && e.constructor === Array; var h = k ? e.shift() : e; if (!h) { return this.export(a, c, d, f + 1); } if ((h = a((c ? c + "." : "") + (g + 1) + "." + b, JSON.stringify(h))) && h.then) { const l = this; return h.then(function() { return rb.call(l, a, c, b, k ? e : null, d, f, g + 1); }); } return rb.call(this, a, c, b, k ? e : null, d, f, g + 1); } function jb(a, c, b = 0, e = 0) { if (b < this.field.length) { const g = this.field[b]; if ((c = this.index.get(g).export(a, g, b, e = 1)) && c.then) { const k = this; return c.then(function() { return k.export(a, g, b + 1); }); } return this.export(a, g, b + 1); } let d, f; switch(e) { case 0: d = "reg"; f = pb(this.reg); c = null; break; case 1: d = "tag"; f = this.tag && nb(this.tag, this.reg.size); c = null; break; case 2: d = "doc"; f = this.store && lb(this.store); c = null; break; default: return; } return rb.call(this, a, c, d, f || null, b, e); } function kb(a, c) { var b = a.split("."); b[b.length - 1] === "json" && b.pop(); const e = b.length > 2 ? b[0] : ""; b = b.length > 2 ? b[2] : b[1]; if (this.worker && e) { return this.index.get(e).import(a); } if (c) { typeof c === "string" && (c = JSON.parse(c)); if (e) { return this.index.get(e).import(b, c); } switch(b) { case "reg": this.fastupdate = !1; this.reg = qb(c, this.reg); for (let d = 0, f; d < this.field.length; d++) { f = this.index.get(this.field[d]), f.fastupdate = !1, f.reg = this.reg; } if (this.worker) { c = []; for (const d of this.index.values()) { c.push(d.import(a)); } return Promise.all(c); } break; case "tag": this.tag = ob(c, this.tag); break; case "doc": this.store = mb(c, this.store); } } } function sb(a, c) { let b = ""; for (const e of a.entries()) { a = e[0]; const d = e[1]; let f = ""; for (let g = 0, k; g < d.length; g++) { k = d[g] || [""]; let h = ""; for (let l = 0; l < k.length; l++) { h += (h ? "," : "") + (c === "string" ? '"' + k[l] + '"' : k[l]); } h = "[" + h + "]"; f += (f ? "," : "") + h; } f = '["' + a + '",[' + f + "]]"; b += (b ? "," : "") + f; } return b; } ;T.prototype.remove = function(a, c) { const b = this.reg.size && (this.fastupdate ? this.reg.get(a) : this.reg.has(a)); if (b) { if (this.fastupdate) { for (let e = 0, d, f; e < b.length; e++) { if ((d = b[e]) && (f = d.length)) { if (d[f - 1] === a) { d.pop(); } else { const g = d.indexOf(a); g >= 0 && d.splice(g, 1); } } } } else { tb(this.map, a), this.depth && tb(this.ctx, a); } c || this.reg.delete(a); } this.db && (this.commit_task.push({del:a}), this.M && ub(this)); this.cache && this.cache.remove(a); return this; }; function tb(a, c) { let b = 0; var e = typeof c === "undefined"; if (a.constructor === Array) { for (let d = 0, f, g, k; d < a.length; d++) { if ((f = a[d]) && f.length) { if (e) { return 1; } g = f.indexOf(c); if (g >= 0) { if (f.length > 1) { return f.splice(g, 1), 1; } delete a[d]; if (b) { return 1; } k = 1; } else { if (k) { return 1; } b++; } } } } else { for (let d of a.entries()) { e = d[0], tb(d[1], c) ? b++ : a.delete(e); } } return b; } ;const vb = {memory:{resolution:1}, performance:{resolution:3, fastupdate:!0, context:{depth:1, resolution:1}}, match:{tokenize:"full"}, score:{resolution:9, context:{depth:2, resolution:3}}}; T.prototype.add = function(a, c, b, e) { if (c && (a || a === 0)) { if (!e && !b && this.reg.has(a)) { return this.update(a, c); } e = this.depth; c = this.encoder.encode(c, !e); const l = c.length; if (l) { const m = I(), p = I(), u = this.resolution; for (let r = 0; r < l; r++) { let t = c[this.rtl ? l - 1 - r : r]; var d = t.length; if (d && (e || !p[t])) { var f = this.score ? this.score(c, t, r, null, 0) : wb(u, l, r), g = ""; switch(this.tokenize) { case "tolerant": Y(this, p, t, f, a, b); if (d > 2) { for (let n = 1, q, x, v, A; n < d - 1; n++) { q = t.charAt(n), x = t.charAt(n + 1), v = t.substring(0, n) + x, A = t.substring(n + 2), g = v + q + A, Y(this, p, g, f, a, b), g = v + A, Y(this, p, g, f, a, b); } Y(this, p, t.substring(0, t.length - 1), f, a, b); } break; case "full": if (d > 2) { for (let n = 0, q; n < d; n++) { for (f = d; f > n; f--) { g = t.substring(n, f); q = this.rtl ? d - 1 - n : n; var k = this.score ? this.score(c, t, r, g, q) : wb(u, l, r, d, q); Y(this, p, g, k, a, b); } } break; } case "bidirectional": case "reverse": if (d > 1) { for (k = d - 1; k > 0; k--) { g = t[this.rtl ? d - 1 - k : k] + g; var h = this.score ? this.score(c, t, r, g, k) : wb(u, l, r, d, k); Y(this, p, g, h, a, b); } g = ""; } case "forward": if (d > 1) { for (k = 0; k < d; k++) { g += t[this.rtl ? d - 1 - k : k], Y(this, p, g, f, a, b); } break; } default: if (Y(this, p, t, f, a, b), e && l > 1 && r < l - 1) { for (d = this.N, g = t, f = Math.min(e + 1, this.rtl ? r + 1 : l - r), k = 1; k < f; k++) { t = c[this.rtl ? l - 1 - r - k : r + k]; h = this.bidirectional && t > g; const n = this.score ? this.score(c, g, r, t, k - 1) : wb(d + (l / 2 > d ? 0 : 1), l, r, f - 1, k - 1); Y(this, m, h ? g : t, n, a, b, h ? t : g); } } } } } this.fastupdate || this.reg.add(a); } } this.db && (this.commit_task.push(b ? {ins:a} : {del:a}), this.M && ub(this)); return this; }; function Y(a, c, b, e, d, f, g) { let k, h; if (!(k = c[b]) || g && !k[g]) { g ? (c = k || (c[b] = I()), c[g] = 1, h = a.ctx, (k = h.get(g)) ? h = k : h.set(g, h = a.keystore ? new Q(a.keystore) : new Map())) : (h = a.map, c[b] = 1); (k = h.get(b)) ? h = k : h.set(b, h = k = []); if (f) { for (let l = 0, m; l < k.length; l++) { if ((m = k[l]) && m.includes(d)) { if (l <= e) { return; } m.splice(m.indexOf(d), 1); a.fastupdate && (c = a.reg.get(d)) && c.splice(c.indexOf(m), 1); break; } } } h = h[e] || (h[e] = []); h.push(d); if (h.length === 2 ** 31 - 1) { c = new Aa(h); if (a.fastupdate) { for (let l of a.reg.values()) { l.includes(h) && (l[l.indexOf(h)] = c); } } k[e] = h = c; } a.fastupdate && ((e = a.reg.get(d)) ? e.push(h) : a.reg.set(d, [h])); } } function wb(a, c, b, e, d) { return b && a > 1 ? c + (e || 0) <= a ? b + (d || 0) : (a - 1) / (c + (e || 0)) * (b + (d || 0)) + 1 | 0 : 0; } ;T.prototype.search = function(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : (b = a, a = "")); if (b && b.cache) { return b.cache = !1, a = this.searchCache(a, c, b), b.cache = !0, a; } let e = [], d, f, g, k = 0, h, l, m, p, u; b && (a = b.query || a, c = b.limit || c, k = b.offset || 0, f = b.context, g = b.suggest, u = (h = b.resolve) && b.enrich, m = b.boost, p = b.resolution, l = this.db && b.tag); typeof h === "undefined" && (h = this.resolve); f = this.depth && f !== !1; let r = this.encoder.encode(a, !f); d = r.length; c = c || (h ? 100 : 0); if (d === 1) { return xb.call(this, r[0], "", c, k, h, u, l); } if (d === 2 && f && !g) { return xb.call(this, r[1], r[0], c, k, h, u, l); } let t = I(), n = 0, q; f && (q = r[0], n = 1); p || p === 0 || (p = q ? this.N : this.resolution); if (this.db) { if (this.db.search && (b = this.db.search(this, r, c, k, g, h, u, l), b !== !1)) { return b; } const x = this; return async function() { for (let v, A; n < d; n++) { if ((A = r[n]) && !t[A]) { t[A] = 1; v = await yb(x, A, q, 0, 0, !1, !1); if (v = zb(v, e, g, p)) { e = v; break; } q && (g && v && e.length || (q = A)); } g && q && n === d - 1 && !e.length && (p = x.resolution, q = "", n = -1, t = I()); } return Ab(e, p, c, k, g, m, h); }(); } for (let x, v; n < d; n++) { if ((v = r[n]) && !t[v]) { t[v] = 1; x = yb(this, v, q, 0, 0, !1, !1); if (x = zb(x, e, g, p)) { e = x; break; } q && (g && x && e.length || (q = v)); } g && q && n === d - 1 && !e.length && (p = this.resolution, q = "", n = -1, t = I()); } return Ab(e, p, c, k, g, m, h); }; function Ab(a, c, b, e, d, f, g) { let k = a.length, h = a; if (k > 1) { h = $a(a, c, b, e, d, f, g); } else if (k === 1) { return g ? Sa.call(null, a[0], b, e) : new X(a[0], this); } return g ? h : new X(h, this); } function xb(a, c, b, e, d, f, g) { a = yb(this, a, c, b, e, d, f, g); return this.db ? a.then(function(k) { return d ? k || [] : new X(k, this); }) : a && a.length ? d ? Sa.call(this, a, b, e) : new X(a, this) : d ? [] : new X([], this); } function zb(a, c, b, e) { let d = []; if (a && a.length) { if (a.length <= e) { c.push(a); return; } for (let f = 0, g; f < e; f++) { if (g = a[f]) { d[f] = g; } } if (d.length) { c.push(d); return; } } if (!b) { return d; } } function yb(a, c, b, e, d, f, g, k) { let h; b && (h = a.bidirectional && c > b) && (h = b, b = c, c = h); if (a.db) { return a.db.get(c, b, e, d, f, g, k); } a = b ? (a = a.ctx.get(b)) && a.get(c) : a.map.get(c); return a; } ;function T(a, c) { if (!this || this.constructor !== T) { return new T(a); } if (a) { var b = N(a) ? a : a.preset; b && (vb[b] || console.warn("Preset not found: " + b), a = Object.assign({}, vb[b], a)); } else { a = {}; } b = a.context; const e = b === !0 ? {depth:1} : b || {}, d = N(a.encoder) ? wa[a.encoder] : a.encode || a.encoder || {}; this.encoder = d.encode ? d : typeof d === "object" ? new ka(d) : {encode:d}; this.resolution = a.resolution || 9; this.tokenize = b = (b = a.tokenize) && b !== "default" && b !== "exact" && b || "strict"; this.depth = b === "strict" && e.depth || 0; this.bidirectional = e.bidirectional !== !1; this.fastupdate = !!a.fastupdate; this.score = a.score || null; e && e.depth && this.tokenize !== "strict" && console.warn('Context-Search could not applied, because it is just supported when using the tokenizer "strict".'); (b = a.keystore || 0) && (this.keystore = b); this.map = b ? new Q(b) : new Map(); this.ctx = b ? new Q(b) : new Map(); this.reg = c || (this.fastupdate ? b ? new Q(b) : new Map() : b ? new R(b) : new Set()); this.N = e.resolution || 3; this.rtl = d.rtl || a.rtl || !1; this.cache = (b = a.cache || null) && new na(b); this.resolve = a.resolve !== !1; if (b = a.db) { this.db = this.mount(b); } this.M = a.commit !== !1; this.commit_task = []; this.commit_timer = null; this.priority = a.priority || 4; } w = T.prototype; w.mount = function(a) { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return a.mount(this); }; w.commit = function() { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return this.db.commit(this); }; w.destroy = function() { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return this.db.destroy(); }; function ub(a) { a.commit_timer || (a.commit_timer = setTimeout(function() { a.commit_timer = null; a.db.commit(a); }, 1)); } w.clear = function() { this.map.clear(); this.ctx.clear(); this.reg.clear(); this.cache && this.cache.clear(); return this.db ? (this.commit_timer && clearTimeout(this.commit_timer), this.commit_timer = null, this.commit_task = [], this.db.clear()) : this; }; w.append = function(a, c) { return this.add(a, c, !0); }; w.contain = function(a) { return this.db ? this.db.has(a) : this.reg.has(a); }; w.update = function(a, c) { const b = this, e = this.remove(a); return e && e.then ? e.then(() => b.add(a, c)) : this.add(a, c); }; w.cleanup = function() { if (!this.fastupdate) { return console.info('Cleanup the index isn\'t required when not using "fastupdate".'), this; } tb(this.map); this.depth && tb(this.ctx); return this; }; w.searchCache = ma; w.export = function(a, c, b = 0, e = 0) { let d, f; switch(e) { case 0: d = "reg"; f = pb(this.reg); break; case 1: d = "cfg"; f = null; break; case 2: d = "map"; f = lb(this.map, this.reg.size); break; case 3: d = "ctx"; f = nb(this.ctx, this.reg.size); break; default: return; } return rb.call(this, a, c, d, f, b, e); }; w.import = function(a, c) { if (c) { switch(typeof c === "string" && (c = JSON.parse(c)), a = a.split("."), a[a.length - 1] === "json" && a.pop(), a.length === 3 && a.shift(), a = a.length > 1 ? a[1] : a[0], a) { case "reg": this.fastupdate = !1; this.reg = qb(c, this.reg); break; case "map": this.map = mb(c, this.map); break; case "ctx": this.ctx = ob(c, this.ctx); } } }; w.serialize = function(a = !0) { let c = "", b = "", e = ""; if (this.reg.size) { let f; for (var d of this.reg.keys()) { f || (f = typeof d), c += (c ? "," : "") + (f === "string" ? '"' + d + '"' : d); } c = "index.reg=new Set([" + c + "]);"; b = sb(this.map, f); b = "index.map=new Map([" + b + "]);"; for (const g of this.ctx.entries()) { d = g[0]; let k = sb(g[1], f); k = "new Map([" + k + "])"; k = '["' + d + '",' + k + "]"; e += (e ? "," : "") + k; } e = "index.ctx=new Map([" + e + "]);"; } return a ? "function inject(index){" + c + b + e + "}" : c + b + e; }; Fa(T.prototype); const Bb = typeof window !== "undefined" && (window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB), Cb = ["map", "ctx", "tag", "reg", "cfg"], Db = I(); function Eb(a, c = {}) { if (!this || this.constructor !== Eb) { return new Eb(a, c); } typeof a === "object" && (c = a, a = a.name); a || console.info("Default storage space was used, because a name was not passed."); this.id = "flexsearch" + (a ? ":" + a.toLowerCase().replace(/[^a-z0-9_\-]/g, "") : ""); this.field = c.field ? c.field.toLowerCase().replace(/[^a-z0-9_\-]/g, "") : ""; this.type = c.type; this.fastupdate = this.support_tag_search = !1; this.db = null; this.h = {}; } w = Eb.prototype; w.mount = function(a) { if (a.index) { return a.mount(this); } a.db = this; return this.open(); }; w.open = function() { if (this.db) { return this.db; } let a = this; navigator.storage && navigator.storage.persist && navigator.storage.persist(); Db[a.id] || (Db[a.id] = []); Db[a.id].push(a.field); const c = Bb.open(a.id, 1); c.onupgradeneeded = function() { const b = a.db = this.result; for (let e = 0, d; e < Cb.length; e++) { d = Cb[e]; for (let f = 0, g; f < Db[a.id].length; f++) { g = Db[a.id][f], b.objectStoreNames.contains(d + (d !== "reg" ? g ? ":" + g : "" : "")) || b.createObjectStore(d + (d !== "reg" ? g ? ":" + g : "" : "")); } } }; return a.db = Z(c, function(b) { a.db = b; a.db.onversionchange = function() { a.close(); }; }); }; w.close = function() { this.db && this.db.close(); this.db = null; }; w.destroy = function() { const a = Bb.deleteDatabase(this.id); return Z(a); }; w.clear = function() { const a = []; for (let b = 0, e; b < Cb.length; b++) { e = Cb[b]; for (let d = 0, f; d < Db[this.id].length; d++) { f = Db[this.id][d], a.push(e + (e !== "reg" ? f ? ":" + f : "" : "")); } } const c = this.db.transaction(a, "readwrite"); for (let b = 0; b < a.length; b++) { c.objectStore(a[b]).clear(); } return Z(c); }; w.get = function(a, c, b = 0, e = 0, d = !0, f = !1) { a = this.db.transaction((c ? "ctx" : "map") + (this.field ? ":" + this.field : ""), "readonly").objectStore((c ? "ctx" : "map") + (this.field ? ":" + this.field : "")).get(c ? c + ":" + a : a); const g = this; return Z(a).then(function(k) { let h = []; if (!k || !k.length) { return h; } if (d) { if (!b && !e && k.length === 1) { return k[0]; } for (let l = 0, m; l < k.length; l++) { if ((m = k[l]) && m.length) { if (e >= m.length) { e -= m.length; continue; } const p = b ? e + Math.min(m.length - e, b) : m.length; for (let u = e; u < p; u++) { h.push(m[u]); } e = 0; if (h.length === b) { break; } } } return f ? g.enrich(h) : h; } return k; }); }; w.tag = function(a, c = 0, b = 0, e = !1) { a = this.db.transaction("tag" + (this.field ? ":" + this.field : ""), "readonly").objectStore("tag" + (this.field ? ":" + this.field : "")).get(a); const d = this; return Z(a).then(function(f) { if (!f || !f.length || b >= f.length) { return []; } if (!c && !b) { return f; } f = f.slice(b, b + c); return e ? d.enrich(f) : f; }); }; w.enrich = function(a) { typeof a !== "object" && (a = [a]); const c = this.db.transaction("reg", "readonly").objectStore("reg"), b = []; for (let e = 0; e < a.length; e++) { b[e] = Z(c.get(a[e])); } return Promise.all(b).then(function(e) { for (let d = 0; d < e.length; d++) { e[d] = {id:a[d], doc:e[d] ? JSON.parse(e[d]) : null}; } return e; }); }; w.has = function(a) { a = this.db.transaction("reg", "readonly").objectStore("reg").getKey(a); return Z(a).then(function(c) { return !!c; }); }; w.search = null; w.info = function() { }; w.transaction = function(a, c, b) { a += a !== "reg" ? this.field ? ":" + this.field : "" : ""; let e = this.h[a + ":" + c]; if (e) { return b.call(this, e); } let d = this.db.transaction(a, c); this.h[a + ":" + c] = e = d.objectStore(a); const f = b.call(this, e); this.h[a + ":" + c] = null; return Z(d).finally(function() { return f; }); }; w.commit = async function(a) { let c = a.commit_task, b = []; a.commit_task = []; for (let e = 0, d; e < c.length; e++) { d = c[e], d.del && b.push(d.del); } b.length && await this.remove(b); a.reg.size && (await this.transaction("map", "readwrite", function(e) { for (const d of a.map) { const f = d[0], g = d[1]; g.length && (e.get(f).onsuccess = function() { let k = this.result; var h; if (k && k.length) { const l = Math.max(k.length, g.length); for (let m = 0, p, u; m < l; m++) { if ((u = g[m]) && u.length) { if ((p = k[m]) && p.length) { for (h = 0; h < u.length; h++) { p.push(u[h]); } } else { k[m] = u; } h = 1; } } } else { k = g, h = 1; } h && e.put(k, f); }); } }), await this.transaction("ctx", "readwrite", function(e) { for (const d of a.ctx) { const f = d[0], g = d[1]; for (const k of g) { const h = k[0], l = k[1]; l.length && (e.get(f + ":" + h).onsuccess = function() { let m = this.result; var p; if (m && m.length) { const u = Math.max(m.length, l.length); for (let r = 0, t, n; r < u; r++) { if ((n = l[r]) && n.length) { if ((t = m[r]) && t.length) { for (p = 0; p < n.length; p++) { t.push(n[p]); } } else { m[r] = n; } p = 1; } } } else { m = l, p = 1; } p && e.put(m, f + ":" + h); }); } } }), a.store ? await this.transaction("reg", "readwrite", function(e) { for (const d of a.store) { const f = d[0], g = d[1]; e.put(typeof g === "object" ? JSON.stringify(g) : 1, f); } }) : a.bypass || await this.transaction("reg", "readwrite", function(e) { for (const d of a.reg.keys()) { e.put(1, d); } }), a.tag && await this.transaction("tag", "readwrite", function(e) { for (const d of a.tag) { const f = d[0], g = d[1]; g.length && (e.get(f).onsuccess = function() { let k = this.result; k = k && k.length ? k.concat(g) : g; e.put(k, f); }); } }), a.map.clear(), a.ctx.clear(), a.tag && a.tag.clear(), a.store && a.store.clear(), a.document || a.reg.clear()); }; function Fb(a, c, b) { const e = a.value; let d, f = 0; for (let g = 0, k; g < e.length; g++) { if (k = b ? e : e[g]) { for (let h = 0, l, m; h < c.length; h++) { if (m = c[h], l = k.indexOf(m), l >= 0) { if (d = 1, k.length > 1) { k.splice(l, 1); } else { e[g] = []; break; } } } f += k.length; } if (b) { break; } } f ? d && a.update(e) : a.delete(); a.continue(); } w.remove = function(a) { typeof a !== "object" && (a = [a]); return Promise.all([this.transaction("map", "readwrite", function(c) { c.openCursor().onsuccess = function() { const b = this.result; b && Fb(b, a); }; }), this.transaction("ctx", "readwrite", function(c) { c.openCursor().onsuccess = function() { const b = this.result; b && Fb(b, a); }; }), this.transaction("tag", "readwrite", function(c) { c.openCursor().onsuccess = function() { const b = this.result; b && Fb(b, a, !0); }; }), this.transaction("reg", "readwrite", function(c) { for (let b = 0; b < a.length; b++) { c.delete(a[b]); } })]); }; function Z(a, c) { return new Promise((b, e) => { a.onsuccess = a.oncomplete = function() { c && c(this.result); c = null; b(this.result); }; a.onerror = a.onblocked = e; a = null; }); } ;export default {Index:T, Charset:wa, Encoder:ka, Document:Na, Worker:La, Resolver:X, IndexedDB:Eb, Language:{}}; export const Index=T;export const Charset=wa;export const Encoder=ka;export const Document=Na;export const Worker=La;export const Resolver=X;export const IndexedDB=Eb;export const Language={}; ================================================ FILE: dist/flexsearch.bundle.module.debug.mjs ================================================ /**! * FlexSearch.js v0.8.214 (Bundle/Module/Debug) * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ var w; function H(a, c, b) { const e = typeof b, d = typeof a; if (e !== "undefined") { if (d !== "undefined") { if (b) { if (d === "function" && e === d) { return function(k) { return a(b(k)); }; } c = a.constructor; if (c === b.constructor) { if (c === Array) { return b.concat(a); } if (c === Map) { var f = new Map(b); for (var g of a) { f.set(g[0], g[1]); } return f; } if (c === Set) { g = new Set(b); for (f of a.values()) { g.add(f); } return g; } } } return a; } return b; } return d === "undefined" ? c : a; } function aa(a, c) { return typeof a === "undefined" ? c : a; } function I() { return Object.create(null); } function N(a) { return typeof a === "string"; } function ba(a) { return typeof a === "object"; } function ca(a, c) { if (N(c)) { a = a[c]; } else { for (let b = 0; a && b < c.length; b++) { a = a[c[b]]; } } return a; } ;const da = /[^\p{L}\p{N}]+/u, fa = /(\d{3})/g, ha = /(\D)(\d{3})/g, ia = /(\d{3})(\D)/g, ja = /[\u0300-\u036f]/g; function ka(a = {}) { if (!this || this.constructor !== ka) { return new ka(...arguments); } if (arguments.length) { for (a = 0; a < arguments.length; a++) { this.assign(arguments[a]); } } else { this.assign(a); } } w = ka.prototype; w.assign = function(a) { this.normalize = H(a.normalize, !0, this.normalize); let c = a.include, b = c || a.exclude || a.split, e; if (b || b === "") { if (typeof b === "object" && b.constructor !== RegExp) { let d = ""; e = !c; c || (d += "\\p{Z}"); b.letter && (d += "\\p{L}"); b.number && (d += "\\p{N}", e = !!c); b.symbol && (d += "\\p{S}"); b.punctuation && (d += "\\p{P}"); b.control && (d += "\\p{C}"); if (b = b.char) { d += typeof b === "object" ? b.join("") : b; } try { this.split = new RegExp("[" + (c ? "^" : "") + d + "]+", "u"); } catch (f) { console.error("Your split configuration:", b, "is not supported on this platform. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } } else { this.split = b, e = b === !1 || "a1a".split(b).length < 2; } this.numeric = H(a.numeric, e); } else { try { this.split = H(this.split, da); } catch (d) { console.warn("This platform does not support unicode regex. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } this.numeric = H(a.numeric, H(this.numeric, !0)); } this.prepare = H(a.prepare, null, this.prepare); this.finalize = H(a.finalize, null, this.finalize); b = a.filter; this.filter = typeof b === "function" ? b : H(b && new Set(b), null, this.filter); this.dedupe = H(a.dedupe, !0, this.dedupe); this.matcher = H((b = a.matcher) && new Map(b), null, this.matcher); this.mapper = H((b = a.mapper) && new Map(b), null, this.mapper); this.stemmer = H((b = a.stemmer) && new Map(b), null, this.stemmer); this.replacer = H(a.replacer, null, this.replacer); this.minlength = H(a.minlength, 1, this.minlength); this.maxlength = H(a.maxlength, 1024, this.maxlength); this.rtl = H(a.rtl, !1, this.rtl); if (this.cache = b = H(a.cache, !0, this.cache)) { this.F = null, this.L = typeof b === "number" ? b : 2e5, this.B = new Map(), this.D = new Map(), this.I = this.H = 128; } this.h = ""; this.J = null; this.A = ""; this.K = null; if (this.matcher) { for (const d of this.matcher.keys()) { this.h += (this.h ? "|" : "") + d; } } if (this.stemmer) { for (const d of this.stemmer.keys()) { this.A += (this.A ? "|" : "") + d; } } return this; }; w.addStemmer = function(a, c) { this.stemmer || (this.stemmer = new Map()); this.stemmer.set(a, c); this.A += (this.A ? "|" : "") + a; this.K = null; this.cache && la(this); return this; }; w.addFilter = function(a) { typeof a === "function" ? this.filter = a : (this.filter || (this.filter = new Set()), this.filter.add(a)); this.cache && la(this); return this; }; w.addMapper = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length > 1) { return this.addMatcher(a, c); } this.mapper || (this.mapper = new Map()); this.mapper.set(a, c); this.cache && la(this); return this; }; w.addMatcher = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length < 2 && (this.dedupe || this.mapper)) { return this.addMapper(a, c); } this.matcher || (this.matcher = new Map()); this.matcher.set(a, c); this.h += (this.h ? "|" : "") + a; this.J = null; this.cache && la(this); return this; }; w.addReplacer = function(a, c) { if (typeof a === "string") { return this.addMatcher(a, c); } this.replacer || (this.replacer = []); this.replacer.push(a, c); this.cache && la(this); return this; }; w.encode = function(a, c) { if (this.cache && a.length <= this.H) { if (this.F) { if (this.B.has(a)) { return this.B.get(a); } } else { this.F = setTimeout(la, 50, this); } } this.normalize && (typeof this.normalize === "function" ? a = this.normalize(a) : a = ja ? a.normalize("NFKD").replace(ja, "").toLowerCase() : a.toLowerCase()); this.prepare && (a = this.prepare(a)); this.numeric && a.length > 3 && (a = a.replace(ha, "$1 $2").replace(ia, "$1 $2").replace(fa, "$1 ")); const b = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer); let e = [], d = I(), f, g, k = this.split || this.split === "" ? a.split(this.split) : [a]; for (let l = 0, m, p; l < k.length; l++) { if ((m = p = k[l]) && !(m.length < this.minlength || m.length > this.maxlength)) { if (c) { if (d[m]) { continue; } d[m] = 1; } else { if (f === m) { continue; } f = m; } if (b) { e.push(m); } else { if (!this.filter || (typeof this.filter === "function" ? this.filter(m) : !this.filter.has(m))) { if (this.cache && m.length <= this.I) { if (this.F) { var h = this.D.get(m); if (h || h === "") { h && e.push(h); continue; } } else { this.F = setTimeout(la, 50, this); } } if (this.stemmer) { this.K || (this.K = new RegExp("(?!^)(" + this.A + ")$")); let u; for (; u !== m && m.length > 2;) { u = m, m = m.replace(this.K, r => this.stemmer.get(r)); } } if (m && (this.mapper || this.dedupe && m.length > 1)) { h = ""; for (let u = 0, r = "", t, n; u < m.length; u++) { t = m.charAt(u), t === r && this.dedupe || ((n = this.mapper && this.mapper.get(t)) || n === "" ? n === r && this.dedupe || !(r = n) || (h += n) : h += r = t); } m = h; } this.matcher && m.length > 1 && (this.J || (this.J = new RegExp("(" + this.h + ")", "g")), m = m.replace(this.J, u => this.matcher.get(u))); if (m && this.replacer) { for (h = 0; m && h < this.replacer.length; h += 2) { m = m.replace(this.replacer[h], this.replacer[h + 1]); } } this.cache && p.length <= this.I && (this.D.set(p, m), this.D.size > this.L && (this.D.clear(), this.I = this.I / 1.1 | 0)); if (m) { if (m !== p) { if (c) { if (d[m]) { continue; } d[m] = 1; } else { if (g === m) { continue; } g = m; } } e.push(m); } } } } } this.finalize && (e = this.finalize(e) || e); this.cache && a.length <= this.H && (this.B.set(a, e), this.B.size > this.L && (this.B.clear(), this.H = this.H / 1.1 | 0)); return e; }; function la(a) { a.F = null; a.B.clear(); a.D.clear(); } ;function ma(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : b = a); b && (a = b.query || a, c = b.limit || c); let e = "" + (c || 0); b && (e += (b.offset || 0) + !!b.context + !!b.suggest + (b.resolve !== !1) + (b.resolution || this.resolution) + (b.boost || 0)); a = ("" + a).toLowerCase(); this.cache || (this.cache = new na()); let d = this.cache.get(a + e); if (!d) { const f = b && b.cache; f && (b.cache = !1); d = this.search(a, c, b); f && (b.cache = f); this.cache.set(a + e, d); } return d; } function na(a) { this.limit = a && a !== !0 ? a : 1000; this.cache = new Map(); this.h = ""; } na.prototype.set = function(a, c) { this.cache.set(this.h = a, c); this.cache.size > this.limit && this.cache.delete(this.cache.keys().next().value); }; na.prototype.get = function(a) { const c = this.cache.get(a); c && this.h !== a && (this.cache.delete(a), this.cache.set(this.h = a, c)); return c; }; na.prototype.remove = function(a) { for (const c of this.cache) { const b = c[0]; c[1].includes(a) && this.cache.delete(b); } }; na.prototype.clear = function() { this.cache.clear(); this.h = ""; }; const oa = {normalize:!1, numeric:!1, dedupe:!1}; const ra = {}; const sa = new Map([["b", "p"], ["v", "f"], ["w", "f"], ["z", "s"], ["x", "s"], ["d", "t"], ["n", "m"], ["c", "k"], ["g", "k"], ["j", "k"], ["q", "k"], ["i", "e"], ["y", "e"], ["u", "o"]]); const ta = new Map([["ae", "a"], ["oe", "o"], ["sh", "s"], ["kh", "k"], ["th", "t"], ["ph", "f"], ["pf", "f"]]), ua = [/([^aeo])h(.)/g, "$1$2", /([aeo])h([^aeo]|$)/g, "$1$2", /(.)\1+/g, "$1"]; const va = {a:"", e:"", i:"", o:"", u:"", y:"", b:1, f:1, p:1, v:1, c:2, g:2, j:2, k:2, q:2, s:2, x:2, z:2, "\u00df":2, d:3, t:3, l:4, m:5, n:5, r:6}; var wa = {Exact:oa, Default:ra, Normalize:ra, LatinBalance:{mapper:sa}, LatinAdvanced:{mapper:sa, matcher:ta, replacer:ua}, LatinExtra:{mapper:sa, replacer:ua.concat([/(?!^)[aeo]/g, ""]), matcher:ta}, LatinSoundex:{dedupe:!1, include:{letter:!0}, finalize:function(a) { for (let b = 0; b < a.length; b++) { var c = a[b]; let e = c.charAt(0), d = va[e]; for (let f = 1, g; f < c.length && (g = c.charAt(f), g === "h" || g === "w" || !(g = va[g]) || g === d || (e += g, d = g, e.length !== 4)); f++) { } a[b] = e; } }}, CJK:{split:""}, LatinExact:oa, LatinDefault:ra, LatinSimple:ra}; function xa(a, c, b, e) { let d = []; for (let f = 0, g; f < a.index.length; f++) { if (g = a.index[f], c >= g.length) { c -= g.length; } else { c = g[e ? "splice" : "slice"](c, b); const k = c.length; if (k && (d = d.length ? d.concat(c) : c, b -= k, e && (a.length -= k), !b)) { break; } c = 0; } } return d; } function Aa(a) { if (!this || this.constructor !== Aa) { return new Aa(a); } this.index = a ? [a] : []; this.length = a ? a.length : 0; const c = this; return new Proxy([], {get(b, e) { if (e === "length") { return c.length; } if (e === "push") { return function(d) { c.index[c.index.length - 1].push(d); c.length++; }; } if (e === "pop") { return function() { if (c.length) { return c.length--, c.index[c.index.length - 1].pop(); } }; } if (e === "indexOf") { return function(d) { let f = 0; for (let g = 0, k, h; g < c.index.length; g++) { k = c.index[g]; h = k.indexOf(d); if (h >= 0) { return f + h; } f += k.length; } return -1; }; } if (e === "includes") { return function(d) { for (let f = 0; f < c.index.length; f++) { if (c.index[f].includes(d)) { return !0; } } return !1; }; } if (e === "slice") { return function(d, f) { return xa(c, d || 0, f || c.length, !1); }; } if (e === "splice") { return function(d, f) { return xa(c, d || 0, f || c.length, !0); }; } if (e === "constructor") { return Array; } if (typeof e !== "symbol") { return (b = c.index[e / 2 ** 31 | 0]) && b[e]; } }, set(b, e, d) { b = e / 2 ** 31 | 0; (c.index[b] || (c.index[b] = []))[e] = d; c.length++; return !0; }}); } Aa.prototype.clear = function() { this.index.length = 0; }; Aa.prototype.push = function() { }; function Q(a = 8) { if (!this || this.constructor !== Q) { return new Q(a); } this.index = I(); this.h = []; this.size = 0; a > 32 ? (this.B = Ba, this.A = BigInt(a)) : (this.B = Ca, this.A = a); } Q.prototype.get = function(a) { const c = this.index[this.B(a)]; return c && c.get(a); }; Q.prototype.set = function(a, c) { var b = this.B(a); let e = this.index[b]; e ? (b = e.size, e.set(a, c), (b -= e.size) && this.size++) : (this.index[b] = e = new Map([[a, c]]), this.h.push(e), this.size++); }; function R(a = 8) { if (!this || this.constructor !== R) { return new R(a); } this.index = I(); this.h = []; this.size = 0; a > 32 ? (this.B = Ba, this.A = BigInt(a)) : (this.B = Ca, this.A = a); } R.prototype.add = function(a) { var c = this.B(a); let b = this.index[c]; b ? (c = b.size, b.add(a), (c -= b.size) && this.size++) : (this.index[c] = b = new Set([a]), this.h.push(b), this.size++); }; w = Q.prototype; w.has = R.prototype.has = function(a) { const c = this.index[this.B(a)]; return c && c.has(a); }; w.delete = R.prototype.delete = function(a) { const c = this.index[this.B(a)]; c && c.delete(a) && this.size--; }; w.clear = R.prototype.clear = function() { this.index = I(); this.h = []; this.size = 0; }; w.values = R.prototype.values = function*() { for (let a = 0; a < this.h.length; a++) { for (let c of this.h[a].values()) { yield c; } } }; w.keys = R.prototype.keys = function*() { for (let a = 0; a < this.h.length; a++) { for (let c of this.h[a].keys()) { yield c; } } }; w.entries = R.prototype.entries = function*() { for (let a = 0; a < this.h.length; a++) { for (let c of this.h[a].entries()) { yield c; } } }; function Ca(a) { let c = 2 ** this.A - 1; if (typeof a == "number") { return a & c; } let b = 0, e = this.A + 1; for (let d = 0; d < a.length; d++) { b = (b * e ^ a.charCodeAt(d)) & c; } return this.A === 32 ? b + 2 ** 31 : b; } function Ba(a) { let c = BigInt(2) ** this.A - BigInt(1); var b = typeof a; if (b === "bigint") { return a & c; } if (b === "number") { return BigInt(a) & c; } b = BigInt(0); let e = this.A + BigInt(1); for (let d = 0; d < a.length; d++) { b = (b * e ^ BigInt(a.charCodeAt(d))) & c; } return b; } ;let Da, S; async function Ea(a) { a = a.data; var c = a.task; const b = a.id; let e = a.args; switch(c) { case "init": S = a.options || {}; (c = a.factory) ? (Function("return " + c)()(self), Da = new self.FlexSearch.Index(S), delete self.FlexSearch) : Da = new T(S); postMessage({id:b}); break; default: let d; if (c === "export") { if (!S.export || typeof S.export !== "function") { throw Error('Either no extern configuration provided for the Worker-Index or no method was defined on the config property "export".'); } e[1] ? (e[0] = S.export, e[2] = 0, e[3] = 1) : e = null; } if (c === "import") { if (!S.import || typeof S.import !== "function") { throw Error('Either no extern configuration provided for the Worker-Index or no method was defined on the config property "import".'); } e[0] && (a = await S.import.call(Da, e[0]), Da.import(e[0], a)); } else { (d = e && Da[c].apply(Da, e)) && d.then && (d = await d), d && d.await && (d = await d.await), c === "search" && d.result && (d = d.result); } postMessage(c === "search" ? {id:b, msg:d} : {id:b}); } } ;function Fa(a) { Ga.call(a, "add"); Ga.call(a, "append"); Ga.call(a, "search"); Ga.call(a, "update"); Ga.call(a, "remove"); Ga.call(a, "searchCache"); } let Ha, Ia, Ja; function Ka() { Ha = Ja = 0; } function Ga(a) { this[a + "Async"] = function() { const c = arguments; var b = c[c.length - 1]; let e; typeof b === "function" && (e = b, delete c[c.length - 1]); Ha ? Ja || (Ja = Date.now() - Ia >= this.priority * this.priority * 3) : (Ha = setTimeout(Ka, 0), Ia = Date.now()); if (Ja) { const f = this; return new Promise(g => { setTimeout(function() { g(f[a + "Async"].apply(f, c)); }, 0); }); } const d = this[a].apply(this, c); b = d.then ? d : new Promise(f => f(d)); e && b.then(e); return b; }; } ;let V = 0; function La(a = {}, c) { function b(k) { function h(l) { l = l.data || l; const m = l.id, p = m && f.h[m]; p && (p(l.msg), delete f.h[m]); } this.worker = k; this.h = I(); if (this.worker) { d ? this.worker.on("message", h) : this.worker.onmessage = h; if (a.config) { return new Promise(function(l) { V > 1e9 && (V = 0); f.h[++V] = function() { l(f); }; f.worker.postMessage({id:V, task:"init", factory:e, options:a}); }); } this.priority = a.priority || 4; this.encoder = c || null; this.worker.postMessage({task:"init", factory:e, options:a}); return this; } console.warn("Worker is not available on this platform. Please report on Github: https://github.com/nextapps-de/flexsearch/issues"); } if (!this || this.constructor !== La) { return new La(a); } let e = typeof self !== "undefined" ? self._factory : typeof window !== "undefined" ? window._factory : null; e && (e = e.toString()); const d = typeof window === "undefined", f = this, g = Ma(e, d, a.worker); return g.then ? g.then(function(k) { return b.call(f, k); }) : b.call(this, g); } W("add"); W("append"); W("search"); W("update"); W("remove"); W("clear"); W("export"); W("import"); La.prototype.searchCache = ma; Fa(La.prototype); function W(a) { La.prototype[a] = function() { const c = this, b = [].slice.call(arguments); var e = b[b.length - 1]; let d; typeof e === "function" && (d = e, b.pop()); e = new Promise(function(f) { a === "export" && typeof b[0] === "function" && (b[0] = null); V > 1e9 && (V = 0); c.h[++V] = f; c.worker.postMessage({task:a, id:V, args:b}); }); return d ? (e.then(d), this) : e; }; } function Ma(a, c, b) { return c ? typeof module !== "undefined" ? new(require("worker_threads")["Worker"])(__dirname+"/worker/node.js") : import("worker_threads").then(function(worker){return new worker["Worker"](import.meta.dirname+"/node/node.mjs")}) : a ? new window.Worker(URL.createObjectURL(new Blob(["onmessage=" + Ea.toString()], {type:"text/javascript"}))) : new window.Worker(typeof b === "string" ? b : import.meta.url.replace("/worker.js", "/worker/worker.js").replace("flexsearch.bundle.module.min.js", "module/worker/worker.js").replace("flexsearch.bundle.module.min.mjs", "module/worker/worker.js"), {type:"module"}); } ;Na.prototype.add = function(a, c, b) { ba(a) && (c = a, a = ca(c, this.key)); if (c && (a || a === 0)) { if (!b && this.reg.has(a)) { return this.update(a, c); } for (let k = 0, h; k < this.field.length; k++) { h = this.B[k]; var e = this.index.get(this.field[k]); if (typeof h === "function") { var d = h(c); d && e.add(a, d, b, !0); } else { if (d = h.G, !d || d(c)) { h.constructor === String ? h = ["" + h] : N(h) && (h = [h]), Qa(c, h, this.D, 0, e, a, h[0], b); } } } if (this.tag) { for (e = 0; e < this.A.length; e++) { var f = this.A[e], g = this.F[e]; d = this.tag.get(g); let k = I(); if (typeof f === "function") { if (f = f(c), !f) { continue; } } else { const h = f.G; if (h && !h(c)) { continue; } f.constructor === String && (f = "" + f); f = ca(c, f); } if (d && f) { N(f) && (f = [f]); for (let h = 0, l, m; h < f.length; h++) { if (l = f[h], !k[l] && (k[l] = 1, (g = d.get(l)) ? m = g : d.set(l, m = []), !b || !m.includes(a))) { if (m.length === 2 ** 31 - 1) { g = new Aa(m); if (this.fastupdate) { for (let p of this.reg.values()) { p.includes(m) && (p[p.indexOf(m)] = g); } } d.set(l, m = g); } m.push(a); this.fastupdate && ((g = this.reg.get(a)) ? g.push(m) : this.reg.set(a, [m])); } } } else { d || console.warn("Tag '" + g + "' was not found"); } } } if (this.store && (!b || !this.store.has(a))) { let k; if (this.h) { k = I(); for (let h = 0, l; h < this.h.length; h++) { l = this.h[h]; if ((b = l.G) && !b(c)) { continue; } let m; if (typeof l === "function") { m = l(c); if (!m) { continue; } l = [l.O]; } else if (N(l) || l.constructor === String) { k[l] = c[l]; continue; } Ra(c, k, l, 0, l[0], m); } } this.store.set(a, k || c); } this.worker && (this.fastupdate || this.reg.add(a)); } return this; }; function Ra(a, c, b, e, d, f) { a = a[d]; if (e === b.length - 1) { c[d] = f || a; } else if (a) { if (a.constructor === Array) { for (c = c[d] = Array(a.length), d = 0; d < a.length; d++) { Ra(a, c, b, e, d); } } else { c = c[d] || (c[d] = I()), d = b[++e], Ra(a, c, b, e, d); } } } function Qa(a, c, b, e, d, f, g, k) { if (a = a[g]) { if (e === c.length - 1) { if (a.constructor === Array) { if (b[e]) { for (c = 0; c < a.length; c++) { d.add(f, a[c], !0, !0); } return; } a = a.join(" "); } d.add(f, a, k, !0); } else { if (a.constructor === Array) { for (g = 0; g < a.length; g++) { Qa(a, c, b, e, d, f, g, k); } } else { g = c[++e], Qa(a, c, b, e, d, f, g, k); } } } } ;function Sa(a, c, b, e) { if (!a.length) { return a; } if (a.length === 1) { return a = a[0], a = b || a.length > c ? a.slice(b, b + c) : a, e ? Ta.call(this, a) : a; } let d = []; for (let f = 0, g, k; f < a.length; f++) { if ((g = a[f]) && (k = g.length)) { if (b) { if (b >= k) { b -= k; continue; } g = g.slice(b, b + c); k = g.length; b = 0; } k > c && (g = g.slice(0, c), k = c); if (!d.length && k >= c) { return e ? Ta.call(this, g) : g; } d.push(g); c -= k; if (!c) { break; } } } d = d.length > 1 ? [].concat.apply([], d) : d[0]; return e ? Ta.call(this, d) : d; } ;function Ua(a, c, b, e) { var d = e[0]; if (d[0] && d[0].query) { return a[c].apply(a, d); } if (!(c !== "and" && c !== "not" || a.result.length || a.await || d.suggest)) { return e.length > 1 && (d = e[e.length - 1]), (e = d.resolve) ? a.await || a.result : a; } let f = [], g = 0, k = 0, h, l, m, p, u; for (c = 0; c < e.length; c++) { if (d = e[c]) { var r = void 0; if (d.constructor === X) { r = d.await || d.result; } else if (d.then || d.constructor === Array) { r = d; } else { g = d.limit || 0; k = d.offset || 0; m = d.suggest; l = d.resolve; h = ((p = d.highlight || a.highlight) || d.enrich) && l; r = d.queue; let t = d.async || r, n = d.index, q = d.query; n ? a.index || (a.index = n) : n = a.index; if (q || d.tag) { if (!n) { throw Error("Resolver can't apply because the corresponding Index was never specified"); } const x = d.field || d.pluck; if (x) { !q || a.query && !p || (a.query = q, a.field = x, a.highlight = p); if (!n.index) { throw Error("Resolver can't apply because the corresponding Document Index was not specified"); } n = n.index.get(x); if (!n) { throw Error("Resolver can't apply because the specified Document Field '" + x + "' was not found"); } } if (r && (u || a.await)) { u = 1; let v; const A = a.C.length, E = new Promise(function(F) { v = F; }); (function(F, B) { E.h = function() { B.index = null; B.resolve = !1; B.enrich = !1; let C = t ? F.searchAsync(B) : F.search(B); if (C.then) { return C.then(function(z) { a.C[A] = z = z.result || z; v(z); return z; }); } C = C.result || C; v(C); return C; }; })(n, Object.assign({}, d)); a.C.push(E); f[c] = E; continue; } else { d.resolve = !1, d.enrich = !1, d.index = null, r = t ? n.searchAsync(d) : n.search(d), d.resolve = l, d.enrich = h, d.index = n; } } else if (d.and) { r = Va(d, "and", n); } else if (d.or) { r = Va(d, "or", n); } else if (d.not) { r = Va(d, "not", n); } else if (d.xor) { r = Va(d, "xor", n); } else { continue; } } r.await ? (u = 1, r = r.await) : r.then ? (u = 1, r = r.then(function(t) { return t.result || t; })) : r = r.result || r; f[c] = r; } } u && !a.await && (a.await = new Promise(function(t) { a.return = t; })); if (u) { const t = Promise.all(f).then(function(n) { for (let q = 0; q < a.C.length; q++) { if (a.C[q] === t) { a.C[q] = function() { return b.call(a, n, g, k, h, l, m, p); }; break; } } Wa(a); }); a.C.push(t); } else if (a.await) { a.C.push(function() { return b.call(a, f, g, k, h, l, m, p); }); } else { return b.call(a, f, g, k, h, l, m, p); } return l ? a.await || a.result : a; } function Va(a, c, b) { a = a[c]; const e = a[0] || a; e.index || (e.index = b); b = new X(e); a.length > 1 && (b = b[c].apply(b, a.slice(1))); return b; } ;X.prototype.or = function() { return Ua(this, "or", Xa, arguments); }; function Xa(a, c, b, e, d, f, g) { a.length && (this.result.length && a.push(this.result), a.length < 2 ? this.result = a[0] : (this.result = Ya(a, c, b, !1, this.h), b = 0)); d && (this.await = null); return d ? this.resolve(c, b, e, g) : this; } ;X.prototype.and = function() { return Ua(this, "and", Za, arguments); }; function Za(a, c, b, e, d, f, g) { if (!f && !this.result.length) { return d ? this.result : this; } let k; if (a.length) { if (this.result.length && a.unshift(this.result), a.length < 2) { this.result = a[0]; } else { let h = 0; for (let l = 0, m, p; l < a.length; l++) { if ((m = a[l]) && (p = m.length)) { h < p && (h = p); } else if (!f) { h = 0; break; } } h ? (this.result = $a(a, h, c, b, f, this.h, d), k = !0) : this.result = []; } } else { f || (this.result = a); } d && (this.await = null); return d ? this.resolve(c, b, e, g, k) : this; } ;X.prototype.xor = function() { return Ua(this, "xor", ab, arguments); }; function ab(a, c, b, e, d, f, g) { if (a.length) { if (this.result.length && a.unshift(this.result), a.length < 2) { this.result = a[0]; } else { a: { f = b; var k = this.h; const h = [], l = I(); let m = 0; for (let p = 0, u; p < a.length; p++) { if (u = a[p]) { m < u.length && (m = u.length); for (let r = 0, t; r < u.length; r++) { if (t = u[r]) { for (let n = 0, q; n < t.length; n++) { q = t[n], l[q] = l[q] ? 2 : 1; } } } } } for (let p = 0, u, r = 0; p < m; p++) { for (let t = 0, n; t < a.length; t++) { if (n = a[t]) { if (u = n[p]) { for (let q = 0, x; q < u.length; q++) { if (x = u[q], l[x] === 1) { if (f) { f--; } else { if (d) { if (h.push(x), h.length === c) { a = h; break a; } } else { const v = p + (t ? k : 0); h[v] || (h[v] = []); h[v].push(x); if (++r === c) { a = h; break a; } } } } } } } } } a = h; } this.result = a; k = !0; } } else { f || (this.result = a); } d && (this.await = null); return d ? this.resolve(c, b, e, g, k) : this; } ;X.prototype.not = function() { return Ua(this, "not", bb, arguments); }; function bb(a, c, b, e, d, f, g) { if (!f && !this.result.length) { return d ? this.result : this; } if (a.length && this.result.length) { a: { f = b; var k = []; a = new Set(a.flat().flat()); for (let h = 0, l, m = 0; h < this.result.length; h++) { if (l = this.result[h]) { for (let p = 0, u; p < l.length; p++) { if (u = l[p], !a.has(u)) { if (f) { f--; } else { if (d) { if (k.push(u), k.length === c) { a = k; break a; } } else { if (k[h] || (k[h] = []), k[h].push(u), ++m === c) { a = k; break a; } } } } } } } a = k; } this.result = a; k = !0; } d && (this.await = null); return d ? this.resolve(c, b, e, g, k) : this; } ;function cb(a, c, b, e, d) { let f, g, k; typeof d === "string" ? (f = d, d = "") : f = d.template; if (!f) { throw Error('No template pattern was specified by the search option "highlight"'); } g = f.indexOf("$1"); if (g === -1) { throw Error('Invalid highlight template. The replacement pattern "$1" was not found in template: ' + f); } k = f.substring(g + 2); g = f.substring(0, g); let h = d && d.boundary, l = !d || d.clip !== !1, m = d && d.merge && k && g && new RegExp(k + " " + g, "g"); d = d && d.ellipsis; var p = 0; if (typeof d === "object") { var u = d.template; p = u.length - 2; d = d.pattern; } typeof d !== "string" && (d = d === !1 ? "" : "..."); p && (d = u.replace("$1", d)); u = d.length - p; let r, t; typeof h === "object" && (r = h.before, r === 0 && (r = -1), t = h.after, t === 0 && (t = -1), h = h.total || 9e5); p = new Map(); for (let Oa = 0, ea, db, pa; Oa < c.length; Oa++) { let qa; if (e) { qa = c, pa = e; } else { var n = c[Oa]; pa = n.field; if (!pa) { continue; } qa = n.result; } db = b.get(pa); ea = db.encoder; n = p.get(ea); typeof n !== "string" && (n = ea.encode(a), p.set(ea, n)); for (let ya = 0; ya < qa.length; ya++) { var q = qa[ya].doc; if (!q) { continue; } q = ca(q, pa); if (!q) { continue; } var x = q.trim().split(/\s+/); if (!x.length) { continue; } q = ""; var v = []; let za = []; var A = -1, E = -1, F = 0; for (var B = 0; B < x.length; B++) { var C = x[B], z = ea.encode(C); z = z.length > 1 ? z.join(" ") : z[0]; let y; if (z && C) { var D = C.length, J = (ea.split ? C.replace(ea.split, "") : C).length - z.length, G = "", L = 0; for (var O = 0; O < n.length; O++) { var P = n[O]; if (P) { var M = P.length; M += J < 0 ? 0 : J; L && M <= L || (P = z.indexOf(P), P > -1 && (G = (P ? C.substring(0, P) : "") + g + C.substring(P, P + M) + k + (P + M < D ? C.substring(P + M) : ""), L = M, y = !0)); } } G && (h && (A < 0 && (A = q.length + (q ? 1 : 0)), E = q.length + (q ? 1 : 0) + G.length, F += D, za.push(v.length), v.push({match:G})), q += (q ? " " : "") + G); } if (!y) { C = x[B], q += (q ? " " : "") + C, h && v.push({text:C}); } else if (h && F >= h) { break; } } F = za.length * (f.length - 2); if (r || t || h && q.length - F > h) { if (F = h + F - u * 2, B = E - A, r > 0 && (B += r), t > 0 && (B += t), B <= F) { x = r ? A - (r > 0 ? r : 0) : A - ((F - B) / 2 | 0), v = t ? E + (t > 0 ? t : 0) : x + F, l || (x > 0 && q.charAt(x) !== " " && q.charAt(x - 1) !== " " && (x = q.indexOf(" ", x), x < 0 && (x = 0)), v < q.length && q.charAt(v - 1) !== " " && q.charAt(v) !== " " && (v = q.lastIndexOf(" ", v), v < E ? v = E : ++v)), q = (x ? d : "") + q.substring(x, v) + (v < q.length ? d : ""); } else { E = []; A = {}; F = {}; B = {}; C = {}; z = {}; G = J = D = 0; for (O = L = 1;;) { var U = void 0; for (let y = 0, K; y < za.length; y++) { K = za[y]; if (G) { if (J !== G) { if (B[y + 1]) { continue; } K += G; if (A[K]) { D -= u; F[y + 1] = 1; B[y + 1] = 1; continue; } if (K >= v.length - 1) { if (K >= v.length) { B[y + 1] = 1; K >= x.length && (F[y + 1] = 1); continue; } D -= u; } q = v[K].text; if (M = t && z[y]) { if (M > 0) { if (q.length > M) { if (B[y + 1] = 1, l) { q = q.substring(0, M); } else { continue; } } (M -= q.length) || (M = -1); z[y] = M; } else { B[y + 1] = 1; continue; } } if (D + q.length + 1 <= h) { q = " " + q, E[y] += q; } else if (l) { U = h - D - 1, U > 0 && (q = " " + q.substring(0, U), E[y] += q), B[y + 1] = 1; } else { B[y + 1] = 1; continue; } } else { if (B[y]) { continue; } K -= J; if (A[K]) { D -= u; B[y] = 1; F[y] = 1; continue; } if (K <= 0) { if (K < 0) { B[y] = 1; F[y] = 1; continue; } D -= u; } q = v[K].text; if (M = r && C[y]) { if (M > 0) { if (q.length > M) { if (B[y] = 1, l) { q = q.substring(q.length - M); } else { continue; } } (M -= q.length) || (M = -1); C[y] = M; } else { B[y] = 1; continue; } } if (D + q.length + 1 <= h) { q += " ", E[y] = q + E[y]; } else if (l) { U = q.length + 1 - (h - D), U >= 0 && U < q.length && (q = q.substring(U) + " ", E[y] = q + E[y]), B[y] = 1; } else { B[y] = 1; continue; } } } else { q = v[K].match; r && (C[y] = r); t && (z[y] = t); y && D++; let Pa; K ? !y && u && (D += u) : (F[y] = 1, B[y] = 1); K >= x.length - 1 ? Pa = 1 : K < v.length - 1 && v[K + 1].match ? Pa = 1 : u && (D += u); D -= f.length - 2; if (!y || D + q.length <= h) { E[y] = q; } else { U = L = O = F[y] = 0; break; } Pa && (F[y + 1] = 1, B[y + 1] = 1); } D += q.length; U = A[K] = 1; } if (U) { J === G ? G++ : J++; } else { J === G ? L = 0 : O = 0; if (!L && !O) { break; } L ? (J++, G = J) : G++; } } q = ""; for (let y = 0, K; y < E.length; y++) { K = (F[y] ? y ? " " : "" : (y && !d ? " " : "") + d) + E[y], q += K; } d && !F[E.length] && (q += d); } } m && (q = q.replace(m, " ")); qa[ya].highlight = q; } if (e) { break; } } return c; } ;function X(a, c) { if (!this || this.constructor !== X) { return new X(a, c); } let b = 0, e, d, f, g, k, h; if (a && a.index) { const l = a; c = l.index; b = l.boost || 0; if (d = l.query) { f = l.field || l.pluck; g = l.highlight; const m = l.resolve; a = l.async || l.queue; l.resolve = !1; l.highlight = ""; l.index = null; a = a ? c.searchAsync(l) : c.search(l); l.resolve = m; l.highlight = g; l.index = c; a = a.result || a; } else { a = []; } } if (a && a.then) { const l = this; a = a.then(function(m) { l.C[0] = l.result = m.result || m; Wa(l); }); e = [a]; a = []; k = new Promise(function(m) { h = m; }); } this.index = c || null; this.result = a || []; this.h = b; this.C = e || []; this.await = k || null; this.return = h || null; this.highlight = g || null; this.query = d || ""; this.field = f || ""; } w = X.prototype; w.limit = function(a) { if (this.await) { const c = this; this.C.push(function() { return c.limit(a).result; }); } else { if (this.result.length) { const c = []; for (let b = 0, e; b < this.result.length; b++) { if (e = this.result[b]) { if (e.length <= a) { if (c[b] = e, a -= e.length, !a) { break; } } else { c[b] = e.slice(0, a); break; } } } this.result = c; } } return this; }; w.offset = function(a) { if (this.await) { const c = this; this.C.push(function() { return c.offset(a).result; }); } else { if (this.result.length) { const c = []; for (let b = 0, e; b < this.result.length; b++) { if (e = this.result[b]) { e.length <= a ? a -= e.length : (c[b] = e.slice(a), a = 0); } } this.result = c; } } return this; }; w.boost = function(a) { if (this.await) { const c = this; this.C.push(function() { return c.boost(a).result; }); } else { this.h += a; } return this; }; function Wa(a, c) { let b = a.result; var e = a.await; a.await = null; for (let d = 0, f; d < a.C.length; d++) { if (f = a.C[d]) { if (typeof f === "function") { b = f(), a.C[d] = b = b.result || b, d--; } else if (f.h) { b = f.h(), a.C[d] = b = b.result || b, d--; } else if (f.then) { return a.await = e; } } } e = a.return; a.C = []; a.return = null; c || e(b); return b; } w.resolve = function(a, c, b, e, d) { let f = this.await ? Wa(this, !0) : this.result; if (f.then) { const g = this; return f.then(function() { return g.resolve(a, c, b, e, d); }); } f.length && (typeof a === "object" ? (e = a.highlight || this.highlight, b = !!e || a.enrich, c = a.offset, a = a.limit) : (e = e || this.highlight, b = !!e || b), f = d ? b ? Ta.call(this.index, f) : f : Sa.call(this.index, f, a || 100, c, b)); return this.finalize(f, e); }; w.finalize = function(a, c) { if (a.then) { const e = this; return a.then(function(d) { return e.finalize(d, c); }); } c && !this.query && console.warn('There was no query specified for highlighting. Please specify a query within the highlight resolver stage like { query: "...", highlight: ... }.'); c && a.length && this.query && (a = cb(this.query, a, this.index.index, this.field, c)); const b = this.return; this.highlight = this.index = this.result = this.C = this.await = this.return = null; this.query = this.field = ""; b && b(a); return a; }; function $a(a, c, b, e, d, f, g) { const k = a.length; let h = [], l, m; l = I(); for (let p = 0, u, r, t, n; p < c; p++) { for (let q = 0; q < k; q++) { if (t = a[q], p < t.length && (u = t[p])) { for (let x = 0; x < u.length; x++) { r = u[x]; (m = l[r]) ? l[r]++ : (m = 0, l[r] = 1); n = h[m] || (h[m] = []); if (!g) { let v = p + (q || !d ? 0 : f || 0); n = n[v] || (n[v] = []); } n.push(r); if (g && b && m === k - 1 && n.length - e === b) { return e ? n.slice(e) : n; } } } } } if (a = h.length) { if (d) { h = h.length > 1 ? Ya(h, b, e, g, f) : (h = h[0]) && b && h.length > b || e ? h.slice(e, b + e) : h; } else { if (a < k) { return []; } h = h[a - 1]; if (b || e) { if (g) { if (h.length > b || e) { h = h.slice(e, b + e); } } else { d = []; for (let p = 0, u; p < h.length; p++) { if (u = h[p]) { if (e && u.length > e) { e -= u.length; } else { if (b && u.length > b || e) { u = u.slice(e, b + e), b -= u.length, e && (e -= u.length); } d.push(u); if (!b) { break; } } } } h = d; } } } } return h; } function Ya(a, c, b, e, d) { const f = [], g = I(); let k; var h = a.length; let l; if (e) { for (d = h - 1; d >= 0; d--) { if (l = (e = a[d]) && e.length) { for (h = 0; h < l; h++) { if (k = e[h], !g[k]) { if (g[k] = 1, b) { b--; } else { if (f.push(k), f.length === c) { return f; } } } } } } } else { for (let m = h - 1, p, u = 0; m >= 0; m--) { p = a[m]; for (let r = 0; r < p.length; r++) { if (l = (e = p[r]) && e.length) { for (let t = 0; t < l; t++) { if (k = e[t], !g[k]) { if (g[k] = 1, b) { b--; } else { let n = (r + (m < h - 1 ? d || 0 : 0)) / (m + 1) | 0; (f[n] || (f[n] = [])).push(k); if (++u === c) { return f; } } } } } } } } return f; } function eb(a, c, b, e, d) { const f = I(), g = []; for (let k = 0, h; k < c.length; k++) { h = c[k]; for (let l = 0; l < h.length; l++) { f[h[l]] = 1; } } if (d) { for (let k = 0, h; k < a.length; k++) { if (h = a[k], f[h]) { if (e) { e--; } else { if (g.push(h), f[h] = 0, b && --b === 0) { break; } } } } } else { a = a.result || a; for (let k = 0, h, l; k < a.length; k++) { for (h = a[k], c = 0; c < h.length; c++) { l = h[c], f[l] && ((g[k] || (g[k] = [])).push(l), f[l] = 0); } } } return g; } ;I(); Na.prototype.search = function(a, c, b, e) { b || (!c && ba(a) ? (b = a, a = "") : ba(c) && (b = c, c = 0)); let d = []; var f = []; let g; let k, h, l, m, p; let u = 0, r = !0, t; if (b) { b.constructor === Array && (b = {index:b}); a = b.query || a; g = b.pluck; k = b.merge; l = b.boost; p = g || b.field || (p = b.index) && (p.index ? null : p); var n = this.tag && b.tag; h = b.suggest; r = b.resolve !== !1; m = b.cache; this.store && b.highlight && !r ? console.warn("Highlighting results can only be done within a resolver stage (and/or/not/xor) or when calling .resolve({ highlight: ... })") : this.store && b.enrich && !r && console.warn("Enrich results can only be done on a final resolver task or when calling .resolve({ enrich: true })"); t = r && this.store && b.highlight; var q = !!t || r && this.store && b.enrich; c = b.limit || c; var x = b.offset || 0; c || (c = r ? 100 : 0); if (n && (!this.db || !e)) { n.constructor !== Array && (n = [n]); var v = []; for (let C = 0, z; C < n.length; C++) { z = n[C]; if (N(z)) { throw Error("A tag option can't be a string, instead it needs a { field: tag } format."); } if (z.field && z.tag) { var A = z.tag; if (A.constructor === Array) { for (var E = 0; E < A.length; E++) { v.push(z.field, A[E]); } } else { v.push(z.field, A); } } else { A = Object.keys(z); for (let D = 0, J, G; D < A.length; D++) { if (J = A[D], G = z[J], G.constructor === Array) { for (E = 0; E < G.length; E++) { v.push(J, G[E]); } } else { v.push(J, G); } } } } if (!v.length) { throw Error("Your tag definition within the search options is probably wrong. No valid tags found."); } n = v; if (!a) { f = []; if (v.length) { for (n = 0; n < v.length; n += 2) { if (this.db) { e = this.index.get(v[n]); if (!e) { console.warn("Tag '" + v[n] + ":" + v[n + 1] + "' will be skipped because there is no field '" + v[n] + "'."); continue; } f.push(e = e.db.tag(v[n + 1], c, x, q)); } else { e = fb.call(this, v[n], v[n + 1], c, x, q); } d.push(r ? {field:v[n], tag:v[n + 1], result:e} : [e]); } } if (f.length) { const C = this; return Promise.all(f).then(function(z) { for (let D = 0; D < z.length; D++) { r ? d[D].result = z[D] : d[D] = z[D]; } return r ? d : new X(d.length > 1 ? $a(d, 1, 0, 0, h, l) : d[0], C); }); } return r ? d : new X(d.length > 1 ? $a(d, 1, 0, 0, h, l) : d[0], this); } } if (!r && !g) { if (p = p || this.field) { N(p) ? g = p : (p.constructor === Array && p.length === 1 && (p = p[0]), g = p.field || p.index); } if (!g) { throw Error("Apply resolver on document search requires either the option 'pluck' to be set or just select a single field name in your query."); } } p && p.constructor !== Array && (p = [p]); } p || (p = this.field); let F; v = (this.worker || this.db) && !e && []; for (let C = 0, z, D, J; C < p.length; C++) { D = p[C]; if (this.db && this.tag && !this.B[C]) { continue; } let G; N(D) || (G = D, D = G.field, a = G.query || a, c = aa(G.limit, c), x = aa(G.offset, x), h = aa(G.suggest, h), t = r && this.store && aa(G.highlight, t), q = !!t || r && this.store && aa(G.enrich, q), m = aa(G.cache, m)); if (e) { z = e[C]; } else { A = G || b || {}; E = A.enrich; var B = this.index.get(D); n && (this.db && (A.tag = n, A.field = p, F = B.db.support_tag_search), !F && E && (A.enrich = !1), F || (A.limit = 0, A.offset = 0)); z = m ? B.searchCache(a, n && !F ? 0 : c, A) : B.search(a, n && !F ? 0 : c, A); n && !F && (A.limit = c, A.offset = x); E && (A.enrich = E); if (v) { v[C] = z; continue; } } J = (z = z.result || z) && z.length; if (n && J) { A = []; E = 0; if (this.db && e) { if (!F) { for (B = p.length; B < e.length; B++) { let L = e[B]; if (L && L.length) { E++, A.push(L); } else if (!h) { return r ? d : new X(d, this); } } } } else { for (let L = 0, O, P; L < n.length; L += 2) { O = this.tag.get(n[L]); if (!O) { if (console.warn("Tag '" + n[L] + ":" + n[L + 1] + "' will be skipped because there is no field '" + n[L] + "'."), h) { continue; } else { return r ? d : new X(d, this); } } if (P = (O = O && O.get(n[L + 1])) && O.length) { E++, A.push(O); } else if (!h) { return r ? d : new X(d, this); } } } if (E) { z = eb(z, A, c, x, r); J = z.length; if (!J && !h) { return r ? z : new X(z, this); } E--; } } if (J) { f[u] = D, d.push(z), u++; } else if (p.length === 1) { return r ? d : new X(d, this); } } if (v) { if (this.db && n && n.length && !F) { for (q = 0; q < n.length; q += 2) { f = this.index.get(n[q]); if (!f) { if (console.warn("Tag '" + n[q] + ":" + n[q + 1] + "' was not found because there is no field '" + n[q] + "'."), h) { continue; } else { return r ? d : new X(d, this); } } v.push(f.db.tag(n[q + 1], c, x, !1)); } } const C = this; return Promise.all(v).then(function(z) { b && (b.resolve = r); z.length && (z = C.search(a, c, b, z)); return z; }); } if (!u) { return r ? d : new X(d, this); } if (g && (!q || !this.store)) { return d = d[0], r ? d : new X(d, this); } v = []; for (x = 0; x < f.length; x++) { n = d[x]; q && n.length && typeof n[0].doc === "undefined" && (this.db ? v.push(n = this.index.get(this.field[0]).db.enrich(n)) : n = Ta.call(this, n)); if (g) { return r ? t ? cb(a, n, this.index, g, t) : n : new X(n, this); } d[x] = {field:f[x], result:n}; } if (q && this.db && v.length) { const C = this; return Promise.all(v).then(function(z) { for (let D = 0; D < z.length; D++) { d[D].result = z[D]; } t && (d = cb(a, d, C.index, g, t)); return k ? gb(d) : d; }); } t && (d = cb(a, d, this.index, g, t)); return k ? gb(d) : d; }; function gb(a) { const c = [], b = I(), e = I(); for (let d = 0, f, g, k, h, l, m, p; d < a.length; d++) { f = a[d]; g = f.field; k = f.result; for (let u = 0; u < k.length; u++) { if (l = k[u], typeof l !== "object" ? l = {id:h = l} : h = l.id, (m = b[h]) ? m.push(g) : (l.field = b[h] = [g], c.push(l)), p = l.highlight) { m = e[h], m || (e[h] = m = {}, l.highlight = m), m[g] = p; } } } return c; } function fb(a, c, b, e, d) { a = this.tag.get(a); if (!a) { return []; } a = a.get(c); if (!a) { return []; } c = a.length - e; if (c > 0) { if (b && c > b || e) { a = a.slice(e, e + b); } d && (a = Ta.call(this, a)); } return a; } function Ta(a) { if (!this || !this.store) { return a; } if (this.db) { return this.index.get(this.field[0]).db.enrich(a); } const c = Array(a.length); for (let b = 0, e; b < a.length; b++) { e = a[b], c[b] = {id:e, doc:this.store.get(e)}; } return c; } ;function Na(a) { if (!this || this.constructor !== Na) { return new Na(a); } const c = a.document || a.doc || a; let b, e; this.B = []; this.field = []; this.D = []; this.key = (b = c.key || c.id) && hb(b, this.D) || "id"; (e = a.keystore || 0) && (this.keystore = e); this.fastupdate = !!a.fastupdate; this.reg = !this.fastupdate || a.worker || a.db ? e ? new R(e) : new Set() : e ? new Q(e) : new Map(); this.h = (b = c.store || null) && b && b !== !0 && []; this.store = b ? e ? new Q(e) : new Map() : null; this.cache = (b = a.cache || null) && new na(b); a.cache = !1; this.worker = a.worker || !1; this.priority = a.priority || 4; this.index = ib.call(this, a, c); this.tag = null; if (b = c.tag) { if (typeof b === "string" && (b = [b]), b.length) { this.tag = new Map(); this.A = []; this.F = []; for (let d = 0, f, g; d < b.length; d++) { f = b[d]; g = f.field || f; if (!g) { throw Error("The tag field from the document descriptor is undefined."); } f.custom ? this.A[d] = f.custom : (this.A[d] = hb(g, this.D), f.filter && (typeof this.A[d] === "string" && (this.A[d] = new String(this.A[d])), this.A[d].G = f.filter)); this.F[d] = g; this.tag.set(g, new Map()); } } } if (this.worker) { this.fastupdate = !1; a = []; for (const d of this.index.values()) { d.then && a.push(d); } if (a.length) { const d = this; return Promise.all(a).then(function(f) { let g = 0; for (const k of d.index.entries()) { const h = k[0]; let l = k[1]; l.then && (l = f[g], d.index.set(h, l), g++); } return d; }); } } else { a.db && (this.fastupdate = !1, this.mount(a.db)); } } w = Na.prototype; w.mount = function(a) { if (this.worker) { throw Error("You can't use Worker-Indexes on a persistent model. That would be useless, since each of the persistent model acts like Worker-Index by default (Master/Slave)."); } let c = this.field; if (this.tag) { for (let f = 0, g; f < this.F.length; f++) { g = this.F[f]; var b = void 0; this.index.set(g, b = new T({}, this.reg)); c === this.field && (c = c.slice(0)); c.push(g); b.tag = this.tag.get(g); } } b = []; const e = {db:a.db, type:a.type, fastupdate:a.fastupdate}; for (let f = 0, g, k; f < c.length; f++) { e.field = k = c[f]; g = this.index.get(k); const h = new a.constructor(a.id, e); h.id = a.id; b[f] = h.mount(g); g.document = !0; f ? g.bypass = !0 : g.store = this.store; } const d = this; return this.db = Promise.all(b).then(function() { d.db = !0; }); }; w.commit = async function() { const a = []; for (const c of this.index.values()) { a.push(c.commit()); } await Promise.all(a); this.reg.clear(); }; w.destroy = function() { const a = []; for (const c of this.index.values()) { a.push(c.destroy()); } return Promise.all(a); }; function ib(a, c) { const b = new Map(); let e = c.index || c.field || c; N(e) && (e = [e]); for (let f = 0, g, k; f < e.length; f++) { g = e[f]; N(g) || (k = g, g = g.field); k = ba(k) ? Object.assign({}, a, k) : a; if (this.worker) { var d = void 0; d = (d = k.encoder) && d.encode ? d : new ka(typeof d === "string" ? wa[d] : d || {}); d = new La(k, d); b.set(g, d); } this.worker || b.set(g, new T(k, this.reg)); k.custom ? this.B[f] = k.custom : (this.B[f] = hb(g, this.D), k.filter && (typeof this.B[f] === "string" && (this.B[f] = new String(this.B[f])), this.B[f].G = k.filter)); this.field[f] = g; } if (this.h) { a = c.store; N(a) && (a = [a]); for (let f = 0, g, k; f < a.length; f++) { g = a[f], k = g.field || g, g.custom ? (this.h[f] = g.custom, g.custom.O = k) : (this.h[f] = hb(k, this.D), g.filter && (typeof this.h[f] === "string" && (this.h[f] = new String(this.h[f])), this.h[f].G = g.filter)); } } return b; } function hb(a, c) { const b = a.split(":"); let e = 0; for (let d = 0; d < b.length; d++) { a = b[d], a[a.length - 1] === "]" && (a = a.substring(0, a.length - 2)) && (c[e] = !0), a && (b[e++] = a); } e < b.length && (b.length = e); return e > 1 ? b : b[0]; } w.append = function(a, c) { return this.add(a, c, !0); }; w.update = function(a, c) { return this.remove(a).add(a, c); }; w.remove = function(a) { ba(a) && (a = ca(a, this.key)); for (var c of this.index.values()) { c.remove(a, !0); } if (this.reg.has(a)) { if (this.tag && !this.fastupdate) { for (let b of this.tag.values()) { for (let e of b) { c = e[0]; const d = e[1], f = d.indexOf(a); f > -1 && (d.length > 1 ? d.splice(f, 1) : b.delete(c)); } } } this.store && this.store.delete(a); this.reg.delete(a); } this.cache && this.cache.remove(a); return this; }; w.clear = function() { const a = []; for (const c of this.index.values()) { const b = c.clear(); b.then && a.push(b); } if (this.tag) { for (const c of this.tag.values()) { c.clear(); } } this.store && this.store.clear(); this.cache && this.cache.clear(); return a.length ? Promise.all(a) : this; }; w.contain = function(a) { return this.db ? this.index.get(this.field[0]).db.has(a) : this.reg.has(a); }; w.cleanup = function() { for (const a of this.index.values()) { a.cleanup(); } return this; }; w.get = function(a) { return this.db ? this.index.get(this.field[0]).db.enrich(a).then(function(c) { return c[0] && c[0].doc || null; }) : this.store.get(a) || null; }; w.set = function(a, c) { typeof a === "object" && (c = a, a = ca(c, this.key)); this.store.set(a, c); return this; }; w.searchCache = ma; w.export = jb; w.import = kb; Fa(Na.prototype); function lb(a, c = 0) { let b = [], e = []; c && (c = 250000 / c * 5000 | 0); for (const d of a.entries()) { e.push(d), e.length === c && (b.push(e), e = []); } e.length && b.push(e); return b; } function mb(a, c) { c || (c = new Map()); for (let b = 0, e; b < a.length; b++) { e = a[b], c.set(e[0], e[1]); } return c; } function nb(a, c = 0) { let b = [], e = []; c && (c = 250000 / c * 1000 | 0); for (const d of a.entries()) { e.push([d[0], lb(d[1])[0] || []]), e.length === c && (b.push(e), e = []); } e.length && b.push(e); return b; } function ob(a, c) { c || (c = new Map()); for (let b = 0, e, d; b < a.length; b++) { e = a[b], d = c.get(e[0]), c.set(e[0], mb(e[1], d)); } return c; } function pb(a) { let c = [], b = []; for (const e of a.keys()) { b.push(e), b.length === 250000 && (c.push(b), b = []); } b.length && c.push(b); return c; } function qb(a, c) { c || (c = new Set()); for (let b = 0; b < a.length; b++) { c.add(a[b]); } return c; } function rb(a, c, b, e, d, f, g = 0) { const k = e && e.constructor === Array; var h = k ? e.shift() : e; if (!h) { return this.export(a, c, d, f + 1); } if ((h = a((c ? c + "." : "") + (g + 1) + "." + b, JSON.stringify(h))) && h.then) { const l = this; return h.then(function() { return rb.call(l, a, c, b, k ? e : null, d, f, g + 1); }); } return rb.call(this, a, c, b, k ? e : null, d, f, g + 1); } function jb(a, c, b = 0, e = 0) { if (b < this.field.length) { const g = this.field[b]; if ((c = this.index.get(g).export(a, g, b, e = 1)) && c.then) { const k = this; return c.then(function() { return k.export(a, g, b + 1); }); } return this.export(a, g, b + 1); } let d, f; switch(e) { case 0: d = "reg"; f = pb(this.reg); c = null; break; case 1: d = "tag"; f = this.tag && nb(this.tag, this.reg.size); c = null; break; case 2: d = "doc"; f = this.store && lb(this.store); c = null; break; default: return; } return rb.call(this, a, c, d, f || null, b, e); } function kb(a, c) { var b = a.split("."); b[b.length - 1] === "json" && b.pop(); const e = b.length > 2 ? b[0] : ""; b = b.length > 2 ? b[2] : b[1]; if (this.worker && e) { return this.index.get(e).import(a); } if (c) { typeof c === "string" && (c = JSON.parse(c)); if (e) { return this.index.get(e).import(b, c); } switch(b) { case "reg": this.fastupdate = !1; this.reg = qb(c, this.reg); for (let d = 0, f; d < this.field.length; d++) { f = this.index.get(this.field[d]), f.fastupdate = !1, f.reg = this.reg; } if (this.worker) { c = []; for (const d of this.index.values()) { c.push(d.import(a)); } return Promise.all(c); } break; case "tag": this.tag = ob(c, this.tag); break; case "doc": this.store = mb(c, this.store); } } } function sb(a, c) { let b = ""; for (const e of a.entries()) { a = e[0]; const d = e[1]; let f = ""; for (let g = 0, k; g < d.length; g++) { k = d[g] || [""]; let h = ""; for (let l = 0; l < k.length; l++) { h += (h ? "," : "") + (c === "string" ? '"' + k[l] + '"' : k[l]); } h = "[" + h + "]"; f += (f ? "," : "") + h; } f = '["' + a + '",[' + f + "]]"; b += (b ? "," : "") + f; } return b; } ;T.prototype.remove = function(a, c) { const b = this.reg.size && (this.fastupdate ? this.reg.get(a) : this.reg.has(a)); if (b) { if (this.fastupdate) { for (let e = 0, d, f; e < b.length; e++) { if ((d = b[e]) && (f = d.length)) { if (d[f - 1] === a) { d.pop(); } else { const g = d.indexOf(a); g >= 0 && d.splice(g, 1); } } } } else { tb(this.map, a), this.depth && tb(this.ctx, a); } c || this.reg.delete(a); } this.db && (this.commit_task.push({del:a}), this.M && ub(this)); this.cache && this.cache.remove(a); return this; }; function tb(a, c) { let b = 0; var e = typeof c === "undefined"; if (a.constructor === Array) { for (let d = 0, f, g, k; d < a.length; d++) { if ((f = a[d]) && f.length) { if (e) { return 1; } g = f.indexOf(c); if (g >= 0) { if (f.length > 1) { return f.splice(g, 1), 1; } delete a[d]; if (b) { return 1; } k = 1; } else { if (k) { return 1; } b++; } } } } else { for (let d of a.entries()) { e = d[0], tb(d[1], c) ? b++ : a.delete(e); } } return b; } ;const vb = {memory:{resolution:1}, performance:{resolution:3, fastupdate:!0, context:{depth:1, resolution:1}}, match:{tokenize:"full"}, score:{resolution:9, context:{depth:2, resolution:3}}}; T.prototype.add = function(a, c, b, e) { if (c && (a || a === 0)) { if (!e && !b && this.reg.has(a)) { return this.update(a, c); } e = this.depth; c = this.encoder.encode(c, !e); const l = c.length; if (l) { const m = I(), p = I(), u = this.resolution; for (let r = 0; r < l; r++) { let t = c[this.rtl ? l - 1 - r : r]; var d = t.length; if (d && (e || !p[t])) { var f = this.score ? this.score(c, t, r, null, 0) : wb(u, l, r), g = ""; switch(this.tokenize) { case "tolerant": Y(this, p, t, f, a, b); if (d > 2) { for (let n = 1, q, x, v, A; n < d - 1; n++) { q = t.charAt(n), x = t.charAt(n + 1), v = t.substring(0, n) + x, A = t.substring(n + 2), g = v + q + A, Y(this, p, g, f, a, b), g = v + A, Y(this, p, g, f, a, b); } Y(this, p, t.substring(0, t.length - 1), f, a, b); } break; case "full": if (d > 2) { for (let n = 0, q; n < d; n++) { for (f = d; f > n; f--) { g = t.substring(n, f); q = this.rtl ? d - 1 - n : n; var k = this.score ? this.score(c, t, r, g, q) : wb(u, l, r, d, q); Y(this, p, g, k, a, b); } } break; } case "bidirectional": case "reverse": if (d > 1) { for (k = d - 1; k > 0; k--) { g = t[this.rtl ? d - 1 - k : k] + g; var h = this.score ? this.score(c, t, r, g, k) : wb(u, l, r, d, k); Y(this, p, g, h, a, b); } g = ""; } case "forward": if (d > 1) { for (k = 0; k < d; k++) { g += t[this.rtl ? d - 1 - k : k], Y(this, p, g, f, a, b); } break; } default: if (Y(this, p, t, f, a, b), e && l > 1 && r < l - 1) { for (d = this.N, g = t, f = Math.min(e + 1, this.rtl ? r + 1 : l - r), k = 1; k < f; k++) { t = c[this.rtl ? l - 1 - r - k : r + k]; h = this.bidirectional && t > g; const n = this.score ? this.score(c, g, r, t, k - 1) : wb(d + (l / 2 > d ? 0 : 1), l, r, f - 1, k - 1); Y(this, m, h ? g : t, n, a, b, h ? t : g); } } } } } this.fastupdate || this.reg.add(a); } } this.db && (this.commit_task.push(b ? {ins:a} : {del:a}), this.M && ub(this)); return this; }; function Y(a, c, b, e, d, f, g) { let k, h; if (!(k = c[b]) || g && !k[g]) { g ? (c = k || (c[b] = I()), c[g] = 1, h = a.ctx, (k = h.get(g)) ? h = k : h.set(g, h = a.keystore ? new Q(a.keystore) : new Map())) : (h = a.map, c[b] = 1); (k = h.get(b)) ? h = k : h.set(b, h = k = []); if (f) { for (let l = 0, m; l < k.length; l++) { if ((m = k[l]) && m.includes(d)) { if (l <= e) { return; } m.splice(m.indexOf(d), 1); a.fastupdate && (c = a.reg.get(d)) && c.splice(c.indexOf(m), 1); break; } } } h = h[e] || (h[e] = []); h.push(d); if (h.length === 2 ** 31 - 1) { c = new Aa(h); if (a.fastupdate) { for (let l of a.reg.values()) { l.includes(h) && (l[l.indexOf(h)] = c); } } k[e] = h = c; } a.fastupdate && ((e = a.reg.get(d)) ? e.push(h) : a.reg.set(d, [h])); } } function wb(a, c, b, e, d) { return b && a > 1 ? c + (e || 0) <= a ? b + (d || 0) : (a - 1) / (c + (e || 0)) * (b + (d || 0)) + 1 | 0 : 0; } ;T.prototype.search = function(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : (b = a, a = "")); if (b && b.cache) { return b.cache = !1, a = this.searchCache(a, c, b), b.cache = !0, a; } let e = [], d, f, g, k = 0, h, l, m, p, u; b && (a = b.query || a, c = b.limit || c, k = b.offset || 0, f = b.context, g = b.suggest, u = (h = b.resolve) && b.enrich, m = b.boost, p = b.resolution, l = this.db && b.tag); typeof h === "undefined" && (h = this.resolve); f = this.depth && f !== !1; let r = this.encoder.encode(a, !f); d = r.length; c = c || (h ? 100 : 0); if (d === 1) { return xb.call(this, r[0], "", c, k, h, u, l); } if (d === 2 && f && !g) { return xb.call(this, r[1], r[0], c, k, h, u, l); } let t = I(), n = 0, q; f && (q = r[0], n = 1); p || p === 0 || (p = q ? this.N : this.resolution); if (this.db) { if (this.db.search && (b = this.db.search(this, r, c, k, g, h, u, l), b !== !1)) { return b; } const x = this; return async function() { for (let v, A; n < d; n++) { if ((A = r[n]) && !t[A]) { t[A] = 1; v = await yb(x, A, q, 0, 0, !1, !1); if (v = zb(v, e, g, p)) { e = v; break; } q && (g && v && e.length || (q = A)); } g && q && n === d - 1 && !e.length && (p = x.resolution, q = "", n = -1, t = I()); } return Ab(e, p, c, k, g, m, h); }(); } for (let x, v; n < d; n++) { if ((v = r[n]) && !t[v]) { t[v] = 1; x = yb(this, v, q, 0, 0, !1, !1); if (x = zb(x, e, g, p)) { e = x; break; } q && (g && x && e.length || (q = v)); } g && q && n === d - 1 && !e.length && (p = this.resolution, q = "", n = -1, t = I()); } return Ab(e, p, c, k, g, m, h); }; function Ab(a, c, b, e, d, f, g) { let k = a.length, h = a; if (k > 1) { h = $a(a, c, b, e, d, f, g); } else if (k === 1) { return g ? Sa.call(null, a[0], b, e) : new X(a[0], this); } return g ? h : new X(h, this); } function xb(a, c, b, e, d, f, g) { a = yb(this, a, c, b, e, d, f, g); return this.db ? a.then(function(k) { return d ? k || [] : new X(k, this); }) : a && a.length ? d ? Sa.call(this, a, b, e) : new X(a, this) : d ? [] : new X([], this); } function zb(a, c, b, e) { let d = []; if (a && a.length) { if (a.length <= e) { c.push(a); return; } for (let f = 0, g; f < e; f++) { if (g = a[f]) { d[f] = g; } } if (d.length) { c.push(d); return; } } if (!b) { return d; } } function yb(a, c, b, e, d, f, g, k) { let h; b && (h = a.bidirectional && c > b) && (h = b, b = c, c = h); if (a.db) { return a.db.get(c, b, e, d, f, g, k); } a = b ? (a = a.ctx.get(b)) && a.get(c) : a.map.get(c); return a; } ;function T(a, c) { if (!this || this.constructor !== T) { return new T(a); } if (a) { var b = N(a) ? a : a.preset; b && (vb[b] || console.warn("Preset not found: " + b), a = Object.assign({}, vb[b], a)); } else { a = {}; } b = a.context; const e = b === !0 ? {depth:1} : b || {}, d = N(a.encoder) ? wa[a.encoder] : a.encode || a.encoder || {}; this.encoder = d.encode ? d : typeof d === "object" ? new ka(d) : {encode:d}; this.resolution = a.resolution || 9; this.tokenize = b = (b = a.tokenize) && b !== "default" && b !== "exact" && b || "strict"; this.depth = b === "strict" && e.depth || 0; this.bidirectional = e.bidirectional !== !1; this.fastupdate = !!a.fastupdate; this.score = a.score || null; e && e.depth && this.tokenize !== "strict" && console.warn('Context-Search could not applied, because it is just supported when using the tokenizer "strict".'); (b = a.keystore || 0) && (this.keystore = b); this.map = b ? new Q(b) : new Map(); this.ctx = b ? new Q(b) : new Map(); this.reg = c || (this.fastupdate ? b ? new Q(b) : new Map() : b ? new R(b) : new Set()); this.N = e.resolution || 3; this.rtl = d.rtl || a.rtl || !1; this.cache = (b = a.cache || null) && new na(b); this.resolve = a.resolve !== !1; if (b = a.db) { this.db = this.mount(b); } this.M = a.commit !== !1; this.commit_task = []; this.commit_timer = null; this.priority = a.priority || 4; } w = T.prototype; w.mount = function(a) { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return a.mount(this); }; w.commit = function() { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return this.db.commit(this); }; w.destroy = function() { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return this.db.destroy(); }; function ub(a) { a.commit_timer || (a.commit_timer = setTimeout(function() { a.commit_timer = null; a.db.commit(a); }, 1)); } w.clear = function() { this.map.clear(); this.ctx.clear(); this.reg.clear(); this.cache && this.cache.clear(); return this.db ? (this.commit_timer && clearTimeout(this.commit_timer), this.commit_timer = null, this.commit_task = [], this.db.clear()) : this; }; w.append = function(a, c) { return this.add(a, c, !0); }; w.contain = function(a) { return this.db ? this.db.has(a) : this.reg.has(a); }; w.update = function(a, c) { const b = this, e = this.remove(a); return e && e.then ? e.then(() => b.add(a, c)) : this.add(a, c); }; w.cleanup = function() { if (!this.fastupdate) { return console.info('Cleanup the index isn\'t required when not using "fastupdate".'), this; } tb(this.map); this.depth && tb(this.ctx); return this; }; w.searchCache = ma; w.export = function(a, c, b = 0, e = 0) { let d, f; switch(e) { case 0: d = "reg"; f = pb(this.reg); break; case 1: d = "cfg"; f = null; break; case 2: d = "map"; f = lb(this.map, this.reg.size); break; case 3: d = "ctx"; f = nb(this.ctx, this.reg.size); break; default: return; } return rb.call(this, a, c, d, f, b, e); }; w.import = function(a, c) { if (c) { switch(typeof c === "string" && (c = JSON.parse(c)), a = a.split("."), a[a.length - 1] === "json" && a.pop(), a.length === 3 && a.shift(), a = a.length > 1 ? a[1] : a[0], a) { case "reg": this.fastupdate = !1; this.reg = qb(c, this.reg); break; case "map": this.map = mb(c, this.map); break; case "ctx": this.ctx = ob(c, this.ctx); } } }; w.serialize = function(a = !0) { let c = "", b = "", e = ""; if (this.reg.size) { let f; for (var d of this.reg.keys()) { f || (f = typeof d), c += (c ? "," : "") + (f === "string" ? '"' + d + '"' : d); } c = "index.reg=new Set([" + c + "]);"; b = sb(this.map, f); b = "index.map=new Map([" + b + "]);"; for (const g of this.ctx.entries()) { d = g[0]; let k = sb(g[1], f); k = "new Map([" + k + "])"; k = '["' + d + '",' + k + "]"; e += (e ? "," : "") + k; } e = "index.ctx=new Map([" + e + "]);"; } return a ? "function inject(index){" + c + b + e + "}" : c + b + e; }; Fa(T.prototype); const Bb = typeof window !== "undefined" && (window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB), Cb = ["map", "ctx", "tag", "reg", "cfg"], Db = I(); function Eb(a, c = {}) { if (!this || this.constructor !== Eb) { return new Eb(a, c); } typeof a === "object" && (c = a, a = a.name); a || console.info("Default storage space was used, because a name was not passed."); this.id = "flexsearch" + (a ? ":" + a.toLowerCase().replace(/[^a-z0-9_\-]/g, "") : ""); this.field = c.field ? c.field.toLowerCase().replace(/[^a-z0-9_\-]/g, "") : ""; this.type = c.type; this.fastupdate = this.support_tag_search = !1; this.db = null; this.h = {}; } w = Eb.prototype; w.mount = function(a) { if (a.index) { return a.mount(this); } a.db = this; return this.open(); }; w.open = function() { if (this.db) { return this.db; } let a = this; navigator.storage && navigator.storage.persist && navigator.storage.persist(); Db[a.id] || (Db[a.id] = []); Db[a.id].push(a.field); const c = Bb.open(a.id, 1); c.onupgradeneeded = function() { const b = a.db = this.result; for (let e = 0, d; e < Cb.length; e++) { d = Cb[e]; for (let f = 0, g; f < Db[a.id].length; f++) { g = Db[a.id][f], b.objectStoreNames.contains(d + (d !== "reg" ? g ? ":" + g : "" : "")) || b.createObjectStore(d + (d !== "reg" ? g ? ":" + g : "" : "")); } } }; return a.db = Z(c, function(b) { a.db = b; a.db.onversionchange = function() { a.close(); }; }); }; w.close = function() { this.db && this.db.close(); this.db = null; }; w.destroy = function() { const a = Bb.deleteDatabase(this.id); return Z(a); }; w.clear = function() { const a = []; for (let b = 0, e; b < Cb.length; b++) { e = Cb[b]; for (let d = 0, f; d < Db[this.id].length; d++) { f = Db[this.id][d], a.push(e + (e !== "reg" ? f ? ":" + f : "" : "")); } } const c = this.db.transaction(a, "readwrite"); for (let b = 0; b < a.length; b++) { c.objectStore(a[b]).clear(); } return Z(c); }; w.get = function(a, c, b = 0, e = 0, d = !0, f = !1) { a = this.db.transaction((c ? "ctx" : "map") + (this.field ? ":" + this.field : ""), "readonly").objectStore((c ? "ctx" : "map") + (this.field ? ":" + this.field : "")).get(c ? c + ":" + a : a); const g = this; return Z(a).then(function(k) { let h = []; if (!k || !k.length) { return h; } if (d) { if (!b && !e && k.length === 1) { return k[0]; } for (let l = 0, m; l < k.length; l++) { if ((m = k[l]) && m.length) { if (e >= m.length) { e -= m.length; continue; } const p = b ? e + Math.min(m.length - e, b) : m.length; for (let u = e; u < p; u++) { h.push(m[u]); } e = 0; if (h.length === b) { break; } } } return f ? g.enrich(h) : h; } return k; }); }; w.tag = function(a, c = 0, b = 0, e = !1) { a = this.db.transaction("tag" + (this.field ? ":" + this.field : ""), "readonly").objectStore("tag" + (this.field ? ":" + this.field : "")).get(a); const d = this; return Z(a).then(function(f) { if (!f || !f.length || b >= f.length) { return []; } if (!c && !b) { return f; } f = f.slice(b, b + c); return e ? d.enrich(f) : f; }); }; w.enrich = function(a) { typeof a !== "object" && (a = [a]); const c = this.db.transaction("reg", "readonly").objectStore("reg"), b = []; for (let e = 0; e < a.length; e++) { b[e] = Z(c.get(a[e])); } return Promise.all(b).then(function(e) { for (let d = 0; d < e.length; d++) { e[d] = {id:a[d], doc:e[d] ? JSON.parse(e[d]) : null}; } return e; }); }; w.has = function(a) { a = this.db.transaction("reg", "readonly").objectStore("reg").getKey(a); return Z(a).then(function(c) { return !!c; }); }; w.search = null; w.info = function() { }; w.transaction = function(a, c, b) { a += a !== "reg" ? this.field ? ":" + this.field : "" : ""; let e = this.h[a + ":" + c]; if (e) { return b.call(this, e); } let d = this.db.transaction(a, c); this.h[a + ":" + c] = e = d.objectStore(a); const f = b.call(this, e); this.h[a + ":" + c] = null; return Z(d).finally(function() { return f; }); }; w.commit = async function(a) { let c = a.commit_task, b = []; a.commit_task = []; for (let e = 0, d; e < c.length; e++) { d = c[e], d.del && b.push(d.del); } b.length && await this.remove(b); a.reg.size && (await this.transaction("map", "readwrite", function(e) { for (const d of a.map) { const f = d[0], g = d[1]; g.length && (e.get(f).onsuccess = function() { let k = this.result; var h; if (k && k.length) { const l = Math.max(k.length, g.length); for (let m = 0, p, u; m < l; m++) { if ((u = g[m]) && u.length) { if ((p = k[m]) && p.length) { for (h = 0; h < u.length; h++) { p.push(u[h]); } } else { k[m] = u; } h = 1; } } } else { k = g, h = 1; } h && e.put(k, f); }); } }), await this.transaction("ctx", "readwrite", function(e) { for (const d of a.ctx) { const f = d[0], g = d[1]; for (const k of g) { const h = k[0], l = k[1]; l.length && (e.get(f + ":" + h).onsuccess = function() { let m = this.result; var p; if (m && m.length) { const u = Math.max(m.length, l.length); for (let r = 0, t, n; r < u; r++) { if ((n = l[r]) && n.length) { if ((t = m[r]) && t.length) { for (p = 0; p < n.length; p++) { t.push(n[p]); } } else { m[r] = n; } p = 1; } } } else { m = l, p = 1; } p && e.put(m, f + ":" + h); }); } } }), a.store ? await this.transaction("reg", "readwrite", function(e) { for (const d of a.store) { const f = d[0], g = d[1]; e.put(typeof g === "object" ? JSON.stringify(g) : 1, f); } }) : a.bypass || await this.transaction("reg", "readwrite", function(e) { for (const d of a.reg.keys()) { e.put(1, d); } }), a.tag && await this.transaction("tag", "readwrite", function(e) { for (const d of a.tag) { const f = d[0], g = d[1]; g.length && (e.get(f).onsuccess = function() { let k = this.result; k = k && k.length ? k.concat(g) : g; e.put(k, f); }); } }), a.map.clear(), a.ctx.clear(), a.tag && a.tag.clear(), a.store && a.store.clear(), a.document || a.reg.clear()); }; function Fb(a, c, b) { const e = a.value; let d, f = 0; for (let g = 0, k; g < e.length; g++) { if (k = b ? e : e[g]) { for (let h = 0, l, m; h < c.length; h++) { if (m = c[h], l = k.indexOf(m), l >= 0) { if (d = 1, k.length > 1) { k.splice(l, 1); } else { e[g] = []; break; } } } f += k.length; } if (b) { break; } } f ? d && a.update(e) : a.delete(); a.continue(); } w.remove = function(a) { typeof a !== "object" && (a = [a]); return Promise.all([this.transaction("map", "readwrite", function(c) { c.openCursor().onsuccess = function() { const b = this.result; b && Fb(b, a); }; }), this.transaction("ctx", "readwrite", function(c) { c.openCursor().onsuccess = function() { const b = this.result; b && Fb(b, a); }; }), this.transaction("tag", "readwrite", function(c) { c.openCursor().onsuccess = function() { const b = this.result; b && Fb(b, a, !0); }; }), this.transaction("reg", "readwrite", function(c) { for (let b = 0; b < a.length; b++) { c.delete(a[b]); } })]); }; function Z(a, c) { return new Promise((b, e) => { a.onsuccess = a.oncomplete = function() { c && c(this.result); c = null; b(this.result); }; a.onerror = a.onblocked = e; a = null; }); } ;export default {Index:T, Charset:wa, Encoder:ka, Document:Na, Worker:La, Resolver:X, IndexedDB:Eb, Language:{}}; export const Index=T;export const Charset=wa;export const Encoder=ka;export const Document=Na;export const Worker=La;export const Resolver=X;export const IndexedDB=Eb;export const Language={}; ================================================ FILE: dist/flexsearch.bundle.module.min.mjs ================================================ /**! * FlexSearch.js v0.8.214 (Bundle/Module) * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ var w;function H(a,c,b){const e=typeof b,d=typeof a;if(e!=="undefined"){if(d!=="undefined"){if(b){if(d==="function"&&e===d)return function(k){return a(b(k))};c=a.constructor;if(c===b.constructor){if(c===Array)return b.concat(a);if(c===Map){var f=new Map(b);for(var g of a)f.set(g[0],g[1]);return f}if(c===Set){g=new Set(b);for(f of a.values())g.add(f);return g}}}return a}return b}return d==="undefined"?c:a}function aa(a,c){return typeof a==="undefined"?c:a}function I(){return Object.create(null)} function M(a){return typeof a==="string"}function ba(a){return typeof a==="object"}function ca(a,c){if(M(c))a=a[c];else for(let b=0;a&&b1)return this.addMatcher(a,c);this.mapper||(this.mapper=new Map);this.mapper.set(a,c);this.cache&&Q(this);return this};w.addMatcher=function(a,c){if(typeof a==="object")return this.addReplacer(a,c);if(a.length<2&&(this.dedupe||this.mapper))return this.addMapper(a,c);this.matcher||(this.matcher=new Map);this.matcher.set(a,c);this.h+=(this.h?"|":"")+a;this.J=null;this.cache&&Q(this);return this}; w.addReplacer=function(a,c){if(typeof a==="string")return this.addMatcher(a,c);this.replacer||(this.replacer=[]);this.replacer.push(a,c);this.cache&&Q(this);return this}; w.encode=function(a,c){if(this.cache&&a.length<=this.H)if(this.F){if(this.B.has(a))return this.B.get(a)}else this.F=setTimeout(Q,50,this);this.normalize&&(typeof this.normalize==="function"?a=this.normalize(a):a=ja?a.normalize("NFKD").replace(ja,"").toLowerCase():a.toLowerCase());this.prepare&&(a=this.prepare(a));this.numeric&&a.length>3&&(a=a.replace(ha,"$1 $2").replace(ia,"$1 $2").replace(fa,"$1 "));const b=!(this.dedupe||this.mapper||this.filter||this.matcher||this.stemmer||this.replacer);let e= [],d=I(),f,g,k=this.split||this.split===""?a.split(this.split):[a];for(let l=0,m,p;lthis.maxlength)){if(c){if(d[m])continue;d[m]=1}else{if(f===m)continue;f=m}if(b)e.push(m);else if(!this.filter||(typeof this.filter==="function"?this.filter(m):!this.filter.has(m))){if(this.cache&&m.length<=this.I)if(this.F){var h=this.D.get(m);if(h||h===""){h&&e.push(h);continue}}else this.F=setTimeout(Q,50,this);if(this.stemmer){this.K||(this.K=new RegExp("(?!^)("+ this.A+")$"));let u;for(;u!==m&&m.length>2;)u=m,m=m.replace(this.K,r=>this.stemmer.get(r))}if(m&&(this.mapper||this.dedupe&&m.length>1)){h="";for(let u=0,r="",t,n;u1&&(this.J||(this.J=new RegExp("("+this.h+")","g")),m=m.replace(this.J,u=>this.matcher.get(u)));if(m&&this.replacer)for(h=0;m&&hthis.L&&(this.D.clear(),this.I=this.I/1.1|0));if(m){if(m!==p)if(c){if(d[m])continue;d[m]=1}else{if(g===m)continue;g=m}e.push(m)}}}this.finalize&&(e=this.finalize(e)||e);this.cache&&a.length<=this.H&&(this.B.set(a,e),this.B.size>this.L&&(this.B.clear(),this.H=this.H/1.1|0));return e};function Q(a){a.F=null;a.B.clear();a.D.clear()};function la(a,c,b){b||(c||typeof a!=="object"?typeof c==="object"&&(b=c,c=0):b=a);b&&(a=b.query||a,c=b.limit||c);let e=""+(c||0);b&&(e+=(b.offset||0)+!!b.context+!!b.suggest+(b.resolve!==!1)+(b.resolution||this.resolution)+(b.boost||0));a=(""+a).toLowerCase();this.cache||(this.cache=new ma);let d=this.cache.get(a+e);if(!d){const f=b&&b.cache;f&&(b.cache=!1);d=this.search(a,c,b);f&&(b.cache=f);this.cache.set(a+e,d)}return d}function ma(a){this.limit=a&&a!==!0?a:1E3;this.cache=new Map;this.h=""} ma.prototype.set=function(a,c){this.cache.set(this.h=a,c);this.cache.size>this.limit&&this.cache.delete(this.cache.keys().next().value)};ma.prototype.get=function(a){const c=this.cache.get(a);c&&this.h!==a&&(this.cache.delete(a),this.cache.set(this.h=a,c));return c};ma.prototype.remove=function(a){for(const c of this.cache){const b=c[0];c[1].includes(a)&&this.cache.delete(b)}};ma.prototype.clear=function(){this.cache.clear();this.h=""};const na={normalize:!1,numeric:!1,dedupe:!1};const oa={};const ra=new Map([["b","p"],["v","f"],["w","f"],["z","s"],["x","s"],["d","t"],["n","m"],["c","k"],["g","k"],["j","k"],["q","k"],["i","e"],["y","e"],["u","o"]]);const sa=new Map([["ae","a"],["oe","o"],["sh","s"],["kh","k"],["th","t"],["ph","f"],["pf","f"]]),ta=[/([^aeo])h(.)/g,"$1$2",/([aeo])h([^aeo]|$)/g,"$1$2",/(.)\1+/g,"$1"];const ua={a:"",e:"",i:"",o:"",u:"",y:"",b:1,f:1,p:1,v:1,c:2,g:2,j:2,k:2,q:2,s:2,x:2,z:2,"\u00df":2,d:3,t:3,l:4,m:5,n:5,r:6};var va={Exact:na,Default:oa,Normalize:oa,LatinBalance:{mapper:ra},LatinAdvanced:{mapper:ra,matcher:sa,replacer:ta},LatinExtra:{mapper:ra,replacer:ta.concat([/(?!^)[aeo]/g,""]),matcher:sa},LatinSoundex:{dedupe:!1,include:{letter:!0},finalize:function(a){for(let b=0;b=g.length)c-=g.length;else{c=g[e?"splice":"slice"](c,b);const k=c.length;if(k&&(d=d.length?d.concat(c):c,b-=k,e&&(a.length-=k),!b))break;c=0}return d} function xa(a){if(!this||this.constructor!==xa)return new xa(a);this.index=a?[a]:[];this.length=a?a.length:0;const c=this;return new Proxy([],{get(b,e){if(e==="length")return c.length;if(e==="push")return function(d){c.index[c.index.length-1].push(d);c.length++};if(e==="pop")return function(){if(c.length)return c.length--,c.index[c.index.length-1].pop()};if(e==="indexOf")return function(d){let f=0;for(let g=0,k,h;g=0)return f+h;f+=k.length}return-1}; if(e==="includes")return function(d){for(let f=0;f32?(this.B=Aa,this.A=BigInt(a)):(this.B=Ba,this.A=a)}R.prototype.get=function(a){const c=this.index[this.B(a)];return c&&c.get(a)};R.prototype.set=function(a,c){var b=this.B(a);let e=this.index[b];e?(b=e.size,e.set(a,c),(b-=e.size)&&this.size++):(this.index[b]=e=new Map([[a,c]]),this.h.push(e),this.size++)}; function S(a=8){if(!this||this.constructor!==S)return new S(a);this.index=I();this.h=[];this.size=0;a>32?(this.B=Aa,this.A=BigInt(a)):(this.B=Ba,this.A=a)}S.prototype.add=function(a){var c=this.B(a);let b=this.index[c];b?(c=b.size,b.add(a),(c-=b.size)&&this.size++):(this.index[c]=b=new Set([a]),this.h.push(b),this.size++)};w=R.prototype;w.has=S.prototype.has=function(a){const c=this.index[this.B(a)];return c&&c.has(a)}; w.delete=S.prototype.delete=function(a){const c=this.index[this.B(a)];c&&c.delete(a)&&this.size--};w.clear=S.prototype.clear=function(){this.index=I();this.h=[];this.size=0};w.values=S.prototype.values=function*(){for(let a=0;a=this.priority*this.priority*3):(Ha=setTimeout(Ka,0),Ia=Date.now());if(Ja){const f=this;return new Promise(g=>{setTimeout(function(){g(f[a+"Async"].apply(f,c))},0)})}const d=this[a].apply(this,c);b=d.then?d:new Promise(f=>f(d));e&&b.then(e);return b}};let V=0; function La(a={},c){function b(k){function h(l){l=l.data||l;const m=l.id,p=m&&f.h[m];p&&(p(l.msg),delete f.h[m])}this.worker=k;this.h=I();if(this.worker){d?this.worker.on("message",h):this.worker.onmessage=h;if(a.config)return new Promise(function(l){V>1E9&&(V=0);f.h[++V]=function(){l(f)};f.worker.postMessage({id:V,task:"init",factory:e,options:a})});this.priority=a.priority||4;this.encoder=c||null;this.worker.postMessage({task:"init",factory:e,options:a});return this}}if(!this||this.constructor!==La)return new La(a); let e=typeof self!=="undefined"?self._factory:typeof window!=="undefined"?window._factory:null;e&&(e=e.toString());const d=typeof window==="undefined",f=this,g=Ma(e,d,a.worker);return g.then?g.then(function(k){return b.call(f,k)}):b.call(this,g)}W("add");W("append");W("search");W("update");W("remove");W("clear");W("export");W("import");La.prototype.searchCache=la;Fa(La.prototype); function W(a){La.prototype[a]=function(){const c=this,b=[].slice.call(arguments);var e=b[b.length-1];let d;typeof e==="function"&&(d=e,b.pop());e=new Promise(function(f){a==="export"&&typeof b[0]==="function"&&(b[0]=null);V>1E9&&(V=0);c.h[++V]=f;c.worker.postMessage({task:a,id:V,args:b})});return d?(e.then(d),this):e}} function Ma(a,c,b){return c?typeof module!=="undefined"?new(require("worker_threads")["Worker"])(__dirname+"/worker/node.js"):import("worker_threads").then(function(worker){return new worker["Worker"](import.meta.dirname+"/node/node.mjs")}):a?new window.Worker(URL.createObjectURL(new Blob(["onmessage="+Ea.toString()],{type:"text/javascript"}))):new window.Worker(typeof b==="string"?b:import.meta.url.replace("/worker.js","/worker/worker.js").replace("flexsearch.bundle.module.min.js", "module/worker/worker.js").replace("flexsearch.bundle.module.min.mjs","module/worker/worker.js"),{type:"module"})};Na.prototype.add=function(a,c,b){ba(a)&&(c=a,a=ca(c,this.key));if(c&&(a||a===0)){if(!b&&this.reg.has(a))return this.update(a,c);for(let k=0,h;kc?a.slice(b,b+c):a,e?Ta.call(this,a):a;let d=[];for(let f=0,g,k;f=k){b-=k;continue}g=g.slice(b,b+c);k=g.length;b=0}k>c&&(g=g.slice(0,c),k=c);if(!d.length&&k>=c)return e?Ta.call(this,g):g;d.push(g);c-=k;if(!c)break}d=d.length>1?[].concat.apply([],d):d[0];return e?Ta.call(this,d):d};function Ua(a,c,b,e){var d=e[0];if(d[0]&&d[0].query)return a[c].apply(a,d);if(!(c!=="and"&&c!=="not"||a.result.length||a.await||d.suggest))return e.length>1&&(d=e[e.length-1]),(e=d.resolve)?a.await||a.result:a;let f=[],g=0,k=0,h,l,m,p,u;for(c=0;c1&&(b=b[c].apply(b,a.slice(1)));return b};X.prototype.or=function(){return Ua(this,"or",Xa,arguments)};function Xa(a,c,b,e,d,f,g){a.length&&(this.result.length&&a.push(this.result),a.length<2?this.result=a[0]:(this.result=Ya(a,c,b,!1,this.h),b=0));d&&(this.await=null);return d?this.resolve(c,b,e,g):this};X.prototype.and=function(){return Ua(this,"and",Za,arguments)};function Za(a,c,b,e,d,f,g){if(!f&&!this.result.length)return d?this.result:this;let k;if(a.length)if(this.result.length&&a.unshift(this.result),a.length<2)this.result=a[0];else{let h=0;for(let l=0,m,p;l1?z.join(" "):z[0];let y;if(z&&C){var D=C.length,J=(da.split? C.replace(da.split,""):C).length-z.length,G="",N=0;for(var O=0;O-1&&(G=(P?C.substring(0,P):"")+g+C.substring(P,P+L)+k+(P+L=h)break}F=za.length*(f.length-2);if(r||t||h&&q.length-F>h)if(F=h+F-u*2,B=E- A,r>0&&(B+=r),t>0&&(B+=t),B<=F)x=r?A-(r>0?r:0):A-((F-B)/2|0),v=t?E+(t>0?t:0):x+F,l||(x>0&&q.charAt(x)!==" "&&q.charAt(x-1)!==" "&&(x=q.indexOf(" ",x),x<0&&(x=0)),v=v.length- 1){if(K>=v.length){B[y+1]=1;K>=x.length&&(F[y+1]=1);continue}D-=u}q=v[K].text;if(L=t&&z[y])if(L>0){if(q.length>L)if(B[y+1]=1,l)q=q.substring(0,L);else continue;(L-=q.length)||(L=-1);z[y]=L}else{B[y+1]=1;continue}if(D+q.length+1<=h)q=" "+q,E[y]+=q;else if(l)U=h-D-1,U>0&&(q=" "+q.substring(0,U),E[y]+=q),B[y+1]=1;else{B[y+1]=1;continue}}else{if(B[y])continue;K-=J;if(A[K]){D-=u;B[y]=1;F[y]=1;continue}if(K<=0){if(K<0){B[y]=1;F[y]=1;continue}D-=u}q=v[K].text;if(L=r&&C[y])if(L>0){if(q.length>L)if(B[y]=1, l)q=q.substring(q.length-L);else continue;(L-=q.length)||(L=-1);C[y]=L}else{B[y]=1;continue}if(D+q.length+1<=h)q+=" ",E[y]=q+E[y];else if(l)U=q.length+1-(h-D),U>=0&&U=x.length-1?Pa=1:K1?Ya(h,b,e,g,f):(h=h[0])&&b&&h.length>b||e?h.slice(e,b+e):h;else{if(ab||e)h=h.slice(e,b+ e)}else{d=[];for(let p=0,u;pe)e-=u.length;else{if(b&&u.length>b||e)u=u.slice(e,b+e),b-=u.length,e&&(e-=u.length);d.push(u);if(!b)break}h=d}}return h} function Ya(a,c,b,e,d){const f=[],g=I();let k;var h=a.length;let l;if(e)for(d=h-1;d>=0;d--){if(l=(e=a[d])&&e.length)for(h=0;h=0;m--){p=a[m];for(let r=0;r1?$a(d,1,0,0,h,l):d[0],C)})}return r?d:new X(d.length>1?$a(d,1,0,0,h,l):d[0],this)}}r||g||!(p=p||this.field)||(M(p)?g=p:(p.constructor===Array&&p.length===1&&(p=p[0]),g=p.field||p.index));p&&p.constructor!==Array&&(p=[p])}p||(p=this.field);let F;v=(this.worker||this.db)&&!e&&[];for(let C=0,z,D,J;C0){if(b&&c>b||e)a=a.slice(e,e+b);d&&(a=Ta.call(this,a))}return a} function Ta(a){if(!this||!this.store)return a;if(this.db)return this.index.get(this.field[0]).db.enrich(a);const c=Array(a.length);for(let b=0,e;b1?b:b[0]}w.append=function(a,c){return this.add(a,c,!0)}; w.update=function(a,c){return this.remove(a).add(a,c)};w.remove=function(a){ba(a)&&(a=ca(a,this.key));for(var c of this.index.values())c.remove(a,!0);if(this.reg.has(a)){if(this.tag&&!this.fastupdate)for(let b of this.tag.values())for(let e of b){c=e[0];const d=e[1],f=d.indexOf(a);f>-1&&(d.length>1?d.splice(f,1):b.delete(c))}this.store&&this.store.delete(a);this.reg.delete(a)}this.cache&&this.cache.remove(a);return this}; w.clear=function(){const a=[];for(const c of this.index.values()){const b=c.clear();b.then&&a.push(b)}if(this.tag)for(const c of this.tag.values())c.clear();this.store&&this.store.clear();this.cache&&this.cache.clear();return a.length?Promise.all(a):this};w.contain=function(a){return this.db?this.index.get(this.field[0]).db.has(a):this.reg.has(a)};w.cleanup=function(){for(const a of this.index.values())a.cleanup();return this}; w.get=function(a){return this.db?this.index.get(this.field[0]).db.enrich(a).then(function(c){return c[0]&&c[0].doc||null}):this.store.get(a)||null};w.set=function(a,c){typeof a==="object"&&(c=a,a=ca(c,this.key));this.store.set(a,c);return this};w.searchCache=la;w.export=jb;w.import=kb;Fa(Na.prototype);function lb(a,c=0){let b=[],e=[];c&&(c=25E4/c*5E3|0);for(const d of a.entries())e.push(d),e.length===c&&(b.push(e),e=[]);e.length&&b.push(e);return b}function mb(a,c){c||(c=new Map);for(let b=0,e;b2?b[0]:"";b=b.length>2?b[2]:b[1];if(this.worker&&e)return this.index.get(e).import(a);if(c){typeof c==="string"&&(c=JSON.parse(c));if(e)return this.index.get(e).import(b,c);switch(b){case "reg":this.fastupdate=!1;this.reg=qb(c,this.reg);for(let d=0,f;d=0&&d.splice(g,1)}}else tb(this.map,a),this.depth&&tb(this.ctx,a);c||this.reg.delete(a)}this.db&&(this.commit_task.push({del:a}),this.M&&ub(this));this.cache&&this.cache.remove(a);return this}; function tb(a,c){let b=0;var e=typeof c==="undefined";if(a.constructor===Array)for(let d=0,f,g,k;d=0){if(f.length>1)return f.splice(g,1),1;delete a[d];if(b)return 1;k=1}else{if(k)return 1;b++}}}else for(let d of a.entries())e=d[0],tb(d[1],c)?b++:a.delete(e);return b};const vb={memory:{resolution:1},performance:{resolution:3,fastupdate:!0,context:{depth:1,resolution:1}},match:{tokenize:"full"},score:{resolution:9,context:{depth:2,resolution:3}}};T.prototype.add=function(a,c,b,e){if(c&&(a||a===0)){if(!e&&!b&&this.reg.has(a))return this.update(a,c);e=this.depth;c=this.encoder.encode(c,!e);const l=c.length;if(l){const m=I(),p=I(),u=this.resolution;for(let r=0;r2){for(let n=1,q,x,v,A;n2){for(let n=0,q;nn;f--){g=t.substring(n,f);q=this.rtl?d-1-n:n;var k=this.score?this.score(c,t,r,g,q):wb(u,l,r,d,q);Y(this,p,g,k,a,b)}break}case "bidirectional":case "reverse":if(d>1){for(k=d-1;k>0;k--){g=t[this.rtl?d-1-k:k]+g;var h=this.score?this.score(c,t,r,g,k):wb(u,l,r,d,k);Y(this,p,g,h,a,b)}g=""}case "forward":if(d>1){for(k=0;k1&&rg;const n=this.score?this.score(c,g,r,t,k-1):wb(d+(l/2>d?0:1),l,r,f-1,k-1);Y(this,m,h?g:t,n,a,b,h?t:g)}}}}this.fastupdate||this.reg.add(a)}}this.db&&(this.commit_task.push(b?{ins:a}:{del:a}),this.M&&ub(this));return this}; function Y(a,c,b,e,d,f,g){let k,h;if(!(k=c[b])||g&&!k[g]){g?(c=k||(c[b]=I()),c[g]=1,h=a.ctx,(k=h.get(g))?h=k:h.set(g,h=a.keystore?new R(a.keystore):new Map)):(h=a.map,c[b]=1);(k=h.get(b))?h=k:h.set(b,h=k=[]);if(f)for(let l=0,m;l1?c+(e||0)<=a?b+(d||0):(a-1)/(c+(e||0))*(b+(d||0))+1|0:0};T.prototype.search=function(a,c,b){b||(c||typeof a!=="object"?typeof c==="object"&&(b=c,c=0):(b=a,a=""));if(b&&b.cache)return b.cache=!1,a=this.searchCache(a,c,b),b.cache=!0,a;let e=[],d,f,g,k=0,h,l,m,p,u;b&&(a=b.query||a,c=b.limit||c,k=b.offset||0,f=b.context,g=b.suggest,u=(h=b.resolve)&&b.enrich,m=b.boost,p=b.resolution,l=this.db&&b.tag);typeof h==="undefined"&&(h=this.resolve);f=this.depth&&f!==!1;let r=this.encoder.encode(a,!f);d=r.length;c=c||(h?100:0);if(d===1)return xb.call(this,r[0],"",c, k,h,u,l);if(d===2&&f&&!g)return xb.call(this,r[1],r[0],c,k,h,u,l);let t=I(),n=0,q;f&&(q=r[0],n=1);p||p===0||(p=q?this.N:this.resolution);if(this.db){if(this.db.search&&(b=this.db.search(this,r,c,k,g,h,u,l),b!==!1))return b;const x=this;return async function(){for(let v,A;n1)h=$a(a,c,b,e,d,f,g);else if(k===1)return g?Sa.call(null,a[0],b,e):new X(a[0],this);return g?h:new X(h,this)} function xb(a,c,b,e,d,f,g){a=yb(this,a,c,b,e,d,f,g);return this.db?a.then(function(k){return d?k||[]:new X(k,this)}):a&&a.length?d?Sa.call(this,a,b,e):new X(a,this):d?[]:new X([],this)}function zb(a,c,b,e){let d=[];if(a&&a.length){if(a.length<=e){c.push(a);return}for(let f=0,g;fb)&&(h=b,b=c,c=h);if(a.db)return a.db.get(c,b,e,d,f,g,k);a=b?(a=a.ctx.get(b))&&a.get(c):a.map.get(c);return a};function T(a,c){if(!this||this.constructor!==T)return new T(a);if(a){var b=M(a)?a:a.preset;b&&(a=Object.assign({},vb[b],a))}else a={};b=a.context;const e=b===!0?{depth:1}:b||{},d=M(a.encoder)?va[a.encoder]:a.encode||a.encoder||{};this.encoder=d.encode?d:typeof d==="object"?new ka(d):{encode:d};this.resolution=a.resolution||9;this.tokenize=b=(b=a.tokenize)&&b!=="default"&&b!=="exact"&&b||"strict";this.depth=b==="strict"&&e.depth||0;this.bidirectional=e.bidirectional!==!1;this.fastupdate=!!a.fastupdate; this.score=a.score||null;(b=a.keystore||0)&&(this.keystore=b);this.map=b?new R(b):new Map;this.ctx=b?new R(b):new Map;this.reg=c||(this.fastupdate?b?new R(b):new Map:b?new S(b):new Set);this.N=e.resolution||3;this.rtl=d.rtl||a.rtl||!1;this.cache=(b=a.cache||null)&&new ma(b);this.resolve=a.resolve!==!1;if(b=a.db)this.db=this.mount(b);this.M=a.commit!==!1;this.commit_task=[];this.commit_timer=null;this.priority=a.priority||4}w=T.prototype; w.mount=function(a){this.commit_timer&&(clearTimeout(this.commit_timer),this.commit_timer=null);return a.mount(this)};w.commit=function(){this.commit_timer&&(clearTimeout(this.commit_timer),this.commit_timer=null);return this.db.commit(this)};w.destroy=function(){this.commit_timer&&(clearTimeout(this.commit_timer),this.commit_timer=null);return this.db.destroy()};function ub(a){a.commit_timer||(a.commit_timer=setTimeout(function(){a.commit_timer=null;a.db.commit(a)},1))} w.clear=function(){this.map.clear();this.ctx.clear();this.reg.clear();this.cache&&this.cache.clear();return this.db?(this.commit_timer&&clearTimeout(this.commit_timer),this.commit_timer=null,this.commit_task=[],this.db.clear()):this};w.append=function(a,c){return this.add(a,c,!0)};w.contain=function(a){return this.db?this.db.has(a):this.reg.has(a)};w.update=function(a,c){const b=this,e=this.remove(a);return e&&e.then?e.then(()=>b.add(a,c)):this.add(a,c)}; w.cleanup=function(){if(!this.fastupdate)return this;tb(this.map);this.depth&&tb(this.ctx);return this};w.searchCache=la;w.export=function(a,c,b=0,e=0){let d,f;switch(e){case 0:d="reg";f=pb(this.reg);break;case 1:d="cfg";f=null;break;case 2:d="map";f=lb(this.map,this.reg.size);break;case 3:d="ctx";f=nb(this.ctx,this.reg.size);break;default:return}return rb.call(this,a,c,d,f,b,e)}; w.import=function(a,c){if(c)switch(typeof c==="string"&&(c=JSON.parse(c)),a=a.split("."),a[a.length-1]==="json"&&a.pop(),a.length===3&&a.shift(),a=a.length>1?a[1]:a[0],a){case "reg":this.fastupdate=!1;this.reg=qb(c,this.reg);break;case "map":this.map=mb(c,this.map);break;case "ctx":this.ctx=ob(c,this.ctx)}}; w.serialize=function(a=!0){let c="",b="",e="";if(this.reg.size){let f;for(var d of this.reg.keys())f||(f=typeof d),c+=(c?",":"")+(f==="string"?'"'+d+'"':d);c="index.reg=new Set(["+c+"]);";b=sb(this.map,f);b="index.map=new Map(["+b+"]);";for(const g of this.ctx.entries()){d=g[0];let k=sb(g[1],f);k="new Map(["+k+"])";k='["'+d+'",'+k+"]";e+=(e?",":"")+k}e="index.ctx=new Map(["+e+"]);"}return a?"function inject(index){"+c+b+e+"}":c+b+e};Fa(T.prototype);const Bb=typeof window!=="undefined"&&(window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB),Cb=["map","ctx","tag","reg","cfg"],Db=I(); function Eb(a,c={}){if(!this||this.constructor!==Eb)return new Eb(a,c);typeof a==="object"&&(c=a,a=a.name);a||console.info("Default storage space was used, because a name was not passed.");this.id="flexsearch"+(a?":"+a.toLowerCase().replace(/[^a-z0-9_\-]/g,""):"");this.field=c.field?c.field.toLowerCase().replace(/[^a-z0-9_\-]/g,""):"";this.type=c.type;this.fastupdate=this.support_tag_search=!1;this.db=null;this.h={}}w=Eb.prototype;w.mount=function(a){if(a.index)return a.mount(this);a.db=this;return this.open()}; w.open=function(){if(this.db)return this.db;let a=this;navigator.storage&&navigator.storage.persist&&navigator.storage.persist();Db[a.id]||(Db[a.id]=[]);Db[a.id].push(a.field);const c=Bb.open(a.id,1);c.onupgradeneeded=function(){const b=a.db=this.result;for(let e=0,d;e=m.length){e-=m.length;continue}const p=b?e+Math.min(m.length-e,b):m.length;for(let u=e;u=f.length)return[];if(!c&&!b)return f;f=f.slice(b,b+c);return e?d.enrich(f):f})}; w.enrich=function(a){typeof a!=="object"&&(a=[a]);const c=this.db.transaction("reg","readonly").objectStore("reg"),b=[];for(let e=0;e=0)if(d=1,k.length>1)k.splice(l,1);else{e[g]=[];break}f+=k.length}if(b)break}f?d&&a.update(e):a.delete();a.continue()} w.remove=function(a){typeof a!=="object"&&(a=[a]);return Promise.all([this.transaction("map","readwrite",function(c){c.openCursor().onsuccess=function(){const b=this.result;b&&Fb(b,a)}}),this.transaction("ctx","readwrite",function(c){c.openCursor().onsuccess=function(){const b=this.result;b&&Fb(b,a)}}),this.transaction("tag","readwrite",function(c){c.openCursor().onsuccess=function(){const b=this.result;b&&Fb(b,a,!0)}}),this.transaction("reg","readwrite",function(c){for(let b=0;b{a.onsuccess=a.oncomplete=function(){c&&c(this.result);c=null;b(this.result)};a.onerror=a.onblocked=e;a=null})};export default {Index:T,Charset:va,Encoder:ka,Document:Na,Worker:La,Resolver:X,IndexedDB:Eb,Language:{}}; export const Index=T;export const Charset=va;export const Encoder=ka;export const Document=Na;export const Worker=La;export const Resolver=X;export const IndexedDB=Eb;export const Language={}; ================================================ FILE: dist/flexsearch.compact.debug.js ================================================ /**! * FlexSearch.js v0.8.214 (Bundle/Debug) * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ (function(self){'use strict'; var B; function J(a, c, b) { const e = typeof b, d = typeof a; if (e !== "undefined") { if (d !== "undefined") { if (b) { if (d === "function" && e === d) { return function(h) { return a(b(h)); }; } c = a.constructor; if (c === b.constructor) { if (c === Array) { return b.concat(a); } if (c === Map) { var f = new Map(b); for (var g of a) { f.set(g[0], g[1]); } return f; } if (c === Set) { g = new Set(b); for (f of a.values()) { g.add(f); } return g; } } } return a; } return b; } return d === "undefined" ? c : a; } function K(a, c) { return typeof a === "undefined" ? c : a; } function M() { return Object.create(null); } function P(a) { return typeof a === "string"; } function Q(a) { return typeof a === "object"; } function aa(a, c) { if (P(c)) { a = a[c]; } else { for (let b = 0; a && b < c.length; b++) { a = a[c[b]]; } } return a; } ;const ba = /[^\p{L}\p{N}]+/u, ea = /(\d{3})/g, fa = /(\D)(\d{3})/g, ha = /(\d{3})(\D)/g, ka = /[\u0300-\u036f]/g; function la(a = {}) { if (!this || this.constructor !== la) { return new la(...arguments); } if (arguments.length) { for (a = 0; a < arguments.length; a++) { this.assign(arguments[a]); } } else { this.assign(a); } } B = la.prototype; B.assign = function(a) { this.normalize = J(a.normalize, !0, this.normalize); let c = a.include, b = c || a.exclude || a.split, e; if (b || b === "") { if (typeof b === "object" && b.constructor !== RegExp) { let d = ""; e = !c; c || (d += "\\p{Z}"); b.letter && (d += "\\p{L}"); b.number && (d += "\\p{N}", e = !!c); b.symbol && (d += "\\p{S}"); b.punctuation && (d += "\\p{P}"); b.control && (d += "\\p{C}"); if (b = b.char) { d += typeof b === "object" ? b.join("") : b; } try { this.split = new RegExp("[" + (c ? "^" : "") + d + "]+", "u"); } catch (f) { console.error("Your split configuration:", b, "is not supported on this platform. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } } else { this.split = b, e = b === !1 || "a1a".split(b).length < 2; } this.numeric = J(a.numeric, e); } else { try { this.split = J(this.split, ba); } catch (d) { console.warn("This platform does not support unicode regex. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } this.numeric = J(a.numeric, J(this.numeric, !0)); } this.prepare = J(a.prepare, null, this.prepare); this.finalize = J(a.finalize, null, this.finalize); b = a.filter; this.filter = typeof b === "function" ? b : J(b && new Set(b), null, this.filter); this.dedupe = J(a.dedupe, !0, this.dedupe); this.matcher = J((b = a.matcher) && new Map(b), null, this.matcher); this.mapper = J((b = a.mapper) && new Map(b), null, this.mapper); this.stemmer = J((b = a.stemmer) && new Map(b), null, this.stemmer); this.replacer = J(a.replacer, null, this.replacer); this.minlength = J(a.minlength, 1, this.minlength); this.maxlength = J(a.maxlength, 1024, this.maxlength); this.rtl = J(a.rtl, !1, this.rtl); if (this.cache = b = J(a.cache, !0, this.cache)) { this.D = null, this.K = typeof b === "number" ? b : 2e5, this.B = new Map(), this.C = new Map(), this.H = this.G = 128; } this.h = ""; this.I = null; this.A = ""; this.J = null; if (this.matcher) { for (const d of this.matcher.keys()) { this.h += (this.h ? "|" : "") + d; } } if (this.stemmer) { for (const d of this.stemmer.keys()) { this.A += (this.A ? "|" : "") + d; } } return this; }; B.addStemmer = function(a, c) { this.stemmer || (this.stemmer = new Map()); this.stemmer.set(a, c); this.A += (this.A ? "|" : "") + a; this.J = null; this.cache && S(this); return this; }; B.addFilter = function(a) { typeof a === "function" ? this.filter = a : (this.filter || (this.filter = new Set()), this.filter.add(a)); this.cache && S(this); return this; }; B.addMapper = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length > 1) { return this.addMatcher(a, c); } this.mapper || (this.mapper = new Map()); this.mapper.set(a, c); this.cache && S(this); return this; }; B.addMatcher = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length < 2 && (this.dedupe || this.mapper)) { return this.addMapper(a, c); } this.matcher || (this.matcher = new Map()); this.matcher.set(a, c); this.h += (this.h ? "|" : "") + a; this.I = null; this.cache && S(this); return this; }; B.addReplacer = function(a, c) { if (typeof a === "string") { return this.addMatcher(a, c); } this.replacer || (this.replacer = []); this.replacer.push(a, c); this.cache && S(this); return this; }; B.encode = function(a, c) { if (this.cache && a.length <= this.G) { if (this.D) { if (this.B.has(a)) { return this.B.get(a); } } else { this.D = setTimeout(S, 50, this); } } this.normalize && (typeof this.normalize === "function" ? a = this.normalize(a) : a = ka ? a.normalize("NFKD").replace(ka, "").toLowerCase() : a.toLowerCase()); this.prepare && (a = this.prepare(a)); this.numeric && a.length > 3 && (a = a.replace(fa, "$1 $2").replace(ha, "$1 $2").replace(ea, "$1 ")); const b = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer); let e = [], d = M(), f, g, h = this.split || this.split === "" ? a.split(this.split) : [a]; for (let m = 0, l, A; m < h.length; m++) { if ((l = A = h[m]) && !(l.length < this.minlength || l.length > this.maxlength)) { if (c) { if (d[l]) { continue; } d[l] = 1; } else { if (f === l) { continue; } f = l; } if (b) { e.push(l); } else { if (!this.filter || (typeof this.filter === "function" ? this.filter(l) : !this.filter.has(l))) { if (this.cache && l.length <= this.H) { if (this.D) { var k = this.C.get(l); if (k || k === "") { k && e.push(k); continue; } } else { this.D = setTimeout(S, 50, this); } } if (this.stemmer) { this.J || (this.J = new RegExp("(?!^)(" + this.A + ")$")); let x; for (; x !== l && l.length > 2;) { x = l, l = l.replace(this.J, u => this.stemmer.get(u)); } } if (l && (this.mapper || this.dedupe && l.length > 1)) { k = ""; for (let x = 0, u = "", p, w; x < l.length; x++) { p = l.charAt(x), p === u && this.dedupe || ((w = this.mapper && this.mapper.get(p)) || w === "" ? w === u && this.dedupe || !(u = w) || (k += w) : k += u = p); } l = k; } this.matcher && l.length > 1 && (this.I || (this.I = new RegExp("(" + this.h + ")", "g")), l = l.replace(this.I, x => this.matcher.get(x))); if (l && this.replacer) { for (k = 0; l && k < this.replacer.length; k += 2) { l = l.replace(this.replacer[k], this.replacer[k + 1]); } } this.cache && A.length <= this.H && (this.C.set(A, l), this.C.size > this.K && (this.C.clear(), this.H = this.H / 1.1 | 0)); if (l) { if (l !== A) { if (c) { if (d[l]) { continue; } d[l] = 1; } else { if (g === l) { continue; } g = l; } } e.push(l); } } } } } this.finalize && (e = this.finalize(e) || e); this.cache && a.length <= this.G && (this.B.set(a, e), this.B.size > this.K && (this.B.clear(), this.G = this.G / 1.1 | 0)); return e; }; function S(a) { a.D = null; a.B.clear(); a.C.clear(); } ;function ma(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : b = a); b && (a = b.query || a, c = b.limit || c); let e = "" + (c || 0); b && (e += (b.offset || 0) + !!b.context + !!b.suggest + (b.resolve !== !1) + (b.resolution || this.resolution) + (b.boost || 0)); a = ("" + a).toLowerCase(); this.cache || (this.cache = new T()); let d = this.cache.get(a + e); if (!d) { const f = b && b.cache; f && (b.cache = !1); d = this.search(a, c, b); f && (b.cache = f); this.cache.set(a + e, d); } return d; } function T(a) { this.limit = a && a !== !0 ? a : 1000; this.cache = new Map(); this.h = ""; } T.prototype.set = function(a, c) { this.cache.set(this.h = a, c); this.cache.size > this.limit && this.cache.delete(this.cache.keys().next().value); }; T.prototype.get = function(a) { const c = this.cache.get(a); c && this.h !== a && (this.cache.delete(a), this.cache.set(this.h = a, c)); return c; }; T.prototype.remove = function(a) { for (const c of this.cache) { const b = c[0]; c[1].includes(a) && this.cache.delete(b); } }; T.prototype.clear = function() { this.cache.clear(); this.h = ""; }; const na = {normalize:!1, numeric:!1, dedupe:!1}; const oa = {}; const pa = new Map([["b", "p"], ["v", "f"], ["w", "f"], ["z", "s"], ["x", "s"], ["d", "t"], ["n", "m"], ["c", "k"], ["g", "k"], ["j", "k"], ["q", "k"], ["i", "e"], ["y", "e"], ["u", "o"]]); const qa = new Map([["ae", "a"], ["oe", "o"], ["sh", "s"], ["kh", "k"], ["th", "t"], ["ph", "f"], ["pf", "f"]]), ra = [/([^aeo])h(.)/g, "$1$2", /([aeo])h([^aeo]|$)/g, "$1$2", /(.)\1+/g, "$1"]; const sa = {a:"", e:"", i:"", o:"", u:"", y:"", b:1, f:1, p:1, v:1, c:2, g:2, j:2, k:2, q:2, s:2, x:2, z:2, "\u00df":2, d:3, t:3, l:4, m:5, n:5, r:6}; var va = {Exact:na, Default:oa, Normalize:oa, LatinBalance:{mapper:pa}, LatinAdvanced:{mapper:pa, matcher:qa, replacer:ra}, LatinExtra:{mapper:pa, replacer:ra.concat([/(?!^)[aeo]/g, ""]), matcher:qa}, LatinSoundex:{dedupe:!1, include:{letter:!0}, finalize:function(a) { for (let b = 0; b < a.length; b++) { var c = a[b]; let e = c.charAt(0), d = sa[e]; for (let f = 1, g; f < c.length && (g = c.charAt(f), g === "h" || g === "w" || !(g = sa[g]) || g === d || (e += g, d = g, e.length !== 4)); f++) { } a[b] = e; } }}, CJK:{split:""}, LatinExact:na, LatinDefault:oa, LatinSimple:oa}; function wa(a) { U.call(a, "add"); U.call(a, "append"); U.call(a, "search"); U.call(a, "update"); U.call(a, "remove"); U.call(a, "searchCache"); } let xa, ya, za; function Aa() { xa = za = 0; } function U(a) { this[a + "Async"] = function() { const c = arguments; var b = c[c.length - 1]; let e; typeof b === "function" && (e = b, delete c[c.length - 1]); xa ? za || (za = Date.now() - ya >= this.priority * this.priority * 3) : (xa = setTimeout(Aa, 0), ya = Date.now()); if (za) { const f = this; return new Promise(g => { setTimeout(function() { g(f[a + "Async"].apply(f, c)); }, 0); }); } const d = this[a].apply(this, c); b = d.then ? d : new Promise(f => f(d)); e && b.then(e); return b; }; } ;X.prototype.add = function(a, c, b) { Q(a) && (c = a, a = aa(c, this.key)); if (c && (a || a === 0)) { if (!b && this.reg.has(a)) { return this.update(a, c); } for (let h = 0, k; h < this.field.length; h++) { k = this.B[h]; var e = this.index.get(this.field[h]); if (typeof k === "function") { var d = k(c); d && e.add(a, d, b, !0); } else { if (d = k.F, !d || d(c)) { k.constructor === String ? k = ["" + k] : P(k) && (k = [k]), Ba(c, k, this.C, 0, e, a, k[0], b); } } } if (this.tag) { for (e = 0; e < this.A.length; e++) { var f = this.A[e], g = this.D[e]; d = this.tag.get(g); let h = M(); if (typeof f === "function") { if (f = f(c), !f) { continue; } } else { const k = f.F; if (k && !k(c)) { continue; } f.constructor === String && (f = "" + f); f = aa(c, f); } if (d && f) { P(f) && (f = [f]); for (let k = 0, m, l; k < f.length; k++) { m = f[k], h[m] || (h[m] = 1, (g = d.get(m)) ? l = g : d.set(m, l = []), b && l.includes(a) || (l.push(a), this.fastupdate && ((g = this.reg.get(a)) ? g.push(l) : this.reg.set(a, [l])))); } } else { d || console.warn("Tag '" + g + "' was not found"); } } } if (this.store && (!b || !this.store.has(a))) { let h; if (this.h) { h = M(); for (let k = 0, m; k < this.h.length; k++) { m = this.h[k]; if ((b = m.F) && !b(c)) { continue; } let l; if (typeof m === "function") { l = m(c); if (!l) { continue; } m = [m.M]; } else if (P(m) || m.constructor === String) { h[m] = c[m]; continue; } Ca(c, h, m, 0, m[0], l); } } this.store.set(a, h || c); } } return this; }; function Ca(a, c, b, e, d, f) { a = a[d]; if (e === b.length - 1) { c[d] = f || a; } else if (a) { if (a.constructor === Array) { for (c = c[d] = Array(a.length), d = 0; d < a.length; d++) { Ca(a, c, b, e, d); } } else { c = c[d] || (c[d] = M()), d = b[++e], Ca(a, c, b, e, d); } } } function Ba(a, c, b, e, d, f, g, h) { if (a = a[g]) { if (e === c.length - 1) { if (a.constructor === Array) { if (b[e]) { for (c = 0; c < a.length; c++) { d.add(f, a[c], !0, !0); } return; } a = a.join(" "); } d.add(f, a, h, !0); } else { if (a.constructor === Array) { for (g = 0; g < a.length; g++) { Ba(a, c, b, e, d, f, g, h); } } else { g = c[++e], Ba(a, c, b, e, d, f, g, h); } } } } ;function Da(a, c, b) { if (!a.length) { return a; } if (a.length === 1) { return a = a[0], a = b || a.length > c ? a.slice(b, b + c) : a; } let e = []; for (let d = 0, f, g; d < a.length; d++) { if ((f = a[d]) && (g = f.length)) { if (b) { if (b >= g) { b -= g; continue; } f = f.slice(b, b + c); g = f.length; b = 0; } g > c && (f = f.slice(0, c), g = c); if (!e.length && g >= c) { return f; } e.push(f); c -= g; if (!c) { break; } } } return e = e.length > 1 ? [].concat.apply([], e) : e[0]; } ;function Ea(a, c, b, e, d) { let f, g, h; typeof d === "string" ? (f = d, d = "") : f = d.template; if (!f) { throw Error('No template pattern was specified by the search option "highlight"'); } g = f.indexOf("$1"); if (g === -1) { throw Error('Invalid highlight template. The replacement pattern "$1" was not found in template: ' + f); } h = f.substring(g + 2); g = f.substring(0, g); let k = d && d.boundary, m = !d || d.clip !== !1, l = d && d.merge && h && g && new RegExp(h + " " + g, "g"); d = d && d.ellipsis; var A = 0; if (typeof d === "object") { var x = d.template; A = x.length - 2; d = d.pattern; } typeof d !== "string" && (d = d === !1 ? "" : "..."); A && (d = x.replace("$1", d)); x = d.length - A; let u, p; typeof k === "object" && (u = k.before, u === 0 && (u = -1), p = k.after, p === 0 && (p = -1), k = k.total || 9e5); A = new Map(); for (let ta = 0, V, Ka, ca; ta < c.length; ta++) { let da; if (e) { da = c, ca = e; } else { var w = c[ta]; ca = w.field; if (!ca) { continue; } da = w.result; } Ka = b.get(ca); V = Ka.encoder; w = A.get(V); typeof w !== "string" && (w = V.encode(a), A.set(V, w)); for (let ia = 0; ia < da.length; ia++) { var n = da[ia].doc; if (!n) { continue; } n = aa(n, ca); if (!n) { continue; } var v = n.trim().split(/\s+/); if (!v.length) { continue; } n = ""; var q = []; let ja = []; var C = -1, z = -1, t = 0; for (var y = 0; y < v.length; y++) { var F = v[y], D = V.encode(F); D = D.length > 1 ? D.join(" ") : D[0]; let r; if (D && F) { var E = F.length, I = (V.split ? F.replace(V.split, "") : F).length - D.length, L = "", R = 0; for (var W = 0; W < w.length; W++) { var N = w[W]; if (N) { var H = N.length; H += I < 0 ? 0 : I; R && H <= R || (N = D.indexOf(N), N > -1 && (L = (N ? F.substring(0, N) : "") + g + F.substring(N, N + H) + h + (N + H < E ? F.substring(N + H) : ""), R = H, r = !0)); } } L && (k && (C < 0 && (C = n.length + (n ? 1 : 0)), z = n.length + (n ? 1 : 0) + L.length, t += E, ja.push(q.length), q.push({match:L})), n += (n ? " " : "") + L); } if (!r) { F = v[y], n += (n ? " " : "") + F, k && q.push({text:F}); } else if (k && t >= k) { break; } } t = ja.length * (f.length - 2); if (u || p || k && n.length - t > k) { if (t = k + t - x * 2, y = z - C, u > 0 && (y += u), p > 0 && (y += p), y <= t) { v = u ? C - (u > 0 ? u : 0) : C - ((t - y) / 2 | 0), q = p ? z + (p > 0 ? p : 0) : v + t, m || (v > 0 && n.charAt(v) !== " " && n.charAt(v - 1) !== " " && (v = n.indexOf(" ", v), v < 0 && (v = 0)), q < n.length && n.charAt(q - 1) !== " " && n.charAt(q) !== " " && (q = n.lastIndexOf(" ", q), q < z ? q = z : ++q)), n = (v ? d : "") + n.substring(v, q) + (q < n.length ? d : ""); } else { z = []; C = {}; t = {}; y = {}; F = {}; D = {}; L = I = E = 0; for (W = R = 1;;) { var O = void 0; for (let r = 0, G; r < ja.length; r++) { G = ja[r]; if (L) { if (I !== L) { if (y[r + 1]) { continue; } G += L; if (C[G]) { E -= x; t[r + 1] = 1; y[r + 1] = 1; continue; } if (G >= q.length - 1) { if (G >= q.length) { y[r + 1] = 1; G >= v.length && (t[r + 1] = 1); continue; } E -= x; } n = q[G].text; if (H = p && D[r]) { if (H > 0) { if (n.length > H) { if (y[r + 1] = 1, m) { n = n.substring(0, H); } else { continue; } } (H -= n.length) || (H = -1); D[r] = H; } else { y[r + 1] = 1; continue; } } if (E + n.length + 1 <= k) { n = " " + n, z[r] += n; } else if (m) { O = k - E - 1, O > 0 && (n = " " + n.substring(0, O), z[r] += n), y[r + 1] = 1; } else { y[r + 1] = 1; continue; } } else { if (y[r]) { continue; } G -= I; if (C[G]) { E -= x; y[r] = 1; t[r] = 1; continue; } if (G <= 0) { if (G < 0) { y[r] = 1; t[r] = 1; continue; } E -= x; } n = q[G].text; if (H = u && F[r]) { if (H > 0) { if (n.length > H) { if (y[r] = 1, m) { n = n.substring(n.length - H); } else { continue; } } (H -= n.length) || (H = -1); F[r] = H; } else { y[r] = 1; continue; } } if (E + n.length + 1 <= k) { n += " ", z[r] = n + z[r]; } else if (m) { O = n.length + 1 - (k - E), O >= 0 && O < n.length && (n = n.substring(O) + " ", z[r] = n + z[r]), y[r] = 1; } else { y[r] = 1; continue; } } } else { n = q[G].match; u && (F[r] = u); p && (D[r] = p); r && E++; let ua; G ? !r && x && (E += x) : (t[r] = 1, y[r] = 1); G >= v.length - 1 ? ua = 1 : G < q.length - 1 && q[G + 1].match ? ua = 1 : x && (E += x); E -= f.length - 2; if (!r || E + n.length <= k) { z[r] = n; } else { O = R = W = t[r] = 0; break; } ua && (t[r + 1] = 1, y[r + 1] = 1); } E += n.length; O = C[G] = 1; } if (O) { I === L ? L++ : I++; } else { I === L ? R = 0 : W = 0; if (!R && !W) { break; } R ? (I++, L = I) : L++; } } n = ""; for (let r = 0, G; r < z.length; r++) { G = (t[r] ? r ? " " : "" : (r && !d ? " " : "") + d) + z[r], n += G; } d && !t[z.length] && (n += d); } } l && (n = n.replace(l, " ")); da[ia].highlight = n; } if (e) { break; } } return c; } ;function Fa(a, c, b, e) { const d = M(), f = []; for (let g = 0, h; g < c.length; g++) { h = c[g]; for (let k = 0; k < h.length; k++) { d[h[k]] = 1; } } for (let g = 0, h; g < a.length; g++) { if (h = a[g], d[h]) { if (e) { e--; } else { if (f.push(h), d[h] = 0, b && --b === 0) { break; } } } } return f; } ;M(); X.prototype.search = function(a, c, b, e) { b || (!c && Q(a) ? (b = a, a = "") : Q(c) && (b = c, c = 0)); let d = []; var f = []; let g; let h, k, m, l; let A = 0, x = !0, u; if (b) { b.constructor === Array && (b = {index:b}); a = b.query || a; var p = b.pluck; var w = b.merge; m = p || b.field || (m = b.index) && (m.index ? null : m); l = this.tag && b.tag; h = b.suggest; x = !0; k = b.cache; this.store && b.highlight && !x ? console.warn("Highlighting results can only be done within a resolver stage (and/or/not/xor) or when calling .resolve({ highlight: ... })") : this.store && b.enrich && !x && console.warn("Enrich results can only be done on a final resolver task or when calling .resolve({ enrich: true })"); u = x && this.store && b.highlight; g = !!u || x && this.store && b.enrich; c = b.limit || c; var n = b.offset || 0; c || (c = x ? 100 : 0); if (l) { l.constructor !== Array && (l = [l]); var v = []; for (let z = 0, t; z < l.length; z++) { t = l[z]; if (P(t)) { throw Error("A tag option can't be a string, instead it needs a { field: tag } format."); } if (t.field && t.tag) { var q = t.tag; if (q.constructor === Array) { for (var C = 0; C < q.length; C++) { v.push(t.field, q[C]); } } else { v.push(t.field, q); } } else { q = Object.keys(t); for (let y = 0, F, D; y < q.length; y++) { if (F = q[y], D = t[F], D.constructor === Array) { for (C = 0; C < D.length; C++) { v.push(F, D[C]); } } else { v.push(F, D); } } } } if (!v.length) { throw Error("Your tag definition within the search options is probably wrong. No valid tags found."); } l = v; if (!a) { f = []; if (v.length) { for (p = 0; p < v.length; p += 2) { w = Ga.call(this, v[p], v[p + 1], c, n, g), d.push({field:v[p], tag:v[p + 1], result:w}); } } return f.length ? Promise.all(f).then(function(z) { for (let t = 0; t < z.length; t++) { d[t].result = z[t]; } return d; }) : d; } } m && m.constructor !== Array && (m = [m]); } m || (m = this.field); v = !1; for (let z = 0, t, y, F; z < m.length; z++) { y = m[z]; let D; P(y) || (D = y, y = D.field, a = D.query || a, c = K(D.limit, c), n = K(D.offset, n), h = K(D.suggest, h), u = x && this.store && K(D.highlight, u), g = !!u || x && this.store && K(D.enrich, g), k = K(D.cache, k)); if (e) { t = e[z]; } else { q = D || b || {}; C = q.enrich; const E = this.index.get(y); l && (C && (q.enrich = !1), q.limit = 0, q.offset = 0); t = k ? E.searchCache(a, l ? 0 : c, q) : E.search(a, l ? 0 : c, q); l && (q.limit = c, q.offset = n); C && (q.enrich = C); if (v) { v[z] = t; continue; } } F = (t = t.result || t) && t.length; if (l && F) { q = []; C = 0; for (let E = 0, I, L; E < l.length; E += 2) { I = this.tag.get(l[E]); if (!I) { if (console.warn("Tag '" + l[E] + ":" + l[E + 1] + "' will be skipped because there is no field '" + l[E] + "'."), h) { continue; } else { return d; } } if (L = (I = I && I.get(l[E + 1])) && I.length) { C++, q.push(I); } else if (!h) { return d; } } if (C) { t = Fa(t, q, c, n); F = t.length; if (!F && !h) { return t; } C--; } } if (F) { f[A] = y, d.push(t), A++; } else if (m.length === 1) { return d; } } if (v) { const z = this; return Promise.all(v).then(function(t) { b && (b.resolve = x); t.length && (t = z.search(a, c, b, t)); return t; }); } if (!A) { return d; } if (p && (!g || !this.store)) { return d = d[0]; } v = []; for (n = 0; n < f.length; n++) { e = d[n]; g && e.length && typeof e[0].doc === "undefined" && (e = Ha.call(this, e)); if (p) { return u ? Ea(a, e, this.index, p, u) : e; } d[n] = {field:f[n], result:e}; } u && (d = Ea(a, d, this.index, p, u)); return w ? Ia(d) : d; }; function Ia(a) { const c = [], b = M(), e = M(); for (let d = 0, f, g, h, k, m, l, A; d < a.length; d++) { f = a[d]; g = f.field; h = f.result; for (let x = 0; x < h.length; x++) { if (m = h[x], typeof m !== "object" ? m = {id:k = m} : k = m.id, (l = b[k]) ? l.push(g) : (m.field = b[k] = [g], c.push(m)), A = m.highlight) { l = e[k], l || (e[k] = l = {}, m.highlight = l), l[g] = A; } } } return c; } function Ga(a, c, b, e, d) { a = this.tag.get(a); if (!a) { return []; } a = a.get(c); if (!a) { return []; } c = a.length - e; if (c > 0) { if (b && c > b || e) { a = a.slice(e, e + b); } d && (a = Ha.call(this, a)); } return a; } function Ha(a) { if (!this || !this.store) { return a; } const c = Array(a.length); for (let b = 0, e; b < a.length; b++) { e = a[b], c[b] = {id:e, doc:this.store.get(e)}; } return c; } ;function X(a) { if (!this || this.constructor !== X) { return new X(a); } const c = a.document || a.doc || a; var b; this.B = []; this.field = []; this.C = []; this.key = (b = c.key || c.id) && Ja(b, this.C) || "id"; this.reg = (this.fastupdate = !!a.fastupdate) ? new Map() : new Set(); this.h = (b = c.store || null) && b && b !== !0 && []; this.store = b ? new Map() : null; this.cache = (b = a.cache || null) && new T(b); a.cache = !1; this.priority = a.priority || 4; b = new Map(); let e = c.index || c.field || c; P(e) && (e = [e]); for (let d = 0, f, g; d < e.length; d++) { f = e[d], P(f) || (g = f, f = f.field), g = Q(g) ? Object.assign({}, a, g) : a, b.set(f, new Y(g, this.reg)), g.custom ? this.B[d] = g.custom : (this.B[d] = Ja(f, this.C), g.filter && (typeof this.B[d] === "string" && (this.B[d] = new String(this.B[d])), this.B[d].F = g.filter)), this.field[d] = f; } if (this.h) { a = c.store; P(a) && (a = [a]); for (let d = 0, f, g; d < a.length; d++) { f = a[d], g = f.field || f, f.custom ? (this.h[d] = f.custom, f.custom.M = g) : (this.h[d] = Ja(g, this.C), f.filter && (typeof this.h[d] === "string" && (this.h[d] = new String(this.h[d])), this.h[d].F = f.filter)); } } this.index = b; this.tag = null; if (b = c.tag) { if (typeof b === "string" && (b = [b]), b.length) { this.tag = new Map(); this.A = []; this.D = []; for (let d = 0, f, g; d < b.length; d++) { f = b[d]; g = f.field || f; if (!g) { throw Error("The tag field from the document descriptor is undefined."); } f.custom ? this.A[d] = f.custom : (this.A[d] = Ja(g, this.C), f.filter && (typeof this.A[d] === "string" && (this.A[d] = new String(this.A[d])), this.A[d].F = f.filter)); this.D[d] = g; this.tag.set(g, new Map()); } } } } function Ja(a, c) { const b = a.split(":"); let e = 0; for (let d = 0; d < b.length; d++) { a = b[d], a[a.length - 1] === "]" && (a = a.substring(0, a.length - 2)) && (c[e] = !0), a && (b[e++] = a); } e < b.length && (b.length = e); return e > 1 ? b : b[0]; } B = X.prototype; B.append = function(a, c) { return this.add(a, c, !0); }; B.update = function(a, c) { return this.remove(a).add(a, c); }; B.remove = function(a) { Q(a) && (a = aa(a, this.key)); for (var c of this.index.values()) { c.remove(a, !0); } if (this.reg.has(a)) { if (this.tag && !this.fastupdate) { for (let b of this.tag.values()) { for (let e of b) { c = e[0]; const d = e[1], f = d.indexOf(a); f > -1 && (d.length > 1 ? d.splice(f, 1) : b.delete(c)); } } } this.store && this.store.delete(a); this.reg.delete(a); } this.cache && this.cache.remove(a); return this; }; B.clear = function() { const a = []; for (const c of this.index.values()) { const b = c.clear(); b.then && a.push(b); } if (this.tag) { for (const c of this.tag.values()) { c.clear(); } } this.store && this.store.clear(); this.cache && this.cache.clear(); return a.length ? Promise.all(a) : this; }; B.contain = function(a) { return this.reg.has(a); }; B.cleanup = function() { for (const a of this.index.values()) { a.cleanup(); } return this; }; B.get = function(a) { return this.store.get(a) || null; }; B.set = function(a, c) { typeof a === "object" && (c = a, a = aa(c, this.key)); this.store.set(a, c); return this; }; B.searchCache = ma; B.export = La; B.import = Ma; wa(X.prototype); function Na(a, c = 0) { let b = [], e = []; c && (c = 250000 / c * 5000 | 0); for (const d of a.entries()) { e.push(d), e.length === c && (b.push(e), e = []); } e.length && b.push(e); return b; } function Oa(a, c) { c || (c = new Map()); for (let b = 0, e; b < a.length; b++) { e = a[b], c.set(e[0], e[1]); } return c; } function Pa(a, c = 0) { let b = [], e = []; c && (c = 250000 / c * 1000 | 0); for (const d of a.entries()) { e.push([d[0], Na(d[1])[0] || []]), e.length === c && (b.push(e), e = []); } e.length && b.push(e); return b; } function Qa(a, c) { c || (c = new Map()); for (let b = 0, e, d; b < a.length; b++) { e = a[b], d = c.get(e[0]), c.set(e[0], Oa(e[1], d)); } return c; } function Ra(a) { let c = [], b = []; for (const e of a.keys()) { b.push(e), b.length === 250000 && (c.push(b), b = []); } b.length && c.push(b); return c; } function Sa(a, c) { c || (c = new Set()); for (let b = 0; b < a.length; b++) { c.add(a[b]); } return c; } function Ta(a, c, b, e, d, f, g = 0) { const h = e && e.constructor === Array; var k = h ? e.shift() : e; if (!k) { return this.export(a, c, d, f + 1); } if ((k = a((c ? c + "." : "") + (g + 1) + "." + b, JSON.stringify(k))) && k.then) { const m = this; return k.then(function() { return Ta.call(m, a, c, b, h ? e : null, d, f, g + 1); }); } return Ta.call(this, a, c, b, h ? e : null, d, f, g + 1); } function La(a, c, b = 0, e = 0) { if (b < this.field.length) { const g = this.field[b]; if ((c = this.index.get(g).export(a, g, b, e = 1)) && c.then) { const h = this; return c.then(function() { return h.export(a, g, b + 1); }); } return this.export(a, g, b + 1); } let d, f; switch(e) { case 0: d = "reg"; f = Ra(this.reg); c = null; break; case 1: d = "tag"; f = this.tag && Pa(this.tag, this.reg.size); c = null; break; case 2: d = "doc"; f = this.store && Na(this.store); c = null; break; default: return; } return Ta.call(this, a, c, d, f || null, b, e); } function Ma(a, c) { var b = a.split("."); b[b.length - 1] === "json" && b.pop(); a = b.length > 2 ? b[0] : ""; b = b.length > 2 ? b[2] : b[1]; if (c) { typeof c === "string" && (c = JSON.parse(c)); if (a) { return this.index.get(a).import(b, c); } switch(b) { case "reg": this.fastupdate = !1; this.reg = Sa(c, this.reg); for (let e = 0, d; e < this.field.length; e++) { d = this.index.get(this.field[e]), d.fastupdate = !1, d.reg = this.reg; } break; case "tag": this.tag = Qa(c, this.tag); break; case "doc": this.store = Oa(c, this.store); } } } function Ua(a, c) { let b = ""; for (const e of a.entries()) { a = e[0]; const d = e[1]; let f = ""; for (let g = 0, h; g < d.length; g++) { h = d[g] || [""]; let k = ""; for (let m = 0; m < h.length; m++) { k += (k ? "," : "") + (c === "string" ? '"' + h[m] + '"' : h[m]); } k = "[" + k + "]"; f += (f ? "," : "") + k; } f = '["' + a + '",[' + f + "]]"; b += (b ? "," : "") + f; } return b; } ;Y.prototype.remove = function(a, c) { const b = this.reg.size && (this.fastupdate ? this.reg.get(a) : this.reg.has(a)); if (b) { if (this.fastupdate) { for (let e = 0, d, f; e < b.length; e++) { if ((d = b[e]) && (f = d.length)) { if (d[f - 1] === a) { d.pop(); } else { const g = d.indexOf(a); g >= 0 && d.splice(g, 1); } } } } else { Va(this.map, a), this.depth && Va(this.ctx, a); } c || this.reg.delete(a); } this.cache && this.cache.remove(a); return this; }; function Va(a, c) { let b = 0; var e = typeof c === "undefined"; if (a.constructor === Array) { for (let d = 0, f, g, h; d < a.length; d++) { if ((f = a[d]) && f.length) { if (e) { return 1; } g = f.indexOf(c); if (g >= 0) { if (f.length > 1) { return f.splice(g, 1), 1; } delete a[d]; if (b) { return 1; } h = 1; } else { if (h) { return 1; } b++; } } } } else { for (let d of a.entries()) { e = d[0], Va(d[1], c) ? b++ : a.delete(e); } } return b; } ;const Wa = {memory:{resolution:1}, performance:{resolution:3, fastupdate:!0, context:{depth:1, resolution:1}}, match:{tokenize:"full"}, score:{resolution:9, context:{depth:2, resolution:3}}}; Y.prototype.add = function(a, c, b, e) { if (c && (a || a === 0)) { if (!e && !b && this.reg.has(a)) { return this.update(a, c); } e = this.depth; c = this.encoder.encode(c, !e); const m = c.length; if (m) { const l = M(), A = M(), x = this.resolution; for (let u = 0; u < m; u++) { let p = c[this.rtl ? m - 1 - u : u]; var d = p.length; if (d && (e || !A[p])) { var f = this.score ? this.score(c, p, u, null, 0) : Xa(x, m, u), g = ""; switch(this.tokenize) { case "tolerant": Z(this, A, p, f, a, b); if (d > 2) { for (let w = 1, n, v, q, C; w < d - 1; w++) { n = p.charAt(w), v = p.charAt(w + 1), q = p.substring(0, w) + v, C = p.substring(w + 2), g = q + n + C, Z(this, A, g, f, a, b), g = q + C, Z(this, A, g, f, a, b); } Z(this, A, p.substring(0, p.length - 1), f, a, b); } break; case "full": if (d > 2) { for (let w = 0, n; w < d; w++) { for (f = d; f > w; f--) { g = p.substring(w, f); n = this.rtl ? d - 1 - w : w; var h = this.score ? this.score(c, p, u, g, n) : Xa(x, m, u, d, n); Z(this, A, g, h, a, b); } } break; } case "bidirectional": case "reverse": if (d > 1) { for (h = d - 1; h > 0; h--) { g = p[this.rtl ? d - 1 - h : h] + g; var k = this.score ? this.score(c, p, u, g, h) : Xa(x, m, u, d, h); Z(this, A, g, k, a, b); } g = ""; } case "forward": if (d > 1) { for (h = 0; h < d; h++) { g += p[this.rtl ? d - 1 - h : h], Z(this, A, g, f, a, b); } break; } default: if (Z(this, A, p, f, a, b), e && m > 1 && u < m - 1) { for (d = this.L, g = p, f = Math.min(e + 1, this.rtl ? u + 1 : m - u), h = 1; h < f; h++) { p = c[this.rtl ? m - 1 - u - h : u + h]; k = this.bidirectional && p > g; const w = this.score ? this.score(c, g, u, p, h - 1) : Xa(d + (m / 2 > d ? 0 : 1), m, u, f - 1, h - 1); Z(this, l, k ? g : p, w, a, b, k ? p : g); } } } } } this.fastupdate || this.reg.add(a); } } return this; }; function Z(a, c, b, e, d, f, g) { let h, k; if (!(h = c[b]) || g && !h[g]) { g ? (c = h || (c[b] = M()), c[g] = 1, k = a.ctx, (h = k.get(g)) ? k = h : k.set(g, k = new Map())) : (k = a.map, c[b] = 1); (h = k.get(b)) ? k = h : k.set(b, k = h = []); if (f) { for (let m = 0, l; m < h.length; m++) { if ((l = h[m]) && l.includes(d)) { if (m <= e) { return; } l.splice(l.indexOf(d), 1); a.fastupdate && (c = a.reg.get(d)) && c.splice(c.indexOf(l), 1); break; } } } k = k[e] || (k[e] = []); k.push(d); a.fastupdate && ((e = a.reg.get(d)) ? e.push(k) : a.reg.set(d, [k])); } } function Xa(a, c, b, e, d) { return b && a > 1 ? c + (e || 0) <= a ? b + (d || 0) : (a - 1) / (c + (e || 0)) * (b + (d || 0)) + 1 | 0 : 0; } ;Y.prototype.search = function(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : (b = a, a = "")); if (b && b.cache) { return b.cache = !1, c = this.searchCache(a, c, b), b.cache = !0, c; } var e = [], d = 0; if (b) { a = b.query || a; c = b.limit || c; d = b.offset || 0; var f = b.context; var g = b.suggest; var h = !0; var k = b.resolution; } typeof h === "undefined" && (h = !0); f = this.depth && f !== !1; b = this.encoder.encode(a, !f); a = b.length; c = c || (h ? 100 : 0); if (a === 1) { return g = d, (d = Ya(this, b[0], "")) && d.length ? Da.call(this, d, c, g) : []; } if (a === 2 && f && !g) { return g = d, (d = Ya(this, b[1], b[0])) && d.length ? Da.call(this, d, c, g) : []; } h = M(); var m = 0; if (f) { var l = b[0]; m = 1; } k || k === 0 || (k = l ? this.L : this.resolution); for (let p, w; m < a; m++) { if ((w = b[m]) && !h[w]) { h[w] = 1; p = Ya(this, w, l); a: { f = p; var A = e, x = g, u = k; let n = []; if (f && f.length) { if (f.length <= u) { A.push(f); p = void 0; break a; } for (let v = 0, q; v < u; v++) { if (q = f[v]) { n[v] = q; } } if (n.length) { A.push(n); p = void 0; break a; } } p = x ? void 0 : n; } if (p) { e = p; break; } l && (g && p && e.length || (l = w)); } g && l && m === a - 1 && !e.length && (k = this.resolution, l = "", m = -1, h = M()); } a: { b = e; e = b.length; l = b; if (e > 1) { b: { e = g; l = b.length; g = []; a = M(); for (let p = 0, w, n, v, q; p < k; p++) { for (m = 0; m < l; m++) { if (v = b[m], p < v.length && (w = v[p])) { for (f = 0; f < w.length; f++) { if (n = w[f], (h = a[n]) ? a[n]++ : (h = 0, a[n] = 1), q = g[h] || (g[h] = []), q.push(n), c && h === l - 1 && q.length - d === c) { l = d ? q.slice(d) : q; break b; } } } } } if (b = g.length) { if (e) { if (g.length > 1) { c: { for (b = [], k = M(), e = g.length, h = e - 1; h >= 0; h--) { if (a = (e = g[h]) && e.length) { for (m = 0; m < a; m++) { if (l = e[m], !k[l]) { if (k[l] = 1, d) { d--; } else { if (b.push(l), b.length === c) { break c; } } } } } } } } else { b = (g = g[0]) && c && g.length > c || d ? g.slice(d, c + d) : g; } g = b; } else { if (b < l) { l = []; break b; } g = g[b - 1]; if (c || d) { if (g.length > c || d) { g = g.slice(d, c + d); } } } } l = g; } } else if (e === 1) { c = Da.call(null, b[0], c, d); break a; } c = l; } return c; }; function Ya(a, c, b) { let e; b && (e = a.bidirectional && c > b) && (e = b, b = c, c = e); a = b ? (a = a.ctx.get(b)) && a.get(c) : a.map.get(c); return a; } ;function Y(a, c) { if (!this || this.constructor !== Y) { return new Y(a); } if (a) { var b = P(a) ? a : a.preset; b && (Wa[b] || console.warn("Preset not found: " + b), a = Object.assign({}, Wa[b], a)); } else { a = {}; } b = a.context; const e = b === !0 ? {depth:1} : b || {}, d = P(a.encoder) ? va[a.encoder] : a.encode || a.encoder || {}; this.encoder = d.encode ? d : typeof d === "object" ? new la(d) : {encode:d}; this.resolution = a.resolution || 9; this.tokenize = b = (b = a.tokenize) && b !== "default" && b !== "exact" && b || "strict"; this.depth = b === "strict" && e.depth || 0; this.bidirectional = e.bidirectional !== !1; this.fastupdate = !!a.fastupdate; this.score = a.score || null; e && e.depth && this.tokenize !== "strict" && console.warn('Context-Search could not applied, because it is just supported when using the tokenizer "strict".'); b = !1; this.map = new Map(); this.ctx = new Map(); this.reg = c || (this.fastupdate ? new Map() : new Set()); this.L = e.resolution || 3; this.rtl = d.rtl || a.rtl || !1; this.cache = (b = a.cache || null) && new T(b); this.priority = a.priority || 4; } B = Y.prototype; B.clear = function() { this.map.clear(); this.ctx.clear(); this.reg.clear(); this.cache && this.cache.clear(); return this; }; B.append = function(a, c) { return this.add(a, c, !0); }; B.contain = function(a) { return this.reg.has(a); }; B.update = function(a, c) { const b = this, e = this.remove(a); return e && e.then ? e.then(() => b.add(a, c)) : this.add(a, c); }; B.cleanup = function() { if (!this.fastupdate) { return console.info('Cleanup the index isn\'t required when not using "fastupdate".'), this; } Va(this.map); this.depth && Va(this.ctx); return this; }; B.searchCache = ma; B.export = function(a, c, b = 0, e = 0) { let d, f; switch(e) { case 0: d = "reg"; f = Ra(this.reg); break; case 1: d = "cfg"; f = null; break; case 2: d = "map"; f = Na(this.map, this.reg.size); break; case 3: d = "ctx"; f = Pa(this.ctx, this.reg.size); break; default: return; } return Ta.call(this, a, c, d, f, b, e); }; B.import = function(a, c) { if (c) { switch(typeof c === "string" && (c = JSON.parse(c)), a = a.split("."), a[a.length - 1] === "json" && a.pop(), a.length === 3 && a.shift(), a = a.length > 1 ? a[1] : a[0], a) { case "reg": this.fastupdate = !1; this.reg = Sa(c, this.reg); break; case "map": this.map = Oa(c, this.map); break; case "ctx": this.ctx = Qa(c, this.ctx); } } }; B.serialize = function(a = !0) { let c = "", b = "", e = ""; if (this.reg.size) { let f; for (var d of this.reg.keys()) { f || (f = typeof d), c += (c ? "," : "") + (f === "string" ? '"' + d + '"' : d); } c = "index.reg=new Set([" + c + "]);"; b = Ua(this.map, f); b = "index.map=new Map([" + b + "]);"; for (const g of this.ctx.entries()) { d = g[0]; let h = Ua(g[1], f); h = "new Map([" + h + "])"; h = '["' + d + '",' + h + "]"; e += (e ? "," : "") + h; } e = "index.ctx=new Map([" + e + "]);"; } return a ? "function inject(index){" + c + b + e + "}" : c + b + e; }; wa(Y.prototype); M(); const Za = {Index:Y, Charset:va, Encoder:la, Document:X, Worker:null, Resolver:null, IndexedDB:null, Language:{}}, $a = typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : self; let ab; (ab = $a.define) && ab.amd ? ab([], function() { return Za; }) : typeof $a.exports === "object" ? $a.exports = Za : $a.FlexSearch = Za; }(this||self)); ================================================ FILE: dist/flexsearch.compact.module.debug.js ================================================ /**! * FlexSearch.js v0.8.214 (Bundle/Debug) * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ var B; function J(a, c, b) { const e = typeof b, d = typeof a; if (e !== "undefined") { if (d !== "undefined") { if (b) { if (d === "function" && e === d) { return function(h) { return a(b(h)); }; } c = a.constructor; if (c === b.constructor) { if (c === Array) { return b.concat(a); } if (c === Map) { var f = new Map(b); for (var g of a) { f.set(g[0], g[1]); } return f; } if (c === Set) { g = new Set(b); for (f of a.values()) { g.add(f); } return g; } } } return a; } return b; } return d === "undefined" ? c : a; } function K(a, c) { return typeof a === "undefined" ? c : a; } function M() { return Object.create(null); } function P(a) { return typeof a === "string"; } function Q(a) { return typeof a === "object"; } function aa(a, c) { if (P(c)) { a = a[c]; } else { for (let b = 0; a && b < c.length; b++) { a = a[c[b]]; } } return a; } ;const ba = /[^\p{L}\p{N}]+/u, ea = /(\d{3})/g, fa = /(\D)(\d{3})/g, ha = /(\d{3})(\D)/g, ka = /[\u0300-\u036f]/g; function la(a = {}) { if (!this || this.constructor !== la) { return new la(...arguments); } if (arguments.length) { for (a = 0; a < arguments.length; a++) { this.assign(arguments[a]); } } else { this.assign(a); } } B = la.prototype; B.assign = function(a) { this.normalize = J(a.normalize, !0, this.normalize); let c = a.include, b = c || a.exclude || a.split, e; if (b || b === "") { if (typeof b === "object" && b.constructor !== RegExp) { let d = ""; e = !c; c || (d += "\\p{Z}"); b.letter && (d += "\\p{L}"); b.number && (d += "\\p{N}", e = !!c); b.symbol && (d += "\\p{S}"); b.punctuation && (d += "\\p{P}"); b.control && (d += "\\p{C}"); if (b = b.char) { d += typeof b === "object" ? b.join("") : b; } try { this.split = new RegExp("[" + (c ? "^" : "") + d + "]+", "u"); } catch (f) { console.error("Your split configuration:", b, "is not supported on this platform. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } } else { this.split = b, e = b === !1 || "a1a".split(b).length < 2; } this.numeric = J(a.numeric, e); } else { try { this.split = J(this.split, ba); } catch (d) { console.warn("This platform does not support unicode regex. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } this.numeric = J(a.numeric, J(this.numeric, !0)); } this.prepare = J(a.prepare, null, this.prepare); this.finalize = J(a.finalize, null, this.finalize); b = a.filter; this.filter = typeof b === "function" ? b : J(b && new Set(b), null, this.filter); this.dedupe = J(a.dedupe, !0, this.dedupe); this.matcher = J((b = a.matcher) && new Map(b), null, this.matcher); this.mapper = J((b = a.mapper) && new Map(b), null, this.mapper); this.stemmer = J((b = a.stemmer) && new Map(b), null, this.stemmer); this.replacer = J(a.replacer, null, this.replacer); this.minlength = J(a.minlength, 1, this.minlength); this.maxlength = J(a.maxlength, 1024, this.maxlength); this.rtl = J(a.rtl, !1, this.rtl); if (this.cache = b = J(a.cache, !0, this.cache)) { this.D = null, this.K = typeof b === "number" ? b : 2e5, this.B = new Map(), this.C = new Map(), this.H = this.G = 128; } this.h = ""; this.I = null; this.A = ""; this.J = null; if (this.matcher) { for (const d of this.matcher.keys()) { this.h += (this.h ? "|" : "") + d; } } if (this.stemmer) { for (const d of this.stemmer.keys()) { this.A += (this.A ? "|" : "") + d; } } return this; }; B.addStemmer = function(a, c) { this.stemmer || (this.stemmer = new Map()); this.stemmer.set(a, c); this.A += (this.A ? "|" : "") + a; this.J = null; this.cache && S(this); return this; }; B.addFilter = function(a) { typeof a === "function" ? this.filter = a : (this.filter || (this.filter = new Set()), this.filter.add(a)); this.cache && S(this); return this; }; B.addMapper = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length > 1) { return this.addMatcher(a, c); } this.mapper || (this.mapper = new Map()); this.mapper.set(a, c); this.cache && S(this); return this; }; B.addMatcher = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length < 2 && (this.dedupe || this.mapper)) { return this.addMapper(a, c); } this.matcher || (this.matcher = new Map()); this.matcher.set(a, c); this.h += (this.h ? "|" : "") + a; this.I = null; this.cache && S(this); return this; }; B.addReplacer = function(a, c) { if (typeof a === "string") { return this.addMatcher(a, c); } this.replacer || (this.replacer = []); this.replacer.push(a, c); this.cache && S(this); return this; }; B.encode = function(a, c) { if (this.cache && a.length <= this.G) { if (this.D) { if (this.B.has(a)) { return this.B.get(a); } } else { this.D = setTimeout(S, 50, this); } } this.normalize && (typeof this.normalize === "function" ? a = this.normalize(a) : a = ka ? a.normalize("NFKD").replace(ka, "").toLowerCase() : a.toLowerCase()); this.prepare && (a = this.prepare(a)); this.numeric && a.length > 3 && (a = a.replace(fa, "$1 $2").replace(ha, "$1 $2").replace(ea, "$1 ")); const b = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer); let e = [], d = M(), f, g, h = this.split || this.split === "" ? a.split(this.split) : [a]; for (let m = 0, l, A; m < h.length; m++) { if ((l = A = h[m]) && !(l.length < this.minlength || l.length > this.maxlength)) { if (c) { if (d[l]) { continue; } d[l] = 1; } else { if (f === l) { continue; } f = l; } if (b) { e.push(l); } else { if (!this.filter || (typeof this.filter === "function" ? this.filter(l) : !this.filter.has(l))) { if (this.cache && l.length <= this.H) { if (this.D) { var k = this.C.get(l); if (k || k === "") { k && e.push(k); continue; } } else { this.D = setTimeout(S, 50, this); } } if (this.stemmer) { this.J || (this.J = new RegExp("(?!^)(" + this.A + ")$")); let x; for (; x !== l && l.length > 2;) { x = l, l = l.replace(this.J, u => this.stemmer.get(u)); } } if (l && (this.mapper || this.dedupe && l.length > 1)) { k = ""; for (let x = 0, u = "", p, w; x < l.length; x++) { p = l.charAt(x), p === u && this.dedupe || ((w = this.mapper && this.mapper.get(p)) || w === "" ? w === u && this.dedupe || !(u = w) || (k += w) : k += u = p); } l = k; } this.matcher && l.length > 1 && (this.I || (this.I = new RegExp("(" + this.h + ")", "g")), l = l.replace(this.I, x => this.matcher.get(x))); if (l && this.replacer) { for (k = 0; l && k < this.replacer.length; k += 2) { l = l.replace(this.replacer[k], this.replacer[k + 1]); } } this.cache && A.length <= this.H && (this.C.set(A, l), this.C.size > this.K && (this.C.clear(), this.H = this.H / 1.1 | 0)); if (l) { if (l !== A) { if (c) { if (d[l]) { continue; } d[l] = 1; } else { if (g === l) { continue; } g = l; } } e.push(l); } } } } } this.finalize && (e = this.finalize(e) || e); this.cache && a.length <= this.G && (this.B.set(a, e), this.B.size > this.K && (this.B.clear(), this.G = this.G / 1.1 | 0)); return e; }; function S(a) { a.D = null; a.B.clear(); a.C.clear(); } ;function ma(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : b = a); b && (a = b.query || a, c = b.limit || c); let e = "" + (c || 0); b && (e += (b.offset || 0) + !!b.context + !!b.suggest + (b.resolve !== !1) + (b.resolution || this.resolution) + (b.boost || 0)); a = ("" + a).toLowerCase(); this.cache || (this.cache = new T()); let d = this.cache.get(a + e); if (!d) { const f = b && b.cache; f && (b.cache = !1); d = this.search(a, c, b); f && (b.cache = f); this.cache.set(a + e, d); } return d; } function T(a) { this.limit = a && a !== !0 ? a : 1000; this.cache = new Map(); this.h = ""; } T.prototype.set = function(a, c) { this.cache.set(this.h = a, c); this.cache.size > this.limit && this.cache.delete(this.cache.keys().next().value); }; T.prototype.get = function(a) { const c = this.cache.get(a); c && this.h !== a && (this.cache.delete(a), this.cache.set(this.h = a, c)); return c; }; T.prototype.remove = function(a) { for (const c of this.cache) { const b = c[0]; c[1].includes(a) && this.cache.delete(b); } }; T.prototype.clear = function() { this.cache.clear(); this.h = ""; }; const na = {normalize:!1, numeric:!1, dedupe:!1}; const oa = {}; const pa = new Map([["b", "p"], ["v", "f"], ["w", "f"], ["z", "s"], ["x", "s"], ["d", "t"], ["n", "m"], ["c", "k"], ["g", "k"], ["j", "k"], ["q", "k"], ["i", "e"], ["y", "e"], ["u", "o"]]); const qa = new Map([["ae", "a"], ["oe", "o"], ["sh", "s"], ["kh", "k"], ["th", "t"], ["ph", "f"], ["pf", "f"]]), ra = [/([^aeo])h(.)/g, "$1$2", /([aeo])h([^aeo]|$)/g, "$1$2", /(.)\1+/g, "$1"]; const ua = {a:"", e:"", i:"", o:"", u:"", y:"", b:1, f:1, p:1, v:1, c:2, g:2, j:2, k:2, q:2, s:2, x:2, z:2, "\u00df":2, d:3, t:3, l:4, m:5, n:5, r:6}; var va = {Exact:na, Default:oa, Normalize:oa, LatinBalance:{mapper:pa}, LatinAdvanced:{mapper:pa, matcher:qa, replacer:ra}, LatinExtra:{mapper:pa, replacer:ra.concat([/(?!^)[aeo]/g, ""]), matcher:qa}, LatinSoundex:{dedupe:!1, include:{letter:!0}, finalize:function(a) { for (let b = 0; b < a.length; b++) { var c = a[b]; let e = c.charAt(0), d = ua[e]; for (let f = 1, g; f < c.length && (g = c.charAt(f), g === "h" || g === "w" || !(g = ua[g]) || g === d || (e += g, d = g, e.length !== 4)); f++) { } a[b] = e; } }}, CJK:{split:""}, LatinExact:na, LatinDefault:oa, LatinSimple:oa}; function wa(a) { U.call(a, "add"); U.call(a, "append"); U.call(a, "search"); U.call(a, "update"); U.call(a, "remove"); U.call(a, "searchCache"); } let xa, ya, za; function Aa() { xa = za = 0; } function U(a) { this[a + "Async"] = function() { const c = arguments; var b = c[c.length - 1]; let e; typeof b === "function" && (e = b, delete c[c.length - 1]); xa ? za || (za = Date.now() - ya >= this.priority * this.priority * 3) : (xa = setTimeout(Aa, 0), ya = Date.now()); if (za) { const f = this; return new Promise(g => { setTimeout(function() { g(f[a + "Async"].apply(f, c)); }, 0); }); } const d = this[a].apply(this, c); b = d.then ? d : new Promise(f => f(d)); e && b.then(e); return b; }; } ;X.prototype.add = function(a, c, b) { Q(a) && (c = a, a = aa(c, this.key)); if (c && (a || a === 0)) { if (!b && this.reg.has(a)) { return this.update(a, c); } for (let h = 0, k; h < this.field.length; h++) { k = this.B[h]; var e = this.index.get(this.field[h]); if (typeof k === "function") { var d = k(c); d && e.add(a, d, b, !0); } else { if (d = k.F, !d || d(c)) { k.constructor === String ? k = ["" + k] : P(k) && (k = [k]), Ba(c, k, this.C, 0, e, a, k[0], b); } } } if (this.tag) { for (e = 0; e < this.A.length; e++) { var f = this.A[e], g = this.D[e]; d = this.tag.get(g); let h = M(); if (typeof f === "function") { if (f = f(c), !f) { continue; } } else { const k = f.F; if (k && !k(c)) { continue; } f.constructor === String && (f = "" + f); f = aa(c, f); } if (d && f) { P(f) && (f = [f]); for (let k = 0, m, l; k < f.length; k++) { m = f[k], h[m] || (h[m] = 1, (g = d.get(m)) ? l = g : d.set(m, l = []), b && l.includes(a) || (l.push(a), this.fastupdate && ((g = this.reg.get(a)) ? g.push(l) : this.reg.set(a, [l])))); } } else { d || console.warn("Tag '" + g + "' was not found"); } } } if (this.store && (!b || !this.store.has(a))) { let h; if (this.h) { h = M(); for (let k = 0, m; k < this.h.length; k++) { m = this.h[k]; if ((b = m.F) && !b(c)) { continue; } let l; if (typeof m === "function") { l = m(c); if (!l) { continue; } m = [m.M]; } else if (P(m) || m.constructor === String) { h[m] = c[m]; continue; } Ca(c, h, m, 0, m[0], l); } } this.store.set(a, h || c); } } return this; }; function Ca(a, c, b, e, d, f) { a = a[d]; if (e === b.length - 1) { c[d] = f || a; } else if (a) { if (a.constructor === Array) { for (c = c[d] = Array(a.length), d = 0; d < a.length; d++) { Ca(a, c, b, e, d); } } else { c = c[d] || (c[d] = M()), d = b[++e], Ca(a, c, b, e, d); } } } function Ba(a, c, b, e, d, f, g, h) { if (a = a[g]) { if (e === c.length - 1) { if (a.constructor === Array) { if (b[e]) { for (c = 0; c < a.length; c++) { d.add(f, a[c], !0, !0); } return; } a = a.join(" "); } d.add(f, a, h, !0); } else { if (a.constructor === Array) { for (g = 0; g < a.length; g++) { Ba(a, c, b, e, d, f, g, h); } } else { g = c[++e], Ba(a, c, b, e, d, f, g, h); } } } } ;function Da(a, c, b) { if (!a.length) { return a; } if (a.length === 1) { return a = a[0], a = b || a.length > c ? a.slice(b, b + c) : a; } let e = []; for (let d = 0, f, g; d < a.length; d++) { if ((f = a[d]) && (g = f.length)) { if (b) { if (b >= g) { b -= g; continue; } f = f.slice(b, b + c); g = f.length; b = 0; } g > c && (f = f.slice(0, c), g = c); if (!e.length && g >= c) { return f; } e.push(f); c -= g; if (!c) { break; } } } return e = e.length > 1 ? [].concat.apply([], e) : e[0]; } ;function Ea(a, c, b, e, d) { let f, g, h; typeof d === "string" ? (f = d, d = "") : f = d.template; if (!f) { throw Error('No template pattern was specified by the search option "highlight"'); } g = f.indexOf("$1"); if (g === -1) { throw Error('Invalid highlight template. The replacement pattern "$1" was not found in template: ' + f); } h = f.substring(g + 2); g = f.substring(0, g); let k = d && d.boundary, m = !d || d.clip !== !1, l = d && d.merge && h && g && new RegExp(h + " " + g, "g"); d = d && d.ellipsis; var A = 0; if (typeof d === "object") { var x = d.template; A = x.length - 2; d = d.pattern; } typeof d !== "string" && (d = d === !1 ? "" : "..."); A && (d = x.replace("$1", d)); x = d.length - A; let u, p; typeof k === "object" && (u = k.before, u === 0 && (u = -1), p = k.after, p === 0 && (p = -1), k = k.total || 9e5); A = new Map(); for (let sa = 0, V, Ha, ca; sa < c.length; sa++) { let da; if (e) { da = c, ca = e; } else { var w = c[sa]; ca = w.field; if (!ca) { continue; } da = w.result; } Ha = b.get(ca); V = Ha.encoder; w = A.get(V); typeof w !== "string" && (w = V.encode(a), A.set(V, w)); for (let ia = 0; ia < da.length; ia++) { var n = da[ia].doc; if (!n) { continue; } n = aa(n, ca); if (!n) { continue; } var v = n.trim().split(/\s+/); if (!v.length) { continue; } n = ""; var q = []; let ja = []; var C = -1, z = -1, t = 0; for (var y = 0; y < v.length; y++) { var F = v[y], D = V.encode(F); D = D.length > 1 ? D.join(" ") : D[0]; let r; if (D && F) { var E = F.length, I = (V.split ? F.replace(V.split, "") : F).length - D.length, L = "", R = 0; for (var W = 0; W < w.length; W++) { var N = w[W]; if (N) { var H = N.length; H += I < 0 ? 0 : I; R && H <= R || (N = D.indexOf(N), N > -1 && (L = (N ? F.substring(0, N) : "") + g + F.substring(N, N + H) + h + (N + H < E ? F.substring(N + H) : ""), R = H, r = !0)); } } L && (k && (C < 0 && (C = n.length + (n ? 1 : 0)), z = n.length + (n ? 1 : 0) + L.length, t += E, ja.push(q.length), q.push({match:L})), n += (n ? " " : "") + L); } if (!r) { F = v[y], n += (n ? " " : "") + F, k && q.push({text:F}); } else if (k && t >= k) { break; } } t = ja.length * (f.length - 2); if (u || p || k && n.length - t > k) { if (t = k + t - x * 2, y = z - C, u > 0 && (y += u), p > 0 && (y += p), y <= t) { v = u ? C - (u > 0 ? u : 0) : C - ((t - y) / 2 | 0), q = p ? z + (p > 0 ? p : 0) : v + t, m || (v > 0 && n.charAt(v) !== " " && n.charAt(v - 1) !== " " && (v = n.indexOf(" ", v), v < 0 && (v = 0)), q < n.length && n.charAt(q - 1) !== " " && n.charAt(q) !== " " && (q = n.lastIndexOf(" ", q), q < z ? q = z : ++q)), n = (v ? d : "") + n.substring(v, q) + (q < n.length ? d : ""); } else { z = []; C = {}; t = {}; y = {}; F = {}; D = {}; L = I = E = 0; for (W = R = 1;;) { var O = void 0; for (let r = 0, G; r < ja.length; r++) { G = ja[r]; if (L) { if (I !== L) { if (y[r + 1]) { continue; } G += L; if (C[G]) { E -= x; t[r + 1] = 1; y[r + 1] = 1; continue; } if (G >= q.length - 1) { if (G >= q.length) { y[r + 1] = 1; G >= v.length && (t[r + 1] = 1); continue; } E -= x; } n = q[G].text; if (H = p && D[r]) { if (H > 0) { if (n.length > H) { if (y[r + 1] = 1, m) { n = n.substring(0, H); } else { continue; } } (H -= n.length) || (H = -1); D[r] = H; } else { y[r + 1] = 1; continue; } } if (E + n.length + 1 <= k) { n = " " + n, z[r] += n; } else if (m) { O = k - E - 1, O > 0 && (n = " " + n.substring(0, O), z[r] += n), y[r + 1] = 1; } else { y[r + 1] = 1; continue; } } else { if (y[r]) { continue; } G -= I; if (C[G]) { E -= x; y[r] = 1; t[r] = 1; continue; } if (G <= 0) { if (G < 0) { y[r] = 1; t[r] = 1; continue; } E -= x; } n = q[G].text; if (H = u && F[r]) { if (H > 0) { if (n.length > H) { if (y[r] = 1, m) { n = n.substring(n.length - H); } else { continue; } } (H -= n.length) || (H = -1); F[r] = H; } else { y[r] = 1; continue; } } if (E + n.length + 1 <= k) { n += " ", z[r] = n + z[r]; } else if (m) { O = n.length + 1 - (k - E), O >= 0 && O < n.length && (n = n.substring(O) + " ", z[r] = n + z[r]), y[r] = 1; } else { y[r] = 1; continue; } } } else { n = q[G].match; u && (F[r] = u); p && (D[r] = p); r && E++; let ta; G ? !r && x && (E += x) : (t[r] = 1, y[r] = 1); G >= v.length - 1 ? ta = 1 : G < q.length - 1 && q[G + 1].match ? ta = 1 : x && (E += x); E -= f.length - 2; if (!r || E + n.length <= k) { z[r] = n; } else { O = R = W = t[r] = 0; break; } ta && (t[r + 1] = 1, y[r + 1] = 1); } E += n.length; O = C[G] = 1; } if (O) { I === L ? L++ : I++; } else { I === L ? R = 0 : W = 0; if (!R && !W) { break; } R ? (I++, L = I) : L++; } } n = ""; for (let r = 0, G; r < z.length; r++) { G = (t[r] ? r ? " " : "" : (r && !d ? " " : "") + d) + z[r], n += G; } d && !t[z.length] && (n += d); } } l && (n = n.replace(l, " ")); da[ia].highlight = n; } if (e) { break; } } return c; } ;function Fa(a, c, b, e) { const d = M(), f = []; for (let g = 0, h; g < c.length; g++) { h = c[g]; for (let k = 0; k < h.length; k++) { d[h[k]] = 1; } } for (let g = 0, h; g < a.length; g++) { if (h = a[g], d[h]) { if (e) { e--; } else { if (f.push(h), d[h] = 0, b && --b === 0) { break; } } } } return f; } ;M(); X.prototype.search = function(a, c, b, e) { b || (!c && Q(a) ? (b = a, a = "") : Q(c) && (b = c, c = 0)); let d = []; var f = []; let g; let h, k, m, l; let A = 0, x = !0, u; if (b) { b.constructor === Array && (b = {index:b}); a = b.query || a; var p = b.pluck; var w = b.merge; m = p || b.field || (m = b.index) && (m.index ? null : m); l = this.tag && b.tag; h = b.suggest; x = !0; k = b.cache; this.store && b.highlight && !x ? console.warn("Highlighting results can only be done within a resolver stage (and/or/not/xor) or when calling .resolve({ highlight: ... })") : this.store && b.enrich && !x && console.warn("Enrich results can only be done on a final resolver task or when calling .resolve({ enrich: true })"); u = x && this.store && b.highlight; g = !!u || x && this.store && b.enrich; c = b.limit || c; var n = b.offset || 0; c || (c = x ? 100 : 0); if (l) { l.constructor !== Array && (l = [l]); var v = []; for (let z = 0, t; z < l.length; z++) { t = l[z]; if (P(t)) { throw Error("A tag option can't be a string, instead it needs a { field: tag } format."); } if (t.field && t.tag) { var q = t.tag; if (q.constructor === Array) { for (var C = 0; C < q.length; C++) { v.push(t.field, q[C]); } } else { v.push(t.field, q); } } else { q = Object.keys(t); for (let y = 0, F, D; y < q.length; y++) { if (F = q[y], D = t[F], D.constructor === Array) { for (C = 0; C < D.length; C++) { v.push(F, D[C]); } } else { v.push(F, D); } } } } if (!v.length) { throw Error("Your tag definition within the search options is probably wrong. No valid tags found."); } l = v; if (!a) { f = []; if (v.length) { for (p = 0; p < v.length; p += 2) { w = Ga.call(this, v[p], v[p + 1], c, n, g), d.push({field:v[p], tag:v[p + 1], result:w}); } } return f.length ? Promise.all(f).then(function(z) { for (let t = 0; t < z.length; t++) { d[t].result = z[t]; } return d; }) : d; } } m && m.constructor !== Array && (m = [m]); } m || (m = this.field); v = !1; for (let z = 0, t, y, F; z < m.length; z++) { y = m[z]; let D; P(y) || (D = y, y = D.field, a = D.query || a, c = K(D.limit, c), n = K(D.offset, n), h = K(D.suggest, h), u = x && this.store && K(D.highlight, u), g = !!u || x && this.store && K(D.enrich, g), k = K(D.cache, k)); if (e) { t = e[z]; } else { q = D || b || {}; C = q.enrich; const E = this.index.get(y); l && (C && (q.enrich = !1), q.limit = 0, q.offset = 0); t = k ? E.searchCache(a, l ? 0 : c, q) : E.search(a, l ? 0 : c, q); l && (q.limit = c, q.offset = n); C && (q.enrich = C); if (v) { v[z] = t; continue; } } F = (t = t.result || t) && t.length; if (l && F) { q = []; C = 0; for (let E = 0, I, L; E < l.length; E += 2) { I = this.tag.get(l[E]); if (!I) { if (console.warn("Tag '" + l[E] + ":" + l[E + 1] + "' will be skipped because there is no field '" + l[E] + "'."), h) { continue; } else { return d; } } if (L = (I = I && I.get(l[E + 1])) && I.length) { C++, q.push(I); } else if (!h) { return d; } } if (C) { t = Fa(t, q, c, n); F = t.length; if (!F && !h) { return t; } C--; } } if (F) { f[A] = y, d.push(t), A++; } else if (m.length === 1) { return d; } } if (v) { const z = this; return Promise.all(v).then(function(t) { b && (b.resolve = x); t.length && (t = z.search(a, c, b, t)); return t; }); } if (!A) { return d; } if (p && (!g || !this.store)) { return d = d[0]; } v = []; for (n = 0; n < f.length; n++) { e = d[n]; g && e.length && typeof e[0].doc === "undefined" && (e = Ia.call(this, e)); if (p) { return u ? Ea(a, e, this.index, p, u) : e; } d[n] = {field:f[n], result:e}; } u && (d = Ea(a, d, this.index, p, u)); return w ? Ja(d) : d; }; function Ja(a) { const c = [], b = M(), e = M(); for (let d = 0, f, g, h, k, m, l, A; d < a.length; d++) { f = a[d]; g = f.field; h = f.result; for (let x = 0; x < h.length; x++) { if (m = h[x], typeof m !== "object" ? m = {id:k = m} : k = m.id, (l = b[k]) ? l.push(g) : (m.field = b[k] = [g], c.push(m)), A = m.highlight) { l = e[k], l || (e[k] = l = {}, m.highlight = l), l[g] = A; } } } return c; } function Ga(a, c, b, e, d) { a = this.tag.get(a); if (!a) { return []; } a = a.get(c); if (!a) { return []; } c = a.length - e; if (c > 0) { if (b && c > b || e) { a = a.slice(e, e + b); } d && (a = Ia.call(this, a)); } return a; } function Ia(a) { if (!this || !this.store) { return a; } const c = Array(a.length); for (let b = 0, e; b < a.length; b++) { e = a[b], c[b] = {id:e, doc:this.store.get(e)}; } return c; } ;function X(a) { if (!this || this.constructor !== X) { return new X(a); } const c = a.document || a.doc || a; var b; this.B = []; this.field = []; this.C = []; this.key = (b = c.key || c.id) && Ka(b, this.C) || "id"; this.reg = (this.fastupdate = !!a.fastupdate) ? new Map() : new Set(); this.h = (b = c.store || null) && b && b !== !0 && []; this.store = b ? new Map() : null; this.cache = (b = a.cache || null) && new T(b); a.cache = !1; this.priority = a.priority || 4; b = new Map(); let e = c.index || c.field || c; P(e) && (e = [e]); for (let d = 0, f, g; d < e.length; d++) { f = e[d], P(f) || (g = f, f = f.field), g = Q(g) ? Object.assign({}, a, g) : a, b.set(f, new Y(g, this.reg)), g.custom ? this.B[d] = g.custom : (this.B[d] = Ka(f, this.C), g.filter && (typeof this.B[d] === "string" && (this.B[d] = new String(this.B[d])), this.B[d].F = g.filter)), this.field[d] = f; } if (this.h) { a = c.store; P(a) && (a = [a]); for (let d = 0, f, g; d < a.length; d++) { f = a[d], g = f.field || f, f.custom ? (this.h[d] = f.custom, f.custom.M = g) : (this.h[d] = Ka(g, this.C), f.filter && (typeof this.h[d] === "string" && (this.h[d] = new String(this.h[d])), this.h[d].F = f.filter)); } } this.index = b; this.tag = null; if (b = c.tag) { if (typeof b === "string" && (b = [b]), b.length) { this.tag = new Map(); this.A = []; this.D = []; for (let d = 0, f, g; d < b.length; d++) { f = b[d]; g = f.field || f; if (!g) { throw Error("The tag field from the document descriptor is undefined."); } f.custom ? this.A[d] = f.custom : (this.A[d] = Ka(g, this.C), f.filter && (typeof this.A[d] === "string" && (this.A[d] = new String(this.A[d])), this.A[d].F = f.filter)); this.D[d] = g; this.tag.set(g, new Map()); } } } } function Ka(a, c) { const b = a.split(":"); let e = 0; for (let d = 0; d < b.length; d++) { a = b[d], a[a.length - 1] === "]" && (a = a.substring(0, a.length - 2)) && (c[e] = !0), a && (b[e++] = a); } e < b.length && (b.length = e); return e > 1 ? b : b[0]; } B = X.prototype; B.append = function(a, c) { return this.add(a, c, !0); }; B.update = function(a, c) { return this.remove(a).add(a, c); }; B.remove = function(a) { Q(a) && (a = aa(a, this.key)); for (var c of this.index.values()) { c.remove(a, !0); } if (this.reg.has(a)) { if (this.tag && !this.fastupdate) { for (let b of this.tag.values()) { for (let e of b) { c = e[0]; const d = e[1], f = d.indexOf(a); f > -1 && (d.length > 1 ? d.splice(f, 1) : b.delete(c)); } } } this.store && this.store.delete(a); this.reg.delete(a); } this.cache && this.cache.remove(a); return this; }; B.clear = function() { const a = []; for (const c of this.index.values()) { const b = c.clear(); b.then && a.push(b); } if (this.tag) { for (const c of this.tag.values()) { c.clear(); } } this.store && this.store.clear(); this.cache && this.cache.clear(); return a.length ? Promise.all(a) : this; }; B.contain = function(a) { return this.reg.has(a); }; B.cleanup = function() { for (const a of this.index.values()) { a.cleanup(); } return this; }; B.get = function(a) { return this.store.get(a) || null; }; B.set = function(a, c) { typeof a === "object" && (c = a, a = aa(c, this.key)); this.store.set(a, c); return this; }; B.searchCache = ma; B.export = La; B.import = Ma; wa(X.prototype); function Na(a, c = 0) { let b = [], e = []; c && (c = 250000 / c * 5000 | 0); for (const d of a.entries()) { e.push(d), e.length === c && (b.push(e), e = []); } e.length && b.push(e); return b; } function Oa(a, c) { c || (c = new Map()); for (let b = 0, e; b < a.length; b++) { e = a[b], c.set(e[0], e[1]); } return c; } function Pa(a, c = 0) { let b = [], e = []; c && (c = 250000 / c * 1000 | 0); for (const d of a.entries()) { e.push([d[0], Na(d[1])[0] || []]), e.length === c && (b.push(e), e = []); } e.length && b.push(e); return b; } function Qa(a, c) { c || (c = new Map()); for (let b = 0, e, d; b < a.length; b++) { e = a[b], d = c.get(e[0]), c.set(e[0], Oa(e[1], d)); } return c; } function Ra(a) { let c = [], b = []; for (const e of a.keys()) { b.push(e), b.length === 250000 && (c.push(b), b = []); } b.length && c.push(b); return c; } function Sa(a, c) { c || (c = new Set()); for (let b = 0; b < a.length; b++) { c.add(a[b]); } return c; } function Ta(a, c, b, e, d, f, g = 0) { const h = e && e.constructor === Array; var k = h ? e.shift() : e; if (!k) { return this.export(a, c, d, f + 1); } if ((k = a((c ? c + "." : "") + (g + 1) + "." + b, JSON.stringify(k))) && k.then) { const m = this; return k.then(function() { return Ta.call(m, a, c, b, h ? e : null, d, f, g + 1); }); } return Ta.call(this, a, c, b, h ? e : null, d, f, g + 1); } function La(a, c, b = 0, e = 0) { if (b < this.field.length) { const g = this.field[b]; if ((c = this.index.get(g).export(a, g, b, e = 1)) && c.then) { const h = this; return c.then(function() { return h.export(a, g, b + 1); }); } return this.export(a, g, b + 1); } let d, f; switch(e) { case 0: d = "reg"; f = Ra(this.reg); c = null; break; case 1: d = "tag"; f = this.tag && Pa(this.tag, this.reg.size); c = null; break; case 2: d = "doc"; f = this.store && Na(this.store); c = null; break; default: return; } return Ta.call(this, a, c, d, f || null, b, e); } function Ma(a, c) { var b = a.split("."); b[b.length - 1] === "json" && b.pop(); a = b.length > 2 ? b[0] : ""; b = b.length > 2 ? b[2] : b[1]; if (c) { typeof c === "string" && (c = JSON.parse(c)); if (a) { return this.index.get(a).import(b, c); } switch(b) { case "reg": this.fastupdate = !1; this.reg = Sa(c, this.reg); for (let e = 0, d; e < this.field.length; e++) { d = this.index.get(this.field[e]), d.fastupdate = !1, d.reg = this.reg; } break; case "tag": this.tag = Qa(c, this.tag); break; case "doc": this.store = Oa(c, this.store); } } } function Ua(a, c) { let b = ""; for (const e of a.entries()) { a = e[0]; const d = e[1]; let f = ""; for (let g = 0, h; g < d.length; g++) { h = d[g] || [""]; let k = ""; for (let m = 0; m < h.length; m++) { k += (k ? "," : "") + (c === "string" ? '"' + h[m] + '"' : h[m]); } k = "[" + k + "]"; f += (f ? "," : "") + k; } f = '["' + a + '",[' + f + "]]"; b += (b ? "," : "") + f; } return b; } ;Y.prototype.remove = function(a, c) { const b = this.reg.size && (this.fastupdate ? this.reg.get(a) : this.reg.has(a)); if (b) { if (this.fastupdate) { for (let e = 0, d, f; e < b.length; e++) { if ((d = b[e]) && (f = d.length)) { if (d[f - 1] === a) { d.pop(); } else { const g = d.indexOf(a); g >= 0 && d.splice(g, 1); } } } } else { Va(this.map, a), this.depth && Va(this.ctx, a); } c || this.reg.delete(a); } this.cache && this.cache.remove(a); return this; }; function Va(a, c) { let b = 0; var e = typeof c === "undefined"; if (a.constructor === Array) { for (let d = 0, f, g, h; d < a.length; d++) { if ((f = a[d]) && f.length) { if (e) { return 1; } g = f.indexOf(c); if (g >= 0) { if (f.length > 1) { return f.splice(g, 1), 1; } delete a[d]; if (b) { return 1; } h = 1; } else { if (h) { return 1; } b++; } } } } else { for (let d of a.entries()) { e = d[0], Va(d[1], c) ? b++ : a.delete(e); } } return b; } ;const Wa = {memory:{resolution:1}, performance:{resolution:3, fastupdate:!0, context:{depth:1, resolution:1}}, match:{tokenize:"full"}, score:{resolution:9, context:{depth:2, resolution:3}}}; Y.prototype.add = function(a, c, b, e) { if (c && (a || a === 0)) { if (!e && !b && this.reg.has(a)) { return this.update(a, c); } e = this.depth; c = this.encoder.encode(c, !e); const m = c.length; if (m) { const l = M(), A = M(), x = this.resolution; for (let u = 0; u < m; u++) { let p = c[this.rtl ? m - 1 - u : u]; var d = p.length; if (d && (e || !A[p])) { var f = this.score ? this.score(c, p, u, null, 0) : Xa(x, m, u), g = ""; switch(this.tokenize) { case "tolerant": Z(this, A, p, f, a, b); if (d > 2) { for (let w = 1, n, v, q, C; w < d - 1; w++) { n = p.charAt(w), v = p.charAt(w + 1), q = p.substring(0, w) + v, C = p.substring(w + 2), g = q + n + C, Z(this, A, g, f, a, b), g = q + C, Z(this, A, g, f, a, b); } Z(this, A, p.substring(0, p.length - 1), f, a, b); } break; case "full": if (d > 2) { for (let w = 0, n; w < d; w++) { for (f = d; f > w; f--) { g = p.substring(w, f); n = this.rtl ? d - 1 - w : w; var h = this.score ? this.score(c, p, u, g, n) : Xa(x, m, u, d, n); Z(this, A, g, h, a, b); } } break; } case "bidirectional": case "reverse": if (d > 1) { for (h = d - 1; h > 0; h--) { g = p[this.rtl ? d - 1 - h : h] + g; var k = this.score ? this.score(c, p, u, g, h) : Xa(x, m, u, d, h); Z(this, A, g, k, a, b); } g = ""; } case "forward": if (d > 1) { for (h = 0; h < d; h++) { g += p[this.rtl ? d - 1 - h : h], Z(this, A, g, f, a, b); } break; } default: if (Z(this, A, p, f, a, b), e && m > 1 && u < m - 1) { for (d = this.L, g = p, f = Math.min(e + 1, this.rtl ? u + 1 : m - u), h = 1; h < f; h++) { p = c[this.rtl ? m - 1 - u - h : u + h]; k = this.bidirectional && p > g; const w = this.score ? this.score(c, g, u, p, h - 1) : Xa(d + (m / 2 > d ? 0 : 1), m, u, f - 1, h - 1); Z(this, l, k ? g : p, w, a, b, k ? p : g); } } } } } this.fastupdate || this.reg.add(a); } } return this; }; function Z(a, c, b, e, d, f, g) { let h, k; if (!(h = c[b]) || g && !h[g]) { g ? (c = h || (c[b] = M()), c[g] = 1, k = a.ctx, (h = k.get(g)) ? k = h : k.set(g, k = new Map())) : (k = a.map, c[b] = 1); (h = k.get(b)) ? k = h : k.set(b, k = h = []); if (f) { for (let m = 0, l; m < h.length; m++) { if ((l = h[m]) && l.includes(d)) { if (m <= e) { return; } l.splice(l.indexOf(d), 1); a.fastupdate && (c = a.reg.get(d)) && c.splice(c.indexOf(l), 1); break; } } } k = k[e] || (k[e] = []); k.push(d); a.fastupdate && ((e = a.reg.get(d)) ? e.push(k) : a.reg.set(d, [k])); } } function Xa(a, c, b, e, d) { return b && a > 1 ? c + (e || 0) <= a ? b + (d || 0) : (a - 1) / (c + (e || 0)) * (b + (d || 0)) + 1 | 0 : 0; } ;Y.prototype.search = function(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : (b = a, a = "")); if (b && b.cache) { return b.cache = !1, c = this.searchCache(a, c, b), b.cache = !0, c; } var e = [], d = 0; if (b) { a = b.query || a; c = b.limit || c; d = b.offset || 0; var f = b.context; var g = b.suggest; var h = !0; var k = b.resolution; } typeof h === "undefined" && (h = !0); f = this.depth && f !== !1; b = this.encoder.encode(a, !f); a = b.length; c = c || (h ? 100 : 0); if (a === 1) { return g = d, (d = Ya(this, b[0], "")) && d.length ? Da.call(this, d, c, g) : []; } if (a === 2 && f && !g) { return g = d, (d = Ya(this, b[1], b[0])) && d.length ? Da.call(this, d, c, g) : []; } h = M(); var m = 0; if (f) { var l = b[0]; m = 1; } k || k === 0 || (k = l ? this.L : this.resolution); for (let p, w; m < a; m++) { if ((w = b[m]) && !h[w]) { h[w] = 1; p = Ya(this, w, l); a: { f = p; var A = e, x = g, u = k; let n = []; if (f && f.length) { if (f.length <= u) { A.push(f); p = void 0; break a; } for (let v = 0, q; v < u; v++) { if (q = f[v]) { n[v] = q; } } if (n.length) { A.push(n); p = void 0; break a; } } p = x ? void 0 : n; } if (p) { e = p; break; } l && (g && p && e.length || (l = w)); } g && l && m === a - 1 && !e.length && (k = this.resolution, l = "", m = -1, h = M()); } a: { b = e; e = b.length; l = b; if (e > 1) { b: { e = g; l = b.length; g = []; a = M(); for (let p = 0, w, n, v, q; p < k; p++) { for (m = 0; m < l; m++) { if (v = b[m], p < v.length && (w = v[p])) { for (f = 0; f < w.length; f++) { if (n = w[f], (h = a[n]) ? a[n]++ : (h = 0, a[n] = 1), q = g[h] || (g[h] = []), q.push(n), c && h === l - 1 && q.length - d === c) { l = d ? q.slice(d) : q; break b; } } } } } if (b = g.length) { if (e) { if (g.length > 1) { c: { for (b = [], k = M(), e = g.length, h = e - 1; h >= 0; h--) { if (a = (e = g[h]) && e.length) { for (m = 0; m < a; m++) { if (l = e[m], !k[l]) { if (k[l] = 1, d) { d--; } else { if (b.push(l), b.length === c) { break c; } } } } } } } } else { b = (g = g[0]) && c && g.length > c || d ? g.slice(d, c + d) : g; } g = b; } else { if (b < l) { l = []; break b; } g = g[b - 1]; if (c || d) { if (g.length > c || d) { g = g.slice(d, c + d); } } } } l = g; } } else if (e === 1) { c = Da.call(null, b[0], c, d); break a; } c = l; } return c; }; function Ya(a, c, b) { let e; b && (e = a.bidirectional && c > b) && (e = b, b = c, c = e); a = b ? (a = a.ctx.get(b)) && a.get(c) : a.map.get(c); return a; } ;function Y(a, c) { if (!this || this.constructor !== Y) { return new Y(a); } if (a) { var b = P(a) ? a : a.preset; b && (Wa[b] || console.warn("Preset not found: " + b), a = Object.assign({}, Wa[b], a)); } else { a = {}; } b = a.context; const e = b === !0 ? {depth:1} : b || {}, d = P(a.encoder) ? va[a.encoder] : a.encode || a.encoder || {}; this.encoder = d.encode ? d : typeof d === "object" ? new la(d) : {encode:d}; this.resolution = a.resolution || 9; this.tokenize = b = (b = a.tokenize) && b !== "default" && b !== "exact" && b || "strict"; this.depth = b === "strict" && e.depth || 0; this.bidirectional = e.bidirectional !== !1; this.fastupdate = !!a.fastupdate; this.score = a.score || null; e && e.depth && this.tokenize !== "strict" && console.warn('Context-Search could not applied, because it is just supported when using the tokenizer "strict".'); b = !1; this.map = new Map(); this.ctx = new Map(); this.reg = c || (this.fastupdate ? new Map() : new Set()); this.L = e.resolution || 3; this.rtl = d.rtl || a.rtl || !1; this.cache = (b = a.cache || null) && new T(b); this.priority = a.priority || 4; } B = Y.prototype; B.clear = function() { this.map.clear(); this.ctx.clear(); this.reg.clear(); this.cache && this.cache.clear(); return this; }; B.append = function(a, c) { return this.add(a, c, !0); }; B.contain = function(a) { return this.reg.has(a); }; B.update = function(a, c) { const b = this, e = this.remove(a); return e && e.then ? e.then(() => b.add(a, c)) : this.add(a, c); }; B.cleanup = function() { if (!this.fastupdate) { return console.info('Cleanup the index isn\'t required when not using "fastupdate".'), this; } Va(this.map); this.depth && Va(this.ctx); return this; }; B.searchCache = ma; B.export = function(a, c, b = 0, e = 0) { let d, f; switch(e) { case 0: d = "reg"; f = Ra(this.reg); break; case 1: d = "cfg"; f = null; break; case 2: d = "map"; f = Na(this.map, this.reg.size); break; case 3: d = "ctx"; f = Pa(this.ctx, this.reg.size); break; default: return; } return Ta.call(this, a, c, d, f, b, e); }; B.import = function(a, c) { if (c) { switch(typeof c === "string" && (c = JSON.parse(c)), a = a.split("."), a[a.length - 1] === "json" && a.pop(), a.length === 3 && a.shift(), a = a.length > 1 ? a[1] : a[0], a) { case "reg": this.fastupdate = !1; this.reg = Sa(c, this.reg); break; case "map": this.map = Oa(c, this.map); break; case "ctx": this.ctx = Qa(c, this.ctx); } } }; B.serialize = function(a = !0) { let c = "", b = "", e = ""; if (this.reg.size) { let f; for (var d of this.reg.keys()) { f || (f = typeof d), c += (c ? "," : "") + (f === "string" ? '"' + d + '"' : d); } c = "index.reg=new Set([" + c + "]);"; b = Ua(this.map, f); b = "index.map=new Map([" + b + "]);"; for (const g of this.ctx.entries()) { d = g[0]; let h = Ua(g[1], f); h = "new Map([" + h + "])"; h = '["' + d + '",' + h + "]"; e += (e ? "," : "") + h; } e = "index.ctx=new Map([" + e + "]);"; } return a ? "function inject(index){" + c + b + e + "}" : c + b + e; }; wa(Y.prototype); M(); export default {Index:Y, Charset:va, Encoder:la, Document:X, Worker:null, Resolver:null, IndexedDB:null, Language:{}}; export const Index=Y;export const Charset=va;export const Encoder=la;export const Document=X;export const Worker=null;export const Resolver=null;export const IndexedDB=null;export const Language={}; ================================================ FILE: dist/flexsearch.es5.debug.js ================================================ /**! * FlexSearch.js v0.8.214 (ES5/Debug) * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ (function _f(self){'use strict';if(typeof module!=='undefined')self=module;else if(typeof process !== 'undefined')self=process;self._factory=_f; var w; function aa(a) { var b = 0; return function() { return b < a.length ? {done:!1, value:a[b++]} : {done:!0}; }; } function D(a) { var b = typeof Symbol != "undefined" && Symbol.iterator && a[Symbol.iterator]; if (b) { return b.call(a); } if (typeof a.length == "number") { return {next:aa(a)}; } throw Error(String(a) + " is not an iterable or ArrayLike"); } var ca = typeof Object.defineProperties == "function" ? Object.defineProperty : function(a, b, c) { if (a == Array.prototype || a == Object.prototype) { return a; } a[b] = c.value; return a; }; function da(a) { a = ["object" == typeof globalThis && globalThis, a, "object" == typeof window && window, "object" == typeof self && self, "object" == typeof global && global]; for (var b = 0; b < a.length; ++b) { var c = a[b]; if (c && c.Math == Math) { return c; } } throw Error("Cannot find global object"); } var ea = da(this); function H(a, b) { if (b) { a: { var c = ea; a = a.split("."); for (var d = 0; d < a.length - 1; d++) { var e = a[d]; if (!(e in c)) { break a; } c = c[e]; } a = a[a.length - 1]; d = c[a]; b = b(d); b != d && b != null && ca(c, a, {configurable:!0, writable:!0, value:b}); } } } var fa; if (typeof Object.setPrototypeOf == "function") { fa = Object.setPrototypeOf; } else { var ha; a: { var ia = {a:!0}, ja = {}; try { ja.__proto__ = ia; ha = ja.a; break a; } catch (a) { } ha = !1; } fa = ha ? function(a, b) { a.__proto__ = b; if (a.__proto__ !== b) { throw new TypeError(a + " is not extensible"); } return a; } : null; } var ka = fa; function la() { this.D = !1; this.A = null; this.B = void 0; this.h = 1; this.I = 0; this.C = null; } function ma(a) { if (a.D) { throw new TypeError("Generator is already running"); } a.D = !0; } la.prototype.H = function(a) { this.B = a; }; function na(a, b) { a.C = {ja:b, ka:!0}; a.h = a.I; } la.prototype.return = function(a) { this.C = {return:a}; this.h = this.I; }; function L(a, b, c) { a.h = c; return {value:b}; } function pa(a) { this.h = new la(); this.A = a; } function qa(a, b) { ma(a.h); var c = a.h.A; if (c) { return ra(a, "return" in c ? c["return"] : function(d) { return {value:d, done:!0}; }, b, a.h.return); } a.h.return(b); return sa(a); } function ra(a, b, c, d) { try { var e = b.call(a.h.A, c); if (!(e instanceof Object)) { throw new TypeError("Iterator result " + e + " is not an object"); } if (!e.done) { return a.h.D = !1, e; } var f = e.value; } catch (g) { return a.h.A = null, na(a.h, g), sa(a); } a.h.A = null; d.call(a.h, f); return sa(a); } function sa(a) { for (; a.h.h;) { try { var b = a.A(a.h); if (b) { return a.h.D = !1, {value:b.value, done:!1}; } } catch (c) { a.h.B = void 0, na(a.h, c); } } a.h.D = !1; if (a.h.C) { b = a.h.C; a.h.C = null; if (b.ka) { throw b.ja; } return {value:b.return, done:!0}; } return {value:void 0, done:!0}; } function ta(a) { this.next = function(b) { ma(a.h); a.h.A ? b = ra(a, a.h.A.next, b, a.h.H) : (a.h.H(b), b = sa(a)); return b; }; this.throw = function(b) { ma(a.h); a.h.A ? b = ra(a, a.h.A["throw"], b, a.h.H) : (na(a.h, b), b = sa(a)); return b; }; this.return = function(b) { return qa(a, b); }; this[Symbol.iterator] = function() { return this; }; } function ua(a, b) { b = new ta(new pa(b)); ka && a.prototype && ka(b, a.prototype); return b; } function va(a) { function b(d) { return a.next(d); } function c(d) { return a.throw(d); } return new Promise(function(d, e) { function f(g) { g.done ? d(g.value) : Promise.resolve(g.value).then(b, c).then(f, e); } f(a.next()); }); } function wa(a) { return va(new ta(new pa(a))); } H("Symbol", function(a) { function b(f) { if (this instanceof b) { throw new TypeError("Symbol is not a constructor"); } return new c(d + (f || "") + "_" + e++, f); } function c(f, g) { this.h = f; ca(this, "description", {configurable:!0, writable:!0, value:g}); } if (a) { return a; } c.prototype.toString = function() { return this.h; }; var d = "jscomp_symbol_" + (Math.random() * 1E9 >>> 0) + "_", e = 0; return b; }); H("Symbol.iterator", function(a) { if (a) { return a; } a = Symbol("Symbol.iterator"); for (var b = "Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "), c = 0; c < b.length; c++) { var d = ea[b[c]]; typeof d === "function" && typeof d.prototype[a] != "function" && ca(d.prototype, a, {configurable:!0, writable:!0, value:function() { return xa(aa(this)); }}); } return a; }); function xa(a) { a = {next:a}; a[Symbol.iterator] = function() { return this; }; return a; } H("Promise", function(a) { function b(g) { this.A = 0; this.B = void 0; this.h = []; this.K = !1; var h = this.C(); try { g(h.resolve, h.reject); } catch (k) { h.reject(k); } } function c() { this.h = null; } function d(g) { return g instanceof b ? g : new b(function(h) { h(g); }); } if (a) { return a; } c.prototype.A = function(g) { if (this.h == null) { this.h = []; var h = this; this.B(function() { h.D(); }); } this.h.push(g); }; var e = ea.setTimeout; c.prototype.B = function(g) { e(g, 0); }; c.prototype.D = function() { for (; this.h && this.h.length;) { var g = this.h; this.h = []; for (var h = 0; h < g.length; ++h) { var k = g[h]; g[h] = null; try { k(); } catch (l) { this.C(l); } } } this.h = null; }; c.prototype.C = function(g) { this.B(function() { throw g; }); }; b.prototype.C = function() { function g(l) { return function(m) { k || (k = !0, l.call(h, m)); }; } var h = this, k = !1; return {resolve:g(this.ea), reject:g(this.D)}; }; b.prototype.ea = function(g) { if (g === this) { this.D(new TypeError("A Promise cannot resolve to itself")); } else { if (g instanceof b) { this.ga(g); } else { a: { switch(typeof g) { case "object": var h = g != null; break a; case "function": h = !0; break a; default: h = !1; } } h ? this.da(g) : this.I(g); } } }; b.prototype.da = function(g) { var h = void 0; try { h = g.then; } catch (k) { this.D(k); return; } typeof h == "function" ? this.ha(h, g) : this.I(g); }; b.prototype.D = function(g) { this.L(2, g); }; b.prototype.I = function(g) { this.L(1, g); }; b.prototype.L = function(g, h) { if (this.A != 0) { throw Error("Cannot settle(" + g + ", " + h + "): Promise already settled in state" + this.A); } this.A = g; this.B = h; this.A === 2 && this.fa(); this.P(); }; b.prototype.fa = function() { var g = this; e(function() { if (g.ca()) { var h = ea.console; typeof h !== "undefined" && h.error(g.B); } }, 1); }; b.prototype.ca = function() { if (this.K) { return !1; } var g = ea.CustomEvent, h = ea.Event, k = ea.dispatchEvent; if (typeof k === "undefined") { return !0; } typeof g === "function" ? g = new g("unhandledrejection", {cancelable:!0}) : typeof h === "function" ? g = new h("unhandledrejection", {cancelable:!0}) : (g = ea.document.createEvent("CustomEvent"), g.initCustomEvent("unhandledrejection", !1, !0, g)); g.promise = this; g.reason = this.B; return k(g); }; b.prototype.P = function() { if (this.h != null) { for (var g = 0; g < this.h.length; ++g) { f.A(this.h[g]); } this.h = null; } }; var f = new c(); b.prototype.ga = function(g) { var h = this.C(); g.R(h.resolve, h.reject); }; b.prototype.ha = function(g, h) { var k = this.C(); try { g.call(h, k.resolve, k.reject); } catch (l) { k.reject(l); } }; b.prototype.then = function(g, h) { function k(n, q) { return typeof n == "function" ? function(t) { try { l(n(t)); } catch (u) { m(u); } } : q; } var l, m, p = new b(function(n, q) { l = n; m = q; }); this.R(k(g, l), k(h, m)); return p; }; b.prototype.catch = function(g) { return this.then(void 0, g); }; b.prototype.R = function(g, h) { function k() { switch(l.A) { case 1: g(l.B); break; case 2: h(l.B); break; default: throw Error("Unexpected state: " + l.A); } } var l = this; this.h == null ? f.A(k) : this.h.push(k); this.K = !0; }; b.resolve = d; b.reject = function(g) { return new b(function(h, k) { k(g); }); }; b.race = function(g) { return new b(function(h, k) { for (var l = D(g), m = l.next(); !m.done; m = l.next()) { d(m.value).R(h, k); } }); }; b.all = function(g) { var h = D(g), k = h.next(); return k.done ? d([]) : new b(function(l, m) { function p(t) { return function(u) { n[t] = u; q--; q == 0 && l(n); }; } var n = [], q = 0; do { n.push(void 0), q++, d(k.value).R(p(n.length - 1), m), k = h.next(); } while (!k.done); }); }; return b; }); function ya(a, b) { a instanceof String && (a += ""); var c = 0, d = !1, e = {next:function() { if (!d && c < a.length) { var f = c++; return {value:b(f, a[f]), done:!1}; } d = !0; return {done:!0, value:void 0}; }}; e[Symbol.iterator] = function() { return e; }; return e; } H("Array.prototype.values", function(a) { return a ? a : function() { return ya(this, function(b, c) { return c; }); }; }); H("Array.prototype.keys", function(a) { return a ? a : function() { return ya(this, function(b) { return b; }); }; }); function za(a, b) { return Object.prototype.hasOwnProperty.call(a, b); } H("WeakMap", function(a) { function b(k) { this.h = (h += Math.random() + 1).toString(); if (k) { k = D(k); for (var l; !(l = k.next()).done;) { l = l.value, this.set(l[0], l[1]); } } } function c() { } function d(k) { var l = typeof k; return l === "object" && k !== null || l === "function"; } function e(k) { if (!za(k, g)) { var l = new c(); ca(k, g, {value:l}); } } function f(k) { var l = Object[k]; l && (Object[k] = function(m) { if (m instanceof c) { return m; } Object.isExtensible(m) && e(m); return l(m); }); } if (function() { if (!a || !Object.seal) { return !1; } try { var k = Object.seal({}), l = Object.seal({}), m = new a([[k, 2], [l, 3]]); if (m.get(k) != 2 || m.get(l) != 3) { return !1; } m.delete(k); m.set(l, 4); return !m.has(k) && m.get(l) == 4; } catch (p) { return !1; } }()) { return a; } var g = "$jscomp_hidden_" + Math.random(); f("freeze"); f("preventExtensions"); f("seal"); var h = 0; b.prototype.set = function(k, l) { if (!d(k)) { throw Error("Invalid WeakMap key"); } e(k); if (!za(k, g)) { throw Error("WeakMap key fail: " + k); } k[g][this.h] = l; return this; }; b.prototype.get = function(k) { return d(k) && za(k, g) ? k[g][this.h] : void 0; }; b.prototype.has = function(k) { return d(k) && za(k, g) && za(k[g], this.h); }; b.prototype.delete = function(k) { return d(k) && za(k, g) && za(k[g], this.h) ? delete k[g][this.h] : !1; }; return b; }); H("Map", function(a) { function b() { var h = {}; return h.J = h.next = h.head = h; } function c(h, k) { var l = h[1]; return xa(function() { if (l) { for (; l.head != h[1];) { l = l.J; } for (; l.next != l.head;) { return l = l.next, {done:!1, value:k(l)}; } l = null; } return {done:!0, value:void 0}; }); } function d(h, k) { var l = k && typeof k; l == "object" || l == "function" ? f.has(k) ? l = f.get(k) : (l = "" + ++g, f.set(k, l)) : l = "p_" + k; var m = h[0][l]; if (m && za(h[0], l)) { for (h = 0; h < m.length; h++) { var p = m[h]; if (k !== k && p.key !== p.key || k === p.key) { return {id:l, list:m, index:h, G:p}; } } } return {id:l, list:m, index:-1, G:void 0}; } function e(h) { this[0] = {}; this[1] = b(); this.size = 0; if (h) { h = D(h); for (var k; !(k = h.next()).done;) { k = k.value, this.set(k[0], k[1]); } } } if (function() { if (!a || typeof a != "function" || !a.prototype.entries || typeof Object.seal != "function") { return !1; } try { var h = Object.seal({x:4}), k = new a(D([[h, "s"]])); if (k.get(h) != "s" || k.size != 1 || k.get({x:4}) || k.set({x:4}, "t") != k || k.size != 2) { return !1; } var l = k.entries(), m = l.next(); if (m.done || m.value[0] != h || m.value[1] != "s") { return !1; } m = l.next(); return m.done || m.value[0].x != 4 || m.value[1] != "t" || !l.next().done ? !1 : !0; } catch (p) { return !1; } }()) { return a; } var f = new WeakMap(); e.prototype.set = function(h, k) { h = h === 0 ? 0 : h; var l = d(this, h); l.list || (l.list = this[0][l.id] = []); l.G ? l.G.value = k : (l.G = {next:this[1], J:this[1].J, head:this[1], key:h, value:k}, l.list.push(l.G), this[1].J.next = l.G, this[1].J = l.G, this.size++); return this; }; e.prototype.delete = function(h) { h = d(this, h); return h.G && h.list ? (h.list.splice(h.index, 1), h.list.length || delete this[0][h.id], h.G.J.next = h.G.next, h.G.next.J = h.G.J, h.G.head = null, this.size--, !0) : !1; }; e.prototype.clear = function() { this[0] = {}; this[1] = this[1].J = b(); this.size = 0; }; e.prototype.has = function(h) { return !!d(this, h).G; }; e.prototype.get = function(h) { return (h = d(this, h).G) && h.value; }; e.prototype.entries = function() { return c(this, function(h) { return [h.key, h.value]; }); }; e.prototype.keys = function() { return c(this, function(h) { return h.key; }); }; e.prototype.values = function() { return c(this, function(h) { return h.value; }); }; e.prototype.forEach = function(h, k) { for (var l = this.entries(), m; !(m = l.next()).done;) { m = m.value, h.call(k, m[1], m[0], this); } }; e.prototype[Symbol.iterator] = e.prototype.entries; var g = 0; return e; }); H("Set", function(a) { function b(c) { this.h = new Map(); if (c) { c = D(c); for (var d; !(d = c.next()).done;) { this.add(d.value); } } this.size = this.h.size; } if (function() { if (!a || typeof a != "function" || !a.prototype.entries || typeof Object.seal != "function") { return !1; } try { var c = Object.seal({x:4}), d = new a(D([c])); if (!d.has(c) || d.size != 1 || d.add(c) != d || d.size != 1 || d.add({x:4}) != d || d.size != 2) { return !1; } var e = d.entries(), f = e.next(); if (f.done || f.value[0] != c || f.value[1] != c) { return !1; } f = e.next(); return f.done || f.value[0] == c || f.value[0].x != 4 || f.value[1] != f.value[0] ? !1 : e.next().done; } catch (g) { return !1; } }()) { return a; } b.prototype.add = function(c) { c = c === 0 ? 0 : c; this.h.set(c, c); this.size = this.h.size; return this; }; b.prototype.delete = function(c) { c = this.h.delete(c); this.size = this.h.size; return c; }; b.prototype.clear = function() { this.h.clear(); this.size = 0; }; b.prototype.has = function(c) { return this.h.has(c); }; b.prototype.entries = function() { return this.h.entries(); }; b.prototype.values = function() { return this.h.values(); }; b.prototype.keys = b.prototype.values; b.prototype[Symbol.iterator] = b.prototype.values; b.prototype.forEach = function(c, d) { var e = this; this.h.forEach(function(f) { return c.call(d, f, f, e); }); }; return b; }); H("Object.is", function(a) { return a ? a : function(b, c) { return b === c ? b !== 0 || 1 / b === 1 / c : b !== b && c !== c; }; }); H("Array.prototype.includes", function(a) { return a ? a : function(b, c) { var d = this; d instanceof String && (d = String(d)); var e = d.length; c = c || 0; for (c < 0 && (c = Math.max(c + e, 0)); c < e; c++) { var f = d[c]; if (f === b || Object.is(f, b)) { return !0; } } return !1; }; }); H("String.prototype.includes", function(a) { return a ? a : function(b, c) { if (this == null) { throw new TypeError("The 'this' value for String.prototype.includes must not be null or undefined"); } if (b instanceof RegExp) { throw new TypeError("First argument to String.prototype.includes must not be a regular expression"); } return this.indexOf(b, c || 0) !== -1; }; }); H("Array.prototype.entries", function(a) { return a ? a : function() { return ya(this, function(b, c) { return [b, c]; }); }; }); var Aa = typeof Object.assign == "function" ? Object.assign : function(a, b) { for (var c = 1; c < arguments.length; c++) { var d = arguments[c]; if (d) { for (var e in d) { za(d, e) && (a[e] = d[e]); } } } return a; }; H("Object.assign", function(a) { return a || Aa; }); H("Array.prototype.flat", function(a) { return a ? a : function(b) { b = b === void 0 ? 1 : b; var c = []; Array.prototype.forEach.call(this, function(d) { Array.isArray(d) && b > 0 ? (d = Array.prototype.flat.call(d, b - 1), c.push.apply(c, d)) : c.push(d); }); return c; }; }); H("Promise.prototype.finally", function(a) { return a ? a : function(b) { return this.then(function(c) { return Promise.resolve(b()).then(function() { return c; }); }, function(c) { return Promise.resolve(b()).then(function() { throw c; }); }); }; }); function Q(a, b, c) { var d = typeof c, e = typeof a; if (d !== "undefined") { if (e !== "undefined") { if (c) { if (e === "function" && d === e) { return function(f) { return a(c(f)); }; } b = a.constructor; if (b === c.constructor) { if (b === Array) { return c.concat(a); } if (b === Map) { b = new Map(c); d = D(a); for (e = d.next(); !e.done; e = d.next()) { e = e.value, b.set(e[0], e[1]); } return b; } if (b === Set) { b = new Set(c); d = D(a.values()); for (e = d.next(); !e.done; e = d.next()) { b.add(e.value); } return b; } } } return a; } return c; } return e === "undefined" ? b : a; } function Ba(a, b) { return typeof a === "undefined" ? b : a; } function S() { return Object.create(null); } function U(a) { return typeof a === "string"; } function Ca(a) { return typeof a === "object"; } function Da(a, b) { if (U(b)) { a = a[b]; } else { for (var c = 0; a && c < b.length; c++) { a = a[b[c]]; } } return a; } ;var Ea = /[^\p{L}\p{N}]+/u, Fa = /(\d{3})/g, Ga = /(\D)(\d{3})/g, Ha = /(\d{3})(\D)/g, Ia = /[\u0300-\u036f]/g; function Ja(a) { a = a === void 0 ? {} : a; if (!this || this.constructor !== Ja) { var b = Function.prototype.bind, c = b.apply, d = [null], e = d.concat; if (arguments instanceof Array) { var f = arguments; } else { f = D(arguments); for (var g, h = []; !(g = f.next()).done;) { h.push(g.value); } f = h; } return new (c.call(b, Ja, e.call(d, f)))(); } if (arguments.length) { for (b = 0; b < arguments.length; b++) { this.assign(arguments[b]); } } else { this.assign(a); } } w = Ja.prototype; w.assign = function(a) { this.normalize = Q(a.normalize, !0, this.normalize); var b = a.include, c = b || a.exclude || a.split; if (c || c === "") { if (typeof c === "object" && c.constructor !== RegExp) { var d = ""; var e = !b; b || (d += "\\p{Z}"); c.letter && (d += "\\p{L}"); c.number && (d += "\\p{N}", e = !!b); c.symbol && (d += "\\p{S}"); c.punctuation && (d += "\\p{P}"); c.control && (d += "\\p{C}"); if (c = c.char) { d += typeof c === "object" ? c.join("") : c; } try { this.split = new RegExp("[" + (b ? "^" : "") + d + "]+", "u"); } catch (f) { console.error("Your split configuration:", c, "is not supported on this platform. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } } else { this.split = c, e = c === !1 || "a1a".split(c).length < 2; } this.numeric = Q(a.numeric, e); } else { try { this.split = Q(this.split, Ea); } catch (f) { console.warn("This platform does not support unicode regex. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } this.numeric = Q(a.numeric, Q(this.numeric, !0)); } this.prepare = Q(a.prepare, null, this.prepare); this.finalize = Q(a.finalize, null, this.finalize); c = a.filter; this.filter = typeof c === "function" ? c : Q(c && new Set(c), null, this.filter); this.dedupe = Q(a.dedupe, !0, this.dedupe); this.matcher = Q((c = a.matcher) && new Map(c), null, this.matcher); this.mapper = Q((c = a.mapper) && new Map(c), null, this.mapper); this.stemmer = Q((c = a.stemmer) && new Map(c), null, this.stemmer); this.replacer = Q(a.replacer, null, this.replacer); this.minlength = Q(a.minlength, 1, this.minlength); this.maxlength = Q(a.maxlength, 1024, this.maxlength); this.rtl = Q(a.rtl, !1, this.rtl); if (this.cache = c = Q(a.cache, !0, this.cache)) { this.D = null, this.P = typeof c === "number" ? c : 2e5, this.B = new Map(), this.C = new Map(), this.I = this.H = 128; } this.h = ""; this.K = null; this.A = ""; this.L = null; if (this.matcher) { for (a = D(this.matcher.keys()), b = a.next(); !b.done; b = a.next()) { this.h += (this.h ? "|" : "") + b.value; } } if (this.stemmer) { for (a = D(this.stemmer.keys()), b = a.next(); !b.done; b = a.next()) { this.A += (this.A ? "|" : "") + b.value; } } return this; }; w.addStemmer = function(a, b) { this.stemmer || (this.stemmer = new Map()); this.stemmer.set(a, b); this.A += (this.A ? "|" : "") + a; this.L = null; this.cache && Ka(this); return this; }; w.addFilter = function(a) { typeof a === "function" ? this.filter = a : (this.filter || (this.filter = new Set()), this.filter.add(a)); this.cache && Ka(this); return this; }; w.addMapper = function(a, b) { if (typeof a === "object") { return this.addReplacer(a, b); } if (a.length > 1) { return this.addMatcher(a, b); } this.mapper || (this.mapper = new Map()); this.mapper.set(a, b); this.cache && Ka(this); return this; }; w.addMatcher = function(a, b) { if (typeof a === "object") { return this.addReplacer(a, b); } if (a.length < 2 && (this.dedupe || this.mapper)) { return this.addMapper(a, b); } this.matcher || (this.matcher = new Map()); this.matcher.set(a, b); this.h += (this.h ? "|" : "") + a; this.K = null; this.cache && Ka(this); return this; }; w.addReplacer = function(a, b) { if (typeof a === "string") { return this.addMatcher(a, b); } this.replacer || (this.replacer = []); this.replacer.push(a, b); this.cache && Ka(this); return this; }; w.encode = function(a, b) { var c = this; if (this.cache && a.length <= this.H) { if (this.D) { if (this.B.has(a)) { return this.B.get(a); } } else { this.D = setTimeout(Ka, 50, this); } } this.normalize && (typeof this.normalize === "function" ? a = this.normalize(a) : a = Ia ? a.normalize("NFKD").replace(Ia, "").toLowerCase() : a.toLowerCase()); this.prepare && (a = this.prepare(a)); this.numeric && a.length > 3 && (a = a.replace(Ga, "$1 $2").replace(Ha, "$1 $2").replace(Fa, "$1 ")); for (var d = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer), e = [], f = S(), g, h, k = this.split || this.split === "" ? a.split(this.split) : [a], l = 0, m = void 0, p = void 0; l < k.length; l++) { if ((m = p = k[l]) && !(m.length < this.minlength || m.length > this.maxlength)) { if (b) { if (f[m]) { continue; } f[m] = 1; } else { if (g === m) { continue; } g = m; } if (d) { e.push(m); } else { if (!this.filter || (typeof this.filter === "function" ? this.filter(m) : !this.filter.has(m))) { if (this.cache && m.length <= this.I) { if (this.D) { var n = this.C.get(m); if (n || n === "") { n && e.push(n); continue; } } else { this.D = setTimeout(Ka, 50, this); } } if (this.stemmer) { for (this.L || (this.L = new RegExp("(?!^)(" + this.A + ")$")), n = void 0; n !== m && m.length > 2;) { n = m, m = m.replace(this.L, function(A) { return c.stemmer.get(A); }); } } if (m && (this.mapper || this.dedupe && m.length > 1)) { n = ""; for (var q = 0, t = "", u = void 0, v = void 0; q < m.length; q++) { u = m.charAt(q), u === t && this.dedupe || ((v = this.mapper && this.mapper.get(u)) || v === "" ? v === t && this.dedupe || !(t = v) || (n += v) : n += t = u); } m = n; } this.matcher && m.length > 1 && (this.K || (this.K = new RegExp("(" + this.h + ")", "g")), m = m.replace(this.K, function(A) { return c.matcher.get(A); })); if (m && this.replacer) { for (n = 0; m && n < this.replacer.length; n += 2) { m = m.replace(this.replacer[n], this.replacer[n + 1]); } } this.cache && p.length <= this.I && (this.C.set(p, m), this.C.size > this.P && (this.C.clear(), this.I = this.I / 1.1 | 0)); if (m) { if (m !== p) { if (b) { if (f[m]) { continue; } f[m] = 1; } else { if (h === m) { continue; } h = m; } } e.push(m); } } } } } this.finalize && (e = this.finalize(e) || e); this.cache && a.length <= this.H && (this.B.set(a, e), this.B.size > this.P && (this.B.clear(), this.H = this.H / 1.1 | 0)); return e; }; function Ka(a) { a.D = null; a.B.clear(); a.C.clear(); } ;function La(a, b, c) { c || (b || typeof a !== "object" ? typeof b === "object" && (c = b, b = 0) : c = a); c && (a = c.query || a, b = c.limit || b); var d = "" + (b || 0); if (c) { var e = c; d += (e.offset || 0) + !!e.context + !!e.suggest + (e.resolve !== !1) + (e.resolution || this.resolution) + (e.boost || 0); } a = ("" + a).toLowerCase(); this.cache || (this.cache = new Ma()); e = this.cache.get(a + d); if (!e) { var f = c && c.cache; f && (c.cache = !1); e = this.search(a, b, c); f && (c.cache = f); this.cache.set(a + d, e); } return e; } function Ma(a) { this.limit = a && a !== !0 ? a : 1000; this.cache = new Map(); this.h = ""; } Ma.prototype.set = function(a, b) { this.cache.set(this.h = a, b); this.cache.size > this.limit && this.cache.delete(this.cache.keys().next().value); }; Ma.prototype.get = function(a) { var b = this.cache.get(a); b && this.h !== a && (this.cache.delete(a), this.cache.set(this.h = a, b)); return b; }; Ma.prototype.remove = function(a) { for (var b = D(this.cache), c = b.next(); !c.done; c = b.next()) { c = c.value; var d = c[0]; c[1].includes(a) && this.cache.delete(d); } }; Ma.prototype.clear = function() { this.cache.clear(); this.h = ""; }; var Na = {normalize:!1, numeric:!1, dedupe:!1}; var Oa = {}; var Pa = new Map([["b", "p"], ["v", "f"], ["w", "f"], ["z", "s"], ["x", "s"], ["d", "t"], ["n", "m"], ["c", "k"], ["g", "k"], ["j", "k"], ["q", "k"], ["i", "e"], ["y", "e"], ["u", "o"]]); var Qa = new Map([["ae", "a"], ["oe", "o"], ["sh", "s"], ["kh", "k"], ["th", "t"], ["ph", "f"], ["pf", "f"]]), Ra = [/([^aeo])h(.)/g, "$1$2", /([aeo])h([^aeo]|$)/g, "$1$2", /(.)\1+/g, "$1"]; var Sa = {a:"", e:"", i:"", o:"", u:"", y:"", b:1, f:1, p:1, v:1, c:2, g:2, j:2, k:2, q:2, s:2, x:2, z:2, "\u00df":2, d:3, t:3, l:4, m:5, n:5, r:6}; var Ta = {Exact:Na, Default:Oa, Normalize:Oa, LatinBalance:{mapper:Pa}, LatinAdvanced:{mapper:Pa, matcher:Qa, replacer:Ra}, LatinExtra:{mapper:Pa, replacer:Ra.concat([/(?!^)[aeo]/g, ""]), matcher:Qa}, LatinSoundex:{dedupe:!1, include:{letter:!0}, finalize:function(a) { for (var b = 0; b < a.length; b++) { for (var c = a[b], d = c.charAt(0), e = Sa[d], f = 1, g; f < c.length && (g = c.charAt(f), g === "h" || g === "w" || !(g = Sa[g]) || g === e || (d += g, e = g, d.length !== 4)); f++) { } a[b] = d; } }}, CJK:{split:""}, LatinExact:Na, LatinDefault:Oa, LatinSimple:Oa}; function Ua(a, b, c, d) { for (var e = [], f = 0, g; f < a.index.length; f++) { if (g = a.index[f], b >= g.length) { b -= g.length; } else { b = g[d ? "splice" : "slice"](b, c); if (g = b.length) { if (e = e.length ? e.concat(b) : b, c -= g, d && (a.length -= g), !c) { break; } } b = 0; } } return e; } function Va(a) { if (!this || this.constructor !== Va) { return new Va(a); } this.index = a ? [a] : []; this.length = a ? a.length : 0; var b = this; return new Proxy([], {get:function(c, d) { if (d === "length") { return b.length; } if (d === "push") { return function(e) { b.index[b.index.length - 1].push(e); b.length++; }; } if (d === "pop") { return function() { if (b.length) { return b.length--, b.index[b.index.length - 1].pop(); } }; } if (d === "indexOf") { return function(e) { for (var f = 0, g = 0, h, k; g < b.index.length; g++) { h = b.index[g]; k = h.indexOf(e); if (k >= 0) { return f + k; } f += h.length; } return -1; }; } if (d === "includes") { return function(e) { for (var f = 0; f < b.index.length; f++) { if (b.index[f].includes(e)) { return !0; } } return !1; }; } if (d === "slice") { return function(e, f) { return Ua(b, e || 0, f || b.length, !1); }; } if (d === "splice") { return function(e, f) { return Ua(b, e || 0, f || b.length, !0); }; } if (d === "constructor") { return Array; } if (typeof d !== "symbol") { return (c = b.index[d / 2147483648 | 0]) && c[d]; } }, set:function(c, d, e) { c = d / 2147483648 | 0; (b.index[c] || (b.index[c] = []))[d] = e; b.length++; return !0; }}); } Va.prototype.clear = function() { this.index.length = 0; }; Va.prototype.push = function() { }; function X(a) { a = a === void 0 ? 8 : a; if (!this || this.constructor !== X) { return new X(a); } this.index = S(); this.h = []; this.size = 0; a > 32 ? (this.A = Wa, this.B = BigInt(a)) : (this.A = Xa, this.B = a); } X.prototype.get = function(a) { var b = this.A(a); return (b = this.index[b]) && b.get(a); }; X.prototype.set = function(a, b) { var c = this.A(a), d = this.index[c]; d ? (c = d.size, d.set(a, b), (c -= d.size) && this.size++) : (this.index[c] = d = new Map([[a, b]]), this.h.push(d), this.size++); }; function Y(a) { a = a === void 0 ? 8 : a; if (!this || this.constructor !== Y) { return new Y(a); } this.index = S(); this.h = []; this.size = 0; a > 32 ? (this.A = Wa, this.B = BigInt(a)) : (this.A = Xa, this.B = a); } Y.prototype.add = function(a) { var b = this.A(a), c = this.index[b]; c ? (b = c.size, c.add(a), (b -= c.size) && this.size++) : (this.index[b] = c = new Set([a]), this.h.push(c), this.size++); }; w = X.prototype; w.has = Y.prototype.has = function(a) { var b = this.A(a); return (b = this.index[b]) && b.has(a); }; w.delete = Y.prototype.delete = function(a) { var b = this.A(a); (b = this.index[b]) && b.delete(a) && this.size--; }; w.clear = Y.prototype.clear = function() { this.index = S(); this.h = []; this.size = 0; }; w.values = Y.prototype.values = function Ya() { var b, c = this, d, e, f; return ua(Ya, function(g) { switch(g.h) { case 1: b = 0; case 2: if (!(b < c.h.length)) { g.h = 0; break; } d = D(c.h[b].values()); e = d.next(); case 5: if (e.done) { b++; g.h = 2; break; } f = e.value; return L(g, f, 6); case 6: e = d.next(), g.h = 5; } }); }; w.keys = Y.prototype.keys = function Za() { var b, c = this, d, e, f; return ua(Za, function(g) { switch(g.h) { case 1: b = 0; case 2: if (!(b < c.h.length)) { g.h = 0; break; } d = D(c.h[b].keys()); e = d.next(); case 5: if (e.done) { b++; g.h = 2; break; } f = e.value; return L(g, f, 6); case 6: e = d.next(), g.h = 5; } }); }; w.entries = Y.prototype.entries = function $a() { var b, c = this, d, e, f; return ua($a, function(g) { switch(g.h) { case 1: b = 0; case 2: if (!(b < c.h.length)) { g.h = 0; break; } d = D(c.h[b].entries()); e = d.next(); case 5: if (e.done) { b++; g.h = 2; break; } f = e.value; return L(g, f, 6); case 6: e = d.next(), g.h = 5; } }); }; function Xa(a) { var b = Math.pow(2, this.B) - 1; if (typeof a == "number") { return a & b; } for (var c = 0, d = this.B + 1, e = 0; e < a.length; e++) { c = (c * d ^ a.charCodeAt(e)) & b; } return this.B === 32 ? c + 2147483648 : c; } function Wa() { throw Error("The keystore is limited to 32 for EcmaScript5"); } ;var ab, bb; function cb(a) { var b, c, d, e, f, g; return wa(function(h) { switch(h.h) { case 1: a = a.data; b = a.task; c = a.id; d = a.args; switch(b) { case "init": bb = a.options || {}; (e = a.factory) ? (Function("return " + e)()(self), ab = new self.FlexSearch.Index(bb), delete self.FlexSearch) : ab = new db(bb); postMessage({id:c}); break; default: h.h = 2; return; }h.h = 0; break; case 2: if (b === "export") { if (!bb.export || typeof bb.export !== "function") { throw Error('Either no extern configuration provided for the Worker-Index or no method was defined on the config property "export".'); } d[1] ? (d[0] = bb.export, d[2] = 0, d[3] = 1) : d = null; } if (b === "import") { if (!bb.import || typeof bb.import !== "function") { throw Error('Either no extern configuration provided for the Worker-Index or no method was defined on the config property "import".'); } if (!d[0]) { h.h = 5; break; } return L(h, bb.import.call(ab, d[0]), 11); } f = d && ab[b].apply(ab, d); if (!f || !f.then) { h.h = 6; break; } return L(h, f, 7); case 7: f = h.B; case 6: if (!f || !f.await) { h.h = 8; break; } return L(h, f.await, 9); case 9: f = h.B; case 8: b === "search" && f.result && (f = f.result); h.h = 5; break; case 11: g = h.B, ab.import(d[0], g); case 5: postMessage(b === "search" ? {id:c, msg:f} : {id:c}), h.h = 0; } }); } ;function eb(a) { fb.call(a, "add"); fb.call(a, "append"); fb.call(a, "search"); fb.call(a, "update"); fb.call(a, "remove"); fb.call(a, "searchCache"); } var gb, hb, ib; function jb() { gb = ib = 0; } function fb(a) { this[a + "Async"] = function() { var b = arguments, c = b[b.length - 1]; if (typeof c === "function") { var d = c; delete b[b.length - 1]; } gb ? ib || (ib = Date.now() - hb >= this.priority * this.priority * 3) : (gb = setTimeout(jb, 0), hb = Date.now()); if (ib) { var e = this; return new Promise(function(g) { setTimeout(function() { g(e[a + "Async"].apply(e, b)); }, 0); }); } var f = this[a].apply(this, b); c = f.then ? f : new Promise(function(g) { return g(f); }); d && c.then(d); return c; }; } ;var kb = 0; function lb(a, b) { function c(h) { function k(l) { l = l.data || l; var m = l.id, p = m && f.h[m]; p && (p(l.msg), delete f.h[m]); } this.worker = h; this.h = S(); if (this.worker) { e ? this.worker.on("message", k) : this.worker.onmessage = k; if (a.config) { return new Promise(function(l) { kb > 1e9 && (kb = 0); f.h[++kb] = function() { l(f); }; f.worker.postMessage({id:kb, task:"init", factory:d, options:a}); }); } this.priority = a.priority || 4; this.encoder = b || null; this.worker.postMessage({task:"init", factory:d, options:a}); return this; } console.warn("Worker is not available on this platform. Please report on Github: https://github.com/nextapps-de/flexsearch/issues"); } a = a === void 0 ? {} : a; if (!this || this.constructor !== lb) { return new lb(a); } var d = typeof self !== "undefined" ? self._factory : typeof window !== "undefined" ? window._factory : null; d && (d = d.toString()); var e = typeof window === "undefined", f = this, g = mb(d, e, a.worker); return g.then ? g.then(function(h) { return c.call(f, h); }) : c.call(this, g); } nb("add"); nb("append"); nb("search"); nb("update"); nb("remove"); nb("clear"); nb("export"); nb("import"); lb.prototype.searchCache = La; eb(lb.prototype); function nb(a) { lb.prototype[a] = function() { var b = this, c = [].slice.call(arguments), d = c[c.length - 1]; if (typeof d === "function") { var e = d; c.pop(); } d = new Promise(function(f) { a === "export" && typeof c[0] === "function" && (c[0] = null); kb > 1e9 && (kb = 0); b.h[++kb] = f; b.worker.postMessage({task:a, id:kb, args:c}); }); return e ? (d.then(e), this) : d; }; } function mb(a, b, c) { return b ? typeof module !== "undefined" ? new(require("worker_threads")["Worker"])(__dirname+"/node/node.js") : import("worker_threads").then(function(worker){return new worker["Worker"]((1,eval)("import.meta.dirname")+"/node/node.mjs")}) : a ? new window.Worker(URL.createObjectURL(new Blob(["onmessage=" + cb.toString()], {type:"text/javascript"}))) : new window.Worker(typeof c === "string" ? c : (0,eval)("import.meta.url").replace("/worker.js", "/worker/worker.js").replace("flexsearch.bundle.module.min.js", "module/worker/worker.js").replace("flexsearch.bundle.module.min.mjs", "module/worker/worker.js"), {type:"module"}); } ;ob.prototype.add = function(a, b, c) { Ca(a) && (b = a, a = Da(b, this.key)); if (b && (a || a === 0)) { if (!c && this.reg.has(a)) { return this.update(a, b); } for (var d = 0, e; d < this.field.length; d++) { e = this.B[d]; var f = this.index.get(this.field[d]); if (typeof e === "function") { (e = e(b)) && f.add(a, e, c, !0); } else { var g = e.M; if (!g || g(b)) { e.constructor === String ? e = ["" + e] : U(e) && (e = [e]), pb(b, e, this.C, 0, f, a, e[0], c); } } } if (this.tag) { for (d = 0; d < this.A.length; d++) { g = this.A[d]; var h = this.D[d]; f = this.tag.get(h); e = S(); if (typeof g === "function") { if (g = g(b), !g) { continue; } } else { var k = g.M; if (k && !k(b)) { continue; } g.constructor === String && (g = "" + g); g = Da(b, g); } if (f && g) { for (U(g) && (g = [g]), h = 0, k = void 0; h < g.length; h++) { var l = g[h]; if (!e[l]) { e[l] = 1; var m; (m = f.get(l)) ? k = m : f.set(l, k = []); if (!c || !k.includes(a)) { if (k.length === 2147483647) { m = new Va(k); if (this.fastupdate) { for (var p = D(this.reg.values()), n = p.next(); !n.done; n = p.next()) { n = n.value, n.includes(k) && (n[n.indexOf(k)] = m); } } f.set(l, k = m); } k.push(a); this.fastupdate && ((l = this.reg.get(a)) ? l.push(k) : this.reg.set(a, [k])); } } } } else { f || console.warn("Tag '" + h + "' was not found"); } } } if (this.store && (!c || !this.store.has(a))) { if (this.h) { var q = S(); for (c = 0; c < this.h.length; c++) { if (d = this.h[c], f = d.M, !f || f(b)) { f = void 0; if (typeof d === "function") { f = d(b); if (!f) { continue; } d = [d.ia]; } else if (U(d) || d.constructor === String) { q[d] = b[d]; continue; } qb(b, q, d, 0, d[0], f); } } } this.store.set(a, q || b); } this.worker && (this.fastupdate || this.reg.add(a)); } return this; }; function qb(a, b, c, d, e, f) { a = a[e]; if (d === c.length - 1) { b[e] = f || a; } else if (a) { if (a.constructor === Array) { for (b = b[e] = Array(a.length), e = 0; e < a.length; e++) { qb(a, b, c, d, e); } } else { b = b[e] || (b[e] = S()), e = c[++d], qb(a, b, c, d, e); } } } function pb(a, b, c, d, e, f, g, h) { if (a = a[g]) { if (d === b.length - 1) { if (a.constructor === Array) { if (c[d]) { for (b = 0; b < a.length; b++) { e.add(f, a[b], !0, !0); } return; } a = a.join(" "); } e.add(f, a, h, !0); } else { if (a.constructor === Array) { for (g = 0; g < a.length; g++) { pb(a, b, c, d, e, f, g, h); } } else { g = b[++d], pb(a, b, c, d, e, f, g, h); } } } } ;function rb(a, b, c, d) { if (!a.length) { return a; } if (a.length === 1) { return a = a[0], a = c || a.length > b ? a.slice(c, c + b) : a, d ? sb.call(this, a) : a; } for (var e = [], f = 0, g = void 0, h = void 0; f < a.length; f++) { if ((g = a[f]) && (h = g.length)) { if (c) { if (c >= h) { c -= h; continue; } g = g.slice(c, c + b); h = g.length; c = 0; } h > b && (g = g.slice(0, b), h = b); if (!e.length && h >= b) { return d ? sb.call(this, g) : g; } e.push(g); b -= h; if (!b) { break; } } } e = e.length > 1 ? [].concat.apply([], e) : e[0]; return d ? sb.call(this, e) : e; } ;function tb(a, b, c, d) { var e = d[0]; if (e[0] && e[0].query) { return a[b].apply(a, e); } if (!(b !== "and" && b !== "not" || a.result.length || a.await || e.suggest)) { return d.length > 1 && (e = d[d.length - 1]), (d = e.resolve) ? a.await || a.result : a; } var f = [], g = 0, h = 0, k; b = {}; for (e = 0; e < d.length; b = {U:void 0, T:void 0, Y:void 0, aa:void 0}, e++) { var l = d[e]; if (l) { var m = void 0; if (l.constructor === Z) { m = l.await || l.result; } else if (l.then || l.constructor === Array) { m = l; } else { g = l.limit || 0; h = l.offset || 0; var p = l.suggest; var n = l.resolve; var q = ((k = l.highlight || a.highlight) || l.enrich) && n; m = l.queue; b.Y = l.async || m; var t = l.index, u = l.query; t ? a.index || (a.index = t) : t = a.index; if (u || l.tag) { if (!t) { throw Error("Resolver can't apply because the corresponding Index was never specified"); } var v = l.field || l.pluck; if (v) { !u || a.query && !k || (a.query = u, a.field = v, a.highlight = k); if (!t.index) { throw Error("Resolver can't apply because the corresponding Document Index was not specified"); } t = t.index.get(v); if (!t) { throw Error("Resolver can't apply because the specified Document Field '" + v + "' was not found"); } } if (m && (A || a.await)) { var A = 1; b.U = void 0; b.aa = a.F.length; b.T = new Promise(function(B) { return function(C) { B.U = C; }; }(b)); (function(B) { return function(C, r) { B.T.H = function() { r.index = null; r.resolve = !1; r.enrich = !1; var x = B.Y ? C.searchAsync(r) : C.search(r); if (x.then) { return x.then(function(E) { a.F[B.aa] = E = E.result || E; (0,B.U)(E); return E; }); } x = x.result || x; (0,B.U)(x); return x; }; }; })(b)(t, Object.assign({}, l)); a.F.push(b.T); f[e] = b.T; continue; } else { l.resolve = !1, l.enrich = !1, l.index = null, m = b.Y ? t.searchAsync(l) : t.search(l), l.resolve = n, l.enrich = q, l.index = t; } } else if (l.and) { m = ub(l, "and", t); } else if (l.or) { m = ub(l, "or", t); } else if (l.not) { m = ub(l, "not", t); } else if (l.xor) { m = ub(l, "xor", t); } else { continue; } } m.await ? (A = 1, m = m.await) : m.then ? (A = 1, m = m.then(function(B) { return B.result || B; })) : m = m.result || m; f[e] = m; } } A && !a.await && (a.await = new Promise(function(B) { a.return = B; })); if (A) { var y = Promise.all(f).then(function(B) { for (var C = 0; C < a.F.length; C++) { if (a.F[C] === y) { a.F[C] = function() { return c.call(a, B, g, h, q, n, p, k); }; break; } } vb(a); }); a.F.push(y); } else if (a.await) { a.F.push(function() { return c.call(a, f, g, h, q, n, p, k); }); } else { return c.call(a, f, g, h, q, n, p, k); } return n ? a.await || a.result : a; } function ub(a, b, c) { a = a[b]; var d = a[0] || a; d.index || (d.index = c); c = new Z(d); a.length > 1 && (c = c[b].apply(c, a.slice(1))); return c; } ;Z.prototype.or = function() { return tb(this, "or", wb, arguments); }; function wb(a, b, c, d, e, f, g) { a.length && (this.result.length && a.push(this.result), a.length < 2 ? this.result = a[0] : (this.result = xb(a, b, c, !1, this.h), c = 0)); e && (this.await = null); return e ? this.resolve(b, c, d, g) : this; } ;Z.prototype.and = function() { return tb(this, "and", yb, arguments); }; function yb(a, b, c, d, e, f, g) { if (!f && !this.result.length) { return e ? this.result : this; } if (a.length) { if (this.result.length && a.unshift(this.result), a.length < 2) { this.result = a[0]; } else { for (var h = 0, k = 0, l = void 0, m = void 0; k < a.length; k++) { if ((l = a[k]) && (m = l.length)) { h < m && (h = m); } else if (!f) { h = 0; break; } } if (h) { this.result = zb(a, h, b, c, f, this.h, e); var p = !0; } else { this.result = []; } } } else { f || (this.result = a); } e && (this.await = null); return e ? this.resolve(b, c, d, g, p) : this; } ;Z.prototype.xor = function() { return tb(this, "xor", Ab, arguments); }; function Ab(a, b, c, d, e, f, g) { if (a.length) { if (this.result.length && a.unshift(this.result), a.length < 2) { this.result = a[0]; } else { a: { f = c; var h = this.h; for (var k = [], l = S(), m = 0, p = 0, n; p < a.length; p++) { if (n = a[p]) { m < n.length && (m = n.length); for (var q = 0, t; q < n.length; q++) { if (t = n[q]) { for (var u = 0, v; u < t.length; u++) { v = t[u], l[v] = l[v] ? 2 : 1; } } } } } for (n = p = 0; p < m; p++) { for (q = 0; q < a.length; q++) { if (t = a[q]) { if (t = t[p]) { for (u = 0; u < t.length; u++) { if (v = t[u], l[v] === 1) { if (f) { f--; } else { if (e) { if (k.push(v), k.length === b) { a = k; break a; } } else { var A = p + (q ? h : 0); k[A] || (k[A] = []); k[A].push(v); if (++n === b) { a = k; break a; } } } } } } } } } a = k; } this.result = a; h = !0; } } else { f || (this.result = a); } e && (this.await = null); return e ? this.resolve(b, c, d, g, h) : this; } ;Z.prototype.not = function() { return tb(this, "not", Bb, arguments); }; function Bb(a, b, c, d, e, f, g) { if (!f && !this.result.length) { return e ? this.result : this; } if (a.length && this.result.length) { a: { f = c; var h = []; a = new Set(a.flat().flat()); for (var k = 0, l, m = 0; k < this.result.length; k++) { if (l = this.result[k]) { for (var p = 0, n; p < l.length; p++) { if (n = l[p], !a.has(n)) { if (f) { f--; } else { if (e) { if (h.push(n), h.length === b) { f = h; break a; } } else { if (h[k] || (h[k] = []), h[k].push(n), ++m === b) { f = h; break a; } } } } } } } f = h; } this.result = f; h = !0; } e && (this.await = null); return e ? this.resolve(b, c, d, g, h) : this; } ;function Cb(a, b, c, d, e) { if (typeof e === "string") { var f = e; e = ""; } else { f = e.template; } if (!f) { throw Error('No template pattern was specified by the search option "highlight"'); } var g = f.indexOf("$1"); if (g === -1) { throw Error('Invalid highlight template. The replacement pattern "$1" was not found in template: ' + f); } var h = f.substring(g + 2); g = f.substring(0, g); var k = e && e.boundary, l = !e || e.clip !== !1, m = e && e.merge && h && g && new RegExp(h + " " + g, "g"); e = e && e.ellipsis; var p = 0; if (typeof e === "object") { var n = e.template; p = n.length - 2; e = e.pattern; } typeof e !== "string" && (e = e === !1 ? "" : "..."); p && (e = n.replace("$1", e)); n = e.length - p; if (typeof k === "object") { var q = k.before; q === 0 && (q = -1); var t = k.after; t === 0 && (t = -1); k = k.total || 9e5; } p = new Map(); for (var u, v = 0, A, y; v < b.length; v++) { if (d) { var B = b; y = d; } else { B = b[v]; y = B.field; if (!y) { continue; } B = B.result; } u = c.get(y); A = u.encoder; u = p.get(A); typeof u !== "string" && (u = A.encode(a), p.set(A, u)); for (var C = 0; C < B.length; C++) { var r = B[C].doc; if (r && (r = Da(r, y))) { var x = r.trim().split(/\s+/); if (x.length) { r = ""; for (var E = [], T = [], G = -1, O = -1, N = 0, F = 0; F < x.length; F++) { var R = x[F], W = A.encode(R); W = W.length > 1 ? W.join(" ") : W[0]; var P = void 0; if (W && R) { for (var M = R.length, K = (A.split ? R.replace(A.split, "") : R).length - W.length, ba = "", oa = 0, z = 0; z < u.length; z++) { var I = u[z]; if (I) { var J = I.length; J += K < 0 ? 0 : K; oa && J <= oa || (I = W.indexOf(I), I > -1 && (ba = (I ? R.substring(0, I) : "") + g + R.substring(I, I + J) + h + (I + J < M ? R.substring(I + J) : ""), oa = J, P = !0)); } } ba && (k && (G < 0 && (G = r.length + (r ? 1 : 0)), O = r.length + (r ? 1 : 0) + ba.length, N += M, T.push(E.length), E.push({match:ba})), r += (r ? " " : "") + ba); } if (!P) { R = x[F], r += (r ? " " : "") + R, k && E.push({text:R}); } else if (k && N >= k) { break; } } N = T.length * (f.length - 2); if (q || t || k && r.length - N > k) { if (N = k + N - n * 2, F = O - G, q > 0 && (F += q), t > 0 && (F += t), F <= N) { x = q ? G - (q > 0 ? q : 0) : G - ((N - F) / 2 | 0), E = t ? O + (t > 0 ? t : 0) : x + N, l || (x > 0 && r.charAt(x) !== " " && r.charAt(x - 1) !== " " && (x = r.indexOf(" ", x), x < 0 && (x = 0)), E < r.length && r.charAt(E - 1) !== " " && r.charAt(E) !== " " && (E = r.lastIndexOf(" ", E), E < O ? E = O : ++E)), r = (x ? e : "") + r.substring(x, E) + (E < r.length ? e : ""); } else { O = []; N = {}; G = {}; F = {}; R = {}; W = {}; K = M = P = 0; for (oa = ba = 1;;) { I = void 0; for (z = 0; z < T.length; z++) { J = T[z]; if (K) { if (M !== K) { if (F[z + 1]) { continue; } J += K; if (N[J]) { P -= n; G[z + 1] = 1; F[z + 1] = 1; continue; } if (J >= E.length - 1) { if (J >= E.length) { F[z + 1] = 1; J >= x.length && (G[z + 1] = 1); continue; } P -= n; } r = E[J].text; var V = t && W[z]; if (V) { if (V > 0) { if (r.length > V) { if (F[z + 1] = 1, l) { r = r.substring(0, V); } else { continue; } } (V -= r.length) || (V = -1); W[z] = V; } else { F[z + 1] = 1; continue; } } if (P + r.length + 1 <= k) { r = " " + r, O[z] += r; } else if (l) { I = k - P - 1, I > 0 && (r = " " + r.substring(0, I), O[z] += r), F[z + 1] = 1; } else { F[z + 1] = 1; continue; } } else { if (F[z]) { continue; } J -= M; if (N[J]) { P -= n; F[z] = 1; G[z] = 1; continue; } if (J <= 0) { if (J < 0) { F[z] = 1; G[z] = 1; continue; } P -= n; } r = E[J].text; if (V = q && R[z]) { if (V > 0) { if (r.length > V) { if (F[z] = 1, l) { r = r.substring(r.length - V); } else { continue; } } (V -= r.length) || (V = -1); R[z] = V; } else { F[z] = 1; continue; } } if (P + r.length + 1 <= k) { r += " ", O[z] = r + O[z]; } else if (l) { I = r.length + 1 - (k - P), I >= 0 && I < r.length && (r = r.substring(I) + " ", O[z] = r + O[z]), F[z] = 1; } else { F[z] = 1; continue; } } } else { r = E[J].match; q && (R[z] = q); t && (W[z] = t); z && P++; I = void 0; J ? !z && n && (P += n) : (G[z] = 1, F[z] = 1); J >= x.length - 1 ? I = 1 : J < E.length - 1 && E[J + 1].match ? I = 1 : n && (P += n); P -= f.length - 2; if (!z || P + r.length <= k) { O[z] = r; } else { I = ba = oa = G[z] = 0; break; } I && (G[z + 1] = 1, F[z + 1] = 1); } P += r.length; I = N[J] = 1; } if (I) { M === K ? K++ : M++; } else { M === K ? ba = 0 : oa = 0; if (!ba && !oa) { break; } ba ? (M++, K = M) : K++; } } r = ""; for (x = 0; x < O.length; x++) { E = (G[x] ? x ? " " : "" : (x && !e ? " " : "") + e) + O[x], r += E; } e && !G[O.length] && (r += e); } } m && (r = r.replace(m, " ")); B[C].highlight = r; } } } if (d) { break; } } return b; } ;function Z(a, b) { if (!this || this.constructor !== Z) { return new Z(a, b); } var c = 0, d, e; if (a && a.index) { var f = a; b = f.index; c = f.boost || 0; if (d = f.query) { var g = f.field || f.pluck; var h = f.highlight; var k = f.resolve; a = f.async || f.queue; f.resolve = !1; f.highlight = ""; f.index = null; a = a ? b.searchAsync(f) : b.search(f); f.resolve = k; f.highlight = h; f.index = b; a = a.result || a; } else { a = []; } } if (a && a.then) { var l = this; a = a.then(function(n) { l.F[0] = l.result = n.result || n; vb(l); }); var m = [a]; a = []; var p = new Promise(function(n) { e = n; }); } this.index = b || null; this.result = a || []; this.h = c; this.F = m || []; this.await = p || null; this.return = e || null; this.highlight = h || null; this.query = d || ""; this.field = g || ""; } w = Z.prototype; w.limit = function(a) { if (this.await) { var b = this; this.F.push(function() { return b.limit(a).result; }); } else { if (this.result.length) { for (var c = [], d = 0, e = void 0; d < this.result.length; d++) { if (e = this.result[d]) { if (e.length <= a) { if (c[d] = e, a -= e.length, !a) { break; } } else { c[d] = e.slice(0, a); break; } } } this.result = c; } } return this; }; w.offset = function(a) { if (this.await) { var b = this; this.F.push(function() { return b.offset(a).result; }); } else { if (this.result.length) { for (var c = [], d = 0, e = void 0; d < this.result.length; d++) { if (e = this.result[d]) { e.length <= a ? a -= e.length : (c[d] = e.slice(a), a = 0); } } this.result = c; } } return this; }; w.boost = function(a) { if (this.await) { var b = this; this.F.push(function() { return b.boost(a).result; }); } else { this.h += a; } return this; }; function vb(a, b) { var c = a.result, d = a.await; a.await = null; for (var e = 0, f; e < a.F.length; e++) { if (f = a.F[e]) { if (typeof f === "function") { c = f(), a.F[e] = c = c.result || c, e--; } else if (f.H) { c = f.H(), a.F[e] = c = c.result || c, e--; } else if (f.then) { return a.await = d; } } } d = a.return; a.F = []; a.return = null; b || d(c); return c; } w.resolve = function(a, b, c, d, e) { var f = this.await ? vb(this, !0) : this.result; if (f.then) { var g = this; return f.then(function() { return g.resolve(a, b, c, d, e); }); } f.length && (typeof a === "object" ? (d = a.highlight || this.highlight, c = !!d || a.enrich, b = a.offset, a = a.limit) : (d = d || this.highlight, c = !!d || c), f = e ? c ? sb.call(this.index, f) : f : rb.call(this.index, f, a || 100, b, c)); return this.finalize(f, d); }; w.finalize = function(a, b) { if (a.then) { var c = this; return a.then(function(e) { return c.finalize(e, b); }); } b && !this.query && console.warn('There was no query specified for highlighting. Please specify a query within the highlight resolver stage like { query: "...", highlight: ... }.'); b && a.length && this.query && (a = Cb(this.query, a, this.index.index, this.field, b)); var d = this.return; this.highlight = this.index = this.result = this.F = this.await = this.return = null; this.query = this.field = ""; d && d(a); return a; }; function zb(a, b, c, d, e, f, g) { var h = a.length, k = []; var l = S(); for (var m = 0, p = void 0, n, q; m < b; m++) { for (var t = 0; t < h; t++) { var u = a[t]; if (m < u.length && (p = u[m])) { for (var v = 0; v < p.length; v++) { n = p[v]; (u = l[n]) ? l[n]++ : (u = 0, l[n] = 1); q = k[u] || (k[u] = []); if (!g) { var A = m + (t || !e ? 0 : f || 0); q = q[A] || (q[A] = []); } q.push(n); if (g && c && u === h - 1 && q.length - d === c) { return d ? q.slice(d) : q; } } } } } if (a = k.length) { if (e) { k = k.length > 1 ? xb(k, c, d, g, f) : (k = k[0]) && c && k.length > c || d ? k.slice(d, c + d) : k; } else { if (a < h) { return []; } k = k[a - 1]; if (c || d) { if (g) { if (k.length > c || d) { k = k.slice(d, c + d); } } else { e = []; for (f = 0; f < k.length; f++) { if (g = k[f]) { if (d && g.length > d) { d -= g.length; } else { if (c && g.length > c || d) { g = g.slice(d, c + d), c -= g.length, d && (d -= g.length); } e.push(g); if (!c) { break; } } } } k = e; } } } } return k; } function xb(a, b, c, d, e) { var f = [], g = S(), h = a.length, k; if (d) { for (e = h - 1; e >= 0; e--) { if (k = (d = a[e]) && d.length) { for (h = 0; h < k; h++) { var l = d[h]; if (!g[l]) { if (g[l] = 1, c) { c--; } else { if (f.push(l), f.length === b) { return f; } } } } } } } else { for (var m = h - 1, p, n = 0; m >= 0; m--) { p = a[m]; for (var q = 0; q < p.length; q++) { if (k = (d = p[q]) && d.length) { for (var t = 0; t < k; t++) { if (l = d[t], !g[l]) { if (g[l] = 1, c) { c--; } else { var u = (q + (m < h - 1 ? e || 0 : 0)) / (m + 1) | 0; (f[u] || (f[u] = [])).push(l); if (++n === b) { return f; } } } } } } } } return f; } function Db(a, b, c, d, e) { for (var f = S(), g = [], h = 0, k; h < b.length; h++) { k = b[h]; for (var l = 0; l < k.length; l++) { f[k[l]] = 1; } } if (e) { for (b = 0; b < a.length; b++) { if (e = a[b], f[e]) { if (d) { d--; } else { if (g.push(e), f[e] = 0, c && --c === 0) { break; } } } } } else { for (a = a.result || a, c = 0; c < a.length; c++) { for (d = a[c], e = 0; e < d.length; e++) { b = d[e], f[b] && ((g[c] || (g[c] = [])).push(b), f[b] = 0); } } } return g; } ;S(); ob.prototype.search = function(a, b, c, d) { c || (!b && Ca(a) ? (c = a, a = "") : Ca(b) && (c = b, b = 0)); var e = [], f = [], g = 0, h = !0; if (c) { c.constructor === Array && (c = {index:c}); a = c.query || a; var k = c.pluck; var l = c.merge; var m = c.boost; var p = k || c.field || (p = c.index) && (p.index ? null : p); var n = this.tag && c.tag; var q = c.suggest; h = c.resolve !== !1; var t = c.cache; this.store && c.highlight && !h ? console.warn("Highlighting results can only be done within a resolver stage (and/or/not/xor) or when calling .resolve({ highlight: ... })") : this.store && c.enrich && !h && console.warn("Enrich results can only be done on a final resolver task or when calling .resolve({ enrich: true })"); var u = h && this.store && c.highlight; var v = !!u || h && this.store && c.enrich; b = c.limit || b; var A = c.offset || 0; b || (b = h ? 100 : 0); if (n && (!this.db || !d)) { n.constructor !== Array && (n = [n]); for (var y = [], B = 0, C = void 0; B < n.length; B++) { C = n[B]; if (U(C)) { throw Error("A tag option can't be a string, instead it needs a { field: tag } format."); } if (C.field && C.tag) { var r = C.tag; if (r.constructor === Array) { for (var x = 0; x < r.length; x++) { y.push(C.field, r[x]); } } else { y.push(C.field, r); } } else { r = Object.keys(C); x = 0; for (var E = void 0, T = void 0; x < r.length; x++) { if (E = r[x], T = C[E], T.constructor === Array) { for (var G = 0; G < T.length; G++) { y.push(E, T[G]); } } else { y.push(E, T); } } } } if (!y.length) { throw Error("Your tag definition within the search options is probably wrong. No valid tags found."); } n = y; if (!a) { f = []; if (y.length) { for (n = 0; n < y.length; n += 2) { d = void 0; if (this.db) { d = this.index.get(y[n]); if (!d) { console.warn("Tag '" + y[n] + ":" + y[n + 1] + "' will be skipped because there is no field '" + y[n] + "'."); continue; } f.push(d = d.db.tag(y[n + 1], b, A, v)); } else { d = Eb.call(this, y[n], y[n + 1], b, A, v); } e.push(h ? {field:y[n], tag:y[n + 1], result:d} : [d]); } } if (f.length) { var O = this; return Promise.all(f).then(function(M) { for (var K = 0; K < M.length; K++) { h ? e[K].result = M[K] : e[K] = M[K]; } return h ? e : new Z(e.length > 1 ? zb(e, 1, 0, 0, q, m) : e[0], O); }); } return h ? e : new Z(e.length > 1 ? zb(e, 1, 0, 0, q, m) : e[0], this); } } if (!h && !k) { if (p = p || this.field) { U(p) ? k = p : (p.constructor === Array && p.length === 1 && (p = p[0]), k = p.field || p.index); } if (!k) { throw Error("Apply resolver on document search requires either the option 'pluck' to be set or just select a single field name in your query."); } } p && p.constructor !== Array && (p = [p]); } p || (p = this.field); y = (this.worker || this.db) && !d && []; B = 0; for (x = C = r = void 0; B < p.length; B++) { if (C = p[B], !this.db || !this.tag || this.B[B]) { r = void 0; U(C) || (r = C, C = r.field, a = r.query || a, b = Ba(r.limit, b), A = Ba(r.offset, A), q = Ba(r.suggest, q), u = h && this.store && Ba(r.highlight, u), v = !!u || h && this.store && Ba(r.enrich, v), t = Ba(r.cache, t)); if (d) { r = d[B]; } else { x = r || c || {}; E = x.enrich; r = this.index.get(C); if (n) { if (this.db) { x.tag = n; x.field = p; var N = r.db.support_tag_search; } !N && E && (x.enrich = !1); N || (x.limit = 0, x.offset = 0); } r = t ? r.searchCache(a, n && !N ? 0 : b, x) : r.search(a, n && !N ? 0 : b, x); n && !N && (x.limit = b, x.offset = A); E && (x.enrich = E); if (y) { y[B] = r; continue; } } x = (r = r.result || r) && r.length; if (n && x) { E = []; T = 0; if (this.db && d) { if (!N) { for (G = p.length; G < d.length; G++) { var F = d[G]; if (F && F.length) { T++, E.push(F); } else if (!q) { return h ? e : new Z(e, this); } } } } else { G = 0; for (var R = F = void 0; G < n.length; G += 2) { F = this.tag.get(n[G]); if (!F) { if (console.warn("Tag '" + n[G] + ":" + n[G + 1] + "' will be skipped because there is no field '" + n[G] + "'."), q) { continue; } else { return h ? e : new Z(e, this); } } if (R = (F = F && F.get(n[G + 1])) && F.length) { T++, E.push(F); } else if (!q) { return h ? e : new Z(e, this); } } } if (T) { r = Db(r, E, b, A, h); x = r.length; if (!x && !q) { return h ? r : new Z(r, this); } T--; } } if (x) { f[g] = C, e.push(r), g++; } else if (p.length === 1) { return h ? e : new Z(e, this); } } } if (y) { if (this.db && n && n.length && !N) { for (v = 0; v < n.length; v += 2) { f = this.index.get(n[v]); if (!f) { if (console.warn("Tag '" + n[v] + ":" + n[v + 1] + "' was not found because there is no field '" + n[v] + "'."), q) { continue; } else { return h ? e : new Z(e, this); } } y.push(f.db.tag(n[v + 1], b, A, !1)); } } var W = this; return Promise.all(y).then(function(M) { c && (c.resolve = h); M.length && (M = W.search(a, b, c, M)); return M; }); } if (!g) { return h ? e : new Z(e, this); } if (k && (!v || !this.store)) { return e = e[0], h ? e : new Z(e, this); } y = []; for (A = 0; A < f.length; A++) { n = e[A]; v && n.length && typeof n[0].doc === "undefined" && (this.db ? y.push(n = this.index.get(this.field[0]).db.enrich(n)) : n = sb.call(this, n)); if (k) { return h ? u ? Cb(a, n, this.index, k, u) : n : new Z(n, this); } e[A] = {field:f[A], result:n}; } if (v && this.db && y.length) { var P = this; return Promise.all(y).then(function(M) { for (var K = 0; K < M.length; K++) { e[K].result = M[K]; } u && (e = Cb(a, e, P.index, k, u)); return l ? Fb(e) : e; }); } u && (e = Cb(a, e, this.index, k, u)); return l ? Fb(e) : e; }; function Fb(a) { for (var b = [], c = S(), d = S(), e = 0, f, g, h = void 0, k, l, m; e < a.length; e++) { f = a[e]; g = f.field; f = f.result; for (var p = 0; p < f.length; p++) { if (k = f[p], typeof k !== "object" ? k = {id:h = k} : h = k.id, (l = c[h]) ? l.push(g) : (k.field = c[h] = [g], b.push(k)), m = k.highlight) { l = d[h], l || (d[h] = l = {}, k.highlight = l), l[g] = m; } } } return b; } function Eb(a, b, c, d, e) { a = this.tag.get(a); if (!a) { return []; } a = a.get(b); if (!a) { return []; } b = a.length - d; if (b > 0) { if (c && b > c || d) { a = a.slice(d, d + c); } e && (a = sb.call(this, a)); } return a; } function sb(a) { if (!this || !this.store) { return a; } if (this.db) { return this.index.get(this.field[0]).db.enrich(a); } for (var b = Array(a.length), c = 0, d; c < a.length; c++) { d = a[c], b[c] = {id:d, doc:this.store.get(d)}; } return b; } ;function ob(a) { if (!this || this.constructor !== ob) { return new ob(a); } var b = a.document || a.doc || a, c, d; this.B = []; this.field = []; this.C = []; this.key = (c = b.key || b.id) && Gb(c, this.C) || "id"; (d = a.keystore || 0) && (this.keystore = d); this.fastupdate = !!a.fastupdate; this.reg = !this.fastupdate || a.worker || a.db ? d ? new Y(d) : new Set() : d ? new X(d) : new Map(); this.h = (c = b.store || null) && c && c !== !0 && []; this.store = c ? d ? new X(d) : new Map() : null; this.cache = (c = a.cache || null) && new Ma(c); a.cache = !1; this.worker = a.worker || !1; this.priority = a.priority || 4; this.index = Hb.call(this, a, b); this.tag = null; if (c = b.tag) { if (typeof c === "string" && (c = [c]), c.length) { this.tag = new Map(); this.A = []; this.D = []; b = 0; for (var e = d = void 0; b < c.length; b++) { d = c[b]; e = d.field || d; if (!e) { throw Error("The tag field from the document descriptor is undefined."); } d.custom ? this.A[b] = d.custom : (this.A[b] = Gb(e, this.C), d.filter && (typeof this.A[b] === "string" && (this.A[b] = new String(this.A[b])), this.A[b].M = d.filter)); this.D[b] = e; this.tag.set(e, new Map()); } } } if (this.worker) { this.fastupdate = !1; a = []; c = D(this.index.values()); for (b = c.next(); !b.done; b = c.next()) { b = b.value, b.then && a.push(b); } if (a.length) { var f = this; return Promise.all(a).then(function(g) { for (var h = 0, k = D(f.index.entries()), l = k.next(); !l.done; l = k.next()) { var m = l.value; l = m[0]; m = m[1]; m.then && (m = g[h], f.index.set(l, m), h++); } return f; }); } } else { a.db && (this.fastupdate = !1, this.mount(a.db)); } } w = ob.prototype; w.mount = function(a) { if (this.worker) { throw Error("You can't use Worker-Indexes on a persistent model. That would be useless, since each of the persistent model acts like Worker-Index by default (Master/Slave)."); } var b = this.field; if (this.tag) { for (var c = 0, d = void 0; c < this.D.length; c++) { d = this.D[c]; var e = void 0; this.index.set(d, e = new db({}, this.reg)); b === this.field && (b = b.slice(0)); b.push(d); e.tag = this.tag.get(d); } } c = []; d = {db:a.db, type:a.type, fastupdate:a.fastupdate}; e = 0; var f = void 0; for (f = void 0; e < b.length; e++) { d.field = f = b[e]; f = this.index.get(f); var g = new a.constructor(a.id, d); g.id = a.id; c[e] = g.mount(f); f.document = !0; e ? f.bypass = !0 : f.store = this.store; } var h = this; return this.db = Promise.all(c).then(function() { h.db = !0; }); }; w.commit = function() { var a = this, b, c, d, e; return wa(function(f) { if (f.h == 1) { b = []; c = D(a.index.values()); for (d = c.next(); !d.done; d = c.next()) { e = d.value, b.push(e.commit()); } return L(f, Promise.all(b), 2); } a.reg.clear(); f.h = 0; }); }; w.destroy = function() { for (var a = [], b = D(this.index.values()), c = b.next(); !c.done; c = b.next()) { a.push(c.value.destroy()); } return Promise.all(a); }; function Hb(a, b) { var c = new Map(), d = b.index || b.field || b; U(d) && (d = [d]); for (var e = 0, f, g = void 0; e < d.length; e++) { f = d[e]; U(f) || (g = f, f = f.field); g = Ca(g) ? Object.assign({}, a, g) : a; if (this.worker) { var h = (h = g.encoder) && h.encode ? h : new Ja(typeof h === "string" ? Ta[h] : h || {}); h = new lb(g, h); c.set(f, h); } this.worker || c.set(f, new db(g, this.reg)); g.custom ? this.B[e] = g.custom : (this.B[e] = Gb(f, this.C), g.filter && (typeof this.B[e] === "string" && (this.B[e] = new String(this.B[e])), this.B[e].M = g.filter)); this.field[e] = f; } if (this.h) { for (a = b.store, U(a) && (a = [a]), b = 0; b < a.length; b++) { d = a[b], e = d.field || d, d.custom ? (this.h[b] = d.custom, d.custom.ia = e) : (this.h[b] = Gb(e, this.C), d.filter && (typeof this.h[b] === "string" && (this.h[b] = new String(this.h[b])), this.h[b].M = d.filter)); } } return c; } function Gb(a, b) { for (var c = a.split(":"), d = 0, e = 0; e < c.length; e++) { a = c[e], a[a.length - 1] === "]" && (a = a.substring(0, a.length - 2)) && (b[d] = !0), a && (c[d++] = a); } d < c.length && (c.length = d); return d > 1 ? c : c[0]; } w.append = function(a, b) { return this.add(a, b, !0); }; w.update = function(a, b) { return this.remove(a).add(a, b); }; w.remove = function(a) { Ca(a) && (a = Da(a, this.key)); for (var b = D(this.index.values()), c = b.next(); !c.done; c = b.next()) { c.value.remove(a, !0); } if (this.reg.has(a)) { if (this.tag && !this.fastupdate) { for (b = D(this.tag.values()), c = b.next(); !c.done; c = b.next()) { c = c.value; for (var d = D(c), e = d.next(); !e.done; e = d.next()) { var f = e.value; e = f[0]; f = f[1]; var g = f.indexOf(a); g > -1 && (f.length > 1 ? f.splice(g, 1) : c.delete(e)); } } } this.store && this.store.delete(a); this.reg.delete(a); } this.cache && this.cache.remove(a); return this; }; w.clear = function() { for (var a = [], b = D(this.index.values()), c = b.next(); !c.done; c = b.next()) { c = c.value.clear(), c.then && a.push(c); } if (this.tag) { for (b = D(this.tag.values()), c = b.next(); !c.done; c = b.next()) { c.value.clear(); } } this.store && this.store.clear(); this.cache && this.cache.clear(); return a.length ? Promise.all(a) : this; }; w.contain = function(a) { return this.db ? this.index.get(this.field[0]).db.has(a) : this.reg.has(a); }; w.cleanup = function() { for (var a = D(this.index.values()), b = a.next(); !b.done; b = a.next()) { b.value.cleanup(); } return this; }; w.get = function(a) { return this.db ? this.index.get(this.field[0]).db.enrich(a).then(function(b) { return b[0] && b[0].doc || null; }) : this.store.get(a) || null; }; w.set = function(a, b) { typeof a === "object" && (b = a, a = Da(b, this.key)); this.store.set(a, b); return this; }; w.searchCache = La; w.export = Ib; w.import = Jb; eb(ob.prototype); function Kb(a, b) { b = b === void 0 ? 0 : b; var c = [], d = []; b && (b = 250000 / b * 5000 | 0); a = D(a.entries()); for (var e = a.next(); !e.done; e = a.next()) { d.push(e.value), d.length === b && (c.push(d), d = []); } d.length && c.push(d); return c; } function Lb(a, b) { b || (b = new Map()); for (var c = 0, d; c < a.length; c++) { d = a[c], b.set(d[0], d[1]); } return b; } function Mb(a, b) { b = b === void 0 ? 0 : b; var c = [], d = []; b && (b = 250000 / b * 1000 | 0); a = D(a.entries()); for (var e = a.next(); !e.done; e = a.next()) { e = e.value, d.push([e[0], Kb(e[1])[0] || []]), d.length === b && (c.push(d), d = []); } d.length && c.push(d); return c; } function Nb(a, b) { b || (b = new Map()); for (var c = 0, d, e; c < a.length; c++) { d = a[c], e = b.get(d[0]), b.set(d[0], Lb(d[1], e)); } return b; } function Ob(a) { var b = [], c = []; a = D(a.keys()); for (var d = a.next(); !d.done; d = a.next()) { c.push(d.value), c.length === 250000 && (b.push(c), c = []); } c.length && b.push(c); return b; } function Pb(a, b) { b || (b = new Set()); for (var c = 0; c < a.length; c++) { b.add(a[c]); } return b; } function Qb(a, b, c, d, e, f, g) { g = g === void 0 ? 0 : g; var h = d && d.constructor === Array, k = h ? d.shift() : d; if (!k) { return this.export(a, b, e, f + 1); } if ((k = a((b ? b + "." : "") + (g + 1) + "." + c, JSON.stringify(k))) && k.then) { var l = this; return k.then(function() { return Qb.call(l, a, b, c, h ? d : null, e, f, g + 1); }); } return Qb.call(this, a, b, c, h ? d : null, e, f, g + 1); } function Ib(a, b, c, d) { c = c === void 0 ? 0 : c; d = d === void 0 ? 0 : d; if (c < this.field.length) { var e = this.field[c]; if ((b = this.index.get(e).export(a, e, c, d = 1)) && b.then) { var f = this; return b.then(function() { return f.export(a, e, c + 1); }); } return this.export(a, e, c + 1); } switch(d) { case 0: var g = "reg"; var h = Ob(this.reg); b = null; break; case 1: g = "tag"; h = this.tag && Mb(this.tag, this.reg.size); b = null; break; case 2: g = "doc"; h = this.store && Kb(this.store); b = null; break; default: return; } return Qb.call(this, a, b, g, h || null, c, d); } function Jb(a, b) { var c = a.split("."); c[c.length - 1] === "json" && c.pop(); var d = c.length > 2 ? c[0] : ""; c = c.length > 2 ? c[2] : c[1]; if (this.worker && d) { return this.index.get(d).import(a); } if (b) { typeof b === "string" && (b = JSON.parse(b)); if (d) { return this.index.get(d).import(c, b); } switch(c) { case "reg": this.fastupdate = !1; this.reg = Pb(b, this.reg); for (b = 0; b < this.field.length; b++) { d = this.index.get(this.field[b]), d.fastupdate = !1, d.reg = this.reg; } if (this.worker) { b = []; d = D(this.index.values()); for (c = d.next(); !c.done; c = d.next()) { b.push(c.value.import(a)); } return Promise.all(b); } break; case "tag": this.tag = Nb(b, this.tag); break; case "doc": this.store = Lb(b, this.store); } } } function Rb(a, b) { var c = ""; a = D(a.entries()); for (var d = a.next(); !d.done; d = a.next()) { var e = d.value; d = e[0]; e = e[1]; for (var f = "", g = 0, h; g < e.length; g++) { h = e[g] || [""]; for (var k = "", l = 0; l < h.length; l++) { k += (k ? "," : "") + (b === "string" ? '"' + h[l] + '"' : h[l]); } k = "[" + k + "]"; f += (f ? "," : "") + k; } f = '["' + d + '",[' + f + "]]"; c += (c ? "," : "") + f; } return c; } ;db.prototype.remove = function(a, b) { var c = this.reg.size && (this.fastupdate ? this.reg.get(a) : this.reg.has(a)); if (c) { if (this.fastupdate) { for (var d = 0, e = void 0, f = void 0; d < c.length; d++) { if ((e = c[d]) && (f = e.length)) { if (e[f - 1] === a) { e.pop(); } else { var g = e.indexOf(a); g >= 0 && e.splice(g, 1); } } } } else { Sb(this.map, a), this.depth && Sb(this.ctx, a); } b || this.reg.delete(a); } this.db && (this.commit_task.push({del:a}), this.$ && Tb(this)); this.cache && this.cache.remove(a); return this; }; function Sb(a, b) { var c = 0, d = typeof b === "undefined"; if (a.constructor === Array) { for (var e = 0, f = void 0, g, h = void 0; e < a.length; e++) { if ((f = a[e]) && f.length) { if (d) { return 1; } g = f.indexOf(b); if (g >= 0) { if (f.length > 1) { return f.splice(g, 1), 1; } delete a[e]; if (c) { return 1; } h = 1; } else { if (h) { return 1; } c++; } } } } else { for (d = D(a.entries()), e = d.next(); !e.done; e = d.next()) { e = e.value, f = e[0], Sb(e[1], b) ? c++ : a.delete(f); } } return c; } ;var Ub = {memory:{resolution:1}, performance:{resolution:3, fastupdate:!0, context:{depth:1, resolution:1}}, match:{tokenize:"full"}, score:{resolution:9, context:{depth:2, resolution:3}}}; db.prototype.add = function(a, b, c, d) { if (b && (a || a === 0)) { if (!d && !c && this.reg.has(a)) { return this.update(a, b); } d = this.depth; b = this.encoder.encode(b, !d); var e = b.length; if (e) { for (var f = S(), g = S(), h = this.resolution, k = 0; k < e; k++) { var l = b[this.rtl ? e - 1 - k : k], m = l.length; if (m && (d || !g[l])) { var p = this.score ? this.score(b, l, k, null, 0) : Vb(h, e, k), n = ""; switch(this.tokenize) { case "tolerant": Wb(this, g, l, p, a, c); if (m > 2) { for (var q = 1, t, u; q < m - 1; q++) { n = l.charAt(q), t = l.charAt(q + 1), t = l.substring(0, q) + t, u = l.substring(q + 2), n = t + n + u, Wb(this, g, n, p, a, c), n = t + u, Wb(this, g, n, p, a, c); } Wb(this, g, l.substring(0, l.length - 1), p, a, c); } break; case "full": if (m > 2) { for (p = 0; p < m; p++) { for (q = m; q > p; q--) { n = l.substring(p, q), t = this.rtl ? m - 1 - p : p, t = this.score ? this.score(b, l, k, n, t) : Vb(h, e, k, m, t), Wb(this, g, n, t, a, c); } } break; } case "bidirectional": case "reverse": if (m > 1) { for (q = m - 1; q > 0; q--) { n = l[this.rtl ? m - 1 - q : q] + n, t = this.score ? this.score(b, l, k, n, q) : Vb(h, e, k, m, q), Wb(this, g, n, t, a, c); } n = ""; } case "forward": if (m > 1) { for (q = 0; q < m; q++) { n += l[this.rtl ? m - 1 - q : q], Wb(this, g, n, p, a, c); } break; } default: if (Wb(this, g, l, p, a, c), d && e > 1 && k < e - 1) { for (m = this.ba, n = l, p = Math.min(d + 1, this.rtl ? k + 1 : e - k), q = 1; q < p; q++) { l = b[this.rtl ? e - 1 - k - q : k + q], t = this.bidirectional && l > n, u = this.score ? this.score(b, n, k, l, q - 1) : Vb(m + (e / 2 > m ? 0 : 1), e, k, p - 1, q - 1), Wb(this, f, t ? n : l, u, a, c, t ? l : n); } } } } } this.fastupdate || this.reg.add(a); } } this.db && (this.commit_task.push(c ? {ins:a} : {del:a}), this.$ && Tb(this)); return this; }; function Wb(a, b, c, d, e, f, g) { var h; if (!(h = b[c]) || g && !h[g]) { if (g) { b = h || (b[c] = S()); b[g] = 1; var k = a.ctx; (h = k.get(g)) ? k = h : k.set(g, k = a.keystore ? new X(a.keystore) : new Map()); } else { k = a.map, b[c] = 1; } (h = k.get(c)) ? k = h : k.set(c, k = h = []); if (f) { for (c = 0; c < h.length; c++) { if ((b = h[c]) && b.includes(e)) { if (c <= d) { return; } b.splice(b.indexOf(e), 1); a.fastupdate && (c = a.reg.get(e)) && c.splice(c.indexOf(b), 1); break; } } } k = k[d] || (k[d] = []); k.push(e); if (k.length === 2147483647) { b = new Va(k); if (a.fastupdate) { for (c = D(a.reg.values()), f = c.next(); !f.done; f = c.next()) { f = f.value, f.includes(k) && (f[f.indexOf(k)] = b); } } h[d] = k = b; } a.fastupdate && ((d = a.reg.get(e)) ? d.push(k) : a.reg.set(e, [k])); } } function Vb(a, b, c, d, e) { return c && a > 1 ? b + (d || 0) <= a ? c + (e || 0) : (a - 1) / (b + (d || 0)) * (c + (e || 0)) + 1 | 0 : 0; } ;db.prototype.search = function(a, b, c) { c || (b || typeof a !== "object" ? typeof b === "object" && (c = b, b = 0) : (c = a, a = "")); if (c && c.cache) { return c.cache = !1, a = this.searchCache(a, b, c), c.cache = !0, a; } var d = [], e = 0, f; if (c) { a = c.query || a; b = c.limit || b; e = c.offset || 0; var g = c.context; var h = c.suggest; var k = (f = c.resolve) && c.enrich; var l = c.boost; var m = c.resolution; var p = this.db && c.tag; } typeof f === "undefined" && (f = this.resolve); g = this.depth && g !== !1; var n = this.encoder.encode(a, !g); var q = n.length; b = b || (f ? 100 : 0); if (q === 1) { return Xb.call(this, n[0], "", b, e, f, k, p); } if (q === 2 && g && !h) { return Xb.call(this, n[1], n[0], b, e, f, k, p); } var t = S(), u = 0; if (g) { var v = n[0]; u = 1; } m || m === 0 || (m = v ? this.ba : this.resolution); if (this.db) { if (this.db.search && (c = this.db.search(this, n, b, e, h, f, k, p), c !== !1)) { return c; } var A = this; return function() { var y, B; return wa(function(C) { switch(C.h) { case 1: B = y = void 0; case 2: if (!(u < q)) { C.h = 4; break; } B = n[u]; if (!B || t[B]) { C.h = 5; break; } t[B] = 1; return L(C, Yb(A, B, v, 0, 0, !1, !1), 6); case 6: y = C.B; if (y = Zb(y, d, h, m)) { d = y; C.h = 4; break; } v && (h && y && d.length || (v = B)); case 5: h && v && u === q - 1 && !d.length && (m = A.resolution, v = "", u = -1, t = S()); u++; C.h = 2; break; case 4: return C.return($b(d, m, b, e, h, l, f)); } }); }(); } for (a = c = void 0; u < q; u++) { if ((a = n[u]) && !t[a]) { t[a] = 1; c = Yb(this, a, v, 0, 0, !1, !1); if (c = Zb(c, d, h, m)) { d = c; break; } v && (h && c && d.length || (v = a)); } h && v && u === q - 1 && !d.length && (m = this.resolution, v = "", u = -1, t = S()); } return $b(d, m, b, e, h, l, f); }; function $b(a, b, c, d, e, f, g) { var h = a.length, k = a; if (h > 1) { k = zb(a, b, c, d, e, f, g); } else if (h === 1) { return g ? rb.call(null, a[0], c, d) : new Z(a[0], this); } return g ? k : new Z(k, this); } function Xb(a, b, c, d, e, f, g) { a = Yb(this, a, b, c, d, e, f, g); return this.db ? a.then(function(h) { return e ? h || [] : new Z(h, this); }) : a && a.length ? e ? rb.call(this, a, c, d) : new Z(a, this) : e ? [] : new Z([], this); } function Zb(a, b, c, d) { var e = []; if (a && a.length) { if (a.length <= d) { b.push(a); return; } for (var f = 0, g; f < d; f++) { if (g = a[f]) { e[f] = g; } } if (e.length) { b.push(e); return; } } if (!c) { return e; } } function Yb(a, b, c, d, e, f, g, h) { var k; c && (k = a.bidirectional && b > c) && (k = c, c = b, b = k); if (a.db) { return a.db.get(b, c, d, e, f, g, h); } a = c ? (a = a.ctx.get(c)) && a.get(b) : a.map.get(b); return a; } ;function db(a, b) { if (!this || this.constructor !== db) { return new db(a); } if (a) { var c = U(a) ? a : a.preset; c && (Ub[c] || console.warn("Preset not found: " + c), a = Object.assign({}, Ub[c], a)); } else { a = {}; } c = a.context; var d = c === !0 ? {depth:1} : c || {}, e = U(a.encoder) ? Ta[a.encoder] : a.encode || a.encoder || {}; this.encoder = e.encode ? e : typeof e === "object" ? new Ja(e) : {encode:e}; this.resolution = a.resolution || 9; this.tokenize = c = (c = a.tokenize) && c !== "default" && c !== "exact" && c || "strict"; this.depth = c === "strict" && d.depth || 0; this.bidirectional = d.bidirectional !== !1; this.fastupdate = !!a.fastupdate; this.score = a.score || null; d && d.depth && this.tokenize !== "strict" && console.warn('Context-Search could not applied, because it is just supported when using the tokenizer "strict".'); (c = a.keystore || 0) && (this.keystore = c); this.map = c ? new X(c) : new Map(); this.ctx = c ? new X(c) : new Map(); this.reg = b || (this.fastupdate ? c ? new X(c) : new Map() : c ? new Y(c) : new Set()); this.ba = d.resolution || 3; this.rtl = e.rtl || a.rtl || !1; this.cache = (c = a.cache || null) && new Ma(c); this.resolve = a.resolve !== !1; if (c = a.db) { this.db = this.mount(c); } this.$ = a.commit !== !1; this.commit_task = []; this.commit_timer = null; this.priority = a.priority || 4; } w = db.prototype; w.mount = function(a) { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return a.mount(this); }; w.commit = function() { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return this.db.commit(this); }; w.destroy = function() { this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); return this.db.destroy(); }; function Tb(a) { a.commit_timer || (a.commit_timer = setTimeout(function() { a.commit_timer = null; a.db.commit(a); }, 1)); } w.clear = function() { this.map.clear(); this.ctx.clear(); this.reg.clear(); this.cache && this.cache.clear(); return this.db ? (this.commit_timer && clearTimeout(this.commit_timer), this.commit_timer = null, this.commit_task = [], this.db.clear()) : this; }; w.append = function(a, b) { return this.add(a, b, !0); }; w.contain = function(a) { return this.db ? this.db.has(a) : this.reg.has(a); }; w.update = function(a, b) { var c = this, d = this.remove(a); return d && d.then ? d.then(function() { return c.add(a, b); }) : this.add(a, b); }; w.cleanup = function() { if (!this.fastupdate) { return console.info('Cleanup the index isn\'t required when not using "fastupdate".'), this; } Sb(this.map); this.depth && Sb(this.ctx); return this; }; w.searchCache = La; w.export = function(a, b, c, d) { c = c === void 0 ? 0 : c; d = d === void 0 ? 0 : d; switch(d) { case 0: var e = "reg"; var f = Ob(this.reg); break; case 1: e = "cfg"; f = null; break; case 2: e = "map"; f = Kb(this.map, this.reg.size); break; case 3: e = "ctx"; f = Mb(this.ctx, this.reg.size); break; default: return; } return Qb.call(this, a, b, e, f, c, d); }; w.import = function(a, b) { if (b) { switch(typeof b === "string" && (b = JSON.parse(b)), a = a.split("."), a[a.length - 1] === "json" && a.pop(), a.length === 3 && a.shift(), a = a.length > 1 ? a[1] : a[0], a) { case "reg": this.fastupdate = !1; this.reg = Pb(b, this.reg); break; case "map": this.map = Lb(b, this.map); break; case "ctx": this.ctx = Nb(b, this.ctx); } } }; w.serialize = function(a) { a = a === void 0 ? !0 : a; var b = "", c = "", d = ""; if (this.reg.size) { var e; c = D(this.reg.keys()); for (var f = c.next(); !f.done; f = c.next()) { f = f.value, e || (e = typeof f), b += (b ? "," : "") + (e === "string" ? '"' + f + '"' : f); } b = "index.reg=new Set([" + b + "]);"; c = Rb(this.map, e); c = "index.map=new Map([" + c + "]);"; f = D(this.ctx.entries()); for (var g = f.next(); !g.done; g = f.next()) { var h = g.value; g = h[0]; h = Rb(h[1], e); h = "new Map([" + h + "])"; h = '["' + g + '",' + h + "]"; d += (d ? "," : "") + h; } d = "index.ctx=new Map([" + d + "]);"; } return a ? "function inject(index){" + b + c + d + "}" : b + c + d; }; eb(db.prototype); var ac = typeof window !== "undefined" && (window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB), bc = ["map", "ctx", "tag", "reg", "cfg"], cc = S(); function dc(a, b) { b = b === void 0 ? {} : b; if (!this || this.constructor !== dc) { return new dc(a, b); } typeof a === "object" && (b = a, a = a.name); a || console.info("Default storage space was used, because a name was not passed."); this.id = "flexsearch" + (a ? ":" + a.toLowerCase().replace(/[^a-z0-9_\-]/g, "") : ""); this.field = b.field ? b.field.toLowerCase().replace(/[^a-z0-9_\-]/g, "") : ""; this.type = b.type; this.fastupdate = this.support_tag_search = !1; this.db = null; this.h = {}; } w = dc.prototype; w.mount = function(a) { if (a.index) { return a.mount(this); } a.db = this; return this.open(); }; w.open = function() { if (this.db) { return this.db; } var a = this; navigator.storage && navigator.storage.persist && navigator.storage.persist(); cc[a.id] || (cc[a.id] = []); cc[a.id].push(a.field); var b = ac.open(a.id, 1); b.onupgradeneeded = function() { for (var c = a.db = this.result, d = 0, e; d < bc.length; d++) { e = bc[d]; for (var f = 0, g; f < cc[a.id].length; f++) { g = cc[a.id][f], c.objectStoreNames.contains(e + (e !== "reg" ? g ? ":" + g : "" : "")) || c.createObjectStore(e + (e !== "reg" ? g ? ":" + g : "" : "")); } } }; return a.db = ec(b, function(c) { a.db = c; a.db.onversionchange = function() { a.close(); }; }); }; w.close = function() { this.db && this.db.close(); this.db = null; }; w.destroy = function() { var a = ac.deleteDatabase(this.id); return ec(a); }; w.clear = function() { for (var a = [], b = 0, c; b < bc.length; b++) { c = bc[b]; for (var d = 0, e; d < cc[this.id].length; d++) { e = cc[this.id][d], a.push(c + (c !== "reg" ? e ? ":" + e : "" : "")); } } b = this.db.transaction(a, "readwrite"); for (c = 0; c < a.length; c++) { b.objectStore(a[c]).clear(); } return ec(b); }; w.get = function(a, b, c, d, e, f) { c = c === void 0 ? 0 : c; d = d === void 0 ? 0 : d; e = e === void 0 ? !0 : e; f = f === void 0 ? !1 : f; a = this.db.transaction((b ? "ctx" : "map") + (this.field ? ":" + this.field : ""), "readonly").objectStore((b ? "ctx" : "map") + (this.field ? ":" + this.field : "")).get(b ? b + ":" + a : a); var g = this; return ec(a).then(function(h) { var k = []; if (!h || !h.length) { return k; } if (e) { if (!c && !d && h.length === 1) { return h[0]; } for (var l = 0, m = void 0; l < h.length; l++) { if ((m = h[l]) && m.length) { if (d >= m.length) { d -= m.length; } else { for (var p = c ? d + Math.min(m.length - d, c) : m.length, n = d; n < p; n++) { k.push(m[n]); } d = 0; if (k.length === c) { break; } } } } return f ? g.enrich(k) : k; } return h; }); }; w.tag = function(a, b, c, d) { b = b === void 0 ? 0 : b; c = c === void 0 ? 0 : c; d = d === void 0 ? !1 : d; a = this.db.transaction("tag" + (this.field ? ":" + this.field : ""), "readonly").objectStore("tag" + (this.field ? ":" + this.field : "")).get(a); var e = this; return ec(a).then(function(f) { if (!f || !f.length || c >= f.length) { return []; } if (!b && !c) { return f; } f = f.slice(c, c + b); return d ? e.enrich(f) : f; }); }; w.enrich = function(a) { typeof a !== "object" && (a = [a]); for (var b = this.db.transaction("reg", "readonly").objectStore("reg"), c = [], d = 0; d < a.length; d++) { c[d] = ec(b.get(a[d])); } return Promise.all(c).then(function(e) { for (var f = 0; f < e.length; f++) { e[f] = {id:a[f], doc:e[f] ? JSON.parse(e[f]) : null}; } return e; }); }; w.has = function(a) { a = this.db.transaction("reg", "readonly").objectStore("reg").getKey(a); return ec(a).then(function(b) { return !!b; }); }; w.search = null; w.info = function() { }; w.transaction = function(a, b, c) { a += a !== "reg" ? this.field ? ":" + this.field : "" : ""; var d = this.h[a + ":" + b]; if (d) { return c.call(this, d); } var e = this.db.transaction(a, b); this.h[a + ":" + b] = d = e.objectStore(a); var f = c.call(this, d); this.h[a + ":" + b] = null; return ec(e).finally(function() { return f; }); }; w.commit = function(a) { var b = this, c, d, e, f; return wa(function(g) { switch(g.h) { case 1: c = a.commit_task; d = []; a.commit_task = []; e = 0; for (f = void 0; e < c.length; e++) { f = c[e], f.del && d.push(f.del); } if (!d.length) { g.h = 2; break; } return L(g, b.remove(d), 2); case 2: return a.reg.size ? L(g, b.transaction("map", "readwrite", function(h) { for (var k = D(a.map), l = k.next(), m = {}; !l.done; m = {N:void 0, W:void 0}, l = k.next()) { l = l.value, m.W = l[0], m.N = l[1], m.N.length && (h.get(m.W).onsuccess = function(p) { return function() { var n = this.result, q; if (n && n.length) { for (var t = Math.max(n.length, p.N.length), u = 0, v; u < t; u++) { if ((v = p.N[u]) && v.length) { if ((q = n[u]) && q.length) { for (var A = 0; A < v.length; A++) { q.push(v[A]); } } else { n[u] = v; } q = 1; } } } else { n = p.N, q = 1; } q && h.put(n, p.W); }; }(m)); } }), 4) : g.return(); case 4: return L(g, b.transaction("ctx", "readwrite", function(h) { for (var k = D(a.ctx), l = k.next(), m = {}; !l.done; m = {V:void 0}, l = k.next()) { l = l.value; m.V = l[0]; l = D(l[1]); for (var p = l.next(), n = {}; !p.done; n = {O:void 0, X:void 0}, p = l.next()) { p = p.value, n.X = p[0], n.O = p[1], n.O.length && (h.get(m.V + ":" + n.X).onsuccess = function(q, t) { return function() { var u = this.result, v; if (u && u.length) { for (var A = Math.max(u.length, q.O.length), y = 0, B; y < A; y++) { if ((B = q.O[y]) && B.length) { if ((v = u[y]) && v.length) { for (var C = 0; C < B.length; C++) { v.push(B[C]); } } else { u[y] = B; } v = 1; } } } else { u = q.O, v = 1; } v && h.put(u, t.V + ":" + q.X); }; }(n, m)); } } }), 5); case 5: if (a.store) { return L(g, b.transaction("reg", "readwrite", function(h) { for (var k = D(a.store), l = k.next(); !l.done; l = k.next()) { var m = l.value; l = m[0]; m = m[1]; h.put(typeof m === "object" ? JSON.stringify(m) : 1, l); } }), 7); } if (a.bypass) { g.h = 7; break; } return L(g, b.transaction("reg", "readwrite", function(h) { for (var k = D(a.reg.keys()), l = k.next(); !l.done; l = k.next()) { h.put(1, l.value); } }), 7); case 7: if (!a.tag) { g.h = 11; break; } return L(g, b.transaction("tag", "readwrite", function(h) { for (var k = D(a.tag), l = k.next(), m = {}; !l.done; m = {S:void 0, Z:void 0}, l = k.next()) { l = l.value, m.Z = l[0], m.S = l[1], m.S.length && (h.get(m.Z).onsuccess = function(p) { return function() { var n = this.result; n = n && n.length ? n.concat(p.S) : p.S; h.put(n, p.Z); }; }(m)); } }), 11); case 11: a.map.clear(), a.ctx.clear(), a.tag && a.tag.clear(), a.store && a.store.clear(), a.document || a.reg.clear(), g.h = 0; } }); }; function fc(a, b, c) { for (var d = a.value, e, f = 0, g = 0, h; g < d.length; g++) { if (h = c ? d : d[g]) { for (var k = 0, l; k < b.length; k++) { if (l = b[k], l = h.indexOf(l), l >= 0) { if (e = 1, h.length > 1) { h.splice(l, 1); } else { d[g] = []; break; } } } f += h.length; } if (c) { break; } } f ? e && a.update(d) : a.delete(); a.continue(); } w.remove = function(a) { typeof a !== "object" && (a = [a]); return Promise.all([this.transaction("map", "readwrite", function(b) { b.openCursor().onsuccess = function() { var c = this.result; c && fc(c, a); }; }), this.transaction("ctx", "readwrite", function(b) { b.openCursor().onsuccess = function() { var c = this.result; c && fc(c, a); }; }), this.transaction("tag", "readwrite", function(b) { b.openCursor().onsuccess = function() { var c = this.result; c && fc(c, a, !0); }; }), this.transaction("reg", "readwrite", function(b) { for (var c = 0; c < a.length; c++) { b.delete(a[c]); } })]); }; function ec(a, b) { return new Promise(function(c, d) { a.onsuccess = a.oncomplete = function() { b && b(this.result); b = null; c(this.result); }; a.onerror = a.onblocked = d; a = null; }); } ;var hc = {Index:db, Charset:Ta, Encoder:Ja, Document:ob, Worker:lb, Resolver:Z, IndexedDB:dc, Language:{}}, ic = typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : self, jc; (jc = ic.define) && jc.amd ? jc([], function() { return hc; }) : typeof ic.exports === "object" ? ic.exports = hc : ic.FlexSearch = hc; }(this||self)); ================================================ FILE: dist/flexsearch.light.debug.js ================================================ /**! * FlexSearch.js v0.8.214 (Light/Debug) * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ (function(self){'use strict'; var r; function v(a, c, b) { const f = typeof b, d = typeof a; if (f !== "undefined") { if (d !== "undefined") { if (b) { if (d === "function" && f === d) { return function(k) { return a(b(k)); }; } c = a.constructor; if (c === b.constructor) { if (c === Array) { return b.concat(a); } if (c === Map) { var h = new Map(b); for (var e of a) { h.set(e[0], e[1]); } return h; } if (c === Set) { e = new Set(b); for (h of a.values()) { e.add(h); } return e; } } } return a; } return b; } return d === "undefined" ? c : a; } function z() { return Object.create(null); } ;const A = /[^\p{L}\p{N}]+/u, B = /(\d{3})/g, C = /(\D)(\d{3})/g, D = /(\d{3})(\D)/g, E = /[\u0300-\u036f]/g; function F(a = {}) { if (!this || this.constructor !== F) { return new F(...arguments); } if (arguments.length) { for (a = 0; a < arguments.length; a++) { this.assign(arguments[a]); } } else { this.assign(a); } } r = F.prototype; r.assign = function(a) { this.normalize = v(a.normalize, !0, this.normalize); let c = a.include, b = c || a.exclude || a.split, f; if (b || b === "") { if (typeof b === "object" && b.constructor !== RegExp) { let d = ""; f = !c; c || (d += "\\p{Z}"); b.letter && (d += "\\p{L}"); b.number && (d += "\\p{N}", f = !!c); b.symbol && (d += "\\p{S}"); b.punctuation && (d += "\\p{P}"); b.control && (d += "\\p{C}"); if (b = b.char) { d += typeof b === "object" ? b.join("") : b; } try { this.split = new RegExp("[" + (c ? "^" : "") + d + "]+", "u"); } catch (h) { console.error("Your split configuration:", b, "is not supported on this platform. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } } else { this.split = b, f = b === !1 || "a1a".split(b).length < 2; } this.numeric = v(a.numeric, f); } else { try { this.split = v(this.split, A); } catch (d) { console.warn("This platform does not support unicode regex. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } this.numeric = v(a.numeric, v(this.numeric, !0)); } this.prepare = v(a.prepare, null, this.prepare); this.finalize = v(a.finalize, null, this.finalize); b = a.filter; this.filter = typeof b === "function" ? b : v(b && new Set(b), null, this.filter); this.dedupe = v(a.dedupe, !0, this.dedupe); this.matcher = v((b = a.matcher) && new Map(b), null, this.matcher); this.mapper = v((b = a.mapper) && new Map(b), null, this.mapper); this.stemmer = v((b = a.stemmer) && new Map(b), null, this.stemmer); this.replacer = v(a.replacer, null, this.replacer); this.minlength = v(a.minlength, 1, this.minlength); this.maxlength = v(a.maxlength, 1024, this.maxlength); this.rtl = v(a.rtl, !1, this.rtl); if (this.cache = b = v(a.cache, !0, this.cache)) { this.l = null, this.A = typeof b === "number" ? b : 2e5, this.i = new Map(), this.j = new Map(), this.o = this.m = 128; } this.g = ""; this.s = null; this.h = ""; this.u = null; if (this.matcher) { for (const d of this.matcher.keys()) { this.g += (this.g ? "|" : "") + d; } } if (this.stemmer) { for (const d of this.stemmer.keys()) { this.h += (this.h ? "|" : "") + d; } } return this; }; r.addStemmer = function(a, c) { this.stemmer || (this.stemmer = new Map()); this.stemmer.set(a, c); this.h += (this.h ? "|" : "") + a; this.u = null; this.cache && G(this); return this; }; r.addFilter = function(a) { typeof a === "function" ? this.filter = a : (this.filter || (this.filter = new Set()), this.filter.add(a)); this.cache && G(this); return this; }; r.addMapper = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length > 1) { return this.addMatcher(a, c); } this.mapper || (this.mapper = new Map()); this.mapper.set(a, c); this.cache && G(this); return this; }; r.addMatcher = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length < 2 && (this.dedupe || this.mapper)) { return this.addMapper(a, c); } this.matcher || (this.matcher = new Map()); this.matcher.set(a, c); this.g += (this.g ? "|" : "") + a; this.s = null; this.cache && G(this); return this; }; r.addReplacer = function(a, c) { if (typeof a === "string") { return this.addMatcher(a, c); } this.replacer || (this.replacer = []); this.replacer.push(a, c); this.cache && G(this); return this; }; r.encode = function(a, c) { if (this.cache && a.length <= this.m) { if (this.l) { if (this.i.has(a)) { return this.i.get(a); } } else { this.l = setTimeout(G, 50, this); } } this.normalize && (typeof this.normalize === "function" ? a = this.normalize(a) : a = E ? a.normalize("NFKD").replace(E, "").toLowerCase() : a.toLowerCase()); this.prepare && (a = this.prepare(a)); this.numeric && a.length > 3 && (a = a.replace(C, "$1 $2").replace(D, "$1 $2").replace(B, "$1 ")); const b = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer); let f = [], d = z(), h, e, k = this.split || this.split === "" ? a.split(this.split) : [a]; for (let n = 0, g, t; n < k.length; n++) { if ((g = t = k[n]) && !(g.length < this.minlength || g.length > this.maxlength)) { if (c) { if (d[g]) { continue; } d[g] = 1; } else { if (h === g) { continue; } h = g; } if (b) { f.push(g); } else { if (!this.filter || (typeof this.filter === "function" ? this.filter(g) : !this.filter.has(g))) { if (this.cache && g.length <= this.o) { if (this.l) { var l = this.j.get(g); if (l || l === "") { l && f.push(l); continue; } } else { this.l = setTimeout(G, 50, this); } } if (this.stemmer) { this.u || (this.u = new RegExp("(?!^)(" + this.h + ")$")); let w; for (; w !== g && g.length > 2;) { w = g, g = g.replace(this.u, q => this.stemmer.get(q)); } } if (g && (this.mapper || this.dedupe && g.length > 1)) { l = ""; for (let w = 0, q = "", m, p; w < g.length; w++) { m = g.charAt(w), m === q && this.dedupe || ((p = this.mapper && this.mapper.get(m)) || p === "" ? p === q && this.dedupe || !(q = p) || (l += p) : l += q = m); } g = l; } this.matcher && g.length > 1 && (this.s || (this.s = new RegExp("(" + this.g + ")", "g")), g = g.replace(this.s, w => this.matcher.get(w))); if (g && this.replacer) { for (l = 0; g && l < this.replacer.length; l += 2) { g = g.replace(this.replacer[l], this.replacer[l + 1]); } } this.cache && t.length <= this.o && (this.j.set(t, g), this.j.size > this.A && (this.j.clear(), this.o = this.o / 1.1 | 0)); if (g) { if (g !== t) { if (c) { if (d[g]) { continue; } d[g] = 1; } else { if (e === g) { continue; } e = g; } } f.push(g); } } } } } this.finalize && (f = this.finalize(f) || f); this.cache && a.length <= this.m && (this.i.set(a, f), this.i.size > this.A && (this.i.clear(), this.m = this.m / 1.1 | 0)); return f; }; function G(a) { a.l = null; a.i.clear(); a.j.clear(); } ;function H(a, c, b) { if (!a.length) { return a; } if (a.length === 1) { return a = a[0], a = b || a.length > c ? a.slice(b, b + c) : a; } let f = []; for (let d = 0, h, e; d < a.length; d++) { if ((h = a[d]) && (e = h.length)) { if (b) { if (b >= e) { b -= e; continue; } h = h.slice(b, b + c); e = h.length; b = 0; } e > c && (h = h.slice(0, c), e = c); if (!f.length && e >= c) { return h; } f.push(h); c -= e; if (!c) { break; } } } return f = f.length > 1 ? [].concat.apply([], f) : f[0]; } ;z(); J.prototype.remove = function(a, c) { const b = this.reg.size && (this.fastupdate ? this.reg.get(a) : this.reg.has(a)); if (b) { if (this.fastupdate) { for (let f = 0, d, h; f < b.length; f++) { if ((d = b[f]) && (h = d.length)) { if (d[h - 1] === a) { d.pop(); } else { const e = d.indexOf(a); e >= 0 && d.splice(e, 1); } } } } else { K(this.map, a), this.depth && K(this.ctx, a); } c || this.reg.delete(a); } return this; }; function K(a, c) { let b = 0; var f = typeof c === "undefined"; if (a.constructor === Array) { for (let d = 0, h, e, k; d < a.length; d++) { if ((h = a[d]) && h.length) { if (f) { return 1; } e = h.indexOf(c); if (e >= 0) { if (h.length > 1) { return h.splice(e, 1), 1; } delete a[d]; if (b) { return 1; } k = 1; } else { if (k) { return 1; } b++; } } } } else { for (let d of a.entries()) { f = d[0], K(d[1], c) ? b++ : a.delete(f); } } return b; } ;const L = {memory:{resolution:1}, performance:{resolution:3, fastupdate:!0, context:{depth:1, resolution:1}}, match:{tokenize:"full"}, score:{resolution:9, context:{depth:2, resolution:3}}}; J.prototype.add = function(a, c, b, f) { if (c && (a || a === 0)) { if (!f && !b && this.reg.has(a)) { return this.update(a, c); } f = this.depth; c = this.encoder.encode(c, !f); const n = c.length; if (n) { const g = z(), t = z(), w = this.resolution; for (let q = 0; q < n; q++) { let m = c[this.rtl ? n - 1 - q : q]; var d = m.length; if (d && (f || !t[m])) { var h = this.score ? this.score(c, m, q, null, 0) : M(w, n, q), e = ""; switch(this.tokenize) { case "tolerant": N(this, t, m, h, a, b); if (d > 2) { for (let p = 1, u, y, x, I; p < d - 1; p++) { u = m.charAt(p), y = m.charAt(p + 1), x = m.substring(0, p) + y, I = m.substring(p + 2), e = x + u + I, N(this, t, e, h, a, b), e = x + I, N(this, t, e, h, a, b); } N(this, t, m.substring(0, m.length - 1), h, a, b); } break; case "full": if (d > 2) { for (let p = 0, u; p < d; p++) { for (h = d; h > p; h--) { e = m.substring(p, h); u = this.rtl ? d - 1 - p : p; var k = this.score ? this.score(c, m, q, e, u) : M(w, n, q, d, u); N(this, t, e, k, a, b); } } break; } case "bidirectional": case "reverse": if (d > 1) { for (k = d - 1; k > 0; k--) { e = m[this.rtl ? d - 1 - k : k] + e; var l = this.score ? this.score(c, m, q, e, k) : M(w, n, q, d, k); N(this, t, e, l, a, b); } e = ""; } case "forward": if (d > 1) { for (k = 0; k < d; k++) { e += m[this.rtl ? d - 1 - k : k], N(this, t, e, h, a, b); } break; } default: if (N(this, t, m, h, a, b), f && n > 1 && q < n - 1) { for (d = this.v, e = m, h = Math.min(f + 1, this.rtl ? q + 1 : n - q), k = 1; k < h; k++) { m = c[this.rtl ? n - 1 - q - k : q + k]; l = this.bidirectional && m > e; const p = this.score ? this.score(c, e, q, m, k - 1) : M(d + (n / 2 > d ? 0 : 1), n, q, h - 1, k - 1); N(this, g, l ? e : m, p, a, b, l ? m : e); } } } } } this.fastupdate || this.reg.add(a); } } return this; }; function N(a, c, b, f, d, h, e) { let k, l; if (!(k = c[b]) || e && !k[e]) { e ? (c = k || (c[b] = z()), c[e] = 1, l = a.ctx, (k = l.get(e)) ? l = k : l.set(e, l = new Map())) : (l = a.map, c[b] = 1); (k = l.get(b)) ? l = k : l.set(b, l = k = []); if (h) { for (let n = 0, g; n < k.length; n++) { if ((g = k[n]) && g.includes(d)) { if (n <= f) { return; } g.splice(g.indexOf(d), 1); a.fastupdate && (c = a.reg.get(d)) && c.splice(c.indexOf(g), 1); break; } } } l = l[f] || (l[f] = []); l.push(d); a.fastupdate && ((f = a.reg.get(d)) ? f.push(l) : a.reg.set(d, [l])); } } function M(a, c, b, f, d) { return b && a > 1 ? c + (f || 0) <= a ? b + (d || 0) : (a - 1) / (c + (f || 0)) * (b + (d || 0)) + 1 | 0 : 0; } ;J.prototype.search = function(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : (b = a, a = "")); var f = [], d = 0; if (b) { a = b.query || a; c = b.limit || c; d = b.offset || 0; var h = b.context; var e = b.suggest; var k = !0; var l = b.resolution; } typeof k === "undefined" && (k = !0); h = this.depth && h !== !1; a = this.encoder.encode(a, !h); b = a.length; c = c || (k ? 100 : 0); if (b === 1) { return e = d, (d = O(this, a[0], "")) && d.length ? H.call(this, d, c, e) : []; } if (b === 2 && h && !e) { return e = d, (d = O(this, a[1], a[0])) && d.length ? H.call(this, d, c, e) : []; } k = z(); var n = 0; if (h) { var g = a[0]; n = 1; } l || l === 0 || (l = g ? this.v : this.resolution); for (let m, p; n < b; n++) { if ((p = a[n]) && !k[p]) { k[p] = 1; m = O(this, p, g); a: { h = m; var t = f, w = e, q = l; let u = []; if (h && h.length) { if (h.length <= q) { t.push(h); m = void 0; break a; } for (let y = 0, x; y < q; y++) { if (x = h[y]) { u[y] = x; } } if (u.length) { t.push(u); m = void 0; break a; } } m = w ? void 0 : u; } if (m) { f = m; break; } g && (e && m && f.length || (g = p)); } e && g && n === b - 1 && !f.length && (l = this.resolution, g = "", n = -1, k = z()); } a: { a = f; f = a.length; g = a; if (f > 1) { b: { f = e; g = a.length; e = []; b = z(); for (let m = 0, p, u, y, x; m < l; m++) { for (n = 0; n < g; n++) { if (y = a[n], m < y.length && (p = y[m])) { for (h = 0; h < p.length; h++) { if (u = p[h], (k = b[u]) ? b[u]++ : (k = 0, b[u] = 1), x = e[k] || (e[k] = []), x.push(u), c && k === g - 1 && x.length - d === c) { g = d ? x.slice(d) : x; break b; } } } } } if (a = e.length) { if (f) { if (e.length > 1) { c: { for (a = [], l = z(), f = e.length, k = f - 1; k >= 0; k--) { if (b = (f = e[k]) && f.length) { for (n = 0; n < b; n++) { if (g = f[n], !l[g]) { if (l[g] = 1, d) { d--; } else { if (a.push(g), a.length === c) { break c; } } } } } } } } else { a = (e = e[0]) && c && e.length > c || d ? e.slice(d, c + d) : e; } e = a; } else { if (a < g) { g = []; break b; } e = e[a - 1]; if (c || d) { if (e.length > c || d) { e = e.slice(d, c + d); } } } } g = e; } } else if (f === 1) { c = H.call(null, a[0], c, d); break a; } c = g; } return c; }; function O(a, c, b) { let f; b && (f = a.bidirectional && c > b) && (f = b, b = c, c = f); a = b ? (a = a.ctx.get(b)) && a.get(c) : a.map.get(c); return a; } ;function J(a, c) { if (!this || this.constructor !== J) { return new J(a); } if (a) { var b = typeof a === "string" ? a : a.preset; b && (L[b] || console.warn("Preset not found: " + b), a = Object.assign({}, L[b], a)); } else { a = {}; } b = a.context; const f = b === !0 ? {depth:1} : b || {}, d = a.encode || a.encoder || {}; this.encoder = d.encode ? d : typeof d === "object" ? new F(d) : {encode:d}; this.resolution = a.resolution || 9; this.tokenize = b = (b = a.tokenize) && b !== "default" && b !== "exact" && b || "strict"; this.depth = b === "strict" && f.depth || 0; this.bidirectional = f.bidirectional !== !1; this.fastupdate = !!a.fastupdate; this.score = a.score || null; f && f.depth && this.tokenize !== "strict" && console.warn('Context-Search could not applied, because it is just supported when using the tokenizer "strict".'); this.map = new Map(); this.ctx = new Map(); this.reg = c || (this.fastupdate ? new Map() : new Set()); this.v = f.resolution || 3; this.rtl = d.rtl || a.rtl || !1; } r = J.prototype; r.clear = function() { this.map.clear(); this.ctx.clear(); this.reg.clear(); return this; }; r.append = function(a, c) { return this.add(a, c, !0); }; r.contain = function(a) { return this.reg.has(a); }; r.update = function(a, c) { const b = this, f = this.remove(a); return f && f.then ? f.then(() => b.add(a, c)) : this.add(a, c); }; r.cleanup = function() { if (!this.fastupdate) { return console.info('Cleanup the index isn\'t required when not using "fastupdate".'), this; } K(this.map); this.depth && K(this.ctx); return this; }; z(); const P = {Index:J, Charset:null, Encoder:F, Document:null, Worker:null, Resolver:null, IndexedDB:null, Language:{}}, Q = typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : self; let R; (R = Q.define) && R.amd ? R([], function() { return P; }) : typeof Q.exports === "object" ? Q.exports = P : Q.FlexSearch = P; }(this||self)); ================================================ FILE: dist/flexsearch.light.module.debug.js ================================================ /**! * FlexSearch.js v0.8.214 (Bundle/Debug) * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ var r; function v(a, c, b) { const f = typeof b, d = typeof a; if (f !== "undefined") { if (d !== "undefined") { if (b) { if (d === "function" && f === d) { return function(k) { return a(b(k)); }; } c = a.constructor; if (c === b.constructor) { if (c === Array) { return b.concat(a); } if (c === Map) { var h = new Map(b); for (var e of a) { h.set(e[0], e[1]); } return h; } if (c === Set) { e = new Set(b); for (h of a.values()) { e.add(h); } return e; } } } return a; } return b; } return d === "undefined" ? c : a; } function z() { return Object.create(null); } ;const A = /[^\p{L}\p{N}]+/u, B = /(\d{3})/g, C = /(\D)(\d{3})/g, D = /(\d{3})(\D)/g, E = /[\u0300-\u036f]/g; function F(a = {}) { if (!this || this.constructor !== F) { return new F(...arguments); } if (arguments.length) { for (a = 0; a < arguments.length; a++) { this.assign(arguments[a]); } } else { this.assign(a); } } r = F.prototype; r.assign = function(a) { this.normalize = v(a.normalize, !0, this.normalize); let c = a.include, b = c || a.exclude || a.split, f; if (b || b === "") { if (typeof b === "object" && b.constructor !== RegExp) { let d = ""; f = !c; c || (d += "\\p{Z}"); b.letter && (d += "\\p{L}"); b.number && (d += "\\p{N}", f = !!c); b.symbol && (d += "\\p{S}"); b.punctuation && (d += "\\p{P}"); b.control && (d += "\\p{C}"); if (b = b.char) { d += typeof b === "object" ? b.join("") : b; } try { this.split = new RegExp("[" + (c ? "^" : "") + d + "]+", "u"); } catch (h) { console.error("Your split configuration:", b, "is not supported on this platform. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } } else { this.split = b, f = b === !1 || "a1a".split(b).length < 2; } this.numeric = v(a.numeric, f); } else { try { this.split = v(this.split, A); } catch (d) { console.warn("This platform does not support unicode regex. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; } this.numeric = v(a.numeric, v(this.numeric, !0)); } this.prepare = v(a.prepare, null, this.prepare); this.finalize = v(a.finalize, null, this.finalize); b = a.filter; this.filter = typeof b === "function" ? b : v(b && new Set(b), null, this.filter); this.dedupe = v(a.dedupe, !0, this.dedupe); this.matcher = v((b = a.matcher) && new Map(b), null, this.matcher); this.mapper = v((b = a.mapper) && new Map(b), null, this.mapper); this.stemmer = v((b = a.stemmer) && new Map(b), null, this.stemmer); this.replacer = v(a.replacer, null, this.replacer); this.minlength = v(a.minlength, 1, this.minlength); this.maxlength = v(a.maxlength, 1024, this.maxlength); this.rtl = v(a.rtl, !1, this.rtl); if (this.cache = b = v(a.cache, !0, this.cache)) { this.l = null, this.A = typeof b === "number" ? b : 2e5, this.i = new Map(), this.j = new Map(), this.o = this.m = 128; } this.g = ""; this.s = null; this.h = ""; this.u = null; if (this.matcher) { for (const d of this.matcher.keys()) { this.g += (this.g ? "|" : "") + d; } } if (this.stemmer) { for (const d of this.stemmer.keys()) { this.h += (this.h ? "|" : "") + d; } } return this; }; r.addStemmer = function(a, c) { this.stemmer || (this.stemmer = new Map()); this.stemmer.set(a, c); this.h += (this.h ? "|" : "") + a; this.u = null; this.cache && G(this); return this; }; r.addFilter = function(a) { typeof a === "function" ? this.filter = a : (this.filter || (this.filter = new Set()), this.filter.add(a)); this.cache && G(this); return this; }; r.addMapper = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length > 1) { return this.addMatcher(a, c); } this.mapper || (this.mapper = new Map()); this.mapper.set(a, c); this.cache && G(this); return this; }; r.addMatcher = function(a, c) { if (typeof a === "object") { return this.addReplacer(a, c); } if (a.length < 2 && (this.dedupe || this.mapper)) { return this.addMapper(a, c); } this.matcher || (this.matcher = new Map()); this.matcher.set(a, c); this.g += (this.g ? "|" : "") + a; this.s = null; this.cache && G(this); return this; }; r.addReplacer = function(a, c) { if (typeof a === "string") { return this.addMatcher(a, c); } this.replacer || (this.replacer = []); this.replacer.push(a, c); this.cache && G(this); return this; }; r.encode = function(a, c) { if (this.cache && a.length <= this.m) { if (this.l) { if (this.i.has(a)) { return this.i.get(a); } } else { this.l = setTimeout(G, 50, this); } } this.normalize && (typeof this.normalize === "function" ? a = this.normalize(a) : a = E ? a.normalize("NFKD").replace(E, "").toLowerCase() : a.toLowerCase()); this.prepare && (a = this.prepare(a)); this.numeric && a.length > 3 && (a = a.replace(C, "$1 $2").replace(D, "$1 $2").replace(B, "$1 ")); const b = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer); let f = [], d = z(), h, e, k = this.split || this.split === "" ? a.split(this.split) : [a]; for (let n = 0, g, t; n < k.length; n++) { if ((g = t = k[n]) && !(g.length < this.minlength || g.length > this.maxlength)) { if (c) { if (d[g]) { continue; } d[g] = 1; } else { if (h === g) { continue; } h = g; } if (b) { f.push(g); } else { if (!this.filter || (typeof this.filter === "function" ? this.filter(g) : !this.filter.has(g))) { if (this.cache && g.length <= this.o) { if (this.l) { var l = this.j.get(g); if (l || l === "") { l && f.push(l); continue; } } else { this.l = setTimeout(G, 50, this); } } if (this.stemmer) { this.u || (this.u = new RegExp("(?!^)(" + this.h + ")$")); let w; for (; w !== g && g.length > 2;) { w = g, g = g.replace(this.u, q => this.stemmer.get(q)); } } if (g && (this.mapper || this.dedupe && g.length > 1)) { l = ""; for (let w = 0, q = "", m, p; w < g.length; w++) { m = g.charAt(w), m === q && this.dedupe || ((p = this.mapper && this.mapper.get(m)) || p === "" ? p === q && this.dedupe || !(q = p) || (l += p) : l += q = m); } g = l; } this.matcher && g.length > 1 && (this.s || (this.s = new RegExp("(" + this.g + ")", "g")), g = g.replace(this.s, w => this.matcher.get(w))); if (g && this.replacer) { for (l = 0; g && l < this.replacer.length; l += 2) { g = g.replace(this.replacer[l], this.replacer[l + 1]); } } this.cache && t.length <= this.o && (this.j.set(t, g), this.j.size > this.A && (this.j.clear(), this.o = this.o / 1.1 | 0)); if (g) { if (g !== t) { if (c) { if (d[g]) { continue; } d[g] = 1; } else { if (e === g) { continue; } e = g; } } f.push(g); } } } } } this.finalize && (f = this.finalize(f) || f); this.cache && a.length <= this.m && (this.i.set(a, f), this.i.size > this.A && (this.i.clear(), this.m = this.m / 1.1 | 0)); return f; }; function G(a) { a.l = null; a.i.clear(); a.j.clear(); } ;function I(a, c, b) { if (!a.length) { return a; } if (a.length === 1) { return a = a[0], a = b || a.length > c ? a.slice(b, b + c) : a; } let f = []; for (let d = 0, h, e; d < a.length; d++) { if ((h = a[d]) && (e = h.length)) { if (b) { if (b >= e) { b -= e; continue; } h = h.slice(b, b + c); e = h.length; b = 0; } e > c && (h = h.slice(0, c), e = c); if (!f.length && e >= c) { return h; } f.push(h); c -= e; if (!c) { break; } } } return f = f.length > 1 ? [].concat.apply([], f) : f[0]; } ;z(); J.prototype.remove = function(a, c) { const b = this.reg.size && (this.fastupdate ? this.reg.get(a) : this.reg.has(a)); if (b) { if (this.fastupdate) { for (let f = 0, d, h; f < b.length; f++) { if ((d = b[f]) && (h = d.length)) { if (d[h - 1] === a) { d.pop(); } else { const e = d.indexOf(a); e >= 0 && d.splice(e, 1); } } } } else { K(this.map, a), this.depth && K(this.ctx, a); } c || this.reg.delete(a); } return this; }; function K(a, c) { let b = 0; var f = typeof c === "undefined"; if (a.constructor === Array) { for (let d = 0, h, e, k; d < a.length; d++) { if ((h = a[d]) && h.length) { if (f) { return 1; } e = h.indexOf(c); if (e >= 0) { if (h.length > 1) { return h.splice(e, 1), 1; } delete a[d]; if (b) { return 1; } k = 1; } else { if (k) { return 1; } b++; } } } } else { for (let d of a.entries()) { f = d[0], K(d[1], c) ? b++ : a.delete(f); } } return b; } ;const L = {memory:{resolution:1}, performance:{resolution:3, fastupdate:!0, context:{depth:1, resolution:1}}, match:{tokenize:"full"}, score:{resolution:9, context:{depth:2, resolution:3}}}; J.prototype.add = function(a, c, b, f) { if (c && (a || a === 0)) { if (!f && !b && this.reg.has(a)) { return this.update(a, c); } f = this.depth; c = this.encoder.encode(c, !f); const n = c.length; if (n) { const g = z(), t = z(), w = this.resolution; for (let q = 0; q < n; q++) { let m = c[this.rtl ? n - 1 - q : q]; var d = m.length; if (d && (f || !t[m])) { var h = this.score ? this.score(c, m, q, null, 0) : M(w, n, q), e = ""; switch(this.tokenize) { case "tolerant": N(this, t, m, h, a, b); if (d > 2) { for (let p = 1, u, y, x, H; p < d - 1; p++) { u = m.charAt(p), y = m.charAt(p + 1), x = m.substring(0, p) + y, H = m.substring(p + 2), e = x + u + H, N(this, t, e, h, a, b), e = x + H, N(this, t, e, h, a, b); } N(this, t, m.substring(0, m.length - 1), h, a, b); } break; case "full": if (d > 2) { for (let p = 0, u; p < d; p++) { for (h = d; h > p; h--) { e = m.substring(p, h); u = this.rtl ? d - 1 - p : p; var k = this.score ? this.score(c, m, q, e, u) : M(w, n, q, d, u); N(this, t, e, k, a, b); } } break; } case "bidirectional": case "reverse": if (d > 1) { for (k = d - 1; k > 0; k--) { e = m[this.rtl ? d - 1 - k : k] + e; var l = this.score ? this.score(c, m, q, e, k) : M(w, n, q, d, k); N(this, t, e, l, a, b); } e = ""; } case "forward": if (d > 1) { for (k = 0; k < d; k++) { e += m[this.rtl ? d - 1 - k : k], N(this, t, e, h, a, b); } break; } default: if (N(this, t, m, h, a, b), f && n > 1 && q < n - 1) { for (d = this.v, e = m, h = Math.min(f + 1, this.rtl ? q + 1 : n - q), k = 1; k < h; k++) { m = c[this.rtl ? n - 1 - q - k : q + k]; l = this.bidirectional && m > e; const p = this.score ? this.score(c, e, q, m, k - 1) : M(d + (n / 2 > d ? 0 : 1), n, q, h - 1, k - 1); N(this, g, l ? e : m, p, a, b, l ? m : e); } } } } } this.fastupdate || this.reg.add(a); } } return this; }; function N(a, c, b, f, d, h, e) { let k, l; if (!(k = c[b]) || e && !k[e]) { e ? (c = k || (c[b] = z()), c[e] = 1, l = a.ctx, (k = l.get(e)) ? l = k : l.set(e, l = new Map())) : (l = a.map, c[b] = 1); (k = l.get(b)) ? l = k : l.set(b, l = k = []); if (h) { for (let n = 0, g; n < k.length; n++) { if ((g = k[n]) && g.includes(d)) { if (n <= f) { return; } g.splice(g.indexOf(d), 1); a.fastupdate && (c = a.reg.get(d)) && c.splice(c.indexOf(g), 1); break; } } } l = l[f] || (l[f] = []); l.push(d); a.fastupdate && ((f = a.reg.get(d)) ? f.push(l) : a.reg.set(d, [l])); } } function M(a, c, b, f, d) { return b && a > 1 ? c + (f || 0) <= a ? b + (d || 0) : (a - 1) / (c + (f || 0)) * (b + (d || 0)) + 1 | 0 : 0; } ;J.prototype.search = function(a, c, b) { b || (c || typeof a !== "object" ? typeof c === "object" && (b = c, c = 0) : (b = a, a = "")); var f = [], d = 0; if (b) { a = b.query || a; c = b.limit || c; d = b.offset || 0; var h = b.context; var e = b.suggest; var k = !0; var l = b.resolution; } typeof k === "undefined" && (k = !0); h = this.depth && h !== !1; a = this.encoder.encode(a, !h); b = a.length; c = c || (k ? 100 : 0); if (b === 1) { return e = d, (d = O(this, a[0], "")) && d.length ? I.call(this, d, c, e) : []; } if (b === 2 && h && !e) { return e = d, (d = O(this, a[1], a[0])) && d.length ? I.call(this, d, c, e) : []; } k = z(); var n = 0; if (h) { var g = a[0]; n = 1; } l || l === 0 || (l = g ? this.v : this.resolution); for (let m, p; n < b; n++) { if ((p = a[n]) && !k[p]) { k[p] = 1; m = O(this, p, g); a: { h = m; var t = f, w = e, q = l; let u = []; if (h && h.length) { if (h.length <= q) { t.push(h); m = void 0; break a; } for (let y = 0, x; y < q; y++) { if (x = h[y]) { u[y] = x; } } if (u.length) { t.push(u); m = void 0; break a; } } m = w ? void 0 : u; } if (m) { f = m; break; } g && (e && m && f.length || (g = p)); } e && g && n === b - 1 && !f.length && (l = this.resolution, g = "", n = -1, k = z()); } a: { a = f; f = a.length; g = a; if (f > 1) { b: { f = e; g = a.length; e = []; b = z(); for (let m = 0, p, u, y, x; m < l; m++) { for (n = 0; n < g; n++) { if (y = a[n], m < y.length && (p = y[m])) { for (h = 0; h < p.length; h++) { if (u = p[h], (k = b[u]) ? b[u]++ : (k = 0, b[u] = 1), x = e[k] || (e[k] = []), x.push(u), c && k === g - 1 && x.length - d === c) { g = d ? x.slice(d) : x; break b; } } } } } if (a = e.length) { if (f) { if (e.length > 1) { c: { for (a = [], l = z(), f = e.length, k = f - 1; k >= 0; k--) { if (b = (f = e[k]) && f.length) { for (n = 0; n < b; n++) { if (g = f[n], !l[g]) { if (l[g] = 1, d) { d--; } else { if (a.push(g), a.length === c) { break c; } } } } } } } } else { a = (e = e[0]) && c && e.length > c || d ? e.slice(d, c + d) : e; } e = a; } else { if (a < g) { g = []; break b; } e = e[a - 1]; if (c || d) { if (e.length > c || d) { e = e.slice(d, c + d); } } } } g = e; } } else if (f === 1) { c = I.call(null, a[0], c, d); break a; } c = g; } return c; }; function O(a, c, b) { let f; b && (f = a.bidirectional && c > b) && (f = b, b = c, c = f); a = b ? (a = a.ctx.get(b)) && a.get(c) : a.map.get(c); return a; } ;function J(a, c) { if (!this || this.constructor !== J) { return new J(a); } if (a) { var b = typeof a === "string" ? a : a.preset; b && (L[b] || console.warn("Preset not found: " + b), a = Object.assign({}, L[b], a)); } else { a = {}; } b = a.context; const f = b === !0 ? {depth:1} : b || {}, d = a.encode || a.encoder || {}; this.encoder = d.encode ? d : typeof d === "object" ? new F(d) : {encode:d}; this.resolution = a.resolution || 9; this.tokenize = b = (b = a.tokenize) && b !== "default" && b !== "exact" && b || "strict"; this.depth = b === "strict" && f.depth || 0; this.bidirectional = f.bidirectional !== !1; this.fastupdate = !!a.fastupdate; this.score = a.score || null; f && f.depth && this.tokenize !== "strict" && console.warn('Context-Search could not applied, because it is just supported when using the tokenizer "strict".'); this.map = new Map(); this.ctx = new Map(); this.reg = c || (this.fastupdate ? new Map() : new Set()); this.v = f.resolution || 3; this.rtl = d.rtl || a.rtl || !1; } r = J.prototype; r.clear = function() { this.map.clear(); this.ctx.clear(); this.reg.clear(); return this; }; r.append = function(a, c) { return this.add(a, c, !0); }; r.contain = function(a) { return this.reg.has(a); }; r.update = function(a, c) { const b = this, f = this.remove(a); return f && f.then ? f.then(() => b.add(a, c)) : this.add(a, c); }; r.cleanup = function() { if (!this.fastupdate) { return console.info('Cleanup the index isn\'t required when not using "fastupdate".'), this; } K(this.map); this.depth && K(this.ctx); return this; }; z(); export default {Index:J, Charset:null, Encoder:F, Document:null, Worker:null, Resolver:null, IndexedDB:null, Language:{}}; export const Index=J;export const Charset=null;export const Encoder=F;export const Document=null;export const Worker=null;export const Resolver=null;export const IndexedDB=null;export const Language={}; ================================================ FILE: dist/module/async.js ================================================ import Document from "./document.js"; import Index from "./index.js"; import WorkerIndex from "./worker.js"; export default function (prototype) { register.call(prototype, "add"); register.call(prototype, "append"); register.call(prototype, "search"); register.call(prototype, "update"); register.call(prototype, "remove"); register.call(prototype, "searchCache"); } let timer, timestamp, cycle; function tick() { timer = cycle = 0; } /** * @param {!string} key * @this {Index|Document|WorkerIndex} */ function register(key) { this[key + "Async"] = function () { const args = arguments, arg = args[args.length - 1]; let callback; if ("function" == typeof arg) { callback = arg; delete args[args.length - 1]; } if (!timer) { timer = setTimeout(tick, 0); timestamp = Date.now(); } else if (!cycle) { const now = Date.now(), duration = now - timestamp, target = 3 * (this.priority * this.priority); cycle = duration >= target; } if (cycle) { const self = this; return new Promise(resolve => { setTimeout(function () { resolve(self[key + "Async"].apply(self, args)); }, 0); }); } const res = this[key].apply(this, args), promise = res.then ? res : new Promise(resolve => resolve(res)); if (callback) { promise.then(callback); } return promise; }; } ================================================ FILE: dist/module/bundle.js ================================================ import { SearchOptions, ContextOptions, DocumentDescriptor, DocumentSearchOptions, FieldOptions, IndexOptions, DocumentOptions, TagOptions, StoreOptions, EncoderOptions, EncoderSplitOptions, PersistentOptions, ResolverOptions, HighlightBoundaryOptions, HighlightEllipsisOptions, HighlightOptions } from "./type.js"; import StorageInterface from "./db/interface.js"; import Document from "./document.js"; import Index from "./index.js"; import WorkerIndex from "./worker.js"; import Resolver from "./resolver.js"; import Encoder from "./encoder.js"; import IdxDB from "./db/indexeddb/index.js"; import Charset from "./charset.js"; import { KeystoreMap, KeystoreArray, KeystoreSet } from "./keystore.js"; /** @export */Index.prototype.add; /** @export */Index.prototype.append; /** @export */Index.prototype.search; /** @export */Index.prototype.update; /** @export */Index.prototype.remove; /** @export */Index.prototype.contain; /** @export */Index.prototype.clear; /** @export */Index.prototype.cleanup; /** @export */Index.prototype.searchCache; /** @export */Index.prototype.addAsync; /** @export */Index.prototype.appendAsync; /** @export */Index.prototype.searchAsync; /** @export */Index.prototype.searchCacheAsync; /** @export */Index.prototype.updateAsync; /** @export */Index.prototype.removeAsync; /** @export */Index.prototype.export; /** @export */Index.prototype.import; /** @export */Index.prototype.serialize; /** @export */Index.prototype.mount; /** @export */Index.prototype.commit; /** @export */Index.prototype.destroy; /** @export */Index.prototype.encoder; /** @export */Index.prototype.reg; /** @export */Index.prototype.map; /** @export */Index.prototype.ctx; /** @export */Index.prototype.db; /** @export */Index.prototype.tag; /** @export */Index.prototype.store; /** @export */Index.prototype.depth; /** @export */Index.prototype.bidirectional; /** @export */Index.prototype.commit_task; /** @export */Index.prototype.commit_timer; /** @export */Index.prototype.cache; /** @export */Index.prototype.bypass; /** @export */Index.prototype.document; /** @export */Encoder.prototype.assign; /** @export */Encoder.prototype.encode; /** @export */Encoder.prototype.addMatcher; /** @export */Encoder.prototype.addStemmer; /** @export */Encoder.prototype.addFilter; /** @export */Encoder.prototype.addMapper; /** @export */Encoder.prototype.addReplacer; /** @export */Document.prototype.add; /** @export */Document.prototype.append; /** @export */Document.prototype.search; /** @export */Document.prototype.update; /** @export */Document.prototype.remove; /** @export */Document.prototype.contain; /** @export */Document.prototype.clear; /** @export */Document.prototype.cleanup; /** @export */Document.prototype.addAsync; /** @export */Document.prototype.appendAsync; /** @export */Document.prototype.updateAsync; /** @export */Document.prototype.removeAsync; /** @export */Document.prototype.searchAsync; /** @export */Document.prototype.searchCacheAsync; /** @export */Document.prototype.searchCache; /** @export */Document.prototype.mount; /** @export */Document.prototype.commit; /** @export */Document.prototype.destroy; /** @export */Document.prototype.export; /** @export */Document.prototype.import; /** @export */Document.prototype.get; /** @export */Document.prototype.set; /** @export */Document.prototype.field; /** @export */Document.prototype.index; /** @export */Document.prototype.reg; /** @export */Document.prototype.tag; /** @export */Document.prototype.store; /** @export */Document.prototype.fastupdate; /** @export */Resolver.prototype.limit; /** @export */Resolver.prototype.offset; /** @export */Resolver.prototype.boost; /** @export */Resolver.prototype.resolve; /** @export */Resolver.prototype.or; /** @export */Resolver.prototype.and; /** @export */Resolver.prototype.xor; /** @export */Resolver.prototype.not; /** @export */Resolver.prototype.result; /** @export */Resolver.prototype.await; /** @export */StorageInterface.db; /** @export */StorageInterface.id; /** @export */StorageInterface.support_tag_search; /** @export */StorageInterface.fastupdate; /** @export */StorageInterface.prototype.mount; /** @export */StorageInterface.prototype.open; /** @export */StorageInterface.prototype.close; /** @export */StorageInterface.prototype.destroy; /** @export */StorageInterface.prototype.clear; /** @export */StorageInterface.prototype.get; /** @export */StorageInterface.prototype.tag; /** @export */StorageInterface.prototype.enrich; /** @export */StorageInterface.prototype.has; /** @export */StorageInterface.prototype.search; /** @export */StorageInterface.prototype.info; /** @export */StorageInterface.prototype.commit; /** @export */StorageInterface.prototype.remove; /** @export */KeystoreArray.length; /** @export */KeystoreMap.size; /** @export */KeystoreSet.size; /** @export */Charset.Exact; /** @export */Charset.Default; /** @export */Charset.Normalize; /** @export */Charset.LatinBalance; /** @export */Charset.LatinAdvanced; /** @export */Charset.LatinExtra; /** @export */Charset.LatinSoundex; /** @export */Charset.CJK; /** @export @deprecated */Charset.LatinExact; /** @export @deprecated */Charset.LatinDefault; /** @export @deprecated */Charset.LatinSimple; /** @export @deprecated */Charset.ArabicDefault; /** @export @deprecated */Charset.CjkDefault; /** @export @deprecated */Charset.CyrillicDefault; /** @export */IndexOptions.preset; /** @export */IndexOptions.context; /** @export */IndexOptions.encoder; /** @export */IndexOptions.encode; /** @export */IndexOptions.resolution; /** @export */IndexOptions.tokenize; /** @export */IndexOptions.fastupdate; /** @export */IndexOptions.score; /** @export */IndexOptions.keystore; /** @export */IndexOptions.rtl; /** @export */IndexOptions.cache; /** @export */IndexOptions.resolve; /** @export */IndexOptions.db; /** @export */IndexOptions.worker; /** @export */IndexOptions.config; /** @export */IndexOptions.priority; /** @export */IndexOptions.export; /** @export */IndexOptions.import; /** @export */FieldOptions.preset; /** @export */FieldOptions.context; /** @export */FieldOptions.encoder; /** @export */FieldOptions.encode; /** @export */FieldOptions.resolution; /** @export */FieldOptions.tokenize; /** @export */FieldOptions.fastupdate; /** @export */FieldOptions.score; /** @export */FieldOptions.keystore; /** @export */FieldOptions.rtl; /** @export */FieldOptions.cache; /** @export */FieldOptions.db; /** @export */FieldOptions.config; /** @export */FieldOptions.resolve; /** @export */FieldOptions.field; /** @export */FieldOptions.filter; /** @export */FieldOptions.custom; /** @export */FieldOptions.worker; /** @export */DocumentOptions.context; /** @export */DocumentOptions.encoder; /** @export */DocumentOptions.encode; /** @export */DocumentOptions.resolution; /** @export */DocumentOptions.tokenize; /** @export */DocumentOptions.fastupdate; /** @export */DocumentOptions.score; /** @export */DocumentOptions.keystore; /** @export */DocumentOptions.rtl; /** @export */DocumentOptions.cache; /** @export */DocumentOptions.db; /** @export */DocumentOptions.doc; /** @export */DocumentOptions.document; /** @export */DocumentOptions.worker; /** @export */DocumentOptions.priority; /** @export */DocumentOptions.export; /** @export */DocumentOptions.import; /** @export */ContextOptions.depth; /** @export */ContextOptions.bidirectional; /** @export */ContextOptions.resolution; /** @export */DocumentDescriptor.field; /** @export */DocumentDescriptor.index; /** @export */DocumentDescriptor.tag; /** @export */DocumentDescriptor.store; /** @export */DocumentDescriptor.id; /** @export */TagOptions.field; /** @export */TagOptions.tag; /** @export */TagOptions.filter; /** @export */TagOptions.custom; /** @export */TagOptions.keystore; /** @export */TagOptions.db; /** @export */TagOptions.config; /** @export */StoreOptions.field; /** @export */StoreOptions.filter; /** @export */StoreOptions.custom; /** @export */StoreOptions.config; /** @export */SearchOptions.query; /** @export */SearchOptions.limit; /** @export */SearchOptions.offset; /** @export */SearchOptions.context; /** @export */SearchOptions.suggest; /** @export */SearchOptions.resolve; /** @export */SearchOptions.cache; /** @export */SearchOptions.resolution; /** @export */DocumentSearchOptions.query; /** @export */DocumentSearchOptions.limit; /** @export */DocumentSearchOptions.offset; /** @export */DocumentSearchOptions.context; /** @export */DocumentSearchOptions.suggest; /** @export */DocumentSearchOptions.resolve; /** @export */DocumentSearchOptions.enrich; /** @export */DocumentSearchOptions.cache; /** @export */DocumentSearchOptions.resolution; /** @export */DocumentSearchOptions.tag; /** @export */DocumentSearchOptions.field; /** @export */DocumentSearchOptions.index; /** @export */DocumentSearchOptions.pluck; /** @export */DocumentSearchOptions.merge; /** @export */DocumentSearchOptions.highlight; /** @export */EncoderOptions.rtl; /** @export */EncoderOptions.dedupe; /** @export */EncoderOptions.split; /** @export */EncoderOptions.include; /** @export */EncoderOptions.exclude; /** @export */EncoderOptions.prepare; /** @export */EncoderOptions.finalize; /** @export */EncoderOptions.filter; /** @export */EncoderOptions.matcher; /** @export */EncoderOptions.mapper; /** @export */EncoderOptions.stemmer; /** @export */EncoderOptions.replacer; /** @export */EncoderOptions.minlength; /** @export */EncoderOptions.maxlength; /** @export */EncoderOptions.cache; /** @export */EncoderSplitOptions.letter; /** @export */EncoderSplitOptions.number; /** @export */EncoderSplitOptions.symbol; /** @export */EncoderSplitOptions.punctuation; /** @export */EncoderSplitOptions.control; /** @export */EncoderSplitOptions.char; /** @export */PersistentOptions.name; /** @export */PersistentOptions.field; /** @export */PersistentOptions.type; /** @export */PersistentOptions.db; /** @export */ResolverOptions.index; /** @export */ResolverOptions.query; /** @export */ResolverOptions.limit; /** @export */ResolverOptions.offset; /** @export */ResolverOptions.boost; /** @export */ResolverOptions.enrich; /** @export */ResolverOptions.resolve; /** @export */ResolverOptions.suggest; /** @export */ResolverOptions.cache; /** @export */ResolverOptions.async; /** @export */ResolverOptions.queue; /** @export */ResolverOptions.and; /** @export */ResolverOptions.or; /** @export */ResolverOptions.xor; /** @export */ResolverOptions.not; /** @export */ResolverOptions.pluck; /** @export */ResolverOptions.field; /** @export */HighlightBoundaryOptions.before; /** @export */HighlightBoundaryOptions.after; /** @export */HighlightBoundaryOptions.total; /** @export */HighlightEllipsisOptions.template; /** @export */HighlightEllipsisOptions.pattern; /** @export */HighlightOptions.template; /** @export */HighlightOptions.boundary; /** @export */HighlightOptions.ellipsis; /** @export */HighlightOptions.clip; /** @export */HighlightOptions.merge; const FlexSearch = { Index: Index, Charset: Charset, Encoder: Encoder, Document: Document, Worker: WorkerIndex, Resolver: Resolver, IndexedDB: IdxDB, Language: {} }; { const root = "undefined" != typeof self ? self : "undefined" != typeof global ? global : self; let prop; if ((prop = root.define) && prop.amd) { prop([], function () { return FlexSearch; }); } else if ("object" == typeof root.exports) { root.exports = FlexSearch; } else { /** @export */ root.FlexSearch = FlexSearch; } } export default FlexSearch; export { Index, Document, Encoder, Charset, WorkerIndex as Worker, Resolver, IdxDB as IndexedDB }; ================================================ FILE: dist/module/cache.js ================================================ import Index from "./index.js"; import { SearchOptions, DocumentSearchOptions } from "./type.js"; /** * @param {string|SearchOptions|DocumentSearchOptions} query * @param {number|SearchOptions|DocumentSearchOptions=} limit * @param {SearchOptions|DocumentSearchOptions=} options * @this {Index} * @returns {Array|Promise} */ export function searchCache(query, limit, options) { if (!options) { if (!limit && "object" == typeof query) { options = query; } else if ("object" == typeof limit) { options = limit; limit = 0; } } if (options) { query = options.query || query; limit = options.limit || limit; } let key = "" + (limit || 0); if (options) { const { context, suggest, offset, resolve, boost, resolution } = options; key += (offset || 0) + !!context + !!suggest + (!1 !== resolve) + (resolution || this.resolution) + (boost || 0); } query = ("" + query).toLowerCase(); if (!this.cache) { this.cache = new CacheClass(); } let cache = this.cache.get(query + key); if (!cache) { const opt_cache = options && options.cache; opt_cache && (options.cache = !1); cache = this.search(query, limit, options); opt_cache && (options.cache = opt_cache); this.cache.set(query + key, cache); } return cache; } /** * @param {boolean|number=} limit * @constructor */ export default function CacheClass(limit) { /** @private */ this.limit = !limit || !0 === limit ? 1000 : limit; /** @private */ this.cache = new Map(); /** @private */ this.last = ""; } /** * @param {string} key * @param {Array} value */ CacheClass.prototype.set = function (key, value) { this.cache.set(this.last = key, value); if (this.cache.size > this.limit) { this.cache.delete(this.cache.keys().next().value); } }; /** * @param {string} key */ CacheClass.prototype.get = function (key) { const cache = this.cache.get(key); if (cache && this.last !== key) { this.cache.delete(key); this.cache.set(this.last = key, cache); } return cache; }; /** * @param {string|number} id */ CacheClass.prototype.remove = function (id) { for (const item of this.cache) { const key = item[0], value = item[1]; if (value.includes(id)) { this.cache.delete(key); } } }; CacheClass.prototype.clear = function () { this.cache.clear(); this.last = ""; }; ================================================ FILE: dist/module/charset/cjk.js ================================================ import { EncoderOptions } from "../type.js"; /** @type EncoderOptions */ const options = { split: "" }; export default options; ================================================ FILE: dist/module/charset/exact.js ================================================ import { EncoderOptions } from "../type.js"; /** @type EncoderOptions */ const options = { normalize: !1, numeric: !1, dedupe: !1 }; export default options; ================================================ FILE: dist/module/charset/latin/advanced.js ================================================ import { EncoderOptions } from "../../type.js"; import { soundex } from "./balance.js"; export const matcher = new Map([["ae", "a"], ["oe", "o"], ["sh", "s"], ["kh", "k"], ["th", "t"], ["ph", "f"], ["pf", "f"]]); export const replacer = [/([^aeo])h(.)/g, "$1$2", /([aeo])h([^aeo]|$)/g, "$1$2", /(.)\1+/g, "$1"]; /** @type EncoderOptions */ const options = { mapper: soundex, matcher: matcher, replacer: replacer }; export default options; ================================================ FILE: dist/module/charset/latin/balance.js ================================================ import { EncoderOptions } from "../../type.js"; export const soundex = new Map([["b", "p"], ["v", "f"], ["w", "f"], ["z", "s"], ["x", "s"], ["d", "t"], ["n", "m"], ["c", "k"], ["g", "k"], ["j", "k"], ["q", "k"], ["i", "e"], ["y", "e"], ["u", "o"]]); /** @type EncoderOptions */ const options = { mapper: soundex }; export default options; ================================================ FILE: dist/module/charset/latin/extra.js ================================================ import { EncoderOptions } from "../../type.js"; import { soundex } from "./balance.js"; import { matcher, replacer } from "./advanced.js"; export const compact = [/(?!^)[aeo]/g, ""]; /** @type EncoderOptions */ const options = { mapper: soundex, replacer: replacer.concat(compact), matcher: matcher }; export default options; ================================================ FILE: dist/module/charset/latin/soundex.js ================================================ import { EncoderOptions } from "../../type.js"; /** @type {EncoderOptions} */ const options = { dedupe: !1, include: { letter: !0 }, finalize: function (arr) { for (let i = 0; i < arr.length; i++) { arr[i] = soundex(arr[i]); } } }; export default options; const codes = { a: "", e: "", i: "", o: "", u: "", y: "", b: 1, f: 1, p: 1, v: 1, c: 2, g: 2, j: 2, k: 2, q: 2, s: 2, x: 2, z: 2, ß: 2, d: 3, t: 3, l: 4, m: 5, n: 5, r: 6 }; function soundex(stringToEncode) { let encodedString = stringToEncode.charAt(0), last = codes[encodedString]; for (let i = 1, char; i < stringToEncode.length; i++) { char = stringToEncode.charAt(i); if ("h" !== char && "w" !== char) { char = codes[char]; if (char) { if (char !== last) { encodedString += char; last = char; if (4 === encodedString.length) { break; } } } } } return encodedString; } ================================================ FILE: dist/module/charset/normalize.js ================================================ import { EncoderOptions } from "../type.js"; /** @type EncoderOptions */ const options = {}; export default options; ================================================ FILE: dist/module/charset/polyfill.js ================================================ export default [["ª", "a"], ["²", "2"], ["³", "3"], ["¹", "1"], ["º", "o"], ["¼", "1⁄4"], ["½", "1⁄2"], ["¾", "3⁄4"], ["à", "a"], ["á", "a"], ["â", "a"], ["ã", "a"], ["ä", "a"], ["å", "a"], ["ç", "c"], ["è", "e"], ["é", "e"], ["ê", "e"], ["ë", "e"], ["ì", "i"], ["í", "i"], ["î", "i"], ["ï", "i"], ["ñ", "n"], ["ò", "o"], ["ó", "o"], ["ô", "o"], ["õ", "o"], ["ö", "o"], ["ù", "u"], ["ú", "u"], ["û", "u"], ["ü", "u"], ["ý", "y"], ["ÿ", "y"], ["ā", "a"], ["ă", "a"], ["ą", "a"], ["ć", "c"], ["ĉ", "c"], ["ċ", "c"], ["č", "c"], ["ď", "d"], ["ē", "e"], ["ĕ", "e"], ["ė", "e"], ["ę", "e"], ["ě", "e"], ["ĝ", "g"], ["ğ", "g"], ["ġ", "g"], ["ģ", "g"], ["ĥ", "h"], ["ĩ", "i"], ["ī", "i"], ["ĭ", "i"], ["į", "i"], ["ij", "ij"], ["ĵ", "j"], ["ķ", "k"], ["ĺ", "l"], ["ļ", "l"], ["ľ", "l"], ["ŀ", "l"], ["ń", "n"], ["ņ", "n"], ["ň", "n"], ["ʼn", "n"], ["ō", "o"], ["ŏ", "o"], ["ő", "o"], ["ŕ", "r"], ["ŗ", "r"], ["ř", "r"], ["ś", "s"], ["ŝ", "s"], ["ş", "s"], ["š", "s"], ["ţ", "t"], ["ť", "t"], ["ũ", "u"], ["ū", "u"], ["ŭ", "u"], ["ů", "u"], ["ű", "u"], ["ų", "u"], ["ŵ", "w"], ["ŷ", "y"], ["ź", "z"], ["ż", "z"], ["ž", "z"], ["ſ", "s"], ["ơ", "o"], ["ư", "u"], ["dž", "dz"], ["lj", "lj"], ["nj", "nj"], ["ǎ", "a"], ["ǐ", "i"], ["ǒ", "o"], ["ǔ", "u"], ["ǖ", "u"], ["ǘ", "u"], ["ǚ", "u"], ["ǜ", "u"], ["ǟ", "a"], ["ǡ", "a"], ["ǣ", "ae"], ["æ", "ae"], ["ǽ", "ae"], ["ǧ", "g"], ["ǩ", "k"], ["ǫ", "o"], ["ǭ", "o"], ["ǯ", "ʒ"], ["ǰ", "j"], ["dz", "dz"], ["ǵ", "g"], ["ǹ", "n"], ["ǻ", "a"], ["ǿ", "ø"], ["ȁ", "a"], ["ȃ", "a"], ["ȅ", "e"], ["ȇ", "e"], ["ȉ", "i"], ["ȋ", "i"], ["ȍ", "o"], ["ȏ", "o"], ["ȑ", "r"], ["ȓ", "r"], ["ȕ", "u"], ["ȗ", "u"], ["ș", "s"], ["ț", "t"], ["ȟ", "h"], ["ȧ", "a"], ["ȩ", "e"], ["ȫ", "o"], ["ȭ", "o"], ["ȯ", "o"], ["ȱ", "o"], ["ȳ", "y"], ["ʰ", "h"], ["ʱ", "h"], ["ɦ", "h"], ["ʲ", "j"], ["ʳ", "r"], ["ʴ", "ɹ"], ["ʵ", "ɻ"], ["ʶ", "ʁ"], ["ʷ", "w"], ["ʸ", "y"], ["ˠ", "ɣ"], ["ˡ", "l"], ["ˢ", "s"], ["ˣ", "x"], ["ˤ", "ʕ"], ["ΐ", "ι"], ["ά", "α"], ["έ", "ε"], ["ή", "η"], ["ί", "ι"], ["ΰ", "υ"], ["ϊ", "ι"], ["ϋ", "υ"], ["ό", "ο"], ["ύ", "υ"], ["ώ", "ω"], ["ϐ", "β"], ["ϑ", "θ"], ["ϒ", "Υ"], ["ϓ", "Υ"], ["ϔ", "Υ"], ["ϕ", "φ"], ["ϖ", "π"], ["ϰ", "κ"], ["ϱ", "ρ"], ["ϲ", "ς"], ["ϵ", "ε"], ["й", "и"], ["ѐ", "е"], ["ё", "е"], ["ѓ", "г"], ["ї", "і"], ["ќ", "к"], ["ѝ", "и"], ["ў", "у"], ["ѷ", "ѵ"], ["ӂ", "ж"], ["ӑ", "а"], ["ӓ", "а"], ["ӗ", "е"], ["ӛ", "ә"], ["ӝ", "ж"], ["ӟ", "з"], ["ӣ", "и"], ["ӥ", "и"], ["ӧ", "о"], ["ӫ", "ө"], ["ӭ", "э"], ["ӯ", "у"], ["ӱ", "у"], ["ӳ", "у"], ["ӵ", "ч"]]; ================================================ FILE: dist/module/charset.js ================================================ import charset_exact from "./charset/exact.js"; import charset_normalize from "./charset/normalize.js"; import charset_latin_balance from "./charset/latin/balance.js"; import charset_latin_advanced from "./charset/latin/advanced.js"; import charset_latin_extra from "./charset/latin/extra.js"; import charset_latin_soundex from "./charset/latin/soundex.js"; import charset_cjk from "./charset/cjk.js"; export const Exact = charset_exact; export const Default = charset_normalize; export const Normalize = charset_normalize; export const LatinBalance = charset_latin_balance; export const LatinAdvanced = charset_latin_advanced; export const LatinExtra = charset_latin_extra; export const LatinSoundex = charset_latin_soundex; export const CJK = charset_cjk; export const LatinExact = charset_exact; export const LatinDefault = charset_normalize; export const LatinSimple = charset_normalize; export default { Exact: charset_exact, Default: charset_normalize, Normalize: charset_normalize, LatinBalance: charset_latin_balance, LatinAdvanced: charset_latin_advanced, LatinExtra: charset_latin_extra, LatinSoundex: charset_latin_soundex, CJK: charset_cjk, LatinExact: charset_exact, LatinDefault: charset_normalize, LatinSimple: charset_normalize }; ================================================ FILE: dist/module/common.js ================================================ /** * @param {*} value * @param {*} default_value * @param {*=} merge_value * @return {*} */ export function merge_option(value, default_value, merge_value) { const type_merge = typeof merge_value, type_value = typeof value; if ("undefined" != type_merge) { if ("undefined" != type_value) { if (merge_value) { if ("function" == type_value && type_merge == type_value) { return function (str) { return (/** @type Function */value( /** @type Function */merge_value(str)) ); }; } const constructor_value = value.constructor, constructor_merge = merge_value.constructor; if (constructor_value === constructor_merge) { if (constructor_value === Array) { return merge_value.concat(value); } if (constructor_value === Map) { const map = new Map( /** @type !Map */merge_value); for (const item of /** @type !Map */value) { const key = item[0], val = item[1]; map.set(key, val); } return map; } if (constructor_value === Set) { const set = new Set( /** @type !Set */merge_value); for (const val of /** @type !Set */value.values()) { set.add(val); } return set; } } } return value; } else { return merge_value; } } return "undefined" == type_value ? default_value : value; } export function inherit(target_value, default_value) { return "undefined" == typeof target_value ? default_value : target_value; } export function create_object() { return Object.create(null); } export function concat(arrays) { return [].concat.apply([], arrays); } export function sort_by_length_down(a, b) { return b.length - a.length; } export function sort_by_length_up(a, b) { return a.length - b.length; } export function is_array(val) { return val.constructor === Array; } export function is_string(val) { return "string" == typeof val; } export function is_object(val) { return "object" == typeof val; } export function is_function(val) { return "function" == typeof val; } /** * @param {Map|Set} val * @param {boolean=} stringify * @return {Array} */ export function toArray(val, stringify) { const result = []; for (const key of val.keys()) { result.push(stringify ? "" + key : key); } return result; } export function parse_simple(obj, tree) { if (is_string(tree)) { obj = obj[tree]; } else for (let i = 0; obj && i < tree.length; i++) { obj = obj[tree[i]]; } return obj; } export function get_max_len(arr) { let len = 0; for (let i = 0, res; i < arr.length; i++) { if (res = arr[i]) { if (len < res.length) { len = res.length; } } } return len; } ================================================ FILE: dist/module/compress.js ================================================ let table, timer; const cache = new Map(); function toRadix(number, radix = 255) { if (!table) { table = Array(radix); for (let i = 0; i < radix; i++) table[i] = i + 1; table = String.fromCharCode.apply(null, table); } let rixit, residual = number, result = ""; while (!0) { rixit = residual % radix; result = table.charAt(rixit) + result; residual = 0 | residual / radix; if (!residual) break; } return result; } export default function (str) { if (timer) { if (cache.has(str)) { return cache.get(str); } } else { timer = setTimeout(clear, 1); } const result = toRadix("number" == typeof str ? str : lcg(str)); 2e5 < cache.size && cache.clear(); cache.set(str, result); return result; } function lcg(str) { let range = 4294967295; if ("number" == typeof str) { return str & range; } let crc = 0; for (let i = 0; i < str.length; i++) { crc = (crc * 33 ^ str.charCodeAt(i)) & range; } return crc + 2147483648; } function clear() { timer = null; cache.clear(); } ================================================ FILE: dist/module/db/clickhouse/index.js ================================================ import { ClickHouse } from "clickhouse"; import StorageInterface from "../interface.js"; import { concat, toArray } from "../../common.js"; const defaults = { host: "http://localhost", port: "8123", debug: !1, basicAuth: null, isUseGzip: !1, trimQuery: !1, usePost: !1, format: "json", raw: !1, config: { output_format_json_quote_64bit_integers: 0, enable_http_compression: 0, database: "default" } }, VERSION = 1, fields = ["map", "ctx", "tag", "reg", "cfg"], types = { text: "String", char: "String", varchar: "String", string: "String", number: "Int32", numeric: "Int32", integer: "Int32", smallint: "Int16", tinyint: "Int8", mediumint: "Int32", int: "Int32", int8: "Int8", uint8: "UInt8", int16: "Int16", uint16: "UInt16", int32: "Int32", uint32: "UInt32", int64: "Int64", uint64: "UInt64", bigint: "Int64" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } let Index; /** * @constructor * @implements StorageInterface */ export default function ClickhouseDB(name, config = {}) { if (!this || this.constructor !== ClickhouseDB) { return new ClickhouseDB(name, config); } if ("object" == typeof name) { config = name; name = name.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = "flexsearch" + (name ? "_" + sanitize(name) : ""); this.field = config.field ? "_" + sanitize(config.field) : ""; this.type = config.type ? types[config.type.toLowerCase()] : "String"; if (!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); this.support_tag_search = !0; this.db = Index || (Index = config.db || null); Object.assign(defaults, config); config.database && (defaults.config.database = config.database); this.db && delete defaults.db; } ClickhouseDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } defaults.resolution = Math.max(flexsearch.resolution, flexsearch.resolution_ctx); flexsearch.db = this; return this.open(); }; ClickhouseDB.prototype.open = async function () { if (!this.db) { this.db = Index || (Index = new ClickHouse(defaults)); } const exists = await this.db.query(` SELECT 1 FROM system.databases WHERE name = '${this.id}'; `).toPromise(); if (!exists || !exists.length) { await this.db.query(` CREATE DATABASE IF NOT EXISTS ${this.id}; `).toPromise(); } for (let i = 0; i < fields.length; i++) { switch (fields[i]) { case "map": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.map${this.field}( key String, res ${255 >= defaults.resolution ? "UInt8" : "UInt16"}, id ${this.type} ) ENGINE = MergeTree ORDER BY (key, id); `, { params: { name: this.id + ".map" + this.field } }).toPromise(); break; case "ctx": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.ctx${this.field}( ctx String, key String, res ${255 >= defaults.resolution ? "UInt8" : "UInt16"}, id ${this.type} ) ENGINE = MergeTree ORDER BY (ctx, key, id); `).toPromise(); break; case "tag": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.tag${this.field}( tag String, id ${this.type} ) ENGINE = MergeTree ORDER BY (tag, id); `).toPromise(); break; case "reg": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.reg( id ${this.type}, doc Nullable(String) ) ENGINE = MergeTree ORDER BY (id); `).toPromise(); break; case "cfg": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.cfg${this.field}( cfg String ) ENGINE = TinyLog; `).toPromise(); break; } } return this.db; }; ClickhouseDB.prototype.close = function () { this.db = Index = null; return this; }; ClickhouseDB.prototype.destroy = function () { return Promise.all([this.db.query(`DROP TABLE ${this.id}.map${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.ctx${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.tag${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.cfg${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.reg;`).toPromise()]); }; ClickhouseDB.prototype.clear = function () { return Promise.all([this.db.query(`TRUNCATE TABLE ${this.id}.map${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.ctx${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.tag${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.cfg${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.reg;`).toPromise()]); }; function create_result(rows, resolve, enrich) { if (resolve) { for (let i = 0; i < rows.length; i++) { if (enrich) { if (rows[i].doc) { rows[i].doc = JSON.parse(rows[i].doc); } } else { rows[i] = rows[i].id; } } return rows; } else { const arr = []; for (let i = 0, row; i < rows.length; i++) { row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id); } return arr; } } ClickhouseDB.prototype.get = function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1, tags) { let rows, params = ctx ? { ctx, key } : { key }, table = this.id + (ctx ? ".ctx" : ".map") + this.field; if (tags) { for (let i = 0, count = 1; i < tags.length; i += 2) { ` AND ${table}.id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = {tag${count}:String})`; params["tag" + count] = tags[i + 1]; count++; } } if (ctx) { rows = this.db.query(` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT OUTER JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE ctx = {ctx:String} AND key = {key:String} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, { params }).toPromise(); } else { rows = this.db.query(` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT OUTER JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE key = {key:String} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, { params }).toPromise(); } return rows.then(function (rows) { return create_result(rows, resolve, enrich); }); }; ClickhouseDB.prototype.tag = function (tag, limit = 0, offset = 0, enrich = !1) { const table = this.id + ".tag" + this.field, promise = this.db.query(` SELECT ${table}.id ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT OUTER JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE tag = {tag:String} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, { params: { tag } }).toPromise(); enrich || promise.then(function (rows) { return create_result(rows, !0, !1); }); return promise; }; ClickhouseDB.prototype.enrich = async function (ids) { let MAXIMUM_QUERY_VARS = 1e5, result = []; if ("object" != typeof ids) { ids = [ids]; } for (let count = 0; count < ids.length;) { const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; let params = {}, stmt = ""; for (let i = 0; i < chunk.length; i++) { stmt += (stmt ? "," : "") + "{id" + (i + 1) + ":String}"; params["id" + (i + 1)] = chunk[i]; } const res = await this.db.query(` SELECT id, doc FROM ${this.id}.reg WHERE id IN (${stmt})`, { params }).toPromise(); if (res && res.length) { for (let i = 0, doc; i < res.length; i++) { if (doc = res[i].doc) { res[i].doc = JSON.parse(doc); } } result.push(res); } } return 1 === result.length ? result[0] : 1 < result.length ? concat(result) : result; }; ClickhouseDB.prototype.has = async function (id) { const result = await this.db.query(` SELECT 1 FROM ${this.id}.reg WHERE id = {id:${this.type}} LIMIT 1`, { params: { id } }).toPromise(); return !!(result && result[0] && result[0][1]); }; ClickhouseDB.prototype.search = function (flexsearch, query, limit = 100, offset = 0, suggest = !1, resolve = !0, enrich = !1, tags) { let rows; if (1 < query.length && flexsearch.depth) { let where = "", params = {}, keyword = query[0], term; for (let i = 1; i < query.length; i++) { term = query[i]; const swap = flexsearch.bidirectional && term > keyword; where += (where ? " OR " : "") + `(ctx = {ctx${i}:String} AND key = {key${i}:String})`; params["ctx" + i] = swap ? term : keyword; params["key" + i] = swap ? keyword : term; keyword = term; } if (tags) { where = "(" + where + ")"; for (let i = 0, count = 1; i < tags.length; i += 2) { where += ` AND id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = {tag${count}:String})`; params["tag" + count] = tags[i + 1]; count++; } } rows = this.db.query(` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM ${this.id}.ctx${this.field} WHERE ${where} GROUP BY id ) as r ${enrich ? ` LEFT OUTER JOIN ${this.id}.reg ON ${this.id}.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + (query.length - 1)} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, { params }).toPromise(); } else { let where = "", params = {}; for (let i = 0; i < query.length; i++) { where += (where ? "," : "") + `{key${i}:String}`; params["key" + i] = query[i]; } where = "key " + (1 < query.length ? "IN (" + where + ")" : "= " + where); if (tags) { where = "(" + where + ")"; for (let i = 0, count = 1; i < tags.length; i += 2) { where += ` AND id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = {tag${count}:String})`; params["tag" + count] = tags[i + 1]; count++; } } rows = this.db.query(` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM ${this.id}.map${this.field} WHERE ${where} GROUP BY id ) as r ${enrich ? ` LEFT OUTER JOIN ${this.id}.reg ON ${this.id}.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + query.length} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, { params }).toPromise(); } return rows.then(function (rows) { return create_result(rows, resolve, enrich); }); }; ClickhouseDB.prototype.info = function () {}; ClickhouseDB.prototype.transaction = function (task) { return task.call(this); }; ClickhouseDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { /** @dict */ task = tasks[i]; if (task.del) { removals.push(task.del); } else if (task.ins) {} } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } const promises = []; if (flexsearch.map.size) { let data = []; for (const item of flexsearch.map) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { for (let j = 0; j < ids.length; j++) { data.push({ key: key, res: i, id: /*this.type === "number" ? parseInt(ids[j], 10) :*/ids[j] }); } } } } if (data.length) { promises.push(this.db.insert(`INSERT INTO ${this.id}.map${this.field} (key, res, id)`, data).toPromise()); } } if (flexsearch.ctx.size) { let data = []; for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { for (let j = 0; j < ids.length; j++) { data.push({ ctx: ctx_key, key: key, res: i, id: /*this.type === "number" ? parseInt(ids[j], 10) :*/ids[j] }); } } } } } if (data.length) { promises.push(this.db.insert(`INSERT INTO ${this.id}.ctx${this.field} (ctx, key, res, id)`, data).toPromise()); } } if (flexsearch.tag) { let data = []; for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; for (let j = 0; j < ids.length; j++) { data.push({ tag, id: ids[j] }); } } if (data.length) { promises.push(this.db.insert(`INSERT INTO ${this.id}.tag${this.field} (tag, id)`, data).toPromise()); } } if (flexsearch.store) { let data = []; for (const item of flexsearch.store.entries()) { const id = item[0], doc = item[1]; data.push({ id, doc: doc && JSON.stringify(doc) }); } if (data.length) { promises.push(this.db.insert(`INSERT INTO ${this.id}.reg (id, doc)`, data).toPromise()); } } else if (!flexsearch.bypass) { let data = toArray(flexsearch.reg); for (let i = 0; i < data.length; i++) { data[i] = { id: data[i] }; } if (data.length) { promises.push(this.db.insert(`INSERT INTO ${this.id}.reg (id)`, data).toPromise()); } } promises.length && (await Promise.all(promises)); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); await Promise.all([this.db.query(`OPTIMIZE TABLE ${this.id}.map${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.ctx${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.tag${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.reg FINAL`).toPromise()]); }; ClickhouseDB.prototype.remove = async function (ids) { if ("object" != typeof ids) { ids = [ids]; } while (ids.length) { let chunk = ids.slice(0, 1e5); ids = ids.slice(1e5); chunk = "String" === this.type ? "'" + chunk.join("','") + "'" : chunk.join(","); await Promise.all([this.db.query(` ALTER TABLE ${this.id}.map${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;`).toPromise(), this.db.query(` ALTER TABLE ${this.id}.ctx${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;`).toPromise(), this.db.query(` ALTER TABLE ${this.id}.tag${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;`).toPromise(), this.db.query(` ALTER TABLE ${this.id}.reg DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;`).toPromise()]); } }; ================================================ FILE: dist/module/db/indexeddb/index.js ================================================ const VERSION = 1, IndexedDB = "undefined" != typeof window && (window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB), IDBTransaction = "undefined" != typeof window && (window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction), IDBKeyRange = "undefined" != typeof window && (window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange), fields = ["map", "ctx", "tag", "reg", "cfg"]; import StorageInterface from "../interface.js"; import { create_object, toArray } from "../../common.js"; /** * @param {!string} str * @return {string} */ function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } const Index = create_object(); /** * @param {string|PersistentOptions=} name * @param {PersistentOptions=} config * @constructor * @implements StorageInterface */ export default function IdxDB(name, config = {}) { if (!this || this.constructor !== IdxDB) { return new IdxDB(name, config); } if ("object" == typeof name) { config = /** @type PersistentOptions */name; name = name.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = "flexsearch" + (name ? ":" + sanitize(name) : ""); this.field = config.field ? sanitize(config.field) : ""; this.type = config.type; this.support_tag_search = !1; this.fastupdate = !1; this.db = null; this.trx = {}; } IdxDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; IdxDB.prototype.open = function () { if (this.db) return this.db; let self = this; navigator.storage && navigator.storage.persist && navigator.storage.persist(); Index[self.id] || (Index[self.id] = []); Index[self.id].push(self.field); const req = IndexedDB.open(self.id, VERSION); /** @this {IDBOpenDBRequest} */ req.onupgradeneeded = function () { const db = self.db = this.result; for (let i = 0, ref; i < fields.length; i++) { ref = fields[i]; for (let j = 0, field; j < Index[self.id].length; j++) { field = Index[self.id][j]; db.objectStoreNames.contains(ref + ("reg" !== ref ? field ? ":" + field : "" : "")) || db.createObjectStore(ref + ("reg" !== ref ? field ? ":" + field : "" : "")); } } }; return self.db = promisfy(req, function (result) { self.db = result; self.db.onversionchange = function () { self.close(); }; }); }; IdxDB.prototype.close = function () { this.db && this.db.close(); this.db = null; }; /** * @return {!Promise} */ IdxDB.prototype.destroy = function () { const req = IndexedDB.deleteDatabase(this.id); return promisfy(req); }; /** * @return {!Promise} */ IdxDB.prototype.clear = function () { const stores = []; for (let i = 0, ref; i < fields.length; i++) { ref = fields[i]; for (let j = 0, field; j < Index[this.id].length; j++) { field = Index[this.id][j]; stores.push(ref + ("reg" !== ref ? field ? ":" + field : "" : "")); } } const transaction = this.db.transaction(stores, "readwrite"); for (let i = 0; i < stores.length; i++) { transaction.objectStore(stores[i]).clear(); } return promisfy(transaction); }; /** * @param {!string} key * @param {string=} ctx * @param {number=} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @return {!Promise} */ IdxDB.prototype.get = function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1) { const transaction = this.db.transaction((ctx ? "ctx" : "map") + (this.field ? ":" + this.field : ""), "readonly"), map = transaction.objectStore((ctx ? "ctx" : "map") + (this.field ? ":" + this.field : "")), req = map.get(ctx ? ctx + ":" + key : key), self = this; return promisfy(req).then(function (res) { let result = []; if (!res || !res.length) return result; if (resolve) { if (!limit && !offset && 1 === res.length) { return res[0]; } for (let i = 0, arr; i < res.length; i++) { if ((arr = res[i]) && arr.length) { if (offset >= arr.length) { offset -= arr.length; continue; } const end = limit ? offset + Math.min(arr.length - offset, limit) : arr.length; for (let j = offset; j < end; j++) { result.push(arr[j]); } offset = 0; if (result.length === limit) { break; } } } return enrich ? self.enrich(result) : result; } else { return res; } }); }; /** * @param {!string} tag * @param {number=} limit * @param {number=} offset * @param {boolean=} enrich * @return {!Promise} */ IdxDB.prototype.tag = function (tag, limit = 0, offset = 0, enrich = !1) { const transaction = this.db.transaction("tag" + (this.field ? ":" + this.field : ""), "readonly"), map = transaction.objectStore("tag" + (this.field ? ":" + this.field : "")), req = map.get(tag), self = this; return promisfy(req).then(function (ids) { if (!ids || !ids.length || offset >= ids.length) return []; if (!limit && !offset) return ids; const result = ids.slice(offset, offset + limit); return enrich ? self.enrich(result) : result; }); }; /** * @param {SearchResults} ids * @return {!Promise} */ IdxDB.prototype.enrich = function (ids) { if ("object" != typeof ids) { ids = [ids]; } const transaction = this.db.transaction("reg", "readonly"), map = transaction.objectStore("reg"), promises = []; for (let i = 0; i < ids.length; i++) { promises[i] = promisfy(map.get(ids[i])); } return Promise.all(promises).then(function (docs) { for (let i = 0; i < docs.length; i++) { docs[i] = { id: ids[i], doc: docs[i] ? JSON.parse(docs[i]) : null }; } return docs; }); }; /** * @param {number|string} id * @return {!Promise} */ IdxDB.prototype.has = function (id) { const transaction = this.db.transaction("reg", "readonly"), map = transaction.objectStore("reg"), req = map.getKey(id); return promisfy(req).then(function (result) { return !!result; }); }; IdxDB.prototype.search = null; IdxDB.prototype.info = function () {}; /** * @param {!string} ref * @param {!string} modifier * @param {!Function} task */ IdxDB.prototype.transaction = function (ref, modifier, task) { const key = ref + ("reg" !== ref ? this.field ? ":" + this.field : "" : ""); /** * @type {IDBObjectStore} */ let store = this.trx[key + ":" + modifier]; if (store) return task.call(this, store); let transaction = this.db.transaction(key, modifier); /** * @type {IDBObjectStore} */ this.trx[key + ":" + modifier] = store = transaction.objectStore(key); const promise = task.call(this, store); this.trx[key + ":" + modifier] = null; return promisfy(transaction).finally(function () { return promise; }); }; IdxDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { /** @dict */ task = tasks[i]; if (task.del) { removals.push(task.del); } } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } await this.transaction("map", "readwrite", function (store) { for (const item of flexsearch.map) { const key = item[0], value = item[1]; if (!value.length) continue; store.get(key).onsuccess = function () { let result = this.result, changed; if (result && result.length) { const maxlen = Math.max(result.length, value.length); for (let i = 0, res, val; i < maxlen; i++) { val = value[i]; if (val && val.length) { res = result[i]; if (res && res.length) { for (let j = 0; j < val.length; j++) { res.push(val[j]); } changed = 1; } else { result[i] = val; changed = 1; } } } } else { result = value; changed = 1; } changed && store.put(result, key); }; } }); await this.transaction("ctx", "readwrite", function (store) { for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], value = item[1]; if (!value.length) continue; store.get(ctx_key + ":" + key).onsuccess = function () { let result = this.result, changed; if (result && result.length) { const maxlen = Math.max(result.length, value.length); for (let i = 0, res, val; i < maxlen; i++) { val = value[i]; if (val && val.length) { res = result[i]; if (res && res.length) { for (let j = 0; j < val.length; j++) { res.push(val[j]); } changed = 1; } else { result[i] = val; changed = 1; } } } } else { result = value; changed = 1; } changed && store.put(result, ctx_key + ":" + key); }; } } }); if (flexsearch.store) { await this.transaction("reg", "readwrite", function (store) { for (const item of flexsearch.store) { const id = item[0], doc = item[1]; store.put("object" == typeof doc ? JSON.stringify(doc) : 1, id); } }); } else if (!flexsearch.bypass) { await this.transaction("reg", "readwrite", function (store) { for (const id of flexsearch.reg.keys()) { store.put(1, id); } }); } if (flexsearch.tag) { await this.transaction("tag", "readwrite", function (store) { for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; store.get(tag).onsuccess = function () { let result = this.result; result = result && result.length ? result.concat(ids) : ids; store.put(result, tag); }; } }); } flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; /** * @param {IDBCursorWithValue} cursor * @param {Array} ids * @param {boolean=} _tag */ function handle(cursor, ids, _tag) { const arr = cursor.value; let changed, count = 0; for (let x = 0, result; x < arr.length; x++) { if (result = _tag ? arr : arr[x]) { for (let i = 0, pos, id; i < ids.length; i++) { id = ids[i]; pos = result.indexOf(id); if (0 <= pos) { changed = 1; if (1 < result.length) { result.splice(pos, 1); } else { arr[x] = []; break; } } } count += result.length; } if (_tag) break; } if (!count) { cursor.delete(); } else if (changed) { cursor.update(arr); } cursor.continue(); } /** * @param {Array} ids * @return {!Promise} */ IdxDB.prototype.remove = function (ids) { const self = this; if ("object" != typeof ids) { ids = [ids]; } return (/** @type {!Promise} */Promise.all([self.transaction("map", "readwrite", function (store) { store.openCursor().onsuccess = function () { const cursor = this.result; cursor && handle(cursor, ids); }; }), self.transaction("ctx", "readwrite", function (store) { store.openCursor().onsuccess = function () { const cursor = this.result; cursor && handle(cursor, ids); }; }), self.transaction("tag", "readwrite", function (store) { store.openCursor().onsuccess = function () { const cursor = this.result; cursor && handle(cursor, ids, !0); }; }), self.transaction("reg", "readwrite", function (store) { for (let i = 0; i < ids.length; i++) { store.delete(ids[i]); } })]) ); }; /** * @param {IDBRequest|IDBOpenDBRequest} req * @param {Function=} callback * @return {!Promise} */ function promisfy(req, callback) { return new Promise((resolve, reject) => { /** @this {IDBRequest|IDBOpenDBRequest} */ req.onsuccess = req.oncomplete = function () { callback && callback(this.result); callback = null; resolve(this.result); }; req.onerror = req.onblocked = reject; req = null; }); } ================================================ FILE: dist/module/db/interface.js ================================================ /** * @interface */ export default function StorageInterface() {} StorageInterface.prototype.mount = async function () {}; StorageInterface.prototype.open = async function () {}; StorageInterface.prototype.close = function () {}; StorageInterface.prototype.destroy = async function () {}; StorageInterface.prototype.commit = async function () {}; /** * get results of a term "key" with optional context "ctx" * @param {!string} key * @param {string=} ctx * @param {number=} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @return {!Promise} */ StorageInterface.prototype.get = async function () {}; /** * get documents stored in index (enrich result) * @param {SearchResults} ids * @return {!Promise} */ StorageInterface.prototype.enrich = async function () {}; StorageInterface.prototype.has = async function () {}; StorageInterface.prototype.remove = async function () {}; StorageInterface.prototype.clear = async function () {}; /** * Perform the query intersection on database side * @type {Function|null} */ StorageInterface.prototype.search = async function () {}; /** * Give some information about the storage * @type {Function|null} */ StorageInterface.prototype.info = async function () {}; ================================================ FILE: dist/module/db/mongodb/index.js ================================================ import { MongoClient } from "mongodb"; const defaults = { host: "localhost", port: "27017", user: null, pass: null }, VERSION = 1, fields = ["map", "ctx", "tag", "reg", "cfg"]; import StorageInterface from "../interface.js"; import { toArray } from "../../common.js"; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } let CLIENT, Index = Object.create(null); /** * @constructor * @implements StorageInterface */ export default function MongoDB(name, config = {}) { if (!this || this.constructor !== MongoDB) { return new MongoDB(name, config); } if ("object" == typeof name) { config = name; name = name.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = "flexsearch" + (name ? "-" + sanitize(name) : ""); this.field = config.field ? "-" + sanitize(config.field) : ""; this.type = config.type || ""; this.db = config.db || Index[this.id] || CLIENT || null; this.trx = !1; this.support_tag_search = !0; Object.assign(defaults, config); this.db && delete defaults.db; } MongoDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; async function createCollection(db, ref, field) { switch (ref) { case "map": await db.createCollection("map" + field); await db.collection("map" + field).createIndex({ key: 1 }); await db.collection("map" + field).createIndex({ id: 1 }); break; case "ctx": await db.createCollection("ctx" + field); await db.collection("ctx" + field).createIndex({ ctx: 1, key: 1 }); await db.collection("ctx" + field).createIndex({ id: 1 }); break; case "tag": await db.createCollection("tag" + field); await db.collection("tag" + field).createIndex({ tag: 1 }); await db.collection("tag" + field).createIndex({ id: 1 }); break; case "reg": await db.createCollection("reg"); await db.collection("reg").createIndex({ id: 1 }); break; case "cfg": await db.createCollection("cfg" + field); } } MongoDB.prototype.open = async function () { if (!this.db) { if (!(this.db = Index[this.id])) { if (!(this.db = CLIENT)) { let url = defaults.url; if (!url) { url = defaults.user ? `mongodb://${defaults.user}:${defaults.pass}@${defaults.host}:${defaults.port}` : `mongodb://${defaults.host}:${defaults.port}`; } this.db = CLIENT = new MongoClient(url); await this.db.connect(); } } } if (this.db.db) { this.db = Index[this.id] = this.db.db(this.id); } const collections = await this.db.listCollections().toArray(); for (let i = 0, found; i < fields.length; i++) { found = !1; for (let j = 0; j < collections.length; j++) { if (collections[j].name === fields[i] + ("reg" !== fields[i] ? this.field : "")) { found = !0; break; } } if (!found) { await createCollection(this.db, fields[i], this.field); } } return this.db; }; MongoDB.prototype.close = function () { this.db = CLIENT = null; Index[this.id] = null; return this; }; MongoDB.prototype.destroy = function () { return Promise.all([this.db.dropCollection("map" + this.field), this.db.dropCollection("ctx" + this.field), this.db.dropCollection("tag" + this.field), this.db.dropCollection("cfg" + this.field), this.db.dropCollection("reg")]); }; async function clear(ref) { await this.db.dropCollection(ref); await createCollection(this.db, ref, this.field); } MongoDB.prototype.clear = function () { return Promise.all([clear.call(this, "map" + this.field), clear.call(this, "ctx" + this.field), clear.call(this, "tag" + this.field), clear.call(this, "cfg" + this.field), clear.call(this, "reg")]); }; function create_result(rows, resolve, enrich) { const _id = rows[0] && "undefined" != typeof rows[0]._id; if (resolve) { if (!enrich || _id) for (let i = 0, row; i < rows.length; i++) { row = rows[i]; if (enrich) { const id = row._id; delete row._id; row.id = id; } else { rows[i] = _id ? row._id : row.id; } } return rows; } else { const arr = []; for (let i = 0, row, res; i < rows.length; i++) { row = rows[i]; res = row.res; (arr[res] || (arr[res] = [])).push(_id ? row._id : row.id); } return arr; } } MongoDB.prototype.get = async function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1, tags) { let rows, params = ctx ? { ctx, key } : { key }; if (!enrich && !tags) { const stmt = { projection: { _id: 0, res: 1, id: 1 } }; limit && (stmt.limit = limit); offset && (stmt.skip = offset); rows = await this.db.collection((ctx ? "ctx" : "map") + this.field).find(params, stmt).toArray(); } else { const project = { _id: 0, id: 1 }, stmt = [{ $match: params }]; if (!resolve) { project.res = 1; } if (enrich) { project.doc = "$doc.doc"; stmt.push({ $lookup: { from: "reg", localField: "id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: !0 } }); } if (tags) { const match = {}; for (let i = 0, count = 1; i < tags.length; i += 2) { project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push({ $lookup: { from: "tag-" + sanitize(tags[i]), localField: "id", foreignField: "id", as: "tag" + count } }); count++; } stmt.push({ $project: project }, { $match: match }, { $project: { id: 1, doc: 1 } }); } else { stmt.push({ $project: project }); } stmt.push({ $sort: { res: 1 } }); limit && stmt.push({ $limit: limit }); offset && stmt.push({ $skip: offset }); rows = []; const result = await this.db.collection((ctx ? "ctx" : "map") + this.field).aggregate(stmt); while (!0) { const row = await result.next(); if (row) rows.push(row);else break; } } return create_result(rows, resolve, enrich); }; MongoDB.prototype.tag = async function (tag, limit = 0, offset = 0, enrich = !1) { let rows; if (!enrich) { const stmt = { projection: { _id: 0, id: 1 } }; limit && (stmt.limit = limit); offset && (stmt.skip = offset); rows = await this.db.collection("tag" + this.field).find({ tag }, stmt).toArray(); } else { const stmt = [{ $match: { tag } }]; limit && stmt.push({ $limit: limit }); offset && stmt.push({ $skip: offset }); stmt.push({ $lookup: { from: "reg", localField: "id", foreignField: "id", as: "doc" } }, { $project: { _id: 0, id: 1, doc: "$doc.doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: !0 } }); rows = []; const result = await this.db.collection("tag" + this.field).aggregate(stmt); while (!0) { const row = await result.next(); if (row) rows.push(row);else break; } } create_result(rows, !0, enrich); }; MongoDB.prototype.enrich = function (ids) { if ("object" != typeof ids) { ids = [ids]; } return this.db.collection("reg").find({ id: { $in: ids } }, { projection: { _id: 0, id: 1, doc: 1 } }).toArray(); }; MongoDB.prototype.has = function (id) { return this.db.collection("reg").countDocuments({ id }, { limit: 1 }).then(function (result) { return !!result; }); }; MongoDB.prototype.search = async function (flexsearch, query, limit = 100, offset = 0, suggest = !1, resolve = !0, enrich = !1, tags) { let result = [], rows; if (1 < query.length && flexsearch.depth) { let params = [], keyword = query[0], term; for (let i = 1; i < query.length; i++) { term = query[i]; const swap = flexsearch.bidirectional && term > keyword; params.push({ ctx: swap ? term : keyword, key: swap ? keyword : term }); keyword = term; } const project = { _id: 1 }; if (!resolve) project.res = 1; if (enrich) project.doc = 1; const stmt = [{ $match: { $or: params } }, { $group: { _id: "$id", count: { $sum: 1 }, res: suggest ? { $sum: "$res" } : { $sum: "$res" } } }]; suggest || stmt.push({ $match: { count: query.length - 1 } }); if (enrich) { project.doc = "$doc.doc"; stmt.push({ $lookup: { from: "reg", localField: "_id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: !0 } }); } if (tags) { const match = {}; for (let i = 0, count = 1; i < tags.length; i += 2) { project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push({ $lookup: { from: "tag-" + sanitize(tags[i]), localField: "_id", foreignField: "id", as: "tag" + count } }); count++; } stmt.push({ $match: match }); } stmt.push({ $sort: suggest ? { count: -1, res: 1 } : { res: 1 } }); limit && stmt.push({ $limit: limit }); offset && stmt.push({ $skip: offset }); stmt.push({ $project: project }); rows = await this.db.collection("ctx" + this.field).aggregate(stmt); } else { const project = { _id: 1 }; if (!resolve) project.res = 1; if (enrich) project.doc = 1; const stmt = [{ $match: { key: { $in: query } } }, { $group: { _id: "$id", count: { $sum: 1 }, res: suggest ? { $sum: "$res" } : { $sum: "$res" } } }]; suggest || stmt.push({ $match: { count: query.length } }); if (enrich) { project.doc = "$doc.doc"; stmt.push({ $lookup: { from: "reg", localField: "_id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: !0 } }); } if (tags) { const match = {}; for (let i = 0, count = 1; i < tags.length; i += 2) { project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push({ $lookup: { from: "tag-" + sanitize(tags[i]), localField: "_id", foreignField: "id", as: "tag" + count } }); count++; } stmt.push({ $match: match }); } stmt.push({ $sort: suggest ? { count: -1, res: 1 } : { res: 1 } }); limit && stmt.push({ $limit: limit }); offset && stmt.push({ $skip: offset }); stmt.push({ $project: project }); rows = await this.db.collection("map" + this.field).aggregate(stmt); } while (!0) { const row = await rows.next(); if (row) { if (resolve && enrich) { row.id = row._id; delete row._id; } result.push(row); } else break; } return create_result(result, resolve, enrich); }; MongoDB.prototype.info = function () {}; MongoDB.prototype.transaction = function (task) { return task.call(this); }; MongoDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { /** @dict */ task = tasks[i]; if (task.del) { removals.push(task.del); } else if (task.ins) {} } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } const promises = []; if (flexsearch.map.size) { let data = []; for (const item of flexsearch.map) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { this.type || (this.type = typeof ids[0]); for (let j = 0; j < ids.length; j++) { data.push({ key: key, res: i, id: ids[j] }); } } } } if (data.length) { promises.push(this.db.collection("map" + this.field).insertMany(data)); } } if (flexsearch.ctx.size) { let data = []; for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { for (let j = 0; j < ids.length; j++) { data.push({ ctx: ctx_key, key: key, res: i, id: ids[j] }); } } } } } if (data.length) { promises.push(this.db.collection("ctx" + this.field).insertMany(data)); } } if (flexsearch.tag) { let data = []; for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; for (let j = 0; j < ids.length; j++) { data.push({ tag, id: ids[j] }); } } if (data.length) { promises.push(this.db.collection("tag" + this.field).insertMany(data)); } } let data = []; if (flexsearch.store) { for (const item of flexsearch.store.entries()) { const id = item[0], doc = item[1]; data.push({ id, doc }); } } else if (!flexsearch.bypass) { for (const id of flexsearch.reg.keys()) { data.push({ id }); } } if (data.length) { promises.push(this.db.collection("reg").insertMany(data)); } promises.length && (await Promise.all(promises)); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; MongoDB.prototype.remove = function (ids) { if (!ids && 0 !== ids) return; if ("object" != typeof ids) { ids = [ids]; } return Promise.all([this.db.collection("map" + this.field).deleteMany({ id: { $in: ids } }), this.db.collection("ctx" + this.field).deleteMany({ id: { $in: ids } }), this.db.collection("tag" + this.field).deleteMany({ id: { $in: ids } }), this.db.collection("reg").deleteMany({ id: { $in: ids } })]); }; ================================================ FILE: dist/module/db/postgres/index.js ================================================ import pg_promise from "pg-promise"; import StorageInterface from "../interface.js"; import { concat, toArray } from "../../common.js"; const defaults = { schema: "flexsearch", user: "postgres", pass: "postgres", name: "postgres", host: "localhost", port: "5432" }, pgp = pg_promise(), VERSION = 1, MAXIMUM_QUERY_VARS = 16000, fields = ["map", "ctx", "reg", "tag", "cfg"], types = { text: "text", char: "text", varchar: "text", string: "text", number: "int", numeric: "int", integer: "int", smallint: "int", tinyint: "int", mediumint: "int", int: "int", int8: "int", uint8: "int", int16: "int", uint16: "int", int32: "int", uint32: "bigint", int64: "bigint", bigint: "bigint" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } let DB, TRX; /** * @constructor * @implements StorageInterface */ export default function PostgresDB(name, config = {}) { if (!this || this.constructor !== PostgresDB) { return new PostgresDB(name, config); } if ("object" == typeof name) { config = name; name = config.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = (config.schema ? sanitize(config.schema) : defaults.schema) + (name ? "_" + sanitize(name) : ""); this.field = config.field ? "_" + sanitize(config.field) : ""; this.type = config.type ? types[config.type.toLowerCase()] : "text"; this.support_tag_search = !0; if (!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); this.db = DB || (DB = config.db || null); Object.assign(defaults, config); this.db && delete defaults.db; } PostgresDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; PostgresDB.prototype.open = async function () { if (!this.db) { this.db = DB || (DB = pgp(`postgres://${defaults.user}:${encodeURIComponent(defaults.pass)}@${defaults.host}:${defaults.port}/${defaults.name}`)); } const exist = await this.db.oneOrNone(` SELECT EXISTS ( SELECT 1 FROM information_schema.schemata WHERE schema_name = '${this.id}' ); `); if (!exist || !exist.exists) { await this.db.none(`CREATE SCHEMA IF NOT EXISTS ${this.id};`); } for (let i = 0; i < fields.length; i++) { const exist = await this.db.oneOrNone(` SELECT EXISTS ( SELECT 1 FROM pg_tables WHERE schemaname = '${this.id}' AND tablename = '${fields[i] + ("reg" !== fields[i] ? this.field : "")}' ); `); if (exist && exist.exists) continue; const type = "text" === this.type ? "varchar(128)" : this.type; switch (fields[i]) { case "map": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.map${this.field}( key varchar(128) NOT NULL, res smallint NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_map_index${this.field} ON ${this.id}.map${this.field} (key); CREATE INDEX IF NOT EXISTS ${this.id}_map_id${this.field} ON ${this.id}.map${this.field} (id); `); break; case "ctx": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.ctx${this.field}( ctx varchar(128) NOT NULL, key varchar(128) NOT NULL, res smallint NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_ctx_index${this.field} ON ${this.id}.ctx${this.field} (ctx, key); CREATE INDEX IF NOT EXISTS ${this.id}_ctx_id${this.field} ON ${this.id}.ctx${this.field} (id); `); break; case "tag": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.tag${this.field}( tag varchar(128) NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_tag_index${this.field} ON ${this.id}.tag${this.field} (tag); CREATE INDEX IF NOT EXISTS ${this.id}_tag_id${this.field} ON ${this.id}.tag${this.field} (id); `); break; case "reg": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.reg( id ${type} NOT NULL CONSTRAINT ${this.id}_reg_pk PRIMARY KEY, doc text DEFAULT NULL ); `).catch(() => {}); break; case "cfg": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.cfg${this.field}( cfg text NOT NULL ); `); break; } } return this.db; }; PostgresDB.prototype.close = function () { this.db = null; return this; }; PostgresDB.prototype.destroy = function () { return this.db.none(` DROP TABLE IF EXISTS ${this.id}.map${this.field}; DROP TABLE IF EXISTS ${this.id}.ctx${this.field}; DROP TABLE IF EXISTS ${this.id}.tag${this.field}; DROP TABLE IF EXISTS ${this.id}.cfg${this.field}; DROP TABLE IF EXISTS ${this.id}.reg; `); }; PostgresDB.prototype.clear = function () { return this.db.none(` TRUNCATE TABLE ${this.id}.map${this.field}; TRUNCATE TABLE ${this.id}.ctx${this.field}; TRUNCATE TABLE ${this.id}.tag${this.field}; TRUNCATE TABLE ${this.id}.cfg${this.field}; TRUNCATE TABLE ${this.id}.reg; `); }; function create_result(rows, resolve, enrich) { if (resolve) { for (let i = 0; i < rows.length; i++) { if (enrich) { if (rows[i].doc) { rows[i].doc = JSON.parse(rows[i].doc); } } else { rows[i] = rows[i].id; } } return rows; } else { const arr = []; for (let i = 0, row; i < rows.length; i++) { row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id); } return arr; } } PostgresDB.prototype.get = function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1, tags) { let rows, stmt = '', params = ctx ? [ctx, key] : [key], table = this.id + (ctx ? ".ctx" : ".map") + this.field; if (tags) { for (let i = 0, count = params.length + 1; i < tags.length; i += 2) { stmt += ` AND ${table}.id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = $${count++})`; params.push(tags[i + 1]); } } if (ctx) { rows = this.db.any(` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE ctx = $1 AND key = $2 ${stmt} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, params); } else { rows = this.db.any(` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE key = $1 ${stmt} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, params); } return rows.then(function (rows) { return create_result(rows, resolve, enrich); }); }; PostgresDB.prototype.tag = function (tag, limit = 0, offset = 0, enrich = !1) { const table = this.id + ".tag" + this.field, promise = this.db.any(` SELECT ${table}.id ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE tag = $1 ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, [tag]); enrich || promise.then(function (rows) { return create_result(rows, !0, !1); }); return promise; }; PostgresDB.prototype.enrich = async function (ids) { let result = []; if ("object" != typeof ids) { ids = [ids]; } for (let count = 0; count < ids.length;) { const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; let stmt = ""; for (let i = 1; i <= chunk.length; i++) { stmt += (stmt ? "," : "") + "$" + i; } const res = await this.db.any(` SELECT id, doc FROM ${this.id}.reg WHERE id IN (${stmt})`, ids); if (res && res.length) { for (let i = 0, doc; i < res.length; i++) { if (doc = res[i].doc) { res[i].doc = JSON.parse(doc); } } result.push(res); } } return 1 === result.length ? result[0] : 1 < result.length ? concat(result) : result; }; PostgresDB.prototype.has = function (id) { return this.db.oneOrNone("SELECT EXISTS(SELECT 1 FROM " + this.id + ".reg WHERE id = $1)", [id]).then(function (result) { return !!(result && result.exists); }); }; PostgresDB.prototype.search = function (flexsearch, query, limit = 100, offset = 0, suggest = !1, resolve = !0, enrich = !1, tags) { let rows; if (1 < query.length && flexsearch.depth) { let where = "", params = [], keyword = query[0], term, count = 1; for (let i = 1; i < query.length; i++) { term = query[i]; const swap = flexsearch.bidirectional && term > keyword; where += (where ? " OR " : "") + `(ctx = $${count++} AND key = $${count++})`; params.push(swap ? term : keyword, swap ? keyword : term); keyword = term; } if (tags) { where = "(" + where + ")"; for (let i = 0; i < tags.length; i += 2) { where += ` AND id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = $${count++})`; params.push(tags[i + 1]); } } rows = this.db.any(` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM ${this.id}.ctx${this.field} WHERE ${where} GROUP BY id ) as r ${enrich ? ` LEFT JOIN ${this.id}.reg ON ${this.id}.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + (query.length - 1)} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params); } else { let where = "", count = 1, query_length = query.length; for (let i = 0; i < query_length; i++) { where += (where ? "," : "") + "$" + count++; } where = "key " + (1 < query_length ? "IN (" + where + ")" : "= " + where); if (tags) { where = "(" + where + ")"; for (let i = 0; i < tags.length; i += 2) { where += ` AND id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = $${count++})`; query.push(tags[i + 1]); } } rows = this.db.any(` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM ${this.id}.map${this.field} WHERE ${where} GROUP BY id ) as r ${enrich ? ` LEFT JOIN ${this.id}.reg ON ${this.id}.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + query_length} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, query); } return rows.then(function (rows) { return create_result(rows, resolve, enrich); }); }; PostgresDB.prototype.info = function () {}; PostgresDB.prototype.transaction = function (task) { const self = this; return this.db.tx(function (trx) { return task.call(self, trx); }); }; PostgresDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { /** @dict */ task = tasks[i]; if (task.del) { removals.push(task.del); } else if (task.ins) {} } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } await this.transaction(function (trx) { const batch = []; if (flexsearch.store) { let data = [], stmt = new pgp.helpers.ColumnSet(["id", "doc"], { table: this.id + ".reg" }); for (const item of flexsearch.store.entries()) { const id = item[0], doc = item[1]; data.push({ id, doc: doc && JSON.stringify(doc) }); if (data.length === MAXIMUM_QUERY_VARS) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); data = []; } } if (data.length) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); } } else if (!flexsearch.bypass) { let data = [], stmt = new pgp.helpers.ColumnSet(["id"], { table: this.id + ".reg" }); for (const id of flexsearch.reg.keys()) { data.push({ id }); if (data.length === MAXIMUM_QUERY_VARS) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); data = []; } } if (data.length) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); } } if (flexsearch.map.size) { let data = [], stmt = new pgp.helpers.ColumnSet(["key", "res", "id"], { table: this.id + ".map" + this.field }); for (const item of flexsearch.map) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { for (let j = 0; j < ids.length; j++) { data.push({ key: key, res: i, id: ids[j] }); if (data.length === MAXIMUM_QUERY_VARS) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); data = []; } } } } } if (data.length) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); } } if (flexsearch.ctx.size) { let data = [], stmt = new pgp.helpers.ColumnSet(["ctx", "key", "res", "id"], { table: this.id + ".ctx" + this.field }); for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { for (let j = 0; j < ids.length; j++) { data.push({ ctx: ctx_key, key: key, res: i, id: ids[j] }); if (data.length === MAXIMUM_QUERY_VARS) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); data = []; } } } } } } if (data.length) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); } } if (flexsearch.tag) { let data = [], stmt = new pgp.helpers.ColumnSet(["tag", "id"], { table: this.id + ".tag" + this.field }); for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; for (let j = 0; j < ids.length; j++) { data.push({ tag, id: ids[j] }); if (data.length === MAXIMUM_QUERY_VARS) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); data = []; } } } if (data.length) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); } } if (batch.length) { return trx.batch(batch); } }); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; PostgresDB.prototype.remove = function (ids) { if (!ids && 0 !== ids) { return; } if ("object" != typeof ids) { ids = [ids]; } if (!ids.length) { return; } return this.transaction(function (trx) { ids = [ids]; return trx.batch([trx.none({ text: "DELETE FROM " + this.id + ".map" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".tag" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".reg WHERE id = ANY ($1)", rowMode: "array" }, ids)]); }); }; ================================================ FILE: dist/module/db/redis/index.js ================================================ import { createClient } from "redis"; import StorageInterface from "../interface.js"; import { toArray } from "../../common.js"; const VERSION = 1, fields = ["map", "ctx", "reg", "tag", "doc", "cfg"], defaults = { host: "localhost", port: "6379", user: null, pass: null }; let DB, TRX; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } /** * @constructor * @implements StorageInterface */ export default function RedisDB(name, config = {}) { if (!this || this.constructor !== RedisDB) { return new RedisDB(name, config); } if ("object" == typeof name) { config = name; name = name.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = (name ? sanitize(name) : "flexsearch") + "|"; this.field = config.field ? "-" + sanitize(config.field) : ""; this.type = config.type || ""; this.fastupdate = !0; this.db = config.db || DB || null; this.support_tag_search = !0; this.resolution = 9; this.resolution_ctx = 9; Object.assign(defaults, config); this.db && delete defaults.db; } RedisDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } flexsearch.db = this; this.resolution = flexsearch.resolution; this.resolution_ctx = flexsearch.resolution_ctx; return this.open(); }; RedisDB.prototype.open = async function () { if (this.db) { return this.db; } if (DB) { return this.db = DB; } let url = defaults.url; if (!url) { url = defaults.user ? `redis://${defaults.user}:${defaults.pass}@${defaults.host}:${defaults.port}` : `redis://${defaults.host}:${defaults.port}`; } return this.db = DB = await createClient(url).on("error", err => console.error(err)).connect(); }; RedisDB.prototype.close = async function () { DB && (await this.db.disconnect()); this.db = DB = null; return this; }; RedisDB.prototype.destroy = function () { return this.clear(!0); }; RedisDB.prototype.clear = function (destroy = !1) { if (!this.id) return; const self = this; function unlink(keys) { return keys.length && self.db.unlink(keys); } return Promise.all([this.db.keys(this.id + "map" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "ctx" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "tag" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "ref" + (destroy ? "" : this.field) + "*").then(unlink), unlink([this.id + "cfg" + (destroy ? "*" : this.field), this.id + "doc", this.id + "reg"])]); }; function create_result(range, type, resolve, enrich, resolution) { if (resolve) { for (let i = 0, tmp, id; i < range.length; i++) { tmp = range[i]; id = "number" === type ? parseInt(tmp.value || tmp, 10) : tmp.value || tmp; range[i] = enrich ? { id, doc: tmp.doc } : id; } return range; } else { let result = []; for (let i = 0, tmp, id, score; i < range.length; i++) { tmp = range[i]; id = "number" === type ? parseInt(tmp.value || tmp, 10) : tmp.value || tmp; score = Math.max(resolution - tmp.score, 0); result[score] || (result[score] = []); result[score].push(id); } return result; } } RedisDB.prototype.get = function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1, tags) { if (tags) { const query = ctx ? [ctx, key] : [key]; return this.search({ depth: !!ctx }, query, limit, offset, !1, resolve, enrich, tags); } const type = this.type, self = this; let result; if (ctx) { result = this.db[resolve ? "zRange" : "zRangeWithScores"](this.id + "ctx" + this.field + ":" + ctx + ":" + key, "" + offset, "" + (offset + limit - 1), { REV: !0 }); } else { result = this.db[resolve ? "zRange" : "zRangeWithScores"](this.id + "map" + this.field + ":" + key, "" + offset, "" + (offset + limit - 1), { REV: !0 }); } return result.then(async function (range) { if (!range.length) return range; if (enrich) range = await self.enrich(range); return create_result(range, type, resolve, enrich, ctx ? self.resolution_ctx : self.resolution); }); }; RedisDB.prototype.tag = function (tag, limit = 0, offset = 0, enrich = !1) { const self = this; return this.db.sMembers(this.id + "tag" + this.field + ":" + tag).then(function (ids) { if (!ids || !ids.length || offset >= ids.length) return []; if (!limit && !offset) return ids; const result = ids.slice(offset, offset + limit); return enrich ? self.enrich(result) : result; }); }; RedisDB.prototype.enrich = function (ids) { if ("object" != typeof ids) { ids = [ids]; } return this.db.hmGet(this.id + "doc", "number" === this.type ? ids.map(i => "" + i) : ids).then(function (res) { for (let i = 0; i < res.length; i++) { res[i] = { id: ids[i], doc: res[i] && JSON.parse(res[i]) }; } return res; }); }; RedisDB.prototype.has = function (id) { return this.db.sIsMember(this.id + "reg", "" + id).then(function (res) { return !!res; }); }; RedisDB.prototype.search = function (flexsearch, query, limit = 100, offset = 0, suggest = !1, resolve = !0, enrich = !1, tags) { const ctx = 1 < query.length && flexsearch.depth; let result, params = [], weights = []; if (ctx) { const key = this.id + "ctx" + this.field + ":"; let keyword = query[0], term; for (let i = 1, swap; i < query.length; i++) { term = query[i]; swap = flexsearch.bidirectional && term > keyword; params.push(key + (swap ? term : keyword) + ":" + (swap ? keyword : term)); weights.push(1); keyword = term; } } else { const key = this.id + "map" + this.field + ":"; for (let i = 0; i < query.length; i++) { params.push(key + query[i]); weights.push(1); } } query = params; const type = this.type; let key = this.id + "tmp:" + Math.random(); if (suggest) { const multi = this.db.multi(); multi.zInterStore(key, query, { AGGREGATE: "SUM" }); query.push(key); weights.push(query.length); multi.zUnionStore(key, query, { WEIGHTS: weights, AGGREGATE: "SUM" }); if (tags) { query = [key]; for (let i = 0; i < tags.length; i += 2) { query.push(this.id + "tag-" + sanitize(tags[i]) + ":" + tags[i + 1]); } multi.zInterStore(key, query, { AGGREGATE: "MAX" }); } result = multi[resolve ? "zRange" : "zRangeWithScores"](key, "" + offset, "" + (offset + limit - 1), { REV: !0 }).unlink(key).exec(); } else { if (tags) for (let i = 0; i < tags.length; i += 2) { query.push(this.id + "tag-" + sanitize(tags[i]) + ":" + tags[i + 1]); } result = this.db.multi().zInterStore(key, query, { AGGREGATE: "MAX" })[resolve ? "zRange" : "zRangeWithScores"](key, "" + offset, "" + (offset + limit - 1), { REV: !0 }).unlink(key).exec(); } const self = this; return result.then(async function (range) { range = suggest && tags ? range[3] : range[suggest ? 2 : 1]; if (!range.length) return range; if (enrich) range = await self.enrich(range); return create_result(range, type, resolve, enrich, ctx ? self.resolution_ctx : self.resolution); }); }; RedisDB.prototype.info = function () {}; RedisDB.prototype.transaction = function (task, callback) { if (TRX) { return task.call(this, TRX); } TRX = this.db.multi(); let promise1 = task.call(this, TRX), promise2 = TRX.exec(); TRX = null; return Promise.all([promise1, promise2]).then(function () { callback && callback(); }); }; RedisDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { /** @dict */ task = tasks[i]; if (task.del) { removals.push(task.del); } else if (task.ins) {} } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } await this.transaction(function (trx) { let refs = new Map(); for (const item of flexsearch.map) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { let result = []; for (let j = 0; j < ids.length; j++) { result.push({ score: this.resolution - i, value: "" + ids[j] }); } if ("number" == typeof ids[0]) { this.type = "number"; } const ref = this.id + "map" + this.field + ":" + key; trx.zAdd(ref, result); if (this.fastupdate) for (let j = 0, id; j < ids.length; j++) { id = ids[j]; let tmp = refs.get(id); tmp || refs.set(id, tmp = []); tmp.push(ref); } } } } if (this.fastupdate) for (const item of refs) { const key = item[0], value = item[1]; trx.sAdd(this.id + "ref" + this.field + ":" + key, value); } refs = new Map(); for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { let result = []; for (let j = 0; j < ids.length; j++) { result.push({ score: this.resolution_ctx - i, value: "" + ids[j] }); } if ("number" == typeof ids[0]) { this.type = "number"; } const ref = this.id + "ctx" + this.field + ":" + ctx_key + ":" + key; trx.zAdd(ref, result); if (this.fastupdate) for (let j = 0, id; j < ids.length; j++) { id = ids[j]; let tmp = refs.get(id); tmp || refs.set(id, tmp = []); tmp.push(ref); } } } } } if (this.fastupdate) for (const item of refs) { const key = item[0], value = item[1]; trx.sAdd(this.id + "ref" + this.field + ":" + key, value); } if (flexsearch.store) { for (const item of flexsearch.store.entries()) { const id = item[0], doc = item[1]; doc && trx.hSet(this.id + "doc", "" + id, JSON.stringify(doc)); } } if (!flexsearch.bypass) { let ids = toArray(flexsearch.reg, !0); if (ids.length) { trx.sAdd(this.id + "reg", ids); } } if (flexsearch.tag) { for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; let result = []; if ("number" == typeof ids[0]) { for (let i = 0; i < ids.length; i++) { result[i] = "" + ids[i]; } } else { result = ids; } trx.sAdd(this.id + "tag" + this.field + ":" + tag, result); } } }); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; RedisDB.prototype.remove = function (ids) { if (!ids && 0 !== ids) { return; } if ("object" != typeof ids) { ids = [ids]; } if (!ids.length) { return; } return this.transaction(async function (trx) { while (ids.length) { let next; if (10000 < ids.length) { next = ids.slice(10000); ids = ids.slice(0, 10000); } if ("number" == typeof ids[0]) { for (let i = 0; i < ids.length; i++) { ids[i] = "" + ids[i]; } } const check = await this.db.smIsMember(this.id + "reg", ids); for (let i = 0, id; i < ids.length; i++) { if (!check[i]) continue; id = "" + ids[i]; if (this.fastupdate) { const ref = await this.db.sMembers(this.id + "ref" + this.field + ":" + id); if (ref) { for (let j = 0; j < ref.length; j++) { trx.zRem(ref[j], id); } trx.unlink(this.id + "ref" + this.field + ":" + id); } } trx.hDel(this.id + "doc", id); trx.sRem(this.id + "reg", id); } if (next) ids = next;else break; } }); }; ================================================ FILE: dist/module/db/sqlite/index.js ================================================ import sqlite3 from "sqlite3"; import path from "path"; import StorageInterface from "../interface.js"; import { concat, toArray } from "../../common.js"; const VERSION = 1, MAXIMUM_QUERY_VARS = 16000, fields = ["map", "ctx", "reg", "tag", "cfg"], types = { text: "text", char: "text", varchar: "text", string: "text", number: "int", numeric: "int", integer: "int", smallint: "int", tinyint: "int", mediumint: "int", int: "int", int8: "int", uint8: "int", int16: "int", uint16: "int", int32: "int", uint32: "bigint", int64: "bigint", bigint: "bigint" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } const TRX = Object.create(null), Index = Object.create(null); /** * @constructor * @implements StorageInterface */ export default function SqliteDB(name, config = {}) { if (!this || this.constructor !== SqliteDB) { return new SqliteDB(name, config); } if ("object" == typeof name) { config = name; name = name.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = config.path || (":memory:" === name ? name : "flexsearch" + (name ? "-" + sanitize(name) : "") + ".sqlite"); this.field = config.field ? "_" + sanitize(config.field) : ""; this.support_tag_search = !0; this.db = config.db || Index[this.id] || null; this.type = config.type ? types[config.type.toLowerCase()] : "string"; if (!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); } SqliteDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; SqliteDB.prototype.open = async function () { if (!this.db) { if (!(this.db = Index[this.id])) { let filepath = this.id; if (":memory:" !== filepath) { if ("/" !== filepath[0] && "\\" !== filepath[0]) { const dir = process.cwd(); filepath = path.join(dir, this.id); } } this.db = Index[this.id] = new sqlite3.Database(filepath); } } const db = this.db; for (let i = 0; i < fields.length; i++) { const exist = await this.promisfy({ method: "get", stmt: "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?) as exist", params: [fields[i] + ("reg" === fields[i] ? "" : this.field)] }); if (!exist || !exist.exist) { let stmt, stmt_index; switch (fields[i]) { case "map": stmt = ` CREATE TABLE IF NOT EXISTS main.map${this.field}( key TEXT NOT NULL, res INTEGER NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS map_key_index${this.field} ON map${this.field} (key); CREATE INDEX IF NOT EXISTS map_id_index${this.field} ON map${this.field} (id); `; break; case "ctx": stmt = ` CREATE TABLE IF NOT EXISTS main.ctx${this.field}( ctx TEXT NOT NULL, key TEXT NOT NULL, res INTEGER NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS ctx_key_index${this.field} ON ctx${this.field} (ctx, key); CREATE INDEX IF NOT EXISTS ctx_id_index${this.field} ON ctx${this.field} (id); `; break; case "tag": stmt = ` CREATE TABLE IF NOT EXISTS main.tag${this.field}( tag TEXT NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS tag_index${this.field} ON tag${this.field} (tag); CREATE INDEX IF NOT EXISTS tag_id_index${this.field} ON tag${this.field} (id); `; break; case "reg": stmt = ` CREATE TABLE IF NOT EXISTS main.reg( id ${this.type} NOT NULL CONSTRAINT reg_pk${this.field} PRIMARY KEY, doc TEXT DEFAULT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS reg_index ON reg (id); `; break; case "cfg": stmt = ` CREATE TABLE IF NOT EXISTS main.cfg${this.field} ( cfg TEXT NOT NULL ); `; break; } await new Promise(function (resolve, reject) { db.exec(stmt, function (err, rows) { if (err) return reject(err); stmt_index ? db.exec(stmt_index, function (err, rows) { if (err) return reject(err); resolve(rows); }) : resolve(rows); }); }); } } db.exec("PRAGMA optimize = 0x10002"); return db; }; SqliteDB.prototype.close = function () { this.db && this.db.close(); this.db = null; Index[this.id] = null; TRX[this.id] = null; return this; }; SqliteDB.prototype.destroy = function () { return this.transaction(function () { this.db.run("DROP TABLE IF EXISTS main.map" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.ctx" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.tag" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.cfg" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.reg;"); }); }; SqliteDB.prototype.clear = function () { return this.transaction(function () { this.db.run("DELETE FROM main.map" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.ctx" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.tag" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.cfg" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.reg WHERE 1;"); }); }; function create_result(rows, resolve, enrich) { if (resolve) { for (let i = 0; i < rows.length; i++) { if (enrich) { if (rows[i].doc) { rows[i].doc = JSON.parse(rows[i].doc); } } else { rows[i] = rows[i].id; } } return rows; } else { const arr = []; for (let i = 0, row; i < rows.length; i++) { row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id); } return arr; } } SqliteDB.prototype.get = function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1, tags) { let result, stmt = '', params = ctx ? [ctx, key] : [key], table = "main." + (ctx ? "ctx" : "map") + this.field; if (tags) { for (let i = 0; i < tags.length; i += 2) { stmt += ` AND ${table}.id IN (SELECT id FROM main.tag_${sanitize(tags[i])} WHERE tag = ?)`; params.push(tags[i + 1]); } } if (ctx) { result = this.promisfy({ method: "all", stmt: ` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${table}.id ` : ""} WHERE ctx = ? AND key = ? ${stmt} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params }); } else { result = this.promisfy({ method: "all", stmt: ` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${table}.id ` : ""} WHERE key = ? ${stmt} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params }); } return result.then(function (rows) { return create_result(rows, resolve, enrich); }); }; SqliteDB.prototype.tag = function (tag, limit = 0, offset = 0, enrich = !1) { const table = "main.tag" + this.field, promise = this.promisfy({ method: "all", stmt: ` SELECT ${table}.id ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${table}.id ` : ""} WHERE tag = ? ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params: [tag] }); enrich || promise.then(function (rows) { return create_result(rows, !0, !1); }); return promise; }; function build_params(length, single_param) { let stmt = single_param ? ",(?)" : ",?"; for (let i = 1; i < length;) { if (i <= length - i) { stmt += stmt; i *= 2; } else { stmt += stmt.substring(0, (length - i) * (single_param ? 4 : 2)); break; } } return stmt.substring(1); } SqliteDB.prototype.enrich = function (ids) { const result = [], promises = []; if ("object" != typeof ids) { ids = [ids]; } for (let count = 0; count < ids.length;) { const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids, stmt = build_params(chunk.length); count += chunk.length; promises.push(this.promisfy({ method: "all", stmt: `SELECT id, doc FROM main.reg WHERE id IN (${stmt})`, params: chunk })); } return Promise.all(promises).then(function (promises) { for (let i = 0, res; i < promises.length; i++) { res = promises[i]; if (res && res.length) { for (let i = 0, doc; i < res.length; i++) { if (doc = res[i].doc) { res[i].doc = JSON.parse(doc); } } result.push(res); } } return 1 === result.length ? result[0] : 1 < result.length ? concat(result) : result; }); }; SqliteDB.prototype.has = function (id) { return this.promisfy({ method: "get", stmt: `SELECT EXISTS(SELECT 1 FROM main.reg WHERE id = ?) as exist`, params: [id] }).then(function (result) { return !!(result && result.exist); }); }; SqliteDB.prototype.search = function (flexsearch, query, limit = 100, offset = 0, suggest = !1, resolve = !0, enrich = !1, tags) { let rows; if (1 < query.length && flexsearch.depth) { let stmt = "", params = [], keyword = query[0], term; for (let i = 1; i < query.length; i++) { term = query[i]; const swap = flexsearch.bidirectional && term > keyword; stmt += (stmt ? " OR " : "") + `(ctx = ? AND key = ?)`; params.push(swap ? term : keyword, swap ? keyword : term); keyword = term; } if (tags) { stmt = "(" + stmt + ")"; for (let i = 0; i < tags.length; i += 2) { stmt += ` AND id IN (SELECT id FROM main.tag_${sanitize(tags[i])} WHERE tag = ?)`; params.push(tags[i + 1]); } } rows = this.promisfy({ method: "all", stmt: ` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM main.ctx${this.field} WHERE ${stmt} GROUP BY id ) as r ${enrich ? ` LEFT JOIN main.reg ON main.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + (query.length - 1)} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params }); } else { let stmt = "", query_length = query.length; for (let i = 0; i < query_length; i++) { stmt += (stmt ? " OR " : "") + `key = ?`; } if (tags) { stmt = "(" + stmt + ")"; for (let i = 0; i < tags.length; i += 2) { stmt += ` AND id IN (SELECT id FROM main.tag_${sanitize(tags[i])} WHERE tag = ?)`; query.push(tags[i + 1]); } } rows = this.promisfy({ method: "all", stmt: ` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM main.map${this.field} WHERE ${stmt} GROUP BY id ) as r ${enrich ? ` LEFT JOIN main.reg ON main.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + query_length} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params: query }); } return rows.then(function (rows) { return create_result(rows, resolve, enrich); }); }; SqliteDB.prototype.info = function () {}; SqliteDB.prototype.transaction = async function (task, callback) { if (TRX[this.id]) { return await task.call(this); } const db = this.db, self = this; return TRX[this.id] = new Promise(function (resolve, reject) { db.exec("PRAGMA optimize"); db.exec('PRAGMA busy_timeout = 5000'); db.exec("BEGIN"); db.parallelize(function () { task.call(self); }); db.exec("COMMIT", function (err, rows) { TRX[self.id] = null; if (err) return reject(err); callback && callback(rows); resolve(rows); db.exec("PRAGMA shrink_memory"); }); }); }; SqliteDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = [], inserts = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { task = tasks[i]; if ("undefined" != typeof task.del) { removals.push(task.del); } else if ("undefined" != typeof task.ins) { inserts.push(task.ins); } } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } await this.transaction(function () { for (const item of flexsearch.map) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { let stmt = "", params = []; for (let j = 0; j < ids.length; j++) { stmt += (stmt ? "," : "") + "(?,?,?)"; params.push(key, i, ids[j]); if (j === ids.length - 1 || params.length + 3 > MAXIMUM_QUERY_VARS) { this.db.run("INSERT INTO main.map" + this.field + " (key, res, id) VALUES " + stmt, params); stmt = ""; params = []; } } } } } for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { let stmt = "", params = []; for (let j = 0; j < ids.length; j++) { stmt += (stmt ? "," : "") + "(?,?,?,?)"; params.push(ctx_key, key, i, ids[j]); if (j === ids.length - 1 || params.length + 4 > MAXIMUM_QUERY_VARS) { this.db.run("INSERT INTO main.ctx" + this.field + " (ctx, key, res, id) VALUES " + stmt, params); stmt = ""; params = []; } } } } } } if (flexsearch.store) { let stmt = "", chunk = []; for (const item of flexsearch.store.entries()) { const id = item[0], doc = item[1]; stmt += (stmt ? "," : "") + "(?,?)"; chunk.push(id, "object" == typeof doc ? JSON.stringify(doc) : doc || null); if (chunk.length + 2 > MAXIMUM_QUERY_VARS) { this.db.run("INSERT INTO main.reg (id, doc) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); stmt = ""; chunk = []; } } if (chunk.length) { this.db.run("INSERT INTO main.reg (id, doc) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); } } else if (!flexsearch.bypass) { let ids = toArray(flexsearch.reg); for (let count = 0; count < ids.length;) { const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; const stmt = build_params(chunk.length, !0); this.db.run("INSERT INTO main.reg (id) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); } } if (flexsearch.tag) { let stmt = "", chunk = []; for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; for (let i = 0; i < ids.length; i++) { stmt += (stmt ? "," : "") + "(?,?)"; chunk.push(tag, ids[i]); } if (chunk.length + 2 > MAXIMUM_QUERY_VARS) { this.db.run("INSERT INTO main.tag" + this.field + " (tag, id) VALUES " + stmt, chunk); stmt = ""; chunk = []; } } if (chunk.length) { this.db.run("INSERT INTO main.tag" + this.field + " (tag, id) VALUES " + stmt, chunk); } } }); if (inserts.length) { await this.cleanup(); } flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; SqliteDB.prototype.remove = function (ids) { if ("object" != typeof ids) { ids = [ids]; } let next; if (ids.length > MAXIMUM_QUERY_VARS) { next = ids.slice(MAXIMUM_QUERY_VARS); ids = ids.slice(0, MAXIMUM_QUERY_VARS); } const self = this; return this.transaction(function () { const stmt = build_params(ids.length); this.db.run("DELETE FROM main.map" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.ctx" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.tag" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.reg WHERE id IN (" + stmt + ")", ids); }).then(function (result) { return next ? self.remove(next) : result; }); }; SqliteDB.prototype.cleanup = function () { return this.transaction(function () { this.db.run("DELETE FROM main.map" + this.field + " WHERE ROWID IN (SELECT ROWID FROM (SELECT ROWID, row_number() OVER dupes AS count FROM main.map" + this.field + " _t WINDOW dupes AS (PARTITION BY id, key ORDER BY res) ) WHERE count > 1)"); this.db.run("DELETE FROM main.ctx" + this.field + " WHERE ROWID IN (SELECT ROWID FROM (SELECT ROWID, row_number() OVER dupes AS count FROM main.ctx" + this.field + " _t WINDOW dupes AS (PARTITION BY id, ctx, key ORDER BY res) ) WHERE count > 1)"); }); }; SqliteDB.prototype.promisfy = function (opt) { const db = this.db; return new Promise(function (resolve, reject) { db[opt.method](opt.stmt, opt.params || [], function (err, rows) { opt.callback && opt.callback(rows); err ? reject(err) : resolve(rows); }); }); }; ================================================ FILE: dist/module/document/add.js ================================================ import { create_object, is_array, is_object, is_string, parse_simple } from "../common.js"; import { KeystoreArray } from "../keystore.js"; import Document from "../document.js"; /** * * @param id * @param content * @param {boolean=} _append * @this Document * @returns {Document|Promise} */ Document.prototype.add = function (id, content, _append) { if (is_object(id)) { content = id; id = parse_simple(content, this.key); } if (content && (id || 0 === id)) { if (!_append && this.reg.has(id)) { return this.update(id, content); } for (let i = 0, tree; i < this.field.length; i++) { tree = this.tree[i]; const index = this.index.get(this.field[i]); if ("function" == typeof tree) { const tmp = tree(content); if (tmp) { index.add(id, tmp, !0); } } else { const filter = tree._filter; if (filter && !filter(content)) { continue; } if (tree.constructor === String) { tree = ["" + tree]; } else if (is_string(tree)) { tree = [tree]; } add_index(content, tree, this.marker, 0, index, id, tree[0], _append); } } if (this.tag) { for (let x = 0; x < this.tagtree.length; x++) { let tree = this.tagtree[x], field = this.tagfield[x], ref = this.tag.get(field), dupes = create_object(), tags; if ("function" == typeof tree) { tags = tree(content); if (!tags) continue; } else { const filter = tree._filter; if (filter && !filter(content)) { continue; } if (tree.constructor === String) { tree = "" + tree; } tags = parse_simple(content, tree); } if (!ref || !tags) { continue; } if (is_string(tags)) { tags = [tags]; } for (let i = 0, tag, arr; i < tags.length; i++) { tag = tags[i]; if (!dupes[tag]) { dupes[tag] = 1; let tmp = ref.get(tag); tmp ? arr = tmp : ref.set(tag, arr = []); if (!_append || ! /** @type {!Array|KeystoreArray} */arr.includes(id)) { if (2147483647 === arr.length) { const keystore = new KeystoreArray(arr); if (this.fastupdate) { for (let value of this.reg.values()) { if (value.includes(arr)) { value[value.indexOf(arr)] = keystore; } } } ref.set(tag, arr = keystore); } arr.push(id); if (this.fastupdate) { const tmp = this.reg.get(id); tmp ? tmp.push(arr) : this.reg.set(id, [arr]); } } } } } } if (this.store && (!_append || !this.store.has(id))) { let payload; if (this.storetree) { payload = create_object(); for (let i = 0, tree; i < this.storetree.length; i++) { tree = this.storetree[i]; const filter = tree._filter; if (filter && !filter(content)) { continue; } let custom; if ("function" == typeof tree) { custom = tree(content); if (!custom) continue; tree = [tree._field]; } else if (is_string(tree) || tree.constructor === String) { payload[tree] = content[tree]; continue; } store_value(content, payload, tree, 0, tree[0], custom); } } this.store.set(id, payload || content); } if (this.worker) { this.fastupdate || this.reg.add(id); } } return this; }; /** * @param obj * @param store * @param tree * @param pos * @param key * @param {*=} custom */ function store_value(obj, store, tree, pos, key, custom) { obj = obj[key]; if (pos === tree.length - 1) { store[key] = custom || obj; } else if (obj) { if (is_array(obj)) { store = store[key] = Array(obj.length); for (let i = 0; i < obj.length; i++) { store_value(obj, store, tree, pos, i); } } else { store = store[key] || (store[key] = create_object()); key = tree[++pos]; store_value(obj, store, tree, pos, key); } } } function add_index(obj, tree, marker, pos, index, id, key, _append) { if (obj = obj[key]) { if (pos === tree.length - 1) { if (is_array(obj)) { if (marker[pos]) { for (let i = 0; i < obj.length; i++) { index.add(id, obj[i], !0); } return; } obj = obj.join(" "); } index.add(id, obj, _append, !0); } else { if (is_array(obj)) { for (let i = 0; i < obj.length; i++) { add_index(obj, tree, marker, pos, index, id, i, _append); } } else { key = tree[++pos]; add_index(obj, tree, marker, pos, index, id, key, _append); } } } } ================================================ FILE: dist/module/document/highlight.js ================================================ import { parse_simple } from "../common.js"; import Index from "../index.js"; import WorkerIndex from "../worker.js"; import { EnrichedDocumentSearchResults, EnrichedSearchResults, HighlightOptions } from "../type.js"; /** * @param {string} query * @param {EnrichedDocumentSearchResults|EnrichedSearchResults} result * @param {Map} index * @param {string} pluck * @param {HighlightOptions|string} config * @return {EnrichedDocumentSearchResults|EnrichedSearchResults} */ export function highlight_fields(query, result, index, pluck, config) { let template, markup_open, markup_close; if ("string" == typeof config) { template = config; config = ""; } else { template = config.template; } markup_open = template.indexOf("$1"); markup_close = template.substring(markup_open + 2); markup_open = template.substring(0, markup_open); let boundary = config && config.boundary, clip = !config || !1 !== config.clip, merge = config && config.merge && markup_close && markup_open && new RegExp(markup_close + " " + markup_open, "g"), ellipsis = config && config.ellipsis, ellipsis_markup_length = 0, ellipsis_markup; if ("object" == typeof ellipsis) { ellipsis_markup = ellipsis.template; ellipsis_markup_length = ellipsis_markup.length - 2; ellipsis = ellipsis.pattern; } if ("string" != typeof ellipsis) { ellipsis = !1 === ellipsis ? "" : "..."; } if (ellipsis_markup_length) { ellipsis = ellipsis_markup.replace("$1", ellipsis); } let ellipsis_length = ellipsis.length - ellipsis_markup_length, boundary_before, boundary_after; if ("object" == typeof boundary) { boundary_before = boundary.before; if (0 === boundary_before) boundary_before = -1; boundary_after = boundary.after; if (0 === boundary_after) boundary_after = -1; boundary = boundary.total || 9e5; } let encoder = new Map(), query_enc; for (let i = 0, enc, idx, path; i < result.length; i++) { /** @type {EnrichedSearchResults} */ let res; if (pluck) { res = result; path = pluck; } else { const tmp = result[i]; path = tmp.field; if (!path) continue; res = tmp.result; } idx = index.get(path); enc = idx.encoder; query_enc = encoder.get(enc); if ("string" != typeof query_enc) { query_enc = enc.encode(query); encoder.set(enc, query_enc); } for (let j = 0; j < res.length; j++) { const doc = res[j].doc; if (!doc) continue; const content = parse_simple(doc, path); if (!content) continue; const doc_org = content.trim().split(/\s+/); if (!doc_org.length) continue; let str = "", str_arr = [], pos_matches = [], pos_first_match = -1, pos_last_match = -1, length_matches_all = 0; for (let k = 0; k < doc_org.length; k++) { let doc_org_cur = doc_org[k], doc_enc_cur = enc.encode(doc_org_cur); doc_enc_cur = 1 < doc_enc_cur.length ? doc_enc_cur.join(" ") : doc_enc_cur[0]; let found; if (doc_enc_cur && doc_org_cur) { let doc_org_cur_len = doc_org_cur.length, doc_org_diff = (enc.split ? doc_org_cur.replace(enc.split, "") : doc_org_cur).length - doc_enc_cur.length, match = "", match_length = 0; for (let l = 0, query_enc_cur; l < query_enc.length; l++) { query_enc_cur = query_enc[l]; if (!query_enc_cur) continue; let query_enc_cur_len = query_enc_cur.length; query_enc_cur_len += 0 > doc_org_diff ? 0 : doc_org_diff; if (match_length && query_enc_cur_len <= match_length) { continue; } const position = doc_enc_cur.indexOf(query_enc_cur); if (-1 < position) { match = (position ? doc_org_cur.substring(0, position) : "") + markup_open + doc_org_cur.substring(position, position + query_enc_cur_len) + markup_close + (position + query_enc_cur_len < doc_org_cur_len ? doc_org_cur.substring(position + query_enc_cur_len) : ""); match_length = query_enc_cur_len; found = !0; } } if (match) { if (boundary) { if (0 > pos_first_match) { pos_first_match = str.length + (str ? 1 : 0); } pos_last_match = str.length + (str ? 1 : 0) + match.length; length_matches_all += doc_org_cur_len; pos_matches.push(str_arr.length); str_arr.push({ match }); } str += (str ? " " : "") + match; } } if (!found) { const text = doc_org[k]; str += (str ? " " : "") + text; boundary && str_arr.push({ text }); } else if (boundary) { if (length_matches_all >= boundary) { break; } } } let markup_length = pos_matches.length * (template.length - 2); if (boundary_before || boundary_after || boundary && str.length - markup_length > boundary) { let boundary_length = boundary + markup_length - 2 * ellipsis_length, length = pos_last_match - pos_first_match, start, end; if (0 < boundary_before) { length += boundary_before; } if (0 < boundary_after) { length += boundary_after; } if (length <= boundary_length) { start = boundary_before ? pos_first_match - (0 < boundary_before ? boundary_before : 0) : pos_first_match - (0 | (boundary_length - length) / 2); end = boundary_after ? pos_last_match + (0 < boundary_after ? boundary_after : 0) : start + boundary_length; if (!clip) { if (0 < start) { if (" " === str.charAt(start)) {} else if (" " !== str.charAt(start - 1)) { start = str.indexOf(" ", start); 0 > start && (start = 0); } } if (end < str.length) { if (" " === str.charAt(end - 1)) {} else if (" " !== str.charAt(end)) { end = str.lastIndexOf(" ", end); end < pos_last_match ? end = pos_last_match : ++end; } } } str = (start ? ellipsis : "") + str.substring(start, end) + (end < str.length ? ellipsis : ""); } else { const final = [], check = {}, seamless = {}, finished = {}, before = {}, after = {}; let final_length = 0, shift_left = 0, shift_right = 0, loop_left = 1, loop_right = 1; while (!0) { let loop; for (let k = 0, pos; k < pos_matches.length; k++) { pos = pos_matches[k]; if (!shift_right) { str = str_arr[pos].match; if (boundary_before) { before[k] = boundary_before; } if (boundary_after) { after[k] = boundary_after; } if (k) { final_length++; } let close; if (!pos) { seamless[k] = 1; finished[k] = 1; } else if (!k && ellipsis_length) { final_length += ellipsis_length; } if (pos >= doc_org.length - 1) { close = 1; } else if (pos < str_arr.length - 1 && str_arr[pos + 1].match) { close = 1; } else if (ellipsis_length) { final_length += ellipsis_length; } final_length -= template.length - 2; if (!k || final_length + str.length <= boundary) { final[k] = str; } else { seamless[k] = 0; loop = loop_left = loop_right = 0; break; } if (close) { seamless[k + 1] = 1; finished[k + 1] = 1; } } else { if (shift_left != shift_right) { if (finished[k + 1]) continue; pos += shift_right; if (check[pos]) { final_length -= ellipsis_length; seamless[k + 1] = 1; finished[k + 1] = 1; continue; } if (pos >= str_arr.length - 1) { if (pos >= str_arr.length) { finished[k + 1] = 1; if (pos >= doc_org.length) { seamless[k + 1] = 1; } continue; } final_length -= ellipsis_length; } str = str_arr[pos].text; let current_after = boundary_after && after[k]; if (current_after) { if (0 < current_after) { if (str.length > current_after) { finished[k + 1] = 1; if (clip) { str = str.substring(0, current_after); } else { continue; } } current_after -= str.length; if (!current_after) current_after = -1; after[k] = current_after; } else { finished[k + 1] = 1; continue; } } if (final_length + str.length + 1 <= boundary) { str = " " + str; final[k] += str; } else if (clip) { const diff = boundary - final_length - 1; if (0 < diff) { str = " " + str.substring(0, diff); final[k] += str; } finished[k + 1] = 1; } else { finished[k + 1] = 1; continue; } } else { if (finished[k]) continue; pos -= shift_left; if (check[pos]) { final_length -= ellipsis_length; finished[k] = 1; seamless[k] = 1; continue; } if (0 >= pos) { if (0 > pos) { finished[k] = 1; seamless[k] = 1; continue; } final_length -= ellipsis_length; } str = str_arr[pos].text; let current_before = boundary_before && before[k]; if (current_before) { if (0 < current_before) { if (str.length > current_before) { finished[k] = 1; if (clip) { str = str.substring(str.length - current_before); } else { continue; } } current_before -= str.length; if (!current_before) current_before = -1; before[k] = current_before; } else { finished[k] = 1; continue; } } if (final_length + str.length + 1 <= boundary) { str += " "; final[k] = str + final[k]; } else if (clip) { const diff = str.length + 1 - (boundary - final_length); if (0 <= diff && diff < str.length) { str = str.substring(diff) + " "; final[k] = str + final[k]; } finished[k] = 1; } else { finished[k] = 1; continue; } } } final_length += str.length; check[pos] = 1; loop = 1; } if (loop) { shift_left == shift_right ? shift_right++ : shift_left++; } else { shift_left == shift_right ? loop_left = 0 : loop_right = 0; if (!loop_left && !loop_right) { break; } if (loop_left) { shift_left++; shift_right = shift_left; } else { shift_right++; } } } str = ""; for (let k = 0, tmp; k < final.length; k++) { tmp = (seamless[k] ? k ? " " : "" : (k && !ellipsis ? " " : "") + ellipsis) + final[k]; str += tmp; } if (ellipsis && !seamless[final.length]) { str += ellipsis; } } } if (merge) { str = str.replace( /** @type {RegExp} */merge, " "); } res[j].highlight = str; } if (pluck) { break; } } return result; } ================================================ FILE: dist/module/document/search.js ================================================ import { DocumentSearchOptions, DocumentSearchResults, EnrichedDocumentSearchResults, MergedDocumentSearchResults, MergedDocumentSearchEntry, EnrichedSearchResults, SearchResults, IntermediateSearchResults } from "../type.js"; import { create_object, is_array, is_object, is_string, inherit } from "../common.js"; import { intersect, intersect_union } from "../intersect.js"; import Document from "../document.js"; import Index from "../index.js"; import WorkerIndex from "../worker.js"; import Resolver from "../resolver.js"; import tick from "../profiler.js"; import { highlight_fields } from "./highlight.js"; /** * @param {!string|DocumentSearchOptions} query * @param {number|DocumentSearchOptions=} limit * @param {DocumentSearchOptions=} options * @param {Array=} _promises async recursion * @this Document * @returns { * DocumentSearchResults| * EnrichedDocumentSearchResults| * MergedDocumentSearchResults| * SearchResults| * IntermediateSearchResults| * EnrichedSearchResults| * Resolver | * Promise< * DocumentSearchResults| * EnrichedDocumentSearchResults| * MergedDocumentSearchResults| * SearchResults| * IntermediateSearchResults| * EnrichedSearchResults| * Resolver * > * } */ Document.prototype.search = function (query, limit, options, _promises) { if (!options) { if (!limit && is_object(query)) { options = /** @type {DocumentSearchOptions} */query; query = ""; } else if (is_object(limit)) { options = /** @type {DocumentSearchOptions} */limit; limit = 0; } } /** @type { * DocumentSearchResults| * EnrichedDocumentSearchResults| * MergedDocumentSearchResults| * SearchResults| * IntermediateSearchResults| * EnrichedSearchResults * } */ let result = [], result_field = [], pluck, enrich, merge, suggest, boost, cache, field, tag, offset, count = 0, resolve = !0, highlight; if (options) { if (is_array(options)) { options = /** @type DocumentSearchOptions */{ index: options }; } query = options.query || query; pluck = options.pluck; merge = options.merge; boost = options.boost; field = pluck || options.field || (field = options.index) && (field.index ? null : field); tag = this.tag && options.tag; suggest = options.suggest; resolve = !1 !== options.resolve; cache = options.cache; highlight = resolve && this.store && options.highlight; enrich = !!highlight || resolve && this.store && options.enrich; limit = options.limit || limit; offset = options.offset || 0; limit || (limit = resolve ? 100 : 0); if (tag && (!this.db || !_promises)) { if (tag.constructor !== Array) { tag = [tag]; } let pairs = []; for (let i = 0, field; i < tag.length; i++) { field = tag[i]; if (field.field && field.tag) { const value = field.tag; if (value.constructor === Array) { for (let k = 0; k < value.length; k++) { pairs.push(field.field, value[k]); } } else { pairs.push(field.field, value); } } else { const keys = Object.keys(field); for (let j = 0, key, value; j < keys.length; j++) { key = keys[j]; value = field[key]; if (value.constructor === Array) { for (let k = 0; k < value.length; k++) { pairs.push(key, value[k]); } } else { pairs.push(key, value); } } } } tag = pairs; if (!query) { let promises = []; if (pairs.length) for (let j = 0; j < pairs.length; j += 2) { let ids; if (this.db) { const index = this.index.get(pairs[j]); if (!index) { continue; } promises.push(ids = index.db.tag(pairs[j + 1], limit, offset, enrich)); } else { ids = get_tag.call(this, pairs[j], pairs[j + 1], limit, offset, enrich); } result.push(resolve ? { field: pairs[j], tag: pairs[j + 1], result: ids } : [ids]); } if (promises.length) { const self = this; return Promise.all(promises).then(function (promises) { for (let j = 0; j < promises.length; j++) { if (resolve) { result[j].result = promises[j]; } else { result[j] = promises[j]; } } return resolve ? result : new Resolver(1 < result.length ? intersect( /** @type {!Array} */result, 1, 0, 0, suggest, boost) : result[0], self); }); } return resolve ? result : new Resolver(1 < result.length ? intersect( /** @type {!Array} */result, 1, 0, 0, suggest, boost) : result[0], this); } } if (!resolve && !pluck) { field = field || this.field; if (field) { if (is_string(field)) { pluck = field; } else { if (is_array(field) && 1 === field.length) { field = field[0]; } pluck = field.field || field.index; } } } if (field && field.constructor !== Array) { field = [field]; } } field || (field = this.field); let db_tag_search, promises = (this.worker || this.db /*|| (SUPPORT_ASYNC && this.async)*/ ) && !_promises && []; for (let i = 0, res, key, len; i < field.length; i++) { key = field[i]; if (this.db && this.tag) { if (!this.tree[i]) { continue; } } let field_options; if (!is_string(key)) { field_options = key; key = field_options.field; query = field_options.query || query; limit = inherit(field_options.limit, limit); offset = inherit(field_options.offset, offset); suggest = inherit(field_options.suggest, suggest); highlight = resolve && this.store && inherit(field_options.highlight, highlight); enrich = !!highlight || resolve && this.store && inherit(field_options.enrich, enrich); cache = inherit(field_options.cache, cache); } if (_promises) { res = _promises[i]; } else { const opt = field_options || options || {}, opt_enrich = opt.enrich, index = this.index.get(key); if (tag) { if (this.db) { opt.tag = tag; opt.field = field; db_tag_search = index.db.support_tag_search; } if (!db_tag_search && opt_enrich) { opt.enrich = !1; } if (!db_tag_search) { opt.limit = 0; opt.offset = 0; } } res = cache ? index.searchCache(query, tag && !db_tag_search ? 0 : limit, opt) : index.search(query, tag && !db_tag_search ? 0 : limit, opt); if (tag && !db_tag_search) { opt.limit = limit; opt.offset = offset; } if (opt_enrich) { opt.enrich = opt_enrich; } if (promises) { promises[i] = res; continue; } } res = res.result || res; len = res && res.length; if (tag && len) { const arr = []; let count = 0; if (this.db && _promises) { if (!db_tag_search) { for (let y = field.length; y < _promises.length; y++) { let ids = _promises[y], len = ids && ids.length; if (len) { count++; arr.push(ids); } else if (!suggest) { return resolve ? result : new Resolver(result, this); } } } } else { for (let y = 0, ids, len; y < tag.length; y += 2) { ids = this.tag.get(tag[y]); if (!ids) { if (suggest) { continue; } else { return resolve ? result : new Resolver(result, this); } } ids = ids && ids.get(tag[y + 1]); len = ids && ids.length; if (len) { count++; arr.push(ids); } else if (!suggest) { return resolve ? result : new Resolver(result, this); } } } if (count) { res = intersect_union( /** @type {IntermediateSearchResults} */res, arr, limit, offset, resolve); len = res.length; if (!len && !suggest) { return resolve ? res : new Resolver( /** @type {IntermediateSearchResults} */res, this); } count--; } } if (len) { result_field[count] = key; result.push(res); count++; } else if (1 === field.length) { return resolve ? result : new Resolver(result, this); } } if (promises) { if (this.db) { if (tag && tag.length && !db_tag_search) { for (let y = 0; y < tag.length; y += 2) { const index = this.index.get(tag[y]); if (!index) { if (suggest) { continue; } else { return resolve ? result : new Resolver(result, this); } } promises.push(index.db.tag(tag[y + 1], limit, offset, !1)); } } } const self = this; return Promise.all(promises).then(function (result) { options && (options.resolve = resolve); if (result.length) { result = self.search(query, limit, options, result); } return result; }); } if (!count) { return resolve ? result : new Resolver(result, this); } if (pluck && (!enrich || !this.store)) { result = /** @type {SearchResults|IntermediateSearchResults} */result[0]; return resolve ? result : new Resolver(result, this); } promises = []; for (let i = 0, res; i < result_field.length; i++) { /** @type {SearchResults|EnrichedSearchResults} */ res = result[i]; if (enrich && res.length && "undefined" == typeof res[0].doc) { if (!this.db) { res = /** @type {EnrichedSearchResults} */apply_enrich.call(this, res); } else { promises.push(res = this.index.get(this.field[0]).db.enrich(res)); } } if (pluck) { return resolve ? highlight ? highlight_fields( /** @type {string} */query, res, this.index, pluck, highlight) : /** @type {SearchResults|EnrichedSearchResults} */res : new Resolver( /** @type {IntermediateSearchResults} */res, this); } result[i] = { field: result_field[i], result: /** @type {SearchResults|EnrichedSearchResults} */res }; } if (enrich && !0 && this.db && promises.length) { const self = this; return Promise.all(promises).then(function (promises) { for (let j = 0; j < promises.length; j++) { result[j].result = promises[j]; } if (highlight) { result = highlight_fields( /** @type {string} */query, result, self.index, pluck, highlight); } return merge ? merge_fields(result) : /** @type {DocumentSearchResults} */result; }); } if (highlight) { result = highlight_fields( /** @type {string} */query, result, this.index, pluck, highlight); } return merge ? merge_fields(result) : /** @type {DocumentSearchResults} */result; }; /** * @param {DocumentSearchResults} fields * @return {MergedDocumentSearchResults} */ function merge_fields(fields) { /** @type {MergedDocumentSearchResults} */ const final = [], group_field = create_object(), group_highlight = create_object(); for (let i = 0, field, key, res, id, entry, tmp, highlight; i < fields.length; i++) { field = fields[i]; key = field.field; res = field.result; for (let j = 0; j < res.length; j++) { entry = res[j]; "object" != typeof entry ? entry = { id: id = entry } : id = entry.id; tmp = group_field[id]; if (!tmp) { entry.field = group_field[id] = [key]; final.push( /** @type {!MergedDocumentSearchEntry} */entry); } else { tmp.push(key); } if (highlight = entry.highlight) { tmp = group_highlight[id]; if (!tmp) { group_highlight[id] = tmp = {}; entry.highlight = tmp; } tmp[key] = highlight; } } } return final; } /** * @this {Document} */ function get_tag(tag, key, limit, offset, enrich) { let res = this.tag.get(tag); if (!res) return []; res = res.get(key); if (!res) return []; let len = res.length - offset; if (0 < len) { if (limit && len > limit || offset) { res = res.slice(offset, offset + limit); } if (enrich) { res = apply_enrich.call(this, res); } } return res; } /** * @param {SearchResults} ids * @return {EnrichedSearchResults|SearchResults|Promise} * @this {Document|Index|WorkerIndex|null} */ export function apply_enrich(ids) { if (!this || !this.store) return ids; if (this.db) { return this.index.get(this.field[0]).db.enrich(ids); } /** @type {EnrichedSearchResults} */ const result = Array(ids.length); for (let x = 0, id; x < ids.length; x++) { id = ids[x]; result[x] = { id: id, doc: this.store.get(id) }; } return result; } ================================================ FILE: dist/module/document.js ================================================ /**! * FlexSearch.js * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ import { IndexOptions, DocumentOptions, DocumentDescriptor, FieldOptions, StoreOptions, EncoderOptions } from "./type.js"; import StorageInterface from "./db/interface.js"; import Index from "./index.js"; import WorkerIndex from "./worker.js"; import Encoder, { fallback_encoder } from "./encoder.js"; import Cache, { searchCache } from "./cache.js"; import { is_string, is_object, parse_simple } from "./common.js"; import apply_async from "./async.js"; import { exportDocument, importDocument } from "./serialize.js"; import { KeystoreMap, KeystoreSet } from "./keystore.js"; import "./document/add.js"; import "./document/search.js"; import Charset from "./charset.js"; /** * @constructor * @param {!DocumentOptions} options * @return {Document|Promise} * @this {Document} */ export default function Document(options) { if (!this || this.constructor !== Document) { return new Document(options); } const document = /** @type DocumentDescriptor */options.document || options.doc || options; let tmp, keystore; this.tree = []; this.field = []; this.marker = []; this.key = (tmp = document.key || document.id) && parse_tree(tmp, this.marker) || "id"; keystore = options.keystore || 0; keystore && (this.keystore = keystore); this.fastupdate = !!options.fastupdate; /** @type { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } */ this.reg = this.fastupdate && !options.worker && !options.db ? keystore && !0 ? new KeystoreMap(keystore) : new Map() : keystore && !0 ? new KeystoreSet(keystore) : new Set(); this.storetree = (tmp = document.store || null) && tmp && !0 !== tmp && []; /** @type {Map|KeystoreMap} */ this.store = tmp ? keystore && !0 ? new KeystoreMap(keystore) : new Map() : null; this.cache = (tmp = options.cache || null) && new Cache(tmp); options.cache = !1; this.worker = options.worker || !1; this.priority = options.priority || 4; /** * @type {Map} */ this.index = parse_descriptor.call(this, options, document); this.tag = null; if (tmp = document.tag) { if ("string" == typeof tmp) { tmp = [tmp]; } if (tmp.length) { this.tag = new Map(); this.tagtree = []; this.tagfield = []; for (let i = 0, params, field; i < tmp.length; i++) { params = tmp[i]; field = params.field || params; if (!field) { throw new Error("The tag field from the document descriptor is undefined."); } if (params.custom) { this.tagtree[i] = params.custom; } else { this.tagtree[i] = parse_tree(field, this.marker); if (params.filter) { if ("string" == typeof this.tagtree[i]) { this.tagtree[i] = new String(this.tagtree[i]); } this.tagtree[i]._filter = params.filter; } } this.tagfield[i] = field; this.tag.set(field, new Map()); } } } if (this.worker) { this.fastupdate = !1; const promises = []; for (const index of this.index.values()) { index.then && promises.push(index); } if (promises.length) { const self = this; return Promise.all(promises).then(function (result) { let count = 0; for (const item of self.index.entries()) { const key = /** @type {string} */item[0]; let index = /** @type {Index|WorkerIndex | Promise} */item[1]; if (index.then) { index = result[count]; self.index.set(key, index); count++; } } return self; }); } } else { if (options.db) { this.fastupdate = !1; this.mount(options.db); } } } /** * @param {!StorageInterface} db * @return {Promise} */ Document.prototype.mount = function (db) { let fields = this.field; if (this.tag) { for (let i = 0, field; i < this.tagfield.length; i++) { field = this.tagfield[i]; let index; this.index.set(field, index = new Index( /** @type IndexOptions */{}, this.reg)); if (fields === this.field) { fields = fields.slice(0); } fields.push(field); index.tag = this.tag.get(field); } } const promises = [], config = { db: db.db, type: db.type, fastupdate: db.fastupdate }; for (let i = 0, index, field; i < fields.length; i++) { config.field = field = fields[i]; index = this.index.get(field); const dbi = new db.constructor(db.id, config); dbi.id = db.id; promises[i] = dbi.mount(index); index.document = !0; if (i) { index.bypass = !0; } else { index.store = this.store; } } const self = this; return this.db = Promise.all(promises).then(function () { self.db = !0; }); }; Document.prototype.commit = async function () { const promises = []; for (const index of this.index.values()) { promises.push(index.commit()); } await Promise.all(promises); this.reg.clear(); }; Document.prototype.destroy = function () { const promises = []; for (const idx of this.index.values()) { promises.push(idx.destroy()); } return Promise.all(promises); }; /** * @this {Document} * @return {Map} */ function parse_descriptor(options, document) { /** @type {Map} */ const index = new Map(); let field = document.index || document.field || document; if (is_string(field)) { field = [field]; } for (let i = 0, key, opt; i < field.length; i++) { key = field[i]; if (!is_string(key)) { opt = key; key = key.field; } opt = /** @type IndexOptions */is_object(opt) ? Object.assign({}, options, opt) : options; if (this.worker) { let encoder = opt.encoder; encoder = encoder && encoder.encode ? encoder : new Encoder("string" == typeof encoder ? Charset[encoder] : encoder || {}); const worker = new WorkerIndex(opt, /** @type {Encoder} */encoder); if (worker) { index.set(key, worker); } else { this.worker = !1; } } if (!this.worker) { index.set(key, new Index( /** @type IndexOptions */opt, this.reg)); } if (opt.custom) { this.tree[i] = opt.custom; } else { this.tree[i] = parse_tree(key, this.marker); if (opt.filter) { if ("string" == typeof this.tree[i]) { this.tree[i] = new String(this.tree[i]); } this.tree[i]._filter = opt.filter; } } this.field[i] = key; } if (this.storetree) { let stores = document.store; if (is_string(stores)) stores = [stores]; for (let i = 0, store, field; i < stores.length; i++) { store = /** @type Array */stores[i]; field = store.field || store; if (store.custom) { this.storetree[i] = store.custom; store.custom._field = field; } else { this.storetree[i] = parse_tree(field, this.marker); if (store.filter) { if ("string" == typeof this.storetree[i]) { this.storetree[i] = new String(this.storetree[i]); } this.storetree[i]._filter = store.filter; } } } } return index; } function parse_tree(key, marker) { const tree = key.split(":"); let count = 0; for (let i = 0; i < tree.length; i++) { key = tree[i]; if ("]" === key[key.length - 1]) { key = key.substring(0, key.length - 2); if (key) { marker[count] = !0; } } if (key) { tree[count++] = key; } } if (count < tree.length) { tree.length = count; } return 1 < count ? tree : tree[0]; } /** * @param {!number|Object} id * @param {!Object} content * @return {Document|Promise} */ Document.prototype.append = function (id, content) { return this.add(id, content, !0); }; /** * @param {!number|Object} id * @param {!Object} content * @return {Document|Promise} */ Document.prototype.update = function (id, content) { return this.remove(id).add(id, content); }; /** * @param {!number|Object} id * @return {Document|Promise} */ Document.prototype.remove = function (id) { if (is_object(id)) { id = parse_simple(id, this.key); } for (const index of this.index.values()) { index.remove(id, !0); } if (this.reg.has(id)) { if (this.tag) { if (!this.fastupdate) { for (let field of this.tag.values()) { for (let item of field) { const tag = item[0], ids = item[1], pos = ids.indexOf(id); if (-1 < pos) { 1 < ids.length ? ids.splice(pos, 1) : field.delete(tag); } } } } } if (this.store) { this.store.delete(id); } this.reg.delete(id); } if (this.cache) { this.cache.remove(id); } return this; }; Document.prototype.clear = function () { const promises = []; for (const index of this.index.values()) { const promise = index.clear(); if (promise.then) { promises.push(promise); } } if (this.tag) { for (const tags of this.tag.values()) { tags.clear(); } } if (this.store) { this.store.clear(); } if (this.cache) { this.cache.clear(); } return promises.length ? Promise.all(promises) : this; }; /** * @param {number|string} id * @return {boolean|Promise} */ Document.prototype.contain = function (id) { if (this.db) { return this.index.get(this.field[0]).db.has(id); } return this.reg.has(id); }; Document.prototype.cleanup = function () { for (const index of this.index.values()) { index.cleanup(); } return this; }; /** * @param {number|string} id * @return {Object} */ Document.prototype.get = function (id) { if (this.db) { return this.index.get(this.field[0]).db.enrich(id).then(function (result) { return result[0] && result[0].doc || null; }); } return this.store.get(id) || null; }; /** * @param {number|string|Object} id * @param {Object} data * @return {Document} */ Document.prototype.set = function (id, data) { if ("object" == typeof id) { data = id; id = parse_simple(data, this.key); } this.store.set(id, data); return this; }; Document.prototype.searchCache = searchCache; Document.prototype.export = exportDocument; Document.prototype.import = importDocument; apply_async(Document.prototype); ================================================ FILE: dist/module/encoder.js ================================================ import { create_object, merge_option } from "./common.js"; import normalize_polyfill from "./charset/polyfill.js"; import { EncoderOptions } from "./type.js"; /* Custom Encoder ---------------- function englishEncoder(string){ return string.toLowerCase().split(/[^a-z]+/) } function chineseEncoder(string){ return string.replace(/\s+/, "").split("") } function fixedEncoder(string){ return [string] } Built-in Encoder ---------------------------- The main workflow follows an increasing strategy, starting from a simple .toLowerCase() to full RegExp Pipeline: 1. apply this.normalize (charset normalization) applied on the whole input string e.g. lowercase, everything you put later into (filter, matcher, stemmer, mapper, etc.) has to be normalized by definition, because it won't apply to them automatically 2. apply this.prepare (custom function, string in - string out) 3 split numerics into triplets 4. split input into terms (by one of them: split/include/exclude) 5. pre-encoded term deduplication 6. apply this.filter (stop-words) 7. apply this.stemmer (replace term endings) 8. apply this.mapper (replace chars) 9. apply this.dedupe (letter deduplication) 10. apply this.matcher (replace terms) 11. apply this.replacer (custom regex) 12. post-encoded term deduplication 13. apply this.finalize (custom function, array in - array out) */ const whitespace = /[^\p{L}\p{N}]+/u, numeric_split_length = /(\d{3})/g, numeric_split_prev_char = /(\D)(\d{3})/g, numeric_split_next_char = /(\d{3})(\D)/g, normalize = /[\u0300-\u036f]/g; /** * @param {EncoderOptions=} options * @constructor */ export default function Encoder(options = {}) { if (!this || this.constructor !== Encoder) { return new Encoder(...arguments); } if (arguments.length) { for (let i = 0; i < arguments.length; i++) { this.assign( /** @type {!EncoderOptions} */arguments[i]); } } else { this.assign( /** @type {!EncoderOptions} */options); } } /** * @param {!EncoderOptions} options */ Encoder.prototype.assign = function (options) { /** * pre-processing string input * @type {Function|boolean} */ this.normalize = /** @type {Function|boolean} */merge_option(options.normalize, !0, this.normalize); let include = options.include, tmp = include || options.exclude || options.split, numeric; if (tmp || "" === tmp) { if ("object" == typeof tmp && tmp.constructor !== RegExp) { let regex = ""; numeric = !include; include || (regex += "\\p{Z}"); if (tmp.letter) { regex += "\\p{L}"; } if (tmp.number) { regex += "\\p{N}"; numeric = !!include; } if (tmp.symbol) { regex += "\\p{S}"; } if (tmp.punctuation) { regex += "\\p{P}"; } if (tmp.control) { regex += "\\p{C}"; } if (tmp = tmp.char) { regex += "object" == typeof tmp ? tmp.join("") : tmp; } try { /** * split string input into terms * @type {string|RegExp|boolean|null} */ this.split = new RegExp("[" + (include ? "^" : "") + regex + "]+", "u"); } catch (e) { this.split = /\s+/; } } else { this.split = /** @type {string|RegExp|boolean} */tmp; numeric = !1 === tmp || 2 > "a1a".split(tmp).length; } this.numeric = merge_option(options.numeric, numeric); } else { try { this.split = /** @type {string|RegExp|boolean} */merge_option(this.split, whitespace); } catch (e) { this.split = /\s+/; } this.numeric = merge_option(options.numeric, merge_option(this.numeric, !0)); } /** * post-processing terms * @type {Function|null} */ this.prepare = /** @type {Function|null} */merge_option(options.prepare, null, this.prepare); /** * final processing * @type {Function|null} */ this.finalize = /** @type {Function|null} */merge_option(options.finalize, null, this.finalize); tmp = options.filter; this.filter = "function" == typeof tmp ? tmp : merge_option(tmp && new Set(tmp), null, this.filter); this.dedupe = merge_option(options.dedupe, !0, this.dedupe); this.matcher = merge_option((tmp = options.matcher) && new Map(tmp), null, this.matcher); this.mapper = merge_option((tmp = options.mapper) && new Map(tmp), null, this.mapper); this.stemmer = merge_option((tmp = options.stemmer) && new Map(tmp), null, this.stemmer); this.replacer = merge_option(options.replacer, null, this.replacer); this.minlength = merge_option(options.minlength, 1, this.minlength); this.maxlength = merge_option(options.maxlength, 1024, this.maxlength); this.rtl = merge_option(options.rtl, !1, this.rtl); this.cache = tmp = merge_option(options.cache, !0, this.cache); if (tmp) { this.timer = null; this.cache_size = "number" == typeof tmp ? tmp : 2e5; this.cache_enc = new Map(); this.cache_term = new Map(); this.cache_enc_length = 128; this.cache_term_length = 128; } this.matcher_str = ""; this.matcher_test = null; this.stemmer_str = ""; this.stemmer_test = null; if (this.matcher) { for (const key of this.matcher.keys()) { this.matcher_str += (this.matcher_str ? "|" : "") + key; } } if (this.stemmer) { for (const key of this.stemmer.keys()) { this.stemmer_str += (this.stemmer_str ? "|" : "") + key; } } return this; }; Encoder.prototype.addStemmer = function (match, replace) { this.stemmer || (this.stemmer = new Map()); this.stemmer.set(match, replace); this.stemmer_str += (this.stemmer_str ? "|" : "") + match; this.stemmer_test = null; this.cache && clear(this); return this; }; Encoder.prototype.addFilter = function (term) { if ("function" == typeof term) { this.filter = term; } else { this.filter || (this.filter = new Set()); this.filter.add(term); } this.cache && clear(this); return this; }; /** * Replace a single char * @param {string} char_match * @param {string} char_replace * @return {Encoder} * @suppress {invalidCasts} */ Encoder.prototype.addMapper = function (char_match, char_replace) { if ("object" == typeof char_match) { return this.addReplacer( /** @type {RegExp} */char_match, char_replace); } if (1 < char_match.length) { return this.addMatcher(char_match, char_replace); } this.mapper || (this.mapper = new Map()); this.mapper.set(char_match, char_replace); this.cache && clear(this); return this; }; /** * Replace a string * @param {string} match * @param {string} replace * @return {Encoder} * @suppress {invalidCasts} */ Encoder.prototype.addMatcher = function (match, replace) { if ("object" == typeof match) { return this.addReplacer( /** @type {RegExp} */match, replace); } if (2 > match.length && (this.dedupe || this.mapper)) { return this.addMapper(match, replace); } this.matcher || (this.matcher = new Map()); this.matcher.set(match, replace); this.matcher_str += (this.matcher_str ? "|" : "") + match; this.matcher_test = null; this.cache && clear(this); return this; }; /** * @param {RegExp} regex * @param {string} replace * @return {Encoder} * @suppress {invalidCasts} */ Encoder.prototype.addReplacer = function (regex, replace) { if ("string" == typeof regex) { return this.addMatcher( /** @type {string} */regex, replace); } this.replacer || (this.replacer = []); this.replacer.push(regex, replace); this.cache && clear(this); return this; }; /** * @param {!string} str * @param {boolean=} dedupe_terms Note: term deduplication will break the context chain * @return {!Array} */ Encoder.prototype.encode = function (str, dedupe_terms) { if (this.cache && str.length <= this.cache_enc_length) { if (this.timer) { if (this.cache_enc.has(str)) { return this.cache_enc.get(str); } } else { this.timer = setTimeout(clear, 50, this); } } if (this.normalize) { if ("function" == typeof this.normalize) { str = this.normalize(str); } else if (normalize) { str = str.normalize("NFKD").replace(normalize, "").toLowerCase(); } else { str = str.toLowerCase(); } } if (this.prepare) { str = this.prepare(str); } if (this.numeric && 3 < str.length) { str = str.replace(numeric_split_prev_char, "$1 $2").replace(numeric_split_next_char, "$1 $2").replace(numeric_split_length, "$1 "); } const skip = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer); let final = [], dupes = create_object(), last_term, last_term_enc, words = this.split || "" === this.split ? str.split( /** @type {string|RegExp} */this.split) : [str]; for (let i = 0, word, base; i < words.length; i++) { if (!(word = base = words[i])) { continue; } if (word.length < this.minlength || word.length > this.maxlength) { continue; } if (dedupe_terms) { if (dupes[word]) { continue; } dupes[word] = 1; } else { if (last_term === word) { continue; } last_term = word; } if (skip) { final.push(word); continue; } if (this.filter && ("function" == typeof this.filter ? !this.filter(word) : this.filter.has(word))) { continue; } if (this.cache && word.length <= this.cache_term_length) { if (this.timer) { const tmp = this.cache_term.get(word); if (tmp || "" === tmp) { tmp && final.push(tmp); continue; } } else { this.timer = setTimeout(clear, 50, this); } } if (this.stemmer) { this.stemmer_test || (this.stemmer_test = new RegExp("(?!^)(" + this.stemmer_str + ")$")); let old; while (old !== word && 2 < word.length) { old = word; word = word.replace(this.stemmer_test, match => this.stemmer.get(match)); } } if (word && (this.mapper || this.dedupe && 1 < word.length)) { let final = ""; for (let i = 0, prev = "", char, tmp; i < word.length; i++) { char = word.charAt(i); if (char !== prev || !this.dedupe) { tmp = this.mapper && this.mapper.get(char); if (!tmp && "" !== tmp) final += prev = char;else if ((tmp !== prev || !this.dedupe) && (prev = tmp)) final += tmp; } } word = final; } if (this.matcher && 1 < word.length) { this.matcher_test || (this.matcher_test = new RegExp("(" + this.matcher_str + ")", "g")); word = word.replace(this.matcher_test, match => this.matcher.get(match)); } if (word && this.replacer) { for (let i = 0; word && i < this.replacer.length; i += 2) { word = word.replace(this.replacer[i], this.replacer[i + 1]); } } if (this.cache && base.length <= this.cache_term_length) { this.cache_term.set(base, word); if (this.cache_term.size > this.cache_size) { this.cache_term.clear(); this.cache_term_length = 0 | this.cache_term_length / 1.1; } } if (word) { if (word !== base) { if (dedupe_terms) { if (dupes[word]) { continue; } dupes[word] = 1; } else { if (last_term_enc === word) { continue; } last_term_enc = word; } } final.push(word); } } if (this.finalize) { final = this.finalize(final) || final; } if (this.cache && str.length <= this.cache_enc_length) { this.cache_enc.set(str, final); if (this.cache_enc.size > this.cache_size) { this.cache_enc.clear(); this.cache_enc_length = 0 | this.cache_enc_length / 1.1; } } return final; }; export function fallback_encoder(str) { return str.normalize("NFKD").replace(normalize, "").toLowerCase().trim().split(/\s+/); } /** * @param {Encoder} self */ function clear(self) { self.timer = null; self.cache_enc.clear(); self.cache_term.clear(); } ================================================ FILE: dist/module/index/add.js ================================================ import { create_object } from "../common.js"; import Index, { autoCommit } from "../index.js"; import default_compress from "../compress.js"; import { KeystoreArray, KeystoreMap } from "../keystore.js"; /** * @param {!number|string} id * @param {!string} content * @param {boolean=} _append * @param {boolean=} _skip_update */ Index.prototype.add = function (id, content, _append, _skip_update) { if (content && (id || 0 === id)) { if (!_skip_update && !_append) { if (this.reg.has(id)) { return this.update(id, content); } } const depth = this.depth; content = this.encoder.encode(content, !depth); const term_count = content.length; if (term_count) { const dupes_ctx = create_object(), dupes = create_object(), resolution = this.resolution; for (let i = 0; i < term_count; i++) { let term = content[this.rtl ? term_count - 1 - i : i], term_length = term.length; if (term_length && (depth || !dupes[term])) { let score = this.score ? this.score(content, term, i, null, 0) : get_score(resolution, term_count, i), token = ""; switch (this.tokenize) { case "tolerant": this._push_index(dupes, term, score, id, _append); if (2 < term_length) { for (let x = 1, char_a, char_b, prt_1, prt_2; x < term_length - 1; x++) { char_a = term.charAt(x); char_b = term.charAt(x + 1); prt_1 = term.substring(0, x) + char_b; prt_2 = term.substring(x + 2); token = prt_1 + char_a + prt_2; this._push_index(dupes, token, score, id, _append); token = prt_1 + prt_2; this._push_index(dupes, token, score, id, _append); } this._push_index(dupes, term.substring(0, term.length - 1), score, id, _append); } break; case "full": if (2 < term_length) { for (let x = 0, _x; x < term_length; x++) { for (let y = term_length; y > x; y--) { token = term.substring(x, y); _x = this.rtl ? term_length - 1 - x : x; const partial_score = this.score ? this.score(content, term, i, token, _x) : get_score(resolution, term_count, i, term_length, _x); this._push_index(dupes, token, partial_score, id, _append); } } break; } case "bidirectional": case "reverse": if (1 < term_length) { for (let x = term_length - 1; 0 < x; x--) { token = term[this.rtl ? term_length - 1 - x : x] + token; const partial_score = this.score ? this.score(content, term, i, token, x) : get_score(resolution, term_count, i, term_length, x); this._push_index(dupes, token, partial_score, id, _append); } token = ""; } case "forward": if (1 < term_length) { for (let x = 0; x < term_length; x++) { token += term[this.rtl ? term_length - 1 - x : x]; this._push_index(dupes, token, score, id, _append); } break; } default: this._push_index(dupes, term, score, id, _append); if (depth && 1 < term_count && i < term_count - 1) { const resolution = this.resolution_ctx, keyword = term, size = Math.min(depth + 1, this.rtl ? i + 1 : term_count - i); for (let x = 1; x < size; x++) { term = content[this.rtl ? term_count - 1 - i - x : i + x]; const swap = this.bidirectional && term > keyword, context_score = this.score ? this.score(content, keyword, i, term, x - 1) : get_score(resolution + (term_count / 2 > resolution ? 0 : 1), term_count, i, size - 1, x - 1); this._push_index(dupes_ctx, swap ? keyword : term, context_score, id, _append, swap ? term : keyword); } } } } } this.fastupdate || this.reg.add(id); } } if (this.db) { this.commit_task.push(_append ? { ins: id } : { del: id }); this.commit_auto && autoCommit(this); } return this; }; /** * @private * @param dupes * @param term * @param score * @param id * @param {boolean=} append * @param {string=} keyword */ Index.prototype._push_index = function (dupes, term, score, id, append, keyword) { let res, arr; if (!(res = dupes[term]) || keyword && !res[keyword]) { if (keyword) { dupes = res || (dupes[term] = create_object()); dupes[keyword] = 1; if (this.compress) { keyword = default_compress(keyword); } arr = this.ctx; res = arr.get(keyword); res ? arr = res : arr.set(keyword, arr = this.keystore ? new KeystoreMap(this.keystore) : new Map()); } else { arr = this.map; dupes[term] = 1; } if (this.compress) { term = default_compress(term); } res = arr.get(term); res ? arr = res : arr.set(term, arr = res = []); if (append) { for (let i = 0, arr; i < res.length; i++) { arr = res[i]; if (arr && arr.includes(id)) { if (i <= score) { return; } else { arr.splice(arr.indexOf(id), 1); if (this.fastupdate) { const tmp = this.reg.get(id); tmp && tmp.splice(tmp.indexOf(arr), 1); } } break; } } } arr = arr[score] || (arr[score] = []); arr.push(id); if (2147483647 === arr.length) { const keystore = new KeystoreArray(arr); if (this.fastupdate) { for (let value of this.reg.values()) { if (value.includes(arr)) { value[value.indexOf(arr)] = keystore; } } } res[score] = arr = keystore; } if (this.fastupdate) { const tmp = this.reg.get(id); tmp ? tmp.push(arr) : this.reg.set(id, [arr]); } } }; /** * @param {number} resolution * @param {number} length * @param {number} i * @param {number=} term_length * @param {number=} x * @returns {number} */ function get_score(resolution, length, i, term_length, x) { return i && 1 < resolution ? length + (term_length || 0) <= resolution ? i + (x || 0) : 0 | (resolution - 1) / (length + (term_length || 0)) * (i + (x || 0)) + 1 : 0; } ================================================ FILE: dist/module/index/remove.js ================================================ import { is_array } from "../common.js"; import Index, { autoCommit } from "../index.js"; import { KeystoreMap } from "../keystore.js"; /** * @param {!number|string} id * @param {boolean=} _skip_deletion */ Index.prototype.remove = function (id, _skip_deletion) { const refs = this.reg.size && (this.fastupdate ? this.reg.get(id) : this.reg.has(id)); if (refs) { if (this.fastupdate) { for (let i = 0, tmp, len; i < refs.length; i++) { if ((tmp = refs[i]) && (len = tmp.length)) { if (tmp[len - 1] === id) { tmp.pop(); } else { const index = tmp.indexOf(id); if (0 <= index) { tmp.splice(index, 1); } } } } } else { remove_index(this.map, id); this.depth && remove_index(this.ctx, id); } _skip_deletion || this.reg.delete(id); } if (this.db) { this.commit_task.push({ del: id }); this.commit_auto && autoCommit(this); } if (this.cache) { this.cache.remove(id); } return this; }; /** * When called without passing ID it just will clean up * @param {!Map|KeystoreMap|Array>} map * @param {!number|string=} id * @return {number} */ export function remove_index(map, id) { let count = 0; if (is_array(map)) { for (let x = 0, arr, index, found; x < map.length; x++) { if ((arr = map[x]) && arr.length) { if ("undefined" == typeof id) { return 1; } else { index = arr.indexOf(id); if (0 <= index) { if (1 < arr.length) { arr.splice(index, 1); return 1; } else { delete map[x]; if (count) { return 1; } found = 1; } } else { if (found) { return 1; } count++; } } } } } else for (let item of map.entries()) { const key = item[0], value = item[1], tmp = remove_index(value, id); tmp ? count++ : map.delete(key); } return count; } ================================================ FILE: dist/module/index/search.js ================================================ import { SearchOptions, SearchResults, EnrichedSearchResults, IntermediateSearchResults } from "../type.js"; import { create_object, is_object, sort_by_length_down } from "../common.js"; import Index from "../index.js"; import default_compress from "../compress.js"; import Resolver from "../resolver.js"; import { intersect } from "../intersect.js"; import resolve_default from "../resolve/default.js"; /** * @param {string|SearchOptions} query * @param {number|SearchOptions=} limit * @param {SearchOptions=} options * @return { * SearchResults|EnrichedSearchResults|Resolver | * Promise * } */ Index.prototype.search = function (query, limit, options) { if (!options) { if (!limit && "object" == typeof query) { options = /** @type {!SearchOptions} */query; query = ""; } else if ("object" == typeof limit) { options = /** @type {!SearchOptions} */limit; limit = 0; } } if (options && options.cache) { options.cache = !1; const res = this.searchCache(query, limit, options); options.cache = !0; return res; } /** @type {!Array} */ let result = [], length, context, suggest, offset = 0, resolve, tag, boost, resolution, enrich; if (options) { query = options.query || query; limit = options.limit || limit; offset = options.offset || 0; context = options.context; suggest = options.suggest; resolve = options.resolve; enrich = resolve && options.enrich; boost = options.boost; resolution = options.resolution; tag = this.db && options.tag; } if ("undefined" == typeof resolve) { resolve = this.resolve; } context = this.depth && !1 !== context; /** @type {Array} */ let query_terms = this.encoder.encode(query, !context); length = query_terms.length; limit = /** @type {!number} */limit || (resolve ? 100 : 0); if (1 === length) { return single_term_query.call(this, query_terms[0], "", limit, offset, resolve, enrich, tag); } if (2 === length && context && !suggest) { return single_term_query.call(this, query_terms[1], query_terms[0], limit, offset, resolve, enrich, tag); } let dupes = create_object(), index = 0, keyword; if (context) { keyword = query_terms[0]; index = 1; } if (!resolution && 0 !== resolution) { resolution = keyword ? this.resolution_ctx : this.resolution; } if (this.db) { if (this.db.search) { const result = this.db.search(this, query_terms, limit, offset, suggest, resolve, enrich, tag); if (!1 !== result) return result; } const self = this; return async function () { for (let arr, term; index < length; index++) { term = query_terms[index]; if (term && !dupes[term]) { dupes[term] = 1; arr = await self._get_array(term, keyword, 0, 0, !1, !1); arr = add_result(arr, /** @type {Array} */result, suggest, resolution); if (arr) { result = arr; break; } if (keyword) { if (!suggest || !arr || !result.length) { keyword = term; } } } if (suggest && keyword && index == length - 1) { if (!result.length) { resolution = self.resolution; keyword = ""; index = -1; dupes = create_object(); } } } return return_result(result, resolution, /** @type {!number} */limit, offset, suggest, boost, resolve); }(); } for (let arr, term; index < length; index++) { term = query_terms[index]; if (term && !dupes[term]) { dupes[term] = 1; arr = this._get_array(term, keyword, 0, 0, !1, !1); arr = add_result(arr, /** @type {Array} */result, suggest, resolution); if (arr) { result = arr; break; } if (keyword) { if (!suggest || !arr || !result.length) { keyword = term; } } } if (suggest && keyword && index == length - 1) { if (!result.length) { resolution = this.resolution; keyword = ""; index = -1; dupes = create_object(); } } } return return_result(result, resolution, /** @type {!number} */limit, offset, suggest, boost, resolve); }; /** * @param {!Array} result * @param {number} resolution * @param {number} limit * @param {number=} offset * @param {boolean=} suggest * @param {number=} boost * @param {boolean=} resolve * @return { * SearchResults|EnrichedSearchResults|Resolver | * Promise * } */ function return_result(result, resolution, limit, offset, suggest, boost, resolve) { let length = result.length, final = result; if (1 < length) { final = intersect(result, resolution, limit, offset, suggest, boost, resolve); } else if (1 === length) { return resolve ? resolve_default.call(null, result[0], limit, offset) : new Resolver(result[0], this); } return resolve ? final : new Resolver(final, this); } /** * @param {!string} term * @param {string|null} keyword * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @param {string=} tag * @this {Index} * @return { * SearchResults|EnrichedSearchResults|Resolver | * Promise * } */ function single_term_query(term, keyword, limit, offset, resolve, enrich, tag) { const result = this._get_array(term, keyword, limit, offset, resolve, enrich, tag); if (this.db) { return result.then(function (result) { return resolve ? result || [] : new Resolver(result, this); }); } return result && result.length ? resolve ? resolve_default.call(this, /** @type {SearchResults|EnrichedSearchResults} */result, limit, offset) : new Resolver(result, this) : resolve ? [] : new Resolver([], this); } /** * Returns a 1-dimensional finalized array when the result is done (fast path return), * returns false when suggestions is enabled and no result was found, * or returns nothing when a set was pushed successfully to the results * * @private * @param {IntermediateSearchResults} arr * @param {Array} result * @param {boolean=} suggest * @param {number=} resolution * @return {Array|undefined} */ function add_result(arr, result, suggest, resolution) { let word_arr = []; if (arr && arr.length) { if (arr.length <= resolution) { result.push(arr); return; } for (let x = 0, tmp; x < resolution; x++) { if (tmp = arr[x]) { word_arr[x] = tmp; } } if (word_arr.length) { result.push(word_arr); return; } } if (!suggest) return word_arr; } /** * @param {!string} term * @param {string|null} keyword * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @param {string=} tag * @return { * IntermediateSearchResults|EnrichedSearchResults | * Promise * } */ Index.prototype._get_array = function (term, keyword, limit, offset, resolve, enrich, tag) { let arr, swap; if (keyword) { swap = this.bidirectional && term > keyword; if (swap) { swap = keyword; keyword = term; term = swap; } } if (this.compress) { term = default_compress(term); keyword && (keyword = default_compress(keyword)); } if (this.db) { return this.db.get(term, keyword, limit, offset, resolve, enrich, tag); } if (keyword) { arr = this.ctx.get(keyword); arr = arr && arr.get(term); } else { arr = this.map.get(term); } return arr; }; ================================================ FILE: dist/module/index.js ================================================ /**! * FlexSearch.js * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ import { IndexOptions, ContextOptions, EncoderOptions } from "./type.js"; import Encoder, { fallback_encoder } from "./encoder.js"; import Cache, { searchCache } from "./cache.js"; import Charset from "./charset.js"; import { KeystoreMap, KeystoreSet } from "./keystore.js"; import { is_array, is_string } from "./common.js"; import { exportIndex, importIndex, serialize } from "./serialize.js"; import { remove_index } from "./index/remove.js"; import apply_preset from "./preset.js"; import apply_async from "./async.js"; import tick from "./profiler.js"; import "./index/add.js"; import "./index/search.js"; import "./index/remove.js"; /** * @constructor * @param {IndexOptions|string=} options Options or preset as string * @param {Map|Set|KeystoreSet|KeystoreMap=} _register */ export default function Index(options, _register) { if (!this || this.constructor !== Index) { return new Index(options); } options = /** @type IndexOptions */options ? apply_preset(options) : {}; /** @type {*} */ let tmp = options.context; /** @type ContextOptions */ const context = /** @type ContextOptions */!0 === tmp ? { depth: 1 } : tmp || {}, encoder = is_string(options.encoder) ? Charset[options.encoder] : options.encode || options.encoder || {}; /** @type Encoder */ this.encoder = encoder.encode ? encoder : "object" == typeof encoder ? new Encoder( /** @type {EncoderOptions} */encoder) : { encode: encoder }; this.compress = options.compress || options.compression || !1; this.resolution = options.resolution || 9; this.tokenize = tmp = (tmp = options.tokenize) && "default" !== tmp && "exact" !== tmp && tmp || "strict"; this.depth = "strict" === tmp && context.depth || 0; this.bidirectional = !1 !== context.bidirectional; this.fastupdate = !!options.fastupdate; this.score = options.score || null; tmp = options.keystore || 0; tmp && (this.keystore = tmp); this.map = tmp && !0 ? new KeystoreMap(tmp) : new Map(); this.ctx = tmp && !0 ? new KeystoreMap(tmp) : new Map(); /** @type { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } */ this.reg = _register || (this.fastupdate ? tmp && !0 ? new KeystoreMap(tmp) : new Map() : tmp && !0 ? new KeystoreSet(tmp) : new Set()); this.resolution_ctx = context.resolution || 3; this.rtl = encoder.rtl || options.rtl || !1; this.cache = (tmp = options.cache || null) && new Cache(tmp); this.resolve = !1 !== options.resolve; if (tmp = options.db) { this.db = this.mount(tmp); } this.commit_auto = !1 !== options.commit; this.commit_task = []; this.commit_timer = null; this.priority = options.priority || 4; } Index.prototype.mount = function (db) { if (this.commit_timer) { clearTimeout(this.commit_timer); this.commit_timer = null; } return db.mount(this); }; Index.prototype.commit = function () { if (this.commit_timer) { clearTimeout(this.commit_timer); this.commit_timer = null; } return this.db.commit(this); }; Index.prototype.destroy = function () { if (this.commit_timer) { clearTimeout(this.commit_timer); this.commit_timer = null; } return this.db.destroy(); }; /** * @param {!Index} self */ export function autoCommit(self) { if (!self.commit_timer) { self.commit_timer = setTimeout(function () { self.commit_timer = null; self.db.commit(self); }, 1); } } Index.prototype.clear = function () { this.map.clear(); this.ctx.clear(); this.reg.clear(); this.cache && this.cache.clear(); if (this.db) { this.commit_timer && clearTimeout(this.commit_timer); this.commit_timer = null; this.commit_task = []; return this.db.clear(); } return this; }; /** * @param {!number|string} id * @param {!string} content */ Index.prototype.append = function (id, content) { return this.add(id, content, !0); }; /** * @param {number|string} id * @return {boolean|Promise} */ Index.prototype.contain = function (id) { return this.db ? this.db.has(id) : this.reg.has(id); }; Index.prototype.update = function (id, content) { const self = this, res = this.remove(id); return res && res.then ? res.then(() => self.add(id, content)) : this.add(id, content); }; Index.prototype.cleanup = function () { if (!this.fastupdate) { return this; } remove_index(this.map); this.depth && remove_index(this.ctx); return this; }; Index.prototype.searchCache = searchCache; Index.prototype.export = exportIndex; Index.prototype.import = importIndex; Index.prototype.serialize = serialize; apply_async(Index.prototype); ================================================ FILE: dist/module/intersect.js ================================================ import Resolver from "./resolver.js"; import { create_object, concat, sort_by_length_up, get_max_len } from "./common.js"; import { SearchResults, IntermediateSearchResults } from "./type.js"; /* from -> result[ res[score][id], res[score][id], ] to -> [id] */ /** * @param {!Array} arrays * @param {number} resolution * @param {number} limit * @param {number=} offset * @param {boolean=} suggest * @param {number=} boost * @param {boolean=} resolve * @returns {SearchResults|IntermediateSearchResults} */ export function intersect(arrays, resolution, limit, offset, suggest, boost, resolve) { const length = arrays.length; /** @type {Array} */ let result = [], check, count; check = create_object(); for (let y = 0, ids, id, res_arr, tmp; y < resolution; y++) { for (let x = 0; x < length; x++) { res_arr = arrays[x]; if (y < res_arr.length && (ids = res_arr[y])) { for (let z = 0; z < ids.length; z++) { id = ids[z]; if (count = check[id]) { check[id]++; } else { count = 0; check[id] = 1; } tmp = result[count] || (result[count] = []); if (!resolve) { let score = y + (x || !suggest ? 0 : boost || 0); tmp = tmp[score] || (tmp[score] = []); } tmp.push(id); if (resolve) { if (limit && count === length - 1) { if (tmp.length - offset === limit) { return offset ? tmp.slice(offset) : tmp; } } } } } } } const result_len = result.length; if (result_len) { if (!suggest) { if (result_len < length) { return []; } result = /** @type {SearchResults|IntermediateSearchResults} */result[result_len - 1]; if (limit || offset) { if (resolve) { if (result.length > limit || offset) { result = result.slice(offset, limit + offset); } } else { const final = []; for (let i = 0, arr; i < result.length; i++) { arr = result[i]; if (!arr) continue; if (offset && arr.length > offset) { offset -= arr.length; continue; } if (limit && arr.length > limit || offset) { arr = arr.slice(offset, limit + offset); limit -= arr.length; if (offset) offset -= arr.length; } final.push(arr); if (!limit) { break; } } result = final; } } } else { result = 1 < result.length ? union(result, limit, offset, resolve, boost) : (result = result[0]) && limit && result.length > limit || offset ? result.slice(offset, limit + offset) : result; } } return (/** @type {SearchResults|IntermediateSearchResults} */result ); } /** * @param {Array} arrays * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {number=} boost * @returns {SearchResults|IntermediateSearchResults} */ export function union(arrays, limit, offset, resolve, boost) { /** @type {SearchResults|IntermediateSearchResults} */ const result = [], check = create_object(); let ids, id, arr_len = arrays.length, ids_len; if (!resolve) { for (let i = arr_len - 1, res, count = 0; 0 <= i; i--) { res = arrays[i]; for (let k = 0; k < res.length; k++) { ids = res[k]; ids_len = ids && ids.length; if (ids_len) for (let j = 0; j < ids_len; j++) { id = ids[j]; if (!check[id]) { check[id] = 1; if (offset) { offset--; } else { let score = 0 | (k + (i < arr_len - 1 ? boost || 0 : 0)) / (i + 1), arr = result[score] || (result[score] = []); arr.push(id); if (++count === limit) { return result; } } } } } } } else for (let i = arr_len - 1; 0 <= i; i--) { ids = arrays[i]; ids_len = ids && ids.length; if (ids_len) for (let j = 0; j < ids_len; j++) { id = ids[j]; if (!check[id]) { check[id] = 1; if (offset) { offset--; } else { result.push(id); if (result.length === limit) { return result; } } } } } return result; } /** * @param {SearchResults|IntermediateSearchResults|Resolver} arrays * @param {Array} mandatory * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @returns {SearchResults} */ export function intersect_union(arrays, mandatory, limit, offset, resolve) { const check = create_object(), result = []; /** @type {SearchResults|IntermediateSearchResults} */ for (let x = 0, ids; x < mandatory.length; x++) { ids = mandatory[x]; for (let i = 0; i < ids.length; i++) { check[ids[i]] = 1; } } if (resolve) { for (let i = 0, id; i < arrays.length; i++) { id = arrays[i]; if (check[id]) { if (offset) { offset--; continue; } result.push(id); check[id] = 0; if (limit) { if (0 == --limit) { break; } } } } } else { arrays = arrays.result || arrays; for (let i = 0, ids, id; i < arrays.length; i++) { ids = arrays[i]; for (let j = 0; j < ids.length; j++) { id = ids[j]; if (check[id]) { const arr = result[i] || (result[i] = []); arr.push(id); check[id] = 0; } } } } return result; } /** * Implementation based on Array.includes() provides better performance, * but it needs at least one word in the query which is less frequent. * Also on large indexes it does not scale well performance-wise. * This strategy also lacks of suggestion capabilities (matching & sorting). * * @param arrays * @param limit * @param offset * @param {boolean|Array=} suggest * @returns {Array} */ ================================================ FILE: dist/module/keystore.js ================================================ import { create_object } from "./common.js"; function _slice(self, start, end, splice) { let arr = []; for (let i = 0, index; i < self.index.length; i++) { index = self.index[i]; if (start >= index.length) { start -= index.length; } else { const tmp = index[splice ? "splice" : "slice"](start, end), length = tmp.length; if (length) { arr = arr.length ? arr.concat(tmp) : tmp; end -= length; if (splice) self.length -= length; if (!end) break; } start = 0; } } return arr; } /** * @param arr * @constructor */ export function KeystoreArray(arr) { if (!this || this.constructor !== KeystoreArray) { return new KeystoreArray(arr); } this.index = arr ? [arr] : []; this.length = arr ? arr.length : 0; const self = this; return new Proxy([], { get(target, key) { if ("length" === key) { return self.length; } if ("push" === key) { return function (value) { self.index[self.index.length - 1].push(value); self.length++; }; } if ("pop" === key) { return function () { if (self.length) { self.length--; return self.index[self.index.length - 1].pop(); } }; } if ("indexOf" === key) { return function (key) { let index = 0; for (let i = 0, arr, tmp; i < self.index.length; i++) { arr = self.index[i]; tmp = arr.indexOf(key); if (0 <= tmp) return index + tmp; index += arr.length; } return -1; }; } if ("includes" === key) { return function (key) { for (let i = 0; i < self.index.length; i++) { if (self.index[i].includes(key)) { return !0; } } return !1; }; } if ("slice" === key) { return function (start, end) { return _slice(self, start || 0, end || self.length, !1); }; } if ("splice" === key) { return function (start, end) { return _slice(self, start || 0, end || self.length, !0); }; } if ("constructor" === key) { return Array; } if ("symbol" == typeof key) { return; } const arr = self.index[0 | key / 2147483648]; return arr && arr[key]; }, set(target, key, value) { const index = 0 | key / 2147483648, arr = self.index[index] || (self.index[index] = []); arr[key] = value; self.length++; return !0; } }); } KeystoreArray.prototype.clear = function () { this.index.length = 0; }; KeystoreArray.prototype.push = function () {}; /** * @interface */ function Keystore() { /** @type {Object} */ this.index; /** @type {Array} */ this.refs; /** @type {number} */ this.size; /** @type {function((string|bigint|number)):number} */ this.crc; /** @type {bigint|number} */ this.bit; } /** * @param bitlength * @constructor * @implements {Keystore} */ export function KeystoreMap(bitlength = 8) { if (!this || this.constructor !== KeystoreMap) { return new KeystoreMap(bitlength); } /** @type {Object} */ this.index = create_object(); /** @type {Array} */ this.refs = []; /** @type {number} */ this.size = 0; if (32 < bitlength) { this.crc = lcg64; this.bit = BigInt(bitlength); } else { this.crc = lcg; this.bit = bitlength; } } /** @param {number|string} key */ KeystoreMap.prototype.get = function (key) { const address = this.crc(key), map = this.index[address]; return map && map.get(key); }; /** * @param {number|string} key * @param {*} value */ KeystoreMap.prototype.set = function (key, value) { const address = this.crc(key); let map = this.index[address]; if (map) { let size = map.size; map.set(key, value); size -= map.size; size && this.size++; } else { this.index[address] = map = new Map([[key, value]]); this.refs.push(map); this.size++; } }; /** * @param bitlength * @constructor * @implements Keystore */ export function KeystoreSet(bitlength = 8) { if (!this || this.constructor !== KeystoreSet) { return new KeystoreSet(bitlength); } /** @type {Object} */ this.index = create_object(); /** @type {Array} */ this.refs = []; /** @type {number} */ this.size = 0; if (32 < bitlength) { this.crc = lcg64; this.bit = BigInt(bitlength); } else { this.crc = lcg; this.bit = bitlength; } } /** @param {number|string} key */ KeystoreSet.prototype.add = function (key) { const address = this.crc(key); let set = this.index[address]; if (set) { let size = set.size; set.add(key); size -= set.size; size && this.size++; } else { this.index[address] = set = new Set([key]); this.refs.push(set); this.size++; } }; KeystoreMap.prototype.has = /** @param {number|string} key */ KeystoreSet.prototype.has = function (key) { const address = this.crc(key), map_or_set = this.index[address]; return map_or_set && map_or_set.has(key); }; /* KeystoreMap.prototype.size = KeystoreSet.prototype.size = function(){ let size = 0; const values = Object.values(this.index); for(let i = 0; i < values.length; i++){ size += values[i].size; } return size; }; */ KeystoreMap.prototype.delete = /** @param {number|string} key */ KeystoreSet.prototype.delete = function (key) { const address = this.crc(key), map_or_set = this.index[address]; map_or_set && map_or_set.delete(key) && this.size--; }; KeystoreMap.prototype.clear = KeystoreSet.prototype.clear = function () { this.index = create_object(); this.refs = []; this.size = 0; }; /** * @return Iterable */ KeystoreMap.prototype.values = KeystoreSet.prototype.values = function* () { for (let i = 0; i < this.refs.length; i++) { for (let value of this.refs[i].values()) { yield value; } } }; /** * @return Iterable */ KeystoreMap.prototype.keys = KeystoreSet.prototype.keys = function* () { for (let i = 0; i < this.refs.length; i++) { for (let key of this.refs[i].keys()) { yield key; } } }; /** * @return Iterable */ KeystoreMap.prototype.entries = KeystoreSet.prototype.entries = function* () { for (let i = 0; i < this.refs.length; i++) { for (let entry of this.refs[i].entries()) { yield entry; } } }; /** * Linear Congruential Generator (LCG) * @param {!number|bigint|string} str * @this {KeystoreMap|KeystoreSet} */ function lcg(str) { let range = 2 ** this.bit - 1; if ("number" == typeof str) { return str & range; } let crc = 0, bit = this.bit + 1; for (let i = 0; i < str.length; i++) { crc = (crc * bit ^ str.charCodeAt(i)) & range; } return 32 === this.bit ? crc + 2147483648 : crc; } /** * @param {!number|bigint|string} str * @this {KeystoreMap|KeystoreSet} */ function lcg64(str) { let range = BigInt(2) ** /** @type {!bigint} */this.bit - BigInt(1), type = typeof str; if ("bigint" == type) { return (/** @type {!bigint} */str & range ); } if ("number" == type) { return BigInt(str) & range; } let crc = BigInt(0), bit = /** @type {!bigint} */this.bit + BigInt(1); for (let i = 0; i < str.length; i++) { crc = (crc * bit ^ BigInt(str.charCodeAt(i))) & range; } return crc; } ================================================ FILE: dist/module/lang/de.js ================================================ import { EncoderOptions } from "../type.js"; /** * Filter are also known as "stopwords", they completely filter out words from being indexed. * Source: http://www.ranks.nl/stopwords * Object Definition: Just provide an array of words. * @type {Set} */ export const filter = new Set(["aber", "als", "am", "an", "auch", "auf", "aus", "bei", "bin", "bis", "bist", "da", "dadurch", "daher", "darum", "das", "dass", "dass", "dein", "deine", "dem", "den", "der", "des", "dessen", "deshalb", "die", "dies", "dieser", "dieses", "doch", "dort", "du", "durch", "ein", "eine", "einem", "einen", "einer", "eines", "er", "es", "euer", "eure", "fuer", "hatte", "hatten", "hattest", "hattet", "hier", "hinter", "ich", "ihr", "ihre", "im", "in", "ist", "ja", "jede", "jedem", "jeden", "jeder", "jedes", "jener", "jenes", "jetzt", "ggf", "kann", "kannst", "koennen", "koennt", "machen", "mein", "meine", "mit", "muss", "musst", "musst", "muessen", "muesst", "nach", "nachdem", "nein", "nicht", "noch", "nun", "oder", "seid", "sein", "seine", "sich", "sie", "sind", "soll", "sollen", "sollst", "sollt", "sonst", "soweit", "sowie", "und", "unser", "unsere", "unter", "usw", "uvm", "vom", "von", "vor", "wann", "warum", "was", "weiter", "weitere", "wenn", "wer", "werde", "werden", "werdet", "weshalb", "wie", "wieder", "wieso", "wir", "wird", "wirst", "wo", "woher", "wohin", "zu", "zum", "zur", "ueber"]); /** * Stemmer removes word endings and is a kind of "partial normalization". A word ending just matched when the word length is bigger than the matched partial. * Example: The word "correct" and "correctness" could be the same word, so you can define {"ness": ""} to normalize the ending. * Object Definition: the key represents the word ending, the value contains the replacement (or empty string for removal). * http://snowball.tartarus.org/algorithms/german/stemmer.html * @type {Map} */ export const stemmer = new Map([["niss", ""], ["isch", ""], ["lich", ""], ["heit", ""], ["keit", ""], ["ell", ""], ["bar", ""], ["end", ""], ["ung", ""], ["est", ""], ["ern", ""], ["em", ""], ["er", ""], ["en", ""], ["es", ""], ["st", ""], ["ig", ""], ["ik", ""], ["e", ""], ["s", ""]]); /** * Matcher replaces all occurrences of a given string regardless of its position and is also a kind of "partial normalization". * Object Definition: the key represents the target term, the value contains the search string which should be replaced (could also be an array of multiple terms). * @type {Map} */ const map = new Map([["_", " "], ["ä", "ae"], ["ö", "oe"], ["ü", "ue"], ["ß", "ss"], ["&", " und "], ["€", " EUR "]]), options = { prepare: function (str) { if (/[_äöüß&€]/.test(str)) str = str.replace(/[_äöüß&€]/g, match => map.get(match)); return str.replace(/str\b/g, "strasse").replace(/(?!\b)strasse\b/g, " strasse"); }, filter: filter, stemmer: stemmer }; /** * @type EncoderOptions */ export default options; ================================================ FILE: dist/module/lang/en.js ================================================ import { EncoderOptions } from "../type.js"; /** * http://www.ranks.nl/stopwords * @type {Set} */ export const filter = new Set(["a", "about", "above", "after", "again", "against", "all", "also", "am", "an", "and", "any", "are", "arent", "as", "at", "back", "be", "because", "been", "before", "being", "below", "between", "both", "but", "by", "can", "cannot", "cant", "come", "could", "couldnt", "did", "didnt", "do", "does", "doesnt", "doing", "dont", "down", "during", "each", "even", "few", "for", "from", "further", "get", "go", "good", "had", "hadnt", "has", "hasnt", "have", "havent", "having", "he", "hed", "her", "here", "heres", "hers", "herself", "hes", "him", "himself", "his", "how", "hows", "i", "id", "if", "ill", "im", "in", "into", "is", "isnt", "it", "its", "itself", "ive", "just", "know", "lets", "like", "lot", "make", "made", "me", "more", "most", "mustnt", "my", "myself", "new", "no", "nor", "not", "now", "of", "off", "on", "once", "one", "only", "or", "other", "ought", "our", "ours", "ourselves", "out", "over", "own", "same", "say", "see", "shant", "she", "shed", "shell", "shes", "should", "shouldnt", "so", "some", "such", "take", "than", "that", "thats", "the", "their", "theirs", "them", "themselves", "then", "there", "theres", "these", "they", "theyd", "theyll", "theyre", "theyve", "think", "this", "those", "through", "time", "times", "to", "too", "under", "until", "up", "us", "use", "very", "want", "was", "wasnt", "way", "we", "wed", "well", "were", "werent", "weve", "what", "whats", "when", "whens", "where", "wheres", "which", "while", "who", "whom", "whos", "why", "whys", "will", "with", "wont", "work", "would", "wouldnt", "ya", "you", "youd", "youll", "your", "youre", "yours", "yourself", "yourselves", "youve"]); /** * @type {Map} */ export const stemmer = new Map([["ization", ""], ["biliti", ""], ["icate", ""], ["ative", ""], ["ation", ""], ["iviti", ""], ["ement", ""], ["izer", ""], ["able", ""], ["ible", ""], ["alli", ""], ["ator", ""], ["less", ""], ["logi", ""], ["ical", ""], ["ance", ""], ["ence", ""], ["ness", ""], ["ble", ""], ["ment", ""], ["eli", ""], ["bli", ""], ["ful", ""], ["ant", ""], ["ent", ""], ["ism", ""], ["ate", ""], ["iti", ""], ["ous", ""], ["ive", ""], ["ize", ""], ["ing", ""], ["ion", ""], ["ies", "y"], ["al", ""], ["ou", ""], ["er", ""], ["ed", ""], ["ic", ""], ["ly", ""], ["li", ""], ["s", ""]]); /* he’s (= he is / he has) she’s (= she is / she has) I’ll (= I will) I’ve (= I have) I’d (= I would / I had) don’t (= do not) doesn’t (= does not) didn’t (= did not) isn’t (= is not) hasn’t (= has not) can’t (= cannot) won’t (= will not) */ /** * @type EncoderOptions */ const options = { prepare: function (str) { return str.replace(/´`’ʼ/g, "'").replace(/&/g, " and ").replace(/\$/g, " USD ").replace(/£/g, " GBP ").replace(/\bi'm\b/g, "i am").replace(/\b(can't|cannot)\b/g, "can not").replace(/\bwon't\b/g, "will not").replace(/([a-z])'s\b/g, "$1 is has").replace(/([a-z])n't\b/g, "$1 not").replace(/([a-z])'ll\b/g, "$1 will").replace(/([a-z])'re\b/g, "$1 are").replace(/([a-z])'ve\b/g, "$1 have").replace(/([a-z])'d\b/g, "$1 would had"); }, filter: filter, stemmer: stemmer }; export default options; ================================================ FILE: dist/module/lang/fr.js ================================================ import { EncoderOptions } from "../type.js"; /** * http://www.ranks.nl/stopwords * http://snowball.tartarus.org/algorithms/french/stop.txt * @type {Set} */ export const filter = new Set(["au", "aux", "avec", "ce", "ces", "dans", "de", "des", "du", "elle", "en", "et", "eux", "il", "je", "la", "le", "leur", "lui", "ma", "mais", "me", "meme", "mes", "moi", "mon", "ne", "nos", "notre", "nous", "on", "ou", "par", "pas", "pour", "qu", "que", "qui", "sa", "se", "ses", "son", "sur", "ta", "te", "tes", "toi", "ton", "tu", "un", "une", "vos", "votre", "vous", "c", "d", "j", "l", "m", "n", "s", "t", "a", "y", "ete", "etee", "etees", "etes", "etant", "suis", "es", "est", "sommes", "etes", "sont", "serai", "seras", "sera", "serons", "serez", "seront", "serais", "serait", "serions", "seriez", "seraient", "etais", "etait", "etions", "etiez", "etaient", "fus", "fut", "fumes", "futes", "furent", "sois", "soit", "soyons", "soyez", "soient", "fusse", "fusses", "fut", "fussions", "fussiez", "fussent", "ayant", "eu", "eue", "eues", "eus", "ai", "as", "avons", "avez", "ont", "aurai", "auras", "aura", "aurons", "aurez", "auront", "aurais", "aurait", "aurions", "auriez", "auraient", "avais", "avait", "avions", "aviez", "avaient", "eut", "eumes", "eutes", "eurent", "aie", "aies", "ait", "ayons", "ayez", "aient", "eusse", "eusses", "eut", "eussions", "eussiez", "eussent", "ceci", "cela", "cela", "cet", "cette", "ici", "ils", "les", "leurs", "quel", "quels", "quelle", "quelles", "sans", "soi"]); /** * @type {Map} */ export const stemmer = new Map([["lement", ""], ["ient", ""], ["nera", ""], ["ment", ""], ["ais", ""], ["ait", ""], ["ant", ""], ["ent", ""], ["iez", ""], ["ion", ""], ["nez", ""], ["ai", ""], ["es", ""], ["er", ""], ["ez", ""], ["le", ""], ["na", ""], ["ne", ""], ["a", ""], ["e", ""]]); /** * @type EncoderOptions */ const options = { prepare: function (str) { return str.replace(/´`’ʼ/g, "'").replace(/_+/g, " ").replace(/&/g, " et ").replace(/€/g, " EUR ").replace(/\bl'([^\b])/g, "la le $1").replace(/\bt'([^\b])/g, "ta te $1").replace(/\bc'([^\b])/g, "ca ce $1").replace(/\bd'([^\b])/g, "da de $1").replace(/\bj'([^\b])/g, "ja je $1").replace(/\bn'([^\b])/g, "na ne $1").replace(/\bm'([^\b])/g, "ma me $1").replace(/\bs'([^\b])/g, "sa se $1").replace(/\bau\b/g, "a le").replace(/\baux\b/g, "a les").replace(/\bdu\b/g, "de le").replace(/\bdes\b/g, "de les"); }, filter: filter, stemmer: stemmer }; export default options; ================================================ FILE: dist/module/preset.js ================================================ import { is_string } from "./common.js"; import { IndexOptions } from "./type.js"; /** * @type {Object} * @const */ const presets = { memory: { resolution: 1 }, performance: { resolution: 3, fastupdate: !0, context: { depth: 1, resolution: 1 } }, match: { tokenize: "full" }, score: { resolution: 9, context: { depth: 2, resolution: 3 } } }; /** * * @param {IndexOptions|string} options * @return {IndexOptions} */ export default function apply_preset(options) { const preset = /** @type string */is_string(options) ? options : options.preset; if (preset) { options = /** @type IndexOptions */Object.assign({}, presets[preset], /** @type {Object} */options); } return (/** @type IndexOptions */options ); } ================================================ FILE: dist/module/profiler.js ================================================ import { create_object } from "./common.js"; const data = create_object(); /** * @param {!string} name */ export default function tick() {} ================================================ FILE: dist/module/resolve/and.js ================================================ import Resolver from "../resolver.js"; import { get_max_len } from "../common.js"; import { intersect } from "../intersect.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } * @this {Resolver} */ Resolver.prototype.and = function () { return this.handler("and", return_result, arguments); }; /** * Aggregate the intersection of N raw results * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight) { if (!suggest && !this.result.length) { return resolve ? this.result : this; } let resolved; if (!final.length) { if (!suggest) { this.result = /** @type {SearchResults|IntermediateSearchResults} */final; } } else { this.result.length && final.unshift(this.result); if (2 > final.length) { this.result = final[0]; } else { let resolution = 0; for (let i = 0, res, len; i < final.length; i++) { if ((res = final[i]) && (len = res.length)) { if (resolution < len) { resolution = len; } } else if (!suggest) { resolution = 0; break; } } if (!resolution) { this.result = []; } else { this.result = intersect(final, resolution, limit, offset, suggest, this.boostval, resolve); resolved = !0; } } } if (resolve) { this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight, resolved) : this; } ================================================ FILE: dist/module/resolve/default.js ================================================ import { concat } from "../common.js"; import { IntermediateSearchResults, SearchResults, EnrichedSearchResults } from "../type.js"; import { apply_enrich } from "../document/search.js"; import Document from "../document.js"; import Index from "../index.js"; import WorkerIndex from "../worker.js"; /* from -> res[score][id] to -> [id] */ /** * Aggregate the union of a single raw result * @param {IntermediateSearchResults} result * @param {!number} limit * @param {number=} offset * @param {boolean=} enrich * @return {SearchResults|EnrichedSearchResults} * @this {Document|Index|WorkerIndex} */ export default function (result, limit, offset, enrich) { if (!result.length) { return result; } if (1 === result.length) { let final = result[0]; final = offset || final.length > limit ? final.slice(offset, offset + limit) : final; return enrich ? /** @type {EnrichedSearchResults} */apply_enrich.call(this, final) : final; } let final = []; for (let i = 0, arr, len; i < result.length; i++) { if (!(arr = result[i]) || !(len = arr.length)) continue; if (offset) { if (offset >= len) { offset -= len; continue; } arr = arr.slice(offset, offset + limit); len = arr.length; offset = 0; } if (len > limit) { arr = arr.slice(0, limit); len = limit; } if (!final.length) { if (len >= limit) { return enrich ? /** @type {EnrichedSearchResults} */apply_enrich.call(this, arr) : arr; } } final.push(arr); limit -= len; if (!limit) { break; } } final = 1 < final.length ? concat(final) : final[0]; return enrich ? apply_enrich.call(this, final) : final; } ================================================ FILE: dist/module/resolve/handler.js ================================================ import Resolver from "../resolver.js"; import { ResolverOptions, SearchResults, EnrichedSearchResults, IntermediateSearchResults } from "../type.js"; /** * @param {string} method * @param {Function} fn * @param {Array|Arguments} args * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ Resolver.prototype.handler = function (method, fn, args) { /** @type {ResolverOptions} */ let arg = args[0]; if (arg[0] && arg[0].query) { return this[method].apply(this, arg); } if ("and" === method || "not" === method) { let execute = this.result.length || this.await, resolve; if (!execute) { if (!arg.suggest) { if (1 < args.length) { arg = args[args.length - 1]; } resolve = arg.resolve; return resolve ? this.await || this.result : this; } } } const self = this; /** @type {!Array>} */ let final = [], limit = 0, offset = 0, enrich, resolve, suggest, highlight, async; for (let i = 0, query; i < args.length; i++) { /** @type {ResolverOptions} */ query = args[i]; if (query) { let result; if (query.constructor === Resolver) { result = query.await || query.result; } else if (query.then || query.constructor === Array) { result = query; } else { limit = query.limit || 0; offset = query.offset || 0; suggest = query.suggest; resolve = query.resolve; highlight = query.highlight || this.highlight; enrich = (highlight || query.enrich) && resolve; let opt_queue = query.queue, opt_async = query.async || opt_queue, index = query.index, query_value = query.query; if (index) { this.index || (this.index = index); } else { index = this.index; } if (query_value || query.tag) { { const field = query.field || query.pluck; if (field) { if (query_value && (!this.query || highlight)) { this.query = query_value; this.field = field; this.highlight = highlight; } index = index.index.get(field); } } if (opt_queue && (async || this.await)) { async = 1; let resolve; const idx = this.promises.length, promise = new Promise(function (_resolve) { resolve = _resolve; }); (function (index, query) { promise._fn = function () { query.index = null; query.resolve = !1; query.enrich = !1; let result = opt_async ? index.searchAsync(query) : index.search(query); if (result.then) { return result.then(function (result) { self.promises[idx] = result = result.result || result; resolve(result); return result; }); } result = result.result || result; resolve(result); return result; }; })(index, Object.assign({}, /** @type Object */query)); this.promises.push(promise); final[i] = promise; continue; } else { query.resolve = !1; query.enrich = !1; query.index = null; result = opt_async ? index.searchAsync(query) : index.search(query); query.resolve = resolve; query.enrich = enrich; query.index = index; } } else if (query.and) { result = inner_call(query, "and", index); } else if (query.or) { result = inner_call(query, "or", index); } else if (query.not) { result = inner_call(query, "not", index); } else if (query.xor) { result = inner_call(query, "xor", index); } else { continue; } } if (result.await) { async = 1; result = result.await; } else if (result.then) { async = 1; result = result.then(function (result) { return result.result || result; }); } else { result = result.result || result; } final[i] = result; } } if (async && !this.await) { this.await = new Promise(function (resolve) { self.return = resolve; }); } if (async) { const promises = Promise.all(final).then(function (final) { for (let i = 0; i < self.promises.length; i++) { if (self.promises[i] === promises) { self.promises[i] = function () { return fn.call(self, final, limit, offset, enrich, resolve, suggest, highlight); }; break; } } self.execute(); }); this.promises.push(promises); } else if (this.await) { this.promises.push(function () { return fn.call(self, final, limit, offset, enrich, resolve, suggest, highlight); }); } else { return fn.call(this, final, limit, offset, enrich, resolve, suggest, highlight); } return resolve ? this.await || this.result : this; }; function inner_call(query, method, index) { const args = query[method], arg = args[0] || args; arg.index || (arg.index = index); let resolver = new Resolver(arg); if (1 < args.length) { resolver = resolver[method].apply(resolver, args.slice(1)); } return resolver; } ================================================ FILE: dist/module/resolve/not.js ================================================ import Resolver from "../resolver.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** @this {Resolver} */ Resolver.prototype.not = function () { return this.handler("not", return_result, arguments); }; /** * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight) { if (!suggest && !this.result.length) { return resolve ? this.result : this; } let resolved; if (final.length && this.result.length) { this.result = exclusion.call(this, final, limit, offset, resolve); resolved = !0; } if (resolve) { this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight, resolved) : this; } /** * @param {!Array} result * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @this {Resolver} * @return {SearchResults|IntermediateSearchResults} */ function exclusion(result, limit, offset, resolve) { /** @type {SearchResults|IntermediateSearchResults} */ const final = [], exclude = new Set(result.flat().flat()); for (let j = 0, ids, count = 0; j < this.result.length; j++) { ids = this.result[j]; if (!ids) continue; for (let k = 0, id; k < ids.length; k++) { id = ids[k]; if (!exclude.has(id)) { if (offset) { offset--; continue; } if (resolve) { final.push(id); if (final.length === limit) { return final; } } else { final[j] || (final[j] = []); final[j].push(id); if (++count === limit) { return final; } } } } } return final; } ================================================ FILE: dist/module/resolve/or.js ================================================ import Resolver from "../resolver.js"; import { union } from "../intersect.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** @this {Resolver} */ Resolver.prototype.or = function () { return this.handler("or", return_result, arguments); }; /** * Aggregate the intersection of N raw results * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight) { if (final.length) { this.result.length && final.push(this.result); if (2 > final.length) { this.result = final[0]; } else { this.result = union(final, limit, offset, !1, this.boostval); offset = 0; } } if (resolve) { this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight) : this; } ================================================ FILE: dist/module/resolve/xor.js ================================================ import Resolver from "../resolver.js"; import { create_object } from "../common.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** @this {Resolver} */ Resolver.prototype.xor = function () { return this.handler("xor", return_result, arguments); }; /** * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight) { let resolved; if (!final.length) { if (!suggest) this.result = /** @type {SearchResults|IntermediateSearchResults} */final; } else { this.result.length && final.unshift(this.result); if (2 > final.length) { this.result = final[0]; } else { this.result = exclusive.call(this, final, limit, offset, resolve, this.boostval); resolved = !0; } } if (resolve) { this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight, resolved) : this; } /** * Aggregate the intersection of N raw results * @param {!Array} result * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {number=} boost * @this {Resolver} * @return {SearchResults|IntermediateSearchResults} */ function exclusive(result, limit, offset, resolve, boost) { /** @type {SearchResults|IntermediateSearchResults} */ const final = [], check = create_object(); let maxres = 0; for (let i = 0, res; i < result.length; i++) { res = result[i]; if (!res) continue; if (maxres < res.length) maxres = res.length; for (let j = 0, ids; j < res.length; j++) { ids = res[j]; if (!ids) continue; for (let k = 0, id; k < ids.length; k++) { id = ids[k]; check[id] = check[id] ? 2 : 1; } } } for (let j = 0, ids, count = 0; j < maxres; j++) { for (let i = 0, res; i < result.length; i++) { res = result[i]; if (!res) continue; ids = res[j]; if (!ids) continue; for (let k = 0, id; k < ids.length; k++) { id = ids[k]; if (1 === check[id]) { if (offset) { offset--; continue; } if (resolve) { final.push(id); if (final.length === limit) { return final; } } else { const index = j + (i ? boost : 0); final[index] || (final[index] = []); final[index].push(id); if (++count === limit) { return final; } } } } } } return final; } ================================================ FILE: dist/module/resolver.js ================================================ import { ResolverOptions, IntermediateSearchResults, SearchResults, EnrichedSearchResults, HighlightOptions } from "./type.js"; import Index from "./index.js"; import Document from "./document.js"; import WorkerIndex from "./worker.js"; import default_resolver from "./resolve/default.js"; import "./resolve/handler.js"; import "./resolve/or.js"; import "./resolve/and.js"; import "./resolve/xor.js"; import "./resolve/not.js"; import { apply_enrich } from "./document/search.js"; import { highlight_fields } from "./document/highlight.js"; /** * @param {IntermediateSearchResults|ResolverOptions=} result * @param {Index|Document|WorkerIndex=} index * @return {Resolver} * @constructor */ export default function Resolver(result, index) { if (!this || this.constructor !== Resolver) { return new Resolver(result, index); } let boost = 0, promises, query, field, highlight, _await, _return; if (result && result.index) { const options = /** @type {ResolverOptions} */result; index = options.index; boost = options.boost || 0; if (query = options.query) { field = options.field || options.pluck; highlight = options.highlight; const resolve = options.resolve, async = options.async || options.queue; options.resolve = !1; options.highlight = ""; options.index = null; result = async ? index.searchAsync(options) : index.search(options); options.resolve = resolve; options.highlight = highlight; options.index = index; result = result.result || result; } else { result = []; } } if (result && result.then) { const self = this; result = result.then(function (result) { self.promises[0] = self.result = result.result || result; self.execute(); }); promises = [result]; result = []; _await = new Promise(function (resolve) { _return = resolve; }); } /** @type {Index|Document|WorkerIndex|null} */ this.index = index || null; /** @type {IntermediateSearchResults} */ this.result = /** @type {IntermediateSearchResults} */result || []; /** @type {number} */ this.boostval = boost; /** @type {Array|IntermediateSearchResults|Function>} */ this.promises = promises || []; /** @type {Promise} */ this.await = _await || null; /** @type {Function} */ this.return = _return || null; /** @type {HighlightOptions|null} */ this.highlight = /** @type {HighlightOptions|null} */highlight || null; /** @type {string} */ this.query = query || ""; /** @type {string} */ this.field = field || ""; } /** * @param {number} limit */ Resolver.prototype.limit = function (limit) { if (this.await) { const self = this; this.promises.push(function () { return self.limit(limit).result; }); } else { if (this.result.length) { /** @type {IntermediateSearchResults} */ const final = []; for (let j = 0, ids; j < this.result.length; j++) { if (ids = this.result[j]) { if (ids.length <= limit) { final[j] = ids; limit -= ids.length; if (!limit) break; } else { final[j] = ids.slice(0, limit); break; } } } this.result = final; } } return this; }; /** * @param {number} offset */ Resolver.prototype.offset = function (offset) { if (this.await) { const self = this; this.promises.push(function () { return self.offset(offset).result; }); } else { if (this.result.length) { /** @type {IntermediateSearchResults} */ const final = []; for (let j = 0, ids; j < this.result.length; j++) { if (ids = this.result[j]) { if (ids.length <= offset) { offset -= ids.length; } else { final[j] = ids.slice(offset); offset = 0; } } } this.result = final; } } return this; }; /** * @param {number} boost */ Resolver.prototype.boost = function (boost) { if (this.await) { const self = this; this.promises.push(function () { return self.boost(boost).result; }); } else { this.boostval += boost; } return this; }; /** * @param {boolean=} _skip_callback * @this {Resolver} */ Resolver.prototype.execute = function (_skip_callback) { let result = this.result, execute = this.await; this.await = null; for (let i = 0, promise; i < this.promises.length; i++) { if (promise = this.promises[i]) { if ("function" == typeof promise) { result = promise(); this.promises[i] = result = result.result || result; i--; } else if (promise._fn) { result = promise._fn(); this.promises[i] = result = result.result || result; i--; } else if (promise.then) { return this.await = execute; } } } const fn = this.return; this.promises = []; this.return = null; _skip_callback || fn(result); return result; }; /** * @param {number|ResolverOptions=} limit * @param {number=} offset * @param {boolean=} enrich * @param {string|HighlightOptions|boolean=} highlight * @param {boolean=} _resolved */ Resolver.prototype.resolve = function (limit, offset, enrich, highlight, _resolved) { let result = this.await ? this.execute(!0) : this.result; if (result.then) { const self = this; return result.then(function () { return self.resolve(limit, offset, enrich, highlight, _resolved); }); } if (result.length) { if ("object" == typeof limit) { highlight = limit.highlight || this.highlight; enrich = !!highlight || limit.enrich; offset = limit.offset; limit = limit.limit; } else { highlight = highlight || this.highlight; enrich = !!highlight || enrich; } result = _resolved ? enrich ? apply_enrich.call( /** @type {Document} */this.index, /** @type {SearchResults} */result) : result : default_resolver.call(this.index, result, limit || 100, offset, enrich); } return this.finalize(result, highlight); }; Resolver.prototype.finalize = function (result, highlight) { if (result.then) { const self = this; return result.then(function (result) { return self.finalize(result, highlight); }); } if (highlight && result.length && this.query) { result = highlight_fields(this.query, result, this.index.index, this.field, highlight); } const fn = this.return; this.index = this.result = this.promises = this.await = this.return = null; this.highlight = null; this.query = this.field = ""; fn && fn(result); return result; }; ================================================ FILE: dist/module/serialize.js ================================================ import Index from "./index.js"; import Document from "./document.js"; import { KeystoreMap, KeystoreSet } from "./keystore.js"; import { is_string } from "./common.js"; const chunk_size_reg = 250000, chunk_size_map = 5000, chunk_size_ctx = 1000; /** * @param {Map|KeystoreMap} map * @param {number=} size * @return {Array} */ function map_to_json(map, size = 0) { let chunk = [], json = []; if (size) { size = 0 | chunk_size_map * (chunk_size_reg / size); } for (const item of map.entries()) { json.push(item); if (json.length === size) { chunk.push(json); json = []; } } json.length && chunk.push(json); return chunk; } /** * @param {Array} json * @param {Map|KeystoreMap} map * @return {Map|KeystoreMap} */ function json_to_map(json, map) { map || (map = new Map()); for (let i = 0, entry; i < json.length; i++) { entry = json[i]; map.set(entry[0], entry[1]); } return (/** @type {Map} */map ); } /** * @param {Map>|KeystoreMap>} ctx * @param {number=} size * @return {Array} */ function ctx_to_json(ctx, size = 0) { let chunk = [], json = []; if (size) { size = 0 | chunk_size_ctx * (chunk_size_reg / size); } for (const item of ctx.entries()) { const key = item[0], value = item[1]; json.push([key, map_to_json(value)[0] || []]); if (json.length === size) { chunk.push(json); json = []; } } json.length && chunk.push(json); return chunk; } /** * @param {Array} json * @param {Map>|KeystoreMap>} ctx * @return {Map>|KeystoreMap>} */ function json_to_ctx(json, ctx) { ctx || (ctx = new Map()); for (let i = 0, entry, map; i < json.length; i++) { entry = json[i]; map = ctx.get(entry[0]); ctx.set(entry[0], json_to_map(entry[1], map)); } return ctx; } /** * @param { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } reg * @return {Array>} */ function reg_to_json(reg) { let chunk = [], json = []; for (const key of reg.keys()) { json.push(key); if (json.length === chunk_size_reg) { chunk.push(json); json = []; } } json.length && chunk.push(json); return chunk; } /** * @param {Array} json * @param { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } reg * @return { * Set| * KeystoreSet * } */ function json_to_reg(json, reg) { reg || (reg = new Set()); for (let i = 0; i < json.length; i++) { reg.add(json[i]); } return (/** @type {Set} */reg ); } /** * * @param {function(string, string):Promise|void} callback * @param {string|null|void} field * @param {string} key * @param {Array|null} chunk * @param {number} index_doc * @param {number} index_obj * @param {number=} index_prt * @this {Index|Document} * @return {Promise} */ function save(callback, field, key, chunk, index_doc, index_obj, index_prt = 0) { const is_arr = chunk && chunk.constructor === Array, data = is_arr ? chunk.shift() : chunk; if (!data) { return this.export(callback, field, index_doc, index_obj + 1); } const res = callback((field ? field + "." : "") + (index_prt + 1) + "." + key, JSON.stringify(data)); if (res && res.then) { const self = this; return res.then(function () { return save.call(self, callback, field, key, is_arr ? chunk : null, index_doc, index_obj, index_prt + 1); }); } return save.call(this, callback, field, key, is_arr ? chunk : null, index_doc, index_obj, index_prt + 1); } /** * @param {function(string,string):Promise|void} callback * @param {!string|null=} _field * @param {number=} _index_doc * @param {number=} _index_obj * @this {Index} */ export function exportIndex(callback, _field, _index_doc = 0, _index_obj = 0) { let key, chunk; switch (_index_obj) { case 0: key = "reg"; chunk = reg_to_json(this.reg); break; case 1: key = "cfg"; chunk = null; break; case 2: key = "map"; chunk = map_to_json(this.map, this.reg.size); break; case 3: key = "ctx"; chunk = ctx_to_json(this.ctx, this.reg.size); break; default: return; } return save.call(this, callback, _field, key, chunk, _index_doc, _index_obj); } /** * @param {string} key * @param {string|Array=} data * @this Index */ export function importIndex(key, data) { if (!data) { return; } if ("string" == typeof data) { data = /** @type {Array} */JSON.parse( /** @type {string} */data); } const split = key.split("."); if ("json" === split[split.length - 1]) { split.pop(); } if (3 === split.length) { split.shift(); } key = 1 < split.length ? split[1] : split[0]; switch (key) { case "cfg": break; case "reg": this.fastupdate = !1; this.reg = json_to_reg( /** @type {Array} */data, this.reg); break; case "map": this.map = json_to_map(data, this.map); break; case "ctx": this.ctx = json_to_ctx(data, this.ctx); break; } } /** * @param {function(string,string):Promise|void} callback * @param {string|null=} _field * @param {number=} _index_doc * @param {number=} _index_obj * @this {Document} */ export function exportDocument(callback, _field, _index_doc = 0, _index_obj = 0) { if (_index_doc < this.field.length) { const field = this.field[_index_doc], idx = this.index.get(field), res = idx.export(callback, field, _index_doc, _index_obj = 1); if (res && res.then) { const self = this; return res.then(function () { return self.export(callback, field, _index_doc + 1); }); } return this.export(callback, field, _index_doc + 1); } else { let key, chunk; switch (_index_obj) { case 0: key = "reg"; chunk = reg_to_json(this.reg); _field = null; break; case 1: key = "tag"; chunk = this.tag && ctx_to_json(this.tag, this.reg.size); _field = null; break; case 2: key = "doc"; chunk = this.store && map_to_json( /** @type {Map} */this.store); _field = null; break; default: return; } return save.call(this, callback, _field, key, /** @type {Array|null} */chunk || null, _index_doc, _index_obj); } } /** * @param {!string} key * @param {string|Array} data * @this {Document} */ export function importDocument(key, data) { const split = key.split("."); if ("json" === split[split.length - 1]) { split.pop(); } const field = 2 < split.length ? split[0] : "", ref = 2 < split.length ? split[2] : split[1]; if (this.worker && field) { return this.index.get(field).import(key); } if (!data) { return; } if ("string" == typeof data) { data = /** @type {Array} */JSON.parse( /** @type {string} */data); } if (!field) { switch (ref) { case "reg": this.fastupdate = !1; this.reg = json_to_reg( /** @type {Array} */data, this.reg); for (let i = 0, idx; i < this.field.length; i++) { idx = this.index.get(this.field[i]); idx.fastupdate = !1; idx.reg = this.reg; } if (this.worker) { const promises = [], self = this; for (const index of this.index.values()) { promises.push(index.import(key)); } return Promise.all(promises); } break; case "tag": this.tag = json_to_ctx(data, this.tag); break; case "doc": this.store = json_to_map(data, this.store); break; case "cfg": break; } } else { return this.index.get(field).import(ref, data); } } /* reg: "1,2,3,4,5,6,7,8,9" map: "gulliver:1,2,3|4,5,6|7,8,9;" ctx: "gulliver+travel:1,2,3|4,5,6|7,8,9;" */ /** * @this {Index} * @param {boolean} withFunctionWrapper * @return {string} */ export function serialize(withFunctionWrapper = !0) { let reg = '', map = '', ctx = ''; if (this.reg.size) { let type; for (const key of this.reg.keys()) { type || (type = typeof key); reg += (reg ? ',' : '') + ("string" === type ? '"' + key + '"' : key); } reg = 'index.reg=new Set([' + reg + ']);'; map = parse_map(this.map, type); map = "index.map=new Map([" + map + "]);"; for (const context of this.ctx.entries()) { const key_ctx = context[0], value_ctx = context[1]; let ctx_map = parse_map(value_ctx, type); ctx_map = "new Map([" + ctx_map + "])"; ctx_map = '["' + key_ctx + '",' + ctx_map + ']'; ctx += (ctx ? ',' : '') + ctx_map; } ctx = "index.ctx=new Map([" + ctx + "]);"; } return withFunctionWrapper ? "function inject(index){" + reg + map + ctx + "}" : reg + map + ctx; } function parse_map(map, type) { let result = ''; for (const item of map.entries()) { const key = item[0], value = item[1]; let res = ''; for (let i = 0, ids; i < value.length; i++) { ids = value[i] || ['']; let str = ''; for (let j = 0; j < ids.length; j++) { str += (str ? ',' : '') + ("string" === type ? '"' + ids[j] + '"' : ids[j]); } str = '[' + str + ']'; res += (res ? ',' : '') + str; } res = '["' + key + '",[' + res + ']]'; result += (result ? ',' : '') + res; } return result; } ================================================ FILE: dist/module/type.js ================================================ import Index from "./index.js"; import Document from "./document.js"; import WorkerIndex from "./worker.js"; import Encoder from "./encoder.js"; import StorageInterface from "./db/interface.js"; /** * @typedef {{ * preset: (string|undefined), * context: (IndexOptions|undefined), * encoder: (Encoder|Function|Object|undefined), * encode: (function(string):Array|undefined), * resolution: (number|undefined), * tokenize: (string|undefined), * fastupdate: (boolean|undefined), * score: (function():number|undefined), * keystore: (number|undefined), * rtl: (boolean|undefined), * cache: (number|boolean|undefined), * db: (StorageInterface|undefined), * commit: (boolean|undefined), * worker: (string|undefined), * config: (string|undefined), * priority: (number|undefined), * export: (Function|undefined), * import: (Function|undefined) * }} */ export let IndexOptions = {}; /** * @typedef {{ * preset: (string|undefined), * context: (IndexOptions|undefined), * encoder: (Encoder|Function|Object|undefined), * encode: (Function|undefined), * resolution: (number|undefined), * tokenize: (string|undefined), * fastupdate: (boolean|undefined), * score: (Function|undefined), * keystore: (number|undefined), * rtl: (boolean|undefined), * cache: (number|boolean|undefined), * db: (StorageInterface|undefined), * commit: (boolean|undefined), * config: (string|undefined), * priority: (number|undefined), * field: (string|undefined), * filter: (Function|undefined), * custom: (Function|undefined) * }} */ export let FieldOptions = {}; /** * @typedef {{ * context: (IndexOptions|undefined), * encoder: (Encoder|Function|Object|undefined), * encode: (Function|undefined), * resolution: (number|undefined), * tokenize: (string|undefined), * fastupdate: (boolean|undefined), * score: (Function|undefined), * keystore: (number|undefined), * rtl: (boolean|undefined), * cache: (number|boolean|undefined), * db: (StorageInterface|undefined), * doc: (DocumentDescriptor|Array|undefined), * document: (DocumentDescriptor|Array|undefined), * worker: (boolean|string|undefined), * priority: (number|undefined), * export: (Function|undefined), * import: (Function|undefined) * }} */ export let DocumentOptions = {}; /** * @typedef {{ * depth: (number|undefined), * bidirectional: (boolean|undefined), * resolution: (number|undefined), * }} */ export let ContextOptions = {}; /** * @typedef {{ * id: (string|undefined), * field: (string|Array|FieldOptions|Array|undefined), * index: (string|Array|FieldOptions|Array|undefined), * tag: (string|Array|TagOptions|Array|undefined), * store: (string|Array|StoreOptions|Array|boolean|undefined), * }} */ export let DocumentDescriptor = {}; /** * @typedef {{ * field: string, * filter: ((function(string):boolean)|undefined), * custom: ((function(string):string)|undefined), * db: (StorageInterface|undefined), * }} */ export let TagOptions = {}; /** * @typedef {{ * field: string, * filter: ((function(string):boolean)|undefined), * custom: ((function(string):string)|undefined) * }} */ export let StoreOptions = {}; /** * @typedef {{ * query: (string|undefined), * limit: (number|undefined), * offset: (number|undefined), * resolution: (number|undefined), * context: (boolean|undefined), * suggest: (boolean|undefined), * resolve: (boolean|undefined), * enrich: (boolean|undefined), * cache: (boolean|undefined) * }} */ export let SearchOptions = {}; /** * @typedef {{ * query: (string|undefined), * limit: (number|undefined), * offset: (number|undefined), * resolution: (number|undefined), * context: (boolean|undefined), * suggest: (boolean|undefined), * resolve: (boolean|undefined), * enrich: (boolean|undefined), * cache: (boolean|undefined), * tag: (Object|Array>|undefined), * field: (Array|Array|DocumentSearchOptions|string|undefined), * index: (Array|Array|DocumentSearchOptions|string|undefined), * pluck: (string|DocumentSearchOptions|undefined), * merge: (boolean|undefined), * highlight: (HighlightOptions|string|undefined) * }} */ export let DocumentSearchOptions = {}; /** * @typedef Array * @global */ export let SearchResults = []; /** * @typedef Array * @global */ export let IntermediateSearchResults = []; /** * @typedef Array<{ * id: (number|string), * doc: (Object|null), * highlight: (string|undefined) * }> */ export let EnrichedSearchResults = []; /** * @typedef Array<{ * field: (string|undefined), * tag: (string|undefined), * result: SearchResults * }> */ export let DocumentSearchResults = []; /** * @typedef Array<{ * field: (string|undefined), * tag: (string|undefined), * result: EnrichedSearchResults * }> */ export let EnrichedDocumentSearchResults = []; /** * @typedef {{ * id: (number|string), * doc: (Object|null|undefined), * field: (Array|undefined), * tag: (Array|undefined), * highlight: (Object|undefined) * }} */ export let MergedDocumentSearchEntry = {}; /** * @typedef Array */ export let MergedDocumentSearchResults = []; /** * @typedef {{ * letter: (boolean|undefined), * number: (boolean|undefined), * symbol: (boolean|undefined), * punctuation: (boolean|undefined), * control: (boolean|undefined), * char: (string|Array|undefined) * }} */ export let EncoderSplitOptions = {}; /** * @typedef {{ * rtl: (boolean|undefined), * dedupe: (boolean|undefined), * include: (EncoderSplitOptions|undefined), * exclude: (EncoderSplitOptions|undefined), * split: (string|boolean|RegExp|undefined), * numeric: (boolean|undefined), * normalize: (boolean|(function(string):string)|undefined), * prepare: ((function(string):string)|undefined), * finalize: ((function(Array):(Array|void))|undefined), * filter: (Set|function(string):boolean|undefined), * matcher: (Map|undefined), * mapper: (Map|undefined), * stemmer: (Map|undefined), * replacer: (Array|undefined), * minlength: (number|undefined), * maxlength: (number|undefined), * cache: (boolean|undefined) * }} */ export let EncoderOptions = {}; /** * @typedef {{ * name: (string|undefined), * field: (string|undefined), * type: (string|undefined), * db: (StorageInterface|undefined) * }} */ export let PersistentOptions = {}; /** * @typedef {{ * index: (Index|Document|WorkerIndex|undefined), * query: (string|undefined), * pluck: (string|undefined), * field: (string|undefined), * limit: (number|undefined), * offset: (number|undefined), * boost: (number|undefined), * enrich: (boolean|undefined), * highlight: (HighlightOptions|string|undefined), * resolve: (boolean|undefined), * suggest: (boolean|undefined), * cache: (boolean|undefined), * async: (boolean|undefined), * queue: (boolean|undefined), * and: (ResolverOptions|Array|undefined), * or: (ResolverOptions|Array|undefined), * xor: (ResolverOptions|Array|undefined), * not: (ResolverOptions|Array|undefined) * }} */ export let ResolverOptions = {}; /** * @typedef {{ * before: (number|undefined), * after: (number|undefined), * total: (number|undefined) * }} */ export let HighlightBoundaryOptions = {}; /** * @typedef {{ * template: string, * pattern: (string|boolean|undefined) * }} */ export let HighlightEllipsisOptions = {}; /** * @typedef {{ * template: string, * boundary: (HighlightBoundaryOptions|number|undefined), * clip: (boolean|undefined), * merge: (boolean|undefined), * ellipsis: (HighlightEllipsisOptions|string|boolean|undefined) * }} */ export let HighlightOptions = {}; ================================================ FILE: dist/module/worker/handler.js ================================================ import Index from "../index.js"; import { IndexOptions } from "../type.js"; /** @type Index */ let index, options; /** @type {IndexOptions} */ export default (async function (data) { data = data.data; const task = data.task, id = data.id; let args = data.args; switch (task) { case "init": options = /** @type {IndexOptions} */data.options || {}; let filepath = options.config; if (filepath) { options = options; } const factory = data.factory; if (factory) { Function("return " + factory)()(self); index = new self.FlexSearch.Index(options); delete self.FlexSearch; } else { index = new Index(options); } postMessage({ id: id }); break; default: let message; if ("export" === task) { if (!args[1]) args = null;else { args[0] = options.export; args[2] = 0; args[3] = 1; } } if ("import" === task) { if (args[0]) { const data = await options.import.call(index, args[0]); index.import(args[0], data); } } else { message = args && index[task].apply(index, args); if (message && message.then) { message = await message; } if (message && message.await) { message = await message.await; } if ("search" === task && message.result) { message = message.result; } } postMessage("search" === task ? { id: id, msg: message } : { id: id }); } }); ================================================ FILE: dist/module/worker/node.js ================================================ /* * Node.js Worker (CommonJS) * This file is a standalone file and isn't being a part of the build/bundle */ const { parentPort } = require("worker_threads"), { Index } = require("flexsearch"); /** @type Index */ let index, options; /** @type {IndexOptions} */ parentPort.on("message", async function (data) { const task = data.task, id = data.id; let args = data.args; switch (task) { case "init": options = data.options || {}; let filepath = options.config; if (filepath) { options = Object.assign({}, options, require(filepath)); delete options.worker; } index = new Index(options); parentPort.postMessage({ id: id }); break; default: let message; if ("export" === task) { if (!options.export || "function" != typeof options.export) { throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"export\"."); } if (!args[1]) args = null;else { args[0] = options.export; args[2] = 0; args[3] = 1; } } if ("import" === task) { if (!options.import || "function" != typeof options.import) { throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"import\"."); } if (args[0]) { const data = await options.import.call(index, args[0]); index.import(args[0], data); } } else { message = args && index[task].apply(index, args); if (message && message.then) { message = await message; } if (message && message.await) { message = await message.await; } if ("search" === task && message.result) { message = message.result; } } parentPort.postMessage("search" === task ? { id: id, msg: message } : { id: id }); } }); ================================================ FILE: dist/module/worker/worker.js ================================================ import handler from "./handler.js"; onmessage = handler; ================================================ FILE: dist/module/worker.js ================================================ import { IndexOptions } from "./type.js"; import { create_object } from "./common.js"; import { searchCache } from "./cache.js"; import handler from "./worker/handler.js"; import apply_async from "./async.js"; import Encoder from "./encoder.js"; let pid = 0; /** * @param {IndexOptions=} options * @param {Encoder=} encoder * @constructor */ export default function WorkerIndex(options = /** @type IndexOptions */{}, encoder) { if (!this || this.constructor !== WorkerIndex) { return new WorkerIndex(options); } let factory = "undefined" != typeof self ? self._factory : "undefined" != typeof window ? window._factory : null; if (factory) { factory = factory.toString(); } const is_node_js = "undefined" == typeof window, _self = this; /** * @this {WorkerIndex} */ function init(worker) { this.worker = worker; this.resolver = create_object(); if (!this.worker) { return; } function onmessage(msg) { msg = msg.data || msg; const id = msg.id, res = id && _self.resolver[id]; if (res) { res(msg.msg); delete _self.resolver[id]; } } is_node_js ? this.worker.on("message", onmessage) : this.worker.onmessage = onmessage; if (options.config) { return new Promise(function (resolve) { if (1e9 < pid) pid = 0; _self.resolver[++pid] = function () { resolve(_self); }; _self.worker.postMessage({ id: pid, task: "init", factory: factory, options: options }); }); } this.priority = options.priority || 4; this.encoder = encoder || null; this.worker.postMessage({ task: "init", factory: factory, options: options }); return this; } const worker = create(factory, is_node_js, options.worker); return worker.then ? worker.then(function (worker) { return init.call(_self, worker); }) : init.call(this, worker); } register("add"); register("append"); register("search"); register("update"); register("remove"); register("clear"); register("export"); register("import"); WorkerIndex.prototype.searchCache = searchCache; apply_async(WorkerIndex.prototype); function register(key) { WorkerIndex.prototype[key] = function () { const self = this, args = [].slice.call(arguments), arg = args[args.length - 1]; let callback; if ("function" == typeof arg) { callback = arg; args.pop(); } const promise = new Promise(function (resolve) { if ("export" === key && "function" == typeof args[0]) { args[0] = null; } if (1e9 < pid) pid = 0; self.resolver[++pid] = resolve; self.worker.postMessage({ task: key, id: pid, args: args }); }); if (callback) { promise.then(callback); return this; } else { return promise; } }; } function create(factory, is_node_js, worker_path) { return is_node_js ? "undefined" != typeof module ? (0,eval)('new(require("worker_threads")["Worker"])(__dirname+"/worker/node.js")') : import("worker_threads").then(function(worker){return new worker["Worker"](import.meta.dirname+"/../node/node.mjs")}) : factory ? new window.Worker(URL.createObjectURL(new Blob(["onmessage=" + handler.toString()], { type: "text/javascript" }))) : new window.Worker("string" == typeof worker_path ? worker_path : (1, eval)("import.meta.url").replace("/worker.js", "/worker/worker.js").replace("flexsearch.bundle.module.min.js", "module/worker/worker.js").replace("flexsearch.bundle.module.min.mjs", "module/worker/worker.js"), { type: "module" }); } ================================================ FILE: dist/module-debug/async.js ================================================ import Document from "./document.js"; import Index from "./index.js"; import WorkerIndex from "./worker.js"; export default function (prototype) { register.call(prototype, "add"); register.call(prototype, "append"); register.call(prototype, "search"); register.call(prototype, "update"); register.call(prototype, "remove"); register.call(prototype, "searchCache"); } let timer, timestamp, cycle; function tick() { timer = cycle = 0; } /** * @param {!string} key * @this {Index|Document|WorkerIndex} */ function register(key) { this[key + "Async"] = function () { const args = arguments, arg = args[args.length - 1]; let callback; if ("function" == typeof arg) { callback = arg; delete args[args.length - 1]; } if (!timer) { timer = setTimeout(tick, 0); timestamp = Date.now(); } else if (!cycle) { const now = Date.now(), duration = now - timestamp, target = 3 * (this.priority * this.priority); cycle = duration >= target; } if (cycle) { const self = this; return new Promise(resolve => { setTimeout(function () { resolve(self[key + "Async"].apply(self, args)); }, 0); }); } const res = this[key].apply(this, args), promise = res.then ? res : new Promise(resolve => resolve(res)); if (callback) { promise.then(callback); } return promise; }; } ================================================ FILE: dist/module-debug/bundle.js ================================================ import { SearchOptions, ContextOptions, DocumentDescriptor, DocumentSearchOptions, FieldOptions, IndexOptions, DocumentOptions, TagOptions, StoreOptions, EncoderOptions, EncoderSplitOptions, PersistentOptions, ResolverOptions, HighlightBoundaryOptions, HighlightEllipsisOptions, HighlightOptions } from "./type.js"; import StorageInterface from "./db/interface.js"; import Document from "./document.js"; import Index from "./index.js"; import WorkerIndex from "./worker.js"; import Resolver from "./resolver.js"; import Encoder from "./encoder.js"; import IdxDB from "./db/indexeddb/index.js"; import Charset from "./charset.js"; import { KeystoreMap, KeystoreArray, KeystoreSet } from "./keystore.js"; /** @export */Index.prototype.add; /** @export */Index.prototype.append; /** @export */Index.prototype.search; /** @export */Index.prototype.update; /** @export */Index.prototype.remove; /** @export */Index.prototype.contain; /** @export */Index.prototype.clear; /** @export */Index.prototype.cleanup; /** @export */Index.prototype.searchCache; /** @export */Index.prototype.addAsync; /** @export */Index.prototype.appendAsync; /** @export */Index.prototype.searchAsync; /** @export */Index.prototype.searchCacheAsync; /** @export */Index.prototype.updateAsync; /** @export */Index.prototype.removeAsync; /** @export */Index.prototype.export; /** @export */Index.prototype.import; /** @export */Index.prototype.serialize; /** @export */Index.prototype.mount; /** @export */Index.prototype.commit; /** @export */Index.prototype.destroy; /** @export */Index.prototype.encoder; /** @export */Index.prototype.reg; /** @export */Index.prototype.map; /** @export */Index.prototype.ctx; /** @export */Index.prototype.db; /** @export */Index.prototype.tag; /** @export */Index.prototype.store; /** @export */Index.prototype.depth; /** @export */Index.prototype.bidirectional; /** @export */Index.prototype.commit_task; /** @export */Index.prototype.commit_timer; /** @export */Index.prototype.cache; /** @export */Index.prototype.bypass; /** @export */Index.prototype.document; /** @export */Encoder.prototype.assign; /** @export */Encoder.prototype.encode; /** @export */Encoder.prototype.addMatcher; /** @export */Encoder.prototype.addStemmer; /** @export */Encoder.prototype.addFilter; /** @export */Encoder.prototype.addMapper; /** @export */Encoder.prototype.addReplacer; /** @export */Document.prototype.add; /** @export */Document.prototype.append; /** @export */Document.prototype.search; /** @export */Document.prototype.update; /** @export */Document.prototype.remove; /** @export */Document.prototype.contain; /** @export */Document.prototype.clear; /** @export */Document.prototype.cleanup; /** @export */Document.prototype.addAsync; /** @export */Document.prototype.appendAsync; /** @export */Document.prototype.updateAsync; /** @export */Document.prototype.removeAsync; /** @export */Document.prototype.searchAsync; /** @export */Document.prototype.searchCacheAsync; /** @export */Document.prototype.searchCache; /** @export */Document.prototype.mount; /** @export */Document.prototype.commit; /** @export */Document.prototype.destroy; /** @export */Document.prototype.export; /** @export */Document.prototype.import; /** @export */Document.prototype.get; /** @export */Document.prototype.set; /** @export */Document.prototype.field; /** @export */Document.prototype.index; /** @export */Document.prototype.reg; /** @export */Document.prototype.tag; /** @export */Document.prototype.store; /** @export */Document.prototype.fastupdate; /** @export */Resolver.prototype.limit; /** @export */Resolver.prototype.offset; /** @export */Resolver.prototype.boost; /** @export */Resolver.prototype.resolve; /** @export */Resolver.prototype.or; /** @export */Resolver.prototype.and; /** @export */Resolver.prototype.xor; /** @export */Resolver.prototype.not; /** @export */Resolver.prototype.result; /** @export */Resolver.prototype.await; /** @export */StorageInterface.db; /** @export */StorageInterface.id; /** @export */StorageInterface.support_tag_search; /** @export */StorageInterface.fastupdate; /** @export */StorageInterface.prototype.mount; /** @export */StorageInterface.prototype.open; /** @export */StorageInterface.prototype.close; /** @export */StorageInterface.prototype.destroy; /** @export */StorageInterface.prototype.clear; /** @export */StorageInterface.prototype.get; /** @export */StorageInterface.prototype.tag; /** @export */StorageInterface.prototype.enrich; /** @export */StorageInterface.prototype.has; /** @export */StorageInterface.prototype.search; /** @export */StorageInterface.prototype.info; /** @export */StorageInterface.prototype.commit; /** @export */StorageInterface.prototype.remove; /** @export */KeystoreArray.length; /** @export */KeystoreMap.size; /** @export */KeystoreSet.size; /** @export */Charset.Exact; /** @export */Charset.Default; /** @export */Charset.Normalize; /** @export */Charset.LatinBalance; /** @export */Charset.LatinAdvanced; /** @export */Charset.LatinExtra; /** @export */Charset.LatinSoundex; /** @export */Charset.CJK; /** @export @deprecated */Charset.LatinExact; /** @export @deprecated */Charset.LatinDefault; /** @export @deprecated */Charset.LatinSimple; /** @export @deprecated */Charset.ArabicDefault; /** @export @deprecated */Charset.CjkDefault; /** @export @deprecated */Charset.CyrillicDefault; /** @export */IndexOptions.preset; /** @export */IndexOptions.context; /** @export */IndexOptions.encoder; /** @export */IndexOptions.encode; /** @export */IndexOptions.resolution; /** @export */IndexOptions.tokenize; /** @export */IndexOptions.fastupdate; /** @export */IndexOptions.score; /** @export */IndexOptions.keystore; /** @export */IndexOptions.rtl; /** @export */IndexOptions.cache; /** @export */IndexOptions.resolve; /** @export */IndexOptions.db; /** @export */IndexOptions.worker; /** @export */IndexOptions.config; /** @export */IndexOptions.priority; /** @export */IndexOptions.export; /** @export */IndexOptions.import; /** @export */FieldOptions.preset; /** @export */FieldOptions.context; /** @export */FieldOptions.encoder; /** @export */FieldOptions.encode; /** @export */FieldOptions.resolution; /** @export */FieldOptions.tokenize; /** @export */FieldOptions.fastupdate; /** @export */FieldOptions.score; /** @export */FieldOptions.keystore; /** @export */FieldOptions.rtl; /** @export */FieldOptions.cache; /** @export */FieldOptions.db; /** @export */FieldOptions.config; /** @export */FieldOptions.resolve; /** @export */FieldOptions.field; /** @export */FieldOptions.filter; /** @export */FieldOptions.custom; /** @export */FieldOptions.worker; /** @export */DocumentOptions.context; /** @export */DocumentOptions.encoder; /** @export */DocumentOptions.encode; /** @export */DocumentOptions.resolution; /** @export */DocumentOptions.tokenize; /** @export */DocumentOptions.fastupdate; /** @export */DocumentOptions.score; /** @export */DocumentOptions.keystore; /** @export */DocumentOptions.rtl; /** @export */DocumentOptions.cache; /** @export */DocumentOptions.db; /** @export */DocumentOptions.doc; /** @export */DocumentOptions.document; /** @export */DocumentOptions.worker; /** @export */DocumentOptions.priority; /** @export */DocumentOptions.export; /** @export */DocumentOptions.import; /** @export */ContextOptions.depth; /** @export */ContextOptions.bidirectional; /** @export */ContextOptions.resolution; /** @export */DocumentDescriptor.field; /** @export */DocumentDescriptor.index; /** @export */DocumentDescriptor.tag; /** @export */DocumentDescriptor.store; /** @export */DocumentDescriptor.id; /** @export */TagOptions.field; /** @export */TagOptions.tag; /** @export */TagOptions.filter; /** @export */TagOptions.custom; /** @export */TagOptions.keystore; /** @export */TagOptions.db; /** @export */TagOptions.config; /** @export */StoreOptions.field; /** @export */StoreOptions.filter; /** @export */StoreOptions.custom; /** @export */StoreOptions.config; /** @export */SearchOptions.query; /** @export */SearchOptions.limit; /** @export */SearchOptions.offset; /** @export */SearchOptions.context; /** @export */SearchOptions.suggest; /** @export */SearchOptions.resolve; /** @export */SearchOptions.cache; /** @export */SearchOptions.resolution; /** @export */DocumentSearchOptions.query; /** @export */DocumentSearchOptions.limit; /** @export */DocumentSearchOptions.offset; /** @export */DocumentSearchOptions.context; /** @export */DocumentSearchOptions.suggest; /** @export */DocumentSearchOptions.resolve; /** @export */DocumentSearchOptions.enrich; /** @export */DocumentSearchOptions.cache; /** @export */DocumentSearchOptions.resolution; /** @export */DocumentSearchOptions.tag; /** @export */DocumentSearchOptions.field; /** @export */DocumentSearchOptions.index; /** @export */DocumentSearchOptions.pluck; /** @export */DocumentSearchOptions.merge; /** @export */DocumentSearchOptions.highlight; /** @export */EncoderOptions.rtl; /** @export */EncoderOptions.dedupe; /** @export */EncoderOptions.split; /** @export */EncoderOptions.include; /** @export */EncoderOptions.exclude; /** @export */EncoderOptions.prepare; /** @export */EncoderOptions.finalize; /** @export */EncoderOptions.filter; /** @export */EncoderOptions.matcher; /** @export */EncoderOptions.mapper; /** @export */EncoderOptions.stemmer; /** @export */EncoderOptions.replacer; /** @export */EncoderOptions.minlength; /** @export */EncoderOptions.maxlength; /** @export */EncoderOptions.cache; /** @export */EncoderSplitOptions.letter; /** @export */EncoderSplitOptions.number; /** @export */EncoderSplitOptions.symbol; /** @export */EncoderSplitOptions.punctuation; /** @export */EncoderSplitOptions.control; /** @export */EncoderSplitOptions.char; /** @export */PersistentOptions.name; /** @export */PersistentOptions.field; /** @export */PersistentOptions.type; /** @export */PersistentOptions.db; /** @export */ResolverOptions.index; /** @export */ResolverOptions.query; /** @export */ResolverOptions.limit; /** @export */ResolverOptions.offset; /** @export */ResolverOptions.boost; /** @export */ResolverOptions.enrich; /** @export */ResolverOptions.resolve; /** @export */ResolverOptions.suggest; /** @export */ResolverOptions.cache; /** @export */ResolverOptions.async; /** @export */ResolverOptions.queue; /** @export */ResolverOptions.and; /** @export */ResolverOptions.or; /** @export */ResolverOptions.xor; /** @export */ResolverOptions.not; /** @export */ResolverOptions.pluck; /** @export */ResolverOptions.field; /** @export */HighlightBoundaryOptions.before; /** @export */HighlightBoundaryOptions.after; /** @export */HighlightBoundaryOptions.total; /** @export */HighlightEllipsisOptions.template; /** @export */HighlightEllipsisOptions.pattern; /** @export */HighlightOptions.template; /** @export */HighlightOptions.boundary; /** @export */HighlightOptions.ellipsis; /** @export */HighlightOptions.clip; /** @export */HighlightOptions.merge; const FlexSearch = { Index: Index, Charset: Charset, Encoder: Encoder, Document: Document, Worker: WorkerIndex, Resolver: Resolver, IndexedDB: IdxDB, Language: {} }; { const root = "undefined" != typeof self ? self : "undefined" != typeof global ? global : self; let prop; if ((prop = root.define) && prop.amd) { prop([], function () { return FlexSearch; }); } else if ("object" == typeof root.exports) { root.exports = FlexSearch; } else { /** @export */ root.FlexSearch = FlexSearch; } } export default FlexSearch; export { Index, Document, Encoder, Charset, WorkerIndex as Worker, Resolver, IdxDB as IndexedDB }; ================================================ FILE: dist/module-debug/cache.js ================================================ import Index from "./index.js"; import { SearchOptions, DocumentSearchOptions } from "./type.js"; /** * @param {string|SearchOptions|DocumentSearchOptions} query * @param {number|SearchOptions|DocumentSearchOptions=} limit * @param {SearchOptions|DocumentSearchOptions=} options * @this {Index} * @returns {Array|Promise} */ export function searchCache(query, limit, options) { if (!options) { if (!limit && "object" == typeof query) { options = query; } else if ("object" == typeof limit) { options = limit; limit = 0; } } if (options) { query = options.query || query; limit = options.limit || limit; } let key = "" + (limit || 0); if (options) { const { context, suggest, offset, resolve, boost, resolution } = options; key += (offset || 0) + !!context + !!suggest + (!1 !== resolve) + (resolution || this.resolution) + (boost || 0); } query = ("" + query).toLowerCase(); if (!this.cache) { this.cache = new CacheClass(); } let cache = this.cache.get(query + key); if (!cache) { const opt_cache = options && options.cache; opt_cache && (options.cache = !1); cache = this.search(query, limit, options); opt_cache && (options.cache = opt_cache); this.cache.set(query + key, cache); } return cache; } /** * @param {boolean|number=} limit * @constructor */ export default function CacheClass(limit) { /** @private */ this.limit = !limit || !0 === limit ? 1000 : limit; /** @private */ this.cache = new Map(); /** @private */ this.last = ""; } /** * @param {string} key * @param {Array} value */ CacheClass.prototype.set = function (key, value) { this.cache.set(this.last = key, value); if (this.cache.size > this.limit) { this.cache.delete(this.cache.keys().next().value); } }; /** * @param {string} key */ CacheClass.prototype.get = function (key) { const cache = this.cache.get(key); if (cache && this.last !== key) { this.cache.delete(key); this.cache.set(this.last = key, cache); } return cache; }; /** * @param {string|number} id */ CacheClass.prototype.remove = function (id) { for (const item of this.cache) { const key = item[0], value = item[1]; if (value.includes(id)) { this.cache.delete(key); } } }; CacheClass.prototype.clear = function () { this.cache.clear(); this.last = ""; }; ================================================ FILE: dist/module-debug/charset/cjk.js ================================================ import { EncoderOptions } from "../type.js"; /** @type EncoderOptions */ const options = { split: "" }; export default options; ================================================ FILE: dist/module-debug/charset/exact.js ================================================ import { EncoderOptions } from "../type.js"; /** @type EncoderOptions */ const options = { normalize: !1, numeric: !1, dedupe: !1 }; export default options; ================================================ FILE: dist/module-debug/charset/latin/advanced.js ================================================ import { EncoderOptions } from "../../type.js"; import { soundex } from "./balance.js"; export const matcher = new Map([["ae", "a"], ["oe", "o"], ["sh", "s"], ["kh", "k"], ["th", "t"], ["ph", "f"], ["pf", "f"]]); export const replacer = [/([^aeo])h(.)/g, "$1$2", /([aeo])h([^aeo]|$)/g, "$1$2", /(.)\1+/g, "$1"]; /** @type EncoderOptions */ const options = { mapper: soundex, matcher: matcher, replacer: replacer }; export default options; ================================================ FILE: dist/module-debug/charset/latin/balance.js ================================================ import { EncoderOptions } from "../../type.js"; export const soundex = new Map([["b", "p"], ["v", "f"], ["w", "f"], ["z", "s"], ["x", "s"], ["d", "t"], ["n", "m"], ["c", "k"], ["g", "k"], ["j", "k"], ["q", "k"], ["i", "e"], ["y", "e"], ["u", "o"]]); /** @type EncoderOptions */ const options = { mapper: soundex }; export default options; ================================================ FILE: dist/module-debug/charset/latin/extra.js ================================================ import { EncoderOptions } from "../../type.js"; import { soundex } from "./balance.js"; import { matcher, replacer } from "./advanced.js"; export const compact = [/(?!^)[aeo]/g, ""]; /** @type EncoderOptions */ const options = { mapper: soundex, replacer: replacer.concat(compact), matcher: matcher }; export default options; ================================================ FILE: dist/module-debug/charset/latin/soundex.js ================================================ import { EncoderOptions } from "../../type.js"; /** @type {EncoderOptions} */ const options = { dedupe: !1, include: { letter: !0 }, finalize: function (arr) { for (let i = 0; i < arr.length; i++) { arr[i] = soundex(arr[i]); } } }; export default options; const codes = { a: "", e: "", i: "", o: "", u: "", y: "", b: 1, f: 1, p: 1, v: 1, c: 2, g: 2, j: 2, k: 2, q: 2, s: 2, x: 2, z: 2, ß: 2, d: 3, t: 3, l: 4, m: 5, n: 5, r: 6 }; function soundex(stringToEncode) { let encodedString = stringToEncode.charAt(0), last = codes[encodedString]; for (let i = 1, char; i < stringToEncode.length; i++) { char = stringToEncode.charAt(i); if ("h" !== char && "w" !== char) { char = codes[char]; if (char) { if (char !== last) { encodedString += char; last = char; if (4 === encodedString.length) { break; } } } } } return encodedString; } ================================================ FILE: dist/module-debug/charset/normalize.js ================================================ import { EncoderOptions } from "../type.js"; /** @type EncoderOptions */ const options = {}; export default options; ================================================ FILE: dist/module-debug/charset/polyfill.js ================================================ export default [["ª", "a"], ["²", "2"], ["³", "3"], ["¹", "1"], ["º", "o"], ["¼", "1⁄4"], ["½", "1⁄2"], ["¾", "3⁄4"], ["à", "a"], ["á", "a"], ["â", "a"], ["ã", "a"], ["ä", "a"], ["å", "a"], ["ç", "c"], ["è", "e"], ["é", "e"], ["ê", "e"], ["ë", "e"], ["ì", "i"], ["í", "i"], ["î", "i"], ["ï", "i"], ["ñ", "n"], ["ò", "o"], ["ó", "o"], ["ô", "o"], ["õ", "o"], ["ö", "o"], ["ù", "u"], ["ú", "u"], ["û", "u"], ["ü", "u"], ["ý", "y"], ["ÿ", "y"], ["ā", "a"], ["ă", "a"], ["ą", "a"], ["ć", "c"], ["ĉ", "c"], ["ċ", "c"], ["č", "c"], ["ď", "d"], ["ē", "e"], ["ĕ", "e"], ["ė", "e"], ["ę", "e"], ["ě", "e"], ["ĝ", "g"], ["ğ", "g"], ["ġ", "g"], ["ģ", "g"], ["ĥ", "h"], ["ĩ", "i"], ["ī", "i"], ["ĭ", "i"], ["į", "i"], ["ij", "ij"], ["ĵ", "j"], ["ķ", "k"], ["ĺ", "l"], ["ļ", "l"], ["ľ", "l"], ["ŀ", "l"], ["ń", "n"], ["ņ", "n"], ["ň", "n"], ["ʼn", "n"], ["ō", "o"], ["ŏ", "o"], ["ő", "o"], ["ŕ", "r"], ["ŗ", "r"], ["ř", "r"], ["ś", "s"], ["ŝ", "s"], ["ş", "s"], ["š", "s"], ["ţ", "t"], ["ť", "t"], ["ũ", "u"], ["ū", "u"], ["ŭ", "u"], ["ů", "u"], ["ű", "u"], ["ų", "u"], ["ŵ", "w"], ["ŷ", "y"], ["ź", "z"], ["ż", "z"], ["ž", "z"], ["ſ", "s"], ["ơ", "o"], ["ư", "u"], ["dž", "dz"], ["lj", "lj"], ["nj", "nj"], ["ǎ", "a"], ["ǐ", "i"], ["ǒ", "o"], ["ǔ", "u"], ["ǖ", "u"], ["ǘ", "u"], ["ǚ", "u"], ["ǜ", "u"], ["ǟ", "a"], ["ǡ", "a"], ["ǣ", "ae"], ["æ", "ae"], ["ǽ", "ae"], ["ǧ", "g"], ["ǩ", "k"], ["ǫ", "o"], ["ǭ", "o"], ["ǯ", "ʒ"], ["ǰ", "j"], ["dz", "dz"], ["ǵ", "g"], ["ǹ", "n"], ["ǻ", "a"], ["ǿ", "ø"], ["ȁ", "a"], ["ȃ", "a"], ["ȅ", "e"], ["ȇ", "e"], ["ȉ", "i"], ["ȋ", "i"], ["ȍ", "o"], ["ȏ", "o"], ["ȑ", "r"], ["ȓ", "r"], ["ȕ", "u"], ["ȗ", "u"], ["ș", "s"], ["ț", "t"], ["ȟ", "h"], ["ȧ", "a"], ["ȩ", "e"], ["ȫ", "o"], ["ȭ", "o"], ["ȯ", "o"], ["ȱ", "o"], ["ȳ", "y"], ["ʰ", "h"], ["ʱ", "h"], ["ɦ", "h"], ["ʲ", "j"], ["ʳ", "r"], ["ʴ", "ɹ"], ["ʵ", "ɻ"], ["ʶ", "ʁ"], ["ʷ", "w"], ["ʸ", "y"], ["ˠ", "ɣ"], ["ˡ", "l"], ["ˢ", "s"], ["ˣ", "x"], ["ˤ", "ʕ"], ["ΐ", "ι"], ["ά", "α"], ["έ", "ε"], ["ή", "η"], ["ί", "ι"], ["ΰ", "υ"], ["ϊ", "ι"], ["ϋ", "υ"], ["ό", "ο"], ["ύ", "υ"], ["ώ", "ω"], ["ϐ", "β"], ["ϑ", "θ"], ["ϒ", "Υ"], ["ϓ", "Υ"], ["ϔ", "Υ"], ["ϕ", "φ"], ["ϖ", "π"], ["ϰ", "κ"], ["ϱ", "ρ"], ["ϲ", "ς"], ["ϵ", "ε"], ["й", "и"], ["ѐ", "е"], ["ё", "е"], ["ѓ", "г"], ["ї", "і"], ["ќ", "к"], ["ѝ", "и"], ["ў", "у"], ["ѷ", "ѵ"], ["ӂ", "ж"], ["ӑ", "а"], ["ӓ", "а"], ["ӗ", "е"], ["ӛ", "ә"], ["ӝ", "ж"], ["ӟ", "з"], ["ӣ", "и"], ["ӥ", "и"], ["ӧ", "о"], ["ӫ", "ө"], ["ӭ", "э"], ["ӯ", "у"], ["ӱ", "у"], ["ӳ", "у"], ["ӵ", "ч"]]; ================================================ FILE: dist/module-debug/charset.js ================================================ import charset_exact from "./charset/exact.js"; import charset_normalize from "./charset/normalize.js"; import charset_latin_balance from "./charset/latin/balance.js"; import charset_latin_advanced from "./charset/latin/advanced.js"; import charset_latin_extra from "./charset/latin/extra.js"; import charset_latin_soundex from "./charset/latin/soundex.js"; import charset_cjk from "./charset/cjk.js"; export const Exact = charset_exact; export const Default = charset_normalize; export const Normalize = charset_normalize; export const LatinBalance = charset_latin_balance; export const LatinAdvanced = charset_latin_advanced; export const LatinExtra = charset_latin_extra; export const LatinSoundex = charset_latin_soundex; export const CJK = charset_cjk; export const LatinExact = charset_exact; export const LatinDefault = charset_normalize; export const LatinSimple = charset_normalize; export default { Exact: charset_exact, Default: charset_normalize, Normalize: charset_normalize, LatinBalance: charset_latin_balance, LatinAdvanced: charset_latin_advanced, LatinExtra: charset_latin_extra, LatinSoundex: charset_latin_soundex, CJK: charset_cjk, LatinExact: charset_exact, LatinDefault: charset_normalize, LatinSimple: charset_normalize }; ================================================ FILE: dist/module-debug/common.js ================================================ /** * @param {*} value * @param {*} default_value * @param {*=} merge_value * @return {*} */ export function merge_option(value, default_value, merge_value) { const type_merge = typeof merge_value, type_value = typeof value; if ("undefined" != type_merge) { if ("undefined" != type_value) { if (merge_value) { if ("function" == type_value && type_merge == type_value) { return function (str) { return (/** @type Function */value( /** @type Function */merge_value(str)) ); }; } const constructor_value = value.constructor, constructor_merge = merge_value.constructor; if (constructor_value === constructor_merge) { if (constructor_value === Array) { return merge_value.concat(value); } if (constructor_value === Map) { const map = new Map( /** @type !Map */merge_value); for (const item of /** @type !Map */value) { const key = item[0], val = item[1]; map.set(key, val); } return map; } if (constructor_value === Set) { const set = new Set( /** @type !Set */merge_value); for (const val of /** @type !Set */value.values()) { set.add(val); } return set; } } } return value; } else { return merge_value; } } return "undefined" == type_value ? default_value : value; } export function inherit(target_value, default_value) { return "undefined" == typeof target_value ? default_value : target_value; } export function create_object() { return Object.create(null); } export function concat(arrays) { return [].concat.apply([], arrays); } export function sort_by_length_down(a, b) { return b.length - a.length; } export function sort_by_length_up(a, b) { return a.length - b.length; } export function is_array(val) { return val.constructor === Array; } export function is_string(val) { return "string" == typeof val; } export function is_object(val) { return "object" == typeof val; } export function is_function(val) { return "function" == typeof val; } /** * @param {Map|Set} val * @param {boolean=} stringify * @return {Array} */ export function toArray(val, stringify) { const result = []; for (const key of val.keys()) { result.push(stringify ? "" + key : key); } return result; } export function parse_simple(obj, tree) { if (is_string(tree)) { obj = obj[tree]; } else for (let i = 0; obj && i < tree.length; i++) { obj = obj[tree[i]]; } return obj; } export function get_max_len(arr) { let len = 0; for (let i = 0, res; i < arr.length; i++) { if (res = arr[i]) { if (len < res.length) { len = res.length; } } } return len; } ================================================ FILE: dist/module-debug/compress.js ================================================ let table, timer; const cache = new Map(); function toRadix(number, radix = 255) { if (!table) { table = Array(radix); for (let i = 0; i < radix; i++) table[i] = i + 1; table = String.fromCharCode.apply(null, table); } let rixit, residual = number, result = ""; while (!0) { rixit = residual % radix; result = table.charAt(rixit) + result; residual = 0 | residual / radix; if (!residual) break; } return result; } export default function (str) { if (timer) { if (cache.has(str)) { return cache.get(str); } } else { timer = setTimeout(clear, 1); } const result = toRadix("number" == typeof str ? str : lcg(str)); 2e5 < cache.size && cache.clear(); cache.set(str, result); return result; } function lcg(str) { let range = 4294967295; if ("number" == typeof str) { return str & range; } let crc = 0; for (let i = 0; i < str.length; i++) { crc = (crc * 33 ^ str.charCodeAt(i)) & range; } return crc + 2147483648; } function clear() { timer = null; cache.clear(); } ================================================ FILE: dist/module-debug/db/clickhouse/index.js ================================================ import { ClickHouse } from "clickhouse"; import StorageInterface from "../interface.js"; import { concat, toArray } from "../../common.js"; const defaults = { host: "http://localhost", port: "8123", debug: !1, basicAuth: null, isUseGzip: !1, trimQuery: !1, usePost: !1, format: "json", raw: !1, config: { output_format_json_quote_64bit_integers: 0, enable_http_compression: 0, database: "default" } }, VERSION = 1, fields = ["map", "ctx", "tag", "reg", "cfg"], types = { text: "String", char: "String", varchar: "String", string: "String", number: "Int32", numeric: "Int32", integer: "Int32", smallint: "Int16", tinyint: "Int8", mediumint: "Int32", int: "Int32", int8: "Int8", uint8: "UInt8", int16: "Int16", uint16: "UInt16", int32: "Int32", uint32: "UInt32", int64: "Int64", uint64: "UInt64", bigint: "Int64" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } let Index; /** * @constructor * @implements StorageInterface */ export default function ClickhouseDB(name, config = {}) { if (!this || this.constructor !== ClickhouseDB) { return new ClickhouseDB(name, config); } if ("object" == typeof name) { config = name; name = name.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = "flexsearch" + (name ? "_" + sanitize(name) : ""); this.field = config.field ? "_" + sanitize(config.field) : ""; this.type = config.type ? types[config.type.toLowerCase()] : "String"; if (!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); this.support_tag_search = !0; this.db = Index || (Index = config.db || null); Object.assign(defaults, config); config.database && (defaults.config.database = config.database); this.db && delete defaults.db; } ClickhouseDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } defaults.resolution = Math.max(flexsearch.resolution, flexsearch.resolution_ctx); flexsearch.db = this; return this.open(); }; ClickhouseDB.prototype.open = async function () { if (!this.db) { this.db = Index || (Index = new ClickHouse(defaults)); } const exists = await this.db.query(` SELECT 1 FROM system.databases WHERE name = '${this.id}'; `).toPromise(); if (!exists || !exists.length) { await this.db.query(` CREATE DATABASE IF NOT EXISTS ${this.id}; `).toPromise(); } for (let i = 0; i < fields.length; i++) { switch (fields[i]) { case "map": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.map${this.field}( key String, res ${255 >= defaults.resolution ? "UInt8" : "UInt16"}, id ${this.type} ) ENGINE = MergeTree ORDER BY (key, id); `, { params: { name: this.id + ".map" + this.field } }).toPromise(); break; case "ctx": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.ctx${this.field}( ctx String, key String, res ${255 >= defaults.resolution ? "UInt8" : "UInt16"}, id ${this.type} ) ENGINE = MergeTree ORDER BY (ctx, key, id); `).toPromise(); break; case "tag": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.tag${this.field}( tag String, id ${this.type} ) ENGINE = MergeTree ORDER BY (tag, id); `).toPromise(); break; case "reg": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.reg( id ${this.type}, doc Nullable(String) ) ENGINE = MergeTree ORDER BY (id); `).toPromise(); break; case "cfg": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.cfg${this.field}( cfg String ) ENGINE = TinyLog; `).toPromise(); break; } } return this.db; }; ClickhouseDB.prototype.close = function () { this.db = Index = null; return this; }; ClickhouseDB.prototype.destroy = function () { return Promise.all([this.db.query(`DROP TABLE ${this.id}.map${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.ctx${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.tag${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.cfg${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.reg;`).toPromise()]); }; ClickhouseDB.prototype.clear = function () { return Promise.all([this.db.query(`TRUNCATE TABLE ${this.id}.map${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.ctx${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.tag${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.cfg${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.reg;`).toPromise()]); }; function create_result(rows, resolve, enrich) { if (resolve) { for (let i = 0; i < rows.length; i++) { if (enrich) { if (rows[i].doc) { rows[i].doc = JSON.parse(rows[i].doc); } } else { rows[i] = rows[i].id; } } return rows; } else { const arr = []; for (let i = 0, row; i < rows.length; i++) { row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id); } return arr; } } ClickhouseDB.prototype.get = function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1, tags) { let rows, params = ctx ? { ctx, key } : { key }, table = this.id + (ctx ? ".ctx" : ".map") + this.field; if (tags) { for (let i = 0, count = 1; i < tags.length; i += 2) { ` AND ${table}.id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = {tag${count}:String})`; params["tag" + count] = tags[i + 1]; count++; } } if (ctx) { rows = this.db.query(` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT OUTER JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE ctx = {ctx:String} AND key = {key:String} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, { params }).toPromise(); } else { rows = this.db.query(` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT OUTER JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE key = {key:String} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, { params }).toPromise(); } return rows.then(function (rows) { return create_result(rows, resolve, enrich); }); }; ClickhouseDB.prototype.tag = function (tag, limit = 0, offset = 0, enrich = !1) { const table = this.id + ".tag" + this.field, promise = this.db.query(` SELECT ${table}.id ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT OUTER JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE tag = {tag:String} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, { params: { tag } }).toPromise(); enrich || promise.then(function (rows) { return create_result(rows, !0, !1); }); return promise; }; ClickhouseDB.prototype.enrich = async function (ids) { let MAXIMUM_QUERY_VARS = 1e5, result = []; if ("object" != typeof ids) { ids = [ids]; } for (let count = 0; count < ids.length;) { const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; let params = {}, stmt = ""; for (let i = 0; i < chunk.length; i++) { stmt += (stmt ? "," : "") + "{id" + (i + 1) + ":String}"; params["id" + (i + 1)] = chunk[i]; } const res = await this.db.query(` SELECT id, doc FROM ${this.id}.reg WHERE id IN (${stmt})`, { params }).toPromise(); if (res && res.length) { for (let i = 0, doc; i < res.length; i++) { if (doc = res[i].doc) { res[i].doc = JSON.parse(doc); } } result.push(res); } } return 1 === result.length ? result[0] : 1 < result.length ? concat(result) : result; }; ClickhouseDB.prototype.has = async function (id) { const result = await this.db.query(` SELECT 1 FROM ${this.id}.reg WHERE id = {id:${this.type}} LIMIT 1`, { params: { id } }).toPromise(); return !!(result && result[0] && result[0][1]); }; ClickhouseDB.prototype.search = function (flexsearch, query, limit = 100, offset = 0, suggest = !1, resolve = !0, enrich = !1, tags) { let rows; if (1 < query.length && flexsearch.depth) { let where = "", params = {}, keyword = query[0], term; for (let i = 1; i < query.length; i++) { term = query[i]; const swap = flexsearch.bidirectional && term > keyword; where += (where ? " OR " : "") + `(ctx = {ctx${i}:String} AND key = {key${i}:String})`; params["ctx" + i] = swap ? term : keyword; params["key" + i] = swap ? keyword : term; keyword = term; } if (tags) { where = "(" + where + ")"; for (let i = 0, count = 1; i < tags.length; i += 2) { where += ` AND id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = {tag${count}:String})`; params["tag" + count] = tags[i + 1]; count++; } } rows = this.db.query(` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM ${this.id}.ctx${this.field} WHERE ${where} GROUP BY id ) as r ${enrich ? ` LEFT OUTER JOIN ${this.id}.reg ON ${this.id}.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + (query.length - 1)} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, { params }).toPromise(); } else { let where = "", params = {}; for (let i = 0; i < query.length; i++) { where += (where ? "," : "") + `{key${i}:String}`; params["key" + i] = query[i]; } where = "key " + (1 < query.length ? "IN (" + where + ")" : "= " + where); if (tags) { where = "(" + where + ")"; for (let i = 0, count = 1; i < tags.length; i += 2) { where += ` AND id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = {tag${count}:String})`; params["tag" + count] = tags[i + 1]; count++; } } rows = this.db.query(` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM ${this.id}.map${this.field} WHERE ${where} GROUP BY id ) as r ${enrich ? ` LEFT OUTER JOIN ${this.id}.reg ON ${this.id}.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + query.length} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, { params }).toPromise(); } return rows.then(function (rows) { return create_result(rows, resolve, enrich); }); }; ClickhouseDB.prototype.info = function () {}; ClickhouseDB.prototype.transaction = function (task) { return task.call(this); }; ClickhouseDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { /** @dict */ task = tasks[i]; if (task.del) { removals.push(task.del); } else if (task.ins) {} } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } const promises = []; if (flexsearch.map.size) { let data = []; for (const item of flexsearch.map) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { for (let j = 0; j < ids.length; j++) { data.push({ key: key, res: i, id: /*this.type === "number" ? parseInt(ids[j], 10) :*/ids[j] }); } } } } if (data.length) { promises.push(this.db.insert(`INSERT INTO ${this.id}.map${this.field} (key, res, id)`, data).toPromise()); } } if (flexsearch.ctx.size) { let data = []; for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { for (let j = 0; j < ids.length; j++) { data.push({ ctx: ctx_key, key: key, res: i, id: /*this.type === "number" ? parseInt(ids[j], 10) :*/ids[j] }); } } } } } if (data.length) { promises.push(this.db.insert(`INSERT INTO ${this.id}.ctx${this.field} (ctx, key, res, id)`, data).toPromise()); } } if (flexsearch.tag) { let data = []; for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; for (let j = 0; j < ids.length; j++) { data.push({ tag, id: ids[j] }); } } if (data.length) { promises.push(this.db.insert(`INSERT INTO ${this.id}.tag${this.field} (tag, id)`, data).toPromise()); } } if (flexsearch.store) { let data = []; for (const item of flexsearch.store.entries()) { const id = item[0], doc = item[1]; data.push({ id, doc: doc && JSON.stringify(doc) }); } if (data.length) { promises.push(this.db.insert(`INSERT INTO ${this.id}.reg (id, doc)`, data).toPromise()); } } else if (!flexsearch.bypass) { let data = toArray(flexsearch.reg); for (let i = 0; i < data.length; i++) { data[i] = { id: data[i] }; } if (data.length) { promises.push(this.db.insert(`INSERT INTO ${this.id}.reg (id)`, data).toPromise()); } } promises.length && (await Promise.all(promises)); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); await Promise.all([this.db.query(`OPTIMIZE TABLE ${this.id}.map${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.ctx${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.tag${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.reg FINAL`).toPromise()]); }; ClickhouseDB.prototype.remove = async function (ids) { if ("object" != typeof ids) { ids = [ids]; } while (ids.length) { let chunk = ids.slice(0, 1e5); ids = ids.slice(1e5); chunk = "String" === this.type ? "'" + chunk.join("','") + "'" : chunk.join(","); await Promise.all([this.db.query(` ALTER TABLE ${this.id}.map${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;`).toPromise(), this.db.query(` ALTER TABLE ${this.id}.ctx${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;`).toPromise(), this.db.query(` ALTER TABLE ${this.id}.tag${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;`).toPromise(), this.db.query(` ALTER TABLE ${this.id}.reg DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;`).toPromise()]); } }; ================================================ FILE: dist/module-debug/db/indexeddb/index.js ================================================ const VERSION = 1, IndexedDB = "undefined" != typeof window && (window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB), IDBTransaction = "undefined" != typeof window && (window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction), IDBKeyRange = "undefined" != typeof window && (window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange), fields = ["map", "ctx", "tag", "reg", "cfg"]; import StorageInterface from "../interface.js"; import { create_object, toArray } from "../../common.js"; /** * @param {!string} str * @return {string} */ function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } const Index = create_object(); /** * @param {string|PersistentOptions=} name * @param {PersistentOptions=} config * @constructor * @implements StorageInterface */ export default function IdxDB(name, config = {}) { if (!this || this.constructor !== IdxDB) { return new IdxDB(name, config); } if ("object" == typeof name) { config = /** @type PersistentOptions */name; name = name.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = "flexsearch" + (name ? ":" + sanitize(name) : ""); this.field = config.field ? sanitize(config.field) : ""; this.type = config.type; this.support_tag_search = !1; this.fastupdate = !1; this.db = null; this.trx = {}; } IdxDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; IdxDB.prototype.open = function () { if (this.db) return this.db; let self = this; navigator.storage && navigator.storage.persist && navigator.storage.persist(); Index[self.id] || (Index[self.id] = []); Index[self.id].push(self.field); const req = IndexedDB.open(self.id, VERSION); /** @this {IDBOpenDBRequest} */ req.onupgradeneeded = function () { const db = self.db = this.result; for (let i = 0, ref; i < fields.length; i++) { ref = fields[i]; for (let j = 0, field; j < Index[self.id].length; j++) { field = Index[self.id][j]; db.objectStoreNames.contains(ref + ("reg" !== ref ? field ? ":" + field : "" : "")) || db.createObjectStore(ref + ("reg" !== ref ? field ? ":" + field : "" : "")); } } }; return self.db = promisfy(req, function (result) { self.db = result; self.db.onversionchange = function () { self.close(); }; }); }; IdxDB.prototype.close = function () { this.db && this.db.close(); this.db = null; }; /** * @return {!Promise} */ IdxDB.prototype.destroy = function () { const req = IndexedDB.deleteDatabase(this.id); return promisfy(req); }; /** * @return {!Promise} */ IdxDB.prototype.clear = function () { const stores = []; for (let i = 0, ref; i < fields.length; i++) { ref = fields[i]; for (let j = 0, field; j < Index[this.id].length; j++) { field = Index[this.id][j]; stores.push(ref + ("reg" !== ref ? field ? ":" + field : "" : "")); } } const transaction = this.db.transaction(stores, "readwrite"); for (let i = 0; i < stores.length; i++) { transaction.objectStore(stores[i]).clear(); } return promisfy(transaction); }; /** * @param {!string} key * @param {string=} ctx * @param {number=} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @return {!Promise} */ IdxDB.prototype.get = function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1) { const transaction = this.db.transaction((ctx ? "ctx" : "map") + (this.field ? ":" + this.field : ""), "readonly"), map = transaction.objectStore((ctx ? "ctx" : "map") + (this.field ? ":" + this.field : "")), req = map.get(ctx ? ctx + ":" + key : key), self = this; return promisfy(req).then(function (res) { let result = []; if (!res || !res.length) return result; if (resolve) { if (!limit && !offset && 1 === res.length) { return res[0]; } for (let i = 0, arr; i < res.length; i++) { if ((arr = res[i]) && arr.length) { if (offset >= arr.length) { offset -= arr.length; continue; } const end = limit ? offset + Math.min(arr.length - offset, limit) : arr.length; for (let j = offset; j < end; j++) { result.push(arr[j]); } offset = 0; if (result.length === limit) { break; } } } return enrich ? self.enrich(result) : result; } else { return res; } }); }; /** * @param {!string} tag * @param {number=} limit * @param {number=} offset * @param {boolean=} enrich * @return {!Promise} */ IdxDB.prototype.tag = function (tag, limit = 0, offset = 0, enrich = !1) { const transaction = this.db.transaction("tag" + (this.field ? ":" + this.field : ""), "readonly"), map = transaction.objectStore("tag" + (this.field ? ":" + this.field : "")), req = map.get(tag), self = this; return promisfy(req).then(function (ids) { if (!ids || !ids.length || offset >= ids.length) return []; if (!limit && !offset) return ids; const result = ids.slice(offset, offset + limit); return enrich ? self.enrich(result) : result; }); }; /** * @param {SearchResults} ids * @return {!Promise} */ IdxDB.prototype.enrich = function (ids) { if ("object" != typeof ids) { ids = [ids]; } const transaction = this.db.transaction("reg", "readonly"), map = transaction.objectStore("reg"), promises = []; for (let i = 0; i < ids.length; i++) { promises[i] = promisfy(map.get(ids[i])); } return Promise.all(promises).then(function (docs) { for (let i = 0; i < docs.length; i++) { docs[i] = { id: ids[i], doc: docs[i] ? JSON.parse(docs[i]) : null }; } return docs; }); }; /** * @param {number|string} id * @return {!Promise} */ IdxDB.prototype.has = function (id) { const transaction = this.db.transaction("reg", "readonly"), map = transaction.objectStore("reg"), req = map.getKey(id); return promisfy(req).then(function (result) { return !!result; }); }; IdxDB.prototype.search = null; IdxDB.prototype.info = function () {}; /** * @param {!string} ref * @param {!string} modifier * @param {!Function} task */ IdxDB.prototype.transaction = function (ref, modifier, task) { const key = ref + ("reg" !== ref ? this.field ? ":" + this.field : "" : ""); /** * @type {IDBObjectStore} */ let store = this.trx[key + ":" + modifier]; if (store) return task.call(this, store); let transaction = this.db.transaction(key, modifier); /** * @type {IDBObjectStore} */ this.trx[key + ":" + modifier] = store = transaction.objectStore(key); const promise = task.call(this, store); this.trx[key + ":" + modifier] = null; return promisfy(transaction).finally(function () { return promise; }); }; IdxDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { /** @dict */ task = tasks[i]; if (task.del) { removals.push(task.del); } } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } await this.transaction("map", "readwrite", function (store) { for (const item of flexsearch.map) { const key = item[0], value = item[1]; if (!value.length) continue; store.get(key).onsuccess = function () { let result = this.result, changed; if (result && result.length) { const maxlen = Math.max(result.length, value.length); for (let i = 0, res, val; i < maxlen; i++) { val = value[i]; if (val && val.length) { res = result[i]; if (res && res.length) { for (let j = 0; j < val.length; j++) { res.push(val[j]); } changed = 1; } else { result[i] = val; changed = 1; } } } } else { result = value; changed = 1; } changed && store.put(result, key); }; } }); await this.transaction("ctx", "readwrite", function (store) { for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], value = item[1]; if (!value.length) continue; store.get(ctx_key + ":" + key).onsuccess = function () { let result = this.result, changed; if (result && result.length) { const maxlen = Math.max(result.length, value.length); for (let i = 0, res, val; i < maxlen; i++) { val = value[i]; if (val && val.length) { res = result[i]; if (res && res.length) { for (let j = 0; j < val.length; j++) { res.push(val[j]); } changed = 1; } else { result[i] = val; changed = 1; } } } } else { result = value; changed = 1; } changed && store.put(result, ctx_key + ":" + key); }; } } }); if (flexsearch.store) { await this.transaction("reg", "readwrite", function (store) { for (const item of flexsearch.store) { const id = item[0], doc = item[1]; store.put("object" == typeof doc ? JSON.stringify(doc) : 1, id); } }); } else if (!flexsearch.bypass) { await this.transaction("reg", "readwrite", function (store) { for (const id of flexsearch.reg.keys()) { store.put(1, id); } }); } if (flexsearch.tag) { await this.transaction("tag", "readwrite", function (store) { for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; store.get(tag).onsuccess = function () { let result = this.result; result = result && result.length ? result.concat(ids) : ids; store.put(result, tag); }; } }); } flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; /** * @param {IDBCursorWithValue} cursor * @param {Array} ids * @param {boolean=} _tag */ function handle(cursor, ids, _tag) { const arr = cursor.value; let changed, count = 0; for (let x = 0, result; x < arr.length; x++) { if (result = _tag ? arr : arr[x]) { for (let i = 0, pos, id; i < ids.length; i++) { id = ids[i]; pos = result.indexOf(id); if (0 <= pos) { changed = 1; if (1 < result.length) { result.splice(pos, 1); } else { arr[x] = []; break; } } } count += result.length; } if (_tag) break; } if (!count) { cursor.delete(); } else if (changed) { cursor.update(arr); } cursor.continue(); } /** * @param {Array} ids * @return {!Promise} */ IdxDB.prototype.remove = function (ids) { const self = this; if ("object" != typeof ids) { ids = [ids]; } return (/** @type {!Promise} */Promise.all([self.transaction("map", "readwrite", function (store) { store.openCursor().onsuccess = function () { const cursor = this.result; cursor && handle(cursor, ids); }; }), self.transaction("ctx", "readwrite", function (store) { store.openCursor().onsuccess = function () { const cursor = this.result; cursor && handle(cursor, ids); }; }), self.transaction("tag", "readwrite", function (store) { store.openCursor().onsuccess = function () { const cursor = this.result; cursor && handle(cursor, ids, !0); }; }), self.transaction("reg", "readwrite", function (store) { for (let i = 0; i < ids.length; i++) { store.delete(ids[i]); } })]) ); }; /** * @param {IDBRequest|IDBOpenDBRequest} req * @param {Function=} callback * @return {!Promise} */ function promisfy(req, callback) { return new Promise((resolve, reject) => { /** @this {IDBRequest|IDBOpenDBRequest} */ req.onsuccess = req.oncomplete = function () { callback && callback(this.result); callback = null; resolve(this.result); }; req.onerror = req.onblocked = reject; req = null; }); } ================================================ FILE: dist/module-debug/db/interface.js ================================================ /** * @interface */ export default function StorageInterface() {} StorageInterface.prototype.mount = async function () {}; StorageInterface.prototype.open = async function () {}; StorageInterface.prototype.close = function () {}; StorageInterface.prototype.destroy = async function () {}; StorageInterface.prototype.commit = async function () {}; /** * get results of a term "key" with optional context "ctx" * @param {!string} key * @param {string=} ctx * @param {number=} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @return {!Promise} */ StorageInterface.prototype.get = async function () {}; /** * get documents stored in index (enrich result) * @param {SearchResults} ids * @return {!Promise} */ StorageInterface.prototype.enrich = async function () {}; StorageInterface.prototype.has = async function () {}; StorageInterface.prototype.remove = async function () {}; StorageInterface.prototype.clear = async function () {}; /** * Perform the query intersection on database side * @type {Function|null} */ StorageInterface.prototype.search = async function () {}; /** * Give some information about the storage * @type {Function|null} */ StorageInterface.prototype.info = async function () {}; ================================================ FILE: dist/module-debug/db/mongodb/index.js ================================================ import { MongoClient } from "mongodb"; const defaults = { host: "localhost", port: "27017", user: null, pass: null }, VERSION = 1, fields = ["map", "ctx", "tag", "reg", "cfg"]; import StorageInterface from "../interface.js"; import { toArray } from "../../common.js"; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } let CLIENT, Index = Object.create(null); /** * @constructor * @implements StorageInterface */ export default function MongoDB(name, config = {}) { if (!this || this.constructor !== MongoDB) { return new MongoDB(name, config); } if ("object" == typeof name) { config = name; name = name.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = "flexsearch" + (name ? "-" + sanitize(name) : ""); this.field = config.field ? "-" + sanitize(config.field) : ""; this.type = config.type || ""; this.db = config.db || Index[this.id] || CLIENT || null; this.trx = !1; this.support_tag_search = !0; Object.assign(defaults, config); this.db && delete defaults.db; } MongoDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; async function createCollection(db, ref, field) { switch (ref) { case "map": await db.createCollection("map" + field); await db.collection("map" + field).createIndex({ key: 1 }); await db.collection("map" + field).createIndex({ id: 1 }); break; case "ctx": await db.createCollection("ctx" + field); await db.collection("ctx" + field).createIndex({ ctx: 1, key: 1 }); await db.collection("ctx" + field).createIndex({ id: 1 }); break; case "tag": await db.createCollection("tag" + field); await db.collection("tag" + field).createIndex({ tag: 1 }); await db.collection("tag" + field).createIndex({ id: 1 }); break; case "reg": await db.createCollection("reg"); await db.collection("reg").createIndex({ id: 1 }); break; case "cfg": await db.createCollection("cfg" + field); } } MongoDB.prototype.open = async function () { if (!this.db) { if (!(this.db = Index[this.id])) { if (!(this.db = CLIENT)) { let url = defaults.url; if (!url) { url = defaults.user ? `mongodb://${defaults.user}:${defaults.pass}@${defaults.host}:${defaults.port}` : `mongodb://${defaults.host}:${defaults.port}`; } this.db = CLIENT = new MongoClient(url); await this.db.connect(); } } } if (this.db.db) { this.db = Index[this.id] = this.db.db(this.id); } const collections = await this.db.listCollections().toArray(); for (let i = 0, found; i < fields.length; i++) { found = !1; for (let j = 0; j < collections.length; j++) { if (collections[j].name === fields[i] + ("reg" !== fields[i] ? this.field : "")) { found = !0; break; } } if (!found) { await createCollection(this.db, fields[i], this.field); } } return this.db; }; MongoDB.prototype.close = function () { this.db = CLIENT = null; Index[this.id] = null; return this; }; MongoDB.prototype.destroy = function () { return Promise.all([this.db.dropCollection("map" + this.field), this.db.dropCollection("ctx" + this.field), this.db.dropCollection("tag" + this.field), this.db.dropCollection("cfg" + this.field), this.db.dropCollection("reg")]); }; async function clear(ref) { await this.db.dropCollection(ref); await createCollection(this.db, ref, this.field); } MongoDB.prototype.clear = function () { return Promise.all([clear.call(this, "map" + this.field), clear.call(this, "ctx" + this.field), clear.call(this, "tag" + this.field), clear.call(this, "cfg" + this.field), clear.call(this, "reg")]); }; function create_result(rows, resolve, enrich) { const _id = rows[0] && "undefined" != typeof rows[0]._id; if (resolve) { if (!enrich || _id) for (let i = 0, row; i < rows.length; i++) { row = rows[i]; if (enrich) { const id = row._id; delete row._id; row.id = id; } else { rows[i] = _id ? row._id : row.id; } } return rows; } else { const arr = []; for (let i = 0, row, res; i < rows.length; i++) { row = rows[i]; res = row.res; (arr[res] || (arr[res] = [])).push(_id ? row._id : row.id); } return arr; } } MongoDB.prototype.get = async function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1, tags) { let rows, params = ctx ? { ctx, key } : { key }; if (!enrich && !tags) { const stmt = { projection: { _id: 0, res: 1, id: 1 } }; limit && (stmt.limit = limit); offset && (stmt.skip = offset); rows = await this.db.collection((ctx ? "ctx" : "map") + this.field).find(params, stmt).toArray(); } else { const project = { _id: 0, id: 1 }, stmt = [{ $match: params }]; if (!resolve) { project.res = 1; } if (enrich) { project.doc = "$doc.doc"; stmt.push({ $lookup: { from: "reg", localField: "id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: !0 } }); } if (tags) { const match = {}; for (let i = 0, count = 1; i < tags.length; i += 2) { project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push({ $lookup: { from: "tag-" + sanitize(tags[i]), localField: "id", foreignField: "id", as: "tag" + count } }); count++; } stmt.push({ $project: project }, { $match: match }, { $project: { id: 1, doc: 1 } }); } else { stmt.push({ $project: project }); } stmt.push({ $sort: { res: 1 } }); limit && stmt.push({ $limit: limit }); offset && stmt.push({ $skip: offset }); rows = []; const result = await this.db.collection((ctx ? "ctx" : "map") + this.field).aggregate(stmt); while (!0) { const row = await result.next(); if (row) rows.push(row);else break; } } return create_result(rows, resolve, enrich); }; MongoDB.prototype.tag = async function (tag, limit = 0, offset = 0, enrich = !1) { let rows; if (!enrich) { const stmt = { projection: { _id: 0, id: 1 } }; limit && (stmt.limit = limit); offset && (stmt.skip = offset); rows = await this.db.collection("tag" + this.field).find({ tag }, stmt).toArray(); } else { const stmt = [{ $match: { tag } }]; limit && stmt.push({ $limit: limit }); offset && stmt.push({ $skip: offset }); stmt.push({ $lookup: { from: "reg", localField: "id", foreignField: "id", as: "doc" } }, { $project: { _id: 0, id: 1, doc: "$doc.doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: !0 } }); rows = []; const result = await this.db.collection("tag" + this.field).aggregate(stmt); while (!0) { const row = await result.next(); if (row) rows.push(row);else break; } } create_result(rows, !0, enrich); }; MongoDB.prototype.enrich = function (ids) { if ("object" != typeof ids) { ids = [ids]; } return this.db.collection("reg").find({ id: { $in: ids } }, { projection: { _id: 0, id: 1, doc: 1 } }).toArray(); }; MongoDB.prototype.has = function (id) { return this.db.collection("reg").countDocuments({ id }, { limit: 1 }).then(function (result) { return !!result; }); }; MongoDB.prototype.search = async function (flexsearch, query, limit = 100, offset = 0, suggest = !1, resolve = !0, enrich = !1, tags) { let result = [], rows; if (1 < query.length && flexsearch.depth) { let params = [], keyword = query[0], term; for (let i = 1; i < query.length; i++) { term = query[i]; const swap = flexsearch.bidirectional && term > keyword; params.push({ ctx: swap ? term : keyword, key: swap ? keyword : term }); keyword = term; } const project = { _id: 1 }; if (!resolve) project.res = 1; if (enrich) project.doc = 1; const stmt = [{ $match: { $or: params } }, { $group: { _id: "$id", count: { $sum: 1 }, res: suggest ? { $sum: "$res" } : { $sum: "$res" } } }]; suggest || stmt.push({ $match: { count: query.length - 1 } }); if (enrich) { project.doc = "$doc.doc"; stmt.push({ $lookup: { from: "reg", localField: "_id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: !0 } }); } if (tags) { const match = {}; for (let i = 0, count = 1; i < tags.length; i += 2) { project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push({ $lookup: { from: "tag-" + sanitize(tags[i]), localField: "_id", foreignField: "id", as: "tag" + count } }); count++; } stmt.push({ $match: match }); } stmt.push({ $sort: suggest ? { count: -1, res: 1 } : { res: 1 } }); limit && stmt.push({ $limit: limit }); offset && stmt.push({ $skip: offset }); stmt.push({ $project: project }); rows = await this.db.collection("ctx" + this.field).aggregate(stmt); } else { const project = { _id: 1 }; if (!resolve) project.res = 1; if (enrich) project.doc = 1; const stmt = [{ $match: { key: { $in: query } } }, { $group: { _id: "$id", count: { $sum: 1 }, res: suggest ? { $sum: "$res" } : { $sum: "$res" } } }]; suggest || stmt.push({ $match: { count: query.length } }); if (enrich) { project.doc = "$doc.doc"; stmt.push({ $lookup: { from: "reg", localField: "_id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: !0 } }); } if (tags) { const match = {}; for (let i = 0, count = 1; i < tags.length; i += 2) { project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push({ $lookup: { from: "tag-" + sanitize(tags[i]), localField: "_id", foreignField: "id", as: "tag" + count } }); count++; } stmt.push({ $match: match }); } stmt.push({ $sort: suggest ? { count: -1, res: 1 } : { res: 1 } }); limit && stmt.push({ $limit: limit }); offset && stmt.push({ $skip: offset }); stmt.push({ $project: project }); rows = await this.db.collection("map" + this.field).aggregate(stmt); } while (!0) { const row = await rows.next(); if (row) { if (resolve && enrich) { row.id = row._id; delete row._id; } result.push(row); } else break; } return create_result(result, resolve, enrich); }; MongoDB.prototype.info = function () {}; MongoDB.prototype.transaction = function (task) { return task.call(this); }; MongoDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { /** @dict */ task = tasks[i]; if (task.del) { removals.push(task.del); } else if (task.ins) {} } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } const promises = []; if (flexsearch.map.size) { let data = []; for (const item of flexsearch.map) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { this.type || (this.type = typeof ids[0]); for (let j = 0; j < ids.length; j++) { data.push({ key: key, res: i, id: ids[j] }); } } } } if (data.length) { promises.push(this.db.collection("map" + this.field).insertMany(data)); } } if (flexsearch.ctx.size) { let data = []; for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { for (let j = 0; j < ids.length; j++) { data.push({ ctx: ctx_key, key: key, res: i, id: ids[j] }); } } } } } if (data.length) { promises.push(this.db.collection("ctx" + this.field).insertMany(data)); } } if (flexsearch.tag) { let data = []; for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; for (let j = 0; j < ids.length; j++) { data.push({ tag, id: ids[j] }); } } if (data.length) { promises.push(this.db.collection("tag" + this.field).insertMany(data)); } } let data = []; if (flexsearch.store) { for (const item of flexsearch.store.entries()) { const id = item[0], doc = item[1]; data.push({ id, doc }); } } else if (!flexsearch.bypass) { for (const id of flexsearch.reg.keys()) { data.push({ id }); } } if (data.length) { promises.push(this.db.collection("reg").insertMany(data)); } promises.length && (await Promise.all(promises)); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; MongoDB.prototype.remove = function (ids) { if (!ids && 0 !== ids) return; if ("object" != typeof ids) { ids = [ids]; } return Promise.all([this.db.collection("map" + this.field).deleteMany({ id: { $in: ids } }), this.db.collection("ctx" + this.field).deleteMany({ id: { $in: ids } }), this.db.collection("tag" + this.field).deleteMany({ id: { $in: ids } }), this.db.collection("reg").deleteMany({ id: { $in: ids } })]); }; ================================================ FILE: dist/module-debug/db/postgres/index.js ================================================ import pg_promise from "pg-promise"; import StorageInterface from "../interface.js"; import { concat, toArray } from "../../common.js"; const defaults = { schema: "flexsearch", user: "postgres", pass: "postgres", name: "postgres", host: "localhost", port: "5432" }, pgp = pg_promise(), VERSION = 1, MAXIMUM_QUERY_VARS = 16000, fields = ["map", "ctx", "reg", "tag", "cfg"], types = { text: "text", char: "text", varchar: "text", string: "text", number: "int", numeric: "int", integer: "int", smallint: "int", tinyint: "int", mediumint: "int", int: "int", int8: "int", uint8: "int", int16: "int", uint16: "int", int32: "int", uint32: "bigint", int64: "bigint", bigint: "bigint" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } let DB, TRX; /** * @constructor * @implements StorageInterface */ export default function PostgresDB(name, config = {}) { if (!this || this.constructor !== PostgresDB) { return new PostgresDB(name, config); } if ("object" == typeof name) { config = name; name = config.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = (config.schema ? sanitize(config.schema) : defaults.schema) + (name ? "_" + sanitize(name) : ""); this.field = config.field ? "_" + sanitize(config.field) : ""; this.type = config.type ? types[config.type.toLowerCase()] : "text"; this.support_tag_search = !0; if (!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); this.db = DB || (DB = config.db || null); Object.assign(defaults, config); this.db && delete defaults.db; } PostgresDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; PostgresDB.prototype.open = async function () { if (!this.db) { this.db = DB || (DB = pgp(`postgres://${defaults.user}:${encodeURIComponent(defaults.pass)}@${defaults.host}:${defaults.port}/${defaults.name}`)); } const exist = await this.db.oneOrNone(` SELECT EXISTS ( SELECT 1 FROM information_schema.schemata WHERE schema_name = '${this.id}' ); `); if (!exist || !exist.exists) { await this.db.none(`CREATE SCHEMA IF NOT EXISTS ${this.id};`); } for (let i = 0; i < fields.length; i++) { const exist = await this.db.oneOrNone(` SELECT EXISTS ( SELECT 1 FROM pg_tables WHERE schemaname = '${this.id}' AND tablename = '${fields[i] + ("reg" !== fields[i] ? this.field : "")}' ); `); if (exist && exist.exists) continue; const type = "text" === this.type ? "varchar(128)" : this.type; switch (fields[i]) { case "map": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.map${this.field}( key varchar(128) NOT NULL, res smallint NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_map_index${this.field} ON ${this.id}.map${this.field} (key); CREATE INDEX IF NOT EXISTS ${this.id}_map_id${this.field} ON ${this.id}.map${this.field} (id); `); break; case "ctx": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.ctx${this.field}( ctx varchar(128) NOT NULL, key varchar(128) NOT NULL, res smallint NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_ctx_index${this.field} ON ${this.id}.ctx${this.field} (ctx, key); CREATE INDEX IF NOT EXISTS ${this.id}_ctx_id${this.field} ON ${this.id}.ctx${this.field} (id); `); break; case "tag": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.tag${this.field}( tag varchar(128) NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_tag_index${this.field} ON ${this.id}.tag${this.field} (tag); CREATE INDEX IF NOT EXISTS ${this.id}_tag_id${this.field} ON ${this.id}.tag${this.field} (id); `); break; case "reg": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.reg( id ${type} NOT NULL CONSTRAINT ${this.id}_reg_pk PRIMARY KEY, doc text DEFAULT NULL ); `).catch(() => {}); break; case "cfg": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.cfg${this.field}( cfg text NOT NULL ); `); break; } } return this.db; }; PostgresDB.prototype.close = function () { this.db = null; return this; }; PostgresDB.prototype.destroy = function () { return this.db.none(` DROP TABLE IF EXISTS ${this.id}.map${this.field}; DROP TABLE IF EXISTS ${this.id}.ctx${this.field}; DROP TABLE IF EXISTS ${this.id}.tag${this.field}; DROP TABLE IF EXISTS ${this.id}.cfg${this.field}; DROP TABLE IF EXISTS ${this.id}.reg; `); }; PostgresDB.prototype.clear = function () { return this.db.none(` TRUNCATE TABLE ${this.id}.map${this.field}; TRUNCATE TABLE ${this.id}.ctx${this.field}; TRUNCATE TABLE ${this.id}.tag${this.field}; TRUNCATE TABLE ${this.id}.cfg${this.field}; TRUNCATE TABLE ${this.id}.reg; `); }; function create_result(rows, resolve, enrich) { if (resolve) { for (let i = 0; i < rows.length; i++) { if (enrich) { if (rows[i].doc) { rows[i].doc = JSON.parse(rows[i].doc); } } else { rows[i] = rows[i].id; } } return rows; } else { const arr = []; for (let i = 0, row; i < rows.length; i++) { row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id); } return arr; } } PostgresDB.prototype.get = function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1, tags) { let rows, stmt = '', params = ctx ? [ctx, key] : [key], table = this.id + (ctx ? ".ctx" : ".map") + this.field; if (tags) { for (let i = 0, count = params.length + 1; i < tags.length; i += 2) { stmt += ` AND ${table}.id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = $${count++})`; params.push(tags[i + 1]); } } if (ctx) { rows = this.db.any(` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE ctx = $1 AND key = $2 ${stmt} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, params); } else { rows = this.db.any(` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE key = $1 ${stmt} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, params); } return rows.then(function (rows) { return create_result(rows, resolve, enrich); }); }; PostgresDB.prototype.tag = function (tag, limit = 0, offset = 0, enrich = !1) { const table = this.id + ".tag" + this.field, promise = this.db.any(` SELECT ${table}.id ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN ${this.id}.reg ON ${this.id}.reg.id = ${table}.id ` : ""} WHERE tag = $1 ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""}`, [tag]); enrich || promise.then(function (rows) { return create_result(rows, !0, !1); }); return promise; }; PostgresDB.prototype.enrich = async function (ids) { let result = []; if ("object" != typeof ids) { ids = [ids]; } for (let count = 0; count < ids.length;) { const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; let stmt = ""; for (let i = 1; i <= chunk.length; i++) { stmt += (stmt ? "," : "") + "$" + i; } const res = await this.db.any(` SELECT id, doc FROM ${this.id}.reg WHERE id IN (${stmt})`, ids); if (res && res.length) { for (let i = 0, doc; i < res.length; i++) { if (doc = res[i].doc) { res[i].doc = JSON.parse(doc); } } result.push(res); } } return 1 === result.length ? result[0] : 1 < result.length ? concat(result) : result; }; PostgresDB.prototype.has = function (id) { return this.db.oneOrNone("SELECT EXISTS(SELECT 1 FROM " + this.id + ".reg WHERE id = $1)", [id]).then(function (result) { return !!(result && result.exists); }); }; PostgresDB.prototype.search = function (flexsearch, query, limit = 100, offset = 0, suggest = !1, resolve = !0, enrich = !1, tags) { let rows; if (1 < query.length && flexsearch.depth) { let where = "", params = [], keyword = query[0], term, count = 1; for (let i = 1; i < query.length; i++) { term = query[i]; const swap = flexsearch.bidirectional && term > keyword; where += (where ? " OR " : "") + `(ctx = $${count++} AND key = $${count++})`; params.push(swap ? term : keyword, swap ? keyword : term); keyword = term; } if (tags) { where = "(" + where + ")"; for (let i = 0; i < tags.length; i += 2) { where += ` AND id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = $${count++})`; params.push(tags[i + 1]); } } rows = this.db.any(` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM ${this.id}.ctx${this.field} WHERE ${where} GROUP BY id ) as r ${enrich ? ` LEFT JOIN ${this.id}.reg ON ${this.id}.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + (query.length - 1)} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params); } else { let where = "", count = 1, query_length = query.length; for (let i = 0; i < query_length; i++) { where += (where ? "," : "") + "$" + count++; } where = "key " + (1 < query_length ? "IN (" + where + ")" : "= " + where); if (tags) { where = "(" + where + ")"; for (let i = 0; i < tags.length; i += 2) { where += ` AND id IN (SELECT id FROM ${this.id}.tag_${sanitize(tags[i])} WHERE tag = $${count++})`; query.push(tags[i + 1]); } } rows = this.db.any(` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM ${this.id}.map${this.field} WHERE ${where} GROUP BY id ) as r ${enrich ? ` LEFT JOIN ${this.id}.reg ON ${this.id}.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + query_length} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, query); } return rows.then(function (rows) { return create_result(rows, resolve, enrich); }); }; PostgresDB.prototype.info = function () {}; PostgresDB.prototype.transaction = function (task) { const self = this; return this.db.tx(function (trx) { return task.call(self, trx); }); }; PostgresDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { /** @dict */ task = tasks[i]; if (task.del) { removals.push(task.del); } else if (task.ins) {} } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } await this.transaction(function (trx) { const batch = []; if (flexsearch.store) { let data = [], stmt = new pgp.helpers.ColumnSet(["id", "doc"], { table: this.id + ".reg" }); for (const item of flexsearch.store.entries()) { const id = item[0], doc = item[1]; data.push({ id, doc: doc && JSON.stringify(doc) }); if (data.length === MAXIMUM_QUERY_VARS) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); data = []; } } if (data.length) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); } } else if (!flexsearch.bypass) { let data = [], stmt = new pgp.helpers.ColumnSet(["id"], { table: this.id + ".reg" }); for (const id of flexsearch.reg.keys()) { data.push({ id }); if (data.length === MAXIMUM_QUERY_VARS) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); data = []; } } if (data.length) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); } } if (flexsearch.map.size) { let data = [], stmt = new pgp.helpers.ColumnSet(["key", "res", "id"], { table: this.id + ".map" + this.field }); for (const item of flexsearch.map) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { for (let j = 0; j < ids.length; j++) { data.push({ key: key, res: i, id: ids[j] }); if (data.length === MAXIMUM_QUERY_VARS) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); data = []; } } } } } if (data.length) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); } } if (flexsearch.ctx.size) { let data = [], stmt = new pgp.helpers.ColumnSet(["ctx", "key", "res", "id"], { table: this.id + ".ctx" + this.field }); for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { for (let j = 0; j < ids.length; j++) { data.push({ ctx: ctx_key, key: key, res: i, id: ids[j] }); if (data.length === MAXIMUM_QUERY_VARS) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); data = []; } } } } } } if (data.length) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); } } if (flexsearch.tag) { let data = [], stmt = new pgp.helpers.ColumnSet(["tag", "id"], { table: this.id + ".tag" + this.field }); for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; for (let j = 0; j < ids.length; j++) { data.push({ tag, id: ids[j] }); if (data.length === MAXIMUM_QUERY_VARS) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); data = []; } } } if (data.length) { let insert = pgp.helpers.insert(data, stmt); batch.push(trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2'))); } } if (batch.length) { return trx.batch(batch); } }); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; PostgresDB.prototype.remove = function (ids) { if (!ids && 0 !== ids) { return; } if ("object" != typeof ids) { ids = [ids]; } if (!ids.length) { return; } return this.transaction(function (trx) { ids = [ids]; return trx.batch([trx.none({ text: "DELETE FROM " + this.id + ".map" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".tag" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".reg WHERE id = ANY ($1)", rowMode: "array" }, ids)]); }); }; ================================================ FILE: dist/module-debug/db/redis/index.js ================================================ import { createClient } from "redis"; import StorageInterface from "../interface.js"; import { toArray } from "../../common.js"; const VERSION = 1, fields = ["map", "ctx", "reg", "tag", "doc", "cfg"], defaults = { host: "localhost", port: "6379", user: null, pass: null }; let DB, TRX; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } /** * @constructor * @implements StorageInterface */ export default function RedisDB(name, config = {}) { if (!this || this.constructor !== RedisDB) { return new RedisDB(name, config); } if ("object" == typeof name) { config = name; name = name.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = (name ? sanitize(name) : "flexsearch") + "|"; this.field = config.field ? "-" + sanitize(config.field) : ""; this.type = config.type || ""; this.fastupdate = !0; this.db = config.db || DB || null; this.support_tag_search = !0; this.resolution = 9; this.resolution_ctx = 9; Object.assign(defaults, config); this.db && delete defaults.db; } RedisDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } flexsearch.db = this; this.resolution = flexsearch.resolution; this.resolution_ctx = flexsearch.resolution_ctx; return this.open(); }; RedisDB.prototype.open = async function () { if (this.db) { return this.db; } if (DB) { return this.db = DB; } let url = defaults.url; if (!url) { url = defaults.user ? `redis://${defaults.user}:${defaults.pass}@${defaults.host}:${defaults.port}` : `redis://${defaults.host}:${defaults.port}`; } return this.db = DB = await createClient(url).on("error", err => console.error(err)).connect(); }; RedisDB.prototype.close = async function () { DB && (await this.db.disconnect()); this.db = DB = null; return this; }; RedisDB.prototype.destroy = function () { return this.clear(!0); }; RedisDB.prototype.clear = function (destroy = !1) { if (!this.id) return; const self = this; function unlink(keys) { return keys.length && self.db.unlink(keys); } return Promise.all([this.db.keys(this.id + "map" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "ctx" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "tag" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "ref" + (destroy ? "" : this.field) + "*").then(unlink), unlink([this.id + "cfg" + (destroy ? "*" : this.field), this.id + "doc", this.id + "reg"])]); }; function create_result(range, type, resolve, enrich, resolution) { if (resolve) { for (let i = 0, tmp, id; i < range.length; i++) { tmp = range[i]; id = "number" === type ? parseInt(tmp.value || tmp, 10) : tmp.value || tmp; range[i] = enrich ? { id, doc: tmp.doc } : id; } return range; } else { let result = []; for (let i = 0, tmp, id, score; i < range.length; i++) { tmp = range[i]; id = "number" === type ? parseInt(tmp.value || tmp, 10) : tmp.value || tmp; score = Math.max(resolution - tmp.score, 0); result[score] || (result[score] = []); result[score].push(id); } return result; } } RedisDB.prototype.get = function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1, tags) { if (tags) { const query = ctx ? [ctx, key] : [key]; return this.search({ depth: !!ctx }, query, limit, offset, !1, resolve, enrich, tags); } const type = this.type, self = this; let result; if (ctx) { result = this.db[resolve ? "zRange" : "zRangeWithScores"](this.id + "ctx" + this.field + ":" + ctx + ":" + key, "" + offset, "" + (offset + limit - 1), { REV: !0 }); } else { result = this.db[resolve ? "zRange" : "zRangeWithScores"](this.id + "map" + this.field + ":" + key, "" + offset, "" + (offset + limit - 1), { REV: !0 }); } return result.then(async function (range) { if (!range.length) return range; if (enrich) range = await self.enrich(range); return create_result(range, type, resolve, enrich, ctx ? self.resolution_ctx : self.resolution); }); }; RedisDB.prototype.tag = function (tag, limit = 0, offset = 0, enrich = !1) { const self = this; return this.db.sMembers(this.id + "tag" + this.field + ":" + tag).then(function (ids) { if (!ids || !ids.length || offset >= ids.length) return []; if (!limit && !offset) return ids; const result = ids.slice(offset, offset + limit); return enrich ? self.enrich(result) : result; }); }; RedisDB.prototype.enrich = function (ids) { if ("object" != typeof ids) { ids = [ids]; } return this.db.hmGet(this.id + "doc", "number" === this.type ? ids.map(i => "" + i) : ids).then(function (res) { for (let i = 0; i < res.length; i++) { res[i] = { id: ids[i], doc: res[i] && JSON.parse(res[i]) }; } return res; }); }; RedisDB.prototype.has = function (id) { return this.db.sIsMember(this.id + "reg", "" + id).then(function (res) { return !!res; }); }; RedisDB.prototype.search = function (flexsearch, query, limit = 100, offset = 0, suggest = !1, resolve = !0, enrich = !1, tags) { const ctx = 1 < query.length && flexsearch.depth; let result, params = [], weights = []; if (ctx) { const key = this.id + "ctx" + this.field + ":"; let keyword = query[0], term; for (let i = 1, swap; i < query.length; i++) { term = query[i]; swap = flexsearch.bidirectional && term > keyword; params.push(key + (swap ? term : keyword) + ":" + (swap ? keyword : term)); weights.push(1); keyword = term; } } else { const key = this.id + "map" + this.field + ":"; for (let i = 0; i < query.length; i++) { params.push(key + query[i]); weights.push(1); } } query = params; const type = this.type; let key = this.id + "tmp:" + Math.random(); if (suggest) { const multi = this.db.multi(); multi.zInterStore(key, query, { AGGREGATE: "SUM" }); query.push(key); weights.push(query.length); multi.zUnionStore(key, query, { WEIGHTS: weights, AGGREGATE: "SUM" }); if (tags) { query = [key]; for (let i = 0; i < tags.length; i += 2) { query.push(this.id + "tag-" + sanitize(tags[i]) + ":" + tags[i + 1]); } multi.zInterStore(key, query, { AGGREGATE: "MAX" }); } result = multi[resolve ? "zRange" : "zRangeWithScores"](key, "" + offset, "" + (offset + limit - 1), { REV: !0 }).unlink(key).exec(); } else { if (tags) for (let i = 0; i < tags.length; i += 2) { query.push(this.id + "tag-" + sanitize(tags[i]) + ":" + tags[i + 1]); } result = this.db.multi().zInterStore(key, query, { AGGREGATE: "MAX" })[resolve ? "zRange" : "zRangeWithScores"](key, "" + offset, "" + (offset + limit - 1), { REV: !0 }).unlink(key).exec(); } const self = this; return result.then(async function (range) { range = suggest && tags ? range[3] : range[suggest ? 2 : 1]; if (!range.length) return range; if (enrich) range = await self.enrich(range); return create_result(range, type, resolve, enrich, ctx ? self.resolution_ctx : self.resolution); }); }; RedisDB.prototype.info = function () {}; RedisDB.prototype.transaction = function (task, callback) { if (TRX) { return task.call(this, TRX); } TRX = this.db.multi(); let promise1 = task.call(this, TRX), promise2 = TRX.exec(); TRX = null; return Promise.all([promise1, promise2]).then(function () { callback && callback(); }); }; RedisDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { /** @dict */ task = tasks[i]; if (task.del) { removals.push(task.del); } else if (task.ins) {} } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } await this.transaction(function (trx) { let refs = new Map(); for (const item of flexsearch.map) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { let result = []; for (let j = 0; j < ids.length; j++) { result.push({ score: this.resolution - i, value: "" + ids[j] }); } if ("number" == typeof ids[0]) { this.type = "number"; } const ref = this.id + "map" + this.field + ":" + key; trx.zAdd(ref, result); if (this.fastupdate) for (let j = 0, id; j < ids.length; j++) { id = ids[j]; let tmp = refs.get(id); tmp || refs.set(id, tmp = []); tmp.push(ref); } } } } if (this.fastupdate) for (const item of refs) { const key = item[0], value = item[1]; trx.sAdd(this.id + "ref" + this.field + ":" + key, value); } refs = new Map(); for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { let result = []; for (let j = 0; j < ids.length; j++) { result.push({ score: this.resolution_ctx - i, value: "" + ids[j] }); } if ("number" == typeof ids[0]) { this.type = "number"; } const ref = this.id + "ctx" + this.field + ":" + ctx_key + ":" + key; trx.zAdd(ref, result); if (this.fastupdate) for (let j = 0, id; j < ids.length; j++) { id = ids[j]; let tmp = refs.get(id); tmp || refs.set(id, tmp = []); tmp.push(ref); } } } } } if (this.fastupdate) for (const item of refs) { const key = item[0], value = item[1]; trx.sAdd(this.id + "ref" + this.field + ":" + key, value); } if (flexsearch.store) { for (const item of flexsearch.store.entries()) { const id = item[0], doc = item[1]; doc && trx.hSet(this.id + "doc", "" + id, JSON.stringify(doc)); } } if (!flexsearch.bypass) { let ids = toArray(flexsearch.reg, !0); if (ids.length) { trx.sAdd(this.id + "reg", ids); } } if (flexsearch.tag) { for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; let result = []; if ("number" == typeof ids[0]) { for (let i = 0; i < ids.length; i++) { result[i] = "" + ids[i]; } } else { result = ids; } trx.sAdd(this.id + "tag" + this.field + ":" + tag, result); } } }); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; RedisDB.prototype.remove = function (ids) { if (!ids && 0 !== ids) { return; } if ("object" != typeof ids) { ids = [ids]; } if (!ids.length) { return; } return this.transaction(async function (trx) { while (ids.length) { let next; if (10000 < ids.length) { next = ids.slice(10000); ids = ids.slice(0, 10000); } if ("number" == typeof ids[0]) { for (let i = 0; i < ids.length; i++) { ids[i] = "" + ids[i]; } } const check = await this.db.smIsMember(this.id + "reg", ids); for (let i = 0, id; i < ids.length; i++) { if (!check[i]) continue; id = "" + ids[i]; if (this.fastupdate) { const ref = await this.db.sMembers(this.id + "ref" + this.field + ":" + id); if (ref) { for (let j = 0; j < ref.length; j++) { trx.zRem(ref[j], id); } trx.unlink(this.id + "ref" + this.field + ":" + id); } } trx.hDel(this.id + "doc", id); trx.sRem(this.id + "reg", id); } if (next) ids = next;else break; } }); }; ================================================ FILE: dist/module-debug/db/sqlite/index.js ================================================ import sqlite3 from "sqlite3"; import path from "path"; import StorageInterface from "../interface.js"; import { concat, toArray } from "../../common.js"; const VERSION = 1, MAXIMUM_QUERY_VARS = 16000, fields = ["map", "ctx", "reg", "tag", "cfg"], types = { text: "text", char: "text", varchar: "text", string: "text", number: "int", numeric: "int", integer: "int", smallint: "int", tinyint: "int", mediumint: "int", int: "int", int8: "int", uint8: "int", int16: "int", uint16: "int", int32: "int", uint32: "bigint", int64: "bigint", bigint: "bigint" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } const TRX = Object.create(null), Index = Object.create(null); /** * @constructor * @implements StorageInterface */ export default function SqliteDB(name, config = {}) { if (!this || this.constructor !== SqliteDB) { return new SqliteDB(name, config); } if ("object" == typeof name) { config = name; name = name.name; } if (!name) { console.info("Default storage space was used, because a name was not passed."); } this.id = config.path || (":memory:" === name ? name : "flexsearch" + (name ? "-" + sanitize(name) : "") + ".sqlite"); this.field = config.field ? "_" + sanitize(config.field) : ""; this.support_tag_search = !0; this.db = config.db || Index[this.id] || null; this.type = config.type ? types[config.type.toLowerCase()] : "string"; if (!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); } SqliteDB.prototype.mount = function (flexsearch) { if (flexsearch.index) { return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; SqliteDB.prototype.open = async function () { if (!this.db) { if (!(this.db = Index[this.id])) { let filepath = this.id; if (":memory:" !== filepath) { if ("/" !== filepath[0] && "\\" !== filepath[0]) { const dir = process.cwd(); filepath = path.join(dir, this.id); } } this.db = Index[this.id] = new sqlite3.Database(filepath); } } const db = this.db; for (let i = 0; i < fields.length; i++) { const exist = await this.promisfy({ method: "get", stmt: "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?) as exist", params: [fields[i] + ("reg" === fields[i] ? "" : this.field)] }); if (!exist || !exist.exist) { let stmt, stmt_index; switch (fields[i]) { case "map": stmt = ` CREATE TABLE IF NOT EXISTS main.map${this.field}( key TEXT NOT NULL, res INTEGER NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS map_key_index${this.field} ON map${this.field} (key); CREATE INDEX IF NOT EXISTS map_id_index${this.field} ON map${this.field} (id); `; break; case "ctx": stmt = ` CREATE TABLE IF NOT EXISTS main.ctx${this.field}( ctx TEXT NOT NULL, key TEXT NOT NULL, res INTEGER NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS ctx_key_index${this.field} ON ctx${this.field} (ctx, key); CREATE INDEX IF NOT EXISTS ctx_id_index${this.field} ON ctx${this.field} (id); `; break; case "tag": stmt = ` CREATE TABLE IF NOT EXISTS main.tag${this.field}( tag TEXT NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS tag_index${this.field} ON tag${this.field} (tag); CREATE INDEX IF NOT EXISTS tag_id_index${this.field} ON tag${this.field} (id); `; break; case "reg": stmt = ` CREATE TABLE IF NOT EXISTS main.reg( id ${this.type} NOT NULL CONSTRAINT reg_pk${this.field} PRIMARY KEY, doc TEXT DEFAULT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS reg_index ON reg (id); `; break; case "cfg": stmt = ` CREATE TABLE IF NOT EXISTS main.cfg${this.field} ( cfg TEXT NOT NULL ); `; break; } await new Promise(function (resolve, reject) { db.exec(stmt, function (err, rows) { if (err) return reject(err); stmt_index ? db.exec(stmt_index, function (err, rows) { if (err) return reject(err); resolve(rows); }) : resolve(rows); }); }); } } db.exec("PRAGMA optimize = 0x10002"); return db; }; SqliteDB.prototype.close = function () { this.db && this.db.close(); this.db = null; Index[this.id] = null; TRX[this.id] = null; return this; }; SqliteDB.prototype.destroy = function () { return this.transaction(function () { this.db.run("DROP TABLE IF EXISTS main.map" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.ctx" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.tag" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.cfg" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.reg;"); }); }; SqliteDB.prototype.clear = function () { return this.transaction(function () { this.db.run("DELETE FROM main.map" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.ctx" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.tag" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.cfg" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.reg WHERE 1;"); }); }; function create_result(rows, resolve, enrich) { if (resolve) { for (let i = 0; i < rows.length; i++) { if (enrich) { if (rows[i].doc) { rows[i].doc = JSON.parse(rows[i].doc); } } else { rows[i] = rows[i].id; } } return rows; } else { const arr = []; for (let i = 0, row; i < rows.length; i++) { row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id); } return arr; } } SqliteDB.prototype.get = function (key, ctx, limit = 0, offset = 0, resolve = !0, enrich = !1, tags) { let result, stmt = '', params = ctx ? [ctx, key] : [key], table = "main." + (ctx ? "ctx" : "map") + this.field; if (tags) { for (let i = 0; i < tags.length; i += 2) { stmt += ` AND ${table}.id IN (SELECT id FROM main.tag_${sanitize(tags[i])} WHERE tag = ?)`; params.push(tags[i + 1]); } } if (ctx) { result = this.promisfy({ method: "all", stmt: ` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${table}.id ` : ""} WHERE ctx = ? AND key = ? ${stmt} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params }); } else { result = this.promisfy({ method: "all", stmt: ` SELECT ${table}.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${table}.id ` : ""} WHERE key = ? ${stmt} ORDER BY res ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params }); } return result.then(function (rows) { return create_result(rows, resolve, enrich); }); }; SqliteDB.prototype.tag = function (tag, limit = 0, offset = 0, enrich = !1) { const table = "main.tag" + this.field, promise = this.promisfy({ method: "all", stmt: ` SELECT ${table}.id ${enrich ? ", doc" : ""} FROM ${table} ${enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${table}.id ` : ""} WHERE tag = ? ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params: [tag] }); enrich || promise.then(function (rows) { return create_result(rows, !0, !1); }); return promise; }; function build_params(length, single_param) { let stmt = single_param ? ",(?)" : ",?"; for (let i = 1; i < length;) { if (i <= length - i) { stmt += stmt; i *= 2; } else { stmt += stmt.substring(0, (length - i) * (single_param ? 4 : 2)); break; } } return stmt.substring(1); } SqliteDB.prototype.enrich = function (ids) { const result = [], promises = []; if ("object" != typeof ids) { ids = [ids]; } for (let count = 0; count < ids.length;) { const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids, stmt = build_params(chunk.length); count += chunk.length; promises.push(this.promisfy({ method: "all", stmt: `SELECT id, doc FROM main.reg WHERE id IN (${stmt})`, params: chunk })); } return Promise.all(promises).then(function (promises) { for (let i = 0, res; i < promises.length; i++) { res = promises[i]; if (res && res.length) { for (let i = 0, doc; i < res.length; i++) { if (doc = res[i].doc) { res[i].doc = JSON.parse(doc); } } result.push(res); } } return 1 === result.length ? result[0] : 1 < result.length ? concat(result) : result; }); }; SqliteDB.prototype.has = function (id) { return this.promisfy({ method: "get", stmt: `SELECT EXISTS(SELECT 1 FROM main.reg WHERE id = ?) as exist`, params: [id] }).then(function (result) { return !!(result && result.exist); }); }; SqliteDB.prototype.search = function (flexsearch, query, limit = 100, offset = 0, suggest = !1, resolve = !0, enrich = !1, tags) { let rows; if (1 < query.length && flexsearch.depth) { let stmt = "", params = [], keyword = query[0], term; for (let i = 1; i < query.length; i++) { term = query[i]; const swap = flexsearch.bidirectional && term > keyword; stmt += (stmt ? " OR " : "") + `(ctx = ? AND key = ?)`; params.push(swap ? term : keyword, swap ? keyword : term); keyword = term; } if (tags) { stmt = "(" + stmt + ")"; for (let i = 0; i < tags.length; i += 2) { stmt += ` AND id IN (SELECT id FROM main.tag_${sanitize(tags[i])} WHERE tag = ?)`; params.push(tags[i + 1]); } } rows = this.promisfy({ method: "all", stmt: ` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM main.ctx${this.field} WHERE ${stmt} GROUP BY id ) as r ${enrich ? ` LEFT JOIN main.reg ON main.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + (query.length - 1)} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params }); } else { let stmt = "", query_length = query.length; for (let i = 0; i < query_length; i++) { stmt += (stmt ? " OR " : "") + `key = ?`; } if (tags) { stmt = "(" + stmt + ")"; for (let i = 0; i < tags.length; i += 2) { stmt += ` AND id IN (SELECT id FROM main.tag_${sanitize(tags[i])} WHERE tag = ?)`; query.push(tags[i + 1]); } } rows = this.promisfy({ method: "all", stmt: ` SELECT r.id ${resolve ? "" : ", res"} ${enrich ? ", doc" : ""} FROM ( SELECT id, count(*) as count, ${suggest ? "SUM" : "SUM"}(res) as res FROM main.map${this.field} WHERE ${stmt} GROUP BY id ) as r ${enrich ? ` LEFT JOIN main.reg ON main.reg.id = r.id ` : ""} ${suggest ? "" : "WHERE count = " + query_length} ORDER BY ${suggest ? "count DESC, res" : "res"} ${limit ? "LIMIT " + limit : ""} ${offset ? "OFFSET " + offset : ""} `, params: query }); } return rows.then(function (rows) { return create_result(rows, resolve, enrich); }); }; SqliteDB.prototype.info = function () {}; SqliteDB.prototype.transaction = async function (task, callback) { if (TRX[this.id]) { return await task.call(this); } const db = this.db, self = this; return TRX[this.id] = new Promise(function (resolve, reject) { db.exec("PRAGMA optimize"); db.exec('PRAGMA busy_timeout = 5000'); db.exec("BEGIN"); db.parallelize(function () { task.call(self); }); db.exec("COMMIT", function (err, rows) { TRX[self.id] = null; if (err) return reject(err); callback && callback(rows); resolve(rows); db.exec("PRAGMA shrink_memory"); }); }); }; SqliteDB.prototype.commit = async function (flexsearch) { let tasks = flexsearch.commit_task, removals = [], inserts = []; flexsearch.commit_task = []; for (let i = 0, task; i < tasks.length; i++) { task = tasks[i]; if ("undefined" != typeof task.del) { removals.push(task.del); } else if ("undefined" != typeof task.ins) { inserts.push(task.ins); } } if (removals.length) { await this.remove(removals); } if (!flexsearch.reg.size) { return; } await this.transaction(function () { for (const item of flexsearch.map) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { let stmt = "", params = []; for (let j = 0; j < ids.length; j++) { stmt += (stmt ? "," : "") + "(?,?,?)"; params.push(key, i, ids[j]); if (j === ids.length - 1 || params.length + 3 > MAXIMUM_QUERY_VARS) { this.db.run("INSERT INTO main.map" + this.field + " (key, res, id) VALUES " + stmt, params); stmt = ""; params = []; } } } } } for (const ctx of flexsearch.ctx) { const ctx_key = ctx[0], ctx_value = ctx[1]; for (const item of ctx_value) { const key = item[0], arr = item[1]; for (let i = 0, ids; i < arr.length; i++) { if ((ids = arr[i]) && ids.length) { let stmt = "", params = []; for (let j = 0; j < ids.length; j++) { stmt += (stmt ? "," : "") + "(?,?,?,?)"; params.push(ctx_key, key, i, ids[j]); if (j === ids.length - 1 || params.length + 4 > MAXIMUM_QUERY_VARS) { this.db.run("INSERT INTO main.ctx" + this.field + " (ctx, key, res, id) VALUES " + stmt, params); stmt = ""; params = []; } } } } } } if (flexsearch.store) { let stmt = "", chunk = []; for (const item of flexsearch.store.entries()) { const id = item[0], doc = item[1]; stmt += (stmt ? "," : "") + "(?,?)"; chunk.push(id, "object" == typeof doc ? JSON.stringify(doc) : doc || null); if (chunk.length + 2 > MAXIMUM_QUERY_VARS) { this.db.run("INSERT INTO main.reg (id, doc) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); stmt = ""; chunk = []; } } if (chunk.length) { this.db.run("INSERT INTO main.reg (id, doc) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); } } else if (!flexsearch.bypass) { let ids = toArray(flexsearch.reg); for (let count = 0; count < ids.length;) { const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; const stmt = build_params(chunk.length, !0); this.db.run("INSERT INTO main.reg (id) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); } } if (flexsearch.tag) { let stmt = "", chunk = []; for (const item of flexsearch.tag) { const tag = item[0], ids = item[1]; if (!ids.length) continue; for (let i = 0; i < ids.length; i++) { stmt += (stmt ? "," : "") + "(?,?)"; chunk.push(tag, ids[i]); } if (chunk.length + 2 > MAXIMUM_QUERY_VARS) { this.db.run("INSERT INTO main.tag" + this.field + " (tag, id) VALUES " + stmt, chunk); stmt = ""; chunk = []; } } if (chunk.length) { this.db.run("INSERT INTO main.tag" + this.field + " (tag, id) VALUES " + stmt, chunk); } } }); if (inserts.length) { await this.cleanup(); } flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; SqliteDB.prototype.remove = function (ids) { if ("object" != typeof ids) { ids = [ids]; } let next; if (ids.length > MAXIMUM_QUERY_VARS) { next = ids.slice(MAXIMUM_QUERY_VARS); ids = ids.slice(0, MAXIMUM_QUERY_VARS); } const self = this; return this.transaction(function () { const stmt = build_params(ids.length); this.db.run("DELETE FROM main.map" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.ctx" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.tag" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.reg WHERE id IN (" + stmt + ")", ids); }).then(function (result) { return next ? self.remove(next) : result; }); }; SqliteDB.prototype.cleanup = function () { return this.transaction(function () { this.db.run("DELETE FROM main.map" + this.field + " WHERE ROWID IN (SELECT ROWID FROM (SELECT ROWID, row_number() OVER dupes AS count FROM main.map" + this.field + " _t WINDOW dupes AS (PARTITION BY id, key ORDER BY res) ) WHERE count > 1)"); this.db.run("DELETE FROM main.ctx" + this.field + " WHERE ROWID IN (SELECT ROWID FROM (SELECT ROWID, row_number() OVER dupes AS count FROM main.ctx" + this.field + " _t WINDOW dupes AS (PARTITION BY id, ctx, key ORDER BY res) ) WHERE count > 1)"); }); }; SqliteDB.prototype.promisfy = function (opt) { const db = this.db; return new Promise(function (resolve, reject) { db[opt.method](opt.stmt, opt.params || [], function (err, rows) { opt.callback && opt.callback(rows); err ? reject(err) : resolve(rows); }); }); }; ================================================ FILE: dist/module-debug/document/add.js ================================================ import { create_object, is_array, is_object, is_string, parse_simple } from "../common.js"; import { KeystoreArray } from "../keystore.js"; import Document from "../document.js"; /** * * @param id * @param content * @param {boolean=} _append * @this Document * @returns {Document|Promise} */ Document.prototype.add = function (id, content, _append) { if (is_object(id)) { content = id; id = parse_simple(content, this.key); } if (content && (id || 0 === id)) { if (!_append && this.reg.has(id)) { return this.update(id, content); } for (let i = 0, tree; i < this.field.length; i++) { tree = this.tree[i]; const index = this.index.get(this.field[i]); if ("function" == typeof tree) { const tmp = tree(content); if (tmp) { index.add(id, tmp, !0); } } else { const filter = tree._filter; if (filter && !filter(content)) { continue; } if (tree.constructor === String) { tree = ["" + tree]; } else if (is_string(tree)) { tree = [tree]; } add_index(content, tree, this.marker, 0, index, id, tree[0], _append); } } if (this.tag) { for (let x = 0; x < this.tagtree.length; x++) { let tree = this.tagtree[x], field = this.tagfield[x], ref = this.tag.get(field), dupes = create_object(), tags; if ("function" == typeof tree) { tags = tree(content); if (!tags) continue; } else { const filter = tree._filter; if (filter && !filter(content)) { continue; } if (tree.constructor === String) { tree = "" + tree; } tags = parse_simple(content, tree); } if (!ref || !tags) { ref || console.warn("Tag '" + field + "' was not found"); continue; } if (is_string(tags)) { tags = [tags]; } for (let i = 0, tag, arr; i < tags.length; i++) { tag = tags[i]; if (!dupes[tag]) { dupes[tag] = 1; let tmp = ref.get(tag); tmp ? arr = tmp : ref.set(tag, arr = []); if (!_append || ! /** @type {!Array|KeystoreArray} */arr.includes(id)) { if (2147483647 === arr.length) { const keystore = new KeystoreArray(arr); if (this.fastupdate) { for (let value of this.reg.values()) { if (value.includes(arr)) { value[value.indexOf(arr)] = keystore; } } } ref.set(tag, arr = keystore); } arr.push(id); if (this.fastupdate) { const tmp = this.reg.get(id); tmp ? tmp.push(arr) : this.reg.set(id, [arr]); } } } } } } if (this.store && (!_append || !this.store.has(id))) { let payload; if (this.storetree) { payload = create_object(); for (let i = 0, tree; i < this.storetree.length; i++) { tree = this.storetree[i]; const filter = tree._filter; if (filter && !filter(content)) { continue; } let custom; if ("function" == typeof tree) { custom = tree(content); if (!custom) continue; tree = [tree._field]; } else if (is_string(tree) || tree.constructor === String) { payload[tree] = content[tree]; continue; } store_value(content, payload, tree, 0, tree[0], custom); } } this.store.set(id, payload || content); } if (this.worker) { this.fastupdate || this.reg.add(id); } } return this; }; /** * @param obj * @param store * @param tree * @param pos * @param key * @param {*=} custom */ function store_value(obj, store, tree, pos, key, custom) { obj = obj[key]; if (pos === tree.length - 1) { store[key] = custom || obj; } else if (obj) { if (is_array(obj)) { store = store[key] = Array(obj.length); for (let i = 0; i < obj.length; i++) { store_value(obj, store, tree, pos, i); } } else { store = store[key] || (store[key] = create_object()); key = tree[++pos]; store_value(obj, store, tree, pos, key); } } } function add_index(obj, tree, marker, pos, index, id, key, _append) { if (obj = obj[key]) { if (pos === tree.length - 1) { if (is_array(obj)) { if (marker[pos]) { for (let i = 0; i < obj.length; i++) { index.add(id, obj[i], !0); } return; } obj = obj.join(" "); } index.add(id, obj, _append, !0); } else { if (is_array(obj)) { for (let i = 0; i < obj.length; i++) { add_index(obj, tree, marker, pos, index, id, i, _append); } } else { key = tree[++pos]; add_index(obj, tree, marker, pos, index, id, key, _append); } } } } ================================================ FILE: dist/module-debug/document/highlight.js ================================================ import { parse_simple } from "../common.js"; import Index from "../index.js"; import WorkerIndex from "../worker.js"; import { EnrichedDocumentSearchResults, EnrichedSearchResults, HighlightOptions } from "../type.js"; /** * @param {string} query * @param {EnrichedDocumentSearchResults|EnrichedSearchResults} result * @param {Map} index * @param {string} pluck * @param {HighlightOptions|string} config * @return {EnrichedDocumentSearchResults|EnrichedSearchResults} */ export function highlight_fields(query, result, index, pluck, config) { let template, markup_open, markup_close; if ("string" == typeof config) { template = config; config = ""; } else { template = config.template; } if (!template) { throw new Error('No template pattern was specified by the search option "highlight"'); } markup_open = template.indexOf("$1"); if (-1 === markup_open) { throw new Error('Invalid highlight template. The replacement pattern "$1" was not found in template: ' + template); } markup_close = template.substring(markup_open + 2); markup_open = template.substring(0, markup_open); let boundary = config && config.boundary, clip = !config || !1 !== config.clip, merge = config && config.merge && markup_close && markup_open && new RegExp(markup_close + " " + markup_open, "g"), ellipsis = config && config.ellipsis, ellipsis_markup_length = 0, ellipsis_markup; if ("object" == typeof ellipsis) { ellipsis_markup = ellipsis.template; ellipsis_markup_length = ellipsis_markup.length - 2; ellipsis = ellipsis.pattern; } if ("string" != typeof ellipsis) { ellipsis = !1 === ellipsis ? "" : "..."; } if (ellipsis_markup_length) { ellipsis = ellipsis_markup.replace("$1", ellipsis); } let ellipsis_length = ellipsis.length - ellipsis_markup_length, boundary_before, boundary_after; if ("object" == typeof boundary) { boundary_before = boundary.before; if (0 === boundary_before) boundary_before = -1; boundary_after = boundary.after; if (0 === boundary_after) boundary_after = -1; boundary = boundary.total || 9e5; } let encoder = new Map(), query_enc; for (let i = 0, enc, idx, path; i < result.length; i++) { /** @type {EnrichedSearchResults} */ let res; if (pluck) { res = result; path = pluck; } else { const tmp = result[i]; path = tmp.field; if (!path) continue; res = tmp.result; } idx = index.get(path); enc = idx.encoder; query_enc = encoder.get(enc); if ("string" != typeof query_enc) { query_enc = enc.encode(query); encoder.set(enc, query_enc); } for (let j = 0; j < res.length; j++) { const doc = res[j].doc; if (!doc) continue; const content = parse_simple(doc, path); if (!content) continue; const doc_org = content.trim().split(/\s+/); if (!doc_org.length) continue; let str = "", str_arr = [], pos_matches = [], pos_first_match = -1, pos_last_match = -1, length_matches_all = 0; for (let k = 0; k < doc_org.length; k++) { let doc_org_cur = doc_org[k], doc_enc_cur = enc.encode(doc_org_cur); doc_enc_cur = 1 < doc_enc_cur.length ? doc_enc_cur.join(" ") : doc_enc_cur[0]; let found; if (doc_enc_cur && doc_org_cur) { let doc_org_cur_len = doc_org_cur.length, doc_org_diff = (enc.split ? doc_org_cur.replace(enc.split, "") : doc_org_cur).length - doc_enc_cur.length, match = "", match_length = 0; for (let l = 0, query_enc_cur; l < query_enc.length; l++) { query_enc_cur = query_enc[l]; if (!query_enc_cur) continue; let query_enc_cur_len = query_enc_cur.length; query_enc_cur_len += 0 > doc_org_diff ? 0 : doc_org_diff; if (match_length && query_enc_cur_len <= match_length) { continue; } const position = doc_enc_cur.indexOf(query_enc_cur); if (-1 < position) { match = (position ? doc_org_cur.substring(0, position) : "") + markup_open + doc_org_cur.substring(position, position + query_enc_cur_len) + markup_close + (position + query_enc_cur_len < doc_org_cur_len ? doc_org_cur.substring(position + query_enc_cur_len) : ""); match_length = query_enc_cur_len; found = !0; } } if (match) { if (boundary) { if (0 > pos_first_match) { pos_first_match = str.length + (str ? 1 : 0); } pos_last_match = str.length + (str ? 1 : 0) + match.length; length_matches_all += doc_org_cur_len; pos_matches.push(str_arr.length); str_arr.push({ match }); } str += (str ? " " : "") + match; } } if (!found) { const text = doc_org[k]; str += (str ? " " : "") + text; boundary && str_arr.push({ text }); } else if (boundary) { if (length_matches_all >= boundary) { break; } } } let markup_length = pos_matches.length * (template.length - 2); if (boundary_before || boundary_after || boundary && str.length - markup_length > boundary) { let boundary_length = boundary + markup_length - 2 * ellipsis_length, length = pos_last_match - pos_first_match, start, end; if (0 < boundary_before) { length += boundary_before; } if (0 < boundary_after) { length += boundary_after; } if (length <= boundary_length) { start = boundary_before ? pos_first_match - (0 < boundary_before ? boundary_before : 0) : pos_first_match - (0 | (boundary_length - length) / 2); end = boundary_after ? pos_last_match + (0 < boundary_after ? boundary_after : 0) : start + boundary_length; if (!clip) { if (0 < start) { if (" " === str.charAt(start)) {} else if (" " !== str.charAt(start - 1)) { start = str.indexOf(" ", start); 0 > start && (start = 0); } } if (end < str.length) { if (" " === str.charAt(end - 1)) {} else if (" " !== str.charAt(end)) { end = str.lastIndexOf(" ", end); end < pos_last_match ? end = pos_last_match : ++end; } } } str = (start ? ellipsis : "") + str.substring(start, end) + (end < str.length ? ellipsis : ""); } else { const final = [], check = {}, seamless = {}, finished = {}, before = {}, after = {}; let final_length = 0, shift_left = 0, shift_right = 0, loop_left = 1, loop_right = 1; while (!0) { let loop; for (let k = 0, pos; k < pos_matches.length; k++) { pos = pos_matches[k]; if (!shift_right) { str = str_arr[pos].match; if (boundary_before) { before[k] = boundary_before; } if (boundary_after) { after[k] = boundary_after; } if (k) { final_length++; } let close; if (!pos) { seamless[k] = 1; finished[k] = 1; } else if (!k && ellipsis_length) { final_length += ellipsis_length; } if (pos >= doc_org.length - 1) { close = 1; } else if (pos < str_arr.length - 1 && str_arr[pos + 1].match) { close = 1; } else if (ellipsis_length) { final_length += ellipsis_length; } final_length -= template.length - 2; if (!k || final_length + str.length <= boundary) { final[k] = str; } else { seamless[k] = 0; loop = loop_left = loop_right = 0; break; } if (close) { seamless[k + 1] = 1; finished[k + 1] = 1; } } else { if (shift_left != shift_right) { if (finished[k + 1]) continue; pos += shift_right; if (check[pos]) { final_length -= ellipsis_length; seamless[k + 1] = 1; finished[k + 1] = 1; continue; } if (pos >= str_arr.length - 1) { if (pos >= str_arr.length) { finished[k + 1] = 1; if (pos >= doc_org.length) { seamless[k + 1] = 1; } continue; } final_length -= ellipsis_length; } str = str_arr[pos].text; let current_after = boundary_after && after[k]; if (current_after) { if (0 < current_after) { if (str.length > current_after) { finished[k + 1] = 1; if (clip) { str = str.substring(0, current_after); } else { continue; } } current_after -= str.length; if (!current_after) current_after = -1; after[k] = current_after; } else { finished[k + 1] = 1; continue; } } if (final_length + str.length + 1 <= boundary) { str = " " + str; final[k] += str; } else if (clip) { const diff = boundary - final_length - 1; if (0 < diff) { str = " " + str.substring(0, diff); final[k] += str; } finished[k + 1] = 1; } else { finished[k + 1] = 1; continue; } } else { if (finished[k]) continue; pos -= shift_left; if (check[pos]) { final_length -= ellipsis_length; finished[k] = 1; seamless[k] = 1; continue; } if (0 >= pos) { if (0 > pos) { finished[k] = 1; seamless[k] = 1; continue; } final_length -= ellipsis_length; } str = str_arr[pos].text; let current_before = boundary_before && before[k]; if (current_before) { if (0 < current_before) { if (str.length > current_before) { finished[k] = 1; if (clip) { str = str.substring(str.length - current_before); } else { continue; } } current_before -= str.length; if (!current_before) current_before = -1; before[k] = current_before; } else { finished[k] = 1; continue; } } if (final_length + str.length + 1 <= boundary) { str += " "; final[k] = str + final[k]; } else if (clip) { const diff = str.length + 1 - (boundary - final_length); if (0 <= diff && diff < str.length) { str = str.substring(diff) + " "; final[k] = str + final[k]; } finished[k] = 1; } else { finished[k] = 1; continue; } } } final_length += str.length; check[pos] = 1; loop = 1; } if (loop) { shift_left == shift_right ? shift_right++ : shift_left++; } else { shift_left == shift_right ? loop_left = 0 : loop_right = 0; if (!loop_left && !loop_right) { break; } if (loop_left) { shift_left++; shift_right = shift_left; } else { shift_right++; } } } str = ""; for (let k = 0, tmp; k < final.length; k++) { tmp = (seamless[k] ? k ? " " : "" : (k && !ellipsis ? " " : "") + ellipsis) + final[k]; str += tmp; } if (ellipsis && !seamless[final.length]) { str += ellipsis; } } } if (merge) { str = str.replace( /** @type {RegExp} */merge, " "); } res[j].highlight = str; } if (pluck) { break; } } return result; } ================================================ FILE: dist/module-debug/document/search.js ================================================ import { DocumentSearchOptions, DocumentSearchResults, EnrichedDocumentSearchResults, MergedDocumentSearchResults, MergedDocumentSearchEntry, EnrichedSearchResults, SearchResults, IntermediateSearchResults } from "../type.js"; import { create_object, is_array, is_object, is_string, inherit } from "../common.js"; import { intersect, intersect_union } from "../intersect.js"; import Document from "../document.js"; import Index from "../index.js"; import WorkerIndex from "../worker.js"; import Resolver from "../resolver.js"; import tick from "../profiler.js"; import { highlight_fields } from "./highlight.js"; /** * @param {!string|DocumentSearchOptions} query * @param {number|DocumentSearchOptions=} limit * @param {DocumentSearchOptions=} options * @param {Array=} _promises async recursion * @this Document * @returns { * DocumentSearchResults| * EnrichedDocumentSearchResults| * MergedDocumentSearchResults| * SearchResults| * IntermediateSearchResults| * EnrichedSearchResults| * Resolver | * Promise< * DocumentSearchResults| * EnrichedDocumentSearchResults| * MergedDocumentSearchResults| * SearchResults| * IntermediateSearchResults| * EnrichedSearchResults| * Resolver * > * } */ Document.prototype.search = function (query, limit, options, _promises) { if (!options) { if (!limit && is_object(query)) { options = /** @type {DocumentSearchOptions} */query; query = ""; } else if (is_object(limit)) { options = /** @type {DocumentSearchOptions} */limit; limit = 0; } } /** @type { * DocumentSearchResults| * EnrichedDocumentSearchResults| * MergedDocumentSearchResults| * SearchResults| * IntermediateSearchResults| * EnrichedSearchResults * } */ let result = [], result_field = [], pluck, enrich, merge, suggest, boost, cache, field, tag, offset, count = 0, resolve = !0, highlight; if (options) { if (is_array(options)) { options = /** @type DocumentSearchOptions */{ index: options }; } query = options.query || query; pluck = options.pluck; merge = options.merge; boost = options.boost; field = pluck || options.field || (field = options.index) && (field.index ? null : field); tag = this.tag && options.tag; suggest = options.suggest; resolve = !1 !== options.resolve; cache = options.cache; if (this.store && options.highlight && !resolve) { console.warn("Highlighting results can only be done within a resolver stage (and/or/not/xor) or when calling .resolve({ highlight: ... })"); } else if (this.store && options.enrich && !resolve) { console.warn("Enrich results can only be done on a final resolver task or when calling .resolve({ enrich: true })"); } highlight = resolve && this.store && options.highlight; enrich = !!highlight || resolve && this.store && options.enrich; limit = options.limit || limit; offset = options.offset || 0; limit || (limit = resolve ? 100 : 0); if (tag && (!this.db || !_promises)) { if (tag.constructor !== Array) { tag = [tag]; } let pairs = []; for (let i = 0, field; i < tag.length; i++) { field = tag[i]; if (is_string(field)) { throw new Error("A tag option can't be a string, instead it needs a { field: tag } format."); } if (field.field && field.tag) { const value = field.tag; if (value.constructor === Array) { for (let k = 0; k < value.length; k++) { pairs.push(field.field, value[k]); } } else { pairs.push(field.field, value); } } else { const keys = Object.keys(field); for (let j = 0, key, value; j < keys.length; j++) { key = keys[j]; value = field[key]; if (value.constructor === Array) { for (let k = 0; k < value.length; k++) { pairs.push(key, value[k]); } } else { pairs.push(key, value); } } } } if (!pairs.length) { throw new Error("Your tag definition within the search options is probably wrong. No valid tags found."); } tag = pairs; if (!query) { let promises = []; if (pairs.length) for (let j = 0; j < pairs.length; j += 2) { let ids; if (this.db) { const index = this.index.get(pairs[j]); if (!index) { console.warn("Tag '" + pairs[j] + ":" + pairs[j + 1] + "' will be skipped because there is no field '" + pairs[j] + "'."); continue; } promises.push(ids = index.db.tag(pairs[j + 1], limit, offset, enrich)); } else { ids = get_tag.call(this, pairs[j], pairs[j + 1], limit, offset, enrich); } result.push(resolve ? { field: pairs[j], tag: pairs[j + 1], result: ids } : [ids]); } if (promises.length) { const self = this; return Promise.all(promises).then(function (promises) { for (let j = 0; j < promises.length; j++) { if (resolve) { result[j].result = promises[j]; } else { result[j] = promises[j]; } } return resolve ? result : new Resolver(1 < result.length ? intersect( /** @type {!Array} */result, 1, 0, 0, suggest, boost) : result[0], self); }); } return resolve ? result : new Resolver(1 < result.length ? intersect( /** @type {!Array} */result, 1, 0, 0, suggest, boost) : result[0], this); } } if (!resolve && !pluck) { field = field || this.field; if (field) { if (is_string(field)) { pluck = field; } else { if (is_array(field) && 1 === field.length) { field = field[0]; } pluck = field.field || field.index; } } if (!pluck) { throw new Error("Apply resolver on document search requires either the option 'pluck' to be set or just select a single field name in your query."); } } if (field && field.constructor !== Array) { field = [field]; } } field || (field = this.field); let db_tag_search, promises = (this.worker || this.db /*|| (SUPPORT_ASYNC && this.async)*/ ) && !_promises && []; for (let i = 0, res, key, len; i < field.length; i++) { key = field[i]; if (this.db && this.tag) { if (!this.tree[i]) { continue; } } let field_options; if (!is_string(key)) { field_options = key; key = field_options.field; query = field_options.query || query; limit = inherit(field_options.limit, limit); offset = inherit(field_options.offset, offset); suggest = inherit(field_options.suggest, suggest); highlight = resolve && this.store && inherit(field_options.highlight, highlight); enrich = !!highlight || resolve && this.store && inherit(field_options.enrich, enrich); cache = inherit(field_options.cache, cache); } if (_promises) { res = _promises[i]; } else { const opt = field_options || options || {}, opt_enrich = opt.enrich, index = this.index.get(key); if (tag) { if (this.db) { opt.tag = tag; opt.field = field; db_tag_search = index.db.support_tag_search; } if (!db_tag_search && opt_enrich) { opt.enrich = !1; } if (!db_tag_search) { opt.limit = 0; opt.offset = 0; } } res = cache ? index.searchCache(query, tag && !db_tag_search ? 0 : limit, opt) : index.search(query, tag && !db_tag_search ? 0 : limit, opt); if (tag && !db_tag_search) { opt.limit = limit; opt.offset = offset; } if (opt_enrich) { opt.enrich = opt_enrich; } if (promises) { promises[i] = res; continue; } } res = res.result || res; len = res && res.length; if (tag && len) { const arr = []; let count = 0; if (this.db && _promises) { if (!db_tag_search) { for (let y = field.length; y < _promises.length; y++) { let ids = _promises[y], len = ids && ids.length; if (len) { count++; arr.push(ids); } else if (!suggest) { return resolve ? result : new Resolver(result, this); } } } } else { for (let y = 0, ids, len; y < tag.length; y += 2) { ids = this.tag.get(tag[y]); if (!ids) { console.warn("Tag '" + tag[y] + ":" + tag[y + 1] + "' will be skipped because there is no field '" + tag[y] + "'."); if (suggest) { continue; } else { return resolve ? result : new Resolver(result, this); } } ids = ids && ids.get(tag[y + 1]); len = ids && ids.length; if (len) { count++; arr.push(ids); } else if (!suggest) { return resolve ? result : new Resolver(result, this); } } } if (count) { res = intersect_union( /** @type {IntermediateSearchResults} */res, arr, limit, offset, resolve); len = res.length; if (!len && !suggest) { return resolve ? res : new Resolver( /** @type {IntermediateSearchResults} */res, this); } count--; } } if (len) { result_field[count] = key; result.push(res); count++; } else if (1 === field.length) { return resolve ? result : new Resolver(result, this); } } if (promises) { if (this.db) { if (tag && tag.length && !db_tag_search) { for (let y = 0; y < tag.length; y += 2) { const index = this.index.get(tag[y]); if (!index) { console.warn("Tag '" + tag[y] + ":" + tag[y + 1] + "' was not found because there is no field '" + tag[y] + "'."); if (suggest) { continue; } else { return resolve ? result : new Resolver(result, this); } } promises.push(index.db.tag(tag[y + 1], limit, offset, !1)); } } } const self = this; return Promise.all(promises).then(function (result) { options && (options.resolve = resolve); if (result.length) { result = self.search(query, limit, options, result); } return result; }); } if (!count) { return resolve ? result : new Resolver(result, this); } if (pluck && (!enrich || !this.store)) { result = /** @type {SearchResults|IntermediateSearchResults} */result[0]; return resolve ? result : new Resolver(result, this); } promises = []; for (let i = 0, res; i < result_field.length; i++) { /** @type {SearchResults|EnrichedSearchResults} */ res = result[i]; if (enrich && res.length && "undefined" == typeof res[0].doc) { if (!this.db) { res = /** @type {EnrichedSearchResults} */apply_enrich.call(this, res); } else { promises.push(res = this.index.get(this.field[0]).db.enrich(res)); } } if (pluck) { return resolve ? highlight ? highlight_fields( /** @type {string} */query, res, this.index, pluck, highlight) : /** @type {SearchResults|EnrichedSearchResults} */res : new Resolver( /** @type {IntermediateSearchResults} */res, this); } result[i] = { field: result_field[i], result: /** @type {SearchResults|EnrichedSearchResults} */res }; } if (enrich && !0 && this.db && promises.length) { const self = this; return Promise.all(promises).then(function (promises) { for (let j = 0; j < promises.length; j++) { result[j].result = promises[j]; } if (highlight) { result = highlight_fields( /** @type {string} */query, result, self.index, pluck, highlight); } return merge ? merge_fields(result) : /** @type {DocumentSearchResults} */result; }); } if (highlight) { result = highlight_fields( /** @type {string} */query, result, this.index, pluck, highlight); } return merge ? merge_fields(result) : /** @type {DocumentSearchResults} */result; }; /** * @param {DocumentSearchResults} fields * @return {MergedDocumentSearchResults} */ function merge_fields(fields) { /** @type {MergedDocumentSearchResults} */ const final = [], group_field = create_object(), group_highlight = create_object(); for (let i = 0, field, key, res, id, entry, tmp, highlight; i < fields.length; i++) { field = fields[i]; key = field.field; res = field.result; for (let j = 0; j < res.length; j++) { entry = res[j]; "object" != typeof entry ? entry = { id: id = entry } : id = entry.id; tmp = group_field[id]; if (!tmp) { entry.field = group_field[id] = [key]; final.push( /** @type {!MergedDocumentSearchEntry} */entry); } else { tmp.push(key); } if (highlight = entry.highlight) { tmp = group_highlight[id]; if (!tmp) { group_highlight[id] = tmp = {}; entry.highlight = tmp; } tmp[key] = highlight; } } } return final; } /** * @this {Document} */ function get_tag(tag, key, limit, offset, enrich) { let res = this.tag.get(tag); if (!res) return []; res = res.get(key); if (!res) return []; let len = res.length - offset; if (0 < len) { if (limit && len > limit || offset) { res = res.slice(offset, offset + limit); } if (enrich) { res = apply_enrich.call(this, res); } } return res; } /** * @param {SearchResults} ids * @return {EnrichedSearchResults|SearchResults|Promise} * @this {Document|Index|WorkerIndex|null} */ export function apply_enrich(ids) { if (!this || !this.store) return ids; if (this.db) { return this.index.get(this.field[0]).db.enrich(ids); } /** @type {EnrichedSearchResults} */ const result = Array(ids.length); for (let x = 0, id; x < ids.length; x++) { id = ids[x]; result[x] = { id: id, doc: this.store.get(id) }; } return result; } ================================================ FILE: dist/module-debug/document.js ================================================ /**! * FlexSearch.js * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ import { IndexOptions, DocumentOptions, DocumentDescriptor, FieldOptions, StoreOptions, EncoderOptions } from "./type.js"; import StorageInterface from "./db/interface.js"; import Index from "./index.js"; import WorkerIndex from "./worker.js"; import Encoder, { fallback_encoder } from "./encoder.js"; import Cache, { searchCache } from "./cache.js"; import { is_string, is_object, parse_simple } from "./common.js"; import apply_async from "./async.js"; import { exportDocument, importDocument } from "./serialize.js"; import { KeystoreMap, KeystoreSet } from "./keystore.js"; import "./document/add.js"; import "./document/search.js"; import Charset from "./charset.js"; /** * @constructor * @param {!DocumentOptions} options * @return {Document|Promise} * @this {Document} */ export default function Document(options) { if (!this || this.constructor !== Document) { return new Document(options); } const document = /** @type DocumentDescriptor */options.document || options.doc || options; let tmp, keystore; this.tree = []; this.field = []; this.marker = []; this.key = (tmp = document.key || document.id) && parse_tree(tmp, this.marker) || "id"; keystore = options.keystore || 0; keystore && (this.keystore = keystore); this.fastupdate = !!options.fastupdate; /** @type { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } */ this.reg = this.fastupdate && !options.worker && !options.db ? keystore && !0 ? new KeystoreMap(keystore) : new Map() : keystore && !0 ? new KeystoreSet(keystore) : new Set(); this.storetree = (tmp = document.store || null) && tmp && !0 !== tmp && []; /** @type {Map|KeystoreMap} */ this.store = tmp ? keystore && !0 ? new KeystoreMap(keystore) : new Map() : null; this.cache = (tmp = options.cache || null) && new Cache(tmp); options.cache = !1; this.worker = options.worker || !1; this.priority = options.priority || 4; /** * @type {Map} */ this.index = parse_descriptor.call(this, options, document); this.tag = null; if (tmp = document.tag) { if ("string" == typeof tmp) { tmp = [tmp]; } if (tmp.length) { this.tag = new Map(); this.tagtree = []; this.tagfield = []; for (let i = 0, params, field; i < tmp.length; i++) { params = tmp[i]; field = params.field || params; if (!field) { throw new Error("The tag field from the document descriptor is undefined."); } if (params.custom) { this.tagtree[i] = params.custom; } else { this.tagtree[i] = parse_tree(field, this.marker); if (params.filter) { if ("string" == typeof this.tagtree[i]) { this.tagtree[i] = new String(this.tagtree[i]); } this.tagtree[i]._filter = params.filter; } } this.tagfield[i] = field; this.tag.set(field, new Map()); } } } if (this.worker) { this.fastupdate = !1; const promises = []; for (const index of this.index.values()) { index.then && promises.push(index); } if (promises.length) { const self = this; return Promise.all(promises).then(function (result) { let count = 0; for (const item of self.index.entries()) { const key = /** @type {string} */item[0]; let index = /** @type {Index|WorkerIndex | Promise} */item[1]; if (index.then) { index = result[count]; self.index.set(key, index); count++; } } return self; }); } } else { if (options.db) { this.fastupdate = !1; this.mount(options.db); } } } /** * @param {!StorageInterface} db * @return {Promise} */ Document.prototype.mount = function (db) { if (this.worker) { throw new Error("You can't use Worker-Indexes on a persistent model. That would be useless, since each of the persistent model acts like Worker-Index by default (Master/Slave)."); } let fields = this.field; if (this.tag) { for (let i = 0, field; i < this.tagfield.length; i++) { field = this.tagfield[i]; let index; this.index.set(field, index = new Index( /** @type IndexOptions */{}, this.reg)); if (fields === this.field) { fields = fields.slice(0); } fields.push(field); index.tag = this.tag.get(field); } } const promises = [], config = { db: db.db, type: db.type, fastupdate: db.fastupdate }; for (let i = 0, index, field; i < fields.length; i++) { config.field = field = fields[i]; index = this.index.get(field); const dbi = new db.constructor(db.id, config); dbi.id = db.id; promises[i] = dbi.mount(index); index.document = !0; if (i) { index.bypass = !0; } else { index.store = this.store; } } const self = this; return this.db = Promise.all(promises).then(function () { self.db = !0; }); }; Document.prototype.commit = async function () { const promises = []; for (const index of this.index.values()) { promises.push(index.commit()); } await Promise.all(promises); this.reg.clear(); }; Document.prototype.destroy = function () { const promises = []; for (const idx of this.index.values()) { promises.push(idx.destroy()); } return Promise.all(promises); }; /** * @this {Document} * @return {Map} */ function parse_descriptor(options, document) { /** @type {Map} */ const index = new Map(); let field = document.index || document.field || document; if (is_string(field)) { field = [field]; } for (let i = 0, key, opt; i < field.length; i++) { key = field[i]; if (!is_string(key)) { opt = key; key = key.field; } opt = /** @type IndexOptions */is_object(opt) ? Object.assign({}, options, opt) : options; if (this.worker) { let encoder = opt.encoder; encoder = encoder && encoder.encode ? encoder : new Encoder("string" == typeof encoder ? Charset[encoder] : encoder || {}); const worker = new WorkerIndex(opt, /** @type {Encoder} */encoder); if (worker) { index.set(key, worker); } else { this.worker = !1; } } if (!this.worker) { index.set(key, new Index( /** @type IndexOptions */opt, this.reg)); } if (opt.custom) { this.tree[i] = opt.custom; } else { this.tree[i] = parse_tree(key, this.marker); if (opt.filter) { if ("string" == typeof this.tree[i]) { this.tree[i] = new String(this.tree[i]); } this.tree[i]._filter = opt.filter; } } this.field[i] = key; } if (this.storetree) { let stores = document.store; if (is_string(stores)) stores = [stores]; for (let i = 0, store, field; i < stores.length; i++) { store = /** @type Array */stores[i]; field = store.field || store; if (store.custom) { this.storetree[i] = store.custom; store.custom._field = field; } else { this.storetree[i] = parse_tree(field, this.marker); if (store.filter) { if ("string" == typeof this.storetree[i]) { this.storetree[i] = new String(this.storetree[i]); } this.storetree[i]._filter = store.filter; } } } } return index; } function parse_tree(key, marker) { const tree = key.split(":"); let count = 0; for (let i = 0; i < tree.length; i++) { key = tree[i]; if ("]" === key[key.length - 1]) { key = key.substring(0, key.length - 2); if (key) { marker[count] = !0; } } if (key) { tree[count++] = key; } } if (count < tree.length) { tree.length = count; } return 1 < count ? tree : tree[0]; } /** * @param {!number|Object} id * @param {!Object} content * @return {Document|Promise} */ Document.prototype.append = function (id, content) { return this.add(id, content, !0); }; /** * @param {!number|Object} id * @param {!Object} content * @return {Document|Promise} */ Document.prototype.update = function (id, content) { return this.remove(id).add(id, content); }; /** * @param {!number|Object} id * @return {Document|Promise} */ Document.prototype.remove = function (id) { if (is_object(id)) { id = parse_simple(id, this.key); } for (const index of this.index.values()) { index.remove(id, !0); } if (this.reg.has(id)) { if (this.tag) { if (!this.fastupdate) { for (let field of this.tag.values()) { for (let item of field) { const tag = item[0], ids = item[1], pos = ids.indexOf(id); if (-1 < pos) { 1 < ids.length ? ids.splice(pos, 1) : field.delete(tag); } } } } } if (this.store) { this.store.delete(id); } this.reg.delete(id); } if (this.cache) { this.cache.remove(id); } return this; }; Document.prototype.clear = function () { const promises = []; for (const index of this.index.values()) { const promise = index.clear(); if (promise.then) { promises.push(promise); } } if (this.tag) { for (const tags of this.tag.values()) { tags.clear(); } } if (this.store) { this.store.clear(); } if (this.cache) { this.cache.clear(); } return promises.length ? Promise.all(promises) : this; }; /** * @param {number|string} id * @return {boolean|Promise} */ Document.prototype.contain = function (id) { if (this.db) { return this.index.get(this.field[0]).db.has(id); } return this.reg.has(id); }; Document.prototype.cleanup = function () { for (const index of this.index.values()) { index.cleanup(); } return this; }; /** * @param {number|string} id * @return {Object} */ Document.prototype.get = function (id) { if (this.db) { return this.index.get(this.field[0]).db.enrich(id).then(function (result) { return result[0] && result[0].doc || null; }); } return this.store.get(id) || null; }; /** * @param {number|string|Object} id * @param {Object} data * @return {Document} */ Document.prototype.set = function (id, data) { if ("object" == typeof id) { data = id; id = parse_simple(data, this.key); } this.store.set(id, data); return this; }; Document.prototype.searchCache = searchCache; Document.prototype.export = exportDocument; Document.prototype.import = importDocument; apply_async(Document.prototype); ================================================ FILE: dist/module-debug/encoder.js ================================================ import { create_object, merge_option } from "./common.js"; import normalize_polyfill from "./charset/polyfill.js"; import { EncoderOptions } from "./type.js"; /* Custom Encoder ---------------- function englishEncoder(string){ return string.toLowerCase().split(/[^a-z]+/) } function chineseEncoder(string){ return string.replace(/\s+/, "").split("") } function fixedEncoder(string){ return [string] } Built-in Encoder ---------------------------- The main workflow follows an increasing strategy, starting from a simple .toLowerCase() to full RegExp Pipeline: 1. apply this.normalize (charset normalization) applied on the whole input string e.g. lowercase, everything you put later into (filter, matcher, stemmer, mapper, etc.) has to be normalized by definition, because it won't apply to them automatically 2. apply this.prepare (custom function, string in - string out) 3 split numerics into triplets 4. split input into terms (by one of them: split/include/exclude) 5. pre-encoded term deduplication 6. apply this.filter (stop-words) 7. apply this.stemmer (replace term endings) 8. apply this.mapper (replace chars) 9. apply this.dedupe (letter deduplication) 10. apply this.matcher (replace terms) 11. apply this.replacer (custom regex) 12. post-encoded term deduplication 13. apply this.finalize (custom function, array in - array out) */ const whitespace = /[^\p{L}\p{N}]+/u, numeric_split_length = /(\d{3})/g, numeric_split_prev_char = /(\D)(\d{3})/g, numeric_split_next_char = /(\d{3})(\D)/g, normalize = /[\u0300-\u036f]/g; /** * @param {EncoderOptions=} options * @constructor */ export default function Encoder(options = {}) { if (!this || this.constructor !== Encoder) { return new Encoder(...arguments); } if (arguments.length) { for (let i = 0; i < arguments.length; i++) { this.assign( /** @type {!EncoderOptions} */arguments[i]); } } else { this.assign( /** @type {!EncoderOptions} */options); } } /** * @param {!EncoderOptions} options */ Encoder.prototype.assign = function (options) { /** * pre-processing string input * @type {Function|boolean} */ this.normalize = /** @type {Function|boolean} */merge_option(options.normalize, !0, this.normalize); let include = options.include, tmp = include || options.exclude || options.split, numeric; if (tmp || "" === tmp) { if ("object" == typeof tmp && tmp.constructor !== RegExp) { let regex = ""; numeric = !include; include || (regex += "\\p{Z}"); if (tmp.letter) { regex += "\\p{L}"; } if (tmp.number) { regex += "\\p{N}"; numeric = !!include; } if (tmp.symbol) { regex += "\\p{S}"; } if (tmp.punctuation) { regex += "\\p{P}"; } if (tmp.control) { regex += "\\p{C}"; } if (tmp = tmp.char) { regex += "object" == typeof tmp ? tmp.join("") : tmp; } try { /** * split string input into terms * @type {string|RegExp|boolean|null} */ this.split = new RegExp("[" + (include ? "^" : "") + regex + "]+", "u"); } catch (e) { console.error("Your split configuration:", tmp, "is not supported on this platform. It falls back to using simple whitespace splitter instead: /\s+/."); this.split = /\s+/; } } else { this.split = /** @type {string|RegExp|boolean} */tmp; numeric = !1 === tmp || 2 > "a1a".split(tmp).length; } this.numeric = merge_option(options.numeric, numeric); } else { try { this.split = /** @type {string|RegExp|boolean} */merge_option(this.split, whitespace); } catch (e) { console.warn("This platform does not support unicode regex. It falls back to using simple whitespace splitter instead: /\s+/."); this.split = /\s+/; } this.numeric = merge_option(options.numeric, merge_option(this.numeric, !0)); } /** * post-processing terms * @type {Function|null} */ this.prepare = /** @type {Function|null} */merge_option(options.prepare, null, this.prepare); /** * final processing * @type {Function|null} */ this.finalize = /** @type {Function|null} */merge_option(options.finalize, null, this.finalize); tmp = options.filter; this.filter = "function" == typeof tmp ? tmp : merge_option(tmp && new Set(tmp), null, this.filter); this.dedupe = merge_option(options.dedupe, !0, this.dedupe); this.matcher = merge_option((tmp = options.matcher) && new Map(tmp), null, this.matcher); this.mapper = merge_option((tmp = options.mapper) && new Map(tmp), null, this.mapper); this.stemmer = merge_option((tmp = options.stemmer) && new Map(tmp), null, this.stemmer); this.replacer = merge_option(options.replacer, null, this.replacer); this.minlength = merge_option(options.minlength, 1, this.minlength); this.maxlength = merge_option(options.maxlength, 1024, this.maxlength); this.rtl = merge_option(options.rtl, !1, this.rtl); this.cache = tmp = merge_option(options.cache, !0, this.cache); if (tmp) { this.timer = null; this.cache_size = "number" == typeof tmp ? tmp : 2e5; this.cache_enc = new Map(); this.cache_term = new Map(); this.cache_enc_length = 128; this.cache_term_length = 128; } this.matcher_str = ""; this.matcher_test = null; this.stemmer_str = ""; this.stemmer_test = null; if (this.matcher) { for (const key of this.matcher.keys()) { this.matcher_str += (this.matcher_str ? "|" : "") + key; } } if (this.stemmer) { for (const key of this.stemmer.keys()) { this.stemmer_str += (this.stemmer_str ? "|" : "") + key; } } return this; }; Encoder.prototype.addStemmer = function (match, replace) { this.stemmer || (this.stemmer = new Map()); this.stemmer.set(match, replace); this.stemmer_str += (this.stemmer_str ? "|" : "") + match; this.stemmer_test = null; this.cache && clear(this); return this; }; Encoder.prototype.addFilter = function (term) { if ("function" == typeof term) { this.filter = term; } else { this.filter || (this.filter = new Set()); this.filter.add(term); } this.cache && clear(this); return this; }; /** * Replace a single char * @param {string} char_match * @param {string} char_replace * @return {Encoder} * @suppress {invalidCasts} */ Encoder.prototype.addMapper = function (char_match, char_replace) { if ("object" == typeof char_match) { return this.addReplacer( /** @type {RegExp} */char_match, char_replace); } if (1 < char_match.length) { return this.addMatcher(char_match, char_replace); } this.mapper || (this.mapper = new Map()); this.mapper.set(char_match, char_replace); this.cache && clear(this); return this; }; /** * Replace a string * @param {string} match * @param {string} replace * @return {Encoder} * @suppress {invalidCasts} */ Encoder.prototype.addMatcher = function (match, replace) { if ("object" == typeof match) { return this.addReplacer( /** @type {RegExp} */match, replace); } if (2 > match.length && (this.dedupe || this.mapper)) { return this.addMapper(match, replace); } this.matcher || (this.matcher = new Map()); this.matcher.set(match, replace); this.matcher_str += (this.matcher_str ? "|" : "") + match; this.matcher_test = null; this.cache && clear(this); return this; }; /** * @param {RegExp} regex * @param {string} replace * @return {Encoder} * @suppress {invalidCasts} */ Encoder.prototype.addReplacer = function (regex, replace) { if ("string" == typeof regex) { return this.addMatcher( /** @type {string} */regex, replace); } this.replacer || (this.replacer = []); this.replacer.push(regex, replace); this.cache && clear(this); return this; }; /** * @param {!string} str * @param {boolean=} dedupe_terms Note: term deduplication will break the context chain * @return {!Array} */ Encoder.prototype.encode = function (str, dedupe_terms) { if (this.cache && str.length <= this.cache_enc_length) { if (this.timer) { if (this.cache_enc.has(str)) { return this.cache_enc.get(str); } } else { this.timer = setTimeout(clear, 50, this); } } if (this.normalize) { if ("function" == typeof this.normalize) { str = this.normalize(str); } else if (normalize) { str = str.normalize("NFKD").replace(normalize, "").toLowerCase(); } else { str = str.toLowerCase(); } } if (this.prepare) { str = this.prepare(str); } if (this.numeric && 3 < str.length) { str = str.replace(numeric_split_prev_char, "$1 $2").replace(numeric_split_next_char, "$1 $2").replace(numeric_split_length, "$1 "); } const skip = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer); let final = [], dupes = create_object(), last_term, last_term_enc, words = this.split || "" === this.split ? str.split( /** @type {string|RegExp} */this.split) : [str]; for (let i = 0, word, base; i < words.length; i++) { if (!(word = base = words[i])) { continue; } if (word.length < this.minlength || word.length > this.maxlength) { continue; } if (dedupe_terms) { if (dupes[word]) { continue; } dupes[word] = 1; } else { if (last_term === word) { continue; } last_term = word; } if (skip) { final.push(word); continue; } if (this.filter && ("function" == typeof this.filter ? !this.filter(word) : this.filter.has(word))) { continue; } if (this.cache && word.length <= this.cache_term_length) { if (this.timer) { const tmp = this.cache_term.get(word); if (tmp || "" === tmp) { tmp && final.push(tmp); continue; } } else { this.timer = setTimeout(clear, 50, this); } } if (this.stemmer) { this.stemmer_test || (this.stemmer_test = new RegExp("(?!^)(" + this.stemmer_str + ")$")); let old; while (old !== word && 2 < word.length) { old = word; word = word.replace(this.stemmer_test, match => this.stemmer.get(match)); } } if (word && (this.mapper || this.dedupe && 1 < word.length)) { let final = ""; for (let i = 0, prev = "", char, tmp; i < word.length; i++) { char = word.charAt(i); if (char !== prev || !this.dedupe) { tmp = this.mapper && this.mapper.get(char); if (!tmp && "" !== tmp) final += prev = char;else if ((tmp !== prev || !this.dedupe) && (prev = tmp)) final += tmp; } } word = final; } if (this.matcher && 1 < word.length) { this.matcher_test || (this.matcher_test = new RegExp("(" + this.matcher_str + ")", "g")); word = word.replace(this.matcher_test, match => this.matcher.get(match)); } if (word && this.replacer) { for (let i = 0; word && i < this.replacer.length; i += 2) { word = word.replace(this.replacer[i], this.replacer[i + 1]); } } if (this.cache && base.length <= this.cache_term_length) { this.cache_term.set(base, word); if (this.cache_term.size > this.cache_size) { this.cache_term.clear(); this.cache_term_length = 0 | this.cache_term_length / 1.1; } } if (word) { if (word !== base) { if (dedupe_terms) { if (dupes[word]) { continue; } dupes[word] = 1; } else { if (last_term_enc === word) { continue; } last_term_enc = word; } } final.push(word); } } if (this.finalize) { final = this.finalize(final) || final; } if (this.cache && str.length <= this.cache_enc_length) { this.cache_enc.set(str, final); if (this.cache_enc.size > this.cache_size) { this.cache_enc.clear(); this.cache_enc_length = 0 | this.cache_enc_length / 1.1; } } return final; }; export function fallback_encoder(str) { return str.normalize("NFKD").replace(normalize, "").toLowerCase().trim().split(/\s+/); } /** * @param {Encoder} self */ function clear(self) { self.timer = null; self.cache_enc.clear(); self.cache_term.clear(); } ================================================ FILE: dist/module-debug/index/add.js ================================================ import { create_object } from "../common.js"; import Index, { autoCommit } from "../index.js"; import default_compress from "../compress.js"; import { KeystoreArray, KeystoreMap } from "../keystore.js"; /** * @param {!number|string} id * @param {!string} content * @param {boolean=} _append * @param {boolean=} _skip_update */ Index.prototype.add = function (id, content, _append, _skip_update) { if (content && (id || 0 === id)) { if (!_skip_update && !_append) { if (this.reg.has(id)) { return this.update(id, content); } } const depth = this.depth; content = this.encoder.encode(content, !depth); const term_count = content.length; if (term_count) { const dupes_ctx = create_object(), dupes = create_object(), resolution = this.resolution; for (let i = 0; i < term_count; i++) { let term = content[this.rtl ? term_count - 1 - i : i], term_length = term.length; if (term_length && (depth || !dupes[term])) { let score = this.score ? this.score(content, term, i, null, 0) : get_score(resolution, term_count, i), token = ""; switch (this.tokenize) { case "tolerant": this._push_index(dupes, term, score, id, _append); if (2 < term_length) { for (let x = 1, char_a, char_b, prt_1, prt_2; x < term_length - 1; x++) { char_a = term.charAt(x); char_b = term.charAt(x + 1); prt_1 = term.substring(0, x) + char_b; prt_2 = term.substring(x + 2); token = prt_1 + char_a + prt_2; this._push_index(dupes, token, score, id, _append); token = prt_1 + prt_2; this._push_index(dupes, token, score, id, _append); } this._push_index(dupes, term.substring(0, term.length - 1), score, id, _append); } break; case "full": if (2 < term_length) { for (let x = 0, _x; x < term_length; x++) { for (let y = term_length; y > x; y--) { token = term.substring(x, y); _x = this.rtl ? term_length - 1 - x : x; const partial_score = this.score ? this.score(content, term, i, token, _x) : get_score(resolution, term_count, i, term_length, _x); this._push_index(dupes, token, partial_score, id, _append); } } break; } case "bidirectional": case "reverse": if (1 < term_length) { for (let x = term_length - 1; 0 < x; x--) { token = term[this.rtl ? term_length - 1 - x : x] + token; const partial_score = this.score ? this.score(content, term, i, token, x) : get_score(resolution, term_count, i, term_length, x); this._push_index(dupes, token, partial_score, id, _append); } token = ""; } case "forward": if (1 < term_length) { for (let x = 0; x < term_length; x++) { token += term[this.rtl ? term_length - 1 - x : x]; this._push_index(dupes, token, score, id, _append); } break; } default: this._push_index(dupes, term, score, id, _append); if (depth && 1 < term_count && i < term_count - 1) { const resolution = this.resolution_ctx, keyword = term, size = Math.min(depth + 1, this.rtl ? i + 1 : term_count - i); for (let x = 1; x < size; x++) { term = content[this.rtl ? term_count - 1 - i - x : i + x]; const swap = this.bidirectional && term > keyword, context_score = this.score ? this.score(content, keyword, i, term, x - 1) : get_score(resolution + (term_count / 2 > resolution ? 0 : 1), term_count, i, size - 1, x - 1); this._push_index(dupes_ctx, swap ? keyword : term, context_score, id, _append, swap ? term : keyword); } } } } } this.fastupdate || this.reg.add(id); } } if (this.db) { this.commit_task.push(_append ? { ins: id } : { del: id }); this.commit_auto && autoCommit(this); } return this; }; /** * @private * @param dupes * @param term * @param score * @param id * @param {boolean=} append * @param {string=} keyword */ Index.prototype._push_index = function (dupes, term, score, id, append, keyword) { let res, arr; if (!(res = dupes[term]) || keyword && !res[keyword]) { if (keyword) { dupes = res || (dupes[term] = create_object()); dupes[keyword] = 1; if (this.compress) { keyword = default_compress(keyword); } arr = this.ctx; res = arr.get(keyword); res ? arr = res : arr.set(keyword, arr = this.keystore ? new KeystoreMap(this.keystore) : new Map()); } else { arr = this.map; dupes[term] = 1; } if (this.compress) { term = default_compress(term); } res = arr.get(term); res ? arr = res : arr.set(term, arr = res = []); if (append) { for (let i = 0, arr; i < res.length; i++) { arr = res[i]; if (arr && arr.includes(id)) { if (i <= score) { return; } else { arr.splice(arr.indexOf(id), 1); if (this.fastupdate) { const tmp = this.reg.get(id); tmp && tmp.splice(tmp.indexOf(arr), 1); } } break; } } } arr = arr[score] || (arr[score] = []); arr.push(id); if (2147483647 === arr.length) { const keystore = new KeystoreArray(arr); if (this.fastupdate) { for (let value of this.reg.values()) { if (value.includes(arr)) { value[value.indexOf(arr)] = keystore; } } } res[score] = arr = keystore; } if (this.fastupdate) { const tmp = this.reg.get(id); tmp ? tmp.push(arr) : this.reg.set(id, [arr]); } } }; /** * @param {number} resolution * @param {number} length * @param {number} i * @param {number=} term_length * @param {number=} x * @returns {number} */ function get_score(resolution, length, i, term_length, x) { return i && 1 < resolution ? length + (term_length || 0) <= resolution ? i + (x || 0) : 0 | (resolution - 1) / (length + (term_length || 0)) * (i + (x || 0)) + 1 : 0; } ================================================ FILE: dist/module-debug/index/remove.js ================================================ import { is_array } from "../common.js"; import Index, { autoCommit } from "../index.js"; import { KeystoreMap } from "../keystore.js"; /** * @param {!number|string} id * @param {boolean=} _skip_deletion */ Index.prototype.remove = function (id, _skip_deletion) { const refs = this.reg.size && (this.fastupdate ? this.reg.get(id) : this.reg.has(id)); if (refs) { if (this.fastupdate) { for (let i = 0, tmp, len; i < refs.length; i++) { if ((tmp = refs[i]) && (len = tmp.length)) { if (tmp[len - 1] === id) { tmp.pop(); } else { const index = tmp.indexOf(id); if (0 <= index) { tmp.splice(index, 1); } } } } } else { remove_index(this.map, id); this.depth && remove_index(this.ctx, id); } _skip_deletion || this.reg.delete(id); } if (this.db) { this.commit_task.push({ del: id }); this.commit_auto && autoCommit(this); } if (this.cache) { this.cache.remove(id); } return this; }; /** * When called without passing ID it just will clean up * @param {!Map|KeystoreMap|Array>} map * @param {!number|string=} id * @return {number} */ export function remove_index(map, id) { let count = 0; if (is_array(map)) { for (let x = 0, arr, index, found; x < map.length; x++) { if ((arr = map[x]) && arr.length) { if ("undefined" == typeof id) { return 1; } else { index = arr.indexOf(id); if (0 <= index) { if (1 < arr.length) { arr.splice(index, 1); return 1; } else { delete map[x]; if (count) { return 1; } found = 1; } } else { if (found) { return 1; } count++; } } } } } else for (let item of map.entries()) { const key = item[0], value = item[1], tmp = remove_index(value, id); tmp ? count++ : map.delete(key); } return count; } ================================================ FILE: dist/module-debug/index/search.js ================================================ import { SearchOptions, SearchResults, EnrichedSearchResults, IntermediateSearchResults } from "../type.js"; import { create_object, is_object, sort_by_length_down } from "../common.js"; import Index from "../index.js"; import default_compress from "../compress.js"; import Resolver from "../resolver.js"; import { intersect } from "../intersect.js"; import resolve_default from "../resolve/default.js"; /** * @param {string|SearchOptions} query * @param {number|SearchOptions=} limit * @param {SearchOptions=} options * @return { * SearchResults|EnrichedSearchResults|Resolver | * Promise * } */ Index.prototype.search = function (query, limit, options) { if (!options) { if (!limit && "object" == typeof query) { options = /** @type {!SearchOptions} */query; query = ""; } else if ("object" == typeof limit) { options = /** @type {!SearchOptions} */limit; limit = 0; } } if (options && options.cache) { options.cache = !1; const res = this.searchCache(query, limit, options); options.cache = !0; return res; } /** @type {!Array} */ let result = [], length, context, suggest, offset = 0, resolve, tag, boost, resolution, enrich; if (options) { query = options.query || query; limit = options.limit || limit; offset = options.offset || 0; context = options.context; suggest = options.suggest; resolve = options.resolve; enrich = resolve && options.enrich; boost = options.boost; resolution = options.resolution; tag = this.db && options.tag; } if ("undefined" == typeof resolve) { resolve = this.resolve; } context = this.depth && !1 !== context; /** @type {Array} */ let query_terms = this.encoder.encode(query, !context); length = query_terms.length; limit = /** @type {!number} */limit || (resolve ? 100 : 0); if (1 === length) { return single_term_query.call(this, query_terms[0], "", limit, offset, resolve, enrich, tag); } if (2 === length && context && !suggest) { return single_term_query.call(this, query_terms[1], query_terms[0], limit, offset, resolve, enrich, tag); } let dupes = create_object(), index = 0, keyword; if (context) { keyword = query_terms[0]; index = 1; } if (!resolution && 0 !== resolution) { resolution = keyword ? this.resolution_ctx : this.resolution; } if (this.db) { if (this.db.search) { const result = this.db.search(this, query_terms, limit, offset, suggest, resolve, enrich, tag); if (!1 !== result) return result; } const self = this; return async function () { for (let arr, term; index < length; index++) { term = query_terms[index]; if (term && !dupes[term]) { dupes[term] = 1; arr = await self._get_array(term, keyword, 0, 0, !1, !1); arr = add_result(arr, /** @type {Array} */result, suggest, resolution); if (arr) { result = arr; break; } if (keyword) { if (!suggest || !arr || !result.length) { keyword = term; } } } if (suggest && keyword && index == length - 1) { if (!result.length) { resolution = self.resolution; keyword = ""; index = -1; dupes = create_object(); } } } return return_result(result, resolution, /** @type {!number} */limit, offset, suggest, boost, resolve); }(); } for (let arr, term; index < length; index++) { term = query_terms[index]; if (term && !dupes[term]) { dupes[term] = 1; arr = this._get_array(term, keyword, 0, 0, !1, !1); arr = add_result(arr, /** @type {Array} */result, suggest, resolution); if (arr) { result = arr; break; } if (keyword) { if (!suggest || !arr || !result.length) { keyword = term; } } } if (suggest && keyword && index == length - 1) { if (!result.length) { resolution = this.resolution; keyword = ""; index = -1; dupes = create_object(); } } } return return_result(result, resolution, /** @type {!number} */limit, offset, suggest, boost, resolve); }; /** * @param {!Array} result * @param {number} resolution * @param {number} limit * @param {number=} offset * @param {boolean=} suggest * @param {number=} boost * @param {boolean=} resolve * @return { * SearchResults|EnrichedSearchResults|Resolver | * Promise * } */ function return_result(result, resolution, limit, offset, suggest, boost, resolve) { let length = result.length, final = result; if (1 < length) { final = intersect(result, resolution, limit, offset, suggest, boost, resolve); } else if (1 === length) { return resolve ? resolve_default.call(null, result[0], limit, offset) : new Resolver(result[0], this); } return resolve ? final : new Resolver(final, this); } /** * @param {!string} term * @param {string|null} keyword * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @param {string=} tag * @this {Index} * @return { * SearchResults|EnrichedSearchResults|Resolver | * Promise * } */ function single_term_query(term, keyword, limit, offset, resolve, enrich, tag) { const result = this._get_array(term, keyword, limit, offset, resolve, enrich, tag); if (this.db) { return result.then(function (result) { return resolve ? result || [] : new Resolver(result, this); }); } return result && result.length ? resolve ? resolve_default.call(this, /** @type {SearchResults|EnrichedSearchResults} */result, limit, offset) : new Resolver(result, this) : resolve ? [] : new Resolver([], this); } /** * Returns a 1-dimensional finalized array when the result is done (fast path return), * returns false when suggestions is enabled and no result was found, * or returns nothing when a set was pushed successfully to the results * * @private * @param {IntermediateSearchResults} arr * @param {Array} result * @param {boolean=} suggest * @param {number=} resolution * @return {Array|undefined} */ function add_result(arr, result, suggest, resolution) { let word_arr = []; if (arr && arr.length) { if (arr.length <= resolution) { result.push(arr); return; } for (let x = 0, tmp; x < resolution; x++) { if (tmp = arr[x]) { word_arr[x] = tmp; } } if (word_arr.length) { result.push(word_arr); return; } } if (!suggest) return word_arr; } /** * @param {!string} term * @param {string|null} keyword * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @param {string=} tag * @return { * IntermediateSearchResults|EnrichedSearchResults | * Promise * } */ Index.prototype._get_array = function (term, keyword, limit, offset, resolve, enrich, tag) { let arr, swap; if (keyword) { swap = this.bidirectional && term > keyword; if (swap) { swap = keyword; keyword = term; term = swap; } } if (this.compress) { term = default_compress(term); keyword && (keyword = default_compress(keyword)); } if (this.db) { return this.db.get(term, keyword, limit, offset, resolve, enrich, tag); } if (keyword) { arr = this.ctx.get(keyword); arr = arr && arr.get(term); } else { arr = this.map.get(term); } return arr; }; ================================================ FILE: dist/module-debug/index.js ================================================ /**! * FlexSearch.js * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ import { IndexOptions, ContextOptions, EncoderOptions } from "./type.js"; import Encoder, { fallback_encoder } from "./encoder.js"; import Cache, { searchCache } from "./cache.js"; import Charset from "./charset.js"; import { KeystoreMap, KeystoreSet } from "./keystore.js"; import { is_array, is_string } from "./common.js"; import { exportIndex, importIndex, serialize } from "./serialize.js"; import { remove_index } from "./index/remove.js"; import apply_preset from "./preset.js"; import apply_async from "./async.js"; import tick from "./profiler.js"; import "./index/add.js"; import "./index/search.js"; import "./index/remove.js"; /** * @constructor * @param {IndexOptions|string=} options Options or preset as string * @param {Map|Set|KeystoreSet|KeystoreMap=} _register */ export default function Index(options, _register) { if (!this || this.constructor !== Index) { return new Index(options); } options = /** @type IndexOptions */options ? apply_preset(options) : {}; /** @type {*} */ let tmp = options.context; /** @type ContextOptions */ const context = /** @type ContextOptions */!0 === tmp ? { depth: 1 } : tmp || {}, encoder = is_string(options.encoder) ? Charset[options.encoder] : options.encode || options.encoder || {}; /** @type Encoder */ this.encoder = encoder.encode ? encoder : "object" == typeof encoder ? new Encoder( /** @type {EncoderOptions} */encoder) : { encode: encoder }; this.compress = options.compress || options.compression || !1; this.resolution = options.resolution || 9; this.tokenize = tmp = (tmp = options.tokenize) && "default" !== tmp && "exact" !== tmp && tmp || "strict"; this.depth = "strict" === tmp && context.depth || 0; this.bidirectional = !1 !== context.bidirectional; this.fastupdate = !!options.fastupdate; this.score = options.score || null; if (context && context.depth && "strict" !== this.tokenize) { console.warn("Context-Search could not applied, because it is just supported when using the tokenizer \"strict\"."); } tmp = options.keystore || 0; tmp && (this.keystore = tmp); this.map = tmp && !0 ? new KeystoreMap(tmp) : new Map(); this.ctx = tmp && !0 ? new KeystoreMap(tmp) : new Map(); /** @type { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } */ this.reg = _register || (this.fastupdate ? tmp && !0 ? new KeystoreMap(tmp) : new Map() : tmp && !0 ? new KeystoreSet(tmp) : new Set()); this.resolution_ctx = context.resolution || 3; this.rtl = encoder.rtl || options.rtl || !1; this.cache = (tmp = options.cache || null) && new Cache(tmp); this.resolve = !1 !== options.resolve; if (tmp = options.db) { this.db = this.mount(tmp); } this.commit_auto = !1 !== options.commit; this.commit_task = []; this.commit_timer = null; this.priority = options.priority || 4; } Index.prototype.mount = function (db) { if (this.commit_timer) { clearTimeout(this.commit_timer); this.commit_timer = null; } return db.mount(this); }; Index.prototype.commit = function () { if (this.commit_timer) { clearTimeout(this.commit_timer); this.commit_timer = null; } return this.db.commit(this); }; Index.prototype.destroy = function () { if (this.commit_timer) { clearTimeout(this.commit_timer); this.commit_timer = null; } return this.db.destroy(); }; /** * @param {!Index} self */ export function autoCommit(self) { if (!self.commit_timer) { self.commit_timer = setTimeout(function () { self.commit_timer = null; self.db.commit(self); }, 1); } } Index.prototype.clear = function () { this.map.clear(); this.ctx.clear(); this.reg.clear(); this.cache && this.cache.clear(); if (this.db) { this.commit_timer && clearTimeout(this.commit_timer); this.commit_timer = null; this.commit_task = []; return this.db.clear(); } return this; }; /** * @param {!number|string} id * @param {!string} content */ Index.prototype.append = function (id, content) { return this.add(id, content, !0); }; /** * @param {number|string} id * @return {boolean|Promise} */ Index.prototype.contain = function (id) { return this.db ? this.db.has(id) : this.reg.has(id); }; Index.prototype.update = function (id, content) { const self = this, res = this.remove(id); return res && res.then ? res.then(() => self.add(id, content)) : this.add(id, content); }; Index.prototype.cleanup = function () { if (!this.fastupdate) { console.info("Cleanup the index isn't required when not using \"fastupdate\"."); return this; } remove_index(this.map); this.depth && remove_index(this.ctx); return this; }; Index.prototype.searchCache = searchCache; Index.prototype.export = exportIndex; Index.prototype.import = importIndex; Index.prototype.serialize = serialize; apply_async(Index.prototype); ================================================ FILE: dist/module-debug/intersect.js ================================================ import Resolver from "./resolver.js"; import { create_object, concat, sort_by_length_up, get_max_len } from "./common.js"; import { SearchResults, IntermediateSearchResults } from "./type.js"; /* from -> result[ res[score][id], res[score][id], ] to -> [id] */ /** * @param {!Array} arrays * @param {number} resolution * @param {number} limit * @param {number=} offset * @param {boolean=} suggest * @param {number=} boost * @param {boolean=} resolve * @returns {SearchResults|IntermediateSearchResults} */ export function intersect(arrays, resolution, limit, offset, suggest, boost, resolve) { const length = arrays.length; /** @type {Array} */ let result = [], check, count; check = create_object(); for (let y = 0, ids, id, res_arr, tmp; y < resolution; y++) { for (let x = 0; x < length; x++) { res_arr = arrays[x]; if (y < res_arr.length && (ids = res_arr[y])) { for (let z = 0; z < ids.length; z++) { id = ids[z]; if (count = check[id]) { check[id]++; } else { count = 0; check[id] = 1; } tmp = result[count] || (result[count] = []); if (!resolve) { let score = y + (x || !suggest ? 0 : boost || 0); tmp = tmp[score] || (tmp[score] = []); } tmp.push(id); if (resolve) { if (limit && count === length - 1) { if (tmp.length - offset === limit) { return offset ? tmp.slice(offset) : tmp; } } } } } } } const result_len = result.length; if (result_len) { if (!suggest) { if (result_len < length) { return []; } result = /** @type {SearchResults|IntermediateSearchResults} */result[result_len - 1]; if (limit || offset) { if (resolve) { if (result.length > limit || offset) { result = result.slice(offset, limit + offset); } } else { const final = []; for (let i = 0, arr; i < result.length; i++) { arr = result[i]; if (!arr) continue; if (offset && arr.length > offset) { offset -= arr.length; continue; } if (limit && arr.length > limit || offset) { arr = arr.slice(offset, limit + offset); limit -= arr.length; if (offset) offset -= arr.length; } final.push(arr); if (!limit) { break; } } result = final; } } } else { result = 1 < result.length ? union(result, limit, offset, resolve, boost) : (result = result[0]) && limit && result.length > limit || offset ? result.slice(offset, limit + offset) : result; } } return (/** @type {SearchResults|IntermediateSearchResults} */result ); } /** * @param {Array} arrays * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {number=} boost * @returns {SearchResults|IntermediateSearchResults} */ export function union(arrays, limit, offset, resolve, boost) { /** @type {SearchResults|IntermediateSearchResults} */ const result = [], check = create_object(); let ids, id, arr_len = arrays.length, ids_len; if (!resolve) { for (let i = arr_len - 1, res, count = 0; 0 <= i; i--) { res = arrays[i]; for (let k = 0; k < res.length; k++) { ids = res[k]; ids_len = ids && ids.length; if (ids_len) for (let j = 0; j < ids_len; j++) { id = ids[j]; if (!check[id]) { check[id] = 1; if (offset) { offset--; } else { let score = 0 | (k + (i < arr_len - 1 ? boost || 0 : 0)) / (i + 1), arr = result[score] || (result[score] = []); arr.push(id); if (++count === limit) { return result; } } } } } } } else for (let i = arr_len - 1; 0 <= i; i--) { ids = arrays[i]; ids_len = ids && ids.length; if (ids_len) for (let j = 0; j < ids_len; j++) { id = ids[j]; if (!check[id]) { check[id] = 1; if (offset) { offset--; } else { result.push(id); if (result.length === limit) { return result; } } } } } return result; } /** * @param {SearchResults|IntermediateSearchResults|Resolver} arrays * @param {Array} mandatory * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @returns {SearchResults} */ export function intersect_union(arrays, mandatory, limit, offset, resolve) { const check = create_object(), result = []; /** @type {SearchResults|IntermediateSearchResults} */ for (let x = 0, ids; x < mandatory.length; x++) { ids = mandatory[x]; for (let i = 0; i < ids.length; i++) { check[ids[i]] = 1; } } if (resolve) { for (let i = 0, id; i < arrays.length; i++) { id = arrays[i]; if (check[id]) { if (offset) { offset--; continue; } result.push(id); check[id] = 0; if (limit) { if (0 == --limit) { break; } } } } } else { arrays = arrays.result || arrays; for (let i = 0, ids, id; i < arrays.length; i++) { ids = arrays[i]; for (let j = 0; j < ids.length; j++) { id = ids[j]; if (check[id]) { const arr = result[i] || (result[i] = []); arr.push(id); check[id] = 0; } } } } return result; } /** * Implementation based on Array.includes() provides better performance, * but it needs at least one word in the query which is less frequent. * Also on large indexes it does not scale well performance-wise. * This strategy also lacks of suggestion capabilities (matching & sorting). * * @param arrays * @param limit * @param offset * @param {boolean|Array=} suggest * @returns {Array} */ ================================================ FILE: dist/module-debug/keystore.js ================================================ import { create_object } from "./common.js"; function _slice(self, start, end, splice) { let arr = []; for (let i = 0, index; i < self.index.length; i++) { index = self.index[i]; if (start >= index.length) { start -= index.length; } else { const tmp = index[splice ? "splice" : "slice"](start, end), length = tmp.length; if (length) { arr = arr.length ? arr.concat(tmp) : tmp; end -= length; if (splice) self.length -= length; if (!end) break; } start = 0; } } return arr; } /** * @param arr * @constructor */ export function KeystoreArray(arr) { if (!this || this.constructor !== KeystoreArray) { return new KeystoreArray(arr); } this.index = arr ? [arr] : []; this.length = arr ? arr.length : 0; const self = this; return new Proxy([], { get(target, key) { if ("length" === key) { return self.length; } if ("push" === key) { return function (value) { self.index[self.index.length - 1].push(value); self.length++; }; } if ("pop" === key) { return function () { if (self.length) { self.length--; return self.index[self.index.length - 1].pop(); } }; } if ("indexOf" === key) { return function (key) { let index = 0; for (let i = 0, arr, tmp; i < self.index.length; i++) { arr = self.index[i]; tmp = arr.indexOf(key); if (0 <= tmp) return index + tmp; index += arr.length; } return -1; }; } if ("includes" === key) { return function (key) { for (let i = 0; i < self.index.length; i++) { if (self.index[i].includes(key)) { return !0; } } return !1; }; } if ("slice" === key) { return function (start, end) { return _slice(self, start || 0, end || self.length, !1); }; } if ("splice" === key) { return function (start, end) { return _slice(self, start || 0, end || self.length, !0); }; } if ("constructor" === key) { return Array; } if ("symbol" == typeof key) { return; } const arr = self.index[0 | key / 2147483648]; return arr && arr[key]; }, set(target, key, value) { const index = 0 | key / 2147483648, arr = self.index[index] || (self.index[index] = []); arr[key] = value; self.length++; return !0; } }); } KeystoreArray.prototype.clear = function () { this.index.length = 0; }; KeystoreArray.prototype.push = function () {}; /** * @interface */ function Keystore() { /** @type {Object} */ this.index; /** @type {Array} */ this.refs; /** @type {number} */ this.size; /** @type {function((string|bigint|number)):number} */ this.crc; /** @type {bigint|number} */ this.bit; } /** * @param bitlength * @constructor * @implements {Keystore} */ export function KeystoreMap(bitlength = 8) { if (!this || this.constructor !== KeystoreMap) { return new KeystoreMap(bitlength); } /** @type {Object} */ this.index = create_object(); /** @type {Array} */ this.refs = []; /** @type {number} */ this.size = 0; if (32 < bitlength) { this.crc = lcg64; this.bit = BigInt(bitlength); } else { this.crc = lcg; this.bit = bitlength; } } /** @param {number|string} key */ KeystoreMap.prototype.get = function (key) { const address = this.crc(key), map = this.index[address]; return map && map.get(key); }; /** * @param {number|string} key * @param {*} value */ KeystoreMap.prototype.set = function (key, value) { const address = this.crc(key); let map = this.index[address]; if (map) { let size = map.size; map.set(key, value); size -= map.size; size && this.size++; } else { this.index[address] = map = new Map([[key, value]]); this.refs.push(map); this.size++; } }; /** * @param bitlength * @constructor * @implements Keystore */ export function KeystoreSet(bitlength = 8) { if (!this || this.constructor !== KeystoreSet) { return new KeystoreSet(bitlength); } /** @type {Object} */ this.index = create_object(); /** @type {Array} */ this.refs = []; /** @type {number} */ this.size = 0; if (32 < bitlength) { this.crc = lcg64; this.bit = BigInt(bitlength); } else { this.crc = lcg; this.bit = bitlength; } } /** @param {number|string} key */ KeystoreSet.prototype.add = function (key) { const address = this.crc(key); let set = this.index[address]; if (set) { let size = set.size; set.add(key); size -= set.size; size && this.size++; } else { this.index[address] = set = new Set([key]); this.refs.push(set); this.size++; } }; KeystoreMap.prototype.has = /** @param {number|string} key */ KeystoreSet.prototype.has = function (key) { const address = this.crc(key), map_or_set = this.index[address]; return map_or_set && map_or_set.has(key); }; /* KeystoreMap.prototype.size = KeystoreSet.prototype.size = function(){ let size = 0; const values = Object.values(this.index); for(let i = 0; i < values.length; i++){ size += values[i].size; } return size; }; */ KeystoreMap.prototype.delete = /** @param {number|string} key */ KeystoreSet.prototype.delete = function (key) { const address = this.crc(key), map_or_set = this.index[address]; map_or_set && map_or_set.delete(key) && this.size--; }; KeystoreMap.prototype.clear = KeystoreSet.prototype.clear = function () { this.index = create_object(); this.refs = []; this.size = 0; }; /** * @return Iterable */ KeystoreMap.prototype.values = KeystoreSet.prototype.values = function* () { for (let i = 0; i < this.refs.length; i++) { for (let value of this.refs[i].values()) { yield value; } } }; /** * @return Iterable */ KeystoreMap.prototype.keys = KeystoreSet.prototype.keys = function* () { for (let i = 0; i < this.refs.length; i++) { for (let key of this.refs[i].keys()) { yield key; } } }; /** * @return Iterable */ KeystoreMap.prototype.entries = KeystoreSet.prototype.entries = function* () { for (let i = 0; i < this.refs.length; i++) { for (let entry of this.refs[i].entries()) { yield entry; } } }; /** * Linear Congruential Generator (LCG) * @param {!number|bigint|string} str * @this {KeystoreMap|KeystoreSet} */ function lcg(str) { let range = 2 ** this.bit - 1; if ("number" == typeof str) { return str & range; } let crc = 0, bit = this.bit + 1; for (let i = 0; i < str.length; i++) { crc = (crc * bit ^ str.charCodeAt(i)) & range; } return 32 === this.bit ? crc + 2147483648 : crc; } /** * @param {!number|bigint|string} str * @this {KeystoreMap|KeystoreSet} */ function lcg64(str) { let range = BigInt(2) ** /** @type {!bigint} */this.bit - BigInt(1), type = typeof str; if ("bigint" == type) { return (/** @type {!bigint} */str & range ); } if ("number" == type) { return BigInt(str) & range; } let crc = BigInt(0), bit = /** @type {!bigint} */this.bit + BigInt(1); for (let i = 0; i < str.length; i++) { crc = (crc * bit ^ BigInt(str.charCodeAt(i))) & range; } return crc; } ================================================ FILE: dist/module-debug/lang/de.js ================================================ import { EncoderOptions } from "../type.js"; /** * Filter are also known as "stopwords", they completely filter out words from being indexed. * Source: http://www.ranks.nl/stopwords * Object Definition: Just provide an array of words. * @type {Set} */ export const filter = new Set(["aber", "als", "am", "an", "auch", "auf", "aus", "bei", "bin", "bis", "bist", "da", "dadurch", "daher", "darum", "das", "dass", "dass", "dein", "deine", "dem", "den", "der", "des", "dessen", "deshalb", "die", "dies", "dieser", "dieses", "doch", "dort", "du", "durch", "ein", "eine", "einem", "einen", "einer", "eines", "er", "es", "euer", "eure", "fuer", "hatte", "hatten", "hattest", "hattet", "hier", "hinter", "ich", "ihr", "ihre", "im", "in", "ist", "ja", "jede", "jedem", "jeden", "jeder", "jedes", "jener", "jenes", "jetzt", "ggf", "kann", "kannst", "koennen", "koennt", "machen", "mein", "meine", "mit", "muss", "musst", "musst", "muessen", "muesst", "nach", "nachdem", "nein", "nicht", "noch", "nun", "oder", "seid", "sein", "seine", "sich", "sie", "sind", "soll", "sollen", "sollst", "sollt", "sonst", "soweit", "sowie", "und", "unser", "unsere", "unter", "usw", "uvm", "vom", "von", "vor", "wann", "warum", "was", "weiter", "weitere", "wenn", "wer", "werde", "werden", "werdet", "weshalb", "wie", "wieder", "wieso", "wir", "wird", "wirst", "wo", "woher", "wohin", "zu", "zum", "zur", "ueber"]); /** * Stemmer removes word endings and is a kind of "partial normalization". A word ending just matched when the word length is bigger than the matched partial. * Example: The word "correct" and "correctness" could be the same word, so you can define {"ness": ""} to normalize the ending. * Object Definition: the key represents the word ending, the value contains the replacement (or empty string for removal). * http://snowball.tartarus.org/algorithms/german/stemmer.html * @type {Map} */ export const stemmer = new Map([["niss", ""], ["isch", ""], ["lich", ""], ["heit", ""], ["keit", ""], ["ell", ""], ["bar", ""], ["end", ""], ["ung", ""], ["est", ""], ["ern", ""], ["em", ""], ["er", ""], ["en", ""], ["es", ""], ["st", ""], ["ig", ""], ["ik", ""], ["e", ""], ["s", ""]]); /** * Matcher replaces all occurrences of a given string regardless of its position and is also a kind of "partial normalization". * Object Definition: the key represents the target term, the value contains the search string which should be replaced (could also be an array of multiple terms). * @type {Map} */ const map = new Map([["_", " "], ["ä", "ae"], ["ö", "oe"], ["ü", "ue"], ["ß", "ss"], ["&", " und "], ["€", " EUR "]]), options = { prepare: function (str) { if (/[_äöüß&€]/.test(str)) str = str.replace(/[_äöüß&€]/g, match => map.get(match)); return str.replace(/str\b/g, "strasse").replace(/(?!\b)strasse\b/g, " strasse"); }, filter: filter, stemmer: stemmer }; /** * @type EncoderOptions */ export default options; ================================================ FILE: dist/module-debug/lang/en.js ================================================ import { EncoderOptions } from "../type.js"; /** * http://www.ranks.nl/stopwords * @type {Set} */ export const filter = new Set(["a", "about", "above", "after", "again", "against", "all", "also", "am", "an", "and", "any", "are", "arent", "as", "at", "back", "be", "because", "been", "before", "being", "below", "between", "both", "but", "by", "can", "cannot", "cant", "come", "could", "couldnt", "did", "didnt", "do", "does", "doesnt", "doing", "dont", "down", "during", "each", "even", "few", "for", "from", "further", "get", "go", "good", "had", "hadnt", "has", "hasnt", "have", "havent", "having", "he", "hed", "her", "here", "heres", "hers", "herself", "hes", "him", "himself", "his", "how", "hows", "i", "id", "if", "ill", "im", "in", "into", "is", "isnt", "it", "its", "itself", "ive", "just", "know", "lets", "like", "lot", "make", "made", "me", "more", "most", "mustnt", "my", "myself", "new", "no", "nor", "not", "now", "of", "off", "on", "once", "one", "only", "or", "other", "ought", "our", "ours", "ourselves", "out", "over", "own", "same", "say", "see", "shant", "she", "shed", "shell", "shes", "should", "shouldnt", "so", "some", "such", "take", "than", "that", "thats", "the", "their", "theirs", "them", "themselves", "then", "there", "theres", "these", "they", "theyd", "theyll", "theyre", "theyve", "think", "this", "those", "through", "time", "times", "to", "too", "under", "until", "up", "us", "use", "very", "want", "was", "wasnt", "way", "we", "wed", "well", "were", "werent", "weve", "what", "whats", "when", "whens", "where", "wheres", "which", "while", "who", "whom", "whos", "why", "whys", "will", "with", "wont", "work", "would", "wouldnt", "ya", "you", "youd", "youll", "your", "youre", "yours", "yourself", "yourselves", "youve"]); /** * @type {Map} */ export const stemmer = new Map([["ization", ""], ["biliti", ""], ["icate", ""], ["ative", ""], ["ation", ""], ["iviti", ""], ["ement", ""], ["izer", ""], ["able", ""], ["ible", ""], ["alli", ""], ["ator", ""], ["less", ""], ["logi", ""], ["ical", ""], ["ance", ""], ["ence", ""], ["ness", ""], ["ble", ""], ["ment", ""], ["eli", ""], ["bli", ""], ["ful", ""], ["ant", ""], ["ent", ""], ["ism", ""], ["ate", ""], ["iti", ""], ["ous", ""], ["ive", ""], ["ize", ""], ["ing", ""], ["ion", ""], ["ies", "y"], ["al", ""], ["ou", ""], ["er", ""], ["ed", ""], ["ic", ""], ["ly", ""], ["li", ""], ["s", ""]]); /* he’s (= he is / he has) she’s (= she is / she has) I’ll (= I will) I’ve (= I have) I’d (= I would / I had) don’t (= do not) doesn’t (= does not) didn’t (= did not) isn’t (= is not) hasn’t (= has not) can’t (= cannot) won’t (= will not) */ /** * @type EncoderOptions */ const options = { prepare: function (str) { return str.replace(/´`’ʼ/g, "'").replace(/&/g, " and ").replace(/\$/g, " USD ").replace(/£/g, " GBP ").replace(/\bi'm\b/g, "i am").replace(/\b(can't|cannot)\b/g, "can not").replace(/\bwon't\b/g, "will not").replace(/([a-z])'s\b/g, "$1 is has").replace(/([a-z])n't\b/g, "$1 not").replace(/([a-z])'ll\b/g, "$1 will").replace(/([a-z])'re\b/g, "$1 are").replace(/([a-z])'ve\b/g, "$1 have").replace(/([a-z])'d\b/g, "$1 would had"); }, filter: filter, stemmer: stemmer }; export default options; ================================================ FILE: dist/module-debug/lang/fr.js ================================================ import { EncoderOptions } from "../type.js"; /** * http://www.ranks.nl/stopwords * http://snowball.tartarus.org/algorithms/french/stop.txt * @type {Set} */ export const filter = new Set(["au", "aux", "avec", "ce", "ces", "dans", "de", "des", "du", "elle", "en", "et", "eux", "il", "je", "la", "le", "leur", "lui", "ma", "mais", "me", "meme", "mes", "moi", "mon", "ne", "nos", "notre", "nous", "on", "ou", "par", "pas", "pour", "qu", "que", "qui", "sa", "se", "ses", "son", "sur", "ta", "te", "tes", "toi", "ton", "tu", "un", "une", "vos", "votre", "vous", "c", "d", "j", "l", "m", "n", "s", "t", "a", "y", "ete", "etee", "etees", "etes", "etant", "suis", "es", "est", "sommes", "etes", "sont", "serai", "seras", "sera", "serons", "serez", "seront", "serais", "serait", "serions", "seriez", "seraient", "etais", "etait", "etions", "etiez", "etaient", "fus", "fut", "fumes", "futes", "furent", "sois", "soit", "soyons", "soyez", "soient", "fusse", "fusses", "fut", "fussions", "fussiez", "fussent", "ayant", "eu", "eue", "eues", "eus", "ai", "as", "avons", "avez", "ont", "aurai", "auras", "aura", "aurons", "aurez", "auront", "aurais", "aurait", "aurions", "auriez", "auraient", "avais", "avait", "avions", "aviez", "avaient", "eut", "eumes", "eutes", "eurent", "aie", "aies", "ait", "ayons", "ayez", "aient", "eusse", "eusses", "eut", "eussions", "eussiez", "eussent", "ceci", "cela", "cela", "cet", "cette", "ici", "ils", "les", "leurs", "quel", "quels", "quelle", "quelles", "sans", "soi"]); /** * @type {Map} */ export const stemmer = new Map([["lement", ""], ["ient", ""], ["nera", ""], ["ment", ""], ["ais", ""], ["ait", ""], ["ant", ""], ["ent", ""], ["iez", ""], ["ion", ""], ["nez", ""], ["ai", ""], ["es", ""], ["er", ""], ["ez", ""], ["le", ""], ["na", ""], ["ne", ""], ["a", ""], ["e", ""]]); /** * @type EncoderOptions */ const options = { prepare: function (str) { return str.replace(/´`’ʼ/g, "'").replace(/_+/g, " ").replace(/&/g, " et ").replace(/€/g, " EUR ").replace(/\bl'([^\b])/g, "la le $1").replace(/\bt'([^\b])/g, "ta te $1").replace(/\bc'([^\b])/g, "ca ce $1").replace(/\bd'([^\b])/g, "da de $1").replace(/\bj'([^\b])/g, "ja je $1").replace(/\bn'([^\b])/g, "na ne $1").replace(/\bm'([^\b])/g, "ma me $1").replace(/\bs'([^\b])/g, "sa se $1").replace(/\bau\b/g, "a le").replace(/\baux\b/g, "a les").replace(/\bdu\b/g, "de le").replace(/\bdes\b/g, "de les"); }, filter: filter, stemmer: stemmer }; export default options; ================================================ FILE: dist/module-debug/preset.js ================================================ import { is_string } from "./common.js"; import { IndexOptions } from "./type.js"; /** * @type {Object} * @const */ const presets = { memory: { resolution: 1 }, performance: { resolution: 3, fastupdate: !0, context: { depth: 1, resolution: 1 } }, match: { tokenize: "full" }, score: { resolution: 9, context: { depth: 2, resolution: 3 } } }; /** * * @param {IndexOptions|string} options * @return {IndexOptions} */ export default function apply_preset(options) { const preset = /** @type string */is_string(options) ? options : options.preset; if (preset) { if (!presets[preset]) { console.warn("Preset not found: " + preset); } options = /** @type IndexOptions */Object.assign({}, presets[preset], /** @type {Object} */options); } return (/** @type IndexOptions */options ); } ================================================ FILE: dist/module-debug/profiler.js ================================================ import { create_object } from "./common.js"; const data = create_object(); /** * @param {!string} name */ export default function tick() {} ================================================ FILE: dist/module-debug/resolve/and.js ================================================ import Resolver from "../resolver.js"; import { get_max_len } from "../common.js"; import { intersect } from "../intersect.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } * @this {Resolver} */ Resolver.prototype.and = function () { return this.handler("and", return_result, arguments); }; /** * Aggregate the intersection of N raw results * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight) { if (!suggest && !this.result.length) { return resolve ? this.result : this; } let resolved; if (!final.length) { if (!suggest) { this.result = /** @type {SearchResults|IntermediateSearchResults} */final; } } else { this.result.length && final.unshift(this.result); if (2 > final.length) { this.result = final[0]; } else { let resolution = 0; for (let i = 0, res, len; i < final.length; i++) { if ((res = final[i]) && (len = res.length)) { if (resolution < len) { resolution = len; } } else if (!suggest) { resolution = 0; break; } } if (!resolution) { this.result = []; } else { this.result = intersect(final, resolution, limit, offset, suggest, this.boostval, resolve); resolved = !0; } } } if (resolve) { this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight, resolved) : this; } ================================================ FILE: dist/module-debug/resolve/default.js ================================================ import { concat } from "../common.js"; import { IntermediateSearchResults, SearchResults, EnrichedSearchResults } from "../type.js"; import { apply_enrich } from "../document/search.js"; import Document from "../document.js"; import Index from "../index.js"; import WorkerIndex from "../worker.js"; /* from -> res[score][id] to -> [id] */ /** * Aggregate the union of a single raw result * @param {IntermediateSearchResults} result * @param {!number} limit * @param {number=} offset * @param {boolean=} enrich * @return {SearchResults|EnrichedSearchResults} * @this {Document|Index|WorkerIndex} */ export default function (result, limit, offset, enrich) { if (!result.length) { return result; } if (1 === result.length) { let final = result[0]; final = offset || final.length > limit ? final.slice(offset, offset + limit) : final; return enrich ? /** @type {EnrichedSearchResults} */apply_enrich.call(this, final) : final; } let final = []; for (let i = 0, arr, len; i < result.length; i++) { if (!(arr = result[i]) || !(len = arr.length)) continue; if (offset) { if (offset >= len) { offset -= len; continue; } arr = arr.slice(offset, offset + limit); len = arr.length; offset = 0; } if (len > limit) { arr = arr.slice(0, limit); len = limit; } if (!final.length) { if (len >= limit) { return enrich ? /** @type {EnrichedSearchResults} */apply_enrich.call(this, arr) : arr; } } final.push(arr); limit -= len; if (!limit) { break; } } final = 1 < final.length ? concat(final) : final[0]; return enrich ? apply_enrich.call(this, final) : final; } ================================================ FILE: dist/module-debug/resolve/handler.js ================================================ import Resolver from "../resolver.js"; import { ResolverOptions, SearchResults, EnrichedSearchResults, IntermediateSearchResults } from "../type.js"; /** * @param {string} method * @param {Function} fn * @param {Array|Arguments} args * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ Resolver.prototype.handler = function (method, fn, args) { /** @type {ResolverOptions} */ let arg = args[0]; if (arg[0] && arg[0].query) { return this[method].apply(this, arg); } if ("and" === method || "not" === method) { let execute = this.result.length || this.await, resolve; if (!execute) { if (!arg.suggest) { if (1 < args.length) { arg = args[args.length - 1]; } resolve = arg.resolve; return resolve ? this.await || this.result : this; } } } const self = this; /** @type {!Array>} */ let final = [], limit = 0, offset = 0, enrich, resolve, suggest, highlight, async; for (let i = 0, query; i < args.length; i++) { /** @type {ResolverOptions} */ query = args[i]; if (query) { let result; if (query.constructor === Resolver) { result = query.await || query.result; } else if (query.then || query.constructor === Array) { result = query; } else { limit = query.limit || 0; offset = query.offset || 0; suggest = query.suggest; resolve = query.resolve; highlight = query.highlight || this.highlight; enrich = (highlight || query.enrich) && resolve; let opt_queue = query.queue, opt_async = query.async || opt_queue, index = query.index, query_value = query.query; if (index) { this.index || (this.index = index); } else { index = this.index; } if (query_value || query.tag) { if (!index) { throw new Error("Resolver can't apply because the corresponding Index was never specified"); } { const field = query.field || query.pluck; if (field) { if (query_value && (!this.query || highlight)) { this.query = query_value; this.field = field; this.highlight = highlight; } if (!index.index) { throw new Error("Resolver can't apply because the corresponding Document Index was not specified"); } index = index.index.get(field); if (!index) { throw new Error("Resolver can't apply because the specified Document Field '" + field + "' was not found"); } } } if (opt_queue && (async || this.await)) { async = 1; let resolve; const idx = this.promises.length, promise = new Promise(function (_resolve) { resolve = _resolve; }); (function (index, query) { promise._fn = function () { query.index = null; query.resolve = !1; query.enrich = !1; let result = opt_async ? index.searchAsync(query) : index.search(query); if (result.then) { return result.then(function (result) { self.promises[idx] = result = result.result || result; resolve(result); return result; }); } result = result.result || result; resolve(result); return result; }; })(index, Object.assign({}, /** @type Object */query)); this.promises.push(promise); final[i] = promise; continue; } else { query.resolve = !1; query.enrich = !1; query.index = null; result = opt_async ? index.searchAsync(query) : index.search(query); query.resolve = resolve; query.enrich = enrich; query.index = index; } } else if (query.and) { result = inner_call(query, "and", index); } else if (query.or) { result = inner_call(query, "or", index); } else if (query.not) { result = inner_call(query, "not", index); } else if (query.xor) { result = inner_call(query, "xor", index); } else { continue; } } if (result.await) { async = 1; result = result.await; } else if (result.then) { async = 1; result = result.then(function (result) { return result.result || result; }); } else { result = result.result || result; } final[i] = result; } } if (async && !this.await) { this.await = new Promise(function (resolve) { self.return = resolve; }); } if (async) { const promises = Promise.all(final).then(function (final) { for (let i = 0; i < self.promises.length; i++) { if (self.promises[i] === promises) { self.promises[i] = function () { return fn.call(self, final, limit, offset, enrich, resolve, suggest, highlight); }; break; } } self.execute(); }); this.promises.push(promises); } else if (this.await) { this.promises.push(function () { return fn.call(self, final, limit, offset, enrich, resolve, suggest, highlight); }); } else { return fn.call(this, final, limit, offset, enrich, resolve, suggest, highlight); } return resolve ? this.await || this.result : this; }; function inner_call(query, method, index) { const args = query[method], arg = args[0] || args; arg.index || (arg.index = index); let resolver = new Resolver(arg); if (1 < args.length) { resolver = resolver[method].apply(resolver, args.slice(1)); } return resolver; } ================================================ FILE: dist/module-debug/resolve/not.js ================================================ import Resolver from "../resolver.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** @this {Resolver} */ Resolver.prototype.not = function () { return this.handler("not", return_result, arguments); }; /** * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight) { if (!suggest && !this.result.length) { return resolve ? this.result : this; } let resolved; if (final.length && this.result.length) { this.result = exclusion.call(this, final, limit, offset, resolve); resolved = !0; } if (resolve) { this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight, resolved) : this; } /** * @param {!Array} result * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @this {Resolver} * @return {SearchResults|IntermediateSearchResults} */ function exclusion(result, limit, offset, resolve) { /** @type {SearchResults|IntermediateSearchResults} */ const final = [], exclude = new Set(result.flat().flat()); for (let j = 0, ids, count = 0; j < this.result.length; j++) { ids = this.result[j]; if (!ids) continue; for (let k = 0, id; k < ids.length; k++) { id = ids[k]; if (!exclude.has(id)) { if (offset) { offset--; continue; } if (resolve) { final.push(id); if (final.length === limit) { return final; } } else { final[j] || (final[j] = []); final[j].push(id); if (++count === limit) { return final; } } } } } return final; } ================================================ FILE: dist/module-debug/resolve/or.js ================================================ import Resolver from "../resolver.js"; import { union } from "../intersect.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** @this {Resolver} */ Resolver.prototype.or = function () { return this.handler("or", return_result, arguments); }; /** * Aggregate the intersection of N raw results * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight) { if (final.length) { this.result.length && final.push(this.result); if (2 > final.length) { this.result = final[0]; } else { this.result = union(final, limit, offset, !1, this.boostval); offset = 0; } } if (resolve) { this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight) : this; } ================================================ FILE: dist/module-debug/resolve/xor.js ================================================ import Resolver from "../resolver.js"; import { create_object } from "../common.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** @this {Resolver} */ Resolver.prototype.xor = function () { return this.handler("xor", return_result, arguments); }; /** * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight) { let resolved; if (!final.length) { if (!suggest) this.result = /** @type {SearchResults|IntermediateSearchResults} */final; } else { this.result.length && final.unshift(this.result); if (2 > final.length) { this.result = final[0]; } else { this.result = exclusive.call(this, final, limit, offset, resolve, this.boostval); resolved = !0; } } if (resolve) { this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight, resolved) : this; } /** * Aggregate the intersection of N raw results * @param {!Array} result * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {number=} boost * @this {Resolver} * @return {SearchResults|IntermediateSearchResults} */ function exclusive(result, limit, offset, resolve, boost) { /** @type {SearchResults|IntermediateSearchResults} */ const final = [], check = create_object(); let maxres = 0; for (let i = 0, res; i < result.length; i++) { res = result[i]; if (!res) continue; if (maxres < res.length) maxres = res.length; for (let j = 0, ids; j < res.length; j++) { ids = res[j]; if (!ids) continue; for (let k = 0, id; k < ids.length; k++) { id = ids[k]; check[id] = check[id] ? 2 : 1; } } } for (let j = 0, ids, count = 0; j < maxres; j++) { for (let i = 0, res; i < result.length; i++) { res = result[i]; if (!res) continue; ids = res[j]; if (!ids) continue; for (let k = 0, id; k < ids.length; k++) { id = ids[k]; if (1 === check[id]) { if (offset) { offset--; continue; } if (resolve) { final.push(id); if (final.length === limit) { return final; } } else { const index = j + (i ? boost : 0); final[index] || (final[index] = []); final[index].push(id); if (++count === limit) { return final; } } } } } } return final; } ================================================ FILE: dist/module-debug/resolver.js ================================================ import { ResolverOptions, IntermediateSearchResults, SearchResults, EnrichedSearchResults, HighlightOptions } from "./type.js"; import Index from "./index.js"; import Document from "./document.js"; import WorkerIndex from "./worker.js"; import default_resolver from "./resolve/default.js"; import "./resolve/handler.js"; import "./resolve/or.js"; import "./resolve/and.js"; import "./resolve/xor.js"; import "./resolve/not.js"; import { apply_enrich } from "./document/search.js"; import { highlight_fields } from "./document/highlight.js"; /** * @param {IntermediateSearchResults|ResolverOptions=} result * @param {Index|Document|WorkerIndex=} index * @return {Resolver} * @constructor */ export default function Resolver(result, index) { if (!this || this.constructor !== Resolver) { return new Resolver(result, index); } let boost = 0, promises, query, field, highlight, _await, _return; if (result && result.index) { const options = /** @type {ResolverOptions} */result; index = options.index; boost = options.boost || 0; if (query = options.query) { field = options.field || options.pluck; highlight = options.highlight; const resolve = options.resolve, async = options.async || options.queue; options.resolve = !1; options.highlight = ""; options.index = null; result = async ? index.searchAsync(options) : index.search(options); options.resolve = resolve; options.highlight = highlight; options.index = index; result = result.result || result; } else { result = []; } } if (result && result.then) { const self = this; result = result.then(function (result) { self.promises[0] = self.result = result.result || result; self.execute(); }); promises = [result]; result = []; _await = new Promise(function (resolve) { _return = resolve; }); } /** @type {Index|Document|WorkerIndex|null} */ this.index = index || null; /** @type {IntermediateSearchResults} */ this.result = /** @type {IntermediateSearchResults} */result || []; /** @type {number} */ this.boostval = boost; /** @type {Array|IntermediateSearchResults|Function>} */ this.promises = promises || []; /** @type {Promise} */ this.await = _await || null; /** @type {Function} */ this.return = _return || null; /** @type {HighlightOptions|null} */ this.highlight = /** @type {HighlightOptions|null} */highlight || null; /** @type {string} */ this.query = query || ""; /** @type {string} */ this.field = field || ""; } /** * @param {number} limit */ Resolver.prototype.limit = function (limit) { if (this.await) { const self = this; this.promises.push(function () { return self.limit(limit).result; }); } else { if (this.result.length) { /** @type {IntermediateSearchResults} */ const final = []; for (let j = 0, ids; j < this.result.length; j++) { if (ids = this.result[j]) { if (ids.length <= limit) { final[j] = ids; limit -= ids.length; if (!limit) break; } else { final[j] = ids.slice(0, limit); break; } } } this.result = final; } } return this; }; /** * @param {number} offset */ Resolver.prototype.offset = function (offset) { if (this.await) { const self = this; this.promises.push(function () { return self.offset(offset).result; }); } else { if (this.result.length) { /** @type {IntermediateSearchResults} */ const final = []; for (let j = 0, ids; j < this.result.length; j++) { if (ids = this.result[j]) { if (ids.length <= offset) { offset -= ids.length; } else { final[j] = ids.slice(offset); offset = 0; } } } this.result = final; } } return this; }; /** * @param {number} boost */ Resolver.prototype.boost = function (boost) { if (this.await) { const self = this; this.promises.push(function () { return self.boost(boost).result; }); } else { this.boostval += boost; } return this; }; /** * @param {boolean=} _skip_callback * @this {Resolver} */ Resolver.prototype.execute = function (_skip_callback) { let result = this.result, execute = this.await; this.await = null; for (let i = 0, promise; i < this.promises.length; i++) { if (promise = this.promises[i]) { if ("function" == typeof promise) { result = promise(); this.promises[i] = result = result.result || result; i--; } else if (promise._fn) { result = promise._fn(); this.promises[i] = result = result.result || result; i--; } else if (promise.then) { return this.await = execute; } } } const fn = this.return; this.promises = []; this.return = null; _skip_callback || fn(result); return result; }; /** * @param {number|ResolverOptions=} limit * @param {number=} offset * @param {boolean=} enrich * @param {string|HighlightOptions|boolean=} highlight * @param {boolean=} _resolved */ Resolver.prototype.resolve = function (limit, offset, enrich, highlight, _resolved) { let result = this.await ? this.execute(!0) : this.result; if (result.then) { const self = this; return result.then(function () { return self.resolve(limit, offset, enrich, highlight, _resolved); }); } if (result.length) { if ("object" == typeof limit) { highlight = limit.highlight || this.highlight; enrich = !!highlight || limit.enrich; offset = limit.offset; limit = limit.limit; } else { highlight = highlight || this.highlight; enrich = !!highlight || enrich; } result = _resolved ? enrich ? apply_enrich.call( /** @type {Document} */this.index, /** @type {SearchResults} */result) : result : default_resolver.call(this.index, result, limit || 100, offset, enrich); } return this.finalize(result, highlight); }; Resolver.prototype.finalize = function (result, highlight) { if (result.then) { const self = this; return result.then(function (result) { return self.finalize(result, highlight); }); } if (highlight && !this.query) { console.warn('There was no query specified for highlighting. Please specify a query within the highlight resolver stage like { query: "...", highlight: ... }.'); } if (highlight && result.length && this.query) { result = highlight_fields(this.query, result, this.index.index, this.field, highlight); } const fn = this.return; this.index = this.result = this.promises = this.await = this.return = null; this.highlight = null; this.query = this.field = ""; fn && fn(result); return result; }; ================================================ FILE: dist/module-debug/serialize.js ================================================ import Index from "./index.js"; import Document from "./document.js"; import { KeystoreMap, KeystoreSet } from "./keystore.js"; import { is_string } from "./common.js"; const chunk_size_reg = 250000, chunk_size_map = 5000, chunk_size_ctx = 1000; /** * @param {Map|KeystoreMap} map * @param {number=} size * @return {Array} */ function map_to_json(map, size = 0) { let chunk = [], json = []; if (size) { size = 0 | chunk_size_map * (chunk_size_reg / size); } for (const item of map.entries()) { json.push(item); if (json.length === size) { chunk.push(json); json = []; } } json.length && chunk.push(json); return chunk; } /** * @param {Array} json * @param {Map|KeystoreMap} map * @return {Map|KeystoreMap} */ function json_to_map(json, map) { map || (map = new Map()); for (let i = 0, entry; i < json.length; i++) { entry = json[i]; map.set(entry[0], entry[1]); } return (/** @type {Map} */map ); } /** * @param {Map>|KeystoreMap>} ctx * @param {number=} size * @return {Array} */ function ctx_to_json(ctx, size = 0) { let chunk = [], json = []; if (size) { size = 0 | chunk_size_ctx * (chunk_size_reg / size); } for (const item of ctx.entries()) { const key = item[0], value = item[1]; json.push([key, map_to_json(value)[0] || []]); if (json.length === size) { chunk.push(json); json = []; } } json.length && chunk.push(json); return chunk; } /** * @param {Array} json * @param {Map>|KeystoreMap>} ctx * @return {Map>|KeystoreMap>} */ function json_to_ctx(json, ctx) { ctx || (ctx = new Map()); for (let i = 0, entry, map; i < json.length; i++) { entry = json[i]; map = ctx.get(entry[0]); ctx.set(entry[0], json_to_map(entry[1], map)); } return ctx; } /** * @param { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } reg * @return {Array>} */ function reg_to_json(reg) { let chunk = [], json = []; for (const key of reg.keys()) { json.push(key); if (json.length === chunk_size_reg) { chunk.push(json); json = []; } } json.length && chunk.push(json); return chunk; } /** * @param {Array} json * @param { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } reg * @return { * Set| * KeystoreSet * } */ function json_to_reg(json, reg) { reg || (reg = new Set()); for (let i = 0; i < json.length; i++) { reg.add(json[i]); } return (/** @type {Set} */reg ); } /** * * @param {function(string, string):Promise|void} callback * @param {string|null|void} field * @param {string} key * @param {Array|null} chunk * @param {number} index_doc * @param {number} index_obj * @param {number=} index_prt * @this {Index|Document} * @return {Promise} */ function save(callback, field, key, chunk, index_doc, index_obj, index_prt = 0) { const is_arr = chunk && chunk.constructor === Array, data = is_arr ? chunk.shift() : chunk; if (!data) { return this.export(callback, field, index_doc, index_obj + 1); } const res = callback((field ? field + "." : "") + (index_prt + 1) + "." + key, JSON.stringify(data)); if (res && res.then) { const self = this; return res.then(function () { return save.call(self, callback, field, key, is_arr ? chunk : null, index_doc, index_obj, index_prt + 1); }); } return save.call(this, callback, field, key, is_arr ? chunk : null, index_doc, index_obj, index_prt + 1); } /** * @param {function(string,string):Promise|void} callback * @param {!string|null=} _field * @param {number=} _index_doc * @param {number=} _index_obj * @this {Index} */ export function exportIndex(callback, _field, _index_doc = 0, _index_obj = 0) { let key, chunk; switch (_index_obj) { case 0: key = "reg"; chunk = reg_to_json(this.reg); break; case 1: key = "cfg"; chunk = null; break; case 2: key = "map"; chunk = map_to_json(this.map, this.reg.size); break; case 3: key = "ctx"; chunk = ctx_to_json(this.ctx, this.reg.size); break; default: return; } return save.call(this, callback, _field, key, chunk, _index_doc, _index_obj); } /** * @param {string} key * @param {string|Array=} data * @this Index */ export function importIndex(key, data) { if (!data) { return; } if ("string" == typeof data) { data = /** @type {Array} */JSON.parse( /** @type {string} */data); } const split = key.split("."); if ("json" === split[split.length - 1]) { split.pop(); } if (3 === split.length) { split.shift(); } key = 1 < split.length ? split[1] : split[0]; switch (key) { case "cfg": break; case "reg": this.fastupdate = !1; this.reg = json_to_reg( /** @type {Array} */data, this.reg); break; case "map": this.map = json_to_map(data, this.map); break; case "ctx": this.ctx = json_to_ctx(data, this.ctx); break; } } /** * @param {function(string,string):Promise|void} callback * @param {string|null=} _field * @param {number=} _index_doc * @param {number=} _index_obj * @this {Document} */ export function exportDocument(callback, _field, _index_doc = 0, _index_obj = 0) { if (_index_doc < this.field.length) { const field = this.field[_index_doc], idx = this.index.get(field), res = idx.export(callback, field, _index_doc, _index_obj = 1); if (res && res.then) { const self = this; return res.then(function () { return self.export(callback, field, _index_doc + 1); }); } return this.export(callback, field, _index_doc + 1); } else { let key, chunk; switch (_index_obj) { case 0: key = "reg"; chunk = reg_to_json(this.reg); _field = null; break; case 1: key = "tag"; chunk = this.tag && ctx_to_json(this.tag, this.reg.size); _field = null; break; case 2: key = "doc"; chunk = this.store && map_to_json( /** @type {Map} */this.store); _field = null; break; default: return; } return save.call(this, callback, _field, key, /** @type {Array|null} */chunk || null, _index_doc, _index_obj); } } /** * @param {!string} key * @param {string|Array} data * @this {Document} */ export function importDocument(key, data) { const split = key.split("."); if ("json" === split[split.length - 1]) { split.pop(); } const field = 2 < split.length ? split[0] : "", ref = 2 < split.length ? split[2] : split[1]; if (this.worker && field) { return this.index.get(field).import(key); } if (!data) { return; } if ("string" == typeof data) { data = /** @type {Array} */JSON.parse( /** @type {string} */data); } if (!field) { switch (ref) { case "reg": this.fastupdate = !1; this.reg = json_to_reg( /** @type {Array} */data, this.reg); for (let i = 0, idx; i < this.field.length; i++) { idx = this.index.get(this.field[i]); idx.fastupdate = !1; idx.reg = this.reg; } if (this.worker) { const promises = [], self = this; for (const index of this.index.values()) { promises.push(index.import(key)); } return Promise.all(promises); } break; case "tag": this.tag = json_to_ctx(data, this.tag); break; case "doc": this.store = json_to_map(data, this.store); break; case "cfg": break; } } else { return this.index.get(field).import(ref, data); } } /* reg: "1,2,3,4,5,6,7,8,9" map: "gulliver:1,2,3|4,5,6|7,8,9;" ctx: "gulliver+travel:1,2,3|4,5,6|7,8,9;" */ /** * @this {Index} * @param {boolean} withFunctionWrapper * @return {string} */ export function serialize(withFunctionWrapper = !0) { let reg = '', map = '', ctx = ''; if (this.reg.size) { let type; for (const key of this.reg.keys()) { type || (type = typeof key); reg += (reg ? ',' : '') + ("string" === type ? '"' + key + '"' : key); } reg = 'index.reg=new Set([' + reg + ']);'; map = parse_map(this.map, type); map = "index.map=new Map([" + map + "]);"; for (const context of this.ctx.entries()) { const key_ctx = context[0], value_ctx = context[1]; let ctx_map = parse_map(value_ctx, type); ctx_map = "new Map([" + ctx_map + "])"; ctx_map = '["' + key_ctx + '",' + ctx_map + ']'; ctx += (ctx ? ',' : '') + ctx_map; } ctx = "index.ctx=new Map([" + ctx + "]);"; } return withFunctionWrapper ? "function inject(index){" + reg + map + ctx + "}" : reg + map + ctx; } function parse_map(map, type) { let result = ''; for (const item of map.entries()) { const key = item[0], value = item[1]; let res = ''; for (let i = 0, ids; i < value.length; i++) { ids = value[i] || ['']; let str = ''; for (let j = 0; j < ids.length; j++) { str += (str ? ',' : '') + ("string" === type ? '"' + ids[j] + '"' : ids[j]); } str = '[' + str + ']'; res += (res ? ',' : '') + str; } res = '["' + key + '",[' + res + ']]'; result += (result ? ',' : '') + res; } return result; } ================================================ FILE: dist/module-debug/type.js ================================================ import Index from "./index.js"; import Document from "./document.js"; import WorkerIndex from "./worker.js"; import Encoder from "./encoder.js"; import StorageInterface from "./db/interface.js"; /** * @typedef {{ * preset: (string|undefined), * context: (IndexOptions|undefined), * encoder: (Encoder|Function|Object|undefined), * encode: (function(string):Array|undefined), * resolution: (number|undefined), * tokenize: (string|undefined), * fastupdate: (boolean|undefined), * score: (function():number|undefined), * keystore: (number|undefined), * rtl: (boolean|undefined), * cache: (number|boolean|undefined), * db: (StorageInterface|undefined), * commit: (boolean|undefined), * worker: (string|undefined), * config: (string|undefined), * priority: (number|undefined), * export: (Function|undefined), * import: (Function|undefined) * }} */ export let IndexOptions = {}; /** * @typedef {{ * preset: (string|undefined), * context: (IndexOptions|undefined), * encoder: (Encoder|Function|Object|undefined), * encode: (Function|undefined), * resolution: (number|undefined), * tokenize: (string|undefined), * fastupdate: (boolean|undefined), * score: (Function|undefined), * keystore: (number|undefined), * rtl: (boolean|undefined), * cache: (number|boolean|undefined), * db: (StorageInterface|undefined), * commit: (boolean|undefined), * config: (string|undefined), * priority: (number|undefined), * field: (string|undefined), * filter: (Function|undefined), * custom: (Function|undefined) * }} */ export let FieldOptions = {}; /** * @typedef {{ * context: (IndexOptions|undefined), * encoder: (Encoder|Function|Object|undefined), * encode: (Function|undefined), * resolution: (number|undefined), * tokenize: (string|undefined), * fastupdate: (boolean|undefined), * score: (Function|undefined), * keystore: (number|undefined), * rtl: (boolean|undefined), * cache: (number|boolean|undefined), * db: (StorageInterface|undefined), * doc: (DocumentDescriptor|Array|undefined), * document: (DocumentDescriptor|Array|undefined), * worker: (boolean|string|undefined), * priority: (number|undefined), * export: (Function|undefined), * import: (Function|undefined) * }} */ export let DocumentOptions = {}; /** * @typedef {{ * depth: (number|undefined), * bidirectional: (boolean|undefined), * resolution: (number|undefined), * }} */ export let ContextOptions = {}; /** * @typedef {{ * id: (string|undefined), * field: (string|Array|FieldOptions|Array|undefined), * index: (string|Array|FieldOptions|Array|undefined), * tag: (string|Array|TagOptions|Array|undefined), * store: (string|Array|StoreOptions|Array|boolean|undefined), * }} */ export let DocumentDescriptor = {}; /** * @typedef {{ * field: string, * filter: ((function(string):boolean)|undefined), * custom: ((function(string):string)|undefined), * db: (StorageInterface|undefined), * }} */ export let TagOptions = {}; /** * @typedef {{ * field: string, * filter: ((function(string):boolean)|undefined), * custom: ((function(string):string)|undefined) * }} */ export let StoreOptions = {}; /** * @typedef {{ * query: (string|undefined), * limit: (number|undefined), * offset: (number|undefined), * resolution: (number|undefined), * context: (boolean|undefined), * suggest: (boolean|undefined), * resolve: (boolean|undefined), * enrich: (boolean|undefined), * cache: (boolean|undefined) * }} */ export let SearchOptions = {}; /** * @typedef {{ * query: (string|undefined), * limit: (number|undefined), * offset: (number|undefined), * resolution: (number|undefined), * context: (boolean|undefined), * suggest: (boolean|undefined), * resolve: (boolean|undefined), * enrich: (boolean|undefined), * cache: (boolean|undefined), * tag: (Object|Array>|undefined), * field: (Array|Array|DocumentSearchOptions|string|undefined), * index: (Array|Array|DocumentSearchOptions|string|undefined), * pluck: (string|DocumentSearchOptions|undefined), * merge: (boolean|undefined), * highlight: (HighlightOptions|string|undefined) * }} */ export let DocumentSearchOptions = {}; /** * @typedef Array * @global */ export let SearchResults = []; /** * @typedef Array * @global */ export let IntermediateSearchResults = []; /** * @typedef Array<{ * id: (number|string), * doc: (Object|null), * highlight: (string|undefined) * }> */ export let EnrichedSearchResults = []; /** * @typedef Array<{ * field: (string|undefined), * tag: (string|undefined), * result: SearchResults * }> */ export let DocumentSearchResults = []; /** * @typedef Array<{ * field: (string|undefined), * tag: (string|undefined), * result: EnrichedSearchResults * }> */ export let EnrichedDocumentSearchResults = []; /** * @typedef {{ * id: (number|string), * doc: (Object|null|undefined), * field: (Array|undefined), * tag: (Array|undefined), * highlight: (Object|undefined) * }} */ export let MergedDocumentSearchEntry = {}; /** * @typedef Array */ export let MergedDocumentSearchResults = []; /** * @typedef {{ * letter: (boolean|undefined), * number: (boolean|undefined), * symbol: (boolean|undefined), * punctuation: (boolean|undefined), * control: (boolean|undefined), * char: (string|Array|undefined) * }} */ export let EncoderSplitOptions = {}; /** * @typedef {{ * rtl: (boolean|undefined), * dedupe: (boolean|undefined), * include: (EncoderSplitOptions|undefined), * exclude: (EncoderSplitOptions|undefined), * split: (string|boolean|RegExp|undefined), * numeric: (boolean|undefined), * normalize: (boolean|(function(string):string)|undefined), * prepare: ((function(string):string)|undefined), * finalize: ((function(Array):(Array|void))|undefined), * filter: (Set|function(string):boolean|undefined), * matcher: (Map|undefined), * mapper: (Map|undefined), * stemmer: (Map|undefined), * replacer: (Array|undefined), * minlength: (number|undefined), * maxlength: (number|undefined), * cache: (boolean|undefined) * }} */ export let EncoderOptions = {}; /** * @typedef {{ * name: (string|undefined), * field: (string|undefined), * type: (string|undefined), * db: (StorageInterface|undefined) * }} */ export let PersistentOptions = {}; /** * @typedef {{ * index: (Index|Document|WorkerIndex|undefined), * query: (string|undefined), * pluck: (string|undefined), * field: (string|undefined), * limit: (number|undefined), * offset: (number|undefined), * boost: (number|undefined), * enrich: (boolean|undefined), * highlight: (HighlightOptions|string|undefined), * resolve: (boolean|undefined), * suggest: (boolean|undefined), * cache: (boolean|undefined), * async: (boolean|undefined), * queue: (boolean|undefined), * and: (ResolverOptions|Array|undefined), * or: (ResolverOptions|Array|undefined), * xor: (ResolverOptions|Array|undefined), * not: (ResolverOptions|Array|undefined) * }} */ export let ResolverOptions = {}; /** * @typedef {{ * before: (number|undefined), * after: (number|undefined), * total: (number|undefined) * }} */ export let HighlightBoundaryOptions = {}; /** * @typedef {{ * template: string, * pattern: (string|boolean|undefined) * }} */ export let HighlightEllipsisOptions = {}; /** * @typedef {{ * template: string, * boundary: (HighlightBoundaryOptions|number|undefined), * clip: (boolean|undefined), * merge: (boolean|undefined), * ellipsis: (HighlightEllipsisOptions|string|boolean|undefined) * }} */ export let HighlightOptions = {}; ================================================ FILE: dist/module-debug/worker/handler.js ================================================ import Index from "../index.js"; import { IndexOptions } from "../type.js"; /** @type Index */ let index, options; /** @type {IndexOptions} */ export default (async function (data) { data = data.data; const task = data.task, id = data.id; let args = data.args; switch (task) { case "init": options = /** @type {IndexOptions} */data.options || {}; let filepath = options.config; if (filepath) { options = options; } const factory = data.factory; if (factory) { Function("return " + factory)()(self); index = new self.FlexSearch.Index(options); delete self.FlexSearch; } else { index = new Index(options); } postMessage({ id: id }); break; default: let message; if ("export" === task) { if (!options.export || "function" != typeof options.export) { throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"export\"."); } if (!args[1]) args = null;else { args[0] = options.export; args[2] = 0; args[3] = 1; } } if ("import" === task) { if (!options.import || "function" != typeof options.import) { throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"import\"."); } if (args[0]) { const data = await options.import.call(index, args[0]); index.import(args[0], data); } } else { message = args && index[task].apply(index, args); if (message && message.then) { message = await message; } if (message && message.await) { message = await message.await; } if ("search" === task && message.result) { message = message.result; } } postMessage("search" === task ? { id: id, msg: message } : { id: id }); } }); ================================================ FILE: dist/module-debug/worker/node.js ================================================ /* * Node.js Worker (CommonJS) * This file is a standalone file and isn't being a part of the build/bundle */ const { parentPort } = require("worker_threads"), { Index } = require("flexsearch"); /** @type Index */ let index, options; /** @type {IndexOptions} */ parentPort.on("message", async function (data) { const task = data.task, id = data.id; let args = data.args; switch (task) { case "init": options = data.options || {}; let filepath = options.config; if (filepath) { options = Object.assign({}, options, require(filepath)); delete options.worker; } index = new Index(options); parentPort.postMessage({ id: id }); break; default: let message; if ("export" === task) { if (!options.export || "function" != typeof options.export) { throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"export\"."); } if (!args[1]) args = null;else { args[0] = options.export; args[2] = 0; args[3] = 1; } } if ("import" === task) { if (!options.import || "function" != typeof options.import) { throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"import\"."); } if (args[0]) { const data = await options.import.call(index, args[0]); index.import(args[0], data); } } else { message = args && index[task].apply(index, args); if (message && message.then) { message = await message; } if (message && message.await) { message = await message.await; } if ("search" === task && message.result) { message = message.result; } } parentPort.postMessage("search" === task ? { id: id, msg: message } : { id: id }); } }); ================================================ FILE: dist/module-debug/worker/worker.js ================================================ import handler from "./handler.js"; onmessage = handler; ================================================ FILE: dist/module-debug/worker.js ================================================ import { IndexOptions } from "./type.js"; import { create_object } from "./common.js"; import { searchCache } from "./cache.js"; import handler from "./worker/handler.js"; import apply_async from "./async.js"; import Encoder from "./encoder.js"; let pid = 0; /** * @param {IndexOptions=} options * @param {Encoder=} encoder * @constructor */ export default function WorkerIndex(options = /** @type IndexOptions */{}, encoder) { if (!this || this.constructor !== WorkerIndex) { return new WorkerIndex(options); } let factory = "undefined" != typeof self ? self._factory : "undefined" != typeof window ? window._factory : null; if (factory) { factory = factory.toString(); } const is_node_js = "undefined" == typeof window, _self = this; /** * @this {WorkerIndex} */ function init(worker) { this.worker = worker; this.resolver = create_object(); if (!this.worker) { console.warn("Worker is not available on this platform. Please report on Github: https://github.com/nextapps-de/flexsearch/issues"); return; } function onmessage(msg) { msg = msg.data || msg; const id = msg.id, res = id && _self.resolver[id]; if (res) { res(msg.msg); delete _self.resolver[id]; } } is_node_js ? this.worker.on("message", onmessage) : this.worker.onmessage = onmessage; if (options.config) { return new Promise(function (resolve) { if (1e9 < pid) pid = 0; _self.resolver[++pid] = function () { resolve(_self); }; _self.worker.postMessage({ id: pid, task: "init", factory: factory, options: options }); }); } this.priority = options.priority || 4; this.encoder = encoder || null; this.worker.postMessage({ task: "init", factory: factory, options: options }); return this; } const worker = create(factory, is_node_js, options.worker); return worker.then ? worker.then(function (worker) { return init.call(_self, worker); }) : init.call(this, worker); } register("add"); register("append"); register("search"); register("update"); register("remove"); register("clear"); register("export"); register("import"); WorkerIndex.prototype.searchCache = searchCache; apply_async(WorkerIndex.prototype); function register(key) { WorkerIndex.prototype[key] = function () { const self = this, args = [].slice.call(arguments), arg = args[args.length - 1]; let callback; if ("function" == typeof arg) { callback = arg; args.pop(); } const promise = new Promise(function (resolve) { if ("export" === key && "function" == typeof args[0]) { args[0] = null; } if (1e9 < pid) pid = 0; self.resolver[++pid] = resolve; self.worker.postMessage({ task: key, id: pid, args: args }); }); if (callback) { promise.then(callback); return this; } else { return promise; } }; } function create(factory, is_node_js, worker_path) { return is_node_js ? "undefined" != typeof module ? (0,eval)('new(require("worker_threads")["Worker"])(__dirname+"/worker/node.js")') : import("worker_threads").then(function(worker){return new worker["Worker"](import.meta.dirname+"/../node/node.mjs")}) : factory ? new window.Worker(URL.createObjectURL(new Blob(["onmessage=" + handler.toString()], { type: "text/javascript" }))) : new window.Worker("string" == typeof worker_path ? worker_path : (1, eval)("import.meta.url").replace("/worker.js", "/worker/worker.js").replace("flexsearch.bundle.module.min.js", "module/worker/worker.js").replace("flexsearch.bundle.module.min.mjs", "module/worker/worker.js"), { type: "module" }); } ================================================ FILE: dist/module-min/async.js ================================================ import Document from"./document.js";import Index from"./index.js";import WorkerIndex from"./worker.js";export default function(a){register.call(a,"add"),register.call(a,"append"),register.call(a,"search"),register.call(a,"update"),register.call(a,"remove"),register.call(a,"searchCache")}let timer,timestamp,cycle;function tick(){timer=cycle=0}function register(a){this[a+"Async"]=function(){const b=arguments,c=b[b.length-1];let d;if("function"==typeof c&&(d=c,delete b[b.length-1]),!timer)timer=setTimeout(tick,0),timestamp=Date.now();else if(!cycle){const a=Date.now(),b=a-timestamp,c=3*(this.priority*this.priority);cycle=b>=c}if(cycle){const c=this;return new Promise(d=>{setTimeout(function(){d(c[a+"Async"].apply(c,b))},0)})}const e=this[a].apply(this,b),f=e.then?e:new Promise(a=>a(e));return d&&f.then(d),f}} ================================================ FILE: dist/module-min/bundle.js ================================================ import{SearchOptions,ContextOptions,DocumentDescriptor,DocumentSearchOptions,FieldOptions,IndexOptions,DocumentOptions,TagOptions,StoreOptions,EncoderOptions,EncoderSplitOptions,PersistentOptions,ResolverOptions,HighlightBoundaryOptions,HighlightEllipsisOptions,HighlightOptions}from"./type.js";import StorageInterface from"./db/interface.js";import Document from"./document.js";import Index from"./index.js";import WorkerIndex from"./worker.js";import Resolver from"./resolver.js";import Encoder from"./encoder.js";import IdxDB from"./db/indexeddb/index.js";import Charset from"./charset.js";import{KeystoreMap,KeystoreArray,KeystoreSet}from"./keystore.js";Index.prototype.add,Index.prototype.append,Index.prototype.search,Index.prototype.update,Index.prototype.remove,Index.prototype.contain,Index.prototype.clear,Index.prototype.cleanup,Index.prototype.searchCache,Index.prototype.addAsync,Index.prototype.appendAsync,Index.prototype.searchAsync,Index.prototype.searchCacheAsync,Index.prototype.updateAsync,Index.prototype.removeAsync,Index.prototype.export,Index.prototype.import,Index.prototype.serialize,Index.prototype.mount,Index.prototype.commit,Index.prototype.destroy,Index.prototype.encoder,Index.prototype.reg,Index.prototype.map,Index.prototype.ctx,Index.prototype.db,Index.prototype.tag,Index.prototype.store,Index.prototype.depth,Index.prototype.bidirectional,Index.prototype.commit_task,Index.prototype.commit_timer,Index.prototype.cache,Index.prototype.bypass,Index.prototype.document,Encoder.prototype.assign,Encoder.prototype.encode,Encoder.prototype.addMatcher,Encoder.prototype.addStemmer,Encoder.prototype.addFilter,Encoder.prototype.addMapper,Encoder.prototype.addReplacer,Document.prototype.add,Document.prototype.append,Document.prototype.search,Document.prototype.update,Document.prototype.remove,Document.prototype.contain,Document.prototype.clear,Document.prototype.cleanup,Document.prototype.addAsync,Document.prototype.appendAsync,Document.prototype.updateAsync,Document.prototype.removeAsync,Document.prototype.searchAsync,Document.prototype.searchCacheAsync,Document.prototype.searchCache,Document.prototype.mount,Document.prototype.commit,Document.prototype.destroy,Document.prototype.export,Document.prototype.import,Document.prototype.get,Document.prototype.set,Document.prototype.field,Document.prototype.index,Document.prototype.reg,Document.prototype.tag,Document.prototype.store,Document.prototype.fastupdate,Resolver.prototype.limit,Resolver.prototype.offset,Resolver.prototype.boost,Resolver.prototype.resolve,Resolver.prototype.or,Resolver.prototype.and,Resolver.prototype.xor,Resolver.prototype.not,Resolver.prototype.result,Resolver.prototype.await,StorageInterface.db,StorageInterface.id,StorageInterface.support_tag_search,StorageInterface.fastupdate,StorageInterface.prototype.mount,StorageInterface.prototype.open,StorageInterface.prototype.close,StorageInterface.prototype.destroy,StorageInterface.prototype.clear,StorageInterface.prototype.get,StorageInterface.prototype.tag,StorageInterface.prototype.enrich,StorageInterface.prototype.has,StorageInterface.prototype.search,StorageInterface.prototype.info,StorageInterface.prototype.commit,StorageInterface.prototype.remove,KeystoreArray.length,KeystoreMap.size,KeystoreSet.size,Charset.Exact,Charset.Default,Charset.Normalize,Charset.LatinBalance,Charset.LatinAdvanced,Charset.LatinExtra,Charset.LatinSoundex,Charset.CJK,Charset.LatinExact,Charset.LatinDefault,Charset.LatinSimple,Charset.ArabicDefault,Charset.CjkDefault,Charset.CyrillicDefault,IndexOptions.preset,IndexOptions.context,IndexOptions.encoder,IndexOptions.encode,IndexOptions.resolution,IndexOptions.tokenize,IndexOptions.fastupdate,IndexOptions.score,IndexOptions.keystore,IndexOptions.rtl,IndexOptions.cache,IndexOptions.resolve,IndexOptions.db,IndexOptions.worker,IndexOptions.config,IndexOptions.priority,IndexOptions.export,IndexOptions.import,FieldOptions.preset,FieldOptions.context,FieldOptions.encoder,FieldOptions.encode,FieldOptions.resolution,FieldOptions.tokenize,FieldOptions.fastupdate,FieldOptions.score,FieldOptions.keystore,FieldOptions.rtl,FieldOptions.cache,FieldOptions.db,FieldOptions.config,FieldOptions.resolve,FieldOptions.field,FieldOptions.filter,FieldOptions.custom,FieldOptions.worker,DocumentOptions.context,DocumentOptions.encoder,DocumentOptions.encode,DocumentOptions.resolution,DocumentOptions.tokenize,DocumentOptions.fastupdate,DocumentOptions.score,DocumentOptions.keystore,DocumentOptions.rtl,DocumentOptions.cache,DocumentOptions.db,DocumentOptions.doc,DocumentOptions.document,DocumentOptions.worker,DocumentOptions.priority,DocumentOptions.export,DocumentOptions.import,ContextOptions.depth,ContextOptions.bidirectional,ContextOptions.resolution,DocumentDescriptor.field,DocumentDescriptor.index,DocumentDescriptor.tag,DocumentDescriptor.store,DocumentDescriptor.id,TagOptions.field,TagOptions.tag,TagOptions.filter,TagOptions.custom,TagOptions.keystore,TagOptions.db,TagOptions.config,StoreOptions.field,StoreOptions.filter,StoreOptions.custom,StoreOptions.config,SearchOptions.query,SearchOptions.limit,SearchOptions.offset,SearchOptions.context,SearchOptions.suggest,SearchOptions.resolve,SearchOptions.cache,SearchOptions.resolution,DocumentSearchOptions.query,DocumentSearchOptions.limit,DocumentSearchOptions.offset,DocumentSearchOptions.context,DocumentSearchOptions.suggest,DocumentSearchOptions.resolve,DocumentSearchOptions.enrich,DocumentSearchOptions.cache,DocumentSearchOptions.resolution,DocumentSearchOptions.tag,DocumentSearchOptions.field,DocumentSearchOptions.index,DocumentSearchOptions.pluck,DocumentSearchOptions.merge,DocumentSearchOptions.highlight,EncoderOptions.rtl,EncoderOptions.dedupe,EncoderOptions.split,EncoderOptions.include,EncoderOptions.exclude,EncoderOptions.prepare,EncoderOptions.finalize,EncoderOptions.filter,EncoderOptions.matcher,EncoderOptions.mapper,EncoderOptions.stemmer,EncoderOptions.replacer,EncoderOptions.minlength,EncoderOptions.maxlength,EncoderOptions.cache,EncoderSplitOptions.letter,EncoderSplitOptions.number,EncoderSplitOptions.symbol,EncoderSplitOptions.punctuation,EncoderSplitOptions.control,EncoderSplitOptions.char,PersistentOptions.name,PersistentOptions.field,PersistentOptions.type,PersistentOptions.db,ResolverOptions.index,ResolverOptions.query,ResolverOptions.limit,ResolverOptions.offset,ResolverOptions.boost,ResolverOptions.enrich,ResolverOptions.resolve,ResolverOptions.suggest,ResolverOptions.cache,ResolverOptions.async,ResolverOptions.queue,ResolverOptions.and,ResolverOptions.or,ResolverOptions.xor,ResolverOptions.not,ResolverOptions.pluck,ResolverOptions.field,HighlightBoundaryOptions.before,HighlightBoundaryOptions.after,HighlightBoundaryOptions.total,HighlightEllipsisOptions.template,HighlightEllipsisOptions.pattern,HighlightOptions.template,HighlightOptions.boundary,HighlightOptions.ellipsis,HighlightOptions.clip,HighlightOptions.merge;const FlexSearch={Index:Index,Charset:Charset,Encoder:Encoder,Document:Document,Worker:WorkerIndex,Resolver:Resolver,IndexedDB:IdxDB,Language:{}};{const a="undefined"==typeof self?"undefined"==typeof global?self:global:self;let b;(b=a.define)&&b.amd?b([],function(){return FlexSearch}):"object"==typeof a.exports?a.exports=FlexSearch:a.FlexSearch=FlexSearch}export default FlexSearch;export{Index,Document,Encoder,Charset,WorkerIndex as Worker,Resolver,IdxDB as IndexedDB}; ================================================ FILE: dist/module-min/cache.js ================================================ import Index from"./index.js";import{SearchOptions,DocumentSearchOptions}from"./type.js";export function searchCache(a,b,c){c||(b||"object"!=typeof a?"object"==typeof b&&(c=b,b=0):c=a),c&&(a=c.query||a,b=c.limit||b);let d=""+(b||0);if(c){const{context:a,suggest:b,offset:e,resolve:f,boost:g,resolution:h}=c;d+=(e||0)+!!a+!!b+(!1!==f)+(h||this.resolution)+(g||0)}a=(""+a).toLowerCase(),this.cache||(this.cache=new CacheClass);let e=this.cache.get(a+d);if(!e){const f=c&&c.cache;f&&(c.cache=!1),e=this.search(a,b,c),f&&(c.cache=f),this.cache.set(a+d,e)}return e}export default function CacheClass(a){this.limit=a&&!0!==a?a:1000,this.cache=new Map,this.last=""}CacheClass.prototype.set=function(a,b){this.cache.set(this.last=a,b),this.cache.size>this.limit&&this.cache.delete(this.cache.keys().next().value)},CacheClass.prototype.get=function(a){const b=this.cache.get(a);return b&&this.last!==a&&(this.cache.delete(a),this.cache.set(this.last=a,b)),b},CacheClass.prototype.remove=function(a){for(const b of this.cache){const c=b[0],d=b[1];d.includes(a)&&this.cache.delete(c)}},CacheClass.prototype.clear=function(){this.cache.clear(),this.last=""}; ================================================ FILE: dist/module-min/charset/cjk.js ================================================ import{EncoderOptions}from"../type.js";const options={split:""};export default options; ================================================ FILE: dist/module-min/charset/exact.js ================================================ import{EncoderOptions}from"../type.js";const options={normalize:!1,numeric:!1,dedupe:!1};export default options; ================================================ FILE: dist/module-min/charset/latin/advanced.js ================================================ import{EncoderOptions}from"../../type.js";import{soundex}from"./balance.js";export const matcher=new Map([["ae","a"],["oe","o"],["sh","s"],["kh","k"],["th","t"],["ph","f"],["pf","f"]]);export const replacer=[/([^aeo])h(.)/g,"$1$2",/([aeo])h([^aeo]|$)/g,"$1$2",/(.)\1+/g,"$1"];const options={mapper:soundex,matcher:matcher,replacer:replacer};export default options; ================================================ FILE: dist/module-min/charset/latin/balance.js ================================================ import{EncoderOptions}from"../../type.js";export const soundex=new Map([["b","p"],["v","f"],["w","f"],["z","s"],["x","s"],["d","t"],["n","m"],["c","k"],["g","k"],["j","k"],["q","k"],["i","e"],["y","e"],["u","o"]]);const options={mapper:soundex};export default options; ================================================ FILE: dist/module-min/charset/latin/extra.js ================================================ import{EncoderOptions}from"../../type.js";import{soundex}from"./balance.js";import{matcher,replacer}from"./advanced.js";export const compact=[/(?!^)[aeo]/g,""];const options={mapper:soundex,replacer:replacer.concat(compact),matcher:matcher};export default options; ================================================ FILE: dist/module-min/charset/latin/soundex.js ================================================ import{EncoderOptions}from"../../type.js";const options={dedupe:!1,include:{letter:!0},finalize:function(a){for(let b=0;b=defaults.resolution?"UInt8":"UInt16"}, id ${this.type} ) ENGINE = MergeTree ORDER BY (key, id); `,{params:{name:this.id+".map"+this.field}}).toPromise();break;case"ctx":await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.ctx${this.field}( ctx String, key String, res ${255>=defaults.resolution?"UInt8":"UInt16"}, id ${this.type} ) ENGINE = MergeTree ORDER BY (ctx, key, id); `).toPromise();break;case"tag":await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.tag${this.field}( tag String, id ${this.type} ) ENGINE = MergeTree ORDER BY (tag, id); `).toPromise();break;case"reg":await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.reg( id ${this.type}, doc Nullable(String) ) ENGINE = MergeTree ORDER BY (id); `).toPromise();break;case"cfg":await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.cfg${this.field}( cfg String ) ENGINE = TinyLog; `).toPromise();}return this.db},ClickhouseDB.prototype.close=function(){return this.db=Index=null,this},ClickhouseDB.prototype.destroy=function(){return Promise.all([this.db.query(`DROP TABLE ${this.id}.map${this.field};`).toPromise(),this.db.query(`DROP TABLE ${this.id}.ctx${this.field};`).toPromise(),this.db.query(`DROP TABLE ${this.id}.tag${this.field};`).toPromise(),this.db.query(`DROP TABLE ${this.id}.cfg${this.field};`).toPromise(),this.db.query(`DROP TABLE ${this.id}.reg;`).toPromise()])},ClickhouseDB.prototype.clear=function(){return Promise.all([this.db.query(`TRUNCATE TABLE ${this.id}.map${this.field};`).toPromise(),this.db.query(`TRUNCATE TABLE ${this.id}.ctx${this.field};`).toPromise(),this.db.query(`TRUNCATE TABLE ${this.id}.tag${this.field};`).toPromise(),this.db.query(`TRUNCATE TABLE ${this.id}.cfg${this.field};`).toPromise(),this.db.query(`TRUNCATE TABLE ${this.id}.reg;`).toPromise()])};function create_result(a,b,c){if(b){for(let b=0;bb?a.slice(d,d+b):d?a.slice(d):a;d+=e.length;let f={},g="";for(let a=0;am;k+=(k?" OR ":"")+`(ctx = {ctx${c}:String} AND key = {key${c}:String})`,l["ctx"+c]=d?j:m,l["key"+c]=d?m:j,m=j}if(h){k="("+k+")";for(let a=0,b=1;a=e.length){d-=e.length;continue}const a=c?d+Math.min(e.length-d,c):e.length;for(let c=d;c=a.length)return[];if(!b&&!c)return a;const e=a.slice(c,c+b);return d?h.enrich(e):e})},IdxDB.prototype.enrich=function(a){"object"!=typeof a&&(a=[a]);const b=this.db.transaction("reg","readonly"),c=b.objectStore("reg"),d=[];for(let b=0;b{a.onsuccess=a.oncomplete=function(){b&&b(this.result),b=null,c(this.result)},a.onerror=a.onblocked=d,a=null})} ================================================ FILE: dist/module-min/db/interface.js ================================================ export default function StorageInterface(){}StorageInterface.prototype.mount=async function(){},StorageInterface.prototype.open=async function(){},StorageInterface.prototype.close=function(){},StorageInterface.prototype.destroy=async function(){},StorageInterface.prototype.commit=async function(){},StorageInterface.prototype.get=async function(){},StorageInterface.prototype.enrich=async function(){},StorageInterface.prototype.has=async function(){},StorageInterface.prototype.remove=async function(){},StorageInterface.prototype.clear=async function(){},StorageInterface.prototype.search=async function(){},StorageInterface.prototype.info=async function(){}; ================================================ FILE: dist/module-min/db/mongodb/index.js ================================================ import{MongoClient}from"mongodb";const defaults={host:"localhost",port:"27017",user:null,pass:null},VERSION=1,fields=["map","ctx","tag","reg","cfg"];import StorageInterface from"../interface.js";import{toArray}from"../../common.js";function sanitize(a){return a.toLowerCase().replace(/[^a-z0-9_\-]/g,"")}let CLIENT,Index=Object.create(null);export default function MongoDB(a,b={}){return this&&this.constructor===MongoDB?void("object"==typeof a&&(b=a,a=a.name),!a&&console.info("Default storage space was used, because a name was not passed."),this.id="flexsearch"+(a?"-"+sanitize(a):""),this.field=b.field?"-"+sanitize(b.field):"",this.type=b.type||"",this.db=b.db||Index[this.id]||CLIENT||null,this.trx=!1,this.support_tag_search=!0,Object.assign(defaults,b),this.db&&delete defaults.db):new MongoDB(a,b)}MongoDB.prototype.mount=function(a){return a.index?a.mount(this):(a.db=this,this.open())};async function createCollection(a,b,c){"map"===b?(await a.createCollection("map"+c),await a.collection("map"+c).createIndex({key:1}),await a.collection("map"+c).createIndex({id:1})):"ctx"===b?(await a.createCollection("ctx"+c),await a.collection("ctx"+c).createIndex({ctx:1,key:1}),await a.collection("ctx"+c).createIndex({id:1})):"tag"===b?(await a.createCollection("tag"+c),await a.collection("tag"+c).createIndex({tag:1}),await a.collection("tag"+c).createIndex({id:1})):"reg"===b?(await a.createCollection("reg"),await a.collection("reg").createIndex({id:1})):"cfg"===b?await a.createCollection("cfg"+c):void 0}MongoDB.prototype.open=async function(){if(!this.db&&!(this.db=Index[this.id])&&!(this.db=CLIENT)){let a=defaults.url;a||(a=defaults.user?`mongodb://${defaults.user}:${defaults.pass}@${defaults.host}:${defaults.port}`:`mongodb://${defaults.host}:${defaults.port}`),this.db=CLIENT=new MongoClient(a),await this.db.connect()}this.db.db&&(this.db=Index[this.id]=this.db.db(this.id));const a=await this.db.listCollections().toArray();for(let b,c=0;cl;k.push({ctx:d?j:l,key:d?l:j}),l=j}const m={_id:1};f||(m.res=1),g&&(m.doc=1);const n=[{$match:{$or:k}},{$group:{_id:"$id",count:{$sum:1},res:e?{$sum:"$res"}:{$sum:"$res"}}}];if(e||n.push({$match:{count:b.length-1}}),g&&(m.doc="$doc.doc",n.push({$lookup:{from:"reg",localField:"_id",foreignField:"id",as:"doc"}},{$unwind:{path:"$doc",preserveNullAndEmptyArrays:!0}})),h){const a={};for(let b=0,c=1;b{});break;case"cfg":await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.cfg${this.field}( cfg text NOT NULL ); `);}}return this.db},PostgresDB.prototype.close=function(){return this.db=null,this},PostgresDB.prototype.destroy=function(){return this.db.none(` DROP TABLE IF EXISTS ${this.id}.map${this.field}; DROP TABLE IF EXISTS ${this.id}.ctx${this.field}; DROP TABLE IF EXISTS ${this.id}.tag${this.field}; DROP TABLE IF EXISTS ${this.id}.cfg${this.field}; DROP TABLE IF EXISTS ${this.id}.reg; `)},PostgresDB.prototype.clear=function(){return this.db.none(` TRUNCATE TABLE ${this.id}.map${this.field}; TRUNCATE TABLE ${this.id}.ctx${this.field}; TRUNCATE TABLE ${this.id}.tag${this.field}; TRUNCATE TABLE ${this.id}.cfg${this.field}; TRUNCATE TABLE ${this.id}.reg; `)};function create_result(a,b,c){if(b){for(let b=0;bMAXIMUM_QUERY_VARS?a.slice(c,c+MAXIMUM_QUERY_VARS):c?a.slice(c):a;c+=d.length;let e="";for(let a=1;a<=d.length;a++)e+=(e?",":"")+"$"+a;const f=await this.db.any(` SELECT id, doc FROM ${this.id}.reg WHERE id IN (${e})`,a);if(f&&f.length){for(let a,b=0;bm;k+=(k?" OR ":"")+`(ctx = $${n++} AND key = $${n++})`,l.push(d?j:m,d?m:j),m=j}if(h){k="("+k+")";for(let a=0;aconsole.error(a)).connect()},RedisDB.prototype.close=async function(){return DB&&(await this.db.disconnect()),this.db=DB=null,this},RedisDB.prototype.destroy=function(){return this.clear(!0)},RedisDB.prototype.clear=function(a=!1){function b(a){return a.length&&c.db.unlink(a)}if(!this.id)return;const c=this;return Promise.all([this.db.keys(this.id+"map"+(a?"":this.field)+"*").then(b),this.db.keys(this.id+"ctx"+(a?"":this.field)+"*").then(b),this.db.keys(this.id+"tag"+(a?"":this.field)+"*").then(b),this.db.keys(this.id+"ref"+(a?"":this.field)+"*").then(b),b([this.id+"cfg"+(a?"*":this.field),this.id+"doc",this.id+"reg"])])};function create_result(a,b,c,d,e){if(c){for(let c,e,f=0;f=a.length)return[];if(!b&&!c)return a;const f=a.slice(c,c+b);return d?e.enrich(f):f})},RedisDB.prototype.enrich=function(a){return"object"!=typeof a&&(a=[a]),this.db.hmGet(this.id+"doc","number"===this.type?a.map(a=>""+a):a).then(function(b){for(let c=0;ce,k.push(c+(f?d:e)+":"+(f?e:d)),l.push(1),e=d}else{const a=this.id+"map"+this.field+":";for(let c=0;cMAXIMUM_QUERY_VARS?a.slice(b,b+MAXIMUM_QUERY_VARS):b?a.slice(b):a,e=build_params(d.length);b+=d.length,c.push(this.promisfy({method:"all",stmt:`SELECT id, doc FROM main.reg WHERE id IN (${e})`,params:d}))}return Promise.all(c).then(function(a){for(let c,d=0;dm;k+=(k?" OR ":"")+`(ctx = ? AND key = ?)`,l.push(d?j:m,d?m:j),m=j}if(h){k="("+k+")";for(let a=0;aMAXIMUM_QUERY_VARS)&&(this.db.run("INSERT INTO main.map"+this.field+" (key, res, id) VALUES "+c,e),c="",e=[])}}for(const b of a.ctx){const a=b[0],c=b[1];for(const b of c){const c=b[0],d=b[1];for(let b,e=0;eMAXIMUM_QUERY_VARS)&&(this.db.run("INSERT INTO main.ctx"+this.field+" (ctx, key, res, id) VALUES "+d,f),d="",f=[])}}}if(a.store){let b="",c=[];for(const d of a.store.entries()){const a=d[0],e=d[1];b+=(b?",":"")+"(?,?)",c.push(a,"object"==typeof e?JSON.stringify(e):e||null),c.length+2>MAXIMUM_QUERY_VARS&&(this.db.run("INSERT INTO main.reg (id, doc) VALUES "+b+" ON CONFLICT DO NOTHING",c),b="",c=[])}c.length&&this.db.run("INSERT INTO main.reg (id, doc) VALUES "+b+" ON CONFLICT DO NOTHING",c)}else if(!a.bypass){let b=toArray(a.reg);for(let a=0;aMAXIMUM_QUERY_VARS?b.slice(a,a+MAXIMUM_QUERY_VARS):a?b.slice(a):b;a+=c.length;const d=build_params(c.length,!0);this.db.run("INSERT INTO main.reg (id) VALUES "+d+" ON CONFLICT DO NOTHING",c)}}if(a.tag){let b="",c=[];for(const d of a.tag){const a=d[0],e=d[1];if(e.length){for(let d=0;dMAXIMUM_QUERY_VARS&&(this.db.run("INSERT INTO main.tag"+this.field+" (tag, id) VALUES "+b,c),b="",c=[])}}c.length&&this.db.run("INSERT INTO main.tag"+this.field+" (tag, id) VALUES "+b,c)}}),d.length&&(await this.cleanup()),a.map.clear(),a.ctx.clear(),a.tag&&a.tag.clear(),a.store&&a.store.clear(),a.document||a.reg.clear())},SqliteDB.prototype.remove=function(a){"object"!=typeof a&&(a=[a]);let b;a.length>MAXIMUM_QUERY_VARS&&(b=a.slice(MAXIMUM_QUERY_VARS),a=a.slice(0,MAXIMUM_QUERY_VARS));const c=this;return this.transaction(function(){const b=build_params(a.length);this.db.run("DELETE FROM main.map"+c.field+" WHERE id IN ("+b+")",a),this.db.run("DELETE FROM main.ctx"+c.field+" WHERE id IN ("+b+")",a),this.db.run("DELETE FROM main.tag"+c.field+" WHERE id IN ("+b+")",a),this.db.run("DELETE FROM main.reg WHERE id IN ("+b+")",a)}).then(function(a){return b?c.remove(b):a})},SqliteDB.prototype.cleanup=function(){return this.transaction(function(){this.db.run("DELETE FROM main.map"+this.field+" WHERE ROWID IN (SELECT ROWID FROM (SELECT ROWID, row_number() OVER dupes AS count FROM main.map"+this.field+" _t WINDOW dupes AS (PARTITION BY id, key ORDER BY res) ) WHERE count > 1)"),this.db.run("DELETE FROM main.ctx"+this.field+" WHERE ROWID IN (SELECT ROWID FROM (SELECT ROWID, row_number() OVER dupes AS count FROM main.ctx"+this.field+" _t WINDOW dupes AS (PARTITION BY id, ctx, key ORDER BY res) ) WHERE count > 1)")})},SqliteDB.prototype.promisfy=function(a){const b=this.db;return new Promise(function(c,d){b[a.method](a.stmt,a.params||[],function(b,e){a.callback&&a.callback(e),b?d(b):c(e)})})}; ================================================ FILE: dist/module-min/document/add.js ================================================ import{create_object,is_array,is_object,is_string,parse_simple}from"../common.js";import{KeystoreArray}from"../keystore.js";import Document from"../document.js";Document.prototype.add=function(a,b,c){if(is_object(a)&&(b=a,a=parse_simple(b,this.key)),b&&(a||0===a)){if(!c&&this.reg.has(a))return this.update(a,b);for(let d,e=0;ed?0:d,k&&l<=k)continue;const m=c.indexOf(i);-1w&&(w=i.length+(i?1:0)),x=i.length+(i?1:0)+f.length,y+=a,v.push(u.length),u.push({match:f})),i+=(i?" ":"")+f)}if(!e){const b=d[a];i+=(i?" ":"")+b,l&&u.push({text:b})}else if(l&&y>=l)break}let z=v.length*(f.length-2);if(q||r||l&&i.length-z>l){let a,b,c=l+z-2*s,e=x-w;if(0a&&(a=0))),b=d.length-1?b=1:x=u.length-1){if(x>=u.length){e[y+1]=1,x>=d.length&&(c[y+1]=1);continue}j-=s}i=u[x].text;let f=r&&h[y];if(f)if(0f)if(e[y+1]=1,m)i=i.substring(0,f);else continue;f-=i.length,f||(f=-1),h[y]=f}else{e[y+1]=1;continue}if(j+i.length+1<=l)i=" "+i,a[y]+=i;else if(m){const b=l-j-1;0=x){if(0>x){e[y]=1,c[y]=1;continue}j-=s}i=u[x].text;let d=q&&g[y];if(d)if(0d)if(e[y]=1,m)i=i.substring(i.length-d);else continue;d-=i.length,d||(d=-1),g[y]=d}else{e[y]=1;continue}if(j+i.length+1<=l)i+=" ",a[y]=i+a[y];else if(m){const b=i.length+1-(l-j);0<=b&&bc||d)&&(f=f.slice(d,d+c)),e&&(f=apply_enrich.call(this,f))),f}export function apply_enrich(a){if(!this||!this.store)return a;if(this.db)return this.index.get(this.field[0]).db.enrich(a);const b=Array(a.length);for(let c,d=0;d"a1a".split(d).length;this.numeric=merge_option(a.numeric,b)}else{try{this.split=merge_option(this.split,whitespace)}catch(a){!1,this.split=/\s+/}this.numeric=merge_option(a.numeric,merge_option(this.numeric,!0))}if(this.prepare=merge_option(a.prepare,null,this.prepare),this.finalize=merge_option(a.finalize,null,this.finalize),d=a.filter,this.filter="function"==typeof d?d:merge_option(d&&new Set(d),null,this.filter),this.dedupe=merge_option(a.dedupe,!0,this.dedupe),this.matcher=merge_option((d=a.matcher)&&new Map(d),null,this.matcher),this.mapper=merge_option((d=a.mapper)&&new Map(d),null,this.mapper),this.stemmer=merge_option((d=a.stemmer)&&new Map(d),null,this.stemmer),this.replacer=merge_option(a.replacer,null,this.replacer),this.minlength=merge_option(a.minlength,1,this.minlength),this.maxlength=merge_option(a.maxlength,1024,this.maxlength),this.rtl=merge_option(a.rtl,!1,this.rtl),this.cache=d=merge_option(a.cache,!0,this.cache),d&&(this.timer=null,this.cache_size="number"==typeof d?d:2e5,this.cache_enc=new Map,this.cache_term=new Map,this.cache_enc_length=128,this.cache_term_length=128),this.matcher_str="",this.matcher_test=null,this.stemmer_str="",this.stemmer_test=null,this.matcher)for(const a of this.matcher.keys())this.matcher_str+=(this.matcher_str?"|":"")+a;if(this.stemmer)for(const a of this.stemmer.keys())this.stemmer_str+=(this.stemmer_str?"|":"")+a;return this},Encoder.prototype.addStemmer=function(a,b){return this.stemmer||(this.stemmer=new Map),this.stemmer.set(a,b),this.stemmer_str+=(this.stemmer_str?"|":"")+a,this.stemmer_test=null,this.cache&&clear(this),this},Encoder.prototype.addFilter=function(a){return"function"==typeof a?this.filter=a:(this.filter||(this.filter=new Set),this.filter.add(a)),this.cache&&clear(this),this},Encoder.prototype.addMapper=function(a,b){return"object"==typeof a?this.addReplacer(a,b):1a.length&&(this.dedupe||this.mapper)?this.addMapper(a,b):(this.matcher||(this.matcher=new Map),this.matcher.set(a,b),this.matcher_str+=(this.matcher_str?"|":"")+a,this.matcher_test=null,this.cache&&clear(this),this)},Encoder.prototype.addReplacer=function(a,b){return"string"==typeof a?this.addMatcher(a,b):(this.replacer||(this.replacer=[]),this.replacer.push(a,b),this.cache&&clear(this),this)},Encoder.prototype.encode=function(a,b){if(this.cache&&a.length<=this.cache_enc_length)if(!this.timer)this.timer=setTimeout(clear,50,this);else if(this.cache_enc.has(a))return this.cache_enc.get(a);this.normalize&&("function"==typeof this.normalize?a=this.normalize(a):normalize?a=a.normalize("NFKD").replace(normalize,"").toLowerCase():a=a.toLowerCase()),this.prepare&&(a=this.prepare(a)),this.numeric&&3this.maxlength)){if(b){if(g[j])continue;g[j]=1}else{if(d===j)continue;d=j}if(c){f.push(j);continue}if(!(this.filter&&("function"==typeof this.filter?!this.filter(j):this.filter.has(j)))){if(this.cache&&j.length<=this.cache_term_length)if(this.timer){const a=this.cache_term.get(j);if(a||""===a){a&&f.push(a);continue}}else this.timer=setTimeout(clear,50,this);if(this.stemmer){this.stemmer_test||(this.stemmer_test=new RegExp("(?!^)("+this.stemmer_str+")$"));for(let a;a!==j&&2this.stemmer.get(a))}if(j&&(this.mapper||this.dedupe&&1this.matcher.get(a))),j&&this.replacer)for(let a=0;j&&athis.cache_size&&(this.cache_term.clear(),this.cache_term_length=0|this.cache_term_length/1.1)),j){if(j!==k)if(b){if(g[j])continue;g[j]=1}else{if(e===j)continue;e=j}f.push(j)}}}return this.finalize&&(f=this.finalize(f)||f),this.cache&&a.length<=this.cache_enc_length&&(this.cache_enc.set(a,f),this.cache_enc.size>this.cache_size&&(this.cache_enc.clear(),this.cache_enc_length=0|this.cache_enc_length/1.1)),f};export function fallback_encoder(a){return a.normalize("NFKD").replace(normalize,"").toLowerCase().trim().split(/\s+/)}function clear(a){a.timer=null,a.cache_enc.clear(),a.cache_term.clear()} ================================================ FILE: dist/module-min/index/add.js ================================================ import{create_object}from"../common.js";import Index,{autoCommit}from"../index.js";import default_compress from"../compress.js";import{KeystoreArray,KeystoreMap}from"../keystore.js";Index.prototype.add=function(a,b,c,d){if(b&&(a||0===a)){if(!d&&!c&&this.reg.has(a))return this.update(a,b);const e=this.depth;b=this.encoder.encode(b,!e);const f=b.length;if(f){const d=create_object(),g=create_object(),h=this.resolution;for(let j=0;je;l--){m=i.substring(e,l),d=this.rtl?k-1-e:e;const n=this.score?this.score(b,i,j,m,d):get_score(h,f,j,k,d);this._push_index(g,m,n,a,c)}break}case"bidirectional":case"reverse":if(1h,m=this.score?this.score(b,h,j,i,e-1):get_score(g+(f/2>g?0:1),f,j,k-1,e-1);this._push_index(d,l?h:i,m,a,c,l?i:h)}}}}}this.fastupdate||this.reg.add(a)}}return this.db&&(this.commit_task.push(c?{ins:a}:{del:a}),this.commit_auto&&autoCommit(this)),this},Index.prototype._push_index=function(a,b,c,d,e,f){let g,h;if(!(g=a[b])||f&&!g[f]){if(f?(a=g||(a[b]=create_object()),a[f]=1,this.compress&&(f=default_compress(f)),h=this.ctx,g=h.get(f),g?h=g:h.set(f,h=this.keystore?new KeystoreMap(this.keystore):new Map)):(h=this.map,a[b]=1),this.compress&&(b=default_compress(b)),g=h.get(b),g?h=g:h.set(b,h=g=[]),e)for(let a,b=0;bb,i&&(i=b,b=a,a=i)),this.compress&&(a=default_compress(a),b&&(b=default_compress(b))),this.db)?this.db.get(a,b,c,d,e,f,g):(b?(h=this.ctx.get(b),h=h&&h.get(a)):h=this.map.get(a),h)}; ================================================ FILE: dist/module-min/index.js ================================================ import{IndexOptions,ContextOptions,EncoderOptions}from"./type.js";import Encoder,{fallback_encoder}from"./encoder.js";import Cache,{searchCache}from"./cache.js";import Charset from"./charset.js";import{KeystoreMap,KeystoreSet}from"./keystore.js";import{is_array,is_string}from"./common.js";import{exportIndex,importIndex,serialize}from"./serialize.js";import{remove_index}from"./index/remove.js";import apply_preset from"./preset.js";import apply_async from"./async.js";import tick from"./profiler.js";import"./index/add.js";import"./index/search.js";import"./index/remove.js";export default function Index(a,b){if(!this||this.constructor!==Index)return new Index(a);!1,a=a?apply_preset(a):{};let c=a.context;const d=!0===c?{depth:1}:c||{},e=is_string(a.encoder)?Charset[a.encoder]:a.encode||a.encoder||{};this.encoder=e.encode?e:"object"==typeof e?new Encoder(e):{encode:e},this.compress=a.compress||a.compression||!1,this.resolution=a.resolution||9,this.tokenize=c=(c=a.tokenize)&&"default"!==c&&"exact"!==c&&c||"strict",this.depth="strict"===c&&d.depth||0,this.bidirectional=!1!==d.bidirectional,this.fastupdate=!!a.fastupdate,this.score=a.score||null,!1,c=a.keystore||0,c&&(this.keystore=c),this.map=c&&!0?new KeystoreMap(c):new Map,this.ctx=c&&!0?new KeystoreMap(c):new Map,this.reg=b||(this.fastupdate?c&&!0?new KeystoreMap(c):new Map:c&&!0?new KeystoreSet(c):new Set),this.resolution_ctx=d.resolution||3,this.rtl=e.rtl||a.rtl||!1,this.cache=(c=a.cache||null)&&new Cache(c),this.resolve=!1!==a.resolve,(c=a.db)&&(this.db=this.mount(c)),this.commit_auto=!1!==a.commit,this.commit_task=[],this.commit_timer=null,this.priority=a.priority||4}Index.prototype.mount=function(a){return this.commit_timer&&(clearTimeout(this.commit_timer),this.commit_timer=null),a.mount(this)},Index.prototype.commit=function(){return this.commit_timer&&(clearTimeout(this.commit_timer),this.commit_timer=null),this.db.commit(this)},Index.prototype.destroy=function(){return this.commit_timer&&(clearTimeout(this.commit_timer),this.commit_timer=null),this.db.destroy()};export function autoCommit(a){a.commit_timer||(a.commit_timer=setTimeout(function(){a.commit_timer=null,a.db.commit(a)},1))}Index.prototype.clear=function(){return this.map.clear(),this.ctx.clear(),this.reg.clear(),this.cache&&this.cache.clear(),this.db?(this.commit_timer&&clearTimeout(this.commit_timer),this.commit_timer=null,this.commit_task=[],this.db.clear()):this},Index.prototype.append=function(a,b){return this.add(a,b,!0)},Index.prototype.contain=function(a){return this.db?this.db.has(a):this.reg.has(a)},Index.prototype.update=function(a,b){const c=this,d=this.remove(a);return d&&d.then?d.then(()=>c.add(a,b)):this.add(a,b)},Index.prototype.cleanup=function(){return this.fastupdate?(remove_index(this.map),this.depth&&remove_index(this.ctx),this):(!1,this)},Index.prototype.searchCache=searchCache,Index.prototype.export=exportIndex,Index.prototype.import=importIndex,Index.prototype.serialize=serialize,apply_async(Index.prototype); ================================================ FILE: dist/module-min/intersect.js ================================================ import Resolver from"./resolver.js";import{create_object,concat,sort_by_length_up,get_max_len}from"./common.js";import{SearchResults,IntermediateSearchResults}from"./type.js";export function intersect(a,b,c,d,e,f,g){const h=a.length;let i,j,k=[];i=create_object();for(let l,m,n,o,p=0;pc||d)&&(k=k.slice(d,c+d));else{const a=[];for(let b,e=0;ed){d-=b.length;continue}if((c&&b.length>c||d)&&(b=b.slice(d,c+d),c-=b.length,d&&(d-=b.length)),a.push(b),!c)break}k=a}}else k=1c||d?k.slice(d,c+d):k;return k}export function union(a,b,c,d,e){const f=[],g=create_object();let h,l,m,n=a.length;if(!d)for(let d,j=n-1,i=0;0<=j;j--){d=a[j];for(let a=0;a=f.length)b-=f.length;else{const g=f[d?"splice":"slice"](b,c),h=g.length;if(h&&(e=e.length?e.concat(g):g,c-=h,d&&(a.length-=h),!c))break;b=0}return e}export function KeystoreArray(a){if(!this||this.constructor!==KeystoreArray)return new KeystoreArray(a);this.index=a?[a]:[],this.length=a?a.length:0;const b=this;return new Proxy([],{get(a,c){if("length"===c)return b.length;if("push"===c)return function(a){b.index[b.index.length-1].push(a),b.length++};if("pop"===c)return function(){if(b.length)return b.length--,b.index[b.index.length-1].pop()};if("indexOf"===c)return function(a){let c=0;for(let d,e,f=0;fmap.get(a))),a.replace(/str\b/g,"strasse").replace(/(?!\b)strasse\b/g," strasse")},filter:filter,stemmer:stemmer};export default options; ================================================ FILE: dist/module-min/lang/en.js ================================================ import{EncoderOptions}from"../type.js";export const filter=new Set(["a","about","above","after","again","against","all","also","am","an","and","any","are","arent","as","at","back","be","because","been","before","being","below","between","both","but","by","can","cannot","cant","come","could","couldnt","did","didnt","do","does","doesnt","doing","dont","down","during","each","even","few","for","from","further","get","go","good","had","hadnt","has","hasnt","have","havent","having","he","hed","her","here","heres","hers","herself","hes","him","himself","his","how","hows","i","id","if","ill","im","in","into","is","isnt","it","its","itself","ive","just","know","lets","like","lot","make","made","me","more","most","mustnt","my","myself","new","no","nor","not","now","of","off","on","once","one","only","or","other","ought","our","ours","ourselves","out","over","own","same","say","see","shant","she","shed","shell","shes","should","shouldnt","so","some","such","take","than","that","thats","the","their","theirs","them","themselves","then","there","theres","these","they","theyd","theyll","theyre","theyve","think","this","those","through","time","times","to","too","under","until","up","us","use","very","want","was","wasnt","way","we","wed","well","were","werent","weve","what","whats","when","whens","where","wheres","which","while","who","whom","whos","why","whys","will","with","wont","work","would","wouldnt","ya","you","youd","youll","your","youre","yours","yourself","yourselves","youve"]);export const stemmer=new Map([["ization",""],["biliti",""],["icate",""],["ative",""],["ation",""],["iviti",""],["ement",""],["izer",""],["able",""],["ible",""],["alli",""],["ator",""],["less",""],["logi",""],["ical",""],["ance",""],["ence",""],["ness",""],["ble",""],["ment",""],["eli",""],["bli",""],["ful",""],["ant",""],["ent",""],["ism",""],["ate",""],["iti",""],["ous",""],["ive",""],["ize",""],["ing",""],["ion",""],["ies","y"],["al",""],["ou",""],["er",""],["ed",""],["ic",""],["ly",""],["li",""],["s",""]]);const options={prepare:function(a){return a.replace(/´`’ʼ/g,"'").replace(/&/g," and ").replace(/\$/g," USD ").replace(/£/g," GBP ").replace(/\bi'm\b/g,"i am").replace(/\b(can't|cannot)\b/g,"can not").replace(/\bwon't\b/g,"will not").replace(/([a-z])'s\b/g,"$1 is has").replace(/([a-z])n't\b/g,"$1 not").replace(/([a-z])'ll\b/g,"$1 will").replace(/([a-z])'re\b/g,"$1 are").replace(/([a-z])'ve\b/g,"$1 have").replace(/([a-z])'d\b/g,"$1 would had")},filter:filter,stemmer:stemmer};export default options; ================================================ FILE: dist/module-min/lang/fr.js ================================================ import{EncoderOptions}from"../type.js";export const filter=new Set(["au","aux","avec","ce","ces","dans","de","des","du","elle","en","et","eux","il","je","la","le","leur","lui","ma","mais","me","meme","mes","moi","mon","ne","nos","notre","nous","on","ou","par","pas","pour","qu","que","qui","sa","se","ses","son","sur","ta","te","tes","toi","ton","tu","un","une","vos","votre","vous","c","d","j","l","m","n","s","t","a","y","ete","etee","etees","etes","etant","suis","es","est","sommes","etes","sont","serai","seras","sera","serons","serez","seront","serais","serait","serions","seriez","seraient","etais","etait","etions","etiez","etaient","fus","fut","fumes","futes","furent","sois","soit","soyons","soyez","soient","fusse","fusses","fut","fussions","fussiez","fussent","ayant","eu","eue","eues","eus","ai","as","avons","avez","ont","aurai","auras","aura","aurons","aurez","auront","aurais","aurait","aurions","auriez","auraient","avais","avait","avions","aviez","avaient","eut","eumes","eutes","eurent","aie","aies","ait","ayons","ayez","aient","eusse","eusses","eut","eussions","eussiez","eussent","ceci","cela","cela","cet","cette","ici","ils","les","leurs","quel","quels","quelle","quelles","sans","soi"]);export const stemmer=new Map([["lement",""],["ient",""],["nera",""],["ment",""],["ais",""],["ait",""],["ant",""],["ent",""],["iez",""],["ion",""],["nez",""],["ai",""],["es",""],["er",""],["ez",""],["le",""],["na",""],["ne",""],["a",""],["e",""]]);const options={prepare:function(a){return a.replace(/´`’ʼ/g,"'").replace(/_+/g," ").replace(/&/g," et ").replace(/€/g," EUR ").replace(/\bl'([^\b])/g,"la le $1").replace(/\bt'([^\b])/g,"ta te $1").replace(/\bc'([^\b])/g,"ca ce $1").replace(/\bd'([^\b])/g,"da de $1").replace(/\bj'([^\b])/g,"ja je $1").replace(/\bn'([^\b])/g,"na ne $1").replace(/\bm'([^\b])/g,"ma me $1").replace(/\bs'([^\b])/g,"sa se $1").replace(/\bau\b/g,"a le").replace(/\baux\b/g,"a les").replace(/\bdu\b/g,"de le").replace(/\bdes\b/g,"de les")},filter:filter,stemmer:stemmer};export default options; ================================================ FILE: dist/module-min/preset.js ================================================ import{is_string}from"./common.js";import{IndexOptions}from"./type.js";const presets={memory:{resolution:1},performance:{resolution:3,fastupdate:!0,context:{depth:1,resolution:1}},match:{tokenize:"full"},score:{resolution:9,context:{depth:2,resolution:3}}};export default function apply_preset(a){const b=is_string(a)?a:a.preset;return b&&(!1,a=Object.assign({},presets[b],a)),a} ================================================ FILE: dist/module-min/profiler.js ================================================ import{create_object}from"./common.js";const data=create_object();export default function tick(){} ================================================ FILE: dist/module-min/resolve/and.js ================================================ import Resolver from"../resolver.js";import{get_max_len}from"../common.js";import{intersect}from"../intersect.js";import{SearchResults,EnrichedSearchResults,IntermediateSearchResults,HighlightOptions}from"../type.js";Resolver.prototype.and=function(){return this.handler("and",return_result,arguments)};function return_result(a,b,c,d,e,f,g){if(!f&&!this.result.length)return e?this.result:this;let h;if(!a.length)f||(this.result=a);else if(this.result.length&&a.unshift(this.result),2>a.length)this.result=a[0];else{let d=0;for(let b,c,e=0;eb?e.slice(c,c+b):e,d?apply_enrich.call(this,e):e}let e=[];for(let f,g,h=0;h=g){c-=g;continue}f=f.slice(c,c+b),g=f.length,c=0}if(g>b&&(f=f.slice(0,b),g=b),!e.length&&g>=b)return d?apply_enrich.call(this,f):f;if(e.push(f),b-=g,!b)break}return e=1a.length?this.result=a[0]:(this.result=union(a,b,c,!1,this.boostval),c=0)),e&&(this.await=null),e?this.resolve(b,c,d,g):this} ================================================ FILE: dist/module-min/resolve/xor.js ================================================ import Resolver from"../resolver.js";import{create_object}from"../common.js";import{SearchResults,EnrichedSearchResults,IntermediateSearchResults,HighlightOptions}from"../type.js";Resolver.prototype.xor=function(){return this.handler("xor",return_result,arguments)};function return_result(a,b,c,d,e,f,g){let h;return a.length?(this.result.length&&a.unshift(this.result),2>a.length?this.result=a[0]:(this.result=exclusive.call(this,a,b,c,e,this.boostval),h=!0)):!f&&(this.result=a),e&&(this.await=null),e?this.resolve(b,c,d,g,h):this}function exclusive(a,b,c,d,e){const f=[],g=create_object();let h=0;for(let f,j=0;j The async processing model is automatically observed by a runtime balancer to prevent any blocking issues, even on page load. Those methods of each index type provides an async version: - addAsync() - appendAsync() - updateAsync() - removeAsync() - searchAsync() All those async versions always return a `Promise`, although a callback can be passed additionally as the last parameter. When calling async methods of the index, a runtime balancer observe the current event loop and will pass to the next event loop automatically. ### Task Priority You can control how early the process should move over to the next event loop by passing the option property `priority`: ```js const index = new Index({ // a value between 1 and 9 priority: 4 }); ``` The lowest valid priority number is `1` and is typically known as `idle` (event loop cycles by native ~4ms). The default priority is `4` which is optimized for non-blocking user interfaces within a browser (event loop cycles every ~45ms). When you have some very smooth running animation you should use a priority of `2` to keep the animation running by 60 fps without any stutter. Targeting 120 fps or higher you should use `1`. On Node.js you can slightly increase this priority e.g. to `6`, because here there is no UI involved. A priority value of `9` will cycle the event loop on every ~250ms which is the maximum recommended blocking time. You should not use a value higher than this. ### Polling Tasks Do not forget to `await` on every async task you apply to the index: ```js for(let i = 0; i < 99999999; i++){ await index.addAsync(i, "test " + i); } ``` You can perform queries to the index during any other async batch is running. ### Examples You can assign callbacks to each async function: ```js index.addAsync(id, content, function(){ console.log("Task Done"); }); index.searchAsync(query, function(result){ console.log("Results: ", result); }); ``` Or do not pass a callback function and getting back a `Promise` instead: ```js index.addAsync(id, content).then(function(){ console.log("Task Done"); }); index.searchAsync(query).then(function(result){ console.log("Results: ", result); }); ``` Or use `async` and `await`: ```js async function add(){ await index.addAsync(id, content); console.log("Task Done"); } async function search(){ const results = await index.searchAsync(query); console.log("Results: ", result); } ``` ================================================ FILE: doc/custom-builds.md ================================================ ## Custom Builds The `/src/` folder of this repository requires some compilation to resolve the build flags. You can run any of the basic builds located in the `/dist/` folder, e.g.: ```bash npm run build:bundle npm run build:light npm run build:module ``` Perform a custom build (UMD bundle) by passing build flags: ```bash npm run build:custom SUPPORT_DOCUMENT=true SUPPORT_TAGS=true LANGUAGE_OUT=ECMASCRIPT5 POLYFILL=true ``` Perform a custom build in ESM module format: ```bash npm run build:custom RELEASE=custom.module SUPPORT_DOCUMENT=true SUPPORT_TAGS=true ``` Perform a debug build: ```bash npm run build:custom DEBUG=true SUPPORT_DOCUMENT=true SUPPORT_TAGS=true ``` > On custom builds each build flag will be set to `false` by default when not passed. Just build the core library: ```bash npm run build:custom ``` The custom build will be saved to `dist/flexsearch.custom.xxxx.min.js` or when format is module to `dist/flexsearch.custom.module.xxxx.min.js` (the "xxxx" is a hash based on the used build flags). ### Supported Build Flags
Flag Values Info

Feature Flags
SUPPORT_WORKER true, false Worker Indexes
SUPPORT_ENCODER true, false When not included you'll need to pass a custom encode method when creating an index
SUPPORT_CHARSET true, false Includes: LatinBalance, LatinAdvanced, LatinExtra, LatinSoundex
SUPPORT_CACHE true, false Support for index.searchCache()
SUPPORT_ASYNC true, false The async version of index standard methods
SUPPORT_STORE true, false Document Datastore
SUPPORT_SUGGESTION true, false Use the option suggestions when searching
SUPPORT_SERIALIZE true, false Export / Import / Serialize Index
SUPPORT_DOCUMENT true, false Document Indexes
SUPPORT_TAGS true, false Tag-Search
SUPPORT_PERSISTENT true, false Use any of the persistent indexes
SUPPORT_KEYSTORE true, false Extended size for InMemory indexes
SUPPORT_RESOLVER true, false Apply complex queries by chaining boolean operations
SUPPORT_HIGHLIGHTING true, false Result Highlighting for Document-Search (also requires SUPPORT_STORE)

Compiler Flags
DEBUG true, false Apply common checks and throw errors more frequently, output debug information and helpful hints to the console
RELEASE custom
custom.module
Choose build schema: custom = Legacy Browser (window.FlexSearch), custom.module = ES6 Modules (ESM)
POLYFILL true, false Include Polyfills (based on LANGUAGE_OUT)
PROFILER true, false Just used for automatic performance tests
LANGUAGE_OUT ECMASCRIPT3
ECMASCRIPT5
ECMASCRIPT_2015
ECMASCRIPT_2016
ECMASCRIPT_2017
ECMASCRIPT_2018
ECMASCRIPT_2019
ECMASCRIPT_2020
ECMASCRIPT_2021
ECMASCRIPT_2022
ECMASCRIPT_NEXT
STABLE
Target language
================================================ FILE: doc/customization.md ================================================ ## Custom Score Function ```js const index = new FlexSearchIndex({ resolution: 10, score: function(content, term, term_index, partial, partial_index){ // you'll need to return a number between 0 and "resolution" // score is starting from 0, which is the highest score // for a resolution of 10 you can return 0 - 9 // ... return 3; } }); ``` A common situation is you have some predefined labels which are related to some kind of order, e.g. the importance or priority. A priority label could be `high`, `moderate`, `low` so you can derive the scoring from those properties. Another example is when you have something already ordered and you would like to keep this order as relevance. Probably you won't need the parameters passed to the score function. But when needed here are the parameters from the score function explained: 1. `content` is the whole content as an array of terms (encoded) 2. `term` is the current term which is actually processed (encoded) 3. `term_index` is the index of the term in the content array 4. `partial` is the current partial of a term which is actually processed 5. `partial_index` is the index position of the partial within the term Partials params are empty when using tokenizer `strict`. Let's take an example by using the tokenizer `full`. The content: "This is an ex[amp]()le of partial encoding"
The highlighting part marks the partial which is actually processed. Then your score function will called by passing these parameters: ```js function score(content, term, term_index, partial, partial_index){ content = ["this", "is", "an", "example", "of", "partial", "encoding"] term = "example" term_index = 3 partial = "amp" partial_index = 2 } ``` ================================================ FILE: doc/document-search.md ================================================ # Document Search (Field-Search) Whereas the simple `Index` can just consume id-content pairs, the `Document`-Index is able to process more complex data structures like JSON. Technically, a `Document`-Index is a layer on top of several default indexes. You can create multiple independent Document-Indexes in parallel, any of them can use the `Worker` or `Persistent` model optionally. FlexSearch Documents also contain these features: - Document Store including Enrichment - Multi-Field-Search - Multi-Tag-Search - Resolver (Chain Complex Queries) - Result Highlighting - Export/Import - Worker - Persistent ### Document Options > Document options basically inherits from [Index Options](../README.md#index-options), so you can apply most of those options either in the top scope of the config (for all fields) or as per field or both of them.
Option Values Description Default
document Document Descriptor Includes any specific information about how the document data should be indexed (mandatory)
worker Boolean
String
Enable a worker distributed model. Read more about here: Worker Index false
### Document Search Options > Document search options basically inherit from [Index Search Options](../README.md#search-options), so you can apply most of those options either in the top scope of the config (for all fields) or as per field or both of them.
Option Values Description Default
index
field
String
Array<String>
Array<SearchOptions>
Sets the document fields which should be searched. When no field is set, all fields will be searched. Custom options per field are also supported.
tag Object<field:tag> Sets the document fields which should be searched. When no field is set, all fields will be searched. Custom options per field are also supported.
enrich Boolean Enrich IDs from the results with the corresponding documents. false
highlight Highlighting Options
String
Highlight query matches in the result (for Document Indexes only) false
merge Boolean Merge multiple fields in resultset into one and group results per ID false
pluck String Pick and apply search to just one field and return a flat result representation false
## The Document Descriptor When creating a `Document`-Index you will need to define a document descriptor in the field `document`. This descriptor is including any specific information about how the document data should be indexed.
Option Values Description Default
id String "id"
index String
Array<String>
Array<FieldOptions>
tag String
Array<String>
Array<FieldOptions>
store Boolean
String
Array<String>
Array<FieldOptions>
false
### Field Options > You can use all standard [Index Options](../README.md#index-options) within field options.
Option Values Description Default
field String The field name (colon seperated syntax) (mandatory)
filter Function
custom Function
Assuming our document has a simple data structure like this: ```json { "id": 0, "content": "some text" } ``` An appropriate Document Descriptor has always to define at least 2 things: 1. the property `id` describes the location of the document ID within a document item 2. the property `index` (or `tag`) containing one or multiple fields from the document, which should be indexed for searching ```js // create a document index const index = new Document({ document: { id: "id", index: "content" } }); // add documents to the index index.add({ id: 0, content: "some text" }); ``` As briefly explained above, the field `id` describes where the ID or unique key lives inside your documents. When not passed it will always take the field `id` from the top level scope of your data. The property `index` takes all fields you would like to have indexed. When just selecting one field, then you can pass a string. The next example will add 2 fields `title` and `content` to the index: ```js var docs = [{ id: 0, title: "Title A", content: "Body A" },{ id: 1, title: "Title B", content: "Body B" }]; ``` ```js const index = new Document({ id: "id", index: ["title", "content"] }); ``` Add both fields to the document descriptor and pass individual [Index-Options](../README.md#index-options) for each field: ```js const index = new Document({ id: "id", index: [{ field: "title", tokenize: "forward", encoder: Charset.LatinAdvanced, resolution: 9 },{ field: "content", tokenize: "forward", encoder: Charset.LatinAdvanced, resolution: 3 }] }); ``` Field options inherits from top level options when passed, e.g.: ```js const index = new Document({ tokenize: "forward", encoder: Charset.LatinAdvanced, resolution: 9, document: { id: "id", index:[{ field: "title" },{ field: "content", resolution: 3 }] } }); ``` > Assigning the `Encoder` instance to the top level configuration will share the encoder to all fields. You should avoid this when contents of fields don't have the same type of content (e.g. one field contains terms, another contains numeric IDs). ### Nested Data Fields (Complex Objects) Assume the document array looks more complex (has nested branches etc.), e.g.: ```json { "record": { "id": 0, "title": "some title", "content": { "header": "some text", "footer": "some text" } } } ``` Then use the colon separated notation `root:child:child` as a name for each field defining the hierarchy which corresponds to the document: ```js const index = new Document({ document: { id: "record:id", index: [ "record:title", "record:content:header", "record:content:footer" ] } }); ``` > [!TIP] > Just add fields you want to query against. Do not add fields to the index, you just need in the result. For this purpose you can store documents independently of its index (read below). To query against one or multiple specific fields you have to pass the exact key of the field you have defined in the document descriptor as a field name (with colon syntax): ```js index.search(query, { field: [ "record:title", "record:content:header", "record:content:footer" ] }); ``` Same as: ```js index.search(query, [ "record:title", "record:content:header", "record:content:footer" ]); ``` Using field-specific options: ```js index.search("some query", [{ field: "record:title", limit: 100, suggest: true },{ field: "record:content:header", limit: 100, suggest: false }]); ``` You can also perform a search through the same field with different queries: ```js index.search([{ field: "record:title", query: "some query", limit: 100, suggest: true },{ field: "record:title", query: "some other query", limit: 100, suggest: true }]); ``` ### Complex Documents You need to follow 2 rules for your documents: 1. The document cannot start with an Array __at the root__. This will introduce sequential data and isn't supported yet. See below for a workaround for such data. ```js [ // <-- not allowed as document start! { "id": 0, "title": "title" } ] ``` 2. The document ID can't be nested __inside an Array__. This will introduce sequential data and isn't supported yet. See below for a workaround for such data. ```js { "records": [ // <-- not allowed when ID or tag lives inside! { "id": 0, "title": "title" } ] } ``` Here an example for a supported complex document: ```json { "meta": { "tag": "cat", "id": 0 }, "contents": [ { "body": { "title": "some title", "footer": "some text" }, "keywords": ["some", "key", "words"] }, { "body": { "title": "some title", "footer": "some text" }, "keywords": ["some", "key", "words"] } ] } ``` The corresponding document descriptor (when all fields should be indexed) looks like: ```js const index = new Document({ document: { id: "meta:id", index: [ "contents:body:title", "contents:body:footer" ], tag: [ "meta:tag", "contents:keywords" ] } }); ``` Remember when searching you have to use the same colon-separated-string as a key from your field definition. ```js index.search(query, { index: "contents:body:title" }); ``` ### Not Supported Documents (Sequential Data) This example breaks both rules described above: ```js [ // <-- not allowed as document start! { "tag": "cat", "records": [ // <-- not allowed when ID or tag lives inside! { "id": 0, "body": { "title": "some title", "footer": "some text" }, "keywords": ["some", "key", "words"] }, { "id": 1, "body": { "title": "some title", "footer": "some text" }, "keywords": ["some", "key", "words"] } ] } ] ``` You need to unroll your data within a simple loop before adding to the index. A workaround to such a data structure from above could look like: ```js const index = new Document({ document: { id: "id", index: [ "body:title", "body:footer" ], tag: [ "tag", "keywords" ] } }); function add(sequential_data){ for(let x = 0, item; x < sequential_data.length; x++){ item = sequential_data[x]; for(let y = 0, record; y < item.records.length; y++){ record = item.records[y]; // append tag to each record record.tag = item.tag; // add to index index.add(record); } } } // now just use add() helper method as usual: add([{ // sequential structured data // take the data example above }]); ``` ### Add/Update/Remove Documents Add a document to the index: ```js index.add({ id: 0, title: "Foo", content: "Bar" }); ``` Update index: ```js index.update({ id: 0, title: "Foo", content: "Foobar" }); ``` Remove a document and all its contents from an index, by ID: ```js index.remove(id); ``` Or by the document data: ```js index.remove(doc); ``` ## Field-Search Search through all fields: ```js index.search(query); ``` Search through a specific field: ```js index.search(query, { index: "title" }); ``` Search through a given set of fields: ```js index.search(query, { index: ["title", "content"] }); ``` Pass custom options and/or queries to each field: ```js index.search([{ field: "content", query: "some query", limit: 100, suggest: true },{ field: "content", query: "some other query", limit: 100, suggest: true }]); ``` ### Limit & Offset > By default, every query is limited to 100 entries. Unbounded queries leads into issues. You need to set the limit as an option to adjust the size. You can set the limit and the offset for each query: ```js index.search(query, { limit: 20, offset: 100 }); ``` > You cannot pre-count the size of the result-set. That's a limit by the design of FlexSearch. When you really need a count of all results you are able to page through, then just assign a high enough limit and get back all results and apply your paging offset manually (this works also on server-side). FlexSearch is fast enough that this isn't an issue. [See all available field-search options](../README.md#search-options) ## The Result Set Schema of the default result-set: > `fields[] => { field, result[] => id }` Schema of an enriched result-set: > `fields[] => { field, result[] => { id, doc }}` The top-level scope of the result set is an array of fields on which the query was applied to. Each of this field has a record (object) with 2 properties `field` and `result`. The `result` could be an array of IDs or is getting enriched by the stored document data (when index was created with `store: true`). A default non-enriched result set looks like: ```js [{ field: "title", result: [0, 1, 2] },{ field: "content", result: [3, 4, 5] }] ``` An enriched result set looks like: ```js [{ field: "title", result: [ { id: 0, doc: { /* document */ }}, { id: 1, doc: { /* document */ }}, { id: 2, doc: { /* document */ }} ] },{ field: "content", result: [ { id: 3, doc: { /* document */ }}, { id: 4, doc: { /* document */ }}, { id: 5, doc: { /* document */ }} ] }] ``` ### Merge Document Results Schema of the merged result-set: > `result[] => { id, doc, field[] }}` By passing the search option `merge: true` all fields of the result set will be merged (grouped by ID): ```js [{ id: 1001, doc: {/* stored document */} field: ["fieldname-1", "fieldname-2"] },{ id: 1002, doc: {/* stored document */} field: ["fieldname-3"] }] ``` ### Pluck Single Fields When using `pluck` instead of `field` you can explicitly select just one field and get back a flat representation: ```js index.search(query, { pluck: "title", enrich: true }); ``` ```js [ { id: 0, doc: { /* document */ }}, { id: 1, doc: { /* document */ }}, { id: 2, doc: { /* document */ }} ] ``` ## Tags Like the property `index` within a document descriptor just define a property `tag`: ```js const index = new Document({ document: { id: "id", tag: "species", index: "content" } }); ``` ```js index.add({ id: 0, species: "cat", content: "Some content ..." }); ``` Your data also can include multiple tags as an array: ```js index.add({ id: 1, species: ["fish", "dog"], content: "Some content ..." }); ``` You can perform a tag-specific search by: ```js index.search(query, { tag: { species: "fish" } }); ``` This just gives you results which was tagged with the given tag. Use multiple tags when searching: ```js index.search(query, { tag: { species: ["cat", "dog"] } }); ``` This give you results which was tagged with at least one of the given tags. Get back all tagged results without passing any query: ```js index.search({ tag: { species: "cat" } }); ``` ### Multi-Tag Search Assume this document schema (a dataset from IMDB): ```js { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ``` An appropriate document descriptor could look like: ```js import Charset from "flexsearch"; const index = new Document({ encoder: Charset.Normalize, resolution: 3, document: { id: "tconst", //store: true, // document store index: [{ field: "primaryTitle", tokenize: "forward" },{ field: "originalTitle", tokenize: "forward" }], tag: [ "startYear", "genres" ] } }); ``` The field contents of `primaryTitle` and `originalTitle` are encoded by the forward tokenizer. The field contents of `startYear` and `genres` are added as tags. Get all entries of a specific tag: ```js const result = index.search({ //enrich: true, // enrich documents tag: { "genres": "Documentary" }, limit: 1000, offset: 0 }); ``` Get entries of multiple tags (intersection): ```js const result = index.search({ //enrich: true, // enrich documents tag: { "genres": ["Documentary", "Short"], "startYear": "1894" } }); ``` Combine tags with queries (intersection): ```js const result = index.search({ query: "Carmen", // forward tokenizer tag: { "genres": ["Documentary", "Short"], "startYear": "1894" } }); ``` Alternative declaration: ```js const result = index.search("Carmen", { tag: [{ field: "genres", tag: ["Documentary", "Short"] },{ field: "startYear", tag: "1894" }] }); ``` ## Document Store Only a document index can have a store. You can use a document index instead of a flat index to get this functionality also when only storing ID-content-pairs. You can define independently which fields should be indexed and which fields should be stored. This way you can index fields which should not be included in the search result. > Do not use a store when: 1. an array of IDs as the result is good enough, or 2. you already have the contents/documents stored elsewhere (outside the index). > When the `store` attribute was set, you have to include all fields which should be stored explicitly (acts like a whitelist). > When the `store` attribute was not set, the original document is stored as a fallback. This will add the whole original content to the store: ```js const index = new Document({ document: { index: "content", store: true } }); index.add({ id: 0, content: "some text" }); ``` ### Access documents from internal store You can get indexed documents from the store: ```js var data = index.get(1); ``` You can update/change store contents directly without changing the index by: ```js index.set(1, data); ``` To update the store and also update the index then just use `index.update`, `index.add` or `index.append`. When you perform a query, weather it is a document index or a flat index, then you will always get back an array of IDs. Optionally you can enrich the query results automatically with stored contents by: ```js index.search(query, { enrich: true }); ``` Your results look now like: ```js [{ id: 0, doc: { /* content from store */ } },{ id: 1, doc: { /* content from store */ } }] ``` ### Configure Document Store (Recommended) When storing documents, you can configure independently what should be indexed and what should be stored. This can reduce required index space significantly. Indexed fields do not require to be included in the stored data (also the ID isn't necessary to keep in store). It is recommended to just add fields to the store you'll need in the final result to process further on. A short example of configuring a document store: ```js const index = new Document({ document: { index: "content", store: ["author", "email"] } }); index.add({ id: 0, author: "Jon Doe", email: "john@mail.com", content: "Some content for the index ..." }); ``` You can query through the contents and will get back the stored values instead: ```js index.search("some content", { enrich: true }); ``` Your results are now looking like: ```js [{ field: "content", result: [{ id: 0, doc: { author: "Jon Doe", email: "john@mail.com", } }] }] ``` Both field "author" and "email" are not indexed, whereas the indexed field "content" was not included in the stored data. ## Filter Fields (Index / Tags / Datastore) You can pass a function to the field option property `filter`. This function just has to return `true` if the document should be indexed. ```js const index = new Document({ document: { id: "id", index: [{ // custom field: field: "somefield", filter: function(data){ // return false to filter out // return anything else to keep return true; } }], tag: [{ field: "city", filter: function(data){ // return false to filter out // return anything else to keep return true; } }], store: [{ field: "anotherfield", filter: function(data){ // return false to filter out // return anything else to keep return true; } }] } }); ``` ## Custom Fields (Index / Tags / Datastore) You can pass a function to the field option property `custom` to either: 1. change and/or extend the original input string 2. create a new "virtual" field which is not included in document data Dataset example: ```js { "id": 10001, "firstname": "John", "lastname": "Doe", "city": "Berlin", "street": "Alexanderplatz", "number": "1a", "postal": "10178" } ``` You can apply custom fields derived from document data or by any external data: ```js const index = new Document({ document: { id: "id", index: [{ // custom field: field: "fullname", custom: function(data){ // return custom string return data.firstname + " " + data.lastname; } },{ // custom field: field: "location", custom: function(data){ return data.street + " " + data.number + ", " + data.postal + " " + data.city; } }], tag: [{ // existing field field: "city" },{ // custom field: field: "category", custom: function(data){ let tags = []; // push one or multiple tags // .... return tags; } }], store: [{ field: "anotherfield", custom: function(data){ // return a falsy value to filter out // return anything else as to keep in store return data; } }] } }); ``` > Filter is also available in custom functions when returning `false`. Perform a query against the custom field as usual: ```js const result = index.search({ query: "10178 Berlin Alexanderplatz", field: "location" }); ``` ```js const result = index.search({ query: "john doe", tag: { "city": "Berlin" } }); ``` ### Best Practices: TypeScript When using TypeScript, you can type your document data when creating a `Document`-Index. This will provide enhanced type checks of your syntax. Create a schema accordingly to your document data, e.g.: ```ts type doctype = { id: number, title: string, description: string, tags: string[] }; ``` Create the document index by assigning the type `doctype`: ```ts const document = new Document({ id: "id", store: true, index: [{ field: "title" },{ field: "description" }], tag: "tags" }); ``` ### Best Practices: Merge Documents [Read here](encoder.md#merge-documents) ================================================ FILE: doc/encoder.md ================================================ ## Encoder > [!IMPORTANT] > You shouldn't miss this part as it is one of the most important aspects of FlexSearch. Search capabilities highly depends on language processing. The Encoder class is one of the most important core functionalities of FlexSearch. > Encoders are basically responsible for "fuzziness". [Read here about Phonetic Search/Fuzzy Search](../README.md#fuzzy-search) ### Default Encoder The default Encoder (when passing no options on creation) uses this configuration: ```js const encoder = new Encoder({ normalize: true, dedupe: true, cache: true, include: { letter: true, number: true, symbol: false, punctuation: false, control: false, char: "" } }); ``` The default configuration will: 1. apply charset normalization, e.g. "é" to "e" 2. apply letter deduplication, e.g. "missing" to "mising" 3. just index alphanumeric content and filter everything else out This is important to keep in mind, because when you need a different configuration you'll have to change those settings accordingly. Let's assume you want including the symbols "#", "@" and "-", because those are needed to differentiate search results (otherwise it would be useless), and let's say you don't need numeric content indexed you can do this by: ```js const encoder = new Encoder({ // default configuration is applied // extend or override: include: { // by default everything is set to false letter: true, number: false, char: ["#", "@", "-"] } }); ``` #### Built-In Universal Encoders 1. Charset.Exact 2. Charset.Normalize (Charset.Default) #### Built-In Latin Encoders 1. Charset.LatinBalance 2. Charset.LatinAdvanced 3. Charset.LatinExtra 4. Charset.LatinSoundex #### Built-In CJK Encoder 1. Charset.CJK ### Basic Usage ```js const encoder = new Encoder({ normalize: true, dedupe: true, cache: true, include: { letter: true, number: true, symbol: false, punctuation: false, control: false, char: "@" } }); ``` You can use an `include` __instead__ of an `exclude` definition: ```js const encoder = new Encoder({ exclude: { letter: false, number: false, symbol: true, punctuation: true, control: true } }); ``` Instead of using `include` or `exclude` you can pass a regular expression or a string to the field `split`: ```js const encoder = new Encoder({ split: /\s+/ }); ``` E.g. this split configuration will tokenize every symbol/char from a content: ```js const encoder = new Encoder({ split: "" }); ``` > The definitions `include` and `exclude` is a replacement for `split`. You can just define one of those 3. Adding custom functions to the encoder pipeline: ```js const encoder = new Encoder({ normalize: function(str){ return str.toLowerCase(); }, prepare: function(str){ return str.replace(/&/g, " and "); }, finalize: function(arr){ return arr.filter(term => term.length > 2); } }); ``` Further reading: [Encoder Processing Workflow](#encoder-processing-workflow) Assign an encoder to an index: ```js const index = new Index({ encoder: encoder }); ``` Define language specific normalizations/transformations: ```js const encoder = new Encoder({ stemmer: new Map([ ["ly", ""] ]), filter: new Set([ "and", ]), mapper: new Map([ ["é", "e"] ]), matcher: new Map([ ["xvi", "16"] ]), replacer: [ /[´`’ʼ]/g, "'" ], }); ``` Further reading: [Encoder Processing Workflow](#encoder-processing-workflow) Or use built-in helpers alternatively: ```js const encoder = new Encoder() .addStemmer("ly", "") .addFilter("and") .addMapper("é", "e") .addMatcher("xvi", "16") .addReplacer(/[´`’ʼ]/g, "'"); ``` Some of the built-in helpers will automatically detect inputs and use the proper helper under the hood. So theoretically you can lazily just write: ```js const encoder = new Encoder() .addStemmer("ly", "") .addFilter("and") .addReplacer("é", "e") .addReplacer("xvi", "16") .addReplacer(/[´`’ʼ]/g, "'"); ``` You can also use presets and extend it with custom options: ```js import EnglishBookPreset from "flexsearch/lang/en"; const encoder = new Encoder( EnglishBookPreset, // use the preset but don't filter terms { filter: false } ); ``` Equivalent: ```js const encoder = new Encoder(EnglishBookPreset); encoder.assign({ filter: false }); ``` Assign multiple extensions to the encoder instance: ```js import Charset from "flexsearch"; import EnglishBookPreset from "flexsearch/lang/en"; // stack definitions to the encoder instance const encoder = new Encoder() .assign(Charset.LatinSoundex) .assign(EnglishBookPreset) // extend or override preset options: .assign({ minlength: 3 }); // assign further presets ... ``` > When adding extension to the encoder every previously assigned configuration is still intact, also when assigning custom functions, the previously added function will still execute. Add custom transformations to an existing index: ```js const encoder = new Encoder(Charset.Normalize); // filter terms encoder.addFilter("and"); // replace single chars encoder.addMapper("é", "e"); // replace char sequences encoder.addMatcher("xvi", "16"); // replace single chars or char sequences // at the end of a term encoder.addStemmer("ly", ""); // custom regex replace encoder.addReplacer(/[´`’ʼ]/g, "'"); ``` Using a custom filter: ```js encoder.addFilter(function(str){ // return true to keep the content return str.length > 1; }); ``` Shortcut for just assigning one encoder configuration to an index: ```js const index = new Index({ encoder: Charset.Normalize }); ``` ## Encoder Options
Option Values Description Default
You can just choose one of those 3 options:
include Encoder Split Options Define which of the string contents should be included (inclusion properties defaults to false) { letter: true, number: true }
exclude Encoder Split Options Define which of the string contents should be excluded (exclusion properties defaults to true) false
split false
RegExp
String
Encoder Split Options
The expression used to split the content into terms → include { letter: true, number: true }
Other options:
dedupe Boolean Deduplicate consecutive letters, e.g. "missing" to "mising" true
numeric Boolean By default, the extended numeric support (Triplets) inherits from chosen Encoder Split Options. You probably might want to disable Triplets to get a more exact result (fewer entries) in some cases. true
minlength Number Set the minimum term length which should be added to the index. This limit does not apply to the forward tokenizer. You still get results when just typing "f" on a term "flexsearch" when e.g. minlength: 4 was used. 1
maxlength Number Set the maximum term length which should be added to the index. Larger content will drop. 1
rtl Boolean Force Right-To-Left encoding (you should just apply this when the string content was not already encoded as RTL) false
normalize true enable normalization (default)
false disable normalization
function(str) => str custom function
The normalization stage will apply basic charset normalization e.g. by replacing "é" to "e" true
prepare function(str) => str custom function The preparation stage is a custom function direct followed when normalization was done false
finalize function([str]) => [str] custom function The finalization stage is a custom function executed at the last task in the encoding pipeline (here it gets an array of tokens and need to return an array of tokens) false
filter Set(["and", "to", "be"])
function(str) => bool custom function

encoder.addFilter("and")
Stop-word filter is like a blacklist of words to be filtered out from indexing at all (e.g. "and", "to" or "be"). This is also very useful when using Context Search false
stemmer Map([["ing", ""], ["ies", "y"]])

encoder.addStemmer("ing", "")
Stemmer will normalize several linguistic mutations of the same word (e.g. "run" and "running", or "property" and "properties"). This is also very useful when using Context Search false
mapper Map([["é", "e"], ["ß", "ss"]])

encoder.addMapper("é", "e")
Mapper will replace a single char (e.g. "é" into "e") false
matcher Map([["and", "&"], ["usd", "$"]])

encoder.addMatcher("and", "&")
Matcher will do same as Mapper but instead of single chars it will replace char sequences false
replacer [/[^a-z0-9]/g, "", /([^aeo])h(.)/g, "$1$2"])

encoder.addReplacer(/[^a-z0-9]/g, "")
Replacer takes custom regular expressions and couldn't get optimized in the same way as Mapper or Matcher. You should take this as the last option when no other replacement can do the same. false
cache Boolean In some very rare situations (large consecutive content with high cardinality) it might be useful to disable the internal event-loop-cache true
> [!TIP] > The methods `.addMapper()`, `.addMatcher()` and `.addReplacer()` might be confusing. For this reason they will automatically resolve to the right one when just using the same method for every rule. You can simplify this e.g. by just use `.addReplacer()` for each of this 3 rules. ### Encoder Split Options
Option Values Description Default
letter Boolean Toggle inclusion of letters on/off true
number Boolean Toggle inclusion of numerics on/off true
symbol Boolean Toggle inclusion of symbols on/off false
punctuation Boolean Toggle inclusion of punctuation on/off false
control Boolean Toggle inclusion of control chars on/off false
char String
Array[String]
Toggle inclusion of specific chars on/off false
## Custom Encoder Since it is very simple to create a custom Encoder, you are welcome to create your own. e.g. ```js function customEncoder(content){ const tokens = []; // split content into terms/tokens // apply your changes to each term/token // you will need to return an Array of terms/tokens // so just iterate through the input string and // push tokens to the array // ... return tokens; } const index = new Index({ // set to strict when your tokenization was already done tokenize: "strict", encode: customEncoder }); ``` You can't extend to the built-in tokenizer "exact", "forward", "bidirectional" or "full". If nothing of them are applicable for your task you should tokenize everything inside your custom encoder function. If you get some good results please feel free creating a pull request to share your encoder to the community. ### Encoder Processing Workflow 1. Charset Normalization 2. Custom Preparation 3. Split Content (into terms, apply includes/excludes) 4. Filter: Pre-Filter 5. Stemmer (substitute term endings) 6. Filter: Post-Filter 7. Replace Chars (Mapper) 8. Letter Deduplication 9. Matcher (substitute partials) 10. Custom Regex (Replacer) 11. Custom Finalize This workflow schema might help you to understand each step in the iteration:

## Right-To-Left Support > [!NOTE] > When a string is already encoded/interpreted as Right-To-Left you didn't need to use that. This option is just useful, when the source content wasn't encoded as RTL. Just set the property `rtl: true` when creating the `Encoder`: ```js const encoder = new Encoder({ rtl: true }); ``` ## CJK Word Break (Chinese, Japanese, Korean) ```js const index = new Index({ encoder: Charset.CJK }); index.add(0, "一个单词"); var results = index.search("单词"); ``` ## Built-In Language Packs - English: `en` - German: `de` - French: `fr` ### Import Language Packs: ES6 Modules The most simple way to assign charset/language specific encoding via modules is: ```js import EnglishPreset from "flexsearch/lang/en"; const index = Index({ charset: EnglishPreset }); ``` You can stack up and combine multiple presets: ```js import { Charset } from "flexsearch"; import EnglishPreset from "flexsearch/lang/en"; const index = Index({ charset: new Encoder( Charset.LatinAdvanced, EnglishPreset, { minlength: 3 } ) }); ``` You can also assign the encoder preset directly: ```js const index = Index({ encoder: Charset.Default }); ``` #### Import Language Packs: ES5 Legacy Browser When loading language packs, make sure that the library was loaded before: ```html ``` The language packs are registered on `FlexSearch.Language`: ```js const index = FlexSearch.Index({ encoder: FlexSearch.Language["en"] }); ``` You can stack up and combine multiple presets: ```js const index = FlexSearch.Index({ charset: new FlexSearch.Encoder( FlexSearch.Charset.LatinAdvanced, FlexSearch.Language["en"], { minlength: 3 } ) }); ``` #### Import Language Packs: Node.js In Node.js all built-in language packs files are available by its scope: ```js const EnglishPreset = require("flexsearch/lang/en"); const index = new Index({ encoder: EnglishPreset }); ``` ### Share Encoders Assigning the `Encoder` instance to the top level configuration will share the encoder to all fields. You should avoid this when contents of fields don't have the same type of content (e.g. one field contains terms, another contains numeric IDs). Sharing the encoder can improve encoding efficiency and memory allocation, but when not properly used also has negative effect to the performance. You can share encoders to any type of index, also through multiple instances of indexes (also documents). You should group similar types of contents to one encoder respectively. When you have different content types then define one for each of them. In this example there are two Document-Indexes for two different documents "orders" and "billings". You can also share encoder to different fields of just one document. ```js // usual term encoding const encoder_terms = Encoder( Charset.LatinAdvanced, // just add letters (no numbers) { include: { letter: true } } ); // numeric encoding const encoder_numeric = new Encoder(Charset.Default); const orders = Document({ document: { id: "id", index: [{ field: "product_title", encoder: encoder_terms },{ field: "product_details", encoder: encoder_terms },{ field: "order_date", encoder: encoder_numeric },{ field: "customer_id", encoder: encoder_numeric }] } }); const billings = Document({ document: { id: "id", index: [{ field: "product_title", encoder: encoder_terms },{ field: "product_content", encoder: encoder_terms },{ field: "billing_date", encoder: encoder_numeric },{ field: "customer_id", encoder: encoder_numeric }] } }); ``` ### Merge Documents When you have multiple document types (indexed by multiple indexes) but some of the data has same fields (like in the example above) and you can refer them by any identifier or key, you should consider merging those documents into one. This will hugely improve index size. E.g. when you merge "orders" and "billings" from example above by ID, then you can use just one index: ```js const encoder_terms = Encoder( Charset.LatinAdvanced, // just add letters (no numbers) { include: { letter: true } } ); const encoder_numeric = new Encoder( Charset.Default ); const merged = Document({ document: { id: "id", index: [{ field: "product_title", encoder: encoder_terms },{ field: "product_details", encoder: encoder_terms },{ field: "order_date", encoder: encoder_numeric },{ field: "billing_date", encoder: encoder_numeric },{ field: "customer_id", encoder: encoder_numeric }] } }); ``` ================================================ FILE: doc/export-import.md ================================================ ## Import / Export (In-Memory) > Persistent-Indexes and Worker-Indexes don't support Import/Export. Export an `Index` or `Document-Index` to the folder `/export/`: ```js import { promises as fs } from "fs"; await index.export(async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }); ``` Import from folder `/export/` into an `Index` or `Document-Index`: ```js const index = new Index({/* keep old config and place it here */}); const files = await fs.readdir("./export/"); for(let i = 0; i < files.length; i++){ const data = await fs.readFile("./export/" + files[i], "utf8"); await index.import(files[i], data); } ``` > You'll need to use the same configuration as you used before the export. Any changes on the configuration needs to be re-indexed. ## Fast-Boot Serialization for Server-Side-Rendering (PHP, Python, Ruby, Rust, Java, Go, Node.js, ...) > This is an experimental feature with limited support which probably might drop in future release. You're welcome to give some feedback. When using Server-Side-Rendering you can create a different export which instantly boot up. Especially when using Server-side rendered content, this could help to restore a __static__ index on page load. Document-Indexes aren't supported yet for this method. > When your index is too large you should use the default export/import mechanism. You'll need Javascript to create the serialized output. Alternatively just create a small Node.js script to build the output. As the first step populate the FlexSearch index with your contents. You have two options: ### 1. Create a function as string ```js const fn_string = index.serialize(); ``` The contents of `fn_string` is a valid Javascript-Function declared as `inject(index)`. Store it or place this somewhere in your code. This function basically looks like: ```js function inject(index){ index.reg = new Set([/* ... */]); index.map = new Map([/* ... */]); index.ctx = new Map([/* ... */]); } ``` You can save this function by e.g. `fs.writeFileSync("inject.js", fn_string);` or place it as string in your SSR-generated markup. After creating the index on client side just call the inject method like: ```js const index = new Index({/* use same configuration! */}); inject(index); ``` That's it. > You'll need to use the same configuration as you used before the export. Any changes on the configuration needs to be re-indexed. ### 2. Create just a function body as string Alternatively you can use lazy function declaration by passing `false` to the serialize function: ```js const fn_body = index.serialize(false); ``` You will get just the function body which looks like: ```js index.reg = new Set([/* ... */]); index.map = new Map([/* ... */]); index.ctx = new Map([/* ... */]); ``` Now you can place this in your code directly (name your index as `index`), or you can also create an inject function from it, e.g.: ```js const inject = new Function("index", fn_body); ``` This function is callable like the above example: ```js const index = new Index(); inject(index); ``` ## Export / Import (In-Memory) ### Node.js > Persistent-Indexes and Worker-Indexes don't support Import/Export. Export an `Index` or `Document-Index` to the folder `/export/`: ```js import { promises as fs } from "fs"; await index.export(async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }); ``` Import from folder `/export/` into an `Index` or `Document-Index`: ```js const index = new Index({/* keep old config and place it here */}); const files = await fs.readdir("./export/"); for(let i = 0; i < files.length; i++){ const data = await fs.readFile("./export/" + files[i], "utf8"); await index.import(files[i], data); } ``` > You'll need to use the same configuration as you used before the export. Any changes on the configuration needs to be re-indexed. ### Browser ```js index.export(function(key, data){ // you need to store both the key and the data! // e.g. use the key for the filename and save your data localStorage.setItem(key, data); }); ``` > The size of the export corresponds to the memory consumption of the library. To reduce export size you have to use a configuration which has less memory footprint (use the table at the bottom to get information about configs and its memory allocation). When your save routine runs asynchronously you have to use `async/await` or return a promise: ```js index.export(function(key, data){ return new Promise(function(resolve){ // do the saving as async resolve(); }); }); ``` Before you can import data, you need to create your index first. For document indexes provide the same document descriptor you used when export the data. This configuration isn't stored in the export. ```js const index = new Index({/* keep old config and place it here */}); ``` To import the data just pass a key and data: ``` const data = localStorage.getItem(key); index.import(key, data); ``` You need to import every key! Otherwise, your index does not work. You need to store the keys from the export and use this keys for the import (the order of the keys can differ). > The feature "fastupdate" is automatically disabled on import. This is just for demonstration and is not recommended, because you might have other keys in your localStorage which aren't supported as an import: ```js var keys = Object.keys(localStorage); for(let i = 0, key, data; i < keys.length; i++){ key = keys[i] data = localStorage.getItem(key); index.import(key, data); } ``` ================================================ FILE: doc/keystore.md ================================================ ## Big In-Memory Keystores The default maximum keystore limit for the In-Memory index is 2^24 of distinct terms/partials being stored (cardinality). An additional register could be enabled and is dividing the index into self-balanced partitions. The extended keystore is supported by any type of index. ```js const index = new Index({ // e.g. set keystore range to 8-Bit: // 2^8 * 2^24 = 2^32 keys total keystore: 8 }); ``` You can theoretically store up to 2^88 keys (64-Bit address range). The internal ID arrays scales automatically when limit of 2^31 has reached by using Proxy. > Persistent storages has no keystore limit by default. > You should not enable keystore when using persistent indexes, as long as you do not stress the buffer too hard before calling `index.commit()`. There is no additional memory cost when using a Keystore. ================================================ FILE: doc/persistent-clickhouse.md ================================================ # Clickhouse FlexSearch Clickhouse is highly optimized for raw I/O and write access. When your index needs to be frequently updated, this storage engine can help to reduce blocking write access. You'll need to install the npm package `clickhouse` into your project: ```bash npm install clickhouse@2.6.0 ``` Create an index and assign a Clickhouse storage adapter to it by using `index.mount(db)`: ```js import { Index } from "flexsearch"; import Database from "flexsearch/db/clickhouse"; // your database configuration const config = { host: "http://localhost", port: "8123", database: "default", basicAuth: null, debug: false }; // create an index const index = new Index(); // create db instance with optional prefix const db = new Database("my-store", config); // mount and await before transfering data await index.mount(db); // update the index as usual index.add(1, "content..."); index.update(2, "content..."); index.remove(3); // changes are automatically committed by default // when you need to wait for the task completion, then you // can use the commit method explicitely: await index.commit(); ``` > Changes are automatically committed by default when you need to wait for the task completion, then you can use the `await index.commit()` method explicitely. You can disable the auto-commit feature optionally. ## Configuration ### Custom DB Instance Pass a valid `clickhouse` instance on creation: ```js import { ClickHouse } from "clickhouse"; import Database from "flexsearch/db/clickhouse"; // assume you've created a custom database instance... const database = new ClickHouse({/* config */}); // pass database instance as option const db = new Database("my-store", { db: database }); ``` For every instance of `DocumentIndex`, `WorkerIndex` or `Index` (as standalone) you'll need to create a `Database` instance having its own name. ### ID Type The Clickhouse driver does not properly support upgrading a merge key by ALTER TABLE. Therefore, the default type for ID is `text`. You will save required disk space and also gain performance when define a numeric ID type explicitly. ```js import Database from "flexsearch/db/clickhouse"; // force integer type const db = new Database("my-store", { type: "integer" }); // .... index.add("15712", "content..."); // IDs will cast to integer let result = await index.search("content..."); // -> [15712] ``` BigInt: ```js const db = new Database("my-store", { type: "bigint" }); ``` To change the type later the index needs to be deleted by `db.destroy()` and re-created by `db.mount()`. ### Table Structure FlexSearch is creating different `DATABASE` entries for each index name e.g. "my-store". That is how you can use different stores for different indexes at the same time without getting collision of naming inheritance. Document Indexes will map their field names into table names respectively. ``` DATABASE |__TABLE map:field (FlexSearch Data) |__TABLE ctx:field (FlexSearch Data) |__TABLE tag:field (FlexSearch Data) |__TABLE cfg:field (FlexSearch Data) |__TABLE reg (FlexSearch Data) ``` The default DATABASE in Clickhouse is commonly named `default` and is required to make the first connection for creating the new databases. You can force using a specific default DATABASE by option: ```js const db = new Database({ database: "public" }); ``` ================================================ FILE: doc/persistent-indexeddb.md ================================================ # IndexedDB FlexSearch IndexedDB is a persistent storage supported by all major browsers. Create an index and assign a IndexedDB storage adapter to it by using `index.mount(db)`: ```js import { Index, IndexedDB } from "../dist/flexsearch.bundle.module.min.js"; // create an index const index = new Index(); // create db instance with optional namespace const db = new IndexedDB("my-store"); // mount and await before transfering data await index.mount(db); // update the index as usual index.add(1, "content..."); index.update(2, "content..."); index.remove(3); // changes are automatically committed by default // when you need to wait for the task completion, then you // can use the commit method explicitely: await index.commit(); ``` > Changes are automatically committed by default when you need to wait for the task completion, then you can use the `await index.commit()` method explicitely. You can disable the auto-commit feature optionally. ### Table Structure FlexSearch is creating different `DATABASE` entries for each index name e.g. "my-store". That is how you can use different stores for different indexes at the same time without getting collision of naming inheritance. Document Indexes will map their field names into table names respectively. ``` DATABASE |__OBJECTSTORE map:field (FlexSearch Data) |__OBJECTSTORE ctx:field (FlexSearch Data) |__OBJECTSTORE tag:field (FlexSearch Data) |__OBJECTSTORE cfg:field (FlexSearch Data) |__OBJECTSTORE reg (FlexSearch Data) ``` ================================================ FILE: doc/persistent-mongodb.md ================================================ # MongoDB FlexSearch MongoDB is a common NoSQL document store providing everything you might want from a solid FlexSearch storage solution. You'll need to install the npm package `mongodb` into your project: ```bash npm install mongodb@6.13.0 ``` Create an index and assign a MongoDB storage adapter to it by using `index.mount(db)`: ```js import { Index } from "flexsearch"; import Database from "flexsearch/db/mongodb"; // create an index const index = new Index(); // create db instance with optional namespace const db = new Database("my-store"); // mount and await before transfering data await index.mount(db); // update the index as usual index.add(1, "content..."); index.update(2, "content..."); index.remove(3); // changes are automatically committed by default // when you need to wait for the task completion, then you // can use the commit method explicitely: await index.commit(); ``` > Changes are automatically committed by default when you need to wait for the task completion, then you can use the `await index.commit()` method explicitely. You can disable the auto-commit feature optionally. ## Configuration ### Custom DB Instance Pass a valid `mongodb` instance on creation: ```js import { MongoClient } from "mongodb"; import Database from "flexsearch/db/mongodb"; // assume you've created a custom database instance... const database = new MongoClient("mongodb://localhost:27017/"); // connect and await await database.connect(); // pass database instance as option const db = new Database("my-store", { db: database }); ``` For every instance of `DocumentIndex`, `WorkerIndex` or `Index` (as standalone) you'll need to create a `Database` instance having its own name. ### Table Structure FlexSearch is creating different `DATABASE` entries for each index name e.g. "my-store". That is how you can use different stores for different indexes at the same time without getting collision of naming inheritance. Document Indexes will map their field names into table names respectively. ``` DATABASE |__COLLECTION map:field (FlexSearch Data) |__COLLECTION ctx:field (FlexSearch Data) |__COLLECTION tag:field (FlexSearch Data) |__COLLECTION cfg:field (FlexSearch Data) |__COLLECTION reg (FlexSearch Data) ``` ================================================ FILE: doc/persistent-postgres.md ================================================ # PostgreSQL FlexSearch Postgres is widely used as the default database engine. It can hold large amounts of data and also shines in scaling capabilities. You'll need to install the npm package `pg-promise` into your project: ```bash npm install pg-promise@11.10.2 ``` Create an index and assign a Postgres storage adapter to it by using `index.mount(db)`: ```js import { Index } from "flexsearch"; import Database from "flexsearch/db/postgres"; // your database configuration const config = { user: "postgres", pass: "postgres", host: "localhost", port: "5432", // database name: name: "postgres" }; // create an index const index = new Index(); // create db instance with optional prefix const db = new Database("my-store", config); // mount and await before transfering data await index.mount(db); // update the index as usual index.add(1, "content..."); index.update(2, "content..."); index.remove(3); // changes are automatically committed by default // when you need to wait for the task completion, then you // can use the commit method explicitely: await index.commit(); ``` > Changes are automatically committed by default when you need to wait for the task completion, then you can use the `await index.commit()` method explicitely. You can disable the auto-commit feature optionally. ## Configuration ### Custom DB Instance Pass a valid `pg-promise` instance on creation: ```js import pg_promise from "pg-promise"; import Database from "flexsearch/db/postgres"; const pgp = pg_promise(); // assume you've created a custom database instance... const database = pgp(`postgres://${user}:${pass}@${host}:${port}/${name}`); // pass database instance as option const db = new Database("my-store", { db: database }); ``` ### ID Type The Postgres driver supports type upgrading by ALTER TABLE. The index will automatically upgrade to `bigint` or to `string`, starting from `integer`. Once the type was upgraded, you'll need to re-create the index to switch back. When you data content is including numeric strings (eg. for ID "15712") then defining the type will automatically cast into the right type: ```js import Database from "flexsearch/db/postgres"; // force integer type const db = new Database("my-store", { type: "integer" }); // .... // the string will cast to integer index.add("15712", "content..."); ``` > You will save required disk space and also gain the performance when using a numeric ID over a string type. ### Table Structure FlexSearch is creating different `SCHEMA` for each index name e.g. "my-store". That is how you can use different stores for different indexes at the same time without getting collision of naming inheritance. Document Indexes will map their field names into table names respectively. ``` DATABASE_NAME |__ SCHEMA |__TABLE map:field (FlexSearch Data) |__TABLE ctx:field (FlexSearch Data) |__TABLE tag:field (FlexSearch Data) |__TABLE cfg:field (FlexSearch Data) |__TABLE reg (FlexSearch Data) ``` You can force using a specific SCHEMA by option without passing a name: ```js const db = new Database({ schema: "public" }); ``` Every Instance you made of `DocumentIndex`, `WorkerIndex` or `Index` (as standalone) you'll need to create a `Database` instance having its own schema. ================================================ FILE: doc/persistent-redis.md ================================================ # Redis FlexSearch Redis is a standard storage engine in almost every modern application stack. It combines the advantages of both worlds: the performance of an InMemory engine by also keeping data persistent. The downside is that all the data has to keep in RAM. You'll need to install the npm package `redis` into your project: ```bash npm install redis@4.7.0 ``` Create an index and assign a Redis storage adapter to it by using `index.mount(db)`: ```js import { Index } from "flexsearch"; import Database from "flexsearch/db/redis"; // Redis Connection const config = { host: "localhost", port: "6379", user: null, pass: null }; // create an index const index = new Index(); // create db instance with optional prefix const db = new Database("my-store", config); // mount and await before transfering data await index.mount(db); // update the index as usual index.add(1, "content..."); index.update(2, "content..."); index.remove(3); // changes are automatically committed by default // when you need to wait for the task completion, then you // can use the commit method explicitely: await index.commit(); ``` > Changes are automatically committed by default when you need to wait for the task completion, then you can use the `await index.commit()` method explicitely. You can disable the auto-commit feature optionally. ## Configuration ### Custom DB Instance Pass a valid `redis` instance on creation: ```js import { createClient } from "redis"; import Database from "flexsearch/db/redis"; // assume you've created a custom redis instance... const redis = await createClient("redis://localhost:6379").connect(); // pass instance as option const db = new Database("my-store", { db: redis }); ``` ### ID Type Since Redis stores everything as Strings, you'll need to define the type of ID. Otherwise, you'll get back stringify ID results by default. Also when your data content includes numeric strings (eg. "15712"), defining a type will automatically cast IDs into your choice: ```js import Database from "flexsearch/db/redis"; // force integer type const db = new Database("my-store", { type: "integer" }); // .... index.add("15712", "content..."); // IDs will cast to integer let result = await index.search("content..."); // -> [15712] ``` ### Table Structure FlexSearch is using a prefix style to prevent collision with other existing keys. Prefix Schema when no name was set: ``` flexsearch|map:field flexsearch|ctx:field flexsearch|tag:field flexsearch|cfg:field flexsearch|reg flexsearch|doc ``` Prefix Schema when name was set e.g. "my-store": ``` my-store|map:field my-store|ctx:field my-store|tag:field my-store|cfg:field my-store|reg my-store|doc ``` For every instance of `DocumentIndex`, `WorkerIndex` or `Index` (as standalone) you'll need to create a `Database` instance having its own name. ================================================ FILE: doc/persistent-sqlite.md ================================================ # SQLite FlexSearch SQLite offers you a compact and resource-friendly database which stores right into a portable db file on your filesystem. You'll need to install the npm package `sqlite3` into your project: ```bash npm install sqlite3@5.1.7 ``` Create an index and assign a SQLite storage adapter to it by using `index.mount(db)`: ```js import { Index } from "flexsearch"; import Database from "flexsearch/db/sqlite"; // create an index const index = new Index(); // create db instance with optional prefix const db = new Database("my-store"); // mount and await before transfering data await index.mount(db); // update the index as usual index.add(1, "content..."); index.update(2, "content..."); index.remove(3); // changes are automatically committed by default // when you need to wait for the task completion, then you // can use the commit method explicitely: await index.commit(); ``` > Changes are automatically committed by default when you need to wait for the task completion, then you can use the `await index.commit()` method explicitely. You can disable the auto-commit feature optionally. ## Configuration ### Custom Filepath ```js // set the filepath without passing a name: const db = new Database({ path: "./path-to-db/main.db" }); // ... await index.mount(db); ``` For any instance you made from type `DocumentIndex`, `WorkerIndex` or `Index` (as standalone) you'll need to create a `Database` instance having its own path. ### Custom DB Instance Pass a valid `sqlite3` instance on creation: ```js import sqlite3 from "sqlite3"; import Database from "flexsearch/db/sqlite"; // assume you've created a custom database instance... const database = new sqlite3.Database("./path-to-db/main.db"); // pass database instance as option const db = new Database("my-store", { db: database }); // ... await index.mount(db); ``` ### ID Type The SQLite driver does not properly support upgrading a key field type by ALTER TABLE. Therefore, the default type for ID is `text`. You can save required disk space and also gain performance when defining a numeric ID type expicitely. ```js // pass type in options const db = new Database("my-store", { type: "integer" }); ``` BigInt Range: ```js const db = new Database("my-store", { type: "bigint" }); ``` To change the ID type later you'll need to delete and re-create the index by calling `index.db.destroy()`. ### In-Memory Engine You can switch to SQLite native In-Memory store by passing `:memory:` as a name: ```js const db = new Database(":memory:"); ``` ## Table Structure FlexSearch is creating different files for each index name e.g. "my-store". That is how you can use different stores for different indexes at the same time without getting collision of naming inheritance. Document Indexes will map their field names into table names respectively. ``` FILENAME |__ MAIN |__TABLES map:field (FlexSearch Data) |__TABLES ctx:field (FlexSearch Data) |__TABLES tag:field (FlexSearch Data) |__TABLES cfg:field (FlexSearch Data) |__TABLES reg (FlexSearch Data) ``` ================================================ FILE: doc/persistent.md ================================================ # Persistent Indexes FlexSearch provides a new Storage Adapter where indexes are delegated through persistent storages. Supported: - [IndexedDB (Browser)](persistent-indexeddb.md) - [Redis](persistent-redis.md) - [SQLite](persistent-sqlite.md) - [Postgres](persistent-postgres.md) - [MongoDB](persistent-mongodb.md) - [Clickhouse](persistent-clickhouse.md) The `.export()` and `.import()` methods are still available for non-persistent In-Memory indexes. All search capabilities are available on persistent indexes like: - Context-Search - Suggestions - Cursor-based Queries (Limit/Offset) - Scoring (supports a resolution of up to 32767 slots) - Document-Search - Partial Search - Multi-Tag-Search - Boost Fields - Custom Encoder - Resolver - Tokenizer - Document Store (incl. enrich results) - Worker Threads to run in parallel - Auto-Balanced Cache (top queries + last queries) All persistent variants are optimized for larger sized indexes under heavy workload. Almost every task will be streamlined to run in batch/parallel, getting the most out of the selected database engine. Whereas the InMemory index can't share their data between different nodes when running in a cluster, every persistent storage can handle this by default. Examples Node.js: - [nodejs-commonjs](../example/nodejs-commonjs): - [basic-persistent](../example/nodejs-commonjs/basic-persistent) - [document-persistent](../example/nodejs-commonjs/document-persistent) - [nodejs-esm](../example/nodejs-esm): - [basic-persistent](../example/nodejs-esm/basic-persistent) - [document-persistent](../example/nodejs-esm/document-persistent) Examples Browser: - [browser-legacy](../example/browser-legacy): - [basic-persistent](../example/browser-legacy/basic-persistent) - [document-persistent](../example/browser-legacy/document-persistent) - [browser-module](../example/browser-module): - [basic-persistent](../example/browser-module/basic-persistent) - [document-persistent](../example/browser-module/document-persistent) ## Browser (IndexedDB) ```js import { Index, IndexedDB } from "../dist/flexsearch.bundle.module.min.js"; // create an index const index = new Index(); // create db instance with optional prefix const db = new IndexedDB("my-store"); // mount and await before transfering data await index.mount(db); // update the index as usual index.add(1, "content..."); index.update(2, "content..."); index.remove(3); // changes are automatically committed by default // when you need to wait for the task completion, then you // can use the commit method explicitely: await index.commit(); ``` Alternatively mount a store by index creation: ```js const index = new Index({ db: new IndexedDB("my-store") }); // await for the db response before access the first time await index.db; // apply changes to the index // ... ``` Query against a persistent storage just as usual: ```js const result = await index.search("gulliver"); ``` Auto-Commit is enabled by default and will process changes asynchronously in batch. You can fully disable the auto-commit feature and perform them manually: ```js const index = new Index({ db: new Storage("my-store"), commit: false }); // update the index index.add(1, "content..."); index.update(2, "content..."); index.remove(3); // transfer all changes to the db await index.commit(); ``` You can call the commit method manually also when `commit: true` option was set. ## Node.js ```js import { Index } from "flexsearch"; import Database from "flexsearch/db/postgres"; // create an index const index = new Index(); // create db instance with optional prefix const db = new Database("my-store"); // mount and await before transfering data await index.mount(db); // update the index as usual index.add(1, "content..."); index.update(2, "content..."); index.remove(3); // changes are automatically committed by default // when you need to wait for the task completion, then you // can use the commit method explicitely: await index.commit(); ``` ## Benchmark The benchmark was measured in "terms per second".
Store Add Search 1 Search N Replace Remove Not Found Scaling
terms per sec terms per sec terms per sec terms per sec terms per sec terms per sec
IndexedDB 123,298 83,823 62,370 57,410 171,053 425,744 No
Redis 1,566,091 201,534 859,463 117,013 129,595 875,526 Yes
Sqlite 269,812 29,627 129,735 174,445 1,406,553 122,566 No
Postgres 354,894 24,329 76,189 324,546 3,702,647 50,305 Yes
MongoDB 515,938 19,684 81,558 243,353 485,192 67,751 Yes
Clickhouse 1,436,992 11,507 22,196 931,026 3,276,847 16,644 Yes
__Search 1:__ Single term query
__Search N:__ Multi term query (Context-Search) The benchmark was executed against a single client. ## Delete Store + Migration Actually there exist no migration tool. You will probably need some kind of migration on future updates or when you need to re-create the index on the database. > [!CAUTION] > Please use the methods `index.destroy()` and `index.clear()` carefully. This methods will delete contents (truncate, drop) from the database accordingly to the passed `name` on initialization. Just clear all contents (truncate equivalent) from a store which connected to an index: ```js // always define a unique name when assigning a storage const db = new Database("my-store", config); await index.mount(db); // truncate all contents await index.clear(); ``` Drop all tables (and its schema): ```js // always define a unique name when assigning a storage const db = new Database("my-store", config); await index.mount(db); // drop all associated tables await index.destroy(); ``` A full migration cycle could be combined by: ```js // always define a unique name when assigning a storage const db = new Database("my-store", config); await index.mount(db); // drop all associated tables await index.destroy(); // when destroyed you'll need to mount again // to run table creation await index.mount(db); // access index ... ``` ================================================ FILE: doc/resolver.md ================================================ ## Resolver (Complex Queries) Retrieve an unresolved result: ```js const raw = index.search("a short query", { resolve: false }); ``` You can apply and chain different resolver methods to the raw result, e.g.: ```js raw.and( ... ) .and( ... ) .boost(2) .or( ... , ... ) .limit(100) .xor( ... ) .not( ... ) // final resolve .resolve({ limit: 10, offset: 0, enrich: true }); ``` The default resolver: ```js const raw = index.search("a short query", { resolve: false }); const result = raw.resolve(); ``` Alternatively you can create a `Resolver` by passing an initial query: ```js import { Resolver } from "flexsearch"; const raw = new Resolver({ // pass the index is required when query was set index: index, query: "a short query" }); const result = raw.resolve(); ``` ### Chainable Boolean Operations The basic concept explained: ```js // 1. get one or multiple unresolved results const raw1 = index.search("a short query", { resolve: false }); const raw2 = index.search("another query", { resolve: false, boost: 2 }); // 2. apply and chain resolver operations const raw3 = raw1.and(raw2, /* ... */); // raw1 has changed, raw2 is same, raw3 refers to raw1 // you can access the raw result by console.log("The aggregated result is:", raw3.result) // apply further operations ... // 3. resolve final result const result = raw3.resolve({ limit: 100, offset: 0 }); console.log("The final result is:", result) ``` Chain operations and nest inline queries: ```js const result = index.search("further query", { // set resolve to false on the first query resolve: false, // boost the first query boost: 2 }) .or({ // nested expression and: [{ query: "a query" },{ query: "another query" }] }) .not({ query: "some query" }) // resolve the result .resolve({ limit: 100, offset: 0 }); ``` Alternatively you can create a `Resolver` by passing an initial query: ```js import { Resolver } from "flexsearch"; const result = new Resolver({ // pass the index is required when query was set index: index, query: "further query", boost: 2 }) .or({ and: [{ query: "a query" },{ // you can bind a different index for this // query when IDs are from same source index: index, query: "another query" }] }) .not({ index: index, query: "some query" }) .resolve({ limit: 100, offset: 0 }); ``` When all queries are made against the same index, you can skip the index in every resolver stage followed after initially calling `new Resolve({ index: ... })`: ```js import { Resolver } from "flexsearch"; const result = new Resolver({ index: index, query: "a query" }) .and({ query: "another query" }) .or ({ query: "further query", boost: 2 }) .not({ query: "some query" }) .resolve(100); ``` ## Resolver Tasks
Method Description Return
.and(options,...)
.or(options,...)
.not(options,...)
.xor(options,...)
Apply an operation Returns a Resolver when resolve was not set to false within the options, otherwise it returns the result (or promise in async context).
.limit(number)
.offset(number)
.boost(number)
Apply boost, limit and offset to the result Returns a Resolver
.resolve(options) Resolve results Returns the final result or promise in async context (can't be executed twice)
## Resolver Options
Option Values Description Default
Resolver Task Options:
query String The search query
index Index
Document
Assign the index where the query should be applied to
suggest Boolean Enables suggestions in results false
boost Number Boost or reduce the score of this query 0
async Boolean Use a parallel processing workflow false
queue Boolean Use a queued processing workflow false
and
or
not
xor
Array<ResolverOptions> Apply nested queries
resolve Boolean Resolve the result immediately or not. When set to true all final resolve options are also allowed and there can't exist any further resolver operations. false
Document Resolver Options:
field
pluck
String Select the Document field on which the query should apply to.
Final Resolve Options:
enrich Boolean Enrich IDs from the results with the corresponding documents (for Document Indexes only) true
highlight Highlighting Options
String
Highlight query matches in the result (for Document Indexes only)
limit Number Sets the limit of results 100
offset Boolean Apply offset (skip items) 0
### Using Cached Queries ```js import { Resolver } from "flexsearch"; const result = new Resolver({ index: index, query: "a query", cache: true }) .and({ query: "another query", cache: true }) .resolve(100); ``` ## Async Resolver ### Using Async Queries (incl. Runtime Balancer) All async tasks will run in parallel, balanced by the runtime observer: ```js import { Resolver } from "flexsearch"; const resolver = new Resolver({ index: index, query: "a query", async: true }) .and({ query: "another query", async: true }) .or({ query: "some query", async: true }); const result = await resolver.resolve(100); ``` When you need to access the raw result `resolver.result` you should await for the task completion of all added resolver stages up to this point. ```js const resolver = new Resolver({ index: index, query: "a query", async: true }) .and({ query: "another query", async: true }); // await for the task completion await resolver.await; // get the raw result const raw = resolver.result; // continue adding further tasks ... ``` ### Queuing Async Queries All queued tasks will run consecutively, also balanced by the runtime observer: ```js import { Resolver } from "flexsearch"; const resolver = await new Resolver({ index: index, query: "a query", async: true }) .and({ query: "another query", queue: true }) .or({ query: "some query", queue: true }) .resolve(100); ``` When tasks are processed consecutively, it will skip specific resolver stages when there is no result expected. ### Compare Parallel VS. Consecutive When using the parallel workflow by passing `{ async: true }`, all resolver stages will send their requests (including nested tasks) to the DB immediately and calculate the results in the right order as soon as the request resolves. When the overall workload of your applications has some free resources, a parallel request workflow improves performance compared to the consecutive counterpart.

When using the consecutive workflow by passing `{ queue: true }`, all resolver stages will send their requests (including nested tasks) to the DB only when the previous request resolves. The advantage of this variant is when a stage becomes invalid because of the previous result, it can skip the request completely and continue with the next stage. This can reduce the overall workload.
================================================ FILE: doc/result-highlighting.md ================================================ ## Result Highlighting Demo: Auto-Complete > Result highlighting could be just enabled when using `Document`-Index with enabled document store by passing option `store` on creation. Alternatively you can simply upgrade id-content-pairs to a flat document when calling `.add(...)`. ```js // 1. create the document index const index = new Document({ document: { // using store is required store: true, index: [{ field: "title", tokenize: "forward", encoder: Charset.LatinBalance }] } }); // 2. add data index.add({ "id": 1, "title": "Carmencita" }); index.add({ "id": 2, "title": "Le clown et ses chiens" }); // 3. perform a query const result = index.search({ query: "karmen or clown or not found", // also get results when query has no exact match suggest: true, // use highlighting options or pass a template, where $1 is // a placeholder for the matched partial highlight: "$1", // optionally pick and apply search to just // one field and get back a flat result pluck: "title" }); ``` The result will look like: ```json [{ "id": 1, "highlight": "Carmencita" },{ "id": 2, "highlight": "Le clown et ses chiens" }] ``` There are several options to customize result highlighting. ### Highlighting Options
Option Values Description Default
template String The template to be applied on matches (e.g. "<b>$1</b>"), where $1 is a placeholder for the matched partial (mandatory)
boundary Boundary Options
Number
Limit the total length of highlighted content (add ellipsis by default). The template markup does not stack to the total length. false
ellipsis Ellipsis Options
Boolean
String
Define a custom ellipsis or disable "..."
merge Boolean Wrap consecutive matches by just a single template false
clip Boolean Allow to clip terms true
Boundary Options
boundary.total Number Limit the total length of highlighted content false
boundary.before Number Limit the length of content before highlighted parts (auto)
boundary.after Number Limit the length of content after highlighted parts (auto)
Ellipsis Options
ellipsis.template String The template to be applied on ellipsis (e.g. "<i>$1</i>"), where $1 is a placeholder for the ellipsis (mandatory)
ellipsis.pattern Boolean
String
Define a custom ellipsis or disable "..."
### Boundaries & Alignment You can limit the length of the highlighted content and also define a custom ellipsis. By default, all matches are automatically aligned to fit into the total size. You can customize these boundaries when also passing limits for surrounded text. Add some content to the index: ```js index.add({ "id": 1, "title": "Lorem ipsum dolor sit amet consetetur sadipscing elitr." }); ``` Perform a highlighted search (no boundaries): ```js const result = index.search({ query: "sit amet", highlight: "$1" }); ``` Result: ```js "Lorem ipsum dolor sit amet consetetur sadipscing elitr." ``` ___ #### Limit total boundary ```js const result = index.search({ query: "sit amet", highlight: { template: "$1", boundary: 32 } }); ``` > The highlight markup does not stack to the total length. > Result: ```js "...um dolor sit amet consetet..." ``` ___ #### Define custom ellipsis (text) ```js const result = index.search({ query: "sit amet", highlight: { template: "$1", boundary: 32, ellipsis: "[...]" } }); ``` Result: ```js "[...] dolor sit amet conset[...]" ``` You can also apply `""` or `false` to remove ellipsis. ___ #### Do not clip terms ```js const result = index.search({ query: "sit amet", highlight: { template: "$1", boundary: 32, clip: false } }); ``` Result: ```js "... dolor sit amet ..." ``` --- #### Merge consecutive matches ```js const result = index.search({ query: "sit amet", highlight: { template: "$1", boundary: 32, merge: true } }); ``` Result: ```js "...um dolor sit amet consetet..." ``` --- #### Limit surrounded text > Each of the boundary limits are optionally. Combine them as needed. ```js const result = index.search({ query: "sit amet", highlight: { template: "$1", boundary: { // length before match before: 3, // length after match after: 15, // overall length total: 32 } } }); ``` Result: ```js "...or sit amet consetetur sad..." ``` #### Use custom ellipsis (markup) When using markup within `ellipsis`, the markup length stack up to the total boundary. You can provide a `template` also for ellipsis to apply total boundary properly by do not stack up the markup length. ```js const result = index.search({ query: "sit amet", highlight: { template: "$1", // limit the total length to 32 chars boundary: 32, ellipsis: { // pass a template, where $1 is // a placeholder for the ellipsis template: "$1", // define custom ellipsis pattern: "..." } } }); ``` Result: ```js "... dolor sit amet conset..." ``` ### Using Result Highlighting on Resolver When using complex queries by `Resolver` you can pass a highlight option to any one of the resolver stages that is also including a query. The last resolver stage will then automatically inherit necessary options. ```js const raw = new Resolver({ index: index, field: "title", query: "some query" }) .or({ field: "title", // highlight requires a query query: "highlight this", // define on a single resolver stage highlight: { /* ... */ } }) .not({ field: "title", query: "undefined", }) .resolve(); ``` ================================================ FILE: doc/worker.md ================================================ # Worker Parallelism (Browser + Node.js) ### Worker Index Using a Worker-Index is pretty much the same as using a standard `Index`. > Worker-Index always return a `Promise` for all methods called on the index. > When adding/updating/removing large bulks of content to the index, it is recommended to use the async version of each method to prevent blocking issues on the main thread. Read more about [Asynchronous Runtime Balancer](async.md) ### Worker Document > The internal worker model is distributed by document fields and will solve subtasks in parallel. Documents will create worker automatically for each field by just apply the option `worker: true`: ```js const index = new Document({ worker: true, document: { id: "id", index: ["name", "title"], tag: ["cat"] } }); index.add({ id: 1, cat: "catA", name: "Tom", title: "some" }).add({ id: 2, cat: "catA", name: "Ben", title: "title" }).add({ id: 3, cat: "catB", name: "Max", title: "to" }).add({ id: 4, cat: "catB", name: "Tim", title: "index"" }); ``` When you perform a field search through multiple fields then this task is being well-balanced through all involved workers, which can solve their subtasks independently. ## Examples ### ES6 Module (Bundle): When using one of the bundles from `/dist/` you can create a Worker-Index: ```js import { Worker } from "./dist/flexsearch.bundle.module.min.js"; const index = new Worker({/* options */ }); await index.add(1, "some"); await index.add(2, "content"); await index.add(3, "to"); await index.add(4, "index"); ``` ### ES6 Module (Non-Bundle): When not using a bundle you can take the worker file from `/dist/` folder as follows: ```js import Worker from "./dist/module/worker.js"; const index = new Worker({/* options */ }); index.add(1, "some") .add(2, "content") .add(3, "to") .add(4, "index"); ``` ### Browser Legacy (Bundle): When loading a legacy bundle via script tag (non-modules): ```js const index = new FlexSearch.Worker({/* options */ }); await index.add(1, "some"); await index.add(2, "content"); await index.add(3, "to"); await index.add(4, "index"); ``` ### Worker (Node.js) The worker model for Node.js is based on native worker threads and works exactly the same way: ```js const { Document } = require("flexsearch"); const index = new Document({ worker: true, document: { id: "id", index: ["name", "title"], tag: ["cat"] } }); ``` Or create a single worker instance for a non-document index: ```js const { Worker } = require("flexsearch"); const index = new Worker({ options }); ``` ### Worker-Index Options > Worker-Index Options extends the default [Index Options](../README.md#index-options), you can apply also.
Option Values Description
config String Either the absolute URL to the config file when used in Browser context (should match the Same-Origin-Policy) or the filepath to the configuration file when used in Node.js context
export function The export handler function. Read more about Export
import function The export handler function. Read more about Import
## Extern Worker Configuration When using Worker by __also__ assign custom functions to the options e.g.: - Custom Encoder - Custom Encoder methods (normalize, prepare, finalize) - Custom Score (function) - Custom Filter (function) - Custom Fields (function) ... then you'll need to move your __field configuration__ into a file which exports the configuration as a `default` export. The field configuration is not the whole Document-Descriptor. When not using custom functions in combination with Worker you can skip this part. Since every field resolves into a dedicated Worker, also every field which includes custom functions should have their own configuration file accordingly. Let's take this document descriptor: ```js { document: { index: [{ // this is the field configuration // ----> field: "custom_field", custom: function(data){ return "custom field content"; } // <------ }] } }; ``` The configuration which needs to be available as a default export is: ```js { field: "custom_field", custom: function(data){ return "custom field content"; } }; ``` You're welcome to make some suggestions how to improve the handling of extern configuration. ### Example Node.js: An extern configuration for one WorkerIndex, let's assume it is located in `./custom_field.js`: ```js const { Charset } = require("flexsearch"); const { LatinSimple } = Charset; // it requires a default export: module.exports = { encoder: LatinSimple, tokenize: "forward", // custom function: custom: function(data){ return "custom field content"; } }; ``` Create Worker Index along the configuration above: ```js const { Document } = require("flexsearch"); const flexsearch = new Document({ worker: true, document: { index: [{ // the field name needs to be set here field: "custom_field", // path to your config from above: config: "./custom_field.js", }] } }); ``` ### Browser (ESM) An extern configuration for one WorkerIndex, let's assume it is located in `./custom_field.js`: ```js import { Charset } from "./dist/flexsearch.bundle.module.min.js"; const { LatinSimple } = Charset; // it requires a default export: export default { encoder: LatinSimple, tokenize: "forward", // custom function: custom: function(data){ return "custom field content"; } }; ``` Create Worker Index with the configuration above: ```js import { Document } from "./dist/flexsearch.bundle.module.min.js"; // you will need to await for the response! const flexsearch = await new Document({ worker: true, document: { index: [{ // the field name needs to be set here field: "custom_field", // Absolute URL to your config from above: config: "http://localhost/custom_field.js" }] } }); ``` Here it needs the __absolute URL__, because the WorkerIndex context is from type `Blob` and you can't use relative URLs starting from this context. ### Test Case As a test the whole IMDB data collection was indexed, containing of: JSON Documents: 9,273,132
Fields: 83,458,188
Tokens: 128,898,832
The used index configuration has 2 fields (using bidirectional context of `depth: 1`), 1 custom field, 2 tags and a full datastore of all input json documents. A non-Worker Document index requires 181 seconds to index all contents.
The Worker index just takes 32 seconds to index them all, by processing every field and tag in parallel. For such large content it is a quite impressive result. ## Export / Import Worker Indexes (Node.js) Worker will save/load their data dedicated and does not need the message channel for the data transfer. ### Basic Worker Index > This feature follows the strategy of using [Extern Worker Configuration](#extern-worker-configuration) in combination with [Basic Export Import](../example/nodejs-commonjs/basic-export-import). Example (CommonJS): [basic-worker-export-import](../example/nodejs-commonjs/basic-worker-export-import)
Example (ESM): [basic-worker-export-import](../example/nodejs-esm/basic-worker-export-import) Provide the index configuration and keep it, because it isn't stored. Provide a parameter `config` which is including the filepath to the extern configuration file: ```js const dirname = import.meta.dirname; const config = { tokenize: "forward", config: dirname + "/config.js" }; ``` > Any changes you made to the configuration will almost require a full re-index. Provide the extern configuration file e.g. `/config.js` as a default export including the methods `export` and `import`: ```js import { promises as fs } from "fs"; export default { tokenize: "forward", export: async function(key, data){ // like the usual export write files by key + data await fs.writeFile("./export/" + key, data, "utf8"); }, import: async function(index){ // get the file contents of the export directory let files = await fs.readdir("./export/"); files = await Promise.all(files); // loop through the files and push their contents to the index // by also passing the filename as the first parameter for(let i = 0; i < files.length; i++){ const data = await fs.readFile("./export/" + files[i], "utf8"); index.import(files[i], data); } } }; ``` Create your index by assigning the configuration file from above: ```js import { Worker as WorkerIndex } from "flexsearch"; const index = await new WorkerIndex(config); // add data to the index // ... ``` Export the index: ```js await index.export(); ``` Import the index: ```js // create the same type of index you have used by .export() // along with the same configuration const index = await new WorkerIndex(config); await index.import(); ``` ### Document Worker Index > This feature follows the strategy of using [Extern Worker Configuration](#extern-worker-configuration) in combination with [Document Export Import](../example/nodejs-esm/document-export-import). Document Worker exports all their feature including: - Multi-Tag Indexes - Context-Search Indexes - Document-Store Example (CommonJS): [document-worker-export-import](../example/nodejs-commonjs/document-worker-export-import)
Example (ESM): [document-worker-export-import](../example/nodejs-esm/document-worker-export-import) Provide the index configuration and keep it, because it isn't stored. Provide a parameter `config` which is including the filepath to the extern configuration file: ```js const dirname = import.meta.dirname; const config = { worker: true, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", config: dirname + "/config.primaryTitle.js" },{ field: "originalTitle", config: dirname + "/config.originalTitle.js" }], tag: [{ field: "startYear" },{ field: "genres" }] } }; ``` > Any changes you made to the configuration will almost require a full re-index. Provide the extern configuration file as a default export including the methods `export` and `import`: ```js import { promises as fs } from "fs"; export default { tokenize: "forward", export: async function(key, data){ // like the usual export write files by key + data await fs.writeFile("./export/" + key, data, "utf8"); }, import: async function(file){ // instead of looping you will get the filename as 2nd paramter // just return the loaded contents as a string return await fs.readFile("./export/" + file, "utf8"); } }; ``` Create your index by assigning the configuration file from above: ```js import { Document } from "flexsearch"; const document = await new Document(config); // add data to the index // ... ``` Export the index by providing a key-data handler: ```js await document.export(async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }); ``` Import the index: ```js const files = await fs.readdir("./export/"); // create the same type of index you have used by .export() // along with the same configuration const document = await new Document(config); await Promise.all(files.map(async file => { const data = await fs.readFile("./export/" + file, "utf8"); // call import (async) await document.import(file, data); })); ``` ## CSP-friendly Worker (Browser) When using worker via one of the bundled versions (e.g. `flexearch.bundle.min.js`), the worker will be created by code generation under the hood. This might have issues when using strict CSP settings. You can overcome this issue by using the non-bundled versions e.g. `dist/module/` or by passing the filepath to the worker file instead of `true` like `worker: "dist/module/worker/worker.js"`. ================================================ FILE: docker-compose.yml ================================================ version: "3.6" services: postgres: image: postgres:latest restart: on-failure volumes: - pg-data:/var/lib/postgresql/data environment: - POSTGRES_DB=postgres - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres ports: - "5432:5432" container_name: flexsearch_postgres_db command: - "postgres" - "-c" - "shared_preload_libraries=pg_stat_statements" clickhouse: image: yandex/clickhouse-server:latest restart: on-failure volumes: - ch-data:/var/lib/clickhouse ports: - "8123:8123" container_name: flexsearch_clickhouse_db redis: image: redislabs/rejson:latest restart: on-failure volumes: - rd-data:/data ports: - "6379:6379" container_name: flexsearch_redis_db mongo: image: mongo:latest restart: on-failure volumes: - mg-data:/data ports: - "27017:27017" container_name: flexsearch_mongo_db volumes: pg-data: driver: local ch-data: driver: local rd-data: driver: local mg-data: driver: local ================================================ FILE: example/browser-legacy/basic/index.html ================================================ Example: browser-legacy-basic ================================================ FILE: example/browser-legacy/basic-persistent/index.html ================================================ Example: browser-legacy-basic-persistent ================================================ FILE: example/browser-legacy/basic-resolver/index.html ================================================ Example: browser-legacy-basic-resolver ================================================ FILE: example/browser-legacy/basic-suggestion/index.html ================================================ Example: browser-legacy-basic-suggestion ================================================ FILE: example/browser-legacy/basic-worker/index.html ================================================ Example: browser-legacy-basic-worker ================================================ FILE: example/browser-legacy/document/index.html ================================================ Example: browser-legacy-document ================================================ FILE: example/browser-legacy/document-highlighting/index.html ================================================ Example: browser-legacy-document-highlighting ================================================ FILE: example/browser-legacy/document-persistent/index.html ================================================ Example: browser-legacy-document-persistent ================================================ FILE: example/browser-legacy/document-resolver/index.html ================================================ Example: browser-legacy-document-resolver ================================================ FILE: example/browser-legacy/document-worker/index.html ================================================ Example: browser-legacy-document-worker ================================================ FILE: example/browser-legacy/language-pack/index.html ================================================ Example: browser-legacy-language-packs ================================================ FILE: example/browser-module/basic/index.html ================================================ Example: browser-module-basic ================================================ FILE: example/browser-module/basic-persistent/index.html ================================================ Example: browser-module-basic-persistent ================================================ FILE: example/browser-module/basic-resolver/index.html ================================================ Example: browser-module-basic-resolver ================================================ FILE: example/browser-module/basic-suggestion/index.html ================================================ Example: browser-module-basic-suggestion ================================================ FILE: example/browser-module/basic-worker/index.html ================================================ Example: browser-module-basic-worker ================================================ FILE: example/browser-module/basic-worker-extern-config/config.js ================================================ import { Encoder } from "https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.bundle.module.min.js"; export default { tokenize: "forward", encoder: new Encoder({ normalize: function(str){ return str.toLowerCase(); } }) }; ================================================ FILE: example/browser-module/basic-worker-extern-config/index.html ================================================ Example: browser-module-basic-worker-extern-config ================================================ FILE: example/browser-module/document/index.html ================================================ Example: browser-module-document ================================================ FILE: example/browser-module/document-highlighting/index.html ================================================ Example: browser-module-document-highlighting ================================================ FILE: example/browser-module/document-persistent/index.html ================================================ Example: browser-module-document-persistent ================================================ FILE: example/browser-module/document-resolver/index.html ================================================ Example: browser-module-document-resolver ================================================ FILE: example/browser-module/document-worker/index.html ================================================ Example: browser-module-document-worker ================================================ FILE: example/browser-module/document-worker-extern-config/config.originalTitle.js ================================================ import { Encoder, Charset } from "https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.bundle.module.min.js"; import EnglishPreset from "https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/module/lang/en.js"; export default { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ) }; ================================================ FILE: example/browser-module/document-worker-extern-config/config.primaryTitle.js ================================================ import { Encoder, Charset } from "https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/flexsearch.bundle.module.min.js"; import EnglishPreset from "https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.8.2/dist/module/lang/en.js"; export default { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ) }; ================================================ FILE: example/browser-module/document-worker-extern-config/index.html ================================================ Example: browser-module-document-worker-extern-config ================================================ FILE: example/browser-module/language-pack/index.html ================================================ Example: browser-module-language-packs ================================================ FILE: example/nodejs-commonjs/.document-worker-persistent/config.originalTitle.js ================================================ const { Encoder, Charset } = require("flexsearch"); const EnglishPreset = require("flexsearch/lang/en"); module.exports = { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ) }; ================================================ FILE: example/nodejs-commonjs/.document-worker-persistent/config.primaryTitle.js ================================================ const { Encoder, Charset } = require("flexsearch"); const EnglishPreset = require("flexsearch/lang/en"); module.exports = { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ) }; ================================================ FILE: example/nodejs-commonjs/.document-worker-persistent/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-commonjs/.document-worker-persistent/index.js ================================================ const { Document } = require("flexsearch"); // const Sqlite = require("flexsearch/db/sqlite"); // const Postgres = require("flexsearch/db/postgres"); // const MongoDB = require("flexsearch/db/mongodb"); // const Redis = require("flexsearch/db/redis"); // const Clickhouse = require("flexsearch/db/clickhouse"); // loading test data const data = require(__dirname + "/data.json"); (async function(){ // create DB instance with namespace //const db = new Sqlite("my-store"); // create the document index const document = await new Document({ worker: true, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", config: __dirname + "/config.primaryTitle.js" },{ field: "originalTitle", config: __dirname + "/config.originalTitle.js" }], tag: [{ field: "startYear" },{ field: "genres" }] } }); //await document.mount(db); // await document.destroy(); // await document.mount(db); // add test data for(let i = 0; i < data.length; i++){ await document.add(data[i]); } // transfer changes (bulk) //await document.commit(); console.log(document) // perform a query const result = await document.search({ query: "carmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log(result); }()); ================================================ FILE: example/nodejs-commonjs/.document-worker-persistent/package.json ================================================ { "name": "nodejs-worker-persistent", "dependencies": { "flexsearch": "^0.8.200", "sqlite3": "^5.1.7" } } ================================================ FILE: example/nodejs-commonjs/basic/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/basic/index.js ================================================ const { Index } = require("flexsearch"); // create a simple index which can store id-content-pairs const index = new Index({ // use forward when you want to match partials // e.g. match "flexsearch" when query "flex" tokenize: "forward" }); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // perform query const result = index.search("cute cat"); // display results result.forEach(i => { console.log(data[i]); }); ================================================ FILE: example/nodejs-commonjs/basic/package.json ================================================ { "name": "nodejs-commonjs-basic", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/basic-export-import/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/basic-export-import/index.js ================================================ const { Index } = require("flexsearch"); (async function(){ // you will need to keep the index configuration // they will not export, also every change to the // configuration requires a full re-index const config = { tokenize: "forward" }; // create a simple index which can store id-content-pairs let index = new Index(config); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // perform query let result = index.search("cute cat"); // display results result.forEach(i => { console.log(data[i]); }); // ----------------------- // EXPORT // ----------------------- const fs = require("fs").promises; await fs.mkdir("./export/").catch(e => {}); await index.export(async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }); // ----------------------- // IMPORT // ----------------------- // create the same type of index you have used by .export() // along with the same configuration index = new Index(config); // load them in parallel const files = await fs.readdir("./export/"); await Promise.all(files.map(async file => { const data = await fs.readFile("./export/" + file, "utf8"); index.import(file, data); })); // perform query result = index.search("cute cat"); // display results console.log("-------------------------------------"); result.forEach(i => { console.log(data[i]); }); }()); ================================================ FILE: example/nodejs-commonjs/basic-export-import/package.json ================================================ { "name": "nodejs-commonjs-basic-export-import", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/basic-persistent/README.md ================================================ ```bash npm install ``` ```bash npm install sqlite3@5.1.7 ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/basic-persistent/index.js ================================================ const { Index } = require("flexsearch"); const Sqlite = require("flexsearch/db/sqlite"); // const Postgres = require("flexsearch/db/postgres"); // const MongoDB = require("flexsearch/db/mongodb"); // const Redis = require("flexsearch/db/redis"); // const Clickhouse = require("flexsearch/db/clickhouse"); (async function(){ // create DB instance with namespace const db = new Sqlite("my-store"); // create a simple index which can store id-content-pairs const index = new Index({ tokenize: "forward" }); // mount database to the index await index.mount(db); // await index.destroy(); // await index.mount(db); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // transfer changes (bulk) await index.commit(); // perform query const result = await index.search({ query: "cute cat" }); // display results result.forEach(i => { console.log(data[i]); }); }()); ================================================ FILE: example/nodejs-commonjs/basic-persistent/package.json ================================================ { "name": "nodejs-commonjs-basic-persistent", "dependencies": { "flexsearch": "^0.8.200", "sqlite3": "^5.1.7" } } ================================================ FILE: example/nodejs-commonjs/basic-resolver/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/basic-resolver/index.js ================================================ const { Index, Resolver } = require("flexsearch"); // create a simple index which can store id-content-pairs const index = new Index({ tokenize: "forward" }); // some test data const data = [ 'cats abcd efgh ijkl dogs pigs rats cute', 'cats abcd efgh ijkl dogs pigs cute', 'cats abcd efgh ijkl dogs cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // perform query const result = new Resolver({ index: index, query: "black" }) .or({ index: index, query: "cute" }) .and([{ index: index, query: "dog" },{ index: index, query: "cat" }]) .not({ index: index, query: "rat" }) .resolve(); // display results result.forEach(i => { console.log(data[i]); }); ================================================ FILE: example/nodejs-commonjs/basic-resolver/package.json ================================================ { "name": "nodejs-commonjs-basic-resolver", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/basic-suggestion/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/basic-suggestion/index.js ================================================ const { Index } = require("flexsearch"); // create a simple index which can store id-content-pairs const index = new Index({ tokenize: "forward" }); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl dogs cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // perform query const result = index.search({ query: "black dog or cute cat", suggest: true }); // display results result.forEach(i => { console.log(data[i]); }); ================================================ FILE: example/nodejs-commonjs/basic-suggestion/package.json ================================================ { "name": "nodejs-commonjs-basic-suggestion", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/basic-worker/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/basic-worker/index.js ================================================ const { Worker: WorkerIndex } = require("flexsearch"); (async function(){ // create a simple index which can store id-content-pairs const index = await new WorkerIndex({ tokenize: "forward" }); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add test data data.forEach((item, id) => { index.add(id, item); }); // perform query const result = await index.search({ query: "cute cat", }); // display results result.forEach(i => { console.log(data[i]); }); }()); ================================================ FILE: example/nodejs-commonjs/basic-worker/package.json ================================================ { "name": "nodejs-commonjs-basic-worker", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/basic-worker-export-import/config.js ================================================ const { Encoder } = require("flexsearch"); const fs = require("fs").promises; (async function(){ await fs.mkdir("./export/").catch(e => {}); }()); module.exports = { tokenize: "forward", encoder: new Encoder({ normalize: function(str){ return str.toLowerCase(); } }), export: async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }, import: async function(index){ let files = await fs.readdir("./export/"); files = await Promise.all(files); for(let i = 0; i < files.length; i++){ const data = await fs.readFile("./export/" + files[i], "utf8"); index.import(files[i], data); } } }; ================================================ FILE: example/nodejs-commonjs/basic-worker-export-import/index.js ================================================ const { Worker: WorkerIndex } = require("flexsearch"); // you will need to keep the index configuration // they will not export, also every change to the // configuration requires a full re-index const config = { tokenize: "forward", config: __dirname + "/config.js" }; (async function(){ // create a simple index which can store id-content-pairs // and await (!) for the worker response let index = await new WorkerIndex(config); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add test data for(let i = 0; i < data.length; i++){ await index.addAsync(i, data[i]); } // perform query let result = await index.search({ query: "cute cat", }); // display results result.forEach(i => { console.log(data[i]); }); // ----------------------- // EXPORT // ----------------------- await index.export(); // ----------------------- // IMPORT // ----------------------- // create the same type of index you have used by .export() // along with the same configuration index = await new WorkerIndex(config); await index.import(); // perform query result = await index.search({ query: "cute cat", }); // display results console.log("-------------------------------------"); result.forEach(i => { console.log(data[i]); }); }()); ================================================ FILE: example/nodejs-commonjs/basic-worker-export-import/package.json ================================================ { "name": "nodejs-commonjs-basic-worker-extern-config", "dependencies": { "flexsearch": "github:nextapps-de/flexsearch" } } ================================================ FILE: example/nodejs-commonjs/basic-worker-extern-config/config.js ================================================ const { Encoder } = require("flexsearch"); module.exports = { tokenize: "forward", encoder: new Encoder({ normalize: function(str){ return str.toLowerCase(); } }) }; ================================================ FILE: example/nodejs-commonjs/basic-worker-extern-config/index.js ================================================ const { Worker: WorkerIndex } = require("flexsearch"); (async function(){ // create a simple index which can store id-content-pairs // and await (!) for the worker response const index = await new WorkerIndex({ tokenize: "forward", config: __dirname + "/config.js" }); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add test data data.forEach((item, id) => { index.add(id, item); }); // perform query const result = await index.search({ query: "cute cat", }); // display results result.forEach(i => { console.log(data[i]); }); }()); ================================================ FILE: example/nodejs-commonjs/basic-worker-extern-config/package.json ================================================ { "name": "nodejs-commonjs-basic-worker-extern-config", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/document/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/document/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-commonjs/document/index.js ================================================ const { Document, Charset } = require("flexsearch"); // loading test data const data = require(__dirname + "/data.json"); // create the document index const document = new Document({ document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward", encoder: Charset.LatinBalance },{ field: "originalTitle", tokenize: "forward", encoder: Charset.LatinBalance }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // add test data for(let i = 0; i < data.length; i++){ document.add(data[i]); } // perform a query const result = document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log(result); ================================================ FILE: example/nodejs-commonjs/document/package.json ================================================ { "name": "nodejs-commonjs-document", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/document-export-import/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/document-export-import/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-commonjs/document-export-import/index.js ================================================ const { Document, Charset } = require("flexsearch"); const fs = require("fs").promises; (async function(){ // loading test data const data = JSON.parse(await fs.readFile(__dirname + "/data.json", "utf8")); // you will need to keep the index configuration // they will not export, also every change to the // configuration requires a full re-index const config = { document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward", encoder: Charset.LatinBalance },{ field: "originalTitle", tokenize: "forward", encoder: Charset.LatinBalance }], tag: [{ field: "startYear" },{ field: "genres" }] } }; // create the document index let document = new Document(config); // add test data for(let i = 0; i < data.length; i++){ document.add(data[i]); } // perform a query let result = document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); // output results console.log(result); // EXPORT // ----------------------- await fs.mkdir("./export/").catch(e => {}); // call export await document.export(async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }); // IMPORT // ----------------------- // create the same type of index you have used by .export() // along with the same configuration document = new Document(config); // load them in parallel const files = await fs.readdir("./export/"); await Promise.all(files.map(async file => { const data = await fs.readFile("./export/" + file, "utf8"); // call import document.import(file, data); })) // perform query result = document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); // output results console.log("-------------------------------------"); console.log(result); }()); ================================================ FILE: example/nodejs-commonjs/document-export-import/package.json ================================================ { "name": "nodejs-commonjs-document-export-import", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/document-persistent/README.md ================================================ ```bash npm install ``` ```bash npm install sqlite3@5.1.7 ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/document-persistent/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-commonjs/document-persistent/index.js ================================================ const { Document, Charset } = require("flexsearch"); const Sqlite = require("flexsearch/db/sqlite"); // const Postgres = require("flexsearch/db/postgres"); // const MongoDB = require("flexsearch/db/mongodb"); // const Redis = require("flexsearch/db/redis"); // const Clickhouse = require("flexsearch/db/clickhouse"); // loading test data const data = require(__dirname + "/data.json"); (async function(){ // create DB instance with namespace const db = new Sqlite("my-store"); // create the document index const document = new Document({ document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward", encoder: Charset.LatinSimple },{ field: "originalTitle", tokenize: "forward", encoder: Charset.LatinSimple }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // mount database to the index await document.mount(db); // await document.destroy(); // await document.mount(db); // add test data for(let i = 0; i < data.length; i++){ document.add(data[i]); } // transfer changes (bulk) await document.commit(); // perform a query const result = await document.search({ query: "carmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log(result); }()); ================================================ FILE: example/nodejs-commonjs/document-persistent/package.json ================================================ { "name": "nodejs-commonjs-document-persistent", "dependencies": { "flexsearch": "^0.8.200", "sqlite3": "^5.1.7" } } ================================================ FILE: example/nodejs-commonjs/document-resolver/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/document-resolver/index.js ================================================ const { Document, Charset, Resolver } = require("flexsearch"); // some test data const data = [{ "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] },{ "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }]; // create the document index const index = new Document({ document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward", encoder: Charset.LatinBalance },{ field: "originalTitle", tokenize: "forward", encoder: Charset.LatinBalance }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // add test data for(let i = 0; i < data.length; i++){ index.add(data[i]); } // perform a complex query + enrich results let result = new Resolver({ index: index, query: "karmen", pluck: "primaryTitle" }).or({ query: "clown", pluck: "primaryTitle" }).and({ query: "not found", field: "originalTitle", suggest: true }).not({ query: "clown", pluck: "primaryTitle" }).resolve({ enrich: true }); // display results console.log(result); ================================================ FILE: example/nodejs-commonjs/document-resolver/package.json ================================================ { "name": "nodejs-commonjs-document-resolver", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/document-worker/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/document-worker/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-commonjs/document-worker/index.js ================================================ const { Document } = require("flexsearch"); // loading test data const data = require(__dirname + "/data.json"); (async function(){ // create the document index const document = new Document({ worker: true, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward", encoder: "LatinBalance" },{ field: "originalTitle", tokenize: "forward", encoder: "LatinBalance" }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // add test data for(let i = 0; i < data.length; i++){ await document.add(data[i]); } // perform a query const result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log(result); }()); ================================================ FILE: example/nodejs-commonjs/document-worker/package.json ================================================ { "name": "nodejs-commonjs-document-worker", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/document-worker-export-import/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/document-worker-export-import/config.originalTitle.js ================================================ const { Encoder, Charset } = require("flexsearch"); const EnglishPreset = require("flexsearch/lang/en"); const fs = require("fs").promises; (async function(){ await fs.mkdir("./export/").catch(e => {}); }()); module.exports = { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ), export: async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }, import: async function(file){ return await fs.readFile("./export/" + file, "utf8"); } }; ================================================ FILE: example/nodejs-commonjs/document-worker-export-import/config.primaryTitle.js ================================================ const { Encoder, Charset } = require("flexsearch"); const EnglishPreset = require("flexsearch/lang/en"); const fs = require("fs").promises; (async function(){ await fs.mkdir("./export/").catch(e => {}); }()); module.exports = { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ), export: async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }, import: async function(file){ return await fs.readFile("./export/" + file, "utf8"); } }; ================================================ FILE: example/nodejs-commonjs/document-worker-export-import/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-commonjs/document-worker-export-import/index.js ================================================ const { Document } = require("flexsearch"); const { promises: fs } = require("fs"); // loading test data const data = require(__dirname + "/data.json"); // you will need to keep the index configuration // they will not export, also every change to the // configuration requires a full re-index const config = { worker: true, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", config: __dirname + "/config.primaryTitle.js" },{ field: "originalTitle", config: __dirname + "/config.originalTitle.js" }], tag: [{ field: "startYear" },{ field: "genres" }] } }; (async function(){ // create the document and await (!) for the instance response let document = await new Document(config); // add test data for(let i = 0; i < data.length; i++){ await document.add(data[i]); } // perform a query let result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log(result); // ----------------------- // EXPORT // ----------------------- // create folders for the export // it should be empty before export await fs.mkdir("./export/").catch(e => {}); await document.export(async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }); // ----------------------- // IMPORT // ----------------------- // create the same type of index you have used by .export() // along with the same configuration document = await new Document(config); // load them in parallel const files = await fs.readdir("./export/"); await Promise.all(files.map(async file => { const data = await fs.readFile("./export/" + file, "utf8"); await document.import(file, data); })); // perform a query result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log("-------------------------------------"); console.log(result); }()); ================================================ FILE: example/nodejs-commonjs/document-worker-export-import/package.json ================================================ { "name": "nodejs-commonjs-document-worker-export-import", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/document-worker-extern-config/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/document-worker-extern-config/config.originalTitle.js ================================================ const { Encoder, Charset } = require("flexsearch"); const EnglishPreset = require("flexsearch/lang/en"); module.exports = { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ) }; ================================================ FILE: example/nodejs-commonjs/document-worker-extern-config/config.primaryTitle.js ================================================ const { Encoder, Charset } = require("flexsearch"); const EnglishPreset = require("flexsearch/lang/en"); module.exports = { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ) }; ================================================ FILE: example/nodejs-commonjs/document-worker-extern-config/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-commonjs/document-worker-extern-config/index.js ================================================ const { Document } = require("flexsearch"); // loading test data const data = require(__dirname + "/data.json"); (async function(){ // create the document and await (!) for the instance response const document = await new Document({ worker: true, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", config: __dirname + "/config.primaryTitle.js" },{ field: "originalTitle", config: __dirname + "/config.originalTitle.js" }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // add test data for(let i = 0; i < data.length; i++){ await document.add(data[i]); } // perform a query const result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log(result); }()); ================================================ FILE: example/nodejs-commonjs/document-worker-extern-config/package.json ================================================ { "name": "nodejs-commonjs-document-worker-extern-config", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-commonjs/language-pack/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-commonjs/language-pack/index.js ================================================ const { Index, Encoder, Charset } = require("flexsearch"); const EnglishPreset = require("flexsearch/lang/en"); const encoder = new Encoder( Charset.LatinSimple, EnglishPreset ); // create a simple index which can store id-content-pairs const index = new Index({ tokenize: "forward", encoder: encoder }); // some test data const data = [ 'She doesn’t get up at six o’clock.', 'It\'s been raining for five hours now.' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // perform query let result = index.search("she does not at clock"); // display results result.forEach(i => { console.log(data[i]); console.log("-------------------------------------"); }); // perform query result = index.search("it is raining"); // display results result.forEach(i => { console.log(data[i]); }); ================================================ FILE: example/nodejs-commonjs/language-pack/package.json ================================================ { "name": "nodejs-commonjs-language-pack", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/basic/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/basic/index.js ================================================ import { Index } from "flexsearch"; // create a simple index which can store id-content-pairs const index = new Index({ // use forward when you want to match partials // e.g. match "flexsearch" when query "flex" tokenize: "forward" }); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // perform query const result = index.search("cute cat"); // display results result.forEach(i => { console.log(data[i]); }); ================================================ FILE: example/nodejs-esm/basic/package.json ================================================ { "name": "nodejs-esm-basic", "type": "module", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/basic-export-import/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/basic-export-import/index.js ================================================ import { Index } from "flexsearch"; // you will need to keep the index configuration // they will not export, also every change to the // configuration requires a full re-index const config = { tokenize: "forward" }; // create a simple index which can store id-content-pairs let index = new Index(config); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // perform query let result = index.search("cute cat"); // display results result.forEach(i => { console.log(data[i]); }); // ----------------------- // EXPORT // ----------------------- import { promises as fs } from "fs"; await fs.mkdir("./export/").catch(e => {}); await index.export(async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }); // ----------------------- // IMPORT // ----------------------- // create the same type of index you have used by .export() // along with the same configuration index = new Index(config); // load them in parallel const files = await fs.readdir("./export/"); await Promise.all(files.map(async file => { const data = await fs.readFile("./export/" + file, "utf8"); index.import(file, data); })) // perform query result = index.search("cute cat"); // display results console.log("-------------------------------------"); result.forEach(i => { console.log(data[i]); }); ================================================ FILE: example/nodejs-esm/basic-export-import/package.json ================================================ { "name": "nodejs-esm-basic-export-import", "type": "module", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/basic-persistent/README.md ================================================ ```bash npm install ``` ```bash npm install sqlite3@5.1.7 ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/basic-persistent/index.js ================================================ import { Index } from "flexsearch"; import Sqlite from "flexsearch/db/sqlite"; // import Postgres from "flexsearch/db/postgres"; // import MongoDB from "flexsearch/db/mongodb"; // import Redis from "flexsearch/db/redis"; // import Clickhouse from "flexsearch/db/clickhouse"; (async function(){ // create DB instance with namespace const db = new Sqlite("my-store"); // create a simple index which can store id-content-pairs const index = new Index({ tokenize: "forward" }); // mount database to the index await index.mount(db); // await index.destroy(); // await index.mount(db); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // transfer changes (bulk) await index.commit(); // perform query const result = await index.search({ query: "cute cat", suggest: true }); console.log(result); }()); ================================================ FILE: example/nodejs-esm/basic-persistent/package.json ================================================ { "name": "nodejs-esm-basic-persistent", "type": "module", "dependencies": { "flexsearch": "^0.8.200", "sqlite3": "^5.1.7" } } ================================================ FILE: example/nodejs-esm/basic-resolver/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/basic-resolver/index.js ================================================ import { Index, Resolver } from "flexsearch"; // create a simple index which can store id-content-pairs const index = new Index({ tokenize: "forward" }); // some test data const data = [ 'cats abcd efgh ijkl dogs pigs rats cute', 'cats abcd efgh ijkl dogs pigs cute', 'cats abcd efgh ijkl dogs cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // perform query const result = new Resolver({ index: index, query: "black" }) .or({ index: index, query: "cute" }) .and([{ index: index, query: "dog" },{ index: index, query: "cat" }]) .not({ index: index, query: "rat" }) .resolve(); // display results result.forEach(i => { console.log(data[i]); }); ================================================ FILE: example/nodejs-esm/basic-resolver/package.json ================================================ { "name": "nodejs-esm-basic-resolver", "type": "module", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/basic-suggestion/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/basic-suggestion/index.js ================================================ import { Index } from "flexsearch"; // create a simple index which can store id-content-pairs const index = new Index({ tokenize: "forward" }); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl dogs cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // perform query const result = index.search({ query: "black dog or cute cat", suggest: true }); // display results result.forEach(i => { console.log(data[i]); }); ================================================ FILE: example/nodejs-esm/basic-suggestion/package.json ================================================ { "name": "nodejs-esm-basic-suggestion", "type": "module", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/basic-worker/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/basic-worker/index.js ================================================ import { Worker as WorkerIndex } from "flexsearch"; (async function(){ // create a simple index which can store id-content-pairs const index = await new WorkerIndex({ tokenize: "forward" }); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add test data data.forEach((item, id) => { index.add(id, item); }); // perform query const result = await index.search({ query: "cute cat", }); // display results result.forEach(i => { console.log(data[i]); }); }()); ================================================ FILE: example/nodejs-esm/basic-worker/package.json ================================================ { "name": "nodejs-esm-basic-worker", "type": "module", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/basic-worker-export-import/config.js ================================================ import { Encoder } from "flexsearch"; import { promises as fs } from "fs"; (async function(){ await fs.mkdir("./export/").catch(e => {}); }()); export default { tokenize: "forward", encoder: new Encoder({ normalize: function(str){ return str.toLowerCase(); } }), export: async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }, import: async function(index){ let files = await fs.readdir("./export/"); files = await Promise.all(files); for(let i = 0; i < files.length; i++){ const data = await fs.readFile("./export/" + files[i], "utf8"); index.import(files[i], data); } } }; ================================================ FILE: example/nodejs-esm/basic-worker-export-import/index.js ================================================ import { Worker as WorkerIndex } from "flexsearch"; const dirname = import.meta.dirname; // you will need to keep the index configuration // they will not export, also every change to the // configuration requires a full re-index const config = { tokenize: "forward", config: dirname + "/config.js" }; (async function(){ // create a simple index which can store id-content-pairs // and await (!) for the worker response let index = await new WorkerIndex(config); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add test data for(let i = 0; i < data.length; i++){ await index.addAsync(i, data[i]); } // perform query let result = await index.search({ query: "cute cat", }); // display results result.forEach(i => { console.log(data[i]); }); // ----------------------- // EXPORT // ----------------------- await index.export(); // ----------------------- // IMPORT // ----------------------- // create the same type of index you have used by .export() // along with the same configuration index = await new WorkerIndex(config); await index.import(); // perform query result = await index.search({ query: "cute cat", }); // display results console.log("-------------------------------------"); result.forEach(i => { console.log(data[i]); }); }()); ================================================ FILE: example/nodejs-esm/basic-worker-export-import/package.json ================================================ { "name": "nodejs-esm-basic-worker-export-import", "type": "module", "dependencies": { "flexsearch": "github:nextapps-de/flexsearch" } } ================================================ FILE: example/nodejs-esm/basic-worker-extern-config/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/basic-worker-extern-config/config.js ================================================ import { Encoder } from "flexsearch"; export default { tokenize: "forward", encoder: new Encoder({ normalize: function(str){ return str.toLowerCase(); } }) }; ================================================ FILE: example/nodejs-esm/basic-worker-extern-config/index.js ================================================ import { Worker as WorkerIndex } from "flexsearch"; const dirname = import.meta.dirname; (async function(){ // create a simple index which can store id-content-pairs // and await (!) for the worker response const index = await new WorkerIndex({ tokenize: "forward", config: dirname + "/config.js" }); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop qrst cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add test data data.forEach((item, id) => { index.add(id, item); }); // perform query const result = await index.search({ query: "cute cat", }); // display results result.forEach(i => { console.log(data[i]); }); }()); ================================================ FILE: example/nodejs-esm/basic-worker-extern-config/package.json ================================================ { "name": "nodejs-esm-basic-worker-extern-config", "type": "module", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/document/README.md ================================================ ```bash npm install ``` ```bash npm install sqlite3@5.1.7 ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/document/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-esm/document/index.js ================================================ import { Document, Charset } from "flexsearch"; import fs from "fs"; const dirname = import.meta.dirname; // loading test data const data = JSON.parse(fs.readFileSync(dirname + "/data.json", "utf8")); // create the document index const document = new Document({ document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward", encoder: Charset.LatinBalance },{ field: "originalTitle", tokenize: "forward", encoder: Charset.LatinBalance }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // add test data for(let i = 0; i < data.length; i++){ document.add(data[i]); } // perform a query const result = document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log(result); ================================================ FILE: example/nodejs-esm/document/package.json ================================================ { "name": "nodejs-esm-document", "type": "module", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/document-export-import/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/document-export-import/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-esm/document-export-import/index.js ================================================ import { Document, Charset } from "flexsearch"; import { promises as fs } from "fs"; const dirname = import.meta.dirname; // loading test data const data = JSON.parse(await fs.readFile(dirname + "/data.json", "utf8")); // you will need to keep the index configuration // they will not export, also every change to the // configuration requires a full re-index const config = { document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward", encoder: Charset.LatinBalance },{ field: "originalTitle", tokenize: "forward", encoder: Charset.LatinBalance }], tag: [{ field: "startYear" },{ field: "genres" }] } }; // create the document index let document = new Document(config); // add test data for(let i = 0; i < data.length; i++){ document.add(data[i]); } // perform a query let result = document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); // output results console.log(result); // EXPORT // ----------------------- await fs.mkdir("./export/").catch(e => {}); // call export await document.export(async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }); // IMPORT // ----------------------- // create the same type of index you have used by .export() // along with the same configuration document = new Document(config); // load them in parallel const files = await fs.readdir("./export/"); await Promise.all(files.map(async file => { const data = await fs.readFile("./export/" + file, "utf8"); // call import document.import(file, data); })); // perform query result = document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); // output results console.log("-------------------------------------"); console.log(result); ================================================ FILE: example/nodejs-esm/document-export-import/package.json ================================================ { "name": "nodejs-esm-document-export-import", "type": "module", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/document-persistent/README.md ================================================ ```bash npm install ``` ```bash npm install sqlite3@5.1.7 ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/document-persistent/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-esm/document-persistent/index.js ================================================ import { Document, Charset } from "flexsearch"; import { promises as fs } from "fs"; const dirname = import.meta.dirname; import Sqlite from "flexsearch/db/sqlite"; // import Postgres from "flexsearch/db/postgres"; // import MongoDB from "flexsearch/db/mongodb"; // import Redis from "flexsearch/db/redis"; // import Clickhouse from "flexsearch/db/clickhouse"; (async function(){ // loading test data const data = JSON.parse(await fs.readFile(dirname + "/data.json")); // create DB instance with namespace const db = new Sqlite("my-store"); // create the document index const document = new Document({ document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward", encoder: Charset.LatinBalance },{ field: "originalTitle", tokenize: "forward", encoder: Charset.LatinBalance }], tag: [{ field: "startYear" },{ field: "genres" }] } }); await document.mount(db); // await document.destroy(); // await document.mount(db); // add test data for(let i = 0; i < data.length; i++){ document.add(data[i]); } // transfer changes in bulk await document.commit(); // perform a query const result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log(result); }()); ================================================ FILE: example/nodejs-esm/document-persistent/package.json ================================================ { "name": "nodejs-esm-document-persistent", "type": "module", "dependencies": { "flexsearch": "^0.8.200", "sqlite3": "^5.1.7" } } ================================================ FILE: example/nodejs-esm/document-resolver/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/document-resolver/index.js ================================================ import { Document, Charset, Resolver } from "flexsearch"; // some test data const data = [{ "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] },{ "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }]; // create the document index const index = new Document({ document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward", encoder: Charset.LatinBalance },{ field: "originalTitle", tokenize: "forward", encoder: Charset.LatinBalance }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // add test data for(let i = 0; i < data.length; i++){ index.add(data[i]); } // perform a complex query + enrich results let result = new Resolver({ index: index, query: "karmen", pluck: "primaryTitle" }).or({ query: "clown", pluck: "primaryTitle" }).and({ query: "not found", field: "originalTitle", suggest: true }).not({ query: "clown", pluck: "primaryTitle" }).resolve({ enrich: true }); // display results console.log(result); ================================================ FILE: example/nodejs-esm/document-resolver/package.json ================================================ { "name": "nodejs-esm-document-resolver", "type": "module", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/document-worker/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/document-worker/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-esm/document-worker/index.js ================================================ import { Document } from "flexsearch"; import { promises as fs } from "fs"; const dirname = import.meta.dirname; (async function(){ // loading test data const data = JSON.parse(await fs.readFile(dirname + "/data.json")); // create the document and await for the instance response const document = await new Document({ worker: true, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward", encoder: "LatinBalance" },{ field: "originalTitle", tokenize: "forward", encoder: "LatinBalance" }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // add test data for(let i = 0; i < data.length; i++){ await document.add(data[i]); } // perform a query const result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log(result); }()); ================================================ FILE: example/nodejs-esm/document-worker/package.json ================================================ { "name": "nodejs-esm-document-worker", "type": "module", "dependencies": { "flexsearch": "github:nextapps-de/flexsearch" } } ================================================ FILE: example/nodejs-esm/document-worker-export-import/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/document-worker-export-import/config.originalTitle.js ================================================ import { Encoder, Charset } from "flexsearch"; import EnglishPreset from "flexsearch/lang/en"; import { promises as fs } from "fs"; (async function(){ await fs.mkdir("./export/").catch(e => {}); }()); export default { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ), export: async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }, import: async function(file){ return await fs.readFile("./export/" + file, "utf8"); } }; ================================================ FILE: example/nodejs-esm/document-worker-export-import/config.primaryTitle.js ================================================ import { Encoder, Charset } from "flexsearch"; import EnglishPreset from "flexsearch/lang/en"; import { promises as fs } from "fs"; (async function(){ await fs.mkdir("./export/").catch(e => {}); }()); export default { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ), export: async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }, import: async function(file){ return await fs.readFile("./export/" + file, "utf8"); } }; ================================================ FILE: example/nodejs-esm/document-worker-export-import/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-esm/document-worker-export-import/index.js ================================================ import { Document } from "flexsearch"; import { promises as fs } from "fs"; const dirname = import.meta.dirname; // you will need to keep the index configuration // they will not export, also every change to the // configuration requires a full re-index const config = { worker: true, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", config: dirname + "/config.primaryTitle.js" },{ field: "originalTitle", config: dirname + "/config.originalTitle.js" }], tag: [{ field: "startYear" },{ field: "genres" }] } }; (async function(){ // loading test data const data = JSON.parse(await fs.readFile(dirname + "/data.json")); // create the document and await (!) for the instance response let document = await new Document(config); // add test data for(let i = 0; i < data.length; i++){ await document.add(data[i]); } // perform a query let result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log(result); // ----------------------- // EXPORT // ----------------------- // create folders for the export // it should be empty before export await fs.mkdir("./export/").catch(e => {}); // call export await document.export(async function(key, data){ await fs.writeFile("./export/" + key, data, "utf8"); }); // ----------------------- // IMPORT // ----------------------- // create the same type of index you have used by .export() // along with the same configuration document = await new Document(config); // load them in parallel const files = await fs.readdir("./export/"); await Promise.all(files.map(async file => { const data = await fs.readFile("./export/" + file, "utf8"); // call import await document.import(file, data); })); // perform a query result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log("-------------------------------------"); console.log(result); }()); ================================================ FILE: example/nodejs-esm/document-worker-export-import/package.json ================================================ { "name": "nodejs-esm-document-worker-export-import", "type": "module", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/document-worker-extern-config/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/document-worker-extern-config/config.originalTitle.js ================================================ import { Encoder, Charset } from "flexsearch"; import EnglishPreset from "flexsearch/lang/en"; export default { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ) }; ================================================ FILE: example/nodejs-esm/document-worker-extern-config/config.primaryTitle.js ================================================ import { Encoder, Charset } from "flexsearch"; import EnglishPreset from "flexsearch/lang/en"; export default { tokenize: "forward", encoder: new Encoder( Charset.LatinBalance, EnglishPreset, { normalize: function(str){ return str.toLowerCase(); }, filter: false, minlength: 3 } ) }; ================================================ FILE: example/nodejs-esm/document-worker-extern-config/data.json ================================================ [ { "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000003", "titleType": "short", "primaryTitle": "Pauvre Pierrot", "originalTitle": "Pauvre Pierrot", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "4", "genres": [ "Animation", "Comedy", "Romance" ] }, { "tconst": "tt0000004", "titleType": "short", "primaryTitle": "Un bon bock", "originalTitle": "Un bon bock", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "12", "genres": [ "Animation", "Short" ] }, { "tconst": "tt0000005", "titleType": "short", "primaryTitle": "Blacksmith Scene", "originalTitle": "Blacksmith Scene", "isAdult": 0, "startYear": "1893", "endYear": "", "runtimeMinutes": "1", "genres": [ "Comedy", "Short" ] }, { "tconst": "tt0000006", "titleType": "short", "primaryTitle": "Chinese Opium Den", "originalTitle": "Chinese Opium Den", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short" ] }, { "tconst": "tt0000007", "titleType": "short", "primaryTitle": "Corbett and Courtney Before the Kinetograph", "originalTitle": "Corbett and Courtney Before the Kinetograph", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Short", "Sport" ] }, { "tconst": "tt0000008", "titleType": "short", "primaryTitle": "Edison Kinetoscopic Record of a Sneeze", "originalTitle": "Edison Kinetoscopic Record of a Sneeze", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] }, { "tconst": "tt0000009", "titleType": "movie", "primaryTitle": "Miss Jerry", "originalTitle": "Miss Jerry", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "45", "genres": [ "Romance" ] }, { "tconst": "tt0000010", "titleType": "short", "primaryTitle": "Leaving the Factory", "originalTitle": "La sortie de l'usine Lumière à Lyon", "isAdult": 0, "startYear": "1895", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] } ] ================================================ FILE: example/nodejs-esm/document-worker-extern-config/index.js ================================================ import { Document } from "flexsearch"; import { promises as fs } from "fs"; const dirname = import.meta.dirname; (async function(){ // loading test data const data = JSON.parse(await fs.readFile(dirname + "/data.json")); // create the document and await (!) for the instance response const document = await new Document({ worker: true, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", config: dirname + "/config.primaryTitle.js" },{ field: "originalTitle", config: dirname + "/config.originalTitle.js" }], tag: [{ field: "startYear" },{ field: "genres" }] } }); //console.log(document) // add test data for(let i = 0; i < data.length; i++){ await document.add(data[i]); } // perform a query const result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true, merge: true }); console.log(result); }()); ================================================ FILE: example/nodejs-esm/document-worker-extern-config/package.json ================================================ { "name": "nodejs-esm-document-worker-extern-config", "type": "module", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: example/nodejs-esm/language-pack/README.md ================================================ ```bash npm install ``` ```bash node index.js ``` ================================================ FILE: example/nodejs-esm/language-pack/index.js ================================================ import { Index, Encoder, Charset } from "flexsearch"; import EnglishPreset from "flexsearch/lang/en"; const encoder = new Encoder( Charset.LatinSimple, EnglishPreset ); // create a simple index which can store id-content-pairs const index = new Index({ tokenize: "forward", encoder: encoder }); // some test data const data = [ 'She doesn’t get up at six o’clock.', 'It\'s been raining for five hours now.' ]; // add data to the index data.forEach((item, id) => { index.add(id, item); }); // perform query let result = index.search("she does not at clock"); // display results result.forEach(i => { console.log(data[i]); console.log("-------------------------------------"); }); // perform query result = index.search("it is raining"); // display results result.forEach(i => { console.log(data[i]); }); ================================================ FILE: example/nodejs-esm/language-pack/package.json ================================================ { "name": "nodejs-esm-language-pack", "dependencies": { "flexsearch": "^0.8.200" } } ================================================ FILE: index.d.ts ================================================ declare module "flexsearch" { // Type Abbreviations: // ------------------------- // D: DocumentData // W: Worker // S: StorageInterface (Persistent) // H: Highlight // P: Pluck // R: Resolve // E: Enrich // M: Merge // A: Async /************************************/ /* Utils */ /************************************/ export type Id = number | string; export type Limit = number; export type ExportHandler = (key: string, data: string) => void; export type ExportHandlerAsync = (key: string, data: string) => Promise; export type AsyncCallback = (result?: T) => void; /************************************/ /* Common Options */ /************************************/ /** * **Document:** * * Presets: https://github.com/nextapps-de/flexsearch#presets */ export type Preset = | "memory" | "performance" | "match" | "score" | "default"; /** * Tokenizer: https://github.com/nextapps-de/flexsearch#tokenizer-prefix-search \ * Custom Tokenizer: https://github.com/nextapps-de/flexsearch#add-custom-tokenizer */ export type Tokenizer = | "strict" | "exact" | "default" | "tolerant" | "forward" | "reverse" | "bidirectional" | "full"; /** * Encoders: https://github.com/nextapps-de/flexsearch#encoders */ export type Encoders = | "Exact" | "Default" | "Normalize" /** @deprecated */ | "LatinExact" /** @deprecated */ | "LatinDefault" /** @deprecated */ | "LatinSimple" | "LatinBalance" | "LatinAdvanced" | "LatinExtra" | "LatinSoundex" | "CJK" | ((content: string) => string[]); /** * **Document:** * * Context Options: https://github.com/nextapps-de/flexsearch#context-options * * Contextual search: https://github.com/nextapps-de/flexsearch#contextual */ export type ContextOptions = { resolution: number; depth: number; bidirectional: boolean; }; /** * **Document:** * * Search options: https://github.com/nextapps-de/flexsearch#search-options */ export type SearchOptions = { query?: string; limit?: number; offset?: number; suggest?: boolean; resolution?: number; context?: boolean; cache?: R extends true ? boolean : false; resolve?: R; }; export type SerializedFunctionString = string; /** * **Document:** * * Language Options: https://github.com/nextapps-de/flexsearch#language-options * * Language: https://github.com/nextapps-de/flexsearch#languages */ export type EncoderOptions = { rtl?: boolean; dedupe?: boolean; include?: EncoderSplitOptions; exclude?: EncoderSplitOptions; split?: string | RegExp | "" | false; numeric?: boolean; normalize?: boolean | ((str: string) => string); prepare?: (str: string) => string; finalize?: (terms: string[]) => string[]; filter?: Set | ((term: string) => boolean); matcher?: Map; mapper?: Map; stemmer?: Map; replacer?: [ string | RegExp, string | "" ]; minlength?: number; maxlength?: number; cache?: boolean | number; } export type EncoderSplitOptions = { letter?: boolean; number?: boolean; symbol?: boolean; punctuation?: boolean; control?: boolean; char?: string | string[]; }; export const Charset: { Exact: EncoderOptions, Default: EncoderOptions, Normalize: EncoderOptions, LatinBalance: EncoderOptions, LatinAdvanced: EncoderOptions, LatinExtra: EncoderOptions, LatinSoundex: EncoderOptions, CJK: EncoderOptions, /** @deprecated */ LatinSimple: EncoderOptions, /** @deprecated */ LatinExact: EncoderOptions, /** @deprecated */ LatinDefault: EncoderOptions }; /** * These options will determine how the contents will be indexed. * * **Document:** * * Index options: https://github.com/nextapps-de/flexsearch#index-options * * Tokenizer: https://github.com/nextapps-de/flexsearch#tokenizer-partial-match * * Encoder: https://github.com/nextapps-de/flexsearch#charset-collection * * Context: https://github.com/nextapps-de/flexsearch#context-search * * Resolver: https://github.com/nextapps-de/flexsearch/doc/resolver.md * * Keystore: https://github.com/nextapps-de/flexsearch/doc/keystore.md * * Persistent: https://github.com/nextapps-de/flexsearch/doc/persistent.md * * Right-To-Left: https://github.com/nextapps-de/flexsearch/doc/encoder.md#right-to-left-support * * Language: https://github.com/nextapps-de/flexsearch/doc/encoder.md#built-in-language-packs */ export type IndexOptions< S extends StorageInterface | boolean = false, R extends boolean = true > = { preset?: Preset; tokenize?: Tokenizer; cache?: boolean | number; resolution?: number; context?: ContextOptions | boolean; keystore?: number; fastupdate?: boolean; priority?: number; score?: ( content: string[], term: string, term_index: number, partial: string, partial_index: number, ) => number; resolve?: R; // Persistent-specific options db?: S; commit?: boolean; // Language-specific Options and Encoding encoder?: Encoders | EncoderOptions | Encoder; encode?: (text: string) => string[], rtl?: boolean; }; /************************************/ /* Index Search */ /************************************/ export type DefaultSearchResults = Id[]; export type IntermediateSearchResults = Array; export type SearchResults< W extends WorkerType | boolean = false, S extends StorageInterface | boolean = false, R extends boolean = true, A extends boolean = false > = R extends false ? Resolver : W extends false ? S extends false ? A extends false ? DefaultSearchResults : Promise : Promise : Promise /** * Basic usage and variants: https://github.com/nextapps-de/flexsearch#basic-usage-and-variants \ * API overview: https://github.com/nextapps-de/flexsearch#api-overview \ * Usage: https://github.com/nextapps-de/flexsearch#usage */ export class Index< W extends WorkerType = false, S extends StorageInterface | boolean = false, r extends boolean = true > { constructor(options?: Preset | IndexOptions); db: Promise; add(id: Id, content: string): W extends false ? this : Promise; append(id: Id, content: string): W extends false ? this : Promise; update(id: Id, content: string): W extends false ? this : Promise; remove(id: Id): W extends false ? this : Promise; search(query: string): SearchResults; /** @deprecated Pass "limit" within options */ search(query: string, limit: Limit, options?: SearchOptions): SearchResults; search(query: string, options?: SearchOptions): SearchResults; search(options: SearchOptions): SearchResults; searchCache(query: string): SearchResults; /** @deprecated Pass "limit" within options */ searchCache(query: string, limit: Limit): SearchResults; /** @deprecated Pass "limit" within options */ searchCache(query: string, limit: Limit, options?: SearchOptions): SearchResults; searchCache(query: string, options?: SearchOptions): SearchResults; searchCache(options: SearchOptions): SearchResults; // https://github.com/nextapps-de/flexsearch#check-existence-of-already-indexed-ids contain(id: Id): S extends false ? boolean : Promise; clear(): W extends false ? S extends false ? this : Promise : Promise; cleanup(): void; // Export and Import export(handler: ExportHandler): void; export(handler: ExportHandlerAsync): Promise; import(key: string, data: string): void; serialize(with_function_wrapper?: boolean): SerializedFunctionString; // Persistent Index mount(db: StorageInterface): Promise; commit(): Promise; destroy(): Promise; // Async Methods addAsync( id: Id, content: string, callback?: AsyncCallback, ): Promise; appendAsync( id: Id, content: string, callback?: AsyncCallback, ): Promise; updateAsync( id: Id, content: string, callback?: AsyncCallback, ): Promise; removeAsync( id: Id, callback?: AsyncCallback ): Promise; searchAsync( query: string, callback?: AsyncCallback ): SearchResults; searchCacheAsync( query: string, callback?: AsyncCallback ): SearchResults; /** @deprecated Pass "limit" within options */ searchAsync( query: string, limit: Limit, callback?: AsyncCallback ): SearchResults; /** @deprecated Pass "limit" within options */ searchCacheAsync( query: string, limit: Limit, callback?: AsyncCallback ): SearchResults; /** @deprecated Pass "limit" within options */ searchAsync( query: string, limit: Limit, options?: SearchOptions, callback?: AsyncCallback ): SearchResults; /** @deprecated Pass "limit" within options */ searchCacheAsync( query: string, limit: Limit, options?: SearchOptions, callback?: AsyncCallback ): SearchResults; searchAsync( query: string, options?: SearchOptions, callback?: AsyncCallback ): SearchResults; searchCacheAsync( query: string, options?: SearchOptions, callback?: AsyncCallback ): SearchResults; searchAsync( options: SearchOptions, callback?: AsyncCallback ): SearchResults; searchCacheAsync( options: SearchOptions, callback?: AsyncCallback ): SearchResults; } /************************************/ /* Worker Index */ /************************************/ export type WorkerURL = string; export type WorkerPath = string; export type WorkerConfigURL = string; export type WorkerConfigPath = string; export type WorkerType = boolean | WorkerURL | WorkerPath; export type WorkerIndexOptions = IndexOptions & IndexWorkerConfig & { //config?: WorkerConfigURL | WorkerConfigPath, export?: () => Promise; import?: () => Promise; // no persistent supported db?: null; commit?: null; }; export interface IndexWorkerConfig { config?: W extends true ? WorkerConfigURL | WorkerConfigPath : undefined; } export class Worker extends Promise> { constructor(options?: Preset | WorkerIndexOptions); export(): Promise; import(): Promise; } /************************************/ /* Document Search */ /************************************/ export type CustomFN = (doc: D) => string | boolean; /** * The template to be applied on matches (e.g. "\$1\"), where \$1 is a placeholder for the matched partial */ export type TemplateResultHighlighting = string; export type TagName = string; export type FieldName = D extends object ? { [K in keyof D]: K extends string ? D[K] extends Array ? `${ K }` | `${ K }[]:${ FieldName & string }` : K | `${ K }:${ FieldName & string }` : never }[keyof D] : never; export type DefaultFieldOptions< D = DocumentData, S extends StorageInterface | boolean = false > = IndexOptions & { field: FieldName; filter?: (doc: D) => boolean; db?: S; }; export type DefaultCustomFieldOptions< D = DocumentData, S extends StorageInterface | boolean = false > = IndexOptions & { custom: CustomFN; field: FieldName; filter?: (doc: D) => boolean; db?: S; }; export type TagOptions< D = DocumentData, S extends StorageInterface | boolean = false > = | DefaultFieldOptions | DefaultCustomFieldOptions; export type StoreOptions< D = DocumentData, S extends StorageInterface | boolean = false > = | DefaultFieldOptions | DefaultCustomFieldOptions; export type FieldOptions< D extends DocumentData, W extends WorkerType = false, S extends StorageInterface | boolean = false > = | (DefaultFieldOptions & IndexWorkerConfig) | (DefaultCustomFieldOptions & IndexWorkerConfig) /** * # Document Search Result * * To make your result return the full document: * * set `store` to `true` while creating the document; * * set `enrich` to `true` while searching. * * If neither of these conditions is met, then the returned result will be a `ISimpleDocumentSearchResult`. */ /* * **Document:** * * Document options: https://github.com/nextapps-de/flexsearch#document-options */ export type DocumentOptions< D extends DocumentData = DocumentData, W extends WorkerType = false, S extends StorageInterface | boolean = false > = IndexOptions & DocumentDescriptor & { doc?: DocumentDescriptor; document?: DocumentDescriptor; worker?: W; }; /** * **Document:** * * The document descriptor: https://github.com/nextapps-de/flexsearch#the-document-descriptor */ export type DocumentDescriptor< D extends DocumentData = DocumentData, W extends WorkerType = false, S extends StorageInterface | boolean = false > = { id?: string | "id"; field?: FieldName | FieldName[] | FieldOptions | Array>; index?: FieldName | FieldName[] | FieldOptions | Array>; tag?: FieldName | FieldName[] | TagOptions | Array>; store?: FieldName | FieldName[] | StoreOptions | Array> | boolean; }; export type DefaultDocumentSearchResults = Array<{ field?: FieldName; tag?: FieldName; result: DefaultSearchResults; }>; export type EnrichedResults = Array<{ id: Id; doc: D | null; highlight?: string; }>; export type EnrichedDocumentSearchResults = Array<{ field?: FieldName; tag?: FieldName; result: EnrichedResults; }>; export type MergedDocumentSearchResults = Array<{ id: Id; doc?: D | null; field?: FieldName[]; tag?: FieldName[]; //highlight?: {[field: FieldName]: string}; highlight?: Record, string>; }>; export type PluckOptions< D extends DocumentData = DocumentData, H extends HighlightOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true > = FieldName | DocumentSearchOptions; export type DocumentSearchResults< D extends DocumentData = DocumentData, H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false > = P extends false ? M extends true ? MergedDocumentSearchResults : E extends true ? EnrichedDocumentSearchResults : H extends false ? DefaultDocumentSearchResults : EnrichedDocumentSearchResults : E extends true ? EnrichedResults : DefaultSearchResults; export type DocumentSearchResultsWrapper< D extends DocumentData = DocumentData, W extends WorkerType = false, S extends StorageInterface | boolean = false, H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false, A extends boolean = false > = R extends true ? W extends false ? S extends false ? A extends false ? DocumentSearchResults : Promise> : Promise> : Promise> : Resolver; /** * **Document:** * * Document search options: https://github.com/nextapps-de/flexsearch#document-search-options */ export type DocumentSearchOptions< D extends DocumentData = DocumentData, H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false, > = SearchOptions & { tag?: {[field: FieldName]: TagName} | Array<{[field: FieldName]: TagName}>; field?: Array> | DocumentSearchOptions | FieldName[] | FieldName; index?: Array> | DocumentSearchOptions | FieldName[] | FieldName; pluck?: P; highlight?: H; enrich?: E; merge?: M; }; export type DocumentValue = | string | number | boolean | null | DocumentData; export type DocumentData = { [key: string]: DocumentValue | DocumentValue[]; }; /** * Basic usage and variants: https://github.com/nextapps-de/flexsearch#basic-usage-and-variants \ * API overview: https://github.com/nextapps-de/flexsearch#api-overview \ * Document store: https://github.com/nextapps-de/flexsearch#document-store */ export class Document< D extends DocumentData = DocumentData, W extends WorkerType = false, S extends StorageInterface | boolean = false > { constructor(options: DocumentOptions); db: Promise; add(id: Id, document: D): W extends false ? this : Promise; add(document: D): W extends false ? this : Promise; append(id: Id, document: D): W extends false ? this : Promise; append(document: D): W extends false ? this : Promise; update(id: Id, document: D): W extends false ? this : Promise; update(document: D): W extends false ? this : Promise; remove(id: Id): W extends false ? this : Promise; remove(document: D): W extends false ? this : Promise; // https://github.com/nextapps-de/flexsearch#field-search search(query: string): DocumentSearchResultsWrapper; searchCache(query: string): DocumentSearchResultsWrapper; search< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, options: DocumentSearchOptions, ): DocumentSearchResultsWrapper; searchCache< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, options: DocumentSearchOptions, ): DocumentSearchResultsWrapper; /** @deprecated Pass "limit" within options */ search< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, limit: Limit, options?: DocumentSearchOptions, ): DocumentSearchResultsWrapper; /** @deprecated Pass "limit" within options */ searchCache< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, limit: Limit, options?: DocumentSearchOptions, ): DocumentSearchResultsWrapper; search< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( options: DocumentSearchOptions, ): DocumentSearchResultsWrapper; searchCache< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( options: DocumentSearchOptions, ): DocumentSearchResultsWrapper; // https://github.com/nextapps-de/flexsearch#check-existence-of-already-indexed-ids contain(id: Id): S extends false ? boolean : Promise; clear(): W extends false ? S extends false ? this : Promise : Promise; cleanup(): void; get(id: Id): S extends false ? D | null : Promise; set(id: Id, document: D): this; set(document: D): this; // Export and Import export(handler: ExportHandler): void; export(handler: ExportHandlerAsync): Promise; import(key: string, data: string): void; // Persistent Index mount>(db: S): Promise; commit(): Promise; destroy(): Promise; // Async Methods addAsync( id: Id, document: D, callback?: AsyncCallback, ): Promise; addAsync( document: D, callback?: AsyncCallback, ): Promise; appendAsync( id: Id, document: D, callback?: AsyncCallback, ): Promise; appendAsync( document: D, callback?: AsyncCallback, ): Promise; updateAsync( id: Id, document: D, callback?: AsyncCallback, ): Promise; updateAsync( document: D, callback?: AsyncCallback, ): Promise; removeAsync( id: Id, callback?: AsyncCallback, ): Promise; removeAsync( document: D, callback?: AsyncCallback, ): Promise; searchAsync< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, callback?: AsyncCallback>, ): DocumentSearchResultsWrapper searchCacheAsync< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, callback?: AsyncCallback>, ): DocumentSearchResultsWrapper /** @deprecated Pass "limit" within options */ searchAsync< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, limit: Limit, callback?: AsyncCallback>, ): DocumentSearchResultsWrapper /** @deprecated Pass "limit" within options */ searchCacheAsync< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, limit: Limit, callback?: AsyncCallback>, ): DocumentSearchResultsWrapper /** @deprecated Pass "limit" within options */ searchAsync< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, limit: Limit, options?: DocumentSearchOptions, callback?: AsyncCallback>, ): DocumentSearchResultsWrapper; /** @deprecated Pass "limit" within options */ searchCacheAsync< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, limit: Limit, options?: DocumentSearchOptions, callback?: AsyncCallback>, ): DocumentSearchResultsWrapper; searchAsync< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, options?: DocumentSearchOptions, callback?: AsyncCallback>, ): DocumentSearchResultsWrapper searchCacheAsync< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( query: string, options?: DocumentSearchOptions, callback?: AsyncCallback>, ): DocumentSearchResultsWrapper searchAsync< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( options: DocumentSearchOptions, callback?: AsyncCallback>, ): DocumentSearchResultsWrapper; searchCacheAsync< H extends HighlightOptions | boolean = false, P extends PluckOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, M extends boolean = false >( options: DocumentSearchOptions, callback?: AsyncCallback>, ): DocumentSearchResultsWrapper; } export type IdType = "text" | "char" | "varchar" | "string" | "number" | "numeric" | "integer" | "smallint" | "tinyint" | "mediumint" | "int" | "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" | "int64" | "uint64" | "bigint"; export type PersistentOptions = { name?: string; type?: IdType; db?: any; }; export type DefaultResolve< D extends DocumentData = undefined, H extends HighlightOptions | boolean = false, R extends boolean = true, E extends boolean = H extends false ? false : true, > = { limit?: number; offset?: number; resolve?: R; /** only usable when "resolve" was not set to false */ enrich?: D extends undefined ? false : R extends true ? E : false; highlight?: D extends undefined ? false : H; // R extends true ? H : false; }; export type ResolverOptions< D extends DocumentData = undefined, W extends WorkerType = false, S extends StorageInterface | boolean = false, H extends HighlightOptions | boolean = false, R extends boolean = false, E extends boolean = H extends false ? false : true, A extends boolean = false > = Resolver | (DefaultResolve & { query?: string; index?: Index | Document | Worker; pluck?: FieldName; field?: FieldName; tag?: {[field: FieldName]: TagName} | Array<{[field: FieldName]: TagName}>; and?: ResolverOptions | Array>; or?: ResolverOptions | Array>; xor?: ResolverOptions | Array>; not?: ResolverOptions | Array>; boost?: number; suggest?: boolean; cache?: boolean; async?: A; queue?: A; }); export type HighlightBoundaryOptions = { before?: number; after?: number; total?: number; }; export type HighlightEllipsisOptions = { template: TemplateResultHighlighting; pattern?: string | boolean; }; export type HighlightOptions = TemplateResultHighlighting | { template: TemplateResultHighlighting; boundary?: HighlightBoundaryOptions | number; ellipsis?: HighlightEllipsisOptions | string | boolean; clip?: boolean; merge?: boolean; }; export class Encoder { constructor(options?: EncoderOptions); assign(options: EncoderOptions): this; encode(content: string): string[]; addMapper(char_match: string, char_replace: string): this; addMatcher(match: string, replace: string): this; addStemmer(match: string, replace: string): this; addFilter(term: string): this; addReplacer(match: string | RegExp, replace: string): this; } export class Resolver< D extends DocumentData = undefined, W extends WorkerType = false, S extends StorageInterface | boolean = false, H extends HighlightOptions | boolean = false, R extends boolean = false, E extends boolean = H extends false ? false : true, A extends boolean = false > { result: IntermediateSearchResults; constructor(options?: ResolverOptions | IntermediateSearchResults); and(...args: ResolverOptions[]): DocumentSearchResultsWrapper; or(...args: ResolverOptions[]): DocumentSearchResultsWrapper; xor(...args: ResolverOptions[]): DocumentSearchResultsWrapper; not(...args: ResolverOptions[]): DocumentSearchResultsWrapper; limit(limit: number): Resolver; offset(offset: number): Resolver; boost(boost: number): Resolver; resolve< h extends HighlightOptions | boolean = H, e extends boolean = h extends HighlightOptions ? true : E, a extends boolean = A >(options?: DefaultResolve): DocumentSearchResultsWrapper; } export class StorageInterface { db: any; constructor(name: string, config: PersistentOptions); constructor(config: string | PersistentOptions); mount(index: Index | Document): Promise; open(): Promise; close(): Promise; destroy(): Promise; clear(): Promise; } export class IndexedDB extends StorageInterface { /*db: IDBDatabase;*/ } const FlexSearch: { Index: typeof Index, Document: typeof Document, Worker: typeof Worker, Encoder: typeof Encoder, Charset: typeof Charset, Resolver: typeof Resolver, IndexedDB: typeof IndexedDB }; export default FlexSearch; } // ----------------------------------- declare module "flexsearch/db/*" { import { StorageInterface } from "flexsearch"; export default StorageInterface; } // declare module "flexsearch/db/indexeddb" { // import { StorageInterface } from "flexsearch"; // export default class IndexedDB extends StorageInterface{ // db: IDBDatabase; // } // } // ----------------------------------- declare module "flexsearch/lang/*" { import { EncoderOptions } from "flexsearch"; const Options: EncoderOptions; export default Options; } // https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html // https://github.com/futurGH/ts-to-jsdoc // https://sethmac.com/typescript-to-jsdoc/ // https://github.com/DefinitelyTyped/DefinitelyTyped ================================================ FILE: package.json ================================================ { "public": true, "preferGlobal": false, "name": "flexsearch", "version": "0.8.214", "description": "Next-Generation full-text search library for Browser and Node.js", "homepage": "https://github.com/nextapps-de/flexsearch/", "author": "Thomas Wilkerling", "copyright": "Nextapps GmbH", "license": "Apache-2.0", "readme": "README.md", "keywords": [ "fulltext search", "elastic search", "fastest search", "contextual search", "document search", "fuzzy search", "fuzzy match", "search engine" ], "repository": { "type": "git", "url": "https://github.com/nextapps-de/flexsearch.git" }, "bugs": { "url": "https://github.com/nextapps-de/flexsearch/issues", "email": "info@nextapps.de" }, "main": "dist/flexsearch.bundle.min.js", "module": "dist/flexsearch.bundle.module.min.mjs", "types": "./index.d.ts", "exports": { ".": { "types": "./index.d.ts", "import": "./dist/flexsearch.bundle.module.min.mjs", "require": "./dist/flexsearch.bundle.min.js" }, "./lang/*": { "import": "./dist/module/lang/*.js", "require": "./dist/lang/*.min.js" }, "./db/*": { "import": "./dist/module/db/*/index.js", "require": "./dist/db/*/index.cjs" }, "./debug": { "import": "./dist/flexsearch.bundle.module.debug.mjs", "require": "./dist/flexsearch.bundle.debug.js" } }, "browser": { "flexsearch": "./dist/flexsearch.bundle.module.min.mjs", "flexsearch/debug": "./dist/flexsearch.bundle.module.debug.mjs", "dist/flexsearch.bundle.min.js": "./dist/flexsearch.bundle.min.js", "dist/flexsearch.bundle.module.min.js": "./dist/flexsearch.bundle.module.min.mjs", "dist/flexsearch.bundle.module.min.mjs": "./dist/flexsearch.bundle.module.min.mjs", "worker_threads": false, "path": false, "clickhouse": false, "mongodb": false, "pg-promise": false, "redis": false, "sqlite3": false }, "scripts": { "build": "npm run build:bundle && npm run build:bundle:debug && exit 0", "build:bundle": "node task/build RELEASE=bundle DEBUG=false PROFILER=false SUPPORT_WORKER=true SUPPORT_ENCODER=true SUPPORT_CHARSET=true SUPPORT_CACHE=true SUPPORT_ASYNC=true SUPPORT_STORE=true SUPPORT_TAGS=true SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=true SUPPORT_DOCUMENT=true POLYFILL=false SUPPORT_PERSISTENT=true SUPPORT_RESOLVER=true SUPPORT_HIGHLIGHTING=true SUPPORT_KEYSTORE=true SUPPORT_COMPRESSION=false", "build:bundle:debug": "node task/build RELEASE=bundle DEBUG=true PROFILER=false SUPPORT_WORKER=true SUPPORT_ENCODER=true SUPPORT_CHARSET=true SUPPORT_CACHE=true SUPPORT_ASYNC=true SUPPORT_STORE=true SUPPORT_TAGS=true SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=true SUPPORT_DOCUMENT=true POLYFILL=false SUPPORT_PERSISTENT=true SUPPORT_RESOLVER=true SUPPORT_HIGHLIGHTING=true SUPPORT_KEYSTORE=true SUPPORT_COMPRESSION=false FORMATTING=PRETTY_PRINT", "build:compact": "node task/build RELEASE=compact DEBUG=false PROFILER=false SUPPORT_WORKER=false SUPPORT_ENCODER=true SUPPORT_CHARSET=true SUPPORT_CACHE=true SUPPORT_ASYNC=true SUPPORT_STORE=true SUPPORT_TAGS=true SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=true SUPPORT_DOCUMENT=true POLYFILL=false SUPPORT_PERSISTENT=false SUPPORT_RESOLVER=false SUPPORT_HIGHLIGHTING=true SUPPORT_KEYSTORE=false SUPPORT_COMPRESSION=false", "build:compact:debug": "node task/build RELEASE=compact DEBUG=true PROFILER=false SUPPORT_WORKER=false SUPPORT_ENCODER=true SUPPORT_CHARSET=true SUPPORT_CACHE=true SUPPORT_ASYNC=true SUPPORT_STORE=true SUPPORT_TAGS=true SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=true SUPPORT_DOCUMENT=true POLYFILL=false SUPPORT_PERSISTENT=false SUPPORT_RESOLVER=false SUPPORT_HIGHLIGHTING=true SUPPORT_KEYSTORE=false SUPPORT_COMPRESSION=false FORMATTING=PRETTY_PRINT", "build:light": "node task/build RELEASE=light DEBUG=false PROFILER=false SUPPORT_WORKER=false SUPPORT_ENCODER=true SUPPORT_CHARSET=false SUPPORT_CACHE=false SUPPORT_ASYNC=false SUPPORT_STORE=false SUPPORT_TAGS=false SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=false SUPPORT_DOCUMENT=false POLYFILL=false SUPPORT_PERSISTENT=false SUPPORT_RESOLVER=false SUPPORT_HIGHLIGHTING=false SUPPORT_KEYSTORE=false SUPPORT_COMPRESSION=false", "build:light:debug": "node task/build RELEASE=light DEBUG=true PROFILER=false SUPPORT_WORKER=false SUPPORT_ENCODER=true SUPPORT_CHARSET=false SUPPORT_CACHE=false SUPPORT_ASYNC=false SUPPORT_STORE=false SUPPORT_TAGS=false SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=false SUPPORT_DOCUMENT=false POLYFILL=false SUPPORT_PERSISTENT=false SUPPORT_RESOLVER=false SUPPORT_HIGHLIGHTING=false SUPPORT_KEYSTORE=false SUPPORT_COMPRESSION=false FORMATTING=PRETTY_PRINT", "build:es5": "node task/build RELEASE=es5 DEBUG=false PROFILER=false SUPPORT_WORKER=true SUPPORT_ENCODER=true SUPPORT_CHARSET=true SUPPORT_CACHE=true SUPPORT_ASYNC=true SUPPORT_STORE=true SUPPORT_TAGS=true SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=true SUPPORT_DOCUMENT=true POLYFILL=true SUPPORT_PERSISTENT=true SUPPORT_RESOLVER=true SUPPORT_HIGHLIGHTING=true SUPPORT_KEYSTORE=true SUPPORT_COMPRESSION=false LANGUAGE_OUT=ECMASCRIPT5_STRICT", "build:es5:debug": "node task/build RELEASE=es5 DEBUG=true PROFILER=false SUPPORT_WORKER=true SUPPORT_ENCODER=true SUPPORT_CHARSET=true SUPPORT_CACHE=true SUPPORT_ASYNC=true SUPPORT_STORE=true SUPPORT_TAGS=true SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=true SUPPORT_DOCUMENT=true POLYFILL=true SUPPORT_PERSISTENT=true SUPPORT_RESOLVER=true SUPPORT_HIGHLIGHTING=true SUPPORT_KEYSTORE=true SUPPORT_COMPRESSION=false FORMATTING=PRETTY_PRINT LANGUAGE_OUT=ECMASCRIPT5_STRICT", "build:lang": "node task/build RELEASE=lang", "build:db": "npx rollup tmp/db/indexeddb/index.js --file dist/db/indexeddb/index.cjs --format cjs && npx rollup tmp/db/postgres/index.js --file dist/db/postgres/index.cjs --format cjs && npx rollup tmp/db/sqlite/index.js --file dist/db/sqlite/index.cjs --format cjs && npx rollup tmp/db/mongodb/index.js --file dist/db/mongodb/index.cjs --format cjs && npx rollup tmp/db/redis/index.js --file dist/db/redis/index.cjs --format cjs && npx rollup tmp/db/clickhouse/index.js --file dist/db/clickhouse/index.cjs --format cjs", "build:module": "node task/babel && exit 0", "build:module:debug": "node task/babel DEBUG=true && exit 0", "build:module:min": "node task/babel RELEASE=min && exit 0", "build:module:bundle": "node task/build RELEASE=bundle.module DEBUG=false PROFILER=false SUPPORT_WORKER=true SUPPORT_ENCODER=true SUPPORT_CHARSET=true SUPPORT_CACHE=true SUPPORT_ASYNC=true SUPPORT_STORE=true SUPPORT_TAGS=true SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=true SUPPORT_DOCUMENT=true POLYFILL=false SUPPORT_PERSISTENT=true SUPPORT_RESOLVER=true SUPPORT_HIGHLIGHTING=true SUPPORT_KEYSTORE=true SUPPORT_COMPRESSION=false", "build:module:bundle:debug": "node task/build RELEASE=bundle.module DEBUG=true PROFILER=false SUPPORT_WORKER=true SUPPORT_ENCODER=true SUPPORT_CHARSET=true SUPPORT_CACHE=true SUPPORT_ASYNC=true SUPPORT_STORE=true SUPPORT_TAGS=true SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=true SUPPORT_DOCUMENT=true POLYFILL=false SUPPORT_PERSISTENT=true SUPPORT_RESOLVER=true SUPPORT_HIGHLIGHTING=true SUPPORT_KEYSTORE=true SUPPORT_COMPRESSION=false FORMATTING=PRETTY_PRINT", "build:module:compact": "node task/build RELEASE=compact.module DEBUG=false PROFILER=false SUPPORT_WORKER=false SUPPORT_ENCODER=true SUPPORT_CHARSET=true SUPPORT_CACHE=true SUPPORT_ASYNC=true SUPPORT_STORE=true SUPPORT_TAGS=true SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=true SUPPORT_DOCUMENT=true POLYFILL=false SUPPORT_PERSISTENT=false SUPPORT_RESOLVER=false SUPPORT_HIGHLIGHTING=true SUPPORT_KEYSTORE=false SUPPORT_COMPRESSION=false", "build:module:compact:debug": "node task/build RELEASE=compact.module DEBUG=true PROFILER=false SUPPORT_WORKER=false SUPPORT_ENCODER=true SUPPORT_CHARSET=true SUPPORT_CACHE=true SUPPORT_ASYNC=true SUPPORT_STORE=true SUPPORT_TAGS=true SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=true SUPPORT_DOCUMENT=true POLYFILL=false SUPPORT_PERSISTENT=false SUPPORT_RESOLVER=false SUPPORT_HIGHLIGHTING=true SUPPORT_KEYSTORE=false SUPPORT_COMPRESSION=false FORMATTING=PRETTY_PRINT", "build:module:light": "node task/build RELEASE=light.module DEBUG=false PROFILER=false SUPPORT_WORKER=false SUPPORT_ENCODER=true SUPPORT_CHARSET=false SUPPORT_CACHE=false SUPPORT_ASYNC=false SUPPORT_STORE=false SUPPORT_TAGS=false SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=false SUPPORT_DOCUMENT=false POLYFILL=false SUPPORT_PERSISTENT=false SUPPORT_RESOLVER=false SUPPORT_HIGHLIGHTING=false SUPPORT_KEYSTORE=false SUPPORT_COMPRESSION=false", "build:module:light:debug": "node task/build RELEASE=light.module DEBUG=true PROFILER=false SUPPORT_WORKER=false SUPPORT_ENCODER=true SUPPORT_CHARSET=false SUPPORT_CACHE=false SUPPORT_ASYNC=false SUPPORT_STORE=false SUPPORT_TAGS=false SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=false SUPPORT_DOCUMENT=false POLYFILL=false SUPPORT_PERSISTENT=false SUPPORT_RESOLVER=false SUPPORT_HIGHLIGHTING=false SUPPORT_KEYSTORE=false SUPPORT_COMPRESSION=false FORMATTING=PRETTY_PRINT", "build:all": "npm run build:bundle && npm run build:bundle:debug && npm run build:light && npm run build:light:debug && npm run build:compact && npm run build:compact:debug && npm run build:module:bundle && npm run build:module:bundle:debug && npm run build:module:light && npm run build:module:light:debug && npm run build:module:compact && npm run build:module:compact:debug && npm run build:es5 && npm run build:es5:debug && npm run build:lang && npm run build:module && npm run build:module:debug && npm run build:module:min && npm run build:db", "build:custom": "node task/build RELEASE=custom", "test:all": "cd test/ && npm install && npm run test:all && cd ..", "release": "npm version --no-git-tag-version patch && npm run build:all && npm run test:all" }, "files": [ "dist/**", "src/**", "task/**", "index.d.ts", "README.md", "CHANGELOG.md", "LICENSE" ], "devDependencies": { "babel-cli": "^6.26.0", "babel-plugin-conditional-compile": "^0.0.5", "babel-plugin-minify-constant-folding": "^0.5.0", "babel-plugin-minify-dead-code-elimination": "^0.5.2", "babel-plugin-minify-flip-comparisons": "^0.4.3", "babel-plugin-minify-guarded-expressions": "^0.4.4", "babel-plugin-minify-infinity": "^0.4.3", "babel-plugin-minify-mangle-names": "^0.5.1", "babel-plugin-minify-replace": "^0.5.0", "babel-plugin-minify-simplify": "^0.5.1", "babel-plugin-minify-type-constructors": "^0.4.3", "babel-plugin-transform-member-expression-literals": "^6.9.4", "babel-plugin-transform-merge-sibling-variables": "^6.9.5", "babel-plugin-transform-minify-booleans": "^6.9.4", "babel-plugin-transform-property-literals": "^6.9.4", "babel-plugin-transform-simplify-comparison-operators": "^6.9.4", "babel-plugin-transform-undefined-to-void": "^6.9.4", "google-closure-compiler": "^20250520.0.0", "rollup": "^4.35.0" }, "funding": [ { "type": "github", "url": "https://github.com/ts-thomas" }, { "type": "paypal", "url": "https://www.paypal.com/donate/?hosted_button_id=GEVR88FC9BWRW" }, { "type": "opencollective", "url": "https://opencollective.com/flexsearch" }, { "type": "patreon", "url": "https://patreon.com/user?u=96245532" }, { "type": "liberapay", "url": "https://liberapay.com/ts-thomas" } ] } ================================================ FILE: src/async.js ================================================ // COMPILER BLOCK --> import { SUPPORT_CACHE } from "./config.js"; // <-- COMPILER BLOCK import Document from "./document.js"; import Index from "./index.js"; import WorkerIndex from "./worker.js"; export default function(prototype){ register.call(prototype, "add"); register.call(prototype, "append"); register.call(prototype, "search"); register.call(prototype, "update"); register.call(prototype, "remove"); if(SUPPORT_CACHE){ register.call(prototype, "searchCache"); } } let timer; let timestamp; let cycle; function tick(){ timer = cycle = 0; } /** * @param {!string} key * @this {Index|Document|WorkerIndex} */ function register(key){ this[key + "Async"] = function(){ const args = /*[].slice.call*/(arguments); const arg = args[args.length - 1]; let callback; if(typeof arg === "function"){ callback = arg; delete args[args.length - 1]; } // event loop runtime balancer if(!timer){ // when the next event loop occurs earlier than task completion // it will reset the state immediately timer = setTimeout(tick, 0); timestamp = Date.now(); } else if(!cycle){ const now = Date.now(); const duration = now - timestamp; const target = this.priority * this.priority * 3; cycle = duration >= target; } // cycle all instances from this point if(cycle){ const self = this; // move the next microtask onto the next macrotask queue return new Promise(resolve => { setTimeout(function(){ resolve(self[key + "Async"].apply(self, args)); }, 0); }); } const res = this[key].apply(this, args); const promise = res.then ? res : new Promise(resolve => resolve(res)); if(callback){ promise.then(callback); } return promise; }; } ================================================ FILE: src/bundle.js ================================================ // COMPILER BLOCK --> import { RELEASE, SUPPORT_DOCUMENT, SUPPORT_WORKER, SUPPORT_ENCODER, SUPPORT_CHARSET, SUPPORT_PERSISTENT, SUPPORT_RESOLVER, SUPPORT_SERIALIZE } from "./config.js"; // <-- COMPILER BLOCK import { SearchOptions, ContextOptions, DocumentDescriptor, DocumentSearchOptions, FieldOptions, IndexOptions, DocumentOptions, TagOptions, StoreOptions, EncoderOptions, EncoderSplitOptions, PersistentOptions, ResolverOptions, HighlightBoundaryOptions, HighlightEllipsisOptions, HighlightOptions } from "./type.js"; import StorageInterface from "./db/interface.js"; import Document from "./document.js"; import Index from "./index.js"; import WorkerIndex from "./worker.js"; import Resolver from "./resolver.js"; import Encoder from "./encoder.js"; import IdxDB from "./db/indexeddb/index.js"; import Charset from "./charset.js"; import { KeystoreMap, KeystoreArray, KeystoreSet } from "./keystore.js"; /** @export */ Index.prototype.add; /** @export */ Index.prototype.append; /** @export */ Index.prototype.search; /** @export */ Index.prototype.update; /** @export */ Index.prototype.remove; /** @export */ Index.prototype.contain; /** @export */ Index.prototype.clear; /** @export */ Index.prototype.cleanup; /** @export */ Index.prototype.searchCache; /** @export */ Index.prototype.addAsync; /** @export */ Index.prototype.appendAsync; /** @export */ Index.prototype.searchAsync; /** @export */ Index.prototype.searchCacheAsync; /** @export */ Index.prototype.updateAsync; /** @export */ Index.prototype.removeAsync; /** @export */ Index.prototype.export; /** @export */ Index.prototype.import; /** @export */ Index.prototype.serialize; /** @export */ Index.prototype.mount; /** @export */ Index.prototype.commit; /** @export */ Index.prototype.destroy; /** @export */ Index.prototype.encoder; if(SUPPORT_SERIALIZE || SUPPORT_PERSISTENT){ /** @export */ Index.prototype.reg; /** @export */ Index.prototype.map; /** @export */ Index.prototype.ctx; } if(SUPPORT_PERSISTENT){ /** @export */ Index.prototype.db; /** @export */ Index.prototype.tag; /** @export */ Index.prototype.store; /** @export */ Index.prototype.depth; /** @export */ Index.prototype.bidirectional; /** @export */ Index.prototype.commit_task; /** @export */ Index.prototype.commit_timer; /** @export */ Index.prototype.cache; /** @export */ Index.prototype.bypass; /** @export */ Index.prototype.document; } /** @export */ Encoder.prototype.assign; /** @export */ Encoder.prototype.encode; /** @export */ Encoder.prototype.addMatcher; /** @export */ Encoder.prototype.addStemmer; /** @export */ Encoder.prototype.addFilter; /** @export */ Encoder.prototype.addMapper; /** @export */ Encoder.prototype.addReplacer; /** @export */ Document.prototype.add; /** @export */ Document.prototype.append; /** @export */ Document.prototype.search; /** @export */ Document.prototype.update; /** @export */ Document.prototype.remove; /** @export */ Document.prototype.contain; /** @export */ Document.prototype.clear; /** @export */ Document.prototype.cleanup; /** @export */ Document.prototype.addAsync; /** @export */ Document.prototype.appendAsync; /** @export */ Document.prototype.updateAsync; /** @export */ Document.prototype.removeAsync; /** @export */ Document.prototype.searchAsync; /** @export */ Document.prototype.searchCacheAsync; /** @export */ Document.prototype.searchCache; /** @export */ Document.prototype.mount; /** @export */ Document.prototype.commit; /** @export */ Document.prototype.destroy; /** @export */ Document.prototype.export; /** @export */ Document.prototype.import; /** @export */ Document.prototype.get; /** @export */ Document.prototype.set; if(SUPPORT_SERIALIZE){ /** @export */ Document.prototype.field; /** @export */ Document.prototype.index; /** @export */ Document.prototype.reg; /** @export */ Document.prototype.tag; /** @export */ Document.prototype.store; /** @export */ Document.prototype.fastupdate; } /** @export */ Resolver.prototype.limit; /** @export */ Resolver.prototype.offset; /** @export */ Resolver.prototype.boost; /** @export */ Resolver.prototype.resolve; /** @export */ Resolver.prototype.or; /** @export */ Resolver.prototype.and; /** @export */ Resolver.prototype.xor; /** @export */ Resolver.prototype.not; /** @export */ Resolver.prototype.result; /** @export */ Resolver.prototype.await; /** @export */ StorageInterface.db; /** @export */ StorageInterface.id; /** @export */ StorageInterface.support_tag_search; /** @export */ StorageInterface.fastupdate; /** @export */ StorageInterface.prototype.mount; /** @export */ StorageInterface.prototype.open; /** @export */ StorageInterface.prototype.close; /** @export */ StorageInterface.prototype.destroy; /** @export */ StorageInterface.prototype.clear; /** @export */ StorageInterface.prototype.get; /** @export */ StorageInterface.prototype.tag; /** @export */ StorageInterface.prototype.enrich; /** @export */ StorageInterface.prototype.has; /** @export */ StorageInterface.prototype.search; /** @export */ StorageInterface.prototype.info; /** @export */ StorageInterface.prototype.commit; /** @export */ StorageInterface.prototype.remove; /** @export */ KeystoreArray.length; /** @export */ KeystoreMap.size; /** @export */ KeystoreSet.size; /** @export */ Charset.Exact; /** @export */ Charset.Default; /** @export */ Charset.Normalize; /** @export */ Charset.LatinBalance; /** @export */ Charset.LatinAdvanced; /** @export */ Charset.LatinExtra; /** @export */ Charset.LatinSoundex; /** @export */ Charset.CJK; /** @export @deprecated */ Charset.LatinExact; /** @export @deprecated */ Charset.LatinDefault; /** @export @deprecated */ Charset.LatinSimple; /** @export @deprecated */ Charset.ArabicDefault; /** @export @deprecated */ Charset.CjkDefault; /** @export @deprecated */ Charset.CyrillicDefault; /** @export */ IndexOptions.preset; /** @export */ IndexOptions.context; /** @export */ IndexOptions.encoder; /** @export */ IndexOptions.encode; /** @export */ IndexOptions.resolution; /** @export */ IndexOptions.tokenize; /** @export */ IndexOptions.fastupdate; /** @export */ IndexOptions.score; /** @export */ IndexOptions.keystore; /** @export */ IndexOptions.rtl; /** @export */ IndexOptions.cache; /** @export */ IndexOptions.resolve; /** @export */ IndexOptions.db; /** @export */ IndexOptions.worker; // worker url /** @export */ IndexOptions.config; // config url /** @export */ IndexOptions.priority; /** @export */ IndexOptions.export; /** @export */ IndexOptions.import; /** @export */ FieldOptions.preset; /** @export */ FieldOptions.context; /** @export */ FieldOptions.encoder; /** @export */ FieldOptions.encode; /** @export */ FieldOptions.resolution; /** @export */ FieldOptions.tokenize; /** @export */ FieldOptions.fastupdate; /** @export */ FieldOptions.score; /** @export */ FieldOptions.keystore; /** @export */ FieldOptions.rtl; /** @export */ FieldOptions.cache; /** @export */ FieldOptions.db; /** @export */ FieldOptions.config; /** @export */ FieldOptions.resolve; /** @export */ FieldOptions.field; /** @export */ FieldOptions.filter; /** @export */ FieldOptions.custom; /** @export */ FieldOptions.worker; /** @export */ DocumentOptions.context; /** @export */ DocumentOptions.encoder; /** @export */ DocumentOptions.encode; /** @export */ DocumentOptions.resolution; /** @export */ DocumentOptions.tokenize; /** @export */ DocumentOptions.fastupdate; /** @export */ DocumentOptions.score; /** @export */ DocumentOptions.keystore; /** @export */ DocumentOptions.rtl; /** @export */ DocumentOptions.cache; /** @export */ DocumentOptions.db; /** @export */ DocumentOptions.doc; /** @export */ DocumentOptions.document; /** @export */ DocumentOptions.worker; /** @export */ DocumentOptions.priority; /** @export */ DocumentOptions.export; /** @export */ DocumentOptions.import; /** @export */ ContextOptions.depth; /** @export */ ContextOptions.bidirectional; /** @export */ ContextOptions.resolution; /** @export */ DocumentDescriptor.field; /** @export */ DocumentDescriptor.index; /** @export */ DocumentDescriptor.tag; /** @export */ DocumentDescriptor.store; /** @export */ DocumentDescriptor.id; /** @export */ TagOptions.field; /** @export */ TagOptions.tag; /** @export */ TagOptions.filter; /** @export */ TagOptions.custom; /** @export */ TagOptions.keystore; /** @export */ TagOptions.db; /** @export */ TagOptions.config; /** @export */ StoreOptions.field; /** @export */ StoreOptions.filter; /** @export */ StoreOptions.custom; /** @export */ StoreOptions.config; /** @export */ SearchOptions.query; /** @export */ SearchOptions.limit; /** @export */ SearchOptions.offset; /** @export */ SearchOptions.context; /** @export */ SearchOptions.suggest; /** @export */ SearchOptions.resolve; /** @export */ SearchOptions.cache; /** @export */ SearchOptions.resolution; /** @export */ DocumentSearchOptions.query; /** @export */ DocumentSearchOptions.limit; /** @export */ DocumentSearchOptions.offset; /** @export */ DocumentSearchOptions.context; /** @export */ DocumentSearchOptions.suggest; /** @export */ DocumentSearchOptions.resolve; /** @export */ DocumentSearchOptions.enrich; /** @export */ DocumentSearchOptions.cache; /** @export */ DocumentSearchOptions.resolution; /** @export */ DocumentSearchOptions.tag; /** @export */ DocumentSearchOptions.field; /** @export */ DocumentSearchOptions.index; /** @export */ DocumentSearchOptions.pluck; /** @export */ DocumentSearchOptions.merge; /** @export */ DocumentSearchOptions.highlight; /** @export */ EncoderOptions.rtl; /** @export */ EncoderOptions.dedupe; /** @export */ EncoderOptions.split; /** @export */ EncoderOptions.include; /** @export */ EncoderOptions.exclude; /** @export */ EncoderOptions.prepare; /** @export */ EncoderOptions.finalize; /** @export */ EncoderOptions.filter; /** @export */ EncoderOptions.matcher; /** @export */ EncoderOptions.mapper; /** @export */ EncoderOptions.stemmer; /** @export */ EncoderOptions.replacer; /** @export */ EncoderOptions.minlength; /** @export */ EncoderOptions.maxlength; /** @export */ EncoderOptions.cache; /** @export */ EncoderSplitOptions.letter; /** @export */ EncoderSplitOptions.number; /** @export */ EncoderSplitOptions.symbol; /** @export */ EncoderSplitOptions.punctuation; /** @export */ EncoderSplitOptions.control; /** @export */ EncoderSplitOptions.char; /** @export */ PersistentOptions.name; /** @export */ PersistentOptions.field; /** @export */ PersistentOptions.type; /** @export */ PersistentOptions.db; /** @export */ ResolverOptions.index; /** @export */ ResolverOptions.query; /** @export */ ResolverOptions.limit; /** @export */ ResolverOptions.offset; /** @export */ ResolverOptions.boost; /** @export */ ResolverOptions.enrich; /** @export */ ResolverOptions.resolve; /** @export */ ResolverOptions.suggest; /** @export */ ResolverOptions.cache; /** @export */ ResolverOptions.async; /** @export */ ResolverOptions.queue; /** @export */ ResolverOptions.and; /** @export */ ResolverOptions.or; /** @export */ ResolverOptions.xor; /** @export */ ResolverOptions.not; /** @export */ ResolverOptions.pluck; /** @export */ ResolverOptions.field; /** @export */ HighlightBoundaryOptions.before; /** @export */ HighlightBoundaryOptions.after; /** @export */ HighlightBoundaryOptions.total; /** @export */ HighlightEllipsisOptions.template; /** @export */ HighlightEllipsisOptions.pattern; /** @export */ HighlightOptions.template; /** @export */ HighlightOptions.boundary; /** @export */ HighlightOptions.ellipsis; /** @export */ HighlightOptions.clip; /** @export */ HighlightOptions.merge; const FlexSearch = { "Index": Index, "Charset": SUPPORT_CHARSET ? Charset : null, "Encoder": SUPPORT_ENCODER ? Encoder : null, "Document": SUPPORT_DOCUMENT ? Document : null, "Worker": SUPPORT_WORKER ? WorkerIndex : null, "Resolver": SUPPORT_RESOLVER ? Resolver : null, "IndexedDB": SUPPORT_PERSISTENT ? IdxDB : null, "Language": {} }; // Export as library (Bundle) // -------------------------------- if(RELEASE !== "bundle.module" && RELEASE !== "light.module" && RELEASE !== "compact.module" && RELEASE !== "custom.module"){ // Legacy Browser: this refers to window // ESM Browser: self refers to window // NodeJS: global refers to the global scope const root = typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : self; let prop; // AMD (RequireJS) if((prop = root["define"]) && prop["amd"]){ prop([], function(){ return FlexSearch; }); } // CommonJS else if(typeof root["exports"] === "object"){ root["exports"] = FlexSearch; } // Global (window) else{ /** @export */ root.FlexSearch = FlexSearch; } } else{ /** @export */ window.FlexSearch = FlexSearch; } export default FlexSearch; export { Index, Document, Encoder, Charset, WorkerIndex as Worker, Resolver, IdxDB as IndexedDB }; ================================================ FILE: src/cache.js ================================================ import Index from "./index.js"; import { SearchOptions, DocumentSearchOptions } from "./type.js"; /** * @param {string|SearchOptions|DocumentSearchOptions} query * @param {number|SearchOptions|DocumentSearchOptions=} limit * @param {SearchOptions|DocumentSearchOptions=} options * @this {Index} * @returns {Array|Promise} */ export function searchCache(query, limit, options){ if(!options){ if(!limit && typeof query === "object"){ options = query; } else if(typeof limit === "object"){ options = limit; limit = 0; } } if(options){ query = options.query || query; limit = options.limit || limit; } let key = "" + (limit || 0); if(options){ const { context, suggest, offset, resolve, boost, resolution } = options; key += (offset || 0) + !!context + !!suggest + (resolve !== false) + (resolution || this.resolution) + (boost || 0); } query = ("" + query).toLowerCase(); if(!this.cache){ this.cache = new CacheClass(); } let cache = this.cache.get(query + key); if(!cache){ const opt_cache = options && options.cache; opt_cache && (options.cache = false); cache = this.search(query, limit, options); opt_cache && (options.cache = opt_cache); // replace the promise with the result // if(cache.then){ // const self = this; // cache.then(function(cache){ // self.cache.set(query + key, cache); // return cache; // }); // } this.cache.set(query + key, cache); } return cache; } /** * @param {boolean|number=} limit * @constructor */ export default function CacheClass(limit){ /** @private */ this.limit = (!limit || limit === true) ? 1000 : limit; /** @private */ this.cache = new Map(); /** @private */ this.last = ""; } /** * @param {string} key * @param {Array} value */ CacheClass.prototype.set = function(key, value){ this.cache.set(this.last = key, value); if(this.cache.size > this.limit){ this.cache.delete(this.cache.keys().next().value); } }; /** * @param {string} key */ CacheClass.prototype.get = function(key){ const cache = this.cache.get(key); if(cache && this.last !== key){ this.cache.delete(key); this.cache.set(this.last = key, cache); } return cache; }; /** * @param {string|number} id */ CacheClass.prototype.remove = function(id){ for(const item of this.cache){ const key = item[0]; const value = item[1]; if(value.includes(id)){ this.cache.delete(key); } } }; CacheClass.prototype.clear = function(){ this.cache.clear(); this.last = ""; }; ================================================ FILE: src/charset/cjk.js ================================================ import { EncoderOptions } from "../type.js"; // https://en.wikipedia.org/wiki/CJK_characters /** @type EncoderOptions */ const options = { split: "" //normalize: true // normalize: function(str){ // return str.toLowerCase(); // }, //dedupe: false }; export default options; ================================================ FILE: src/charset/exact.js ================================================ import { EncoderOptions } from "../type.js"; /** @type EncoderOptions */ const options = { normalize: false, numeric: false, dedupe: false //split: /\s+/ //normalize: false, //dedupe: false }; export default options; ================================================ FILE: src/charset/latin/advanced.js ================================================ import { EncoderOptions } from "../../type.js"; import { soundex } from "./balance.js"; export const matcher = new Map([ //["ai", "ei"], // before soundex ["ae", "a"], ["oe", "o"], //["ue", "u"], // soundex map ["sh", "s"], // replacer "h" //["ch", "c"], // before soundex ["kh", "k"], // after soundex ["th", "t"], // replacer "h" ["ph", "f"], // replacer "h" ["pf", "f"], //["ps", "s"], //["ts", "s"], ]); export const replacer = [ /([^aeo])h(.)/g, "$1$2", /([aeo])h([^aeo]|$)/g, "$1$2", /(.)\1+/g, "$1" // /([^0-9])\1+/g, "$1" ]; /** @type EncoderOptions */ const options = { //normalize: true, //dedupe: true, mapper: soundex, matcher: matcher, replacer: replacer }; export default options; ================================================ FILE: src/charset/latin/balance.js ================================================ import { EncoderOptions } from "../../type.js"; export const soundex = new Map([ ["b", "p"], //["p", "p"], //["f", "f"], ["v", "f"], ["w", "f"], //["s", "s"], ["z", "s"], ["x", "s"], ["d", "t"], //["t", "t"], //["m", "m"], ["n", "m"], //["k", "k"], ["c", "k"], ["g", "k"], ["j", "k"], ["q", "k"], //["r", "r"], //["h", "h"], //["l", "l"], //["a", "a"], //["e", "e"], ["i", "e"], ["y", "e"], //["o", "o"], ["u", "o"] ]); /** @type EncoderOptions */ const options = { //normalize: true, //dedupe: true, mapper: soundex }; export default options; ================================================ FILE: src/charset/latin/extra.js ================================================ import { EncoderOptions } from "../../type.js"; import { soundex } from "./balance.js"; import { matcher, replacer } from "./advanced.js"; export const compact = [ /(?!^)[aeo]/g, "" // before soundex: aeoy, old: aioy ]; /** @type EncoderOptions */ const options = { //normalize: true, //dedupe: true, mapper: soundex, replacer: replacer.concat(compact), matcher: matcher }; export default options; ================================================ FILE: src/charset/latin/soundex.js ================================================ import { EncoderOptions } from "../../type.js"; /** @type {EncoderOptions} */ const options = { //normalize: true, dedupe: false, include: { letter: true }, finalize: function(arr){ for(let i = 0; i < arr.length; i++){ arr[i] = soundex(arr[i]); } } }; export default options; const codes = { "a": "", "e": "", "i": "", "o": "", "u": "", "y": "", "b": 1, "f": 1, "p": 1, "v": 1, "c": 2, "g": 2, "j": 2, "k": 2, "q": 2, "s": 2, "x": 2, "z": 2, "ß": 2, "d": 3, "t": 3, "l": 4, "m": 5, "n": 5, "r": 6 }; function soundex(stringToEncode){ let encodedString = stringToEncode.charAt(0); let last = codes[encodedString]; for(let i = 1, char; i < stringToEncode.length; i++){ char = stringToEncode.charAt(i); // Remove all occurrences of "h" and "w" if(char !== "h" && char !== "w"){ // Replace all consonants with digits char = codes[char]; // Remove all occurrences of a,e,i,o,u,y except first letter if(char){ // Replace all adjacent same digits with one digit if(char !== last){ encodedString += char; last = char; if(encodedString.length === 4){ break; } } } } } // while(encodedString.length < 4){ // encodedString += "0"; // } return encodedString; } ================================================ FILE: src/charset/normalize.js ================================================ import { EncoderOptions } from "../type.js"; /** @type EncoderOptions */ const options = { //normalize: true // normalize: function(str){ // return str.toLowerCase(); // }, //dedupe: false }; export default options; ================================================ FILE: src/charset/polyfill.js ================================================ export default [ // Charset Normalization ["ª","a"], ["²","2"], ["³","3"], ["¹","1"], ["º","o"], ["¼","1⁄4"], ["½","1⁄2"], ["¾","3⁄4"], ["à","a"], ["á","a"], ["â","a"], ["ã","a"], ["ä","a"], ["å","a"], ["ç","c"], ["è","e"], ["é","e"], ["ê","e"], ["ë","e"], ["ì","i"], ["í","i"], ["î","i"], ["ï","i"], ["ñ","n"], ["ò","o"], ["ó","o"], ["ô","o"], ["õ","o"], ["ö","o"], ["ù","u"], ["ú","u"], ["û","u"], ["ü","u"], ["ý","y"], ["ÿ","y"], ["ā","a"], ["ă","a"], ["ą","a"], ["ć","c"], ["ĉ","c"], ["ċ","c"], ["č","c"], ["ď","d"], ["ē","e"], ["ĕ","e"], ["ė","e"], ["ę","e"], ["ě","e"], ["ĝ","g"], ["ğ","g"], ["ġ","g"], ["ģ","g"], ["ĥ","h"], ["ĩ","i"], ["ī","i"], ["ĭ","i"], ["į","i"], ["ij","ij"], ["ĵ","j"], ["ķ","k"], ["ĺ","l"], ["ļ","l"], ["ľ","l"], ["ŀ","l"], ["ń","n"], ["ņ","n"], ["ň","n"], ["ʼn","n"], ["ō","o"], ["ŏ","o"], ["ő","o"], ["ŕ","r"], ["ŗ","r"], ["ř","r"], ["ś","s"], ["ŝ","s"], ["ş","s"], ["š","s"], ["ţ","t"], ["ť","t"], ["ũ","u"], ["ū","u"], ["ŭ","u"], ["ů","u"], ["ű","u"], ["ų","u"], ["ŵ","w"], ["ŷ","y"], ["ź","z"], ["ż","z"], ["ž","z"], ["ſ","s"], ["ơ","o"], ["ư","u"], ["dž","dz"], ["lj","lj"], ["nj","nj"], ["ǎ","a"], ["ǐ","i"], ["ǒ","o"], ["ǔ","u"], ["ǖ","u"], ["ǘ","u"], ["ǚ","u"], ["ǜ","u"], ["ǟ","a"], ["ǡ","a"], ["ǣ","ae"], ["æ","ae"], ["ǽ","ae"], ["ǧ","g"], ["ǩ","k"], ["ǫ","o"], ["ǭ","o"], ["ǯ","ʒ"], ["ǰ","j"], ["dz","dz"], ["ǵ","g"], ["ǹ","n"], ["ǻ","a"], ["ǿ","ø"], ["ȁ","a"], ["ȃ","a"], ["ȅ","e"], ["ȇ","e"], ["ȉ","i"], ["ȋ","i"], ["ȍ","o"], ["ȏ","o"], ["ȑ","r"], ["ȓ","r"], ["ȕ","u"], ["ȗ","u"], ["ș","s"], ["ț","t"], ["ȟ","h"], ["ȧ","a"], ["ȩ","e"], ["ȫ","o"], ["ȭ","o"], ["ȯ","o"], ["ȱ","o"], ["ȳ","y"], ["ʰ","h"], ["ʱ","h"], ["ɦ","h"], ["ʲ","j"], ["ʳ","r"], ["ʴ","ɹ"], ["ʵ","ɻ"], ["ʶ","ʁ"], ["ʷ","w"], ["ʸ","y"], ["ˠ","ɣ"], ["ˡ","l"], ["ˢ","s"], ["ˣ","x"], ["ˤ","ʕ"], ["ΐ","ι"], ["ά","α"], ["έ","ε"], ["ή","η"], ["ί","ι"], ["ΰ","υ"], ["ϊ","ι"], ["ϋ","υ"], ["ό","ο"], ["ύ","υ"], ["ώ","ω"], ["ϐ","β"], ["ϑ","θ"], ["ϒ","Υ"], ["ϓ","Υ"], ["ϔ","Υ"], ["ϕ","φ"], ["ϖ","π"], ["ϰ","κ"], ["ϱ","ρ"], ["ϲ","ς"], ["ϵ","ε"], ["й","и"], ["ѐ","е"], ["ё","е"], ["ѓ","г"], ["ї","і"], ["ќ","к"], ["ѝ","и"], ["ў","у"], ["ѷ","ѵ"], ["ӂ","ж"], ["ӑ","а"], ["ӓ","а"], ["ӗ","е"], ["ӛ","ә"], ["ӝ","ж"], ["ӟ","з"], ["ӣ","и"], ["ӥ","и"], ["ӧ","о"], ["ӫ","ө"], ["ӭ","э"], ["ӯ","у"], ["ӱ","у"], ["ӳ","у"], ["ӵ","ч"] // Term Separators // ["'", ""], // it's -> its // ["´", ""], // ["`", ""], // ["’", ""], // ["ʼ", ""], // Numeric-Separators Chars Removal // [",", ""], // [".", ""] // Non-Whitespace Separators // already was split by default via p{P} // ["-", " "], // [":", " "], // ["_", " "], // ["|", " "], // ["/", " "], // ["\\", " "] ]; ================================================ FILE: src/charset.js ================================================ import charset_exact from "./charset/exact.js"; import charset_normalize from "./charset/normalize.js"; import charset_latin_balance from "./charset/latin/balance.js"; import charset_latin_advanced from "./charset/latin/advanced.js"; import charset_latin_extra from "./charset/latin/extra.js"; import charset_latin_soundex from "./charset/latin/soundex.js"; import charset_cjk from "./charset/cjk.js"; // universal charset export const Exact = charset_exact; export const Default = charset_normalize; export const Normalize = charset_normalize; // latin charset export const LatinBalance = charset_latin_balance; export const LatinAdvanced = charset_latin_advanced; export const LatinExtra = charset_latin_extra; export const LatinSoundex = charset_latin_soundex; // CJK export const CJK = charset_cjk; // deprecated export const LatinExact = charset_exact; export const LatinDefault = charset_normalize; export const LatinSimple = charset_normalize; export default { // universal charset Exact: charset_exact, Default: charset_normalize, Normalize: charset_normalize, // latin charset LatinBalance: charset_latin_balance, LatinAdvanced: charset_latin_advanced, LatinExtra: charset_latin_extra, LatinSoundex: charset_latin_soundex, // CJK CJK: charset_cjk, // deprecated LatinExact: charset_exact, LatinDefault: charset_normalize, LatinSimple: charset_normalize }; ================================================ FILE: src/common.js ================================================ /** * @param {*} value * @param {*} default_value * @param {*=} merge_value * @return {*} */ export function merge_option(value, default_value, merge_value){ const type_merge = typeof merge_value; const type_value = typeof value; if(type_merge !== "undefined"){ if(type_value !== "undefined"){ if(merge_value){ if(type_value === "function" && type_merge === type_value){ return function(str){ return /** @type Function */ (value)( /** @type Function */ (merge_value)(str) ); } } const constructor_value = value.constructor; const constructor_merge = merge_value.constructor; if(constructor_value === constructor_merge){ if(constructor_value === Array){ return merge_value.concat(value); } if(constructor_value === Map){ const map = new Map(/** @type !Map */ (merge_value)); for(const item of /** @type !Map */ (value)){ const key = item[0]; const val = item[1]; map.set(key, val); } return map; } if(constructor_value === Set){ const set = new Set(/** @type !Set */ (merge_value)); for(const val of /** @type !Set */ (value).values()){ set.add(val); } return set; } } } return value; } else{ return merge_value; } } return type_value === "undefined" ? default_value : value; } export function inherit(target_value, default_value){ return typeof target_value === "undefined" ? default_value : target_value; } export function create_object(){ return Object.create(null); } export function concat(arrays){ return [].concat.apply([], arrays); } export function sort_by_length_down(a, b){ return b.length - a.length; } export function sort_by_length_up(a, b){ return a.length - b.length; } export function is_array(val){ return val.constructor === Array; } export function is_string(val){ return typeof val === "string"; } export function is_object(val){ return typeof val === "object"; } export function is_function(val){ return typeof val === "function"; } /** * @param {Map|Set} val * @param {boolean=} stringify * @return {Array} */ export function toArray(val, stringify){ const result = []; for(const key of val.keys()){ result.push(stringify ? "" + key : key); } return result; } // TODO support generic function created from string when tree depth > 1 export function parse_simple(obj, tree){ if(is_string(tree)){ obj = obj[tree]; } else for(let i = 0; obj && (i < tree.length); i++){ obj = obj[tree[i]]; } return obj; } export function get_max_len(arr){ let len = 0; for(let i = 0, res; i < arr.length; i++){ if((res = arr[i])){ if(len < res.length){ len = res.length; } } } return len; } ================================================ FILE: src/compress.js ================================================ // COMPILER BLOCK --> import { SUPPORT_CACHE } from "./config.js"; // <-- COMPILER BLOCK let table;// = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"; let timer; const cache = new Map(); function toRadix(number, radix = 255) { if(!table){ table = new Array(radix); // the char code 0 could be a special marker for(let i = 0; i < radix; i++) table[i] = i + 1; table = String.fromCharCode.apply(null, table); } let rixit; let residual = number; let result = ""; while(true){ rixit = residual % radix; result = table.charAt(rixit) + result; residual = residual / radix | 0; if(!residual) break; } return result; } export default function(str){ if(SUPPORT_CACHE){ if(timer){ if(cache.has(str)){ return cache.get(str); } } else{ timer = setTimeout(clear, 1); } } /* 2 ** ((level + 1.5) * 1.6 | 0) */ const result = toRadix( typeof str == "number" ? str : lcg(str) ); if(SUPPORT_CACHE){ cache.size > 2e5 && cache.clear(); cache.set(str, result); } return result; } function lcg(str) { let range = 2 ** 32 - 1; if(typeof str == "number"){ return str & range; } let crc = 0, bit = 32 + 1; for(let i = 0; i < str.length; i++) { crc = (crc * bit ^ str.charCodeAt(i)) & range; } // shift up from Int32 to UInt32 range 0xFFFFFFFF return crc + 2 ** 31; } function clear(){ timer = null; cache.clear(); } ================================================ FILE: src/config.js ================================================ /** @define {string} */ export const RELEASE = "source"; /** @define {boolean} */ export const DEBUG = true; /** @define {boolean} */ export const PROFILER = false; /** @define {boolean} */ export const POLYFILL = false; /** @define {boolean} */ export const SUPPORT_WORKER = true; /** @define {boolean|string} */ export const SUPPORT_ENCODER = true; /** @define {boolean|string} */ export const SUPPORT_CHARSET = true; /** @define {boolean} */ export const SUPPORT_CACHE = true; /** @define {boolean} */ export const SUPPORT_ASYNC = true; /** @define {boolean} */ export const SUPPORT_STORE = true; /** @define {boolean} */ export const SUPPORT_SUGGESTION = true; /** @define {boolean} */ export const SUPPORT_SERIALIZE = true; /** @define {boolean} */ export const SUPPORT_DOCUMENT = true; /** @define {boolean} */ export const SUPPORT_TAGS = true; /** @define {boolean} */ export const SUPPORT_PERSISTENT = true; /** @define {boolean} */ export const SUPPORT_KEYSTORE = true; /** @define {boolean} */ export const SUPPORT_COMPRESSION = false; /** @define {boolean} */ export const SUPPORT_RESOLVER = true; /** @define {boolean} */ export const SUPPORT_HIGHLIGHTING = true; ================================================ FILE: src/db/clickhouse/index.js ================================================ import { ClickHouse } from "clickhouse"; import StorageInterface from "../interface.js"; import { concat, toArray } from "../../common.js"; const defaults = { host: "http://localhost", port: "8123", debug: false, basicAuth: null, isUseGzip: false, trimQuery: false, usePost: false, format: "json", raw: false, config: { output_format_json_quote_64bit_integers: 0, enable_http_compression: 0, database: "default" } }; const VERSION = 1; const fields = ["map", "ctx", "tag", "reg", "cfg"]; const types = { "text": "String", "char": "String", "varchar": "String", "string": "String", "number": "Int32", "numeric": "Int32", "integer": "Int32", "smallint": "Int16", "tinyint": "Int8", "mediumint": "Int32", "int": "Int32", "int8": "Int8", "uint8": "UInt8", "int16": "Int16", "uint16": "UInt16", "int32": "Int32", "uint32": "UInt32", "int64": "Int64", "uint64": "UInt64", "bigint": "Int64" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } let Index; /** * @constructor * @implements StorageInterface */ export default function ClickhouseDB(name, config = {}){ if(!this || this.constructor !== ClickhouseDB){ return new ClickhouseDB(name, config); } if(typeof name === "object"){ config = name; name = name.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } //field = "Test-456"; this.id = "flexsearch" + (name ? "_" + sanitize(name) : ""); this.field = config.field ? "_" + sanitize(config.field) : ""; // Clickhouse does not support ALTER TABLE to upgrade // the type of the ID when it is a part of the merge key this.type = config.type ? types[config.type.toLowerCase()] : "String"; if(!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); //this.trx = false; this.support_tag_search = true; this.db = Index || (Index = config.db || null); Object.assign(defaults, config); config.database && (defaults.config.database = config.database); this.db && delete defaults.db; }; ClickhouseDB.prototype.mount = function(flexsearch){ //if(flexsearch.constructor === Document){ if(flexsearch.index){ return flexsearch.mount(this); } defaults.resolution = Math.max(flexsearch.resolution, flexsearch.resolution_ctx); flexsearch.db = this; return this.open(); }; ClickhouseDB.prototype.open = async function(){ if(!this.db) { this.db = Index || ( Index = new ClickHouse(defaults) ); } const exists = await this.db.query(` SELECT 1 FROM system.databases WHERE name = '${this.id}'; `).toPromise(); if(!exists || !exists.length){ await this.db.query(` CREATE DATABASE IF NOT EXISTS ${this.id}; `).toPromise(); } for(let i = 0; i < fields.length; i++){ switch(fields[i]){ case "map": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.map${this.field}( key String, res ${defaults.resolution <= 255 ? "UInt8" : "UInt16"}, id ${this.type} ) ENGINE = MergeTree /*PRIMARY KEY (key)*/ ORDER BY (key, id); `, { params: { name: this.id + ".map" + this.field }}).toPromise(); break; case "ctx": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.ctx${this.field}( ctx String, key String, res ${defaults.resolution <= 255 ? "UInt8" : "UInt16"}, id ${this.type} ) ENGINE = MergeTree /*PRIMARY KEY (ctx, key)*/ ORDER BY (ctx, key, id); `).toPromise(); break; case "tag": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.tag${this.field}( tag String, id ${this.type} ) ENGINE = MergeTree /*PRIMARY KEY (ctx, key)*/ ORDER BY (tag, id); `).toPromise(); break; case "reg": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.reg( id ${this.type}, doc Nullable(String) ) ENGINE = MergeTree ORDER BY (id); `).toPromise(); break; case "cfg": await this.db.query(` CREATE TABLE IF NOT EXISTS ${this.id}.cfg${this.field}( cfg String ) ENGINE = TinyLog; `).toPromise(); break; } } return this.db; }; ClickhouseDB.prototype.close = function(){ //DB && DB.close(); this.db = Index = null; return this; }; ClickhouseDB.prototype.destroy = function(){ return Promise.all([ this.db.query(`DROP TABLE ${this.id}.map${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.ctx${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.tag${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.cfg${this.field};`).toPromise(), this.db.query(`DROP TABLE ${this.id}.reg;`).toPromise() ]); }; ClickhouseDB.prototype.clear = function(){ return Promise.all([ this.db.query(`TRUNCATE TABLE ${this.id}.map${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.ctx${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.tag${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.cfg${this.field};`).toPromise(), this.db.query(`TRUNCATE TABLE ${this.id}.reg;`).toPromise() ]); }; function create_result(rows, resolve, enrich){ if(resolve){ for(let i = 0; i < rows.length; i++){ if(enrich){ if(rows[i].doc){ rows[i].doc = JSON.parse(rows[i].doc); } } else{ rows[i] = rows[i].id; } } return rows; } else{ const arr = []; for(let i = 0, row; i < rows.length; i++){ row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id ); } return arr; } } ClickhouseDB.prototype.get = function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false, tags){ let rows; let stmt = ''; let params = ctx ? { ctx, key } : { key }; let table = this.id + (ctx ? ".ctx" : ".map") + this.field; if(tags){ for(let i = 0, count = 1; i < tags.length; i+=2){ stmt += ` AND ${ table }.id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = {tag${ count }:String})`; params["tag" + count] = tags[i + 1]; count++; } } if(ctx){ rows = this.db.query(` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT OUTER JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE ctx = {ctx:String} AND key = {key:String} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, { params } ).toPromise(); } else{ rows = this.db.query(` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT OUTER JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE key = {key:String} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, { params } ).toPromise(); } return rows.then(function(rows){ return create_result(rows, resolve, enrich); }); }; ClickhouseDB.prototype.tag = function(tag, limit = 0, offset = 0, enrich = false){ const table = this.id + ".tag" + this.field; const promise = this.db.query(` SELECT ${ table }.id ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT OUTER JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE tag = {tag:String} ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, { params: { tag } } ).toPromise(); enrich || promise.then(function(rows){ return create_result(rows, true, false); }); return promise; } ClickhouseDB.prototype.enrich = async function(ids){ let MAXIMUM_QUERY_VARS = 1e5; let result = []; if(typeof ids !== "object"){ ids = [ids]; } for(let count = 0; count < ids.length;){ const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; let params = {}; let stmt = ""; for(let i = 0; i < chunk.length; i++){ stmt += (stmt ? "," : "") + "{id" + (i + 1) + ":String}"; params["id" + (i + 1)] = chunk[i]; } const res = await this.db.query(` SELECT id, doc FROM ${ this.id }.reg WHERE id IN (${ stmt })`, { params } ).toPromise(); if(res && res.length){ for(let i = 0, doc; i < res.length; i++){ if((doc = res[i].doc)){ res[i].doc = JSON.parse(doc); } } result.push(res); } } return result.length === 1 ? result[0] : result.length > 1 ? concat(result) : result; } ClickhouseDB.prototype.has = async function(id){ const result = await this.db.query(` SELECT 1 FROM ${this.id}.reg WHERE id = {id:${this.type /*=== "number" ? "Int32" : "String"*/}} LIMIT 1`, { params: { id }} ).toPromise(); return !!(result && result[0] && result[0]["1"]); }; ClickhouseDB.prototype.search = function(flexsearch, query, limit = 100, offset = 0, suggest = false, resolve = true, enrich = false, tags){ let rows; if(query.length > 1 && flexsearch.depth){ let where = ""; let params = {}; let keyword = query[0]; let term; for(let i = 1; i < query.length; i++){ term = query[i]; const swap = flexsearch.bidirectional && (term > keyword); where += (where ? " OR " : "") + `(ctx = {ctx${ i }:String} AND key = {key${ i }:String})` params["ctx" + i] = swap ? term : keyword; params["key" + i] = swap ? keyword : term; keyword = term; } if(tags){ where = "(" + where + ")"; for(let i = 0, count = 1; i < tags.length; i+=2){ where += ` AND id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = {tag${ count }:String})`; params["tag" + count] = tags[i + 1]; count++; } } rows = this.db.query(` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" /*"MIN"*/ }(res) as res FROM ${ this.id }.ctx${ this.field } WHERE ${ where } GROUP BY id ) as r ${ enrich ? ` LEFT OUTER JOIN ${ this.id }.reg ON ${ this.id }.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + (query.length - 1) } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, { params }).toPromise(); // for(let i = 1; i < query.length; i++){ // where += (where ? " UNION ALL " : "") + ` // SELECT id, res // FROM ${this.id}.ctx${this.field} // WHERE ctx = {ctx${i}:String} AND key = {key${i}:String} // `; // term = query[i]; // const swap = flexsearch.bidirectional && (term > keyword); // params["ctx" + i] = swap ? term : keyword; // params["key" + i] = swap ? keyword : term; // keyword = term; // } // // rows = await this.db.query(` // SELECT id, res // FROM ( // SELECT id, ${suggest ? "SUM" : "MIN"}(res) as res, count(*) as count // FROM (${where}) as t // GROUP BY id // ORDER BY ${suggest ? "count DESC, res" : "res"} // LIMIT ${limit} // OFFSET ${offset} // ) as r // ${suggest ? "" : "WHERE count = " + (query.length - 1)} // `, { params }).toPromise(); } else{ let where = ""; let params = {}; for(let i = 0; i < query.length; i++){ where += (where ? "," : "") + `{key${i}:String}`; params["key" + i] = query[i]; } where = "key " + (query.length > 1 ? "IN (" + where + ")" : "= " + where ); if(tags){ where = "(" + where + ")"; for(let i = 0, count = 1; i < tags.length; i+=2){ where += ` AND id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = {tag${ count }:String})`; params["tag" + count] = tags[i + 1]; count++; } } rows = this.db.query(` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" /*"MIN"*/ }(res) as res FROM ${ this.id }.map${ this.field } WHERE ${ where } GROUP BY id ) as r ${ enrich ? ` LEFT OUTER JOIN ${ this.id }.reg ON ${ this.id }.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + query.length } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, { params }).toPromise(); // for(let i = 0; i < query.length; i++){ // params["key" + i] = query[i]; // where += (where ? " UNION ALL " : "") + ` // SELECT id, res // FROM ${ this.id }.map${ this.field } // WHERE key = {key${i}:String} // `; // } // rows = await this.db.query(` // SELECT id, res // FROM ( // SELECT id, ${suggest ? "SUM" : "MIN"}(res) as res, count(*) as count // FROM (${where}) as t // GROUP BY id // ORDER BY ${suggest ? "count DESC, res" : "res"} // LIMIT ${limit} // OFFSET ${offset} // ) as r // ${ suggest ? "" : "WHERE count = " + query.length } // `, { params }).toPromise(); } return rows.then(function(rows){ return create_result(rows, resolve, enrich); }); } ClickhouseDB.prototype.info = function(){ // todo }; ClickhouseDB.prototype.transaction = function(task){ // not supported return task.call(this); }; ClickhouseDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ /** @dict */ task = tasks[i]; if(task["del"]){ removals.push(task["del"]); } else if(task["ins"]){ } } if(removals.length){ await this.remove(removals); } if(!flexsearch.reg.size){ return; } const promises = []; if(flexsearch.map.size){ let data = []; for(const item of flexsearch.map){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ //this.type || (this.type = typeof ids[0]); for(let j = 0; j < ids.length; j++){ data.push({ key: key, res: i, id: /*this.type === "number" ? parseInt(ids[j], 10) :*/ ids[j] }); } } } } if(data.length){ promises.push(this.db.insert( `INSERT INTO ${ this.id }.map${ this.field } (key, res, id)`, data ).toPromise()); } } if(flexsearch.ctx.size){ let data = []; for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ for(let j = 0; j < ids.length; j++){ data.push({ ctx: ctx_key, key: key, res: i, id: /*this.type === "number" ? parseInt(ids[j], 10) :*/ ids[j] }); } } } } } if(data.length){ promises.push(this.db.insert( `INSERT INTO ${ this.id }.ctx${ this.field } (ctx, key, res, id)`, data ).toPromise()); } } if(flexsearch.tag){ let data = []; for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; for(let j = 0; j < ids.length; j++){ data.push({ tag, id: ids[j] }); } } if(data.length){ promises.push(this.db.insert( `INSERT INTO ${this.id}.tag${ this.field } (tag, id)`, data ).toPromise()); } } if(flexsearch.store){ let data = []; for(const item of flexsearch.store.entries()){ const id = item[0]; const doc = item[1]; data.push({ id, doc: doc && JSON.stringify(doc) }); } if(data.length){ promises.push(this.db.insert( `INSERT INTO ${this.id}.reg (id, doc)`, data ).toPromise()); } } else if(!flexsearch.bypass){ let data = toArray(flexsearch.reg); for(let i = 0; i < data.length; i++){ data[i] = { id: data[i] }; } if(data.length){ promises.push(this.db.insert( `INSERT INTO ${this.id}.reg (id)`, data ).toPromise()); } } // TODO // await this.db.insert(`INSERT INTO ${this.id}.cfg${this.field} (cfg)`, [{ // cfg: JSON.stringify({ // "encode": typeof flexsearch.encode === "string" ? flexsearch.encode : "", // "charset": typeof flexsearch.charset === "string" ? flexsearch.charset : "", // "tokenize": flexsearch.tokenize, // "resolution": flexsearch.resolution, // "minlength": flexsearch.minlength, // "optimize": flexsearch.optimize, // "fastupdate": flexsearch.fastupdate, // "encoder": flexsearch.encoder, // "context": { // "depth": flexsearch.depth, // "bidirectional": flexsearch.bidirectional, // "resolution": flexsearch.resolution_ctx // } // }) // }]).toPromise(); promises.length && await Promise.all(promises); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); await Promise.all([ this.db.query(`OPTIMIZE TABLE ${this.id}.map${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.ctx${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.tag${this.field} FINAL`).toPromise(), this.db.query(`OPTIMIZE TABLE ${this.id}.reg FINAL`).toPromise() ]); }; ClickhouseDB.prototype.remove = async function(ids){ if(typeof ids !== "object"){ ids = [ids]; } while(ids.length){ let chunk = ids.slice(0, 1e5); ids = ids.slice(1e5); chunk = this.type === "String" ? "'" + chunk.join("','") + "'" : chunk.join(","); await Promise.all([ this.db.query(` ALTER TABLE ${this.id}.map${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;` ).toPromise(), this.db.query(` ALTER TABLE ${this.id}.ctx${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;` ).toPromise(), this.db.query(` ALTER TABLE ${this.id}.tag${this.field} DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;` ).toPromise(), this.db.query(` ALTER TABLE ${this.id}.reg DELETE WHERE id IN (${chunk}) SETTINGS mutations_sync = 1;` ).toPromise() ]); } }; ================================================ FILE: src/db/clickhouse/package.json ================================================ { "public": true, "preferGlobal": false, "name": "flexsearch-clickhouse", "version": "0.1.0", "main": "index.js", "dependencies": { "clickhouse": "^2.6.0" } } ================================================ FILE: src/db/indexeddb/index.js ================================================ // COMPILER BLOCK --> import { DEBUG, SUPPORT_STORE, SUPPORT_TAGS } from "../../config.js"; // <-- COMPILER BLOCK import { PersistentOptions, SearchResults, EnrichedSearchResults } from "../../type.js"; const VERSION = 1; const IndexedDB = typeof window !== "undefined" && ( window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB ); const IDBTransaction = typeof window !== "undefined" && ( window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction ); const IDBKeyRange = typeof window !== "undefined" && ( window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange ); const fields = ["map", "ctx", "tag", "reg", "cfg"]; import StorageInterface from "../interface.js"; import { create_object, toArray } from "../../common.js"; /** * @param {!string} str * @return {string} */ function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } const Index = create_object(); /** * @param {string|PersistentOptions=} name * @param {PersistentOptions=} config * @constructor * @implements StorageInterface */ export default function IdxDB(name, config = {}){ if(!this || this.constructor !== IdxDB){ return new IdxDB(name, config); } if(typeof name === "object"){ config = /** @type PersistentOptions */ (name); name = name.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } this.id = "flexsearch" + (name ? ":" + sanitize(name) : ""); this.field = config.field ? sanitize(config.field) : ""; this.type = config.type; this.support_tag_search = false; this.fastupdate = false; this.db = null; this.trx = {}; }; IdxDB.prototype.mount = function(flexsearch){ //if(flexsearch.constructor === Document){ if(flexsearch.index){ return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; IdxDB.prototype.open = function(){ if(this.db) return this.db; let self = this; navigator.storage && navigator.storage.persist && navigator.storage.persist(); // return this.db = new Promise(function(resolve, reject){ Index[self.id] || (Index[self.id] = []); Index[self.id].push(self.field); const req = IndexedDB.open(self.id, VERSION); /** @this {IDBOpenDBRequest} */ req.onupgradeneeded = function(event){ const db = self.db = this.result; // Using Indexes + IDBKeyRange on schema map => [key, res, id] performs // too bad and blows up amazingly in size // The schema map:key => [res][id] is currently used instead // In fact that bypass the idea of a storage solution, // IndexedDB is such a poor contribution :( for(let i = 0, ref; i < fields.length; i++){ ref = fields[i]; for(let j = 0, field; j < Index[self.id].length; j++){ field = Index[self.id][j]; db.objectStoreNames.contains(ref + (ref !== "reg" ? (field ? ":" + field : "") : "")) || db.createObjectStore(ref + (ref !== "reg" ? (field ? ":" + field : "") : ""));//{ autoIncrement: true /*keyPath: "id"*/ } //.createIndex("idx", "ids", { multiEntry: true, unique: false }); } } // switch(event.oldVersion){ // existing db version // case 0: // // version 0 means that the client had no database // // perform initialization // case 1: // // client had version 1 // // update // } }; return self.db = promisfy(req, function(result){ self.db = result; //event.target.result; self.db.onversionchange = function(){ //database is outdated self.close(); }; }); // req.onblocked = function(event) { // // this event shouldn't trigger if we handle onversionchange correctly // // it means that there's another open connection to the same database // // and it wasn't closed after db.onversionchange triggered for it // console.error("blocked", event); // reject(); // }; // // req.onerror = function(event){ // console.error(this.error, event); // reject(); // }; // // req.onsuccess = function(event){ // self.db = this.result; //event.target.result; // self.db.onversionchange = function(){ // //database is outdated // self.close(); // }; // resolve(self); // }; // }); }; IdxDB.prototype.close = function(){ this.db && this.db.close(); this.db = null; }; /** * @return {!Promise} */ IdxDB.prototype.destroy = function(){ const req = IndexedDB.deleteDatabase(this.id); return promisfy(req); }; // IdxDB.prototype.set = function(ref, key, ctx, data){ // const transaction = this.db.transaction(ref, "readwrite"); // const map = transaction.objectStore(ref); // const req = map.put(data, ctx ? ctx + ":" + key : key); // return transaction;//promisfy(req, callback); // }; // IdxDB.prototype.delete = function(ref, key, ctx){ // const transaction = this.db.transaction(ref, "readwrite"); // const map = transaction.objectStore(ref); // const req = map.delete(ctx ? ctx + ":" + key : key); // return transaction;//promisfy(req, callback); // }; /** * @return {!Promise} */ IdxDB.prototype.clear = function(){ const stores = []; for(let i = 0, ref; i < fields.length; i++){ ref = fields[i]; for(let j = 0, field; j < Index[this.id].length; j++){ field = Index[this.id][j]; stores.push(ref + (ref !== "reg" ? (field ? ":" + field : "") : "")); } } const transaction = this.db.transaction(stores, "readwrite"); for(let i = 0; i < stores.length; i++){ transaction.objectStore(stores[i]).clear(); } return promisfy(transaction); }; /** * @param {!string} key * @param {string=} ctx * @param {number=} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @return {!Promise} */ IdxDB.prototype.get = function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false){ const transaction = this.db.transaction((ctx ? "ctx" : "map") + (this.field ? ":" + this.field : ""), "readonly"); const map = transaction.objectStore((ctx ? "ctx" : "map") + (this.field ? ":" + this.field : "")); const req = map.get(ctx ? ctx + ":" + key : key); const self = this; return promisfy(req).then(function(res){ let result = []; if(!res || !res.length) return result; if(resolve){ if(!limit && !offset && res.length === 1){ return res[0]; } for(let i = 0, arr; i < res.length; i++){ if((arr = res[i]) && arr.length){ if(offset >= arr.length){ offset -= arr.length; continue; } const end = limit ? offset + Math.min(arr.length - offset, limit) : arr.length; for(let j = offset; j < end; j++){ result.push(arr[j]); } offset = 0; if(result.length === limit){ break; } } } return SUPPORT_STORE && enrich ? self.enrich(result) : result; } else{ return res; } }); }; if(SUPPORT_TAGS){ /** * @param {!string} tag * @param {number=} limit * @param {number=} offset * @param {boolean=} enrich * @return {!Promise} */ IdxDB.prototype.tag = function(tag, limit = 0, offset = 0, enrich = false){ const transaction = this.db.transaction("tag" + (this.field ? ":" + this.field : ""), "readonly"); const map = transaction.objectStore("tag" + (this.field ? ":" + this.field : "")); const req = map.get(tag); const self = this; return promisfy(req).then(function(ids){ if(!ids || !ids.length || offset >= ids.length) return []; if(!limit && !offset) return ids; const result = ids.slice(offset, offset + limit); return SUPPORT_STORE && enrich ? self.enrich(result) : result; }); }; } if(SUPPORT_STORE){ /** * @param {SearchResults} ids * @return {!Promise} */ IdxDB.prototype.enrich = function(ids){ if(typeof ids !== "object"){ ids = [ids]; } const transaction = this.db.transaction("reg", "readonly"); const map = transaction.objectStore("reg"); const promises = []; for(let i = 0; i < ids.length; i++){ promises[i] = promisfy(map.get(ids[i])); } return Promise.all(promises).then(function(docs){ for(let i = 0; i < docs.length; i++){ docs[i] = { "id": ids[i], "doc": docs[i] ? JSON.parse(docs[i]) : null }; } return docs; }); }; } /** * @param {number|string} id * @return {!Promise} */ IdxDB.prototype.has = function(id){ const transaction = this.db.transaction("reg", "readonly"); const map = transaction.objectStore("reg"); const req = map.getKey(id); return promisfy(req).then(function(result){ return !!result; }); }; IdxDB.prototype.search = null; // IdxDB.prototype.has = function(ref, key, ctx){ // const transaction = this.db.transaction(ref, "readonly"); // const map = transaction.objectStore(ref); // const req = map.getKey(ctx ? ctx + ":" + key : key); // return promisfy(req); // }; IdxDB.prototype.info = function(){ // todo }; /** * @param {!string} ref * @param {!string} modifier * @param {!Function} task */ IdxDB.prototype.transaction = function(ref, modifier, task){ const key = ref + (ref !== "reg" ? (this.field ? ":" + this.field : "") : ""); /** * @type {IDBObjectStore} */ let store = this.trx[key + ":" + modifier]; if(store) return task.call(this, store); let transaction = this.db.transaction(key, modifier); /** * @type {IDBObjectStore} */ this.trx[key + ":" + modifier] = store = transaction.objectStore(key); const promise = task.call(this, store); this.trx[key + ":" + modifier] = null; return promisfy(transaction).finally(function(){ //transaction = store = null; return promise; }); // return new Promise((resolve, reject) => { // transaction.onerror = (err) => { // transaction.abort(); // transaction = store = null; // reject(err); // //db.close; // }; // transaction.oncomplete = (res) => { // transaction = store = null; // resolve(res || true); // //db.close; // }; // const promise = task.call(this, store); // // transactions can just be used within the same event loop // // the indexeddb is such a stupid tool :( // this.trx[key + ":" + modifier] = null; // return promise; // }); }; IdxDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ /** @dict */ task = tasks[i]; if(task["del"]){ removals.push(task["del"]); } // else if(task["ins"]){ // // } } if(removals.length){ await this.remove(removals); } if(!flexsearch.reg.size){ return; } await this.transaction("map", "readwrite", function(store){ for(const item of flexsearch.map){ const key = item[0]; const value = item[1]; if(!value.length) continue; // if(_replace){ // store.put(value, key); // continue; // } store.get(key).onsuccess = function(){ let result = this.result; let changed; if(result && result.length){ const maxlen = Math.max(result.length, value.length); for(let i = 0, res, val; i < maxlen; i++){ val = value[i]; if(val && val.length){ res = result[i]; if(res && res.length){ for(let j = 0; j < val.length; j++){ res.push(val[j]); } changed = 1; //result[i] = res.concat(val); //result[i] = new Set([...result[i], ...value[i]]); //result[i] = result[i].union(new Set(value[i])); } else{ result[i] = val; changed = 1; //result[i] = new Set(value[i]) } } } } else{ result = value; changed = 1; //result = []; //for(let i = 0; i < value.length; i++){ // if(value[i]) result[i] = new Set(value[i]); //} } changed && store.put(result, key); } } }); await this.transaction("ctx", "readwrite", function(store){ for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const value = item[1]; if(!value.length) continue; // if(_replace){ // store.put(value, ctx_key + ":" + key); // continue; // } store.get(ctx_key + ":" + key).onsuccess = function(){ let result = this.result; let changed; if(result && result.length){ const maxlen = Math.max(result.length, value.length); for(let i = 0, res, val; i < maxlen; i++){ val = value[i]; if(val && val.length){ res = result[i]; if(res && res.length){ for(let j = 0; j < val.length; j++){ res.push(val[j]); } //result[i] = res.concat(val); changed = 1; } else{ result[i] = val; changed = 1; } } } } else{ result = value; changed = 1; } changed && store.put(result, ctx_key + ":" + key); } } } }); if(SUPPORT_STORE && flexsearch.store){ await this.transaction("reg", "readwrite", function(store){ for(const item of flexsearch.store){ const id = item[0]; const doc = item[1]; store.put(typeof doc === "object" ? JSON.stringify(doc) : 1 , id); } }); } else if(!flexsearch.bypass){ await this.transaction("reg", "readwrite", function(store){ for(const id of flexsearch.reg.keys()){ store.put(1, id); } }); } if(SUPPORT_TAGS && flexsearch.tag){ await this.transaction("tag", "readwrite", function(store){ for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; store.get(tag).onsuccess = function(){ let result = this.result; result = result && result.length ? result.concat(ids) : ids; store.put(result, tag); } } }); } // TODO // await this.transaction("cfg", "readwrite", function(store){ // store.put({ // "charset": flexsearch.charset, // "tokenize": flexsearch.tokenize, // "resolution": flexsearch.resolution, // "fastupdate": flexsearch.fastupdate, // "compress": flexsearch.compress, // "encoder": { // "minlength": flexsearch.encoder.minlength // }, // "context": { // "depth": flexsearch.depth, // "bidirectional": flexsearch.bidirectional, // "resolution": flexsearch.resolution_ctx // } // }, "current"); // }); flexsearch.map.clear(); flexsearch.ctx.clear(); if(SUPPORT_TAGS){ flexsearch.tag && flexsearch.tag.clear(); } if(SUPPORT_STORE){ flexsearch.store && flexsearch.store.clear(); } flexsearch.document || flexsearch.reg.clear(); }; /** * @param {IDBCursorWithValue} cursor * @param {Array} ids * @param {boolean=} _tag */ function handle(cursor, ids, _tag){ const arr = cursor.value; let changed; let count = 0; for(let x = 0, result; x < arr.length; x++){ // tags has no resolution layer if((result = _tag ? arr : arr[x])){ for(let i = 0, pos, id; i < ids.length; i++){ id = ids[i]; pos = result.indexOf(id); if(pos >= 0){ changed = 1; if(result.length > 1){ result.splice(pos, 1); } else{ arr[x] = []; break; } } } count += result.length; } if(_tag) break; } if(!count){ cursor.delete(); //store.delete(cursor.key); } else if(changed){ //await new Promise(resolve => { cursor.update(arr);//.onsuccess = resolve; //}); } cursor.continue(); } /** * @param {Array} ids * @return {!Promise} */ IdxDB.prototype.remove = function(ids){ const self = this; if(typeof ids !== "object"){ ids = [ids]; } return /** @type {!Promise} */(Promise.all([ self.transaction("map", "readwrite", function(store){ store.openCursor().onsuccess = function(){ const cursor = this.result; cursor && handle(cursor, ids); }; }), self.transaction("ctx", "readwrite", function(store){ store.openCursor().onsuccess = function(){ const cursor = this.result; cursor && handle(cursor, ids); }; }), SUPPORT_TAGS && self.transaction("tag", "readwrite", function(store){ store.openCursor().onsuccess = function(){ const cursor = this.result; cursor && handle(cursor, ids, /* tag? */ true); }; }), // let filtered = []; self.transaction("reg", "readwrite", function(store){ for(let i = 0; i < ids.length; i++){ store.delete(ids[i]); } // return new Promise(resolve => { // store.openCursor().onsuccess = function(){ // const cursor = this.result; // if(cursor){ // const id = cursor.value; // if(ids.includes(id)){ // filtered.push(id); // cursor.delete(); // } // cursor.continue(); // } // else{ // resolve(); // } // }; // }); }) // ids = filtered; ])); }; /** * @param {IDBRequest|IDBOpenDBRequest} req * @param {Function=} callback * @return {!Promise} */ function promisfy(req, callback){ return new Promise((resolve, reject) => { // oncomplete is used for transaction /** @this {IDBRequest|IDBOpenDBRequest} */ req.onsuccess = req.oncomplete = function(){ callback && callback(this.result); callback = null; resolve(this.result); }; req.onerror = req.onblocked = reject; req = null; }); } ================================================ FILE: src/db/interface.js ================================================ import { PersistentOptions, SearchResults, EnrichedSearchResults } from "../type.js"; /** * @interface */ export default function StorageInterface(name, config){}; // Mandatory Initializer // ------------------------------ // assign store to an index StorageInterface.prototype.mount = async function(index){}; // open connection StorageInterface.prototype.open = async function(){}; // close connection StorageInterface.prototype.close = function(){}; // drop the database (drop tables) StorageInterface.prototype.destroy = async function(){}; // Mandatory Query Tasks // ------------------------------ // transfer all changes of an index to the database StorageInterface.prototype.commit = async function(index/*, _replace, _append*/){}; /** * get results of a term "key" with optional context "ctx" * @param {!string} key * @param {string=} ctx * @param {number=} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @return {!Promise} */ StorageInterface.prototype.get = async function(key, ctx, limit, offset, resolve, enrich){}; /** * get documents stored in index (enrich result) * @param {SearchResults} ids * @return {!Promise} */ StorageInterface.prototype.enrich = async function(ids){}; // check if id exists on a specific index StorageInterface.prototype.has = async function(id){}; // delete one id or multiple ids on a specific index StorageInterface.prototype.remove = async function(ids){}; // clear all data (truncate) StorageInterface.prototype.clear = async function(){}; // Optional Methods // ------------------------------ /** * Perform the query intersection on database side * @type {Function|null} */ StorageInterface.prototype.search = async function(index, query, limit, offset, suggest, resolve, enrich){}; /** * Give some information about the storage * @type {Function|null} */ StorageInterface.prototype.info = async function(){}; ================================================ FILE: src/db/mongodb/index.js ================================================ import { MongoClient } from "mongodb"; const defaults = { host: "localhost", port: "27017", user: null, pass: null }; const VERSION = 1; const fields = ["map", "ctx", "tag", "reg", "cfg"]; import StorageInterface from "../interface.js"; import { toArray } from "../../common.js"; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } let CLIENT; let Index = Object.create(null); /** * @constructor * @implements StorageInterface */ export default function MongoDB(name, config = {}){ if(!this || this.constructor !== MongoDB){ return new MongoDB(name, config); } if(typeof name === "object"){ config = name; name = name.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } this.id = "flexsearch" + (name ? "-" + sanitize(name) : ""); this.field = config.field ? "-" + sanitize(config.field) : ""; this.type = config.type || ""; this.db = config.db || Index[this.id] || CLIENT || null; this.trx = false; this.support_tag_search = true; Object.assign(defaults, config); this.db && delete defaults.db; }; // MongoDB.mount = function(flexsearch){ // return new this().mount(flexsearch); // }; MongoDB.prototype.mount = function(flexsearch){ //if(flexsearch.constructor === Document){ if(flexsearch.index){ return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; async function createCollection(db, ref, field){ switch(ref){ case "map": await db.createCollection("map" + field); await db.collection("map" + field).createIndex({ key: 1 }); await db.collection("map" + field).createIndex({ id: 1 }); break; case "ctx": await db.createCollection("ctx" + field); await db.collection("ctx" + field).createIndex({ ctx: 1, key: 1 }); await db.collection("ctx" + field).createIndex({ id: 1 }); break; case "tag": await db.createCollection("tag" + field); await db.collection("tag" + field).createIndex({ tag: 1 }); await db.collection("tag" + field).createIndex({ id: 1 }); break; case "reg": await db.createCollection("reg"); await db.collection("reg").createIndex({ id: 1 }); break; case "cfg": await db.createCollection("cfg" + field); } } MongoDB.prototype.open = async function(){ if(!this.db){ if(!(this.db = Index[this.id])){ if(!(this.db = CLIENT)){ let url = defaults.url; if(!url){ url = defaults.user ? `mongodb://${ defaults.user }:${ defaults.pass }@${ defaults.host }:${ defaults.port }` : `mongodb://${ defaults.host }:${ defaults.port }`; } this.db = CLIENT = new MongoClient(url); await this.db.connect(); } } } if(this.db.db){ this.db = Index[this.id] = this.db.db(this.id); } const collections = await this.db.listCollections().toArray(); for(let i = 0; i < fields.length; i++){ let found = false; for(let j = 0; j < collections.length; j++){ if(collections[j].name === fields[i] + (fields[i] !== "reg" ? this.field : "")){ found = true; break; } } if(!found){ await createCollection(this.db, fields[i], this.field); } } return this.db; }; MongoDB.prototype.close = function(){ //CLIENT && CLIENT.close(); this.db = CLIENT = null; Index[this.id] = null; return this; }; MongoDB.prototype.destroy = function(){ return Promise.all([ this.db.dropCollection("map" + this.field), this.db.dropCollection("ctx" + this.field), this.db.dropCollection("tag" + this.field), this.db.dropCollection("cfg" + this.field), this.db.dropCollection("reg") ]); }; async function clear(ref){ await this.db.dropCollection(ref); await createCollection(this.db, ref, this.field); } MongoDB.prototype.clear = function(){ return Promise.all([ clear.call(this, "map" + this.field), clear.call(this, "ctx" + this.field), clear.call(this, "tag" + this.field), clear.call(this, "cfg" + this.field), clear.call(this, "reg") ]); }; function create_result(rows, resolve, enrich){ const _id = rows[0] && typeof rows[0]._id !== "undefined"; if(resolve){ if(!enrich || _id) for(let i = 0, row; i < rows.length; i++){ row = rows[i]; if(enrich){ const id = row._id; delete row._id; row.id = id; } else{ rows[i] = _id ? row._id : row.id; } } return rows; } else{ const arr = []; for(let i = 0, row, res; i < rows.length; i++){ row = rows[i]; res = row.res; (arr[res] || (arr[res] = [])).push( _id ? row._id : row.id ); } return arr; } } MongoDB.prototype.get = async function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false, tags){ let rows; let params = ctx ? { ctx, key } : { key }; if(!enrich && !tags){ const stmt = { projection: { _id: 0, res: 1, id: 1 } }; limit && (stmt.limit = limit); offset && (stmt.skip = offset); rows = await this.db.collection((ctx ? "ctx" : "map") + this.field) .find(params, stmt) .toArray(); } else{ const project = { _id: 0, id: 1 }; const stmt = [ { $match: params } ]; if(!resolve){ project["res"] = 1; } if(enrich){ project["doc"] = "$doc.doc"; stmt.push( { $lookup: { from: "reg", localField: "id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: true } } ); } if(tags){ const match = {}; for(let i = 0, count = 1; i < tags.length; i += 2){ project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push( { $lookup: { from: "tag-" + sanitize(tags[i]), localField: "id", foreignField: "id", as: "tag" + count } } ); count++; } stmt.push( { $project: project }, { $match: match }, { $project: { id: 1, doc: 1 } } ); } else{ stmt.push( { $project: project } ); } stmt.push( { $sort: { res: 1 } } ); limit && stmt.push( { $limit: limit } ); offset && stmt.push( { $skip: offset} ); rows = []; const result = await this.db.collection((ctx ? "ctx" : "map") + this.field).aggregate(stmt); while(true/*await rows.hasNext()*/){ const row = await result.next(); if(row) rows.push(row) else break; } } return create_result(rows, resolve, enrich); }; MongoDB.prototype.tag = async function(tag, limit = 0, offset = 0, enrich = false){ let rows; if(!enrich){ const stmt = { projection: { _id: 0, id: 1 } }; limit && (stmt.limit = limit); offset && (stmt.skip = offset); rows = await this.db.collection("tag" + this.field) .find({ tag }, stmt) .toArray(); } else{ const stmt = [ { $match: { tag } } ]; limit && stmt.push( { $limit: limit } ); offset && stmt.push( { $skip: offset} ); stmt.push( { $lookup: { from: "reg", localField: "id", foreignField: "id", as: "doc" } }, { $project: { _id: 0, id: 1, doc: "$doc.doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: true } } ); rows = []; const result = await this.db.collection("tag" + this.field).aggregate(stmt); while(true/*await rows.hasNext()*/){ const row = await result.next(); if(row) rows.push(row) else break; } } create_result(rows, true, enrich); }; MongoDB.prototype.enrich = function(ids){ if(typeof ids !== "object"){ ids = [ids]; } return this.db.collection("reg") .find({ id: { $in: ids } }, { projection: { _id: 0, id: 1, doc: 1 } }) .toArray(); }; MongoDB.prototype.has = function(id){ return this.db.collection("reg").countDocuments({ id }, { limit: 1 }).then(function(result){ return !!result; }); }; MongoDB.prototype.search = async function(flexsearch, query, limit = 100, offset = 0, suggest = false, resolve = true, enrich = false, tags){ let result = [], rows; if(query.length > 1 && flexsearch.depth){ let params = []; let keyword = query[0]; let term; for(let i = 1; i < query.length; i++){ term = query[i]; const swap = flexsearch.bidirectional && (term > keyword); params.push({ ctx: swap ? term : keyword, key: swap ? keyword : term }); keyword = term; } const project = { _id: 1 }; if(!resolve) project["res"] = 1; if(enrich) project["doc"] = 1; const stmt = [ { $match: { $or: params } }, { $group: { _id: "$id", count: { $sum: 1 }, res: suggest ? { $sum: "$res" } : { $sum /*$min*/: "$res" } } } ]; suggest || stmt.push( { $match: { count: query.length - 1 } } ); if(enrich){ project["doc"] = "$doc.doc"; stmt.push( { $lookup: { from: "reg", localField: "_id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: true } } ); } if(tags){ const match = {}; for(let i = 0, count = 1; i < tags.length; i += 2){ project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push( { $lookup: { from: "tag-" + sanitize(tags[i]), localField: "_id", foreignField: "id", as: "tag" + count } } ); count++; } stmt.push( { $match: match } ); } stmt.push( { $sort: suggest ? { count: -1, res: 1} : { res: 1 } } ); limit && stmt.push( { $limit: limit } ); offset && stmt.push( { $skip: offset} ); stmt.push( { $project: project } ); rows = await this.db.collection("ctx" + this.field).aggregate(stmt); } else{ const project = { _id: 1 }; if(!resolve) project["res"] = 1; if(enrich) project["doc"] = 1; const stmt = [ { $match: { key: { $in: query } } }, { $group: { _id: "$id", count: { $sum: 1 }, res: suggest ? { $sum: "$res" } : { $sum /*$min*/: "$res" } } } ]; suggest || stmt.push( { $match: { count: query.length } } ); if(enrich){ project["doc"] = "$doc.doc"; stmt.push( { $lookup: { from: "reg", localField: "_id", foreignField: "id", as: "doc" } }, { $unwind: { path: "$doc", preserveNullAndEmptyArrays: true } } ); } if(tags){ const match = {}; for(let i = 0, count = 1; i < tags.length; i += 2){ project["tag" + count] = "$tag" + count + ".tag"; match["tag" + count] = tags[i + 1]; stmt.push( { $lookup: { from: "tag-" + sanitize(tags[i]), localField: "_id", foreignField: "id", as: "tag" + count } } ); count++; } stmt.push( { $match: match } ); } stmt.push( { $sort: suggest ? { count: -1, res: 1 } : { res: 1 } } ); limit && stmt.push( { $limit: limit } ); offset && stmt.push( { $skip: offset} ); stmt.push( { $project: project } ); rows = await this.db.collection("map" + this.field).aggregate(stmt); } while(true/*await rows.hasNext()*/) { const row = await rows.next(); if(row){ if(resolve && enrich){ row.id = row._id; delete row._id; } result.push(row); } else break; } return create_result(result, resolve, enrich); } MongoDB.prototype.info = function(){ // todo }; MongoDB.prototype.transaction = function(task){ // not supported return task.call(this); }; MongoDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ /** @dict */ task = tasks[i]; if(task["del"]){ removals.push(task["del"]); } else if(task["ins"]){ } } if(removals.length){ await this.remove(removals); } if(!flexsearch.reg.size){ return; } const promises = []; if(flexsearch.map.size){ let data = []; for(const item of flexsearch.map){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ this.type || (this.type = typeof ids[0]); for(let j = 0; j < ids.length; j++){ data.push({ key: key, res: i, id: ids[j] }); } } } } if(data.length){ promises.push( this.db.collection("map" + this.field).insertMany(data) ); } } if(flexsearch.ctx.size){ let data = []; for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ for(let j = 0; j < ids.length; j++){ data.push({ ctx: ctx_key, key: key, res: i, id: ids[j] }); } } } } } if(data.length){ promises.push( this.db.collection("ctx" + this.field).insertMany(data) ); } } if(flexsearch.tag){ let data = []; for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; for(let j = 0; j < ids.length; j++){ data.push({ tag, id: ids[j] }); } } if(data.length){ promises.push( this.db.collection("tag" + this.field).insertMany(data) ); } } let data = []; if(flexsearch.store){ for(const item of flexsearch.store.entries()){ const id = item[0]; const doc = item[1]; data.push({ id, doc }); } } else if(!flexsearch.bypass){ for(const id of flexsearch.reg.keys()){ data.push({ id }); } } if(data.length){ promises.push( this.db.collection("reg").insertMany(data) ); } promises.length && await Promise.all(promises); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); // TODO // await this.db.collection("cfg" + this.field).insertOne({ // "encode": typeof flexsearch.encode === "string" ? flexsearch.encode : "", // "charset": typeof flexsearch.charset === "string" ? flexsearch.charset : "", // "tokenize": flexsearch.tokenize, // "resolution": flexsearch.resolution, // "minlength": flexsearch.minlength, // "optimize": flexsearch.optimize, // "fastupdate": flexsearch.fastupdate, // "encoder": flexsearch.encoder, // "context": { // "depth": flexsearch.depth, // "bidirectional": flexsearch.bidirectional, // "resolution": flexsearch.resolution_ctx // } // }); }; MongoDB.prototype.remove = function(ids){ if(!ids && ids !== 0) return; if(typeof ids !== "object"){ ids = [ids]; } // if(this.type !== "string" && typeof ids[0] !== "number"){ // ids = ids.map(item => parseInt(item, 10)); // } return Promise.all([ this.db.collection("map" + this.field).deleteMany({ "id": { "$in": ids }}), this.db.collection("ctx" + this.field).deleteMany({ "id": { "$in": ids }}), this.db.collection("tag" + this.field).deleteMany({ "id": { "$in": ids }}), this.db.collection("reg").deleteMany({ "id": { "$in": ids }}) ]); }; ================================================ FILE: src/db/mongodb/package.json ================================================ { "public": true, "preferGlobal": false, "name": "flexsearch-mongodb", "version": "0.1.0", "main": "index.js", "dependencies": { "mongodb": "^6.13.0" } } ================================================ FILE: src/db/postgres/index.js ================================================ import pg_promise from "pg-promise"; import StorageInterface from "../interface.js"; import { concat, toArray } from "../../common.js"; const defaults = { schema: "flexsearch", user: "postgres", pass: "postgres", name: "postgres", host: "localhost", port: "5432" }; const pgp = pg_promise(/*{ noWarnings: true }*/); const VERSION = 1; const MAXIMUM_QUERY_VARS = 16000; const fields = ["map", "ctx", "reg", "tag", "cfg"]; const types = { "text": "text", "char": "text", "varchar": "text", "string": "text", "number": "int", "numeric": "int", "integer": "int", "smallint": "int", "tinyint": "int", "mediumint": "int", "int": "int", "int8": "int", "uint8": "int", "int16": "int", "uint16": "int", "int32": "int", "uint32": "bigint", "int64": "bigint", "bigint": "bigint" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } let DB, TRX; /** * @constructor * @implements StorageInterface */ export default function PostgresDB(name, config = {}){ if(!this || this.constructor !== PostgresDB){ return new PostgresDB(name, config); } if(typeof name === "object"){ config = name; name = config.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } this.id = (config.schema ? sanitize(config.schema) : defaults.schema) + (name ? "_" + sanitize(name) : ""); this.field = config.field ? "_" + sanitize(config.field) : ""; this.type = config.type ? types[config.type.toLowerCase()] : "text"; this.support_tag_search = true; if(!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); this.db = DB || (DB = config.db || null); Object.assign(defaults, config); this.db && delete defaults.db; }; PostgresDB.prototype.mount = function(flexsearch){ //if(flexsearch.constructor === Document){ if(flexsearch.index){ return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; PostgresDB.prototype.open = async function(){ if(!this.db) { this.db = DB || ( DB = pgp(`postgres://${defaults.user}:${encodeURIComponent(defaults.pass)}@${defaults.host}:${defaults.port}/${defaults.name}`) ); } const exist = await this.db.oneOrNone(` SELECT EXISTS ( SELECT 1 FROM information_schema.schemata WHERE schema_name = '${this.id}' ); `); if(!exist || !exist.exists){ await this.db.none(`CREATE SCHEMA IF NOT EXISTS ${ this.id };`); } for(let i = 0; i < fields.length; i++){ const exist = await this.db.oneOrNone(` SELECT EXISTS ( SELECT 1 FROM pg_tables WHERE schemaname = '${this.id}' AND tablename = '${fields[i] + (fields[i] !== "reg" ? this.field : "")}' ); `); if(exist && exist.exists) continue; const type = this.type === "text" ? "varchar(128)" : this.type; switch(fields[i]){ case "map": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.map${this.field}( key varchar(128) NOT NULL, res smallint NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_map_index${this.field} ON ${this.id}.map${this.field} (key); CREATE INDEX IF NOT EXISTS ${this.id}_map_id${this.field} ON ${this.id}.map${this.field} (id); `); break; case "ctx": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.ctx${this.field}( ctx varchar(128) NOT NULL, key varchar(128) NOT NULL, res smallint NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_ctx_index${this.field} ON ${this.id}.ctx${this.field} (ctx, key); CREATE INDEX IF NOT EXISTS ${this.id}_ctx_id${this.field} ON ${this.id}.ctx${this.field} (id); `); break; case "tag": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.tag${this.field}( tag varchar(128) NOT NULL, id ${type} NOT NULL ); CREATE INDEX IF NOT EXISTS ${this.id}_tag_index${this.field} ON ${this.id}.tag${this.field} (tag); CREATE INDEX IF NOT EXISTS ${this.id}_tag_id${this.field} ON ${this.id}.tag${this.field} (id); `); break; case "reg": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.reg( id ${type} NOT NULL CONSTRAINT ${this.id}_reg_pk PRIMARY KEY, doc text DEFAULT NULL ); `).catch(e => { // document indexes will try to create same registry table // and the "IF NOT EXISTS" did not apply on parallel }); break; case "cfg": await this.db.none(` CREATE TABLE IF NOT EXISTS ${this.id}.cfg${this.field}( cfg text NOT NULL ); `); break; } } return this.db; }; PostgresDB.prototype.close = function(){ //DB && DB.close && DB.close(); this.db = /*DB =*/ null; return this; }; PostgresDB.prototype.destroy = function(){ return this.db.none(` DROP TABLE IF EXISTS ${this.id}.map${this.field}; DROP TABLE IF EXISTS ${this.id}.ctx${this.field}; DROP TABLE IF EXISTS ${this.id}.tag${this.field}; DROP TABLE IF EXISTS ${this.id}.cfg${this.field}; DROP TABLE IF EXISTS ${this.id}.reg; `); }; PostgresDB.prototype.clear = function(){ return this.db.none(` TRUNCATE TABLE ${this.id}.map${ this.field }; TRUNCATE TABLE ${this.id}.ctx${ this.field }; TRUNCATE TABLE ${this.id}.tag${ this.field }; TRUNCATE TABLE ${this.id}.cfg${ this.field }; TRUNCATE TABLE ${this.id}.reg; `); }; function create_result(rows, resolve, enrich){ if(resolve){ for(let i = 0; i < rows.length; i++){ if(enrich){ if(rows[i].doc){ rows[i].doc = JSON.parse(rows[i].doc); } } else{ rows[i] = rows[i].id; } } return rows; } else{ const arr = []; for(let i = 0, row; i < rows.length; i++){ row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id ); } return arr; } } PostgresDB.prototype.get = function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false, tags){ let rows; let stmt = ''; let params = ctx ? [ctx, key] : [key]; let table = this.id + (ctx ? ".ctx" : ".map") + this.field; if(tags){ for(let i = 0, count = params.length + 1; i < tags.length; i+=2){ stmt += ` AND ${ table }.id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = $${ count++ })`; params.push(tags[i + 1]); } } if(ctx){ rows = this.db.any(` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE ctx = $1 AND key = $2 ${stmt} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, params ); } else{ rows = this.db.any(` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE key = $1 ${stmt} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, params ); } return rows.then(function(rows){ return create_result(rows, resolve, enrich); }); }; PostgresDB.prototype.tag = function(tag, limit = 0, offset = 0, enrich = false){ const table = this.id + ".tag" + this.field; const promise = this.db.any(` SELECT ${ table }.id ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN ${ this.id }.reg ON ${ this.id }.reg.id = ${ table }.id ` : "" } WHERE tag = $1 ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" }`, [tag] ); enrich || promise.then(function(rows){ return create_result(rows, true, false); }); return promise; }; PostgresDB.prototype.enrich = async function(ids){ let result = []; if(typeof ids !== "object"){ ids = [ids]; } for(let count = 0; count < ids.length;){ const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; let stmt = ""; for(let i = 1; i <= chunk.length; i++){ stmt += (stmt ? "," : "") + "$" + i; } const res = await this.db.any(` SELECT id, doc FROM ${ this.id }.reg WHERE id IN (${ stmt })`, ids ); if(res && res.length){ for(let i = 0, doc; i < res.length; i++){ if((doc = res[i].doc)){ res[i].doc = JSON.parse(doc); } } result.push(res); } } return result.length === 1 ? result[0] : result.length > 1 ? concat(result) : result; }; PostgresDB.prototype.has = function(id){ return this.db.oneOrNone("SELECT EXISTS(SELECT 1 FROM " + this.id + ".reg WHERE id = $1)", [id]).then(function(result){ return !!(result && result.exists); }); }; PostgresDB.prototype.search = function(flexsearch, query, limit = 100, offset = 0, suggest = false, resolve = true, enrich = false, tags){ let rows; if(query.length > 1 && flexsearch.depth){ let where = ""; let params = []; let keyword = query[0]; let term; let count = 1; // variant new for(let i = 1; i < query.length; i++){ term = query[i]; const swap = flexsearch.bidirectional && (term > keyword); where += (where ? " OR " : "") + `(ctx = $${ count++ } AND key = $${ count++ })` params.push(swap ? term : keyword, swap ? keyword : term); keyword = term; } if(tags){ where = "(" + where + ")"; for(let i = 0; i < tags.length; i+=2){ where += ` AND id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = $${ count++ })`; params.push(tags[i + 1]); } } rows = this.db.any(` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" /*"MIN"*/ }(res) as res FROM ${ this.id }.ctx${ this.field } WHERE ${ where } GROUP BY id ) as r ${ enrich ? ` LEFT JOIN ${ this.id }.reg ON ${ this.id }.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + (query.length - 1) } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params); // variant 1 // for(let i = 1, count = 1; i < query.length; i++){ // where += (where ? " UNION " : "") + ` // SELECT id, res // FROM ${this.id}.ctx${this.field} // WHERE ctx = $${count++} AND key = $${count++} // `; // term = query[i]; // const swap = flexsearch.bidirectional && (term > keyword); // params.push( // swap ? term : keyword, // swap ? keyword : term // ); // keyword = term; // } // // rows = await db.any(` // SELECT id, res // FROM ( // SELECT id, ${suggest ? "SUM" : "MIN"}(res) as res, count(*) as count // FROM (${where}) as t // GROUP BY id // ORDER BY ${suggest ? "count DESC, res" : "res"} // LIMIT ${limit} // OFFSET ${offset} // ) as r // ${suggest ? "" : "WHERE count = " + (query.length - 1)} // `, params); } else{ let where = ""; let count = 1; let query_length = query.length; for(let i = 0; i < query_length; i++){ where += (where ? "," : "") + "$" + count++; } where = "key " + (query_length > 1 ? "IN (" + where + ")" : "= " + where ); if(tags){ where = "(" + where + ")"; for(let i = 0; i < tags.length; i+=2){ where += ` AND id IN (SELECT id FROM ${ this.id }.tag_${ sanitize(tags[i]) } WHERE tag = $${ count++ })`; query.push(tags[i + 1]); } } rows = this.db.any(` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" /*"MIN"*/ }(res) as res FROM ${ this.id }.map${ this.field } WHERE ${ where } GROUP BY id ) as r ${ enrich ? ` LEFT JOIN ${ this.id }.reg ON ${ this.id }.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + query_length } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, query); // variant 1 // for(let i = 1; i <= query.length; i++){ // where += (where ? " UNION " : "") + ` // SELECT id, res // FROM ${ this.id }.map${ this.field } // WHERE key = $${i} // `; // } // rows = await db.any(` // SELECT id, res // FROM ( // SELECT id, ${suggest ? "SUM" : "MIN"}(res) as res, count(*) as count // FROM (${where}) as t // GROUP BY id // ORDER BY ${suggest ? "count DESC, res" : "res"} // LIMIT ${limit} // OFFSET ${offset} // ) as r // ${ suggest ? "" : "WHERE count = " + query.length } // `, query); // variant 2 // for(let i = 1; i <= query.length; i++){ // where += (where ? " AND EXISTS " : "") + `(SELECT FROM ${this.id}.map${this.field} as d WHERE d.id = t.id AND d.key = $` + i + ")"; // } // rows = await db.any(` // SELECT t.id, min(t.res) // FROM ${this.id}.map${this.field} AS t // WHERE EXISTS ${where} // GROUP BY t.id // LIMIT ${limit || 100} // OFFSET ${offset || 0} // `, query); // variant 3 // for(let i = 1; i <= query.length; i++){ // where += (where ? " INTERSECT " : "") + `SELECT id FROM ${this.id}.map${this.field} WHERE key = $` + i; // } // rows = await db.any(` // WITH filtering (id) AS(${where}) // SELECT t.id, min(t.res) // FROM ${this.id}.map${this.field} AS t // JOIN filtering USING (id) // GROUP BY t.id // LIMIT ${limit || 100} // OFFSET ${offset || 0} // `, query); // variant 4 // for(let i = 1; i <= query.length; i++){ // where += (where ? " INTERSECT " : "") + `SELECT id FROM ${this.id}.map${this.field} WHERE key = $` + i; // } // rows = await db.any(` // SELECT id, min(res) // FROM ${this.id}.map${this.field} // WHERE id IN (${where}) // GROUP BY id // LIMIT ${limit || 100} // OFFSET ${offset || 0} // `, query); } return rows.then(function(rows){ return create_result(rows, resolve, enrich); }); }; PostgresDB.prototype.info = function(){ // todo }; // PostgresDB.prototype.transaction = async function(task){ // const self = this; // if(TRX){ // return TRX.then(function(){ // return self.transaction(task); // //task.call(self, TRX); // }); // } // TRX = await this.db.tx(async function(trx){ // await task.call(self, trx); // }); // TRX = null; // }; PostgresDB.prototype.transaction = function(task){ const self = this; return this.db.tx(function(trx){ return task.call(self, trx); }); }; // PostgresDB.prototype.transaction = async function(task){ // if(TRX){ // return await task.call(this, TRX); // } // const self = this; // return this.db.tx(async function(trx){ // await task.call(self, TRX = trx); // TRX = null; // }); // }; PostgresDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ /** @dict */ task = tasks[i]; if(task["del"]){ removals.push(task["del"]); } else if(task["ins"]){ } } if(removals.length){ await this.remove(removals); } if(!flexsearch.reg.size){ return; } await this.transaction(function(trx){ const batch = []; // Datastore + Registry if(flexsearch.store){ let data = []; let stmt = new pgp.helpers.ColumnSet(["id", "doc"],{ table: this.id + ".reg" }); for(const item of flexsearch.store.entries()){ const id = item[0]; const doc = item[1]; // const migration = checkMigration.call(this, id); // migration && await migration; data.push({ id, doc: doc && JSON.stringify(doc) }); if(data.length === MAXIMUM_QUERY_VARS){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); data = []; } } if(data.length){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); } // while(data.length){ // let next; // if(data.length > MAXIMUM_QUERY_VARS){ // next = data.slice(MAXIMUM_QUERY_VARS); // data = data.slice(0, MAXIMUM_QUERY_VARS); // } // let insert = pgp.helpers.insert(data, stmt); // trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')); // if(next) data = next; // else break; // } } // Registry Only else if(!flexsearch.bypass){ let data = []; let stmt = new pgp.helpers.ColumnSet(["id"],{ table: this.id + ".reg" }); for(const id of flexsearch.reg.keys()){ // const migration = checkMigration.call(this, id); // migration && await migration; data.push({ id }); if(data.length === MAXIMUM_QUERY_VARS){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); data = []; } } if(data.length){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); } } // Default Index if(flexsearch.map.size){ let data = []; let stmt = new pgp.helpers.ColumnSet(["key", "res", "id"], { table: this.id + ".map" + this.field }); for(const item of flexsearch.map){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ //this.type || (this.type = typeof ids[0]); // let stmt = "($1,$2,$3)"; // let params = [key, i, ids[0]]; for(let j = 0; j < ids.length; j++){ // stmt += ",($1,$2,$3)"; // params.push(key, i, ids[j]); //trx.none(`INSERT INTO ${config.schema}.map${self.field} (key, res, id) VALUES ($1,$2,$3)`, [key, i, ids[j]]); data.push({ key: key, res: i, id: ids[j] }); if(data.length === MAXIMUM_QUERY_VARS){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); data = []; } } } } } if(data.length){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); } } // Context Index if(flexsearch.ctx.size){ let data = []; let stmt = new pgp.helpers.ColumnSet(["ctx", "key", "res", "id"], { table: this.id + ".ctx" + this.field }); for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ // let stmt = "(?,?,?)"; // let params = [ctx_key + ":" + key, i, ids[0]]; for(let j = 0; j < ids.length; j++){ // stmt += ",(?,?,?)"; // params.push(ctx_key + ":" + key, i, ids[j]); //trx.none("INSERT INTO " + config.schema + ".ctx" + self.field + " (ctx, key, res, id) VALUES ($1,$2,$3,$4)", [ctx_key, key, i, ids[j]]); data.push({ ctx: ctx_key, key: key, res: i, id: ids[j] }); if(data.length === MAXIMUM_QUERY_VARS){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); data = []; } } } } } } if(data.length){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); } } // Tag Index if(flexsearch.tag){ let data = []; let stmt = new pgp.helpers.ColumnSet(["tag", "id"],{ table: this.id + ".tag" + this.field }); for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; for(let j = 0; j < ids.length; j++){ data.push({ tag, id: ids[j] }); if(data.length === MAXIMUM_QUERY_VARS){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); data = []; } } } if(data.length){ let insert = pgp.helpers.insert(data, stmt); batch.push( trx.none(insert.replace(/^(insert into )"([^"]+)"/, '$1 $2')) ); } } // Field Configuration // TODO // trx.none("INSERT INTO " + this.id + ".cfg" + this.field + " (cfg) VALUES ($1)", [ // JSON.stringify({ // "encode": typeof flexsearch.encode === "string" ? flexsearch.encode : "", // "charset": typeof flexsearch.charset === "string" ? flexsearch.charset : "", // "tokenize": flexsearch.tokenize, // "resolution": flexsearch.resolution, // "minlength": flexsearch.minlength, // "optimize": flexsearch.optimize, // "fastupdate": flexsearch.fastupdate, // "encoder": flexsearch.encoder, // "context": { // "depth": flexsearch.depth, // "bidirectional": flexsearch.bidirectional, // "resolution": flexsearch.resolution_ctx // } // }) // ]); //return Promise.all(batch); if(batch.length){ return trx.batch(batch); } }); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; PostgresDB.prototype.remove = function(ids){ if(!ids && ids !== 0){ return; } if(typeof ids !== "object"){ ids = [ids]; } if(!ids.length){ return; } // ids = [ids]; // return this.db.none( // "DELETE FROM " + this.id + ".map" + this.field + " WHERE id = ANY ($1);" + // "DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id = ANY ($1);" + // "DELETE FROM " + this.id + ".tag" + this.field + " WHERE id = ANY ($1);" + // "DELETE FROM " + this.id + ".reg WHERE id = ANY ($1);", [ids] // ); // ids = [ids]; // return Promise.all([ // this.db.none({ text: "DELETE FROM " + this.id + ".map" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), // this.db.none({ text: "DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), // this.db.none({ text: "DELETE FROM " + this.id + ".tag" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), // this.db.none({ text: "DELETE FROM " + this.id + ".reg WHERE id = ANY ($1)", rowMode: "array" }, ids) // ]); return this.transaction(function(trx){ //console.log("remove:", ids); // ids = [ids]; // trx.none({ text: "DELETE FROM " + this.id + ".map" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids); // trx.none({ text: "DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids); // trx.none({ text: "DELETE FROM " + this.id + ".tag" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids); // trx.none({ text: "DELETE FROM " + this.id + ".reg WHERE id = ANY ($1)", rowMode: "array" }, ids); // ids = [ids]; // trx.none("DELETE FROM " + this.id + ".map" + this.field + " WHERE id = ANY ($1)", ids); // trx.none("DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id = ANY ($1)", ids); // trx.none("DELETE FROM " + this.id + ".tag" + this.field + " WHERE id = ANY ($1)", ids); // trx.none("DELETE FROM " + this.id + ".reg WHERE id = ANY ($1)", ids); // return; // return trx.none( // "DELETE FROM " + this.id + ".map" + this.field + " WHERE id = ANY ($1);" + // "DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id = ANY ($1);" + // "DELETE FROM " + this.id + ".tag" + this.field + " WHERE id = ANY ($1);" + // "DELETE FROM " + this.id + ".reg WHERE id = ANY ($1);", [ids] // ); // while(ids.length){ // let next; // let stmt = ""; // let chunk = 0; // let migration; // for(let i = 0; i < ids.length; i++){ // stmt += (stmt ? "," : "") + "$" + (i + 1); // + "::text"; // // maximum count of variables supported // if(++chunk === MAXIMUM_QUERY_VARS){ // next = ids.slice(MAXIMUM_QUERY_VARS); // ids = ids.slice(0, MAXIMUM_QUERY_VARS); // break; // } // } // // trx.batch([ // trx.none("DELETE FROM " + this.id + ".map" + this.field + " WHERE id IN (" + stmt + ")", ids), // trx.none("DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id IN (" + stmt + ")", ids), // trx.none("DELETE FROM " + this.id + ".tag" + this.field + " WHERE id IN (" + stmt + ")", ids), // trx.none("DELETE FROM " + this.id + ".reg WHERE id IN (" + stmt + ")", ids) // ]); // // // trx.none( // // "DELETE FROM " + this.id + ".map" + this.field + " WHERE id IN (" + stmt + ");" + // // "DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id IN (" + stmt + ");" + // // "DELETE FROM " + this.id + ".tag" + this.field + " WHERE id IN (" + stmt + ");" + // // "DELETE FROM " + this.id + ".reg WHERE id IN (" + stmt + ");", ids // // ); // // if(next) ids = next; // else break; // } ids = [ids]; return trx.batch([ trx.none({ text: "DELETE FROM " + this.id + ".map" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".tag" + this.field + " WHERE id = ANY ($1)", rowMode: "array" }, ids), trx.none({ text: "DELETE FROM " + this.id + ".reg WHERE id = ANY ($1)", rowMode: "array" }, ids) ]); // ids = [ids]; // return trx.batch([ // trx.none("DELETE FROM " + this.id + ".map" + this.field + " WHERE id = ANY ($1)", ids), // trx.none("DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id = ANY ($1)", ids), // trx.none("DELETE FROM " + this.id + ".tag" + this.field + " WHERE id = ANY ($1)", ids), // trx.none("DELETE FROM " + this.id + ".reg WHERE id = ANY ($1)", ids) // ]); // return trx.batch([ // trx.none("DELETE FROM " + this.id + ".map" + this.field + " WHERE id IN ($1:csv)", [ids]), // trx.none("DELETE FROM " + this.id + ".ctx" + this.field + " WHERE id IN ($1:csv)", [ids]), // trx.none("DELETE FROM " + this.id + ".tag" + this.field + " WHERE id IN ($1:csv)", [ids]), // trx.none("DELETE FROM " + this.id + ".reg WHERE id IN ($1:csv)", [ids]) // ]); // const type = self.type === "text" ? "text" : "int"; // return trx.batch([ // trx.none("DELETE FROM " + this.id + ".map" + self.field + " WHERE id = ANY($1::" + type + "[])", [ids]), // trx.none("DELETE FROM " + this.id + ".ctx" + self.field + " WHERE id = ANY($1::" + type + "[])", [ids]), // trx.none("DELETE FROM " + this.id + ".reg WHERE id = ANY($1::" + type + "[])", [ids]) // ]); // return trx.batch([ // trx.none("DELETE FROM " + this.id + ".map" + self.field + " WHERE id = ANY($1)", [ids]), // trx.none("DELETE FROM " + this.id + ".ctx" + self.field + " WHERE id = ANY($1)", [ids]), // trx.none("DELETE FROM " + this.id + ".reg WHERE id = ANY($1)", [ids]) // ]); }); }; // if(this.type === "int" && typeof ids[0] === "string"){ // ids = ids.map(item => parseInt(item, 10)); // } // if(this.type === "bigint" && typeof ids[0] === "string"){ // ids = ids.map(item => BigInt(item)); // } // if(this.type === "string" && typeof ids[0] === "number"){ // ids = ids.map(item => item + ""); // } // this.type !== "string" && checkMigration.call(this, ids[0]); // function checkMigration(id){ // if(this.type !== "string"){ // if(typeof id === "object"){ // id = id[0]; // } // if(typeof id === "string"){ // this.type = "string"; // return upgradeTextId.call(this); // } // if(this.type !== "bigint"){ // if(id > 2 ** 31 - 1){ // this.type = "bigint"; // return upgradeBigIntId.call(this); // } // } // } // } // // function upgradeTextId(){ // return this.db.none(` // ALTER TABLE ${this.id}.map${this.field} // ALTER COLUMN id type varchar(128) // USING id::text; // ALTER TABLE ${this.id}.ctx${this.field} // ALTER COLUMN id type varchar(128) // USING id::text; // ALTER TABLE ${this.id}.tag${this.field} // ALTER COLUMN id type varchar(128) // USING id::text; // ALTER TABLE ${this.id}.reg // ALTER COLUMN id type varchar(128) // USING id::text; // `); // } // // function upgradeBigIntId(){ // return this.db.none(` // ALTER TABLE ${this.id}.map${this.field} // ALTER COLUMN id type bigint // USING id::bigint; // ALTER TABLE ${this.id}.ctx${this.field} // ALTER COLUMN id type bigint // USING id::bigint; // ALTER TABLE ${this.id}.tag${this.field} // ALTER COLUMN id type bigint // USING id::bigint; // ALTER TABLE ${this.id}.reg // ALTER COLUMN id type bigint // USING id::bigint; // `); // } ================================================ FILE: src/db/postgres/package.json ================================================ { "public": true, "preferGlobal": false, "name": "flexsearch-postgres", "version": "0.1.0", "main": "index.js", "dependencies": { "pg-promise": "^11.10.2" } } ================================================ FILE: src/db/redis/index.js ================================================ import { createClient } from "redis"; import StorageInterface from "../interface.js"; import { toArray } from "../../common.js"; const VERSION = 1; const fields = ["map", "ctx", "reg", "tag", "doc", "cfg"]; const defaults = { host: "localhost", port: "6379", user: null, pass: null }; let DB, TRX; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_\-]/g, ""); } /** * @constructor * @implements StorageInterface */ export default function RedisDB(name, config = {}){ if(!this || this.constructor !== RedisDB){ return new RedisDB(name, config); } if(typeof name === "object"){ config = name; name = name.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } this.id = (name ? sanitize(name) : "flexsearch") + "|"; this.field = config.field ? "-" + sanitize(config.field) : ""; this.type = config.type || ""; this.fastupdate = true; this.db = config.db || DB || null; this.support_tag_search = true; this.resolution = 9; this.resolution_ctx = 9; //this.trx = false; Object.assign(defaults, config); this.db && delete defaults.db; }; // RedisDB.mount = function(flexsearch){ // return new this().mount(flexsearch); // }; RedisDB.prototype.mount = function(flexsearch){ //if(flexsearch.constructor === Document){ if(flexsearch.index){ return flexsearch.mount(this); } flexsearch.db = this; this.resolution = flexsearch.resolution; this.resolution_ctx = flexsearch.resolution_ctx; // todo support //this.fastupdate = flexsearch.fastupdate; return this.open(); }; RedisDB.prototype.open = async function(){ if(this.db){ return this.db } if(DB){ return this.db = DB; } let url = defaults.url; if(!url){ url = defaults.user ? `redis://${defaults.user}:${defaults.pass}@${defaults.host}:${defaults.port}` : `redis://${defaults.host}:${defaults.port}`; } return this.db = DB = await createClient(url) .on("error", err => console.error(err)) .connect(); }; RedisDB.prototype.close = async function(){ DB && await this.db.disconnect(); // this.db.client.disconnect(); this.db = DB = null; return this; }; RedisDB.prototype.destroy = function(){ return this.clear(true); }; RedisDB.prototype.clear = function(destroy = false){ if(!this.id) return; const self = this; function unlink(keys){ return keys.length && self.db.unlink(keys); } return Promise.all([ this.db.keys(this.id + "map" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "ctx" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "tag" + (destroy ? "" : this.field) + "*").then(unlink), this.db.keys(this.id + "ref" + (destroy ? "" : this.field) + "*").then(unlink), unlink([ this.id + "cfg" + (destroy ? "*" : this.field), this.id + "doc", this.id + "reg" ]) ]); }; function create_result(range, type, resolve, enrich, resolution){ if(resolve){ for(let i = 0, tmp, id; i < range.length; i++){ tmp = range[i]; id = type === "number" ? parseInt(tmp.value || tmp, 10) : tmp.value || tmp; range[i] = enrich ? { id, doc: tmp.doc } : id; } return range; } else { let result = []; for(let i = 0, tmp, id, score; i < range.length; i++){ tmp = range[i]; id = type === "number" ? parseInt(tmp.value || tmp, 10) : tmp.value || tmp; score = Math.max(resolution - tmp.score, 0); result[score] || (result[score] = []); result[score].push(id); } return result; } } RedisDB.prototype.get = function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false, tags){ if(tags){ // flexsearch dummy const flexsearch = { depth: !!ctx }; const query = ctx ? [ctx, key] : [key]; // keyword first return this.search(flexsearch, query, limit, offset, /* suggest */ false, resolve, enrich, tags); } const type = this.type; const self = this; let result; if(ctx){ result = this.db[resolve ? "zRange" : "zRangeWithScores"]( this.id + "ctx" + this.field + ":" + ctx + ":" + key, "" + offset, "" + (offset + limit - 1), { REV: true } ); } else{ result = this.db[resolve ? "zRange" : "zRangeWithScores"]( this.id + "map" + this.field + ":" + key, "" + offset, "" + (offset + limit - 1), { REV: true } ); } return result.then(async function(range){ if(!range.length) return range; if(enrich) range = await self.enrich(range); return create_result(range, type, resolve, enrich, ctx ? self.resolution_ctx : self.resolution); }); }; RedisDB.prototype.tag = function(tag, limit = 0, offset = 0, enrich = false){ const self = this; return this.db.sMembers(this.id + "tag" + this.field + ":" + tag).then(function(ids){ if(!ids || !ids.length || offset >= ids.length) return []; if(!limit && !offset) return ids; const result = ids.slice(offset, offset + limit); return enrich ? self.enrich(result) : result; }); }; RedisDB.prototype.enrich = function(ids){ if(typeof ids !== "object"){ ids = [ids]; } return this.db.hmGet(this.id + "doc", this.type === "number" ? ids.map(i => "" + i) : ids).then(function(res){ for(let i = 0; i < res.length; i++){ res[i] = { id: ids[i], doc: res[i] && JSON.parse(res[i]) }; } return res; }); }; RedisDB.prototype.has = function(id){ return this.db.sIsMember(this.id + "reg", "" + id).then(function(res){ return !!res; }); }; RedisDB.prototype.search = function(flexsearch, query, limit = 100, offset = 0, suggest = false, resolve = true, enrich = false, tags){ const ctx = query.length > 1 && flexsearch.depth; let result; let params = []; let weights = []; if(ctx){ const key = this.id + "ctx" + this.field + ":"; let keyword = query[0]; let term; for(let i = 1, swap; i < query.length; i++){ term = query[i]; swap = flexsearch.bidirectional && (term > keyword); params.push(key + (swap ? term : keyword) + ":" + (swap ? keyword : term)); weights.push(1); keyword = term; } } else{ const key = this.id + "map" + this.field + ":"; for(let i = 0; i < query.length; i++){ params.push(key + query[i]); weights.push(1); } } query = params; const type = this.type; let key = this.id + "tmp:" + Math.random(); const strict_tag_intersection = true; if(suggest){ if(!strict_tag_intersection){ if(tags) for(let i = 0; i < tags.length; i += 2){ query.push(this.id + "tag-" + sanitize(tags[i]) + ":" + tags[i + 1]); weights.push(1); } } const multi = this.db.multi(); // The zStore implementation lacks of ordering by match count (occurrences). // Unfortunately, I couldn't find an elegant way to overcome this issue completely. // For this reason it needs additionally a zInterStore to boost at least matches // when all terms were found multi.zInterStore(key, query, { AGGREGATE: "SUM" }); query.push(key); weights.push(query.length); multi.zUnionStore(key, query, { WEIGHTS: weights, AGGREGATE: "SUM" }); // Strict Tag Intersection: it does not put tags into union, instead it calculates the // intersection against the term match union. This was the default behavior // of Tag-Search. But putting everything into union will also provide suggestions derived // from tags when no term was matched. if(strict_tag_intersection){ if(tags){ // copy over zInterStore into the same destination surprisingly works fine // const key2 = key + ":2"; query = [key]; for(let i = 0; i < tags.length; i += 2){ query.push(this.id + "tag-" + sanitize(tags[i]) + ":" + tags[i + 1]); } multi.zInterStore(key, query, { AGGREGATE: "MAX" }); // .unlink(key) // key = key2; } } result = multi [(resolve ? "zRange" : "zRangeWithScores")]( key, "" + offset, "" + (offset + limit - 1), { REV: true } ) .unlink(key) .exec(); } else{ if(tags) for(let i = 0; i < tags.length; i += 2){ query.push(this.id + "tag-" + sanitize(tags[i]) + ":" + tags[i + 1]); } result = this.db.multi() .zInterStore(key, query, { AGGREGATE: "MAX" }) [(resolve ? "zRange" : "zRangeWithScores")]( key, "" + offset, "" + (offset + limit - 1), { REV: true } ) .unlink(key) .exec(); } const self = this; return result.then(async function(range){ range = suggest && tags // take the 3rd result from batch return ? range[3] // take the 2nd result from batch return : range[suggest ? 2 : 1]; if(!range.length) return range; if(enrich) range = await self.enrich(range); return create_result(range, type, resolve, enrich, ctx ? self.resolution_ctx : self.resolution); }); }; RedisDB.prototype.info = function(){ // todo }; RedisDB.prototype.transaction = function(task, callback){ if(TRX){ return task.call(this, TRX); } TRX = this.db.multi(); let promise1 = /*await*/ task.call(this, TRX) let promise2 = TRX.exec(); TRX = null; return Promise.all([promise1, promise2]).then(function(){ callback && callback(); }); }; RedisDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ /** @dict */ task = tasks[i]; if(task["del"]){ removals.push(task["del"]); } else if(task["ins"]){ } } if(removals.length){ await this.remove(removals); } if(!flexsearch.reg.size){ return; } await this.transaction(function(trx){ let refs = new Map(); for(const item of flexsearch.map){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ let result = []; for(let j = 0; j < ids.length; j++){ result.push({ score: this.resolution - i, value: "" + ids[j] }); } if(typeof ids[0] === "number"){ this.type = "number"; } const ref = this.id + "map" + this.field + ":" + key; trx.zAdd(ref, result); // if(this.fastupdate) for(let j = 0; j < ids.length; j++){ // trx.sAdd("ref" + this.field + ":" + ids[j], ref); // } if(this.fastupdate) for(let j = 0, id; j < ids.length; j++){ // Map performs bad when pushing numeric-like values as key // id = ids[j]; // let tmp = refs.get(id); // tmp || refs.set(id, tmp = []); // tmp.push(ref); id = ids[j]; let tmp = refs.get(id); tmp || refs.set(id, tmp = []); tmp.push(ref); } } } } // if(this.fastupdate) for(let item of refs){ // const key = item[0]; // const value = item[1]; // trx.sAdd("ref" + this.field + ":" + key, value); // } if(this.fastupdate) for(const item of refs){ const key = item[0]; const value = item[1]; trx.sAdd(this.id + "ref" + this.field + ":" + key, value); } refs = new Map(); for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ let result = []; for(let j = 0; j < ids.length; j++){ result.push({ score: this.resolution_ctx - i, value: "" + ids[j] }); } if(typeof ids[0] === "number"){ this.type = "number"; } const ref = this.id + "ctx" + this.field + ":" + ctx_key + ":" + key; trx.zAdd(ref, result); // if(this.fastupdate) for(let j = 0; j < ids.length; j++){ // trx.sAdd("ref" + this.field + ":" + ids[j], ref); // } if(this.fastupdate) for(let j = 0, id; j < ids.length; j++){ // Map performs bad when pushing numeric-like values as key // id = ids[j]; // let tmp = refs.get(id); // tmp || refs.set(id, tmp = []); // tmp.push(ref); id = ids[j]; let tmp = refs.get(id); tmp || refs.set(id, tmp = []); tmp.push(ref); } } } } } if(this.fastupdate) for(const item of refs){ const key = item[0]; const value = item[1]; trx.sAdd(this.id + "ref" + this.field + ":" + key, value); } if(flexsearch.store){ for(const item of flexsearch.store.entries()){ const id = item[0]; const doc = item[1]; doc && trx.hSet(this.id + "doc", "" + id, JSON.stringify(doc)); } } if(!flexsearch.bypass){ let ids = toArray(flexsearch.reg, /* stringify */ true); if(ids.length){ trx.sAdd(this.id + "reg", ids); } } if(flexsearch.tag){ for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; let result = []; // for(let i = 0; i < ids.length; i++){ // result.push({ // score: 0, // value: "" + ids[i] // }); // } if(typeof ids[0] === "number"){ for(let i = 0; i < ids.length; i++){ result[i] = "" + ids[i]; } } else{ result = ids; } trx.sAdd(this.id + "tag" + this.field + ":" + tag, result); } } // TODO // trx.set(this.id + "cfg" + this.field, JSON.stringify({ // "encode": typeof flexsearch.encode === "string" ? flexsearch.encode : "", // "charset": typeof flexsearch.charset === "string" ? flexsearch.charset : "", // "tokenize": flexsearch.tokenize, // "resolution": flexsearch.resolution, // "minlength": flexsearch.minlength, // "optimize": flexsearch.optimize, // "fastupdate": flexsearch.fastupdate, // "encoder": flexsearch.encoder, // "context": { // "depth": flexsearch.depth, // "bidirectional": flexsearch.bidirectional, // "resolution": flexsearch.resolution_ctx // } // })); }); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; RedisDB.prototype.remove = function(ids){ if(!ids && ids !== 0){ return; } if(typeof ids !== "object"){ ids = [ids]; } if(!ids.length){ return; } return this.transaction(async function(trx){ while(ids.length){ let next; if(ids.length > 10000){ next = ids.slice(10000); ids = ids.slice(0, 10000); } if(typeof ids[0] === "number"){ for(let i = 0; i < ids.length; i++){ ids[i] = "" + ids[i]; } } const check = await this.db.smIsMember(this.id + "reg", ids); for(let i = 0, id; i < ids.length; i++){ if(!check[i]) continue; id = "" + ids[i]; if(this.fastupdate){ // const refs = new Map(); const ref = await this.db.sMembers(this.id + "ref" + this.field + ":" + id); if(ref){ for(let j = 0; j < ref.length; j++){ // let tmp = refs.get(ref[j]); // tmp || refs.set(ref[j], tmp = []); // tmp.push(id); trx.zRem(ref[j], id); } trx.unlink(this.id + "ref" + this.field + ":" + id); } // for(let item of refs){ // //console.log(item[0], item[1]) // trx.zRem(item[0], item[1]); // } } else{ // todo scan } trx.hDel(this.id + "doc", id); trx.sRem(this.id + "reg", id); } if(next) ids = next; else break; } }); }; ================================================ FILE: src/db/redis/package.json ================================================ { "public": true, "preferGlobal": false, "name": "flexsearch-redis", "version": "0.1.0", "main": "index.js", "dependencies": { "redis": "^4.7.0" } } ================================================ FILE: src/db/sqlite/index.js ================================================ //const sqlite3 = require("sqlite3").verbose(); import sqlite3 from "sqlite3"; import path from "path"; import StorageInterface from "../interface.js"; import { concat, toArray } from "../../common.js"; const VERSION = 1; const MAXIMUM_QUERY_VARS = 16000; const fields = ["map", "ctx", "reg", "tag", "cfg"]; const types = { "text": "text", "char": "text", "varchar": "text", "string": "text", "number": "int", "numeric": "int", "integer": "int", "smallint": "int", "tinyint": "int", "mediumint": "int", "int": "int", "int8": "int", "uint8": "int", "int16": "int", "uint16": "int", "int32": "int", "uint32": "bigint", "int64": "bigint", "bigint": "bigint" }; function sanitize(str) { return str.toLowerCase().replace(/[^a-z0-9_]/g, ""); } // global transaction to keep track of database lock const TRX = Object.create(null); const Index = Object.create(null); /** * @constructor * @implements StorageInterface */ export default function SqliteDB(name, config = {}){ if(!this || this.constructor !== SqliteDB){ return new SqliteDB(name, config); } if(typeof name === "object"){ config = name; name = name.name; } if(!name){ console.info("Default storage space was used, because a name was not passed."); } //field = "Test-456"; this.id = config.path || ( name === ":memory:" ? name : "flexsearch" + (name ? "-" + sanitize(name) : "") + ".sqlite" ); this.field = config.field ? "_" + sanitize(config.field) : ""; this.support_tag_search = true; this.db = config.db || Index[this.id] || null; this.type = config.type ? types[config.type.toLowerCase()] : "string"; if(!this.type) throw new Error("Unknown type of ID '" + config.type + "'"); }; SqliteDB.prototype.mount = function(flexsearch){ //if(flexsearch.constructor === Document){ if(flexsearch.index){ return flexsearch.mount(this); } flexsearch.db = this; return this.open(); }; SqliteDB.prototype.open = async function(){ if(!this.db){ if(!(this.db = Index[this.id])){ let filepath = this.id; if(filepath !== ":memory:"){ // skip absolute path if(filepath[0] !== "/" && filepath[0] !== "\\"){ // current working directory const dir = process.cwd(); filepath = path.join(dir, this.id); } } this.db = Index[this.id] = new sqlite3.Database(filepath); } } const db = this.db; for(let i = 0; i < fields.length; i++){ const exist = await this.promisfy({ method: "get", stmt: "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?) as exist", params: [fields[i] + (fields[i] === "reg" ? "" : this.field)] }); if(!exist || !exist.exist){ let stmt, stmt_index; switch(fields[i]){ case "map": stmt = ` CREATE TABLE IF NOT EXISTS main.map${this.field}( key TEXT NOT NULL, res INTEGER NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS map_key_index${this.field} ON map${this.field} (key); CREATE INDEX IF NOT EXISTS map_id_index${this.field} ON map${this.field} (id); `; break; case "ctx": stmt = ` CREATE TABLE IF NOT EXISTS main.ctx${this.field}( ctx TEXT NOT NULL, key TEXT NOT NULL, res INTEGER NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS ctx_key_index${this.field} ON ctx${this.field} (ctx, key); CREATE INDEX IF NOT EXISTS ctx_id_index${this.field} ON ctx${this.field} (id); `; break; case "tag": stmt = ` CREATE TABLE IF NOT EXISTS main.tag${this.field}( tag TEXT NOT NULL, id ${this.type} NOT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS tag_index${this.field} ON tag${this.field} (tag); CREATE INDEX IF NOT EXISTS tag_id_index${this.field} ON tag${this.field} (id); `; break; case "reg": stmt = ` CREATE TABLE IF NOT EXISTS main.reg( id ${this.type} NOT NULL CONSTRAINT reg_pk${this.field} PRIMARY KEY, doc TEXT DEFAULT NULL ); `; stmt_index = ` CREATE INDEX IF NOT EXISTS reg_index ON reg (id); `; break; case "cfg": stmt = ` CREATE TABLE IF NOT EXISTS main.cfg${this.field} ( cfg TEXT NOT NULL ); `; break; } await new Promise(function(resolve, reject){ db.exec(stmt, function(err, rows){ if(err) return reject(err); stmt_index ? db.exec(stmt_index, function(err, rows){ if(err) return reject(err); resolve(rows); }) : resolve(rows); }); }); } } db.exec("PRAGMA optimize = 0x10002"); return db; }; SqliteDB.prototype.close = function(){ this.db && this.db.close(); this.db = null; Index[this.id] = null; TRX[this.id] = null; return this; }; SqliteDB.prototype.destroy = function(){ return this.transaction(function(){ this.db.run("DROP TABLE IF EXISTS main.map" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.ctx" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.tag" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.cfg" + this.field + ";"); this.db.run("DROP TABLE IF EXISTS main.reg;"); }); }; SqliteDB.prototype.clear = function(){ return this.transaction(function(){ this.db.run("DELETE FROM main.map" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.ctx" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.tag" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.cfg" + this.field + " WHERE 1;"); this.db.run("DELETE FROM main.reg WHERE 1;"); }); }; function create_result(rows, resolve, enrich){ if(resolve){ for(let i = 0; i < rows.length; i++){ if(enrich){ if(rows[i].doc){ rows[i].doc = JSON.parse(rows[i].doc); } } else{ rows[i] = rows[i].id; } } return rows; } else{ const arr = []; for(let i = 0, row; i < rows.length; i++){ row = rows[i]; arr[row.res] || (arr[row.res] = []); arr[row.res].push(enrich ? row : row.id ); } return arr; } } SqliteDB.prototype.get = function(key, ctx, limit = 0, offset = 0, resolve = true, enrich = false, tags){ let result; let stmt = ''; let params = ctx ? [ctx, key] : [key]; let table = "main." + (ctx ? "ctx" : "map") + this.field; if(tags){ for(let i = 0; i < tags.length; i+=2){ stmt += ` AND ${ table }.id IN (SELECT id FROM main.tag_${ sanitize(tags[i]) } WHERE tag = ?)`; params.push(tags[i + 1]); } } if(ctx){ result = this.promisfy({ method: "all", stmt: ` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${ table }.id ` : "" } WHERE ctx = ? AND key = ? ${stmt} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params }); } else{ result = this.promisfy({ method: "all", stmt: ` SELECT ${ table }.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${ table }.id ` : "" } WHERE key = ? ${stmt} ORDER BY res ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params }); } return result.then(function(rows){ return create_result(rows, resolve, enrich); }); }; SqliteDB.prototype.tag = function(tag, limit = 0, offset = 0, enrich = false){ const table = "main.tag" + this.field; const promise = this.promisfy({ method: "all", stmt: ` SELECT ${ table }.id ${ enrich ? ", doc" : "" } FROM ${ table } ${ enrich ? ` LEFT JOIN main.reg ON main.reg.id = ${ table }.id ` : "" } WHERE tag = ? ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params: [tag] }); enrich || promise.then(function(rows){ return create_result(rows, true, false); }); return promise; }; function build_params(length, single_param){ // let stmt = "?"; // for(let i = 1; i < length; i++){ // stmt += ",?"; // } let stmt = single_param ? ",(?)" : ",?"; for(let i = 1; i < length;){ if(i <= (length - i)){ stmt += stmt; i *= 2; } else{ stmt += stmt.substring(0, (length - i) * (single_param ? 4 : 2)); break; } } return stmt.substring(1); } SqliteDB.prototype.enrich = function(ids){ const result = []; const promises = []; if(typeof ids !== "object"){ ids = [ids]; } for(let count = 0; count < ids.length;){ const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; const stmt = build_params(chunk.length); count += chunk.length; promises.push(this.promisfy({ method: "all", stmt: `SELECT id, doc FROM main.reg WHERE id IN (${stmt})`, params: chunk })); } return Promise.all(promises).then(function(promises){ for(let i = 0, res; i < promises.length; i++){ res = promises[i]; if(res && res.length){ for(let i = 0, doc; i < res.length; i++){ if((doc = res[i].doc)){ res[i].doc = JSON.parse(doc); } } result.push(res); } } return result.length === 1 ? result[0] : result.length > 1 ? concat(result) : result; }); }; SqliteDB.prototype.has = function(id){ return this.promisfy({ method: "get", stmt: `SELECT EXISTS(SELECT 1 FROM main.reg WHERE id = ?) as exist`, params: [id] }).then(function(result){ return !!(result && result.exist); }); }; SqliteDB.prototype.search = function(flexsearch, query, limit = 100, offset = 0, suggest = false, resolve = true, enrich = false, tags){ let rows; if(query.length > 1 && flexsearch.depth){ let stmt = ""; let params = []; let keyword = query[0]; let term; for(let i = 1; i < query.length; i++){ term = query[i]; const swap = flexsearch.bidirectional && (term > keyword); stmt += (stmt ? " OR " : "") + `(ctx = ? AND key = ?)` params.push(swap ? term : keyword, swap ? keyword : term); keyword = term; } if(tags){ stmt = "(" + stmt + ")"; for(let i = 0; i < tags.length; i+=2){ stmt += ` AND id IN (SELECT id FROM main.tag_${ sanitize(tags[i]) } WHERE tag = ?)`; params.push(tags[i + 1]); } } rows = this.promisfy({ method: "all", stmt: ` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" /*"MIN"*/ }(res) as res FROM main.ctx${ this.field } WHERE ${ stmt } GROUP BY id ) as r ${ enrich ? ` LEFT JOIN main.reg ON main.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + (query.length - 1) } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params }); // variant 1 // for(let i = 1; i < query.length; i++){ // stmt += (stmt ? " UNION ALL " : "") + ` // SELECT id, res // FROM main.ctx${this.field} // WHERE ctx = ? AND key = ? // `; // term = query[i]; // const swap = flexsearch.bidirectional && (term > keyword); // params.push(swap ? term : keyword, swap ? keyword : term); // keyword = term; // } // // rows = await this.promisfy({ // method: "all", // stmt: ` // SELECT id/*, res */ // FROM ( // SELECT id, ${suggest ? "SUM" : "MIN"}(res) as res, count(*) as count // FROM (${stmt}) as t // GROUP BY id // ORDER BY ${suggest ? "count DESC, res" : "res"} // LIMIT ${limit} // OFFSET ${offset} // ) as r // ${suggest ? "" : "WHERE count = " + (query.length - 1)} // `, // params // }); } else{ let stmt = ""; let query_length = query.length; for(let i = 0; i < query_length; i++){ stmt += (stmt ? " OR " : "") + `key = ?` } if(tags){ stmt = "(" + stmt + ")"; for(let i = 0; i < tags.length; i+=2){ stmt += ` AND id IN (SELECT id FROM main.tag_${sanitize(tags[i])} WHERE tag = ?)`; query.push(tags[i + 1]); } } rows = this.promisfy({ method: "all", stmt: ` SELECT r.id ${ resolve ? "" : ", res" } ${ enrich ? ", doc" : "" } FROM ( SELECT id, count(*) as count, ${ suggest ? "SUM" : "SUM" /*"MIN"*/ }(res) as res FROM main.map${ this.field } WHERE ${ stmt } GROUP BY id ) as r ${ enrich ? ` LEFT JOIN main.reg ON main.reg.id = r.id ` : "" } ${ suggest ? "" : "WHERE count = " + query_length } ORDER BY ${ suggest ? "count DESC, res" : "res" } ${ limit ? "LIMIT " + limit : "" } ${ offset ? "OFFSET " + offset : "" } `, params: query }); // variant 1 // for(let i = 0; i < query.length; i++){ // stmt += (stmt ? " UNION ALL " : "") + ` // SELECT id, res // FROM main.map${ this.field } // WHERE key = ? // `; // } // // rows = await this.promisfy({ // method: "all", // stmt: ` // SELECT id/*, res*/ // FROM ( // SELECT id, ${suggest ? "SUM" : "MIN"}(res) as res, count(*) as count // FROM (${stmt}) as t // GROUP BY id // ORDER BY ${suggest ? "count DESC, res" : "res"} // LIMIT ${limit} // OFFSET ${offset} // ) as r // ${ suggest ? "" : "WHERE count = " + query.length } // `, // params: query // }); } return rows.then(function(rows){ return create_result(rows, resolve, enrich); }); }; SqliteDB.prototype.info = function(){ // todo }; SqliteDB.prototype.transaction = async function(task, callback){ if(TRX[this.id]){ return await task.call(this); } const db = this.db; const self = this; return TRX[this.id] = new Promise(function(resolve, reject){ db.exec("PRAGMA optimize"); db.exec('PRAGMA busy_timeout = 5000'); db.exec("BEGIN"); db.parallelize(function(){ task.call(self); }); db.exec("COMMIT", function(err, rows){ TRX[self.id] = null; if(err) return reject(err); callback && callback(rows); resolve(rows); db.exec("PRAGMA shrink_memory"); }); }); }; SqliteDB.prototype.commit = async function(flexsearch){ let tasks = flexsearch.commit_task; let removals = []; let inserts = []; flexsearch.commit_task = []; for(let i = 0, task; i < tasks.length; i++){ task = tasks[i]; if(typeof task["del"] !== "undefined"){ removals.push(task["del"]); } else if(typeof task["ins"] !== "undefined"){ inserts.push(task["ins"]); } } if(removals.length){ await this.remove(removals); } // if(inserts_map.length || inserts_ctx.length){ // await this.insert(inserts_map, inserts_ctx); // } if(!flexsearch.reg.size){ return; } await this.transaction(function(){ for(const item of flexsearch.map){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ let stmt = ""; let params = []; for(let j = 0; j < ids.length; j++){ stmt += (stmt ? "," : "") + "(?,?,?)"; params.push(key, i, ids[j]); // maximum count of variables supported if((j === ids.length - 1) || (params.length + 3 > MAXIMUM_QUERY_VARS)){ this.db.run( "INSERT INTO main.map" + this.field + " (key, res, id) VALUES " + stmt // " ON CONFLICT DO " + // "UPDATE main.map" + this.field + " SET key = '', res = 0 WHERE key = ? AND res > ? AND id = ?" , params); stmt = ""; params = []; } //this.db.run("INSERT INTO map (key, res, id) VALUES (?, ?, ?) ON CONFLICT DO NOTHING", [key, i, ids[j]]); } } } } //}); //await this.transaction(function(){ for(const ctx of flexsearch.ctx){ const ctx_key = ctx[0]; const ctx_value = ctx[1]; for(const item of ctx_value){ const key = item[0]; const arr = item[1]; for(let i = 0, ids; i < arr.length; i++){ if((ids = arr[i]) && ids.length){ let stmt = ""; let params = []; for(let j = 0; j < ids.length; j++){ stmt += (stmt ? "," : "") + "(?,?,?,?)"; params.push(ctx_key, key, i, ids[j]); // maximum count of variables supported if((j === ids.length - 1) || (params.length + 4 > MAXIMUM_QUERY_VARS)){ this.db.run("INSERT INTO main.ctx" + this.field + " (ctx, key, res, id) VALUES " + stmt, params); stmt = ""; params = []; } } } } } } //}); //await this.transaction(function(){ if(flexsearch.store){ let stmt = ""; let chunk = []; for(const item of flexsearch.store.entries()){ const id = item[0]; const doc = item[1]; stmt += (stmt ? "," : "") + "(?,?)"; chunk.push(id, typeof doc === "object" ? JSON.stringify(doc) : doc || null ); if(chunk.length + 2 > MAXIMUM_QUERY_VARS){ this.db.run("INSERT INTO main.reg (id, doc) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); stmt = ""; chunk = []; } } if(chunk.length){ this.db.run("INSERT INTO main.reg (id, doc) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); } } else if(!flexsearch.bypass){ let ids = toArray(flexsearch.reg); for(let count = 0; count < ids.length;){ const chunk = ids.length - count > MAXIMUM_QUERY_VARS ? ids.slice(count, count + MAXIMUM_QUERY_VARS) : count ? ids.slice(count) : ids; count += chunk.length; const stmt = build_params(chunk.length, /* single param */ true); this.db.run("INSERT INTO main.reg (id) VALUES " + stmt + " ON CONFLICT DO NOTHING", chunk); } } //}); //await this.transaction(function(){ if(flexsearch.tag){ let stmt = ""; let chunk = []; for(const item of flexsearch.tag){ const tag = item[0]; const ids = item[1]; if(!ids.length) continue; for(let i = 0; i < ids.length; i++){ stmt += (stmt ? "," : "") + "(?,?)"; chunk.push(tag, ids[i]); } if(chunk.length + 2 > MAXIMUM_QUERY_VARS){ this.db.run("INSERT INTO main.tag" + this.field + " (tag, id) VALUES " + stmt, chunk); stmt = ""; chunk = []; } } if(chunk.length){ this.db.run("INSERT INTO main.tag" + this.field + " (tag, id) VALUES " + stmt, chunk); } } }); if(inserts.length){ await this.cleanup(); } // TODO //await this.transaction(function(){ // this.db.run("INSERT INTO main.cfg" + this.field + " (cfg) VALUES (?)", [JSON.stringify({ // "charset": flexsearch.charset, // "tokenize": flexsearch.tokenize, // "resolution": flexsearch.resolution, // "fastupdate": flexsearch.fastupdate, // "compress": flexsearch.compress, // "encoder": { // "minlength": flexsearch.encoder.minlength // }, // "context": { // "depth": flexsearch.depth, // "bidirectional": flexsearch.bidirectional, // "resolution": flexsearch.resolution_ctx // } // })]); //}); flexsearch.map.clear(); flexsearch.ctx.clear(); flexsearch.tag && flexsearch.tag.clear(); flexsearch.store && flexsearch.store.clear(); flexsearch.document || flexsearch.reg.clear(); }; SqliteDB.prototype.remove = function(ids){ if(typeof ids !== "object"){ ids = [ids]; } let next; // maximum count of variables supported if(ids.length > MAXIMUM_QUERY_VARS){ next = ids.slice(MAXIMUM_QUERY_VARS); ids = ids.slice(0, MAXIMUM_QUERY_VARS); } const self = this; return this.transaction(function(){ const stmt = build_params(ids.length); this.db.run("DELETE FROM main.map" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.ctx" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.tag" + self.field + " WHERE id IN (" + stmt + ")", ids); this.db.run("DELETE FROM main.reg WHERE id IN (" + stmt + ")", ids); }).then(function(result){ return next ? self.remove(next) : result; }); }; SqliteDB.prototype.cleanup = function(){ return this.transaction(function(){ this.db.run( "DELETE FROM main.map" + this.field + " " + "WHERE ROWID IN (" + "SELECT ROWID FROM (" + "SELECT ROWID, row_number() OVER dupes AS count " + "FROM main.map" + this.field + " _t " + "WINDOW dupes AS (PARTITION BY id, key ORDER BY res) " + ") " + "WHERE count > 1" + ")" ); this.db.run( "DELETE FROM main.ctx" + this.field + " " + "WHERE ROWID IN (" + "SELECT ROWID FROM (" + "SELECT ROWID, row_number() OVER dupes AS count " + "FROM main.ctx" + this.field + " _t " + "WINDOW dupes AS (PARTITION BY id, ctx, key ORDER BY res) " + ") " + "WHERE count > 1" + ")" ); }); }; SqliteDB.prototype.promisfy = function(opt){ const db = this.db; return new Promise(function(resolve, reject){ db[opt.method](opt.stmt, opt.params || [], function(err, rows){ opt.callback && opt.callback(rows); err ? reject(err) : resolve(rows); }); }); }; ================================================ FILE: src/db/sqlite/package.json ================================================ { "public": true, "preferGlobal": false, "name": "flexsearch-sqlite", "version": "0.1.0", "main": "index.js", "dependencies": { "sqlite3": "^5.1.7" } } ================================================ FILE: src/document/add.js ================================================ // COMPILER BLOCK --> import { DEBUG, SUPPORT_KEYSTORE, SUPPORT_PERSISTENT, SUPPORT_STORE, SUPPORT_TAGS, SUPPORT_WORKER } from "../config.js"; // <-- COMPILER BLOCK import { create_object, is_array, is_object, is_string, parse_simple } from "../common.js"; import { KeystoreArray } from "../keystore.js"; import Document from "../document.js"; /** * * @param id * @param content * @param {boolean=} _append * @this Document * @returns {Document|Promise} */ Document.prototype.add = function(id, content, _append){ if(is_object(id)){ content = id; id = parse_simple(content, this.key); } if(content && (id || (id === 0))){ if(!_append && this.reg.has(id)){ return this.update(id, content); } // this.field does not include db tag indexes for(let i = 0, tree; i < this.field.length; i++){ tree = this.tree[i]; const index = this.index.get(this.field[i]); if(typeof tree === "function"){ const tmp = tree(content); if(tmp){ index.add(id, tmp, /* append: */ _append, /* skip update: */ true); } } else{ const filter = tree._filter; if(filter && !filter(content)){ continue; } if(tree.constructor === String){ tree = ["" + tree]; } else if(is_string(tree)){ tree = [tree]; } add_index(content, tree, this.marker, 0, index, id, tree[0], _append); } } if(SUPPORT_TAGS && this.tag){ //console.log(this.tag, this.tagtree) for(let x = 0; x < this.tagtree.length; x++){ let tree = this.tagtree[x]; let field = this.tagfield[x]; let ref = this.tag.get(field); let dupes = create_object(); let tags; if(typeof tree === "function"){ tags = tree(content); if(!tags) continue; } else{ const filter = tree._filter; if(filter && !filter(content)){ continue; } if(tree.constructor === String){ tree = "" + tree; } tags = parse_simple(content, tree); } if(!ref || !tags){ ref || (DEBUG && console.warn("Tag '" + field + "' was not found")); continue; } if(is_string(tags)){ tags = [tags]; } for(let i = 0, tag, arr; i < tags.length; i++){ tag = tags[i]; //console.log(this.tag, tag, key, field) if(!dupes[tag]){ dupes[tag] = 1; let tmp; tmp = ref.get(tag); tmp ? arr = tmp : ref.set(tag, arr = []); if(!_append || ! /** @type {!Array|KeystoreArray} */(arr).includes(id)){ // auto-upgrade to keystore array if max size exceeded if(SUPPORT_KEYSTORE){ if(arr.length === 2**31-1 /*|| !(arr instanceof KeystoreArray)*/){ const keystore = new KeystoreArray(arr); if(this.fastupdate){ for(let value of this.reg.values()){ if(value.includes(arr)){ value[value.indexOf(arr)] = keystore; } } } ref.set(tag, arr = keystore); } } arr.push(id); // add a reference to the register for fast updates if(this.fastupdate){ const tmp = this.reg.get(id); tmp ? tmp.push(arr) : this.reg.set(id, [arr]); } } } } } } if(SUPPORT_STORE && this.store && (!_append || !this.store.has(id))){ let payload; if(this.storetree){ payload = create_object(); for(let i = 0, tree; i < this.storetree.length; i++){ tree = this.storetree[i]; const filter = tree._filter; if(filter && !filter(content)){ continue; } let custom; if(typeof tree === "function"){ custom = tree(content); if(!custom) continue; tree = [tree._field]; } else if(is_string(tree) || tree.constructor === String){ payload[tree] = content[tree]; continue; } store_value(content, payload, tree, 0, tree[0], custom); } } this.store.set(id, payload || content); } if(SUPPORT_WORKER && this.worker){ this.fastupdate || this.reg.add(id); } } return this; }; // TODO support generic function created from string when tree depth > 1 /** * @param obj * @param store * @param tree * @param pos * @param key * @param {*=} custom */ function store_value(obj, store, tree, pos, key, custom){ obj = obj[key]; // reached target field if(pos === (tree.length - 1)){ // store target value store[key] = custom || obj; } else if(obj){ if(is_array(obj)){ store = store[key] = new Array(obj.length); for(let i = 0; i < obj.length; i++){ // do not increase pos (an array is not a field) store_value(obj, store, tree, pos, i); } } else{ store = store[key] || (store[key] = create_object()); key = tree[++pos]; store_value(obj, store, tree, pos, key); } } } function add_index(obj, tree, marker, pos, index, id, key, _append){ if((obj = obj[key])){ // reached the target field if(pos === (tree.length - 1)){ // handle target value if(is_array(obj)){ // append array contents so each entry gets a new scoring context if(marker[pos]){ for(let i = 0; i < obj.length; i++){ index.add(id, obj[i], /* append: */ true, /* skip update: */ true); } return; } // or join array contents and use one scoring context obj = obj.join(" "); } index.add(id, obj, _append, /* skip_update: */ true); } else{ if(is_array(obj)){ for(let i = 0; i < obj.length; i++){ // do not increase index, an array is not a field add_index(obj, tree, marker, pos, index, id, i, _append); } } else{ key = tree[++pos]; add_index(obj, tree, marker, pos, index, id, key, _append); } } } // else{ // if(SUPPORT_PERSISTENT && index.db){ // index.remove(id); // } // } } ================================================ FILE: src/document/highlight.js ================================================ // COMPILER BLOCK --> import { DEBUG } from "../config.js"; // <-- COMPILER BLOCK import { parse_simple } from "../common.js"; import Index from "../index.js"; import WorkerIndex from "../worker.js"; import { EnrichedDocumentSearchResults, EnrichedSearchResults, HighlightOptions } from "../type.js"; /** * @param {string} query * @param {EnrichedDocumentSearchResults|EnrichedSearchResults} result * @param {Map} index * @param {string} pluck * @param {HighlightOptions|string} config * @return {EnrichedDocumentSearchResults|EnrichedSearchResults} */ export function highlight_fields(query, result, index, pluck, config){ // The biggest issue is dealing with custom encoders, for this reason // a combined regular expression can't apply as a template let template, markup_open, markup_close; if(typeof config === "string"){ template = config; config = ""; } else{ template = config.template; } if(DEBUG){ if(!template){ throw new Error('No template pattern was specified by the search option "highlight"'); } } markup_open = template.indexOf("$1"); if(DEBUG){ if(markup_open === -1){ throw new Error('Invalid highlight template. The replacement pattern "$1" was not found in template: ' + template); } } markup_close = template.substring(markup_open + 2); markup_open = template.substring(0, markup_open); let boundary = config && config.boundary; let clip = !config || config.clip !== false; let merge = config && config.merge && markup_close && markup_open && new RegExp(markup_close + " " + markup_open, "g"); let ellipsis = config && config.ellipsis; let ellipsis_markup_length = 0; let ellipsis_markup; if(typeof ellipsis === "object"){ ellipsis_markup = ellipsis.template; ellipsis_markup_length = ellipsis_markup.length - 2; ellipsis = ellipsis.pattern; } if(typeof ellipsis !== "string"){ ellipsis = ellipsis === false ? "" : "..."; } if(ellipsis_markup_length){ ellipsis = ellipsis_markup.replace("$1", ellipsis); } let ellipsis_length = ellipsis.length - ellipsis_markup_length; let boundary_before, boundary_after; if(typeof boundary === "object"){ boundary_before = boundary.before; if(boundary_before === 0) boundary_before = -1; boundary_after = boundary.after; if(boundary_after === 0) boundary_after = -1; boundary = boundary.total || 9e5; } // cache shared encoders across fields let encoder = new Map(); let query_enc; // todo remove this loop and pass in the field data directly // todo support field-specific configuration // for every field for(let i = 0, enc, idx, path; i < result.length; i++){ /** @type {EnrichedSearchResults} */ let res; if(pluck){ res = result; path = pluck; } else{ const tmp = result[i]; path = tmp.field; // skip when not a field entry (e.g., tags) if(!path) continue; res = tmp.result; } idx = index.get(path); enc = idx.encoder; query_enc = encoder.get(enc); // re-encode a query when encoder has changed or take cache from shared encoders if(typeof query_enc !== "string"){ query_enc = enc.encode(query); encoder.set(enc, query_enc); } // for every doc in results for(let j = 0; j < res.length; j++){ const doc = res[j]["doc"]; if(!doc) continue; const content = parse_simple(doc, path); if(!content) continue; // just split on whitespace and keep the original string (encoder split can't apply) const doc_org = content.trim().split(/\s+/); if(!doc_org.length) continue; let str = ""; let str_arr = []; let pos_matches = []; let pos_first_match = -1; let pos_last_match = -1; let length_matches_all = 0; // loop terms of encoded doc content for(let k = 0; k < doc_org.length; k++){ let doc_org_cur = doc_org[k]; let doc_enc_cur = enc.encode(doc_org_cur); doc_enc_cur = doc_enc_cur.length > 1 ? doc_enc_cur.join(" ") : doc_enc_cur[0]; let found; if(doc_enc_cur && doc_org_cur){ let doc_org_cur_len = doc_org_cur.length; let doc_org_diff = (enc.split ? doc_org_cur.replace(enc.split, "") : doc_org_cur).length - doc_enc_cur.length; let match = ""; let match_length = 0; // loop terms of encoded query content and determine the longest match for(let l = 0; l < query_enc.length; l++){ let query_enc_cur = query_enc[l]; if(!query_enc_cur) continue; let query_enc_cur_len = query_enc_cur.length; // add length from shrinking phonetic transformations (todo: add tests) query_enc_cur_len += doc_org_diff < 0 ? 0 : doc_org_diff; // skip the query token when match length can't exceed the previously highest found match if(match_length && query_enc_cur_len <= match_length){ continue; } const position = doc_enc_cur.indexOf(query_enc_cur); if(position > -1){ match = // prefix (position ? doc_org_cur.substring(0, position) : "") + // match markup_open + doc_org_cur.substring(position, position + query_enc_cur_len) + markup_close + // suffix (position + query_enc_cur_len < doc_org_cur_len ? doc_org_cur.substring(position + query_enc_cur_len) : ""); match_length = query_enc_cur_len; found = true; } //console.log(doc_org_cur, doc_enc_cur, query_enc_cur, position, match) } // apply the longest match if(match){ if(boundary){ // the outer boundary is used to check if all matches are within the total boundary // if so, it can apply a simpler alignment if(pos_first_match < 0){ pos_first_match = str.length + (str ? 1 : 0); } pos_last_match = str.length + (str ? 1 : 0) + match.length; // the overall length of all matches is used to check if matches exceed the total boundary // if so, it can early stop further processing length_matches_all += doc_org_cur_len; // the match positions are used to pick items for the final result more quickly pos_matches.push(str_arr.length) // collect every term as a match or text str_arr.push({ match }); } str += (str ? " " : "") + match; } } if(!found){ const text = doc_org[k]; str += (str ? " " : "") + text; // collect every term as a match or text boundary && str_arr.push({ text }); } else if(boundary){ if(length_matches_all >= boundary){ // matches have reached the total boundary break; } } } // the markup length does not apply to the total boundary let markup_length = pos_matches.length * (template.length - 2); // apply boundaries and align highlights if(boundary_before || boundary_after || (boundary && (str.length - markup_length) > boundary)){ // also reduce ellipsis length from the boundary let boundary_length = boundary + markup_length - ellipsis_length * 2; let length = pos_last_match - pos_first_match; let start, end; if(boundary_before > 0){ length += boundary_before; } if(boundary_after > 0){ length += boundary_after; } // 1. all matches are withing the overall boundary (apply simple alignment) if(length <= boundary_length){ start = boundary_before ? pos_first_match - (boundary_before > 0 ? boundary_before : 0) : pos_first_match - ((boundary_length - length) / 2 | 0); end = boundary_after ? pos_last_match + (boundary_after > 0 ? boundary_after : 0) : start + boundary_length; // do not clip terms if(!clip){ if(start > 0){ if(str.charAt(start) === " "){} else if(str.charAt(start - 1) !== " "){ start = str.indexOf(" ", start); (start < 0) && (start = 0); } } if(end < str.length){ if(str.charAt(end - 1) === " "){} else if(str.charAt(end) !== " "){ end = str.lastIndexOf(" ", end); end < pos_last_match ? end = pos_last_match : ++end; } } } str = (start ? ellipsis : "") + str.substring(start, end) + (end < str.length ? ellipsis : ""); } // 2. matches needs to be split by surrounded terms to fit into the boundary else{ const final = []; const check = {}; const seamless = {}; const finished = {}; const before = {}; const after = {}; let final_length = 0; let shift_left = 0; let shift_right = 0; let loop_left = 1; let loop_right = 1; while(true){ let loop; for(let k = 0, pos; k < pos_matches.length; k++){ pos = pos_matches[k]; // 1. add matches to the result if(!shift_right){ str = str_arr[pos].match; // initialize custom boundaries for each slot if(boundary_before){ before[k] = boundary_before; } if(boundary_after){ after[k] = boundary_after; } // count whitespaces between each term if(k){ final_length++; } let close; // close left side when first term was matched if(!pos){ // it can be set before content was added, // because the first term match is always added seamless[k] = 1; finished[k] = 1; } // initial ellipsis else if(!k && ellipsis_length){ final_length += ellipsis_length; } // close right side when last term was matched if(pos >= doc_org.length - 1){ close = 1; } // close right side when next term was a match else if((pos < str_arr.length - 1) && str_arr[pos + 1].match){ close = 1; } else if(ellipsis_length){ final_length += ellipsis_length; } // reduce template length on matches final_length -= template.length - 2; // at least add one match if(!k || (final_length + str.length <= boundary)){ final[k] = str; } else{ seamless[k] = 0; loop = loop_left = loop_right = 0; break; } // update state when term was added if(close){ seamless[k + 1] = 1; finished[k + 1] = 1; } } // 2. add surrounded text to the result else{ // alternate direction term by term // 2.1. extend to the right first (index: k + 1) if(shift_left !== shift_right){ if(finished[k + 1]) continue; pos += shift_right; // overlap with another slot if(check[pos]){ final_length -= ellipsis_length; seamless[k + 1] = 1; finished[k + 1] = 1; continue; } // end reached if(pos >= str_arr.length - 1){ if(pos >= str_arr.length){ finished[k + 1] = 1; if(pos >= doc_org.length){ seamless[k + 1] = 1; } continue; } final_length -= ellipsis_length; } str = str_arr[pos].text; let current_after = boundary_after && after[k]; if(current_after){ if(current_after > 0){ if(str.length > current_after){ finished[k + 1] = 1; if(clip){ str = str.substring(0, current_after); } else{ continue; } } current_after -= str.length; if(!current_after) current_after = -1; after[k] = current_after; } else{ finished[k + 1] = 1; continue; } } // count whitespaces between each term if(final_length + str.length + 1 <= boundary){ str = " " + str; final[k] += str; } else if(clip){ const diff = boundary - final_length - 1; if(diff > 0){ str = " " + str.substring(0, diff); final[k] += str; } finished[k + 1] = 1; } else{ finished[k + 1] = 1; continue; } } // 2.2. extend to left (index: k) else{ if(finished[k]) continue; pos -= shift_left; // overlap with another slot if(check[pos]){ final_length -= ellipsis_length; finished[k] = 1; seamless[k] = 1; continue; } // start reached if(pos <= 0){ if(pos < 0){ finished[k] = 1; seamless[k] = 1; continue; } final_length -= ellipsis_length; } str = str_arr[pos].text; let current_before = boundary_before && before[k]; if(current_before){ if(current_before > 0){ if(str.length > current_before){ finished[k] = 1; if(clip){ str = str.substring(str.length - current_before); } else{ continue; } } current_before -= str.length; if(!current_before) current_before = -1; before[k] = current_before; } else{ finished[k] = 1; continue; } } // count whitespaces between each term if(final_length + str.length + 1 <= boundary){ str += " "; final[k] = str + final[k]; } else if(clip){ const diff = str.length + 1 - (boundary - final_length); if(diff >= 0 && diff < str.length){ str = str.substring(diff) + " "; final[k] = str + final[k]; } finished[k] = 1; } else{ finished[k] = 1; continue; } } } // update state when term was added final_length += str.length; check[pos] = 1; loop = 1; } if(loop){ // alternate shift direction shift_left === shift_right ? shift_right++ : shift_left++; } else{ // check finish state shift_left === shift_right ? loop_left = 0 : loop_right = 0; // break the process when both directions are done if(!loop_left && !loop_right){ break; } // continue with the opposite direction if(loop_left){ shift_left++; shift_right = shift_left; } else{ shift_right++; } } } str = ""; for(let k = 0, tmp; k < final.length; k++){ tmp = (seamless[k] ? (k ? " " : "") : (k && !ellipsis ? " " : "") + ellipsis) + final[k]; str += tmp; } if(ellipsis && !seamless[final.length]){ str += ellipsis; } //console.log(query, seamless, final) } } if(merge){ str = str.replace(/** @type {RegExp} */ (merge), " "); } res[j]["highlight"] = str; } if(pluck){ break; } } return result; } // /** // * @param {string} query // * @param {EnrichedDocumentSearchResults|EnrichedSearchResults} result // * @param {Map} index // * @param {string} pluck // * @param {HighlightOptions|string} config // * @return {EnrichedDocumentSearchResults|EnrichedSearchResults} // */ // export function highlight_fields(query, result, index, pluck, config){ // // // The biggest issue is dealing with custom encoders, for this reason // // a combined regular expression can't apply as a template // // let template, markup_open, markup_close; // // if(typeof config === "string"){ // template = config; // config = ""; // } // else{ // template = config.template; // } // // if(DEBUG){ // if(!template){ // throw new Error('No template pattern was specified by the search option "highlight"'); // } // } // // markup_open = template.indexOf("$1"); // // if(DEBUG){ // if(markup_open === -1){ // throw new Error('Invalid highlight template. The replacement pattern "$1" was not found in template: ' + template); // } // } // // markup_close = template.substring(markup_open + 2); // markup_open = template.substring(0, markup_open); // // let boundary = config && config.boundary; // let clip = !config || config.clip !== false; // let merge = config && config.merge && markup_close && markup_open && new RegExp(markup_close + " " + markup_open, "g"); // let ellipsis = config && config.ellipsis; // if(typeof ellipsis !== "string"){ // ellipsis = "..."; // } // // let boundary_before, boundary_after; // // if(typeof boundary === "object"){ // boundary_before = boundary.before; // if(boundary_before === 0) boundary_before = -1; // boundary_after = boundary.after; // if(boundary_after === 0) boundary_after = -1; // boundary = boundary.total || 9e5; // } // // // cache shared encoders across fields // let encoder = new Map(); // let query_enc; // // // todo remove this loop and pass in the field data directly // // todo support field-specific configuration // // // for every field // for(let i = 0, enc, idx, path; i < result.length; i++){ // // /** @type {EnrichedSearchResults} */ // let res; // // if(pluck){ // res = result; // path = pluck; // } // else{ // const tmp = result[i]; // path = tmp.field; // // skip when not a field entry (e.g. tags) // if(!path) continue; // res = tmp.result; // } // // idx = index.get(path); // enc = idx.encoder; // query_enc = encoder.get(enc); // // // re-encode query when encoder has changed or take cache from shared encoders // if(typeof query_enc !== "string"){ // query_enc = enc.encode(query); // encoder.set(enc, query_enc); // } // // // for every doc in results // for(let j = 0; j < res.length; j++){ // // const doc = res[j]["doc"]; // if(!doc) continue; // const content = parse_simple(doc, path); // if(!content) continue; // const doc_org = content.trim().split(/\s+/); // if(!doc_org.length) continue; // // let str = ""; // let pos_matches = []; // let length_matches_all = 0; // // // loop terms of encoded doc content // for(let k = 0; k < doc_org.length; k++){ // let doc_org_cur = doc_org[k]; // let doc_org_cur_len = doc_org_cur.length; // let doc_enc_cur = enc.encode(doc_org_cur); // doc_enc_cur = doc_enc_cur.length > 1 // ? doc_enc_cur.join(" ") // : doc_enc_cur[0]; // // let found; // // if(doc_enc_cur && doc_org_cur){ // // let match = ""; // let match_length = 0; // // // loop terms of encoded query content and determine the longest match // for(let l = 0; l < query_enc.length; l++){ // let query_enc_cur = query_enc[l]; // if(!query_enc_cur) continue; // let query_enc_cur_len = query_enc_cur.length; // // add length from shrinking phonetic transformations (todo: add tests) // query_enc_cur_len += doc_org_cur.length - doc_enc_cur.length; // // skip query token when match length can't exceed previously highest found match // if(match_length && query_enc_cur_len <= match_length){ // continue; // } // const position = doc_enc_cur.indexOf(query_enc_cur); // //console.log(doc_org_cur, doc_enc_cur, query_enc_cur, position) // if(position > -1){ // match = // // prefix // (position ? doc_org_cur.substring(0, position) : "") + // // match // markup_open + doc_org_cur.substring(position, position + query_enc_cur_len) + markup_close + // // suffix // (position + query_enc_cur_len < doc_org_cur_len ? doc_org_cur.substring(position + query_enc_cur_len) : ""); // match_length = query_enc_cur_len; // found = true; // } // } // // // apply the longest match // if(match){ // if(boundary){ // if(!pos_matches.length && k) length_matches_all += ellipsis.length; // // the overall length of all matches is used to check if matches exceeds the total boundary // // if so, it can early stop further processing // length_matches_all += match.length;//doc_org_cur_len + (str ? 1 : 0) + (k < doc_org.length - 1 ? ellipsis.length : 0); // // the match positions are used to pick items for the final result more quickly // pos_matches.push([ // str.length + (str ? 1 : 0), // str.length + (str ? 1 : 0) + match.length, // k // ]); // } // str += (str ? " " : "") + match; // } // } // // if(!found){ // const text = doc_org[k]; // str += (str ? " " : "") + text; // } // else if(boundary){ // if(length_matches_all >= boundary){ // // matches has reached total boundary // break; // } // } // } // // // the markup length does not apply to the total boundary // let markup_length = pos_matches.length * (template.length - 2); // // // apply boundaries and align highlights // if(boundary_before || boundary_after || (boundary && (str.length - markup_length) > boundary)){ // // let final = ""; // let surrounded_length = (((((boundary + markup_length) - length_matches_all) / pos_matches.length) - ellipsis.length) / 2); // //if(surrounded_length < 0) surrounded_length = 0; // // let before = boundary_before || ( // surrounded_length > 0 // ? Math.floor(surrounded_length + // (boundary_after // ? surrounded_length - boundary_after // : 0)) // : 0 // ); // let after = boundary_after || ( // surrounded_length > 0 // ? Math.ceil(surrounded_length + // (boundary_before // ? surrounded_length - boundary_before // : 0)) // : 0 // ); // // //console.log(surrounded_length, before, after) // // for(let k = 0, cur, prev, next; k < pos_matches.length; k++){ // // prev = cur; // cur = next || pos_matches[k]; // next = pos_matches[k + 1]; // // let start = cur[0] - before; // let end = cur[1] + after; // let closed_left; // let closed_right; // // // if(k){ // // closed_left = 1; // // } // // // apply right limit // if(next && (end >= next[0] - before)){ // end = cur[1] + (next[0] - cur[1]) / 2 | 0; // start -= ellipsis.length + 1; // closed_right = 1; // } // // apply left limit // if(prev && (start <= prev[1] + after)){ // start = cur[0] - (cur[0] - prev[1]) / 2 | 0; // end += ellipsis.length + 1; // closed_left = 1; // // // repeat right limit // if(next && (end >= next[0] - before)){ // end = cur[1] + (next[0] - cur[1]) / 2 | 0; // closed_right = 1; // } // } // // //console.log(start, end, prev, cur, next); // // // do not clip terms // if(!clip){ // if(start){ // if(str.charAt(start) === " "){ // //start++; // } // else if(str.charAt(start - 1) !== " "){ // start = str.indexOf(" ", start); // start < 0 // ? start = cur[0] // : start;//++; // } // } // if(end < str.length){ // if(str.charAt(end - 1) === " "){ // //end--; // } // else if(str.charAt(end) !== " "){ // end = str.lastIndexOf(" ", end); // end < cur[1] // ? end = cur[1] // : end++; // } // } // } // // final += // /*(final ? " " : "") +*/ // (!closed_left && start > 0 ? ellipsis : "") + // str.substring(start, end) + // (!closed_right && cur[2] < doc_org.length - 1 ? ellipsis : ""); // // //console.log(final) // } // // str = final; // } // // if(merge){ // str = str.replace(merge, " "); // } // // res[j]["highlight"] = str; // } // // if(pluck){ // break; // } // } // // return result; // } ================================================ FILE: src/document/search.js ================================================ // COMPILER BLOCK --> import { DEBUG, PROFILER, SUPPORT_CACHE, SUPPORT_HIGHLIGHTING, SUPPORT_PERSISTENT, SUPPORT_RESOLVER, SUPPORT_STORE, SUPPORT_SUGGESTION, SUPPORT_TAGS, SUPPORT_WORKER } from "../config.js"; // <-- COMPILER BLOCK import { DocumentSearchOptions, DocumentSearchResults, EnrichedDocumentSearchResults, MergedDocumentSearchResults, MergedDocumentSearchEntry, EnrichedSearchResults, SearchResults, IntermediateSearchResults } from "../type.js"; import { create_object, is_array, is_object, is_string, inherit } from "../common.js"; import { intersect, intersect_union } from "../intersect.js"; import Document from "../document.js"; import Index from "../index.js"; import WorkerIndex from "../worker.js"; import Resolver from "../resolver.js"; import tick from "../profiler.js"; import { highlight_fields } from "./highlight.js"; /** * @param {!string|DocumentSearchOptions} query * @param {number|DocumentSearchOptions=} limit * @param {DocumentSearchOptions=} options * @param {Array=} _promises async recursion * @this Document * @returns { * DocumentSearchResults| * EnrichedDocumentSearchResults| * MergedDocumentSearchResults| * SearchResults| * IntermediateSearchResults| * EnrichedSearchResults| * Resolver | * Promise< * DocumentSearchResults| * EnrichedDocumentSearchResults| * MergedDocumentSearchResults| * SearchResults| * IntermediateSearchResults| * EnrichedSearchResults| * Resolver * > * } */ Document.prototype.search = function(query, limit, options, _promises){ PROFILER && tick("Document.search:" + !!_promises); if(!options){ if(!limit && is_object(query)){ options = /** @type {DocumentSearchOptions} */ (query); query = ""; } else if(is_object(limit)){ options = /** @type {DocumentSearchOptions} */ (limit); limit = 0; } } // if(SUPPORT_CACHE && options && options.cache){ // options.cache = false; // const res = this.searchCache(query, limit, options); // options.cache = true; // return res; // } /** @type { * DocumentSearchResults| * EnrichedDocumentSearchResults| * MergedDocumentSearchResults| * SearchResults| * IntermediateSearchResults| * EnrichedSearchResults * } */ let result = []; let result_field = []; let pluck, enrich, merge, suggest, boost, cache; let field, tag, offset, count = 0, resolve = true, highlight; if(options){ if(is_array(options)){ options = /** @type DocumentSearchOptions */ ({ index: options }); } query = options.query || query; pluck = options.pluck; merge = options.merge; boost = options.boost; field = pluck || options.field || ((field = options.index) && (field.index ? null : field)); tag = SUPPORT_TAGS && this.tag && options.tag; suggest = SUPPORT_SUGGESTION && options.suggest; resolve = !SUPPORT_RESOLVER || (options.resolve !== false); cache = SUPPORT_CACHE && options.cache; if(DEBUG){ if(SUPPORT_STORE && this.store && options.highlight && !resolve){ console.warn("Highlighting results can only be done within a resolver stage (and/or/not/xor) or when calling .resolve({ highlight: ... })"); } else if(SUPPORT_STORE && this.store && options.enrich && !resolve){ console.warn("Enrich results can only be done on a final resolver task or when calling .resolve({ enrich: true })"); } } highlight = SUPPORT_STORE && SUPPORT_HIGHLIGHTING && resolve && this.store && options.highlight; enrich = SUPPORT_STORE && (!!highlight || (resolve && this.store && options.enrich)); limit = options.limit || limit; offset = options.offset || 0; limit || (limit = (resolve ? 100 : 0)); if(tag && (!SUPPORT_PERSISTENT || !this.db || !_promises)){ // ----------------------------- // Tag-Search // ----------------------------- PROFILER && tick("Document.search:tag"); if(tag.constructor !== Array){ tag = [tag]; } let pairs = []; for(let i = 0, field; i < tag.length; i++){ field = tag[i]; if(DEBUG && is_string(field)){ throw new Error("A tag option can't be a string, instead it needs a { field: tag } format."); } // default array notation if(field.field && field.tag){ const value = field.tag; if(value.constructor === Array){ for(let k = 0; k < value.length; k++){ pairs.push(field.field, value[k]); } } else{ pairs.push(field.field, value); } } // shorter object notation else{ const keys = Object.keys(field); for(let j = 0, key, value; j < keys.length; j++){ key = keys[j]; value = field[key]; if(value.constructor === Array){ for(let k = 0; k < value.length; k++){ pairs.push(key, value[k]); } } else{ pairs.push(key, value); } } } } if(DEBUG && !pairs.length){ throw new Error("Your tag definition within the search options is probably wrong. No valid tags found."); } // tag used as pairs from this point tag = pairs; // when tags is used and no query was set, // then just return the tag indexes if(!query){ let promises = []; if(pairs.length) for(let j = 0; j < pairs.length; j+=2){ let ids; if(SUPPORT_PERSISTENT && this.db){ const index = this.index.get(pairs[j]); if(!index){ if(DEBUG){ console.warn("Tag '" + pairs[j] + ":" + pairs[j + 1] + "' will be skipped because there is no field '" + pairs[j] + "'."); } continue; } PROFILER && tick("Document.search:tag:get:" + pairs[j + 1]); promises.push(ids = index.db.tag(pairs[j + 1], limit, offset, enrich)); } else{ PROFILER && tick("Document.search:tag:get:" + pairs[j + 1]); ids = get_tag.call(this, pairs[j], pairs[j + 1], limit, offset, enrich); } result.push(!SUPPORT_RESOLVER || resolve ? { "field": pairs[j], "tag": pairs[j + 1], "result": ids } : [ids]); } if(promises.length){ const self = this; return Promise.all(promises).then(function(promises){ for(let j = 0; j < promises.length; j++){ if(!SUPPORT_RESOLVER || resolve){ result[j]["result"] = promises[j]; } else{ result[j] = promises[j]; } } return !SUPPORT_RESOLVER || resolve ? result : new Resolver(result.length > 1 ? intersect(/** @type {!Array} */ (result), 1, 0, 0, suggest, boost) : result[0], self) }); } return !SUPPORT_RESOLVER || resolve ? result : new Resolver(result.length > 1 ? intersect(/** @type {!Array} */ (result), 1, 0, 0, suggest, boost) : result[0], this) } } // upgrade pluck for resolver when missing if(SUPPORT_RESOLVER && !resolve && !pluck){ field = field || this.field; if(field){ if(is_string(field)){ pluck = field; } else{ if(is_array(field) && field.length === 1){ field = field[0]; } pluck = field.field || field.index; } } if(DEBUG && !pluck){ throw new Error("Apply resolver on document search requires either the option 'pluck' to be set or just select a single field name in your query."); } } // extend to multi field search by default if(field && field.constructor !== Array){ field = [field]; } } field || (field = this.field); let db_tag_search; let promises = ( (SUPPORT_WORKER && this.worker) || (SUPPORT_PERSISTENT && this.db) /*|| (SUPPORT_ASYNC && this.async)*/ ) && !_promises && []; // multi field search // field could be a custom set of selected fields by this query // db tag indexes are also included in this field list for(let i = 0, res, key, len; i < field.length; i++){ key = field[i]; if(SUPPORT_PERSISTENT && SUPPORT_TAGS && this.db && this.tag){ // tree is missing when it is a tag-only index (db) if(!this.tree[i]){ continue; } } let field_options; if(!is_string(key)){ field_options = key; key = field_options.field; query = field_options.query || query; limit = inherit(field_options.limit, limit); offset = inherit(field_options.offset, offset); suggest = SUPPORT_SUGGESTION && inherit(field_options.suggest, suggest); highlight = SUPPORT_STORE && SUPPORT_HIGHLIGHTING && resolve && this.store && inherit(field_options.highlight, highlight); enrich = SUPPORT_STORE && (!!highlight || (resolve && this.store && inherit(field_options.enrich, enrich))); cache = SUPPORT_CACHE && inherit(field_options.cache, cache); } if(_promises){ res = _promises[i]; } else{ PROFILER && tick("Document.search:get:" + key); const opt = field_options || options || {}; const opt_enrich = opt.enrich; const index = this.index.get(key); if(tag){ if(SUPPORT_PERSISTENT && this.db){ opt.tag = tag; opt.field = field; db_tag_search = index.db.support_tag_search; } if(!db_tag_search && opt_enrich){ opt.enrich = false; } if(!db_tag_search){ opt.limit = 0; opt.offset = 0; } } // TODO merge sorted by score // TODO add score property to results // if(merge){ // opt.resolve = false; // } res = cache ? index.searchCache(query, tag && !db_tag_search ? 0 : limit, opt) : index.search(query, tag && !db_tag_search ? 0 : limit, opt); if(tag && !db_tag_search){ opt.limit = limit; opt.offset = offset; } // restore state // if(merge){ // opt.resolve = resolve; // } if(opt_enrich) { opt.enrich = opt_enrich; } if(promises){ promises[i] = res; // collect and continue continue; } } res = res.result || res; len = res && res.length; // todo when no term was matched but tag was retrieved extend suggestion to tags // every field has to intersect against all selected tag fields if(tag && len){ const arr = []; let count = 0; // tags are only applied in resolve phase when it's a db if(SUPPORT_PERSISTENT && this.db && _promises){ if(!db_tag_search){ // retrieve tag results assigned to it's field for(let y = field.length; y < _promises.length; y++){ let ids = _promises[y]; let len = ids && ids.length; if(len){ count++; arr.push(ids); } else if(!suggest){ // no tags found return !SUPPORT_RESOLVER || resolve ? result : new Resolver(result, this) } } } } else{ // tag[] are pairs at this line for(let y = 0, ids, len; y < tag.length; y+=2){ PROFILER && tick("Document.search:tag:get:" + tag[y + 1]); ids = this.tag.get(tag[y]); if(!ids){ if(DEBUG){ console.warn("Tag '" + tag[y] + ":" + tag[y + 1] + "' will be skipped because there is no field '" + tag[y] + "'."); } if(suggest){ continue; } else{ return !SUPPORT_RESOLVER || resolve ? result : new Resolver(result, this) } } ids = ids && ids.get(tag[y + 1]); len = ids && ids.length; if(len){ count++; arr.push(ids); } else if(!suggest){ // no tags found return !SUPPORT_RESOLVER || resolve ? result : new Resolver(result, this) } } } if(count){ PROFILER && tick("Document.search:tag:intersect"); res = intersect_union(/** @type {IntermediateSearchResults} */ (res), arr, limit, offset, resolve); // intersect(arr, limit, offset) len = res.length; if(!len && !suggest){ // nothing matched return !SUPPORT_RESOLVER || resolve ? res : new Resolver(/** @type {IntermediateSearchResults} */ (res), this); } // move counter back by 1 count--; } } if(len){ result_field[count] = key; result.push(res); count++; } else if(field.length === 1){ // fast path: nothing matched return !SUPPORT_RESOLVER || resolve ? result : new Resolver(result, this); } } if(promises){ if(SUPPORT_PERSISTENT && SUPPORT_TAGS && this.db){ // todo when a tag index is never a search index this could be extracted // push tag promises to the end if(tag && tag.length && !db_tag_search){ for(let y = 0; y < tag.length; y += 2){ // it needs to retrieve data from tag pairs const index = this.index.get(tag[y]); if(!index){ if(DEBUG){ console.warn("Tag '" + tag[y] + ":" + tag[y + 1] + "' was not found because there is no field '" + tag[y] + "'."); } if(suggest){ continue; } else{ return !SUPPORT_RESOLVER || resolve ? result : new Resolver(result, this); } } PROFILER && tick("Document.search:tag:get:" + tag[y + 1]); promises.push(index.db.tag(tag[y + 1], limit, offset, /* enrich */ false)); } } } const self = this; // TODO unroll this recursion return Promise.all(promises).then(function(result){ // todo restore resolve state options && (options.resolve = resolve); if(result.length){ result = self.search(query, limit, options, /* promises: */ result); // if(!resolve && pluck && result[0]){ // result = result[0].result; // } } return result; }); } if(!count){ return !SUPPORT_RESOLVER || resolve ? result : new Resolver(result, this); } if(pluck && (!enrich || !this.store)){ result = /** @type {SearchResults|IntermediateSearchResults} */ (result[0]); return !SUPPORT_RESOLVER || resolve ? result : new Resolver(result, this); } promises = []; for(let i = 0; i < result_field.length; i++){ /** @type {SearchResults|EnrichedSearchResults} */ let res = result[i]; if(enrich && res.length && typeof res[0]["doc"] === "undefined"){ if(!SUPPORT_PERSISTENT || !this.db){ // if(res.length){ res = /** @type {EnrichedSearchResults} */ (apply_enrich.call(this, res)); // } } else{ PROFILER && tick("Document.search:doc:get"); // todo // the documents are stored on the first field promises.push(res = this.index.get(this.field[0]).db.enrich(res)); } } if(pluck){ return !SUPPORT_RESOLVER || resolve ? (highlight ? highlight_fields(/** @type {string} */ (query), res, this.index, pluck, highlight) : /** @type {SearchResults|EnrichedSearchResults} */ (res)) : new Resolver(/** @type {IntermediateSearchResults} */ (res), this); } result[i] = { "field": result_field[i], "result": /** @type {SearchResults|EnrichedSearchResults} */ (res) }; } if(enrich && SUPPORT_PERSISTENT && this.db && promises.length){ const self = this; return Promise.all(promises).then(function(promises){ for(let j = 0; j < promises.length; j++){ result[j]["result"] = promises[j]; } if(highlight){ result = highlight_fields(/** @type {string} */ (query), result, self.index, pluck, highlight); } return merge ? merge_fields(result) : /** @type {DocumentSearchResults} */ (result); }); } if(highlight){ result = highlight_fields(/** @type {string} */ (query), result, this.index, pluck, highlight); } return merge ? merge_fields(result) : /** @type {DocumentSearchResults} */ (result); } // todo support Resolver // todo when searching through multiple fields each term should // be found at least by one field to get a valid match without // using suggestion explicitly /** * @param {DocumentSearchResults} fields * @return {MergedDocumentSearchResults} */ function merge_fields(fields){ /** @type {MergedDocumentSearchResults} */ const final = []; const group_field = create_object(); const group_highlight = create_object(); for(let i = 0, field, key, res, id, entry, tmp, highlight; i < fields.length; i++){ field = fields[i]; key = field.field; res = field.result; for(let j = 0; j < res.length; j++){ entry = res[j]; // upgrade flat results typeof entry !== "object" ? entry = { "id": id = entry } : id = entry["id"]; tmp = group_field[id]; if(!tmp){ entry["field"] = group_field[id] = [key]; final.push(/** @type {!MergedDocumentSearchEntry} */ (entry)); } else{ tmp.push(key); } if(SUPPORT_HIGHLIGHTING && SUPPORT_STORE && (highlight = entry["highlight"])){ tmp = group_highlight[id]; if(!tmp){ group_highlight[id] = tmp = {}; entry["highlight"] = tmp; } tmp[key] = highlight; } } } return final; } /** * @this {Document} */ function get_tag(tag, key, limit, offset, enrich){ PROFILER && tick("Document.search:tag:get:" + key); let res = this.tag.get(tag); if(!res) return []; res = res.get(key); if(!res) return []; let len = res.length - offset; if(len > 0){ if((limit && len > limit) || offset){ res = res.slice(offset, offset + limit); } if(enrich){ res = apply_enrich.call(this, res); } } return res; } /** * @param {SearchResults} ids * @return {EnrichedSearchResults|SearchResults|Promise} * @this {Document|Index|WorkerIndex|null} */ export function apply_enrich(ids){ if(!SUPPORT_STORE || !this || !this.store) return ids; if(SUPPORT_PERSISTENT && this.db){ return this.index.get(this.field[0]).db.enrich(ids); } /** @type {EnrichedSearchResults} */ const result = new Array(ids.length); for(let x = 0, id; x < ids.length; x++){ id = ids[x]; result[x] = { "id": id, "doc": this.store.get(id) }; } return result; } ================================================ FILE: src/document.js ================================================ /**! * FlexSearch.js * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ // COMPILER BLOCK --> import { DEBUG, SUPPORT_ASYNC, SUPPORT_CACHE, SUPPORT_CHARSET, SUPPORT_ENCODER, SUPPORT_HIGHLIGHTING, SUPPORT_KEYSTORE, SUPPORT_PERSISTENT, SUPPORT_SERIALIZE, SUPPORT_STORE, SUPPORT_TAGS, SUPPORT_WORKER } from "./config.js"; // <-- COMPILER BLOCK import { IndexOptions, DocumentOptions, DocumentDescriptor, FieldOptions, StoreOptions, EncoderOptions } from "./type.js"; import StorageInterface from "./db/interface.js"; import Index from "./index.js"; import WorkerIndex from "./worker.js"; import Encoder, { fallback_encoder } from "./encoder.js"; import Cache, { searchCache } from "./cache.js"; import { is_string, is_object, parse_simple } from "./common.js"; import apply_async from "./async.js"; import { exportDocument, importDocument } from "./serialize.js"; import { KeystoreMap, KeystoreSet } from "./keystore.js"; import "./document/add.js"; import "./document/search.js"; import Charset from "./charset.js"; /** * @constructor * @param {!DocumentOptions} options * @return {Document|Promise} * @this {Document} */ export default function Document(options){ if(!this || this.constructor !== Document) { return new Document(options); } const document = /** @type DocumentDescriptor */ ( options.document || options.doc || options ); let tmp, keystore; this.tree = []; this.field = []; this.marker = []; this.key = ((tmp = document.key || document.id) && parse_tree(tmp, this.marker)) || "id"; keystore = SUPPORT_KEYSTORE && (options.keystore || 0); keystore && (this.keystore = keystore); this.fastupdate = !!options.fastupdate; // Shared Registry /** @type { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } */ this.reg = this.fastupdate && (!SUPPORT_WORKER || !options.worker) && (!SUPPORT_PERSISTENT || !options.db) ? (keystore && SUPPORT_KEYSTORE ? new KeystoreMap(keystore) : new Map()) : (keystore && SUPPORT_KEYSTORE ? new KeystoreSet(keystore) : new Set()); if(SUPPORT_STORE){ // todo support custom filter function this.storetree = (tmp = document.store || null) && tmp && tmp !== true && []; /** @type {Map|KeystoreMap} */ this.store = tmp ? ( keystore && SUPPORT_KEYSTORE ? new KeystoreMap(keystore) : new Map() ) : null; } if(SUPPORT_CACHE){ this.cache = (tmp = options.cache || null) && new Cache(tmp); // do not apply cache again for the indexes since .searchCache() // is just a wrapper over .search() options.cache = false; } if(SUPPORT_WORKER){ this.worker = options.worker || false; } if(SUPPORT_ASYNC){ this.priority = options.priority || 4; } /** * @type {Map} */ this.index = parse_descriptor.call(this, options, document); if(SUPPORT_TAGS){ this.tag = null; // TODO case-insensitive tags? if((tmp = document.tag)){ if(typeof tmp === "string"){ tmp = [tmp]; } if(tmp.length){ this.tag = new Map(); this.tagtree = []; this.tagfield = []; for(let i = 0, params, field; i < tmp.length; i++){ params = tmp[i]; field = params.field || params; if(!field){ throw new Error("The tag field from the document descriptor is undefined."); } if(params.custom){ this.tagtree[i] = params.custom; } else{ this.tagtree[i] = parse_tree(field, this.marker); if(params.filter){ if(typeof this.tagtree[i] === "string"){ // it needs an object to put a property to it this.tagtree[i] = new String(this.tagtree[i]); } this.tagtree[i]._filter = params.filter; } } // the tag fields need to be hold by indices this.tagfield[i] = field; this.tag.set(field, new Map()); } } } } // resolve worker promises and swap instances if(SUPPORT_WORKER && this.worker){ this.fastupdate = false; const promises = []; for(const index of this.index.values()){ index.then && promises.push(index); } if(promises.length){ const self = this; return Promise.all(promises).then(function(result){ let count = 0; for(const item of self.index.entries()){ const key = /** @type {string} */ (item[0]); let index = /** @type {Index|WorkerIndex | Promise} */ (item[1]); if(index.then){ index = result[count]; self.index.set(key, index); count++; } } return self; }); } } else if(SUPPORT_PERSISTENT){ if(options.db){ this.fastupdate = false; // a constructor should not return a promise // it can be awaited on "await index.db" this.mount(options.db); } } } if(SUPPORT_PERSISTENT){ /** * @param {!StorageInterface} db * @return {Promise} */ Document.prototype.mount = function(db){ if(DEBUG){ if(this.worker){ throw new Error("You can't use Worker-Indexes on a persistent model. That would be useless, since each of the persistent model acts like Worker-Index by default (Master/Slave).") } } let fields = this.field; if(SUPPORT_TAGS && this.tag){ // tag indexes are referenced by field // move tags to their field indexes respectively for(let i = 0, field; i < this.tagfield.length; i++){ field = this.tagfield[i]; let index;// = this.index.get(field); //if(!index){ // create raw index when not exists this.index.set(field, index = new Index(/** @type IndexOptions */ ({}), this.reg)); // copy and push to the field selection if(fields === this.field){ fields = fields.slice(0); } // tag indexes also needs to be upgraded to db instances fields.push(field); //} // assign reference index.tag = this.tag.get(field); } } const promises = []; const config = { db: db.db, type: db.type, fastupdate: db.fastupdate }; // upgrade all indexes to db instances for(let i = 0, index, field; i < fields.length; i++){ config.field = field = fields[i]; index = this.index.get(field); const dbi = new db.constructor(db.id, config); // take over the storage id dbi.id = db.id; promises[i] = dbi.mount(index); // add an identification property index.document = true; if(i){ // the register has to export just one time // also it's needed by the index for ID contain check index.bypass = true; } else if(SUPPORT_STORE){ // the datastore has to export one time index.store = this.store; } } const self = this; return this.db = Promise.all(promises).then(function(){ self.db = true; }); }; Document.prototype.commit = async function(/*replace, append*/){ // parallel: const promises = []; for(const index of this.index.values()){ promises.push(index.commit(/*replace, append*/)); } await Promise.all(promises); this.reg.clear(); // queued: // for(const index of this.index.values()){ // await index.db.commit(index, replace, append); // } // this.reg.clear(); }; Document.prototype.destroy = function(){ const promises = []; for(const idx of this.index.values()){ promises.push(idx.destroy()); } return Promise.all(promises); } } /** * @this {Document} * @return {Map} */ function parse_descriptor(options, document){ /** @type {Map} */ const index = new Map(); let field = document.index || document.field || document; if(is_string(field)){ field = [field]; } for(let i = 0, key, opt; i < field.length; i++){ key = field[i]; if(!is_string(key)){ opt = key; key = key.field; } opt = /** @type IndexOptions */ ( is_object(opt) ? Object.assign({}, options, opt) : options ); if(SUPPORT_WORKER && this.worker){ let encoder; // assign encoder for result highlighting if(SUPPORT_HIGHLIGHTING && SUPPORT_STORE){ encoder = opt.encoder; encoder = encoder && encoder.encode ? encoder : SUPPORT_ENCODER ? new Encoder( SUPPORT_CHARSET && typeof encoder === "string" ? Charset[encoder] : encoder || {}) : { encode: fallback_encoder }; } const worker = new WorkerIndex(opt, /** @type {Encoder} */ (encoder)); if(worker){ // worker could be a promise // it needs to be resolved and swapped later index.set(key, worker); } else{ // fallback when not supported this.worker = false; } } if(!SUPPORT_WORKER || !this.worker){ index.set(key, new Index(/** @type IndexOptions */ (opt), this.reg)); } if(opt.custom){ this.tree[i] = opt.custom; } else{ this.tree[i] = parse_tree(key, this.marker); if(opt.filter){ if(typeof this.tree[i] === "string"){ // it needs an object to put a property to it this.tree[i] = new String(this.tree[i]); } this.tree[i]._filter = opt.filter; } } this.field[i] = key; } if(SUPPORT_STORE && this.storetree){ let stores = document.store; if(is_string(stores)) stores = [stores]; for(let i = 0, store, field; i < stores.length; i++){ store = /** @type Array */ (stores[i]); field = store.field || store; if(store.custom){ this.storetree[i] = store.custom; store.custom._field = field; } else{ this.storetree[i] = parse_tree(field, this.marker); if(store.filter){ if(typeof this.storetree[i] === "string"){ // it needs an object to put a property to it this.storetree[i] = new String(this.storetree[i]); } this.storetree[i]._filter = store.filter; } } } } return index; } function parse_tree(key, marker){ const tree = key.split(":"); let count = 0; for(let i = 0; i < tree.length; i++){ key = tree[i]; // todo apply some indexes e.g. [0], [-1], [0-2] if(key[key.length - 1] === "]"){ key = key.substring(0, key.length - 2); if(key){ marker[count] = true; } } if(key){ tree[count++] = key; } } if(count < tree.length){ tree.length = count; } return count > 1 ? tree : tree[0]; } /** * @param {!number|Object} id * @param {!Object} content * @return {Document|Promise} */ Document.prototype.append = function(id, content){ return this.add(id, content, true); }; /** * @param {!number|Object} id * @param {!Object} content * @return {Document|Promise} */ Document.prototype.update = function(id, content){ return this.remove(id).add(id, content); }; /** * @param {!number|Object} id * @return {Document|Promise} */ Document.prototype.remove = function(id){ if(is_object(id)){ id = parse_simple(id, this.key); } for(const index of this.index.values()){ index.remove(id, /* skip deletion */ true); } if(this.reg.has(id)){ if(SUPPORT_TAGS && this.tag){ // when fastupdate was enabled all ids are already removed if(!this.fastupdate){ for(let field of this.tag.values()){ for(let item of field){ const tag = item[0]; const ids = item[1]; const pos = ids.indexOf(id); if(pos > -1){ ids.length > 1 ? ids.splice(pos, 1) : field.delete(tag); } } } } } if(SUPPORT_STORE && this.store){ this.store.delete(id); } this.reg.delete(id); } // the cache could be used outside the InMemory store if(SUPPORT_CACHE && this.cache){ this.cache.remove(id); } return this; }; Document.prototype.clear = function(){ const promises = []; for(const index of this.index.values()){ // db index will add clear task const promise = index.clear(); // worker indexes will return promises if(promise.then){ promises.push(promise); } } if(SUPPORT_TAGS && this.tag){ for(const tags of this.tag.values()){ tags.clear(); } } if(SUPPORT_STORE && this.store){ this.store.clear(); } if(SUPPORT_CACHE && this.cache){ this.cache.clear(); } return promises.length ? Promise.all(promises) : this }; /** * @param {number|string} id * @return {boolean|Promise} */ Document.prototype.contain = function(id){ if(SUPPORT_PERSISTENT && this.db){ return this.index.get(this.field[0]).db.has(id); } return this.reg.has(id); }; Document.prototype.cleanup = function(){ for(const index of this.index.values()){ index.cleanup(); } return this; }; if(SUPPORT_STORE){ /** * @param {number|string} id * @return {Object} */ Document.prototype.get = function(id){ if(SUPPORT_PERSISTENT && this.db){ return this.index.get(this.field[0]).db.enrich(id).then(function(result){ return (result[0] && result[0]["doc"]) || null; }); } return this.store.get(id) || null; }; /** * @param {number|string|Object} id * @param {Object} data * @return {Document} */ Document.prototype.set = function(id, data){ if(typeof id === "object"){ data = id; id = parse_simple(data, this.key); } this.store.set(id, data); return this; }; } if(SUPPORT_CACHE){ Document.prototype.searchCache = searchCache; } if(SUPPORT_SERIALIZE){ Document.prototype.export = exportDocument; Document.prototype.import = importDocument; } if(SUPPORT_ASYNC){ apply_async(Document.prototype); } ================================================ FILE: src/encoder.js ================================================ // COMPILER BLOCK --> import { DEBUG, SUPPORT_CACHE, SUPPORT_CHARSET } from "./config.js"; // <-- COMPILER BLOCK import { create_object, merge_option } from "./common.js"; import normalize_polyfill from "./charset/polyfill.js"; import { EncoderOptions } from "./type.js"; /* Custom Encoder ---------------- // Split a passed string into an Array of words: function englishEncoder(string){ return string.toLowerCase().split(/[^a-z]+/) } // For CJK split a passed string into an Array of chars: function chineseEncoder(string){ return string.replace(/\s+/, "").split("") } // Alternatively do not split the input: function fixedEncoder(string){ return [string] } Built-in Encoder ---------------------------- The main workflow follows an increasing strategy, starting from a simple .toLowerCase() to full RegExp Pipeline: 1. apply this.normalize (charset normalization) applied on the whole input string e.g. lowercase, everything you put later into (filter, matcher, stemmer, mapper, etc.) has to be normalized by definition, because it won't apply to them automatically 2. apply this.prepare (custom function, string in - string out) 3 split numerics into triplets 4. split input into terms (by one of them: split/include/exclude) 5. pre-encoded term deduplication 6. apply this.filter (stop-words) 7. apply this.stemmer (replace term endings) 8. apply this.mapper (replace chars) 9. apply this.dedupe (letter deduplication) 10. apply this.matcher (replace terms) 11. apply this.replacer (custom regex) 12. post-encoded term deduplication 13. apply this.finalize (custom function, array in - array out) */ const whitespace = /[^\p{L}\p{N}]+/u; // /[\p{Z}\p{S}\p{P}\p{C}]+/u; //const numeric_split = /(\d{3})/g; const numeric_split_length = /(\d{3})/g; const numeric_split_prev_char = /(\D)(\d{3})/g; const numeric_split_next_char = /(\d{3})(\D)/g; //.replace(/(\d{3})/g, "$1 ") //.replace(/([^\d])([\d])/g, "$1 $2") //.replace(/([\d])([^\d])/g, "$1 $2") const normalize = /*"".normalize &&*/ /[\u0300-\u036f]/g; // '´`’ʼ., //const normalize_mapper = SUPPORT_CHARSET && !normalize && normalize_polyfill; /** * @param {EncoderOptions=} options * @constructor */ export default function Encoder(options = {}){ if(!this || this.constructor !== Encoder){ // let args = Array.prototype.slice.call(arguments); // args.unshift(Encoder); // return new (Encoder.bind.apply(Encoder, args)); return new Encoder(...arguments); } if(arguments.length){ for(let i = 0; i < arguments.length; i++){ this.assign(/** @type {!EncoderOptions} */ (arguments[i])); } } else{ this.assign(/** @type {!EncoderOptions} */ (options)); } }; /** * @param {!EncoderOptions} options */ Encoder.prototype.assign = function(options){ /** * pre-processing string input * @type {Function|boolean} */ this.normalize = /** @type {Function|boolean} */ ( merge_option(options.normalize, true, this.normalize) ); // { // letter: true, // number: true, // whitespace: true, // symbol: true, // punctuation: true, // control: true, // char: "" // } let include = options.include; let tmp = include || options.exclude || options.split; let numeric; if(tmp || tmp === ""){ if(typeof tmp === "object" && tmp.constructor !== RegExp){ let regex = ""; numeric = !include; // split on whitespace by default include || ( regex += "\\p{Z}" ); if(tmp.letter){ regex += "\\p{L}"; } if(tmp.number){ regex += "\\p{N}"; numeric = !!include; } if(tmp.symbol){ regex += "\\p{S}"; } if(tmp.punctuation){ regex += "\\p{P}"; } if(tmp.control){ regex += "\\p{C}"; } if((tmp = tmp.char)){ regex += typeof tmp === "object" ? tmp.join("") : tmp; } try{ // https://github.com/nextapps-de/flexsearch/issues/410 /** * split string input into terms * @type {string|RegExp|boolean|null} */ this.split = new RegExp("[" + (include ? "^" : "") + regex + "]+", "u"); } catch(e){ if(DEBUG){ console.error("Your split configuration:", tmp, "is not supported on this platform. It falls back to using simple whitespace splitter instead: /\s+/."); } // fallback to a simple whitespace splitter this.split = /\s+/; } } else{ this.split = /** @type {string|RegExp|boolean} */ (tmp); // determine numeric encoding numeric = tmp === false || "a1a".split(tmp).length < 2; } this.numeric = merge_option(options.numeric, numeric); } else{ try{ // https://github.com/nextapps-de/flexsearch/issues/410 this.split = /** @type {string|RegExp|boolean} */ ( merge_option(this.split, whitespace) ); } catch(e){ if(DEBUG){ console.warn("This platform does not support unicode regex. It falls back to using simple whitespace splitter instead: /\s+/."); } // fallback to a simple whitespace splitter this.split = /\s+/; } this.numeric = merge_option(options.numeric, merge_option(this.numeric, true)); } /** * post-processing terms * @type {Function|null} */ this.prepare = /** @type {Function|null} */ ( merge_option(options.prepare, null, this.prepare) ); /** * final processing * @type {Function|null} */ this.finalize = /** @type {Function|null} */ ( merge_option(options.finalize, null, this.finalize) ); // assign the normalization fallback to the mapper // if(SUPPORT_CHARSET && !normalize){ // this.mapper = new Map( // /** @type {Array>} */ ( // normalize_polyfill // ) // ); // } tmp = options.filter; this.filter = typeof tmp === "function" ? tmp : merge_option(tmp && new Set(tmp), null, this.filter); this.dedupe = merge_option(options.dedupe, true, this.dedupe); this.matcher = merge_option((tmp = options.matcher) && new Map(tmp), null, this.matcher); this.mapper = merge_option((tmp = options.mapper) && new Map(tmp), null, this.mapper); this.stemmer = merge_option((tmp = options.stemmer) && new Map(tmp), null, this.stemmer); this.replacer = merge_option(options.replacer, null, this.replacer); this.minlength = merge_option(options.minlength, 1, this.minlength); this.maxlength = merge_option(options.maxlength, 1024, this.maxlength); this.rtl = merge_option(options.rtl, false, this.rtl); // auto-balanced cache this.cache = tmp = merge_option(options.cache, true, this.cache); if(tmp){ this.timer = null; this.cache_size = typeof tmp === "number" ? tmp : 2e5; this.cache_enc = new Map(); this.cache_term = new Map(); this.cache_enc_length = 128; this.cache_term_length = 128; } // regex temporary state this.matcher_str = ""; this.matcher_test = null; this.stemmer_str = ""; this.stemmer_test = null; // prebuilt // if(this.filter && this.split){ // for(const key of this.filter){ // const tmp = key.replace(this.split, ""); // if(key !== tmp){ // this.filter.delete(key); // this.filter.add(tmp); // } // } // } if(this.matcher){ for(const key of this.matcher.keys()){ this.matcher_str += (this.matcher_str ? "|" : "") + key; } } if(this.stemmer){ for(const key of this.stemmer.keys()){ this.stemmer_str += (this.stemmer_str ? "|" : "") + key; } } // if(SUPPORT_COMPRESSION){ // this.compression = merge_option(options.compress || options.compression, 0, this.compression); // if(this.compression && !table){ // table = new Array(radix); // for(let i = 0; i < radix; i++) table[i] = i + 33; // table = String.fromCharCode.apply(null, table); // } // } return this; }; Encoder.prototype.addStemmer = function(match, replace){ this.stemmer || (this.stemmer = new Map()); this.stemmer.set(match, replace); this.stemmer_str += (this.stemmer_str ? "|" : "") + match; this.stemmer_test = null; this.cache && clear(this); return this; }; Encoder.prototype.addFilter = function(term){ if(typeof term === "function"){ // does not support merge yet this.filter = term; //merge_option(term, term, this.filter); } else{ this.filter || (this.filter = new Set()); this.filter.add(term); } this.cache && clear(this); return this; }; /** * Replace a single char * @param {string} char_match * @param {string} char_replace * @return {Encoder} * @suppress {invalidCasts} */ Encoder.prototype.addMapper = function(char_match, char_replace){ // regex: if(typeof char_match === "object"){ return this.addReplacer(/** @type {RegExp} */ (char_match), char_replace); } // not a char: if(char_match.length > 1){ return this.addMatcher(char_match, char_replace); } this.mapper || (this.mapper = new Map()); this.mapper.set(char_match, char_replace); this.cache && clear(this); return this; }; /** * Replace a string * @param {string} match * @param {string} replace * @return {Encoder} * @suppress {invalidCasts} */ Encoder.prototype.addMatcher = function(match, replace){ // regex: if(typeof match === "object"){ return this.addReplacer(/** @type {RegExp} */ (match), replace); } // a single char: // only downgrade when dedupe is on or mapper already was filled if(match.length < 2 && (this.dedupe || this.mapper)){ return this.addMapper(match, replace); } this.matcher || (this.matcher = new Map()); this.matcher.set(match , replace); this.matcher_str += (this.matcher_str ? "|" : "") + match; this.matcher_test = null; this.cache && clear(this); return this; }; /** * @param {RegExp} regex * @param {string} replace * @return {Encoder} * @suppress {invalidCasts} */ Encoder.prototype.addReplacer = function(regex, replace){ if(typeof regex === "string"){ return this.addMatcher(/** @type {string} */ (regex), replace); } this.replacer || (this.replacer = []); this.replacer.push(regex, replace); this.cache && clear(this); return this; }; /** * @param {!string} str * @param {boolean=} dedupe_terms Note: term deduplication will break the context chain * @return {!Array} */ Encoder.prototype.encode = function(str, dedupe_terms){ if(this.cache && str.length <= this.cache_enc_length){ if(this.timer){ if(this.cache_enc.has(str)){ return this.cache_enc.get(str); } } else{ this.timer = setTimeout(clear, 50, this); } } // apply charset normalization if(this.normalize){ if(typeof this.normalize === "function"){ str = this.normalize(str); } else if(normalize){ str = str.normalize("NFKD").replace(normalize, "").toLowerCase(); } else{ str = str.toLowerCase(); } } // apply custom encoder (can replace split) if(this.prepare){ str = this.prepare(str); } // split numbers into triplets if(this.numeric && str.length > 3){ str = str.replace(numeric_split_prev_char, "$1 $2") .replace(numeric_split_next_char, "$1 $2") .replace(numeric_split_length, "$1 "); } // if(this.matcher && (str.length > 1)){ // this.matcher_test || ( // this.matcher_test = new RegExp("(" + this.matcher_str + ")", "g") // ); // str = str.replace(this.matcher_test, match => this.matcher.get(match)); // } // if(this.stemmer){ // this.stemmer_test || ( // this.stemmer_test = new RegExp("(?!\\b)(" + this.stemmer_str + ")(\\b|_)", "g") // ); // str = str.replace(this.stemmer_test, match => this.stemmer.get(match)); // } const skip = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer); let final = []; let dupes = create_object(); let last_term; let last_term_enc; let words = this.split || this.split === "" ? str.split(/** @type {string|RegExp} */ (this.split)) : [str]; // str; for(let i = 0, word, base; i < words.length; i++){ if(!(word = base = words[i])){ continue; } if(word.length < this.minlength || word.length > this.maxlength){ continue; } if(dedupe_terms){ if(dupes[word]){ continue; } dupes[word] = 1; } else{ if(last_term === word){ continue; } last_term = word; } if(skip) { final.push(word); continue; } if(this.filter && ( typeof this.filter === "function" ? !this.filter(word) : this.filter.has(word) )){ continue; } if(this.cache && word.length <= this.cache_term_length){ if(this.timer){ const tmp = this.cache_term.get(word); if(tmp || tmp === ""){ tmp && final.push(tmp); continue; } } else{ this.timer = setTimeout(clear, 50, this); } } // from here minlength should not apply again // when the input string is further shrinking // it needs to apply stemmer before bigger transformations // it needs to apply stemmer after filter (user -> us -> filter out) if(this.stemmer){ // for(const item of this.stemmer){ // const key = item[0]; // const value = item[1]; // if(word.length > key.length && word.endsWith(key)){ // word = word.substring(0, word.length - key.length) + value; // break; // } // // const position = word.length - key.length; // // if(position > 0 && word.substring(position) === key){ // // word = word.substring(0, position) + value; // // break; // // } // } // todo compare advantages when filter/stemmer are also encoded this.stemmer_test || ( this.stemmer_test = new RegExp("(?!^)(" + this.stemmer_str + ")$") ); let old; // loop stemmer as long as anything has matched // just terms with length > 2 should need a stemmer (its -> it) // the minlength also prevents stemmer looping to cut off everything while(old !== word && word.length > 2){ old = word; word = word.replace(this.stemmer_test, match => this.stemmer.get(match)); } } // apply mapper and collapsing if(word && (this.mapper || (this.dedupe && word.length > 1))){ let final = ""; for(let i = 0, prev = "", char, tmp; i < word.length; i++){ char = word.charAt(i); if(char !== prev || !this.dedupe){ tmp = this.mapper && this.mapper.get(char); if(!tmp && tmp !== "") final += (prev = char); else if((tmp !== prev || !this.dedupe) && (prev = tmp)) final += tmp; } } word = final; } // apply matcher if(this.matcher && (word.length > 1)){ this.matcher_test || ( this.matcher_test = new RegExp("(" + this.matcher_str + ")", "g") ); word = word.replace(this.matcher_test, match => this.matcher.get(match)); } // apply custom regex if(word && this.replacer){ for(let i = 0; word && (i < this.replacer.length); i+=2){ word = word.replace(this.replacer[i], this.replacer[i+1]); } } // slower variants for removing same chars in a row: //word = word.replace(/([^0-9])\1+/g, "$1"); //word = word.replace(/(.)\1+/g, "$1"); //word = word.replace(/(?<=(.))\1+/g, ""); if(this.cache && base.length <= this.cache_term_length){ this.cache_term.set(base, word); if(this.cache_term.size > this.cache_size){ this.cache_term.clear(); this.cache_term_length = this.cache_term_length / 1.1 | 0; } } if(word){ if(word !== base){ if(dedupe_terms){ if(dupes[word]){ continue; } dupes[word] = 1; } else{ if(last_term_enc === word){ continue; } last_term_enc = word; } } final.push(word); } } if(this.finalize){ final = this.finalize(final) || final; } if(this.cache && str.length <= this.cache_enc_length){ this.cache_enc.set(str, final); if(this.cache_enc.size > this.cache_size){ this.cache_enc.clear(); this.cache_enc_length = this.cache_enc_length / 1.1 | 0; } } return final; }; export function fallback_encoder(str){ return str.normalize("NFKD") .replace(normalize, "") .toLowerCase() .trim() .split(/\s+/); } // Encoder.prototype.compress = function(str) { // // //return str; // //if(!str) return str; // // if(SUPPORT_CACHE && this.cache && str.length <= this.cache_term_length){ // if(this.timer){ // if(this.cache_cmp.has(str)){ // return this.cache_cmp.get(str); // } // } // else{ // this.timer = setTimeout(clear, 0, this); // } // } // // const result = typeof this.compression === "function" // ? this.compression(str) // : hash(str); //window.hash(str); // // if(SUPPORT_CACHE && this.cache && str.length <= this.cache_term_length){ // this.cache_cmp.set(str, result); // this.cache_cmp.size > this.cache_size && // this.cache_cmp.clear(); // } // // return result; // }; // function hash(str){ // return str; // } /** * @param {Encoder} self */ function clear(self){ self.timer = null; self.cache_enc.clear(); self.cache_term.clear(); } ================================================ FILE: src/index/add.js ================================================ // COMPILER BLOCK --> import { SUPPORT_COMPRESSION, SUPPORT_KEYSTORE, SUPPORT_PERSISTENT } from "../config.js"; // <-- COMPILER BLOCK import { create_object } from "../common.js"; import Index, { autoCommit } from "../index.js"; import default_compress from "../compress.js"; import { KeystoreArray, KeystoreMap } from "../keystore.js"; // TODO: // string + number as text // boolean, null, undefined as ? /** * @param {!number|string} id * @param {!string} content * @param {boolean=} _append * @param {boolean=} _skip_update */ Index.prototype.add = function(id, content, _append, _skip_update){ if(content && (id || (id === 0))){ // todo check skip_update //_skip_update = false; if(!_skip_update && !_append){ if(this.reg.has(id)){ return this.update(id, content); } } const depth = this.depth; // do not force a string as input // https://github.com/nextapps-de/flexsearch/issues/432 content = this.encoder.encode(content, !depth); const term_count = content.length; if(term_count){ // check context dupes to skip all contextual redundancy along a document const dupes_ctx = create_object(); const dupes = create_object(); const resolution = this.resolution; for(let i = 0; i < term_count; i++){ let term = content[this.rtl ? term_count - 1 - i : i]; let term_length = term.length; // skip dupes will break the context chain if(term_length && (depth || !dupes[term])){ let score = this.score ? this.score(content, term, i, null, 0) : get_score(resolution, term_count, i); let token = ""; switch(this.tokenize){ // Swap: Remove: // -------------------- // ABCDE ABCDE // A[CB]DE A[]CDE // AB[DC]E AB[]DE // ABC[ED] ABC[]E // ABCD[] case "tolerant": this._push_index(dupes, term, score, id, _append); if(term_length > 2){ for(let x = 1, char_a, char_b, prt_1, prt_2; x < term_length - 1; x++){ char_a = term.charAt(x); char_b = term.charAt(x + 1); prt_1 = term.substring(0, x) + char_b; prt_2 = term.substring(x + 2); // swapped letters token = prt_1 /*+ char_b*/ + char_a + prt_2; this._push_index(dupes, token, score, id, _append); // missing letters token = prt_1 /*+ char_b*/ + prt_2; this._push_index(dupes, token, score, id, _append); } // missing last letter this._push_index(dupes, term.substring(0, term.length - 1), score, id, _append); } break; case "full": if(term_length > 2){ for(let x = 0, _x; x < term_length; x++){ for(let y = term_length; y > x; y--){ token = term.substring(x, y); _x = this.rtl ? term_length - 1 - x : x; const partial_score = this.score ? this.score(content, term, i, token, _x) : get_score(resolution, term_count, i, term_length, _x); this._push_index(dupes, token, partial_score, id, _append); } } break; } // fallthrough to the next case when term length < 3 case "bidirectional": case "reverse": // skip the last round (this token exists already in "forward") if(term_length > 1){ for(let x = term_length - 1; x > 0; x--){ token = term[ this.rtl ? term_length - 1 - x : x ] + token; const partial_score = this.score ? this.score(content, term, i, token, x) : get_score(resolution, term_count, i, term_length, x); this._push_index(dupes, token, partial_score, id, _append); } token = ""; } // fallthrough to the next case to apply forward also case "forward": if(term_length > 1){ for(let x = 0; x < term_length; x++){ token += term[ this.rtl ? term_length - 1 - x : x ]; this._push_index(dupes, token, score, id, _append); } break; } // fallthrough to next case when token has a length of 1 default: // "strict": this._push_index(dupes, term, score, id, _append); // context is just supported by tokenizer "strict" if(depth && (term_count > 1) && (i < (term_count - 1))){ const resolution = this.resolution_ctx; const keyword = term; const size = Math.min( depth + 1, this.rtl ? i + 1 : term_count - i ); for(let x = 1; x < size; x++){ term = content[ this.rtl ? term_count - 1 - i - x : i + x ]; const swap = this.bidirectional && (term > keyword); const context_score = this.score ? this.score(content, keyword, i, term, x - 1) : get_score(resolution + ((term_count / 2) > resolution ? 0 : 1), term_count, i, size - 1, x - 1); this._push_index( dupes_ctx, swap ? keyword : term, context_score, id, _append, swap ? term : keyword ); } } } } } this.fastupdate || this.reg.add(id); } } if(SUPPORT_PERSISTENT && this.db){ this.commit_task.push(_append ? { "ins": id } : { "del": id }); this.commit_auto && autoCommit(this); } return this; }; /** * @private * @param dupes * @param term * @param score * @param id * @param {boolean=} append * @param {string=} keyword */ Index.prototype._push_index = function(dupes, term, score, id, append, keyword){ let res, arr; if(!(res = dupes[term]) || (keyword && !res[keyword])){ if(keyword){ dupes = res || (dupes[term] = create_object()); dupes[keyword] = 1; if(SUPPORT_COMPRESSION && this.compress){ keyword = default_compress(keyword); } arr = this.ctx; res = arr.get(keyword); res ? arr = res : arr.set( keyword, arr = SUPPORT_KEYSTORE && this.keystore ? new KeystoreMap(this.keystore) : new Map() ); } else{ arr = this.map; dupes[term] = 1; } if(SUPPORT_COMPRESSION && this.compress){ term = default_compress(term); } res = arr.get(term); res ? arr = res : arr.set(term, arr = res = []); if(append){ for(let i = 0, arr; i < res.length; i++){ arr = res[i]; if(arr && arr.includes(id)){ if(i <= score){ // already included in the right slot return; } else{ // update slot arr.splice(arr.indexOf(id), 1); if(this.fastupdate){ const tmp = this.reg.get(id); tmp && tmp.splice(tmp.indexOf(arr), 1); } } break; } } } // the ID keystore array will upgrade automatically arr = arr[score] || (arr[score] = []); arr.push(id); // auto-upgrade to a keystore array if max size exceeded if(SUPPORT_KEYSTORE){ if(arr.length === 2**31-1 /*|| !(arr instanceof KeystoreArray)*/){ const keystore = new KeystoreArray(arr); if(this.fastupdate){ for(let value of this.reg.values()){ if(value.includes(arr)){ value[value.indexOf(arr)] = keystore; } } } res[score] = arr = keystore; } } // add a reference to the register for fast updates if(this.fastupdate){ const tmp = this.reg.get(id); tmp ? tmp.push(arr) : this.reg.set(id, [arr]); } } } /** * @param {number} resolution * @param {number} length * @param {number} i * @param {number=} term_length * @param {number=} x * @returns {number} */ function get_score(resolution, length, i, term_length, x){ // console.log("resolution", resolution); // console.log("length", length); // console.log("term_length", term_length); // console.log("i", i); // console.log("x", x); // console.log((resolution - 1) / (length + (term_length || 0)) * (i + (x || 0)) + 1); // the first resolution slot is reserved for the best match // when a query matches the first word(s). // also, to stretch the score to the whole range of resolution, the // calculation is shifted by one and cut the floating point. // this needs the resolution "1" to be handled additionally. // do not stretch the resolution more than the term length will // improve performance and memory, also it improves scoring in // most cases between a short document and a long document return i && (resolution > 1) ? ( (length + (term_length || 0)) <= resolution ? i + (x || 0) : ((resolution - 1) / (length + (term_length || 0)) * (i + (x || 0)) + 1) | 0 ): 0; } ================================================ FILE: src/index/remove.js ================================================ // COMPILER BLOCK --> import { SUPPORT_CACHE, SUPPORT_PERSISTENT } from "../config.js"; // <-- COMPILER BLOCK import { is_array } from "../common.js"; import Index, { autoCommit } from "../index.js"; import { KeystoreMap } from "../keystore.js"; /** * @param {!number|string} id * @param {boolean=} _skip_deletion */ Index.prototype.remove = function(id, _skip_deletion){ const refs = this.reg.size && ( this.fastupdate ? this.reg.get(id) : this.reg.has(id) ); if(refs){ if(this.fastupdate){ // fast updates did not fully clean up the key entries for(let i = 0, tmp, len; i < refs.length; i++){ if((tmp = refs[i]) && (len = tmp.length)){ // todo check //if(tmp.length < 1) throw new Error("invalid length"); //if(tmp.indexOf(id) < 0) throw new Error("invalid id"); if(tmp[len - 1] === id){ tmp.pop(); } else{ const index = tmp.indexOf(id); if(index >= 0){ tmp.splice(index, 1); } } } else{ // todo investigate empty entries // console.log(tmp) } } // todo variation which cleans up, requires to push [ctx, key] instead of arr to the index.reg // for some reason pushing [ctx, key] as reference will increase memory drastically // todo test again by using set.add(ref) instead of [ref] // for(let i = 0, arr, term, keyword; i < refs.length; i++){ // arr = refs[i]; // if(typeof arr === "string"){ // arr = this.map.get(term = arr); // } // else{ // arr = this.ctx.get(keyword = arr[0]); // arr && (arr = arr.get(arr[1])); // } // let counter = 0, found; // if(arr && arr.length){ // for(let j = 0, tmp; j < arr.length; j++){ // if((tmp = arr[j])){ // if(!found && tmp.length){ // const index = tmp.indexOf(id); // if(index >= 0){ // tmp.splice(index, 1); // // the index [ctx, key]:[res, id] is unique // found = 1; // } // } // if(tmp.length){ // counter++; // if(found){ // break; // } // } // else{ // delete arr[j]; // } // } // } // } // if(!counter){ // keyword // ? this.ctx.delete(keyword) // : this.map.delete(term); // } // } } else{ remove_index(this.map, id/*, this.resolution*/); this.depth && remove_index(this.ctx, id/*, this.resolution_ctx*/); } _skip_deletion || this.reg.delete(id); } if(SUPPORT_PERSISTENT && this.db){ this.commit_task.push({ "del": id }); this.commit_auto && autoCommit(this); //return this.db.remove(id); } // the cache could be used outside the InMemory store if(SUPPORT_CACHE && this.cache){ this.cache.remove(id); } return this; }; /** * When called without passing ID it just will clean up * @param {!Map|KeystoreMap|Array>} map * @param {!number|string=} id * @return {number} */ export function remove_index(map, id){ // a check counter of filled resolution slots // to prevent removing the field let count = 0; let cleanup = typeof id === "undefined"; if(is_array(map)){ for(let x = 0, arr, index, found; x < map.length; x++){ if((arr = map[x]) && arr.length){ if(cleanup){ return 1; } else{ index = arr.indexOf(id); if(index >= 0){ if(arr.length > 1){ arr.splice(index, 1); // the index key:[res, id] is unique return 1; } else{ // remove resolution slot delete map[x]; if(count){ return 1; } found = 1; } } else{ if(found){ return 1; } count++; } } } } } else for(let item of map.entries()){ const key = item[0]; const value = item[1]; const tmp = remove_index(value, id); tmp ? count++ : map.delete(key); } return count; } ================================================ FILE: src/index/search.js ================================================ // COMPILER BLOCK --> import { SUPPORT_CACHE, SUPPORT_COMPRESSION, SUPPORT_DOCUMENT, SUPPORT_PERSISTENT, SUPPORT_RESOLVER, SUPPORT_SUGGESTION, SUPPORT_TAGS } from "../config.js"; // <-- COMPILER BLOCK import { SearchOptions, SearchResults, EnrichedSearchResults, IntermediateSearchResults } from "../type.js"; import { create_object, is_object, sort_by_length_down } from "../common.js"; import Index from "../index.js"; import default_compress from "../compress.js"; import Resolver from "../resolver.js"; import { intersect } from "../intersect.js"; import resolve_default from "../resolve/default.js"; /** * @param {string|SearchOptions} query * @param {number|SearchOptions=} limit * @param {SearchOptions=} options * @return { * SearchResults|EnrichedSearchResults|Resolver | * Promise * } */ Index.prototype.search = function(query, limit, options){ if(!options){ if(!limit && typeof query === "object"){ options = /** @type {!SearchOptions} */ (query); query = ""; } else if(typeof limit === "object"){ options = /** @type {!SearchOptions} */ (limit); limit = 0; } } if(SUPPORT_CACHE && options && options.cache){ options.cache = false; const res = this.searchCache(query, limit, options); options.cache = true; return res; } /** @type {!Array} */ let result = []; let length; let context, suggest, offset = 0, resolve, tag, boost, resolution, // enrich is internally used just // for the persistent indexes enrich; if(options){ query = options.query || query; limit = options.limit || limit; offset = options.offset || 0; context = options.context; suggest = SUPPORT_SUGGESTION && options.suggest; resolve = !SUPPORT_RESOLVER || options.resolve; enrich = resolve && options.enrich; boost = SUPPORT_DOCUMENT && options.boost; resolution = options.resolution; tag = SUPPORT_DOCUMENT && SUPPORT_TAGS && SUPPORT_PERSISTENT && this.db && options.tag; } if(typeof resolve === "undefined"){ resolve = !SUPPORT_RESOLVER || this.resolve; } context = this.depth && context !== false; // do not force a string as input // https://github.com/nextapps-de/flexsearch/issues/432 /** @type {Array} */ let query_terms = this.encoder.encode(query, !context); length = query_terms.length; limit = /** @type {!number} */ (limit || (resolve ? 100 : 0)); // fast path single term if(length === 1){ return single_term_query.call(this, query_terms[0], // term "", // ctx limit, offset, resolve, enrich, tag ); } // fast path single context if(length === 2 && context && !suggest){ return single_term_query.call(this, query_terms[1], // term query_terms[0], // ctx limit, offset, resolve, enrich, tag ); } let dupes = create_object(); let index = 0, keyword; if(context){ // start with context right away keyword = query_terms[0]; index = 1; } // else { // // sorting terms will break the context chain // // bigger terms have a less occurrence // // this might reduce the intersection task // query_terms.sort(sort_by_length_down); // } if(!resolution && resolution !== 0){ resolution = keyword ? this.resolution_ctx : this.resolution; } // from this point there are just multi-term queries if(SUPPORT_PERSISTENT && this.db){ if(this.db.search){ // when the configuration is not supported it returns false const result = this.db.search(this, query_terms, limit, offset, suggest, resolve, enrich, tag); if(result !== false) return result; } const self = this; return (async function(){ for(let arr, term; index < length; index++){ term = query_terms[index]; if(term && !dupes[term]){ dupes[term] = 1; arr = await self._get_array(term, keyword, 0, 0, false, false); arr = add_result(arr, /** @type {Array} */ (result), suggest, resolution); if(arr){ result = arr; break; } if(keyword){ // the context is a moving window where the keyword is going forward like a cursor // 1. when suggestion enabled just forward keyword if term was found // 2. as long as the result is empty forward the pointer also if(!suggest || !arr || !result.length){ keyword = term; } } } // fallback to non-contextual search when no result was found if(suggest && keyword && (index === length - 1)){ if(!result.length){ resolution = self.resolution; keyword = ""; index = -1; dupes = create_object(); } } } return return_result( result, resolution, /** @type {!number} */ (limit), offset, suggest, boost, resolve ); }()); } for(let arr, term; index < length; index++){ term = query_terms[index]; // todo should the dupe check applied on [keyword:term]? if(term && !dupes[term]){ dupes[term] = 1; arr = this._get_array(term, keyword, 0, 0, false, false); arr = add_result(arr, /** @type {Array} */ (result), suggest, resolution); if(arr){ result = arr; break; } if(keyword){ // the context is a moving window where the keyword is going forward like a cursor // 1. when suggestion enabled just forward keyword if term was found // 2. as long as the result is empty forward the pointer also if(!suggest || !arr || !result.length){ keyword = term; } } } // fallback to non-contextual search when no result was found if(suggest && keyword && (index === length - 1)){ if(!result.length){ resolution = this.resolution; keyword = ""; index = -1; dupes = create_object(); } } } return return_result( result, resolution, /** @type {!number} */ (limit), offset, suggest, boost, resolve ); }; /** * @param {!Array} result * @param {number} resolution * @param {number} limit * @param {number=} offset * @param {boolean=} suggest * @param {number=} boost * @param {boolean=} resolve * @return { * SearchResults|EnrichedSearchResults|Resolver | * Promise * } */ function return_result(result, resolution, limit, offset, suggest, boost, resolve){ let length = result.length; let final = result; if(length > 1){ final = intersect( result, resolution, limit, offset, suggest, boost, resolve ); } else if(length === 1){ return !SUPPORT_RESOLVER || resolve ? resolve_default.call(null, result[0], limit, offset ) : new Resolver(result[0], this); } return !SUPPORT_RESOLVER || resolve ? final : new Resolver(final, this); } /** * @param {!string} term * @param {string|null} keyword * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @param {string=} tag * @this {Index} * @return { * SearchResults|EnrichedSearchResults|Resolver | * Promise * } */ function single_term_query(term, keyword, limit, offset, resolve, enrich, tag){ const result = this._get_array( term, keyword, limit, offset, resolve, enrich, tag ); if(SUPPORT_PERSISTENT && this.db){ return result.then(function(result){ return !SUPPORT_RESOLVER || resolve ? result || [] : new Resolver(result, this); }); } return result && result.length ? (!SUPPORT_RESOLVER || resolve ? resolve_default.call(this, /** @type {SearchResults|EnrichedSearchResults} */ (result), limit, offset) : new Resolver(result, this) ) : !SUPPORT_RESOLVER || resolve ? [] : new Resolver([], this); } /** * Returns a 1-dimensional finalized array when the result is done (fast path return), * returns false when suggestions is enabled and no result was found, * or returns nothing when a set was pushed successfully to the results * * @private * @param {IntermediateSearchResults} arr * @param {Array} result * @param {boolean=} suggest * @param {number=} resolution * @return {Array|undefined} */ function add_result(arr, result, suggest, resolution){ let word_arr = []; if(arr && arr.length){ // short when resolution does not exceed: if(arr.length <= resolution){ result.push(arr); // return nothing will continue the query return; } // apply reduced resolution for queries for(let x = 0, tmp; x < resolution; x++){ if((tmp = arr[x])){ word_arr[x] = tmp; } } if(word_arr.length){ result.push(word_arr); // return nothing will continue the query return; } } // 1. return an empty array will stop the loop // 2. return a false value to prevent stop when using suggestions if(!suggest) return word_arr; } /** * @param {!string} term * @param {string|null} keyword * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {boolean=} enrich * @param {string=} tag * @return { * IntermediateSearchResults|EnrichedSearchResults | * Promise * } */ Index.prototype._get_array = function(term, keyword, limit, offset, resolve, enrich, tag){ let arr, swap; if(keyword){ swap = this.bidirectional && (term > keyword); if(swap){ swap = keyword; keyword = term; term = swap; } } if(SUPPORT_COMPRESSION && this.compress){ term = default_compress(term); keyword && (keyword = default_compress(keyword)); } if(SUPPORT_PERSISTENT && this.db){ return this.db.get( term, keyword, limit, offset, resolve, enrich, tag ); } if(keyword){ // the frequency of the starting letter is slightly less // on the last half of the alphabet (m-z) in almost every latin language, // so we sort downwards (https://en.wikipedia.org/wiki/Letter_frequency) arr = this.ctx.get(keyword); arr = arr && arr.get(term); } else{ arr = this.map.get(term); } return arr; } ================================================ FILE: src/index.js ================================================ /**! * FlexSearch.js * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/flexsearch */ // COMPILER BLOCK --> import { RELEASE, DEBUG, PROFILER, SUPPORT_ENCODER, SUPPORT_CACHE, SUPPORT_ASYNC, SUPPORT_SERIALIZE, SUPPORT_PERSISTENT, SUPPORT_COMPRESSION, SUPPORT_KEYSTORE, SUPPORT_RESOLVER, SUPPORT_CHARSET } from "./config.js"; // <-- COMPILER BLOCK import { IndexOptions, ContextOptions, EncoderOptions } from "./type.js"; import Encoder, { fallback_encoder } from "./encoder.js"; import Cache, { searchCache } from "./cache.js"; import Charset from "./charset.js"; import { KeystoreMap, KeystoreSet } from "./keystore.js"; import { is_array, is_string } from "./common.js"; import { exportIndex, importIndex, serialize } from "./serialize.js"; import { remove_index } from "./index/remove.js"; //import default_encoder from "./charset/latin/default.js"; import apply_preset from "./preset.js"; import apply_async from "./async.js"; import tick from "./profiler.js"; import "./index/add.js"; import "./index/search.js"; import "./index/remove.js"; if(DEBUG && RELEASE === "source"){ typeof console !== "undefined" && console.log && console.log( "You see this warning, because it looks like you are using the source folder of FlexSearch which is not intended to use directly. Consider using one of the builds from the /dist/ folder instead. More information: https://github.com/nextapps-de/flexsearch?tab=readme-ov-file#load-library-nodejs-esm-legacy-browser" ); } /** * @constructor * @param {IndexOptions|string=} options Options or preset as string * @param {Map|Set|KeystoreSet|KeystoreMap=} _register */ export default function Index(options, _register){ if(!this || this.constructor !== Index){ return new Index(options); } PROFILER && tick("Index.create"); options = /** @type IndexOptions */ ( options ? apply_preset(options) : {} ); /** @type {*} */ let tmp = options.context; /** @type ContextOptions */ const context = /** @type ContextOptions */ ( tmp === true ? { depth: 1 } : tmp || {} ); const encoder = SUPPORT_CHARSET && is_string(options.encoder) ? Charset[options.encoder] : options.encode || options.encoder || ( SUPPORT_ENCODER ? {} /*default_encoder*/ : fallback_encoder ); /** @type Encoder */ this.encoder = encoder.encode ? encoder : typeof encoder === "object" ? (SUPPORT_ENCODER ? new Encoder(/** @type {EncoderOptions} */ (encoder)) : encoder ) : { encode: encoder }; if(SUPPORT_COMPRESSION){ this.compress = options.compress || options.compression || false; } this.resolution = options.resolution || 9; this.tokenize = tmp = ((tmp = options.tokenize) && (tmp !== "default") && (tmp !== "exact") && tmp) || "strict"; this.depth = (tmp === "strict" && context.depth) || 0; this.bidirectional = context.bidirectional !== false; this.fastupdate = !!options.fastupdate; this.score = options.score || null; if(DEBUG){ if(context && context.depth && this.tokenize !== "strict"){ console.warn("Context-Search could not applied, because it is just supported when using the tokenizer \"strict\".") } } tmp = SUPPORT_KEYSTORE && (options.keystore || 0); tmp && (this.keystore = tmp); this.map = tmp && SUPPORT_KEYSTORE ? new KeystoreMap(tmp) : new Map(); this.ctx = tmp && SUPPORT_KEYSTORE ? new KeystoreMap(tmp) : new Map(); /** @type { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } */ this.reg = _register || ( this.fastupdate ? (tmp && SUPPORT_KEYSTORE ? new KeystoreMap(tmp) : new Map()) : (tmp && SUPPORT_KEYSTORE ? new KeystoreSet(tmp) : new Set()) ); this.resolution_ctx = context.resolution || 3; this.rtl = (encoder.rtl) || options.rtl || false; if(SUPPORT_CACHE){ this.cache = (tmp = options.cache || null) && new Cache(tmp); } if(SUPPORT_RESOLVER){ this.resolve = options.resolve !== false; } if(SUPPORT_PERSISTENT){ if((tmp = options.db)){ this.db = this.mount(tmp); } this.commit_auto = options.commit !== false; this.commit_task = []; this.commit_timer = null; } if(SUPPORT_ASYNC){ this.priority = options.priority || 4; } } if(SUPPORT_PERSISTENT){ Index.prototype.mount = function(db){ if(this.commit_timer){ clearTimeout(this.commit_timer); this.commit_timer = null; } return db.mount(this); }; Index.prototype.commit = function(/*replace*/){ if(this.commit_timer){ clearTimeout(this.commit_timer); this.commit_timer = null; } return this.db.commit(this/*, replace*/); }; Index.prototype.destroy = function(){ if(this.commit_timer){ clearTimeout(this.commit_timer); this.commit_timer = null; } return this.db.destroy(); }; } /** * @param {!Index} self */ export function autoCommit(self/*, replace, append*/){ if(!self.commit_timer){ self.commit_timer = setTimeout(function(){ self.commit_timer = null; self.db.commit(self/*, replace, append*/); }, 1); } } Index.prototype.clear = function(){ this.map.clear(); this.ctx.clear(); this.reg.clear(); if(SUPPORT_CACHE){ this.cache && this.cache.clear(); } if(SUPPORT_PERSISTENT && this.db){ this.commit_timer && clearTimeout(this.commit_timer); this.commit_timer = null; this.commit_task = [];// [{ "clear": true }]; return this.db.clear(); } return this; }; /** * @param {!number|string} id * @param {!string} content */ Index.prototype.append = function(id, content){ return this.add(id, content, true); }; /** * @param {number|string} id * @return {boolean|Promise} */ Index.prototype.contain = function(id){ return SUPPORT_PERSISTENT && this.db ? this.db.has(id) : this.reg.has(id); }; Index.prototype.update = function(id, content){ const self = this; const res = this.remove(id); return res && res.then ? res.then(() => self.add(id, content)) : this.add(id, content); }; // /** // * @param map // * @return {number} // */ // // function cleanup_index(map){ // // let count = 0; // // if(is_array(map)){ // for(let i = 0, arr; i < map.length; i++){ // (arr = map[i]) && // (count += arr.length); // } // } // else for(const item of map.entries()){ // const key = item[0]; // const value = item[1]; // const tmp = cleanup_index(value); // tmp ? count += tmp // : map.delete(key); // } // // return count; // } Index.prototype.cleanup = function(){ if(!this.fastupdate){ DEBUG && console.info("Cleanup the index isn't required when not using \"fastupdate\"."); return this; } remove_index(this.map); //cleanup_index(this.map); this.depth && //cleanup_index(this.ctx); remove_index(this.ctx); return this; }; if(SUPPORT_CACHE){ Index.prototype.searchCache = searchCache; } if(SUPPORT_SERIALIZE){ Index.prototype.export = exportIndex; Index.prototype.import = importIndex; Index.prototype.serialize = serialize; } if(SUPPORT_ASYNC){ apply_async(Index.prototype); } ================================================ FILE: src/intersect.js ================================================ // COMPILER BLOCK --> import { SUPPORT_RESOLVER } from "./config.js"; // <-- COMPILER BLOCK import Resolver from "./resolver.js"; import { create_object, concat, sort_by_length_up, get_max_len } from "./common.js"; import { SearchResults, IntermediateSearchResults } from "./type.js"; /* from -> result[ res[score][id], res[score][id], ] to -> [id] */ /** * @param {!Array} arrays * @param {number} resolution * @param {number} limit * @param {number=} offset * @param {boolean=} suggest * @param {number=} boost * @param {boolean=} resolve * @returns {SearchResults|IntermediateSearchResults} */ export function intersect(arrays, resolution, limit, offset, suggest, boost, resolve) { const length = arrays.length; /** @type {Array} */ let result = []; let check; let count; // alternatively the results could be sorted by length up //arrays.sort(sort_by_length_up); check = create_object(); for(let y = 0, ids, id, res_arr, tmp; y < resolution; y++){ for(let x = 0; x < length; x++){ res_arr = arrays[x]; if(y < res_arr.length && (ids = res_arr[y])){ for(let z = 0; z < ids.length; z++){ id = ids[z]; // todo the persistent implementation will count term matches // and also aggregate the score (group by id) // min(score): suggestions off (already covered) // sum(score): suggestions on (actually not covered) if((count = check[id])){ check[id]++; // tmp.count++; // tmp.sum += y; } else{ count = 0; check[id] = 1; // check[id] = { // count: 1, // sum: y // }; } tmp = result[count] || (result[count] = []); if(SUPPORT_RESOLVER && !resolve){ // boost everything after first result let score = y + (x || !suggest ? 0 : boost || 0); tmp = tmp[score] || (tmp[score] = []); } tmp.push(id); // fast path early result when limit was set if(!SUPPORT_RESOLVER || resolve){ if(limit && (count === length - 1)){ if(tmp.length - offset === limit){ return offset ? tmp.slice(offset) : tmp; } } } // todo break early on suggest: true } } } } // result.sort(function(a, b){ // return check[a] - check[b]; // }); const result_len = result.length; if(result_len){ if(!suggest){ if(result_len < length){ return []; } result = /** @type {SearchResults|IntermediateSearchResults} */ ( result[result_len - 1] ); if(limit || offset){ if(!SUPPORT_RESOLVER || resolve){ if((result.length > limit) || offset){ result = result.slice(offset, limit + offset); } } else{ // todo this is doing the same as Resolver.resolve({limit}) ? // todo check limit + offset when resolve = false const final = []; for(let i = 0, arr; i < result.length; i++){ arr = result[i]; if(!arr) continue; if(offset && arr.length > offset){ offset -= arr.length; continue; } if((limit && arr.length > limit) || offset){ arr = arr.slice(offset, limit + offset); limit -= arr.length; if(offset) offset -= arr.length; } final.push(arr); if(!limit){ break; } } result = final // result = final.length > 1 // ? concat(final) // : final[0]; } // return /** @type {SearchResults|IntermediateSearchResults} */ ( // result // ); } } else{ result = result.length > 1 ? union(result, limit, offset, resolve, boost) : ((result = result[0]) && limit && result.length > limit) || offset ? result.slice(offset, limit + offset) : result; } } return /** @type {SearchResults|IntermediateSearchResults} */ ( result ); } /** * @param {Array} arrays * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {number=} boost * @returns {SearchResults|IntermediateSearchResults} */ export function union(arrays, limit, offset, resolve, boost){ /** @type {SearchResults|IntermediateSearchResults} */ const result = []; const check = create_object(); let ids, id, arr_len = arrays.length, ids_len; //let maxres = get_max_len(arrays); if(SUPPORT_RESOLVER && !resolve){ for(let i = arr_len - 1, res, count = 0; i >= 0; i--){ res = arrays[i]; for(let k = 0; k < res.length; k++){ ids = res[k]; ids_len = ids && ids.length; if(ids_len) for(let j = 0; j < ids_len; j++){ id = ids[j]; if(!check[id]){ check[id] = 1; if(offset){ offset--; } else{ // adjust score to reduce resolution of suggestions // todo: instead of applying the resolve task directly it could // be added to the chain and resolved later, that will keep // the original score but also can't resolve early when // nothing was found let score = (k + (i < arr_len - 1 ? boost || 0 : 0)) / (i + 1) | 0; let arr = result[score] || (result[score] = []); arr.push(id); if(++count === limit){ return result; } } } } } } } else for(let i = arr_len - 1; i >= 0; i--){ ids = arrays[i]; ids_len = ids && ids.length; if(ids_len) for(let j = 0; j < ids_len; j++){ id = ids[j]; if(!check[id]){ check[id] = 1; if(offset){ offset--; } else{ result.push(id); if(result.length === limit){ return result; } } } } } return result; } /** * @param {SearchResults|IntermediateSearchResults|Resolver} arrays * @param {Array} mandatory * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @returns {SearchResults} */ export function intersect_union(arrays, mandatory, limit, offset, resolve) { const check = create_object(); /** @type {SearchResults|IntermediateSearchResults} */ const result = []; for(let x = 0, ids; x < mandatory.length; x++){ ids = mandatory[x]; for(let i = 0; i < ids.length; i++){ check[ids[i]] = 1; } } if(!SUPPORT_RESOLVER || resolve){ for(let i = 0, id; i < arrays.length; i++){ id = arrays[i]; if(check[id]){ if(offset){ offset--; continue; } result.push(id); check[id] = 0; if(limit){ if(--limit === 0){ break; } } } } } else{ arrays = arrays.result || arrays; for(let i = 0, ids, id; i < arrays.length; i++){ ids = arrays[i]; for(let j = 0; j < ids.length; j++){ id = ids[j]; if(check[id]){ const arr = result[i] || (result[i] = []); arr.push(id); check[id] = 0; } } } } return result; } // export function intersect_union(mandatory, arrays, resolution) { // // const check = create_object(); // const union = create_object(); // const result = []; // // for(let x = 0; x < mandatory.length; x++){ // check[mandatory[x]] = 1; // } // // // for(let y = 0, ids, id; y < resolution; y++){ // for(let x = 0; x < arrays.length; x++){ // // ids = arrays[x]; // // if(y < ids.length){ // // id = ids[y]; // // if(check[id]){ // // if(!union[id]){ // // union[id] = 1; // result.push(id); // } // } // } // } // } // // return result; // } // // /** // * Implementation based on Object[key] provides better suggestions // * capabilities and has less performance scaling issues on large indexes. // * // * @param arrays // * @param limit // * @param offset // * @param {boolean|Array=} suggest // * @param {boolean=} resolve // * @returns {Array} // */ // // export function intersect(arrays, limit, offset, suggest, resolve) { // // const length = arrays.length; // // // todo remove // // if(length < 2){ // // throw new Error("Not optimized intersect"); // // } // // let result = []; // let size = 0; // let check; // let check_suggest; // let check_new; // let res_arr; // // if(suggest){ // suggest = []; // } // // // 1. a reversed order prioritize the order of words from a query // // because it ends with the first term. // // 2. process terms in reversed order often has advantage for // // the fast path "end reached". // // // alternatively the results could be sorted by length up // //arrays.sort(sort_by_length_up); // // // todo the outer loop should be the res array instead of term array, // // this isn't just simple because the intersection calculation needs to reflect this // //const maxlen = get_max_len(arrays); // // for(let x = length - 1, found; x >= 0; x--){ // //for(let x = 0, found; x < length; x++){ // // res_arr = arrays[x]; // check_new = create_object(); // found = !check; // // // process relevance in forward order (direction is // // important for adding IDs during the last round) // // for(let y = 0, ids; y < res_arr.length; y++){ // // ids = res_arr[y]; // if(!ids || !ids.length) continue; // // for(let z = 0, id; z < ids.length; z++){ // // id = ids[z]; // // // check exists starting from the 2nd slot // if(check){ // if(check[id]){ // // // check if in last round // if(!x){ // //if(x === length - 1){ // // if(offset){ // offset--; // } // else{ // // result[size++] = id; // // if(size === limit){ // // fast path "end reached" // return result /*resolve === false // ? { result, suggest } // :*/ // } // } // } // // if(x || suggest){ // //if((x < length - 1) || suggest){ // check_new[id] = 1; // } // // found = true; // } // // if(suggest){ // // if(!check_suggest[id]){ // check_suggest[id] = 1; // const arr = suggest[y] || (suggest[y] = []); // arr.push(id); // } // // // OLD: // // // // check_idx = (check_suggest[id] || 0) + 1; // // check_suggest[id] = check_idx; // // // // // do not adding IDs which are already included in the result (saves one loop) // // // the first intersection match has the check index 2, so shift by -2 // // // // if(check_idx < length){ // // // // const tmp = suggest[check_idx - 2] || (suggest[check_idx - 2] = []); // // tmp[tmp.length] = id; // // } // } // } // else{ // // // pre-fill in first round // check_new[id] = 1; // } // } // } // // if(suggest){ // // // re-use the first pre-filled check for suggestions // check || (check_suggest = check_new); // } // else if(!found){ // // return []; // } // // check = check_new; // } // // // return intermediate result // // if(resolve === false){ // // return { result, suggest }; // // } // // if(suggest){ // // // needs to iterate in reverse direction // for(let x = suggest.length - 1, ids, len; x >= 0; x--){ // // ids = suggest[x]; // len = ids.length; // // for(let y = 0, id; y < len; y++){ // // id = ids[y]; // // if(!check[id]){ // // if(offset){ // offset--; // } // else{ // // result[size++] = id; // // if(size === limit){ // // fast path "end reached" // return result; // } // } // // check[id] = 1; // } // } // } // } // // return result; // } /** * Implementation based on Array.includes() provides better performance, * but it needs at least one word in the query which is less frequent. * Also on large indexes it does not scale well performance-wise. * This strategy also lacks of suggestion capabilities (matching & sorting). * * @param arrays * @param limit * @param offset * @param {boolean|Array=} suggest * @returns {Array} */ // export function intersect(arrays, limit, offset, suggest) { // // const length = arrays.length; // let result = []; // let check; // // // determine shortest array and collect results // // from the sparse relevance arrays // // let smallest_size; // let smallest_arr; // let smallest_index; // // for(let x = 0; x < length; x++){ // // const arr = arrays[x]; // const len = arr.length; // // let size = 0; // // for(let y = 0, tmp; y < len; y++){ // // tmp = arr[y]; // // if(tmp){ // // size += tmp.length; // } // } // // if(!smallest_size || (size < smallest_size)){ // // smallest_size = size; // smallest_arr = arr; // smallest_index = x; // } // } // // smallest_arr = smallest_arr.length === 1 ? // // smallest_arr[0] // : // concat(smallest_arr); // // if(suggest){ // // suggest = [smallest_arr]; // check = create_object(); // } // // let size = 0; // let steps = 0; // // // process terms in reversed order often results in better performance. // // the outer loop must be the words array, using the // // smallest array here disables the "fast fail" optimization. // // for(let x = length - 1; x >= 0; x--){ // // if(x !== smallest_index){ // // steps++; // // const word_arr = arrays[x]; // const word_arr_len = word_arr.length; // const new_arr = []; // // let count = 0; // // for(let z = 0, id; z < smallest_arr.length; z++){ // // id = smallest_arr[z]; // // let found; // // // process relevance in forward order (direction is // // important for adding IDs during the last round) // // for(let y = 0; y < word_arr_len; y++){ // // const arr = word_arr[y]; // // if(arr.length){ // // found = arr.includes(id); // // if(found){ // // // check if in last round // // if(steps === length - 1){ // // if(offset){ // // offset--; // } // else{ // // result[size++] = id; // // if(size === limit){ // // // fast path "end reached" // // return result; // } // } // // if(suggest){ // // check[id] = 1; // } // } // // break; // } // } // } // // if(found){ // // new_arr[count++] = id; // } // } // // if(suggest){ // // suggest[steps] = new_arr; // } // else if(!count){ // // return []; // } // // smallest_arr = new_arr; // } // } // // if(suggest){ // // // needs to iterate in reverse direction // // for(let x = suggest.length - 1, arr, len; x >= 0; x--){ // // arr = suggest[x]; // len = arr && arr.length; // // if(len){ // // for(let y = 0, id; y < len; y++){ // // id = arr[y]; // // if(!check[id]){ // // check[id] = 1; // // if(offset){ // // offset--; // } // else{ // // result[size++] = id; // // if(size === limit){ // // // fast path "end reached" // // return result; // } // } // } // } // } // } // } // // return result; // } ================================================ FILE: src/keystore.js ================================================ import { create_object } from "./common.js"; function _slice(self, start, end, splice){ let arr = []; for(let i = 0, index; i < self.index.length; i++){ index = self.index[i]; if(start >= index.length){ start -= index.length; } else{ const tmp = index[splice ? "splice" : "slice"](start, end); const length = tmp.length; if(length){ arr = arr.length ? arr.concat(tmp) : tmp; end -= length; if(splice) self.length -= length; if(!end) break; } start = 0; } } return arr; } /** * @param arr * @constructor */ export function KeystoreArray(arr){ if(!this || this.constructor !== KeystoreArray){ return new KeystoreArray(arr); } this.index = arr ? [arr] : []; this.length = arr ? arr.length : 0; const self = this; return /*this.proxy =*/ new Proxy([], { get(target, key) { if(key === "length"){ return self.length; } if(key === "push"){ return function(value){ self.index[self.index.length - 1].push(value); self.length++; } } if(key === "pop"){ return function(){ if(self.length){ self.length--; return self.index[self.index.length - 1].pop(); } } } if(key === "indexOf"){ return function(key){ let index = 0; for(let i = 0, arr, tmp; i < self.index.length; i++){ arr = self.index[i]; //if(!arr.includes(key)) continue; tmp = arr.indexOf(key); if(tmp >= 0) return index + tmp; index += arr.length; } return -1; } } if(key === "includes"){ return function(key){ for(let i = 0; i < self.index.length; i++){ if(self.index[i].includes(key)){ return true; } } return false; } } if(key === "slice"){ return function(start, end){ return _slice( self, start || 0, end || self.length, false ); } } if(key === "splice"){ return function(start, end){ return _slice( self, start || 0, end || self.length, // splice: true ); } } if(key === "constructor"){ return Array; } if(typeof key === "symbol" /*|| isNaN(key)*/){ // not supported return; } const index = key / (2**31) | 0; const arr = self.index[index]; return arr && arr[key]; }, set(target, key, value){ const index = key / (2**31) | 0; const arr = self.index[index] || (self.index[index] = []); arr[key] = value; self.length++; return true; } }); } KeystoreArray.prototype.clear = function(){ this.index.length = 0; }; // KeystoreArray.prototype.destroy = function(){ // this.index = null; // this.proxy = null; // }; KeystoreArray.prototype.push = function(val){}; /** * @interface */ function Keystore() { /** @type {Object} */ this.index; /** @type {Array} */ this.refs; /** @type {number} */ this.size; /** @type {function((string|bigint|number)):number} */ this.crc; /** @type {bigint|number} */ this.bit; } /** * @param bitlength * @constructor * @implements {Keystore} */ export function KeystoreMap(bitlength = 8){ if(!this || this.constructor !== KeystoreMap){ return new KeystoreMap(bitlength); } /** @type {Object} */ this.index = create_object(); /** @type {Array} */ this.refs = []; /** @type {number} */ this.size = 0; if(bitlength > 32){ this.crc = lcg64; this.bit = BigInt(bitlength); } else { this.crc = lcg; this.bit = bitlength; } } /** @param {number|string} key */ KeystoreMap.prototype.get = function(key) { const address = this.crc(key); const map = this.index[address]; return map && map.get(key); }; /** * @param {number|string} key * @param {*} value */ KeystoreMap.prototype.set = function(key, value){ const address = this.crc(key); let map = this.index[address]; if(map){ let size = map.size; map.set(key, value); size -= map.size; size && this.size++; } else{ this.index[address] = map = new Map([[key, value]]); this.refs.push(map); this.size++; } }; /** * @param bitlength * @constructor * @implements Keystore */ export function KeystoreSet(bitlength = 8){ if(!this || this.constructor !== KeystoreSet){ return new KeystoreSet(bitlength); } /** @type {Object} */ this.index = create_object(); /** @type {Array} */ this.refs = []; /** @type {number} */ this.size = 0; if(bitlength > 32){ this.crc = lcg64; this.bit = BigInt(bitlength); } else { this.crc = lcg; this.bit = bitlength; } } /** @param {number|string} key */ KeystoreSet.prototype.add = function(key){ const address = this.crc(key); let set = this.index[address]; if(set){ let size = set.size; set.add(key); size -= set.size; size && this.size++; } else{ this.index[address] = set = new Set([key]); this.refs.push(set); this.size++; } }; KeystoreMap.prototype.has = /** @param {number|string} key */ KeystoreSet.prototype.has = function(key) { const address = this.crc(key); const map_or_set = this.index[address]; return map_or_set && map_or_set.has(key); }; /* KeystoreMap.prototype.size = KeystoreSet.prototype.size = function(){ let size = 0; const values = Object.values(this.index); for(let i = 0; i < values.length; i++){ size += values[i].size; } return size; }; */ KeystoreMap.prototype.delete = /** @param {number|string} key */ KeystoreSet.prototype.delete = function(key){ const address = this.crc(key); const map_or_set = this.index[address]; // set && (set.size === 1 // ? this.index.delete(address) // : set.delete(key)); map_or_set && map_or_set.delete(key) && this.size--; }; KeystoreMap.prototype.clear = KeystoreSet.prototype.clear = function(){ this.index = create_object(); this.refs = []; this.size = 0; }; // KeystoreMap.prototype.destroy = // KeystoreSet.prototype.destroy = function(){ // this.index = null; // this.refs = null; // this.proxy = null; // }; /** * @return Iterable */ KeystoreMap.prototype.values = KeystoreSet.prototype.values = function*(){ // alternatively iterate through this.keys[] //const refs = Object.values(this.index); for(let i = 0; i < this.refs.length; i++){ for(let value of this.refs[i].values()){ yield value; } } }; /** * @return Iterable */ KeystoreMap.prototype.keys = KeystoreSet.prototype.keys = function*(){ //const values = Object.values(this.index); for(let i = 0; i < this.refs.length; i++){ for(let key of this.refs[i].keys()){ yield key; } } }; /** * @return Iterable */ KeystoreMap.prototype.entries = KeystoreSet.prototype.entries = function*(){ //const values = Object.values(this.index); for(let i = 0; i < this.refs.length; i++){ for(let entry of this.refs[i].entries()){ yield entry; } } }; // /** // * @param bitlength // * @constructor // */ // // export function KeystoreObj(bitlength = 8){ // // if(!this || this.constructor !== KeystoreObj){ // return new KeystoreObj(bitlength); // } // // this.index = create_object(); // this.keys = []; // // if(bitlength > 32){ // this.crc = lcg64; // this.bit = BigInt(bitlength); // } // else { // this.crc = lcg; // this.bit = bitlength; // } // // return /*this.proxy =*/ new Proxy(this, { // get(target, key) { // const address = target.crc(key); // const obj = target.index[address]; // return obj && obj[key]; // }, // set(target, key, value){ // const address = target.crc(key); // let obj = target.index[address]; // if(!obj){ // target.index[address] = obj = create_object(); // target.keys.push(address); // } // obj[key] = value; // return true; // }, // delete(target, key){ // const address = target.crc(key); // const obj = target.index[address]; // obj && delete obj[key]; // return true; // } // }); // } // // KeystoreObj.prototype.clear = function(){ // this.index = create_object(); // this.keys = []; // }; // KeystoreObj.prototype.destroy = function(){ // this.index = null; // this.keys = null; // this.proxy = null; // }; /** * Linear Congruential Generator (LCG) * @param {!number|bigint|string} str * @this {KeystoreMap|KeystoreSet} */ function lcg(str) { let range = 2 ** this.bit - 1; if(typeof str == "number"){ return str & range; } let crc = 0, bit = this.bit + 1; for(let i = 0; i < str.length; i++) { crc = (crc * bit ^ str.charCodeAt(i)) & range; } // shift Int32 to UInt32 because negative numbers // extremely slows down key lookup return this.bit === 32 ? crc + 2 ** 31 : crc;// & 0xFFFF; } /** * @param {!number|bigint|string} str * @this {KeystoreMap|KeystoreSet} */ function lcg64(str) { let range = BigInt(2) ** /** @type {!bigint} */ (this.bit) - BigInt(1); let type = typeof str; if(type === "bigint"){ return /** @type {!bigint} */ (str) & range; } if(type === "number"){ return BigInt(str) & range; } let crc = BigInt(0), bit = /** @type {!bigint} */ (this.bit) + BigInt(1); for(let i = 0; i < str.length; i++){ crc = (crc * bit ^ BigInt(str.charCodeAt(i))) & range; } return crc;// & 0xFFFFFFFFFFFFFFFF; } ================================================ FILE: src/lang/de.js ================================================ import { EncoderOptions } from "../type.js"; /** * Filter are also known as "stopwords", they completely filter out words from being indexed. * Source: http://www.ranks.nl/stopwords * Object Definition: Just provide an array of words. * @type {Set} */ export const filter = new Set([ "aber", "als", "am", "an", "auch", "auf", "aus", "bei", "bin", "bis", "bist", "da", "dadurch", "daher", "darum", "das", "dass", "dass", "dein", "deine", "dem", "den", "der", "des", "dessen", "deshalb", "die", "dies", "dieser", "dieses", "doch", "dort", "du", "durch", "ein", "eine", "einem", "einen", "einer", "eines", "er", "es", "euer", "eure", "fuer", "hatte", "hatten", "hattest", "hattet", "hier", "hinter", "ich", "ihr", "ihre", "im", "in", "ist", "ja", "jede", "jedem", "jeden", "jeder", "jedes", "jener", "jenes", "jetzt", "ggf", "kann", "kannst", "koennen", "koennt", "machen", "mein", "meine", "mit", "muss", "musst", "musst", "muessen", "muesst", "nach", "nachdem", "nein", "nicht", "noch", "nun", "oder", "seid", "sein", "seine", "sich", "sie", "sind", "soll", "sollen", "sollst", "sollt", "sonst", "soweit", "sowie", "und", "unser", "unsere", "unter", "usw", "uvm", "vom", "von", "vor", "wann", "warum", "was", "weiter", "weitere", "wenn", "wer", "werde", "werden", "werdet", "weshalb", "wie", "wieder", "wieso", "wir", "wird", "wirst", "wo", "woher", "wohin", "zu", "zum", "zur", "ueber" ]); /** * Stemmer removes word endings and is a kind of "partial normalization". A word ending just matched when the word length is bigger than the matched partial. * Example: The word "correct" and "correctness" could be the same word, so you can define {"ness": ""} to normalize the ending. * Object Definition: the key represents the word ending, the value contains the replacement (or empty string for removal). * http://snowball.tartarus.org/algorithms/german/stemmer.html * @type {Map} */ export const stemmer = new Map([ ["niss", ""], ["isch", ""], ["lich", ""], ["heit", ""], ["keit", ""], ["ell", ""], ["bar", ""], ["end", ""], ["ung", ""], ["est", ""], ["ern", ""], ["em", ""], ["er", ""], ["en", ""], ["es", ""], ["st", ""], ["ig", ""], ["ik", ""], ["e", ""], ["s", ""] ]); /** * Matcher replaces all occurrences of a given string regardless of its position and is also a kind of "partial normalization". * Object Definition: the key represents the target term, the value contains the search string which should be replaced (could also be an array of multiple terms). * @type {Map} */ const map = new Map([ ["_", " "], ["ä", "ae"], ["ö", "oe"], ["ü", "ue"], ["ß", "ss"], ["&", " und "], ["€", " EUR "] ]); /** * @type EncoderOptions */ const options = { prepare: function(str){ // normalization if(/[_äöüß&€]/.test(str)) str = str.replace(/[_äöüß&€]/g, match => map.get(match)); // street names return str.replace(/str\b/g, "strasse") .replace(/(?!\b)strasse\b/g, " strasse"); }, filter: filter, stemmer: stemmer }; export default options; ================================================ FILE: src/lang/en.js ================================================ import { EncoderOptions } from "../type.js"; // todo filter out minlength /** * http://www.ranks.nl/stopwords * @type {Set} */ export const filter = new Set([ "a", "about", "above", "after", "again", "against", "all", "also", "am", "an", "and", "any", "are", "arent", "as", "at", "back", "be", "because", "been", "before", "being", "below", "between", "both", "but", "by", "can", "cannot", "cant", "come", "could", "couldnt", //"day", "did", "didnt", "do", "does", "doesnt", "doing", "dont", "down", "during", "each", "even", "few", //"first", "for", "from", "further", "get", //"give", "go", "good", "had", "hadnt", "has", "hasnt", "have", "havent", "having", "he", "hed", //"hell", "her", "here", "heres", "hers", "herself", "hes", "him", "himself", "his", "how", "hows", "i", "id", "if", "ill", "im", "in", "into", "is", "isnt", "it", "its", "itself", "ive", "just", "know", "lets", "like", //"look", "lot", "make", "made", "me", "more", "most", "mustnt", "my", "myself", "new", "no", "nor", "not", "now", "of", "off", "on", "once", "one", "only", "or", "other", "ought", "our", "ours", "ourselves", "out", "over", "own", //"people", "same", "say", "see", "shant", "she", "shed", "shell", "shes", "should", "shouldnt", "so", "some", "such", "take", "than", "that", "thats", "the", "their", "theirs", "them", "themselves", "then", "there", "theres", "these", "they", "theyd", "theyll", "theyre", "theyve", "think", "this", "those", "through", "time", "times", "to", "too", //"two", "under", "until", "up", "us", "use", "very", "want", "was", "wasnt", "way", "we", "wed", "well", "were", "werent", "weve", "what", "whats", "when", "whens", "where", "wheres", "which", "while", "who", "whom", "whos", "why", "whys", "will", "with", "wont", "work", "would", "wouldnt", //"year", "ya", "you", "youd", "youll", "your", "youre", "yours", "yourself", "yourselves", "youve" ]); /** * @type {Map} */ export const stemmer = new Map([ //["ational", "ate"], //["iveness", ""], //["fulness", ""], //["ousness", ""], ["ization", ""], //["tional", "tion"], ["biliti", ""], ["icate", ""], ["ative", ""], //["alize", ""], //["iciti", ""], //["entli", ""], //["ousli", ""], //["alism", ""], ["ation", ""], //["aliti", ""], ["iviti", ""], ["ement", ""], ["izer", ""], ["able", ""], ["ible", ""], ["alli", ""], ["ator", ""], ["less", ""], ["logi", ""], ["ical", ""], ["ance", ""], ["ence", ""], ["ness", ""], ["ble", ""], ["ment", ""], //["nal", "n"], ["eli", ""], ["bli", ""], ["ful", ""], ["ant", ""], ["ent", ""], ["ism", ""], ["ate", ""], ["iti", ""], ["ous", ""], ["ive", ""], ["ize", ""], ["ing", ""], ["ion", ""], ["ies", "y"], ["al", ""], ["ou", ""], ["er", ""], ["ed", ""], //["es", "e"], ["ic", ""], ["ly", ""], ["li", ""], ["s", ""] ]); /* he’s (= he is / he has) she’s (= she is / she has) I’ll (= I will) I’ve (= I have) I’d (= I would / I had) don’t (= do not) doesn’t (= does not) didn’t (= did not) isn’t (= is not) hasn’t (= has not) can’t (= cannot) won’t (= will not) */ // const explode = new Map([ // ["^i'm$", "i am"], // ["^can't$", "can not"], // ["^cannot$", "can not"], // ["^won't$", "will not"], // ["'s$", " is has"], // ["n't$", " not"], // ["'ll$", " will"], // ["'re$", " are"], // ["'ve$", " have"], // ["'d$", " would had"], // ]); /** * @type EncoderOptions */ const options = { prepare: function(str){ return str // normalize symbols .replace(/´`’ʼ/g, "'") //.replace(/[_\-]+/g, " ") .replace(/&/g, " and ") .replace(/\$/g, " USD ") .replace(/£/g, " GBP ") // explode short forms .replace(/\bi'm\b/g, "i am") .replace(/\b(can't|cannot)\b/g, "can not") .replace(/\bwon't\b/g, "will not") .replace(/([a-z])'s\b/g, "$1 is has") .replace(/([a-z])n't\b/g, "$1 not") .replace(/([a-z])'ll\b/g, "$1 will") .replace(/([a-z])'re\b/g, "$1 are") .replace(/([a-z])'ve\b/g, "$1 have") .replace(/([a-z])'d\b/g, "$1 would had"); }, filter: filter, stemmer: stemmer }; export default options; ================================================ FILE: src/lang/fr.js ================================================ import { EncoderOptions } from "../type.js"; /** * http://www.ranks.nl/stopwords * http://snowball.tartarus.org/algorithms/french/stop.txt * @type {Set} */ export const filter = new Set([ "au", "aux", "avec", "ce", "ces", "dans", "de", "des", "du", "elle", "en", "et", "eux", "il", "je", "la", "le", "leur", "lui", "ma", "mais", "me", "meme", "mes", "moi", "mon", "ne", "nos", "notre", "nous", "on", "ou", "par", "pas", "pour", "qu", "que", "qui", "sa", "se", "ses", "son", "sur", "ta", "te", "tes", "toi", "ton", "tu", "un", "une", "vos", "votre", "vous", "c", "d", "j", "l", "m", "n", "s", "t", "a", "y", "ete", "etee", "etees", "etes", "etant", "suis", "es", "est", "sommes", "etes", "sont", "serai", "seras", "sera", "serons", "serez", "seront", "serais", "serait", "serions", "seriez", "seraient", "etais", "etait", "etions", "etiez", "etaient", "fus", "fut", "fumes", "futes", "furent", "sois", "soit", "soyons", "soyez", "soient", "fusse", "fusses", "fut", "fussions", "fussiez", "fussent", "ayant", "eu", "eue", "eues", "eus", "ai", "as", "avons", "avez", "ont", "aurai", "auras", "aura", "aurons", "aurez", "auront", "aurais", "aurait", "aurions", "auriez", "auraient", "avais", "avait", "avions", "aviez", "avaient", "eut", "eumes", "eutes", "eurent", "aie", "aies", "ait", "ayons", "ayez", "aient", "eusse", "eusses", "eut", "eussions", "eussiez", "eussent", "ceci", "cela", "cela", "cet", "cette", "ici", "ils", "les", "leurs", "quel", "quels", "quelle", "quelles", "sans", "soi" ]); /** * @type {Map} */ export const stemmer = new Map([ ["lement", ""], ["ient", ""], ["nera", ""], ["ment", ""], ["ais", ""], ["ait", ""], ["ant", ""], ["ent", ""], ["iez", ""], ["ion", ""], ["nez", ""], ["ai", ""], ["es", ""], ["er", ""], ["ez", ""], ["le", ""], ["na", ""], ["ne", ""], ["a", ""], ["e", ""] ]); /** * @type EncoderOptions */ const options = { prepare: function(str){ return str .replace(/´`’ʼ/g, "'") .replace(/_+/g, " ") .replace(/&/g, " et ") .replace(/€/g, " EUR ") .replace(/\bl'([^\b])/g, "la le $1") .replace(/\bt'([^\b])/g, "ta te $1") .replace(/\bc'([^\b])/g, "ca ce $1") .replace(/\bd'([^\b])/g, "da de $1") .replace(/\bj'([^\b])/g, "ja je $1") .replace(/\bn'([^\b])/g, "na ne $1") .replace(/\bm'([^\b])/g, "ma me $1") .replace(/\bs'([^\b])/g, "sa se $1") .replace(/\bau\b/g, "a le") .replace(/\baux\b/g, "a les") .replace(/\bdu\b/g, "de le") .replace(/\bdes\b/g, "de les") }, filter: filter, stemmer: stemmer }; export default options; ================================================ FILE: src/preset.js ================================================ // COMPILER BLOCK --> import { DEBUG } from "./config.js"; // <-- COMPILER BLOCK import { is_string } from "./common.js"; import { IndexOptions } from "./type.js"; /** * @type {Object} * @const */ const presets = { "memory": { resolution: 1 }, "performance": { resolution: 3, fastupdate: true, context: { depth: 1, resolution: 1 } }, "match": { tokenize: "full" }, "score": { resolution: 9, context: { depth: 2, resolution: 3 } } }; /** * * @param {IndexOptions|string} options * @return {IndexOptions} */ export default function apply_preset(options){ const preset = /** @type string */ ( is_string(options) ? options : options.preset ); if(preset){ if(DEBUG && !presets[preset]){ console.warn("Preset not found: " + preset); } options = /** @type IndexOptions */ ( Object.assign({}, presets[preset], /** @type {Object} */ (options)) ); } return /** @type IndexOptions */ (options); } ================================================ FILE: src/profiler.js ================================================ // COMPILER BLOCK --> import { PROFILER } from "./config.js"; // <-- COMPILER BLOCK import { create_object } from "./common.js"; const data = create_object(); if(PROFILER){ if(typeof window !== "undefined"){ window.profiler = data; } } /** * @param {!string} name */ export default function tick(name){ if(PROFILER){ /** @type {!Object} */ const profiler = data; //data["profiler"] || (data["profiler"] = {}); profiler[name] || (profiler[name] = 0); profiler[name]++; } } ================================================ FILE: src/resolve/and.js ================================================ import Resolver from "../resolver.js"; import { get_max_len } from "../common.js"; import { intersect } from "../intersect.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } * @this {Resolver} */ Resolver.prototype["and"] = function(){ return this.handler("and", return_result, arguments); } /** * Aggregate the intersection of N raw results * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight){ if(!suggest && !this.result.length){ return resolve ? this.result : this; } let resolved; if(!final.length){ if(!suggest){ this.result = /** @type {SearchResults|IntermediateSearchResults} */ ( final ); } } else{ this.result.length && final.unshift(this.result); if(final.length < 2){ this.result = final[0]; } else{ let resolution = 0; for(let i = 0, res, len; i < final.length; i++){ if((res = final[i]) && (len = res.length)){ if(resolution < len){ resolution = len; } } else if(!suggest){ resolution = 0; break; } } if(!resolution){ this.result = []; } else{ this.result = intersect( final, resolution, limit, offset, suggest, this.boostval, resolve ); resolved = true; } } } // skip recursive promise execution in .resolve() if(resolve){ this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight, resolved) : this; } ================================================ FILE: src/resolve/default.js ================================================ import { concat } from "../common.js"; import { IntermediateSearchResults, SearchResults, EnrichedSearchResults } from "../type.js"; import { apply_enrich } from "../document/search.js"; import Document from "../document.js"; import Index from "../index.js"; import WorkerIndex from "../worker.js"; /* from -> res[score][id] to -> [id] */ /** * Aggregate the union of a single raw result * @param {IntermediateSearchResults} result * @param {!number} limit * @param {number=} offset * @param {boolean=} enrich * @return {SearchResults|EnrichedSearchResults} * @this {Document|Index|WorkerIndex} */ export default function(result, limit, offset, enrich){ if(!result.length){ return result; } // fast path: when there is just one slot in the result if(result.length === 1){ let final = result[0]; final = offset || (final.length > limit) ? final.slice(offset, offset + limit) : final; return enrich ? /** @type {EnrichedSearchResults} */ (apply_enrich.call(this, final)) : final; } let final = []; // this is an optimized workaround instead of // just doing result = concat(result) for(let i = 0, arr, len; i < result.length; i++){ if(!(arr = result[i]) || !(len = arr.length)) continue; if(offset){ // forward offset pointer if(offset >= len){ offset -= len; continue; } // apply the remaining offset arr = arr.slice(offset, offset + limit); len = arr.length; offset = 0; } if(len > limit){ // apply limit arr = arr.slice(0, limit); len = limit; } if(!final.length){ // fast path: when limit was reached in the first slot if(len >= limit){ return enrich ? /** @type {EnrichedSearchResults} */ (apply_enrich.call(this, arr)) : arr; } } final.push(arr); limit -= len; // break if limit was reached if(!limit){ break; } } final = final.length > 1 ? concat(final) : final[0]; return enrich ? apply_enrich.call(this, final) : final; } ================================================ FILE: src/resolve/handler.js ================================================ // COMPILER BLOCK --> import { DEBUG, SUPPORT_ASYNC, SUPPORT_DOCUMENT, SUPPORT_HIGHLIGHTING, SUPPORT_STORE } from "../config.js"; // <-- COMPILER BLOCK import Resolver from "../resolver.js"; import { ResolverOptions, SearchResults, EnrichedSearchResults, IntermediateSearchResults } from "../type.js"; /** * @param {string} method * @param {Function} fn * @param {Array|Arguments} args * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ Resolver.prototype.handler = function(method, fn, args){ /** @type {ResolverOptions} */ let arg = args[0]; // detect array parameter style if(arg[0] && arg[0].query){ return this[method].apply(this, arg); } // skip tasks on specific conditions if(method === "and" || method === "not") { let execute = this.result.length || this.await; let resolve; if(!execute){ if(!arg.suggest){ if(args.length > 1){ arg = args[args.length - 1]; } resolve = arg.resolve; return resolve ? this.await || this.result : this; } } } const self = this; /** @type {!Array>} */ let final = []; let limit = 0, offset = 0, enrich, resolve, suggest, highlight; let async; for(let i = 0; i < args.length; i++){ /** @type {ResolverOptions} */ let query = args[i]; if(query){ let result; if(query.constructor === Resolver){ result = query.await || query.result; } else if(query.then || query.constructor === Array){ result = query; } else{ limit = query.limit || 0; offset = query.offset || 0; suggest = query.suggest; resolve = query.resolve; highlight = SUPPORT_DOCUMENT && SUPPORT_STORE && SUPPORT_HIGHLIGHTING && (query.highlight || this.highlight); enrich = SUPPORT_DOCUMENT && SUPPORT_STORE && (highlight || query.enrich) && resolve; let opt_queue = SUPPORT_ASYNC && query.queue; let opt_async = SUPPORT_ASYNC && (query.async || opt_queue); let index = query.index; let query_value = query.query; if(index){ this.index || (this.index = index); } else{ index = this.index; } if(query_value || query.tag){ if(DEBUG){ if(!index){ throw new Error("Resolver can't apply because the corresponding Index was never specified"); } } if(SUPPORT_DOCUMENT){ const field = query.field || query.pluck; if(field){ if(SUPPORT_STORE && SUPPORT_HIGHLIGHTING && query_value && (!this.query || highlight)){ this.query = query_value; this.field = field; this.highlight = highlight; } if(DEBUG){ if(!index.index){ throw new Error("Resolver can't apply because the corresponding Document Index was not specified"); } } index = index.index.get(field); if(DEBUG){ if(!index){ throw new Error("Resolver can't apply because the specified Document Field '" + field + "' was not found"); } } } } if(opt_queue && (async || this.await)){ async = 1; // create a new promise for the main queue // the promise return is controlled internally let resolve; const idx = this.promises.length; const promise = new Promise(function(_resolve){ resolve = _resolve; }); (function(index, query){ promise._fn = function(){ query.index = null; query.resolve = false; query.enrich = false; let result = opt_async ? index.searchAsync(query) : index.search(query); if(result.then){ return result.then(function(result){ self.promises[idx] = result = result.result || result; resolve(result); return result; }); } result = result.result || result; resolve(result); return result; } }(index, Object.assign({}, /** @type Object */ (query)))); this.promises.push(promise); final[i] = promise; continue; } else { query.resolve = false; query.enrich = false; query.index = null; result = opt_async ? index.searchAsync(query) : index.search(query); query.resolve = resolve; query.enrich = enrich; query.index = index; } } else if(query.and){ result = inner_call(query, "and", index); } else if(query.or){ result = inner_call(query, "or", index); } else if(query.not){ result = inner_call(query, "not", index); } else if(query.xor){ result = inner_call(query, "xor", index); } else{ continue; } } if(result.await){ async = 1; result = result.await; } else if(result.then){ async = 1; result = result.then(function(result){ return result.result || result; }); } else{ result = result.result || result; } final[i] = result; } } if(async && !this.await){ this.await = new Promise(function(resolve){ self.return = resolve; }); } if(async){ const promises = Promise.all(final).then(function(final){ for(let i = 0; i < self.promises.length; i++){ if(self.promises[i] === promises){ self.promises[i] = function(){ return fn.call(self, final, limit, offset, enrich, resolve, suggest, highlight ) }; break; } } self.execute(); }); this.promises.push(promises); } else if(this.await){ this.promises.push(function(){ return fn.call(self, final, limit, offset, enrich, resolve, suggest, highlight ); }); } else{ return fn.call(this, final, limit, offset, enrich, resolve, suggest, highlight ); } return resolve ? this.await || this.result : this; } function inner_call(query, method, index){ const args = query[method]; const arg = args[0] || args; arg.index || (arg.index = index); let resolver = new Resolver(arg); if(args.length > 1){ resolver = resolver[method].apply(resolver, args.slice(1)); } return resolver; } ================================================ FILE: src/resolve/not.js ================================================ import Resolver from "../resolver.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** @this {Resolver} */ Resolver.prototype["not"] = function(){ return this.handler("not", return_result, arguments); } /** * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight){ if(!suggest && !this.result.length){ return resolve ? this.result : this; } let resolved; if(final.length && this.result.length){ this.result = exclusion.call(this, final, limit, offset, resolve ); resolved = true; } // skip recursive promise execution in .resolve() if(resolve){ this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight, resolved) : this; } /** * @param {!Array} result * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @this {Resolver} * @return {SearchResults|IntermediateSearchResults} */ function exclusion(result, limit, offset, resolve){ /** @type {SearchResults|IntermediateSearchResults} */ const final = []; const exclude = new Set(result.flat().flat()); for(let j = 0, ids, count = 0; j < this.result.length; j++){ ids = this.result[j]; if(!ids) continue; for(let k = 0, id; k < ids.length; k++){ id = ids[k]; if(!exclude.has(id)){ if(offset){ offset--; continue; } if(resolve){ final.push(id); if(final.length === limit){ return final; } } else{ final[j] || (final[j] = []); final[j].push(id); if(++count === limit){ return final; } } } } } return final; } ================================================ FILE: src/resolve/or.js ================================================ import Resolver from "../resolver.js"; import { union } from "../intersect.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** @this {Resolver} */ Resolver.prototype["or"] = function(){ return this.handler("or", return_result, arguments); }; /** * Aggregate the intersection of N raw results * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight){ if(final.length){ this.result.length && (final.push(this.result)); if(final.length < 2){ this.result = final[0]; } else{ // the suggest-union (reversed processing, resolve needs to be disabled) this.result = union( final, limit, offset, // bypass resolve for the union helper /* resolve: */ false, this.boostval ); // limit + offset was already applied offset = 0; } } // skip recursive promise execution in .resolve() if(resolve){ this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight) : this; } ================================================ FILE: src/resolve/xor.js ================================================ import Resolver from "../resolver.js"; import { create_object } from "../common.js"; import { SearchResults, EnrichedSearchResults, IntermediateSearchResults, HighlightOptions } from "../type.js"; /** @this {Resolver} */ Resolver.prototype["xor"] = function(){ return this.handler("xor", return_result, arguments); } /** * @param {!Array} final * @param {number} limit * @param {number=} offset * @param {boolean=} enrich * @param {boolean=} resolve * @param {boolean=} suggest * @param {string|HighlightOptions=} highlight * @this {Resolver} * @return { * SearchResults | * EnrichedSearchResults | * IntermediateSearchResults | * Promise | * Resolver * } */ function return_result(final, limit, offset, enrich, resolve, suggest, highlight){ let resolved; if(!final.length){ if(!suggest) this.result = /** @type {SearchResults|IntermediateSearchResults} */ (final); } else{ this.result.length && final.unshift(this.result); if(final.length < 2){ this.result = final[0]; } else{ this.result = exclusive.call(this, final, limit, offset, resolve, this.boostval ); resolved = true; } } // skip recursive promise execution in .resolve() if(resolve){ this.await = null; } return resolve ? this.resolve(limit, offset, enrich, highlight, resolved) : this; } /** * Aggregate the intersection of N raw results * @param {!Array} result * @param {number} limit * @param {number=} offset * @param {boolean=} resolve * @param {number=} boost * @this {Resolver} * @return {SearchResults|IntermediateSearchResults} */ function exclusive(result, limit, offset, resolve, boost){ /** @type {SearchResults|IntermediateSearchResults} */ const final = []; const check = create_object(); let maxres = 0; for(let i = 0, res; i < result.length; i++){ res = result[i]; if(!res) continue; if(maxres < res.length) maxres = res.length; for(let j = 0, ids; j < res.length; j++){ ids = res[j]; if(!ids) continue for(let k = 0, id; k < ids.length; k++){ id = ids[k]; check[id] = check[id] ? 2 : 1; } } } for(let j = 0, ids, count = 0; j < maxres; j++){ for(let i = 0, res; i < result.length; i++){ res = result[i]; if(!res) continue; ids = res[j]; if(!ids) continue; for(let k = 0, id; k < ids.length; k++){ id = ids[k]; if(check[id] === 1){ if(offset){ offset--; continue; } if(resolve){ final.push(id); if(final.length === limit){ return final; } } else{ // shift resolution by boost (inverse) const index = j + (i ? boost : 0); final[index] || (final[index] = []); final[index].push(id); if(++count === limit){ return final; } } } } } } return final; } ================================================ FILE: src/resolver.js ================================================ // COMPILER BLOCK --> import { DEBUG, SUPPORT_ASYNC, SUPPORT_DOCUMENT, SUPPORT_HIGHLIGHTING, SUPPORT_STORE } from "./config.js"; // <-- COMPILER BLOCK import { ResolverOptions, IntermediateSearchResults, SearchResults, EnrichedSearchResults, HighlightOptions } from "./type.js"; import Index from "./index.js"; import Document from "./document.js"; import WorkerIndex from "./worker.js"; import default_resolver from "./resolve/default.js"; import "./resolve/handler.js"; import "./resolve/or.js"; import "./resolve/and.js"; import "./resolve/xor.js"; import "./resolve/not.js"; import { apply_enrich } from "./document/search.js"; import { highlight_fields } from "./document/highlight.js"; /** * @param {IntermediateSearchResults|ResolverOptions=} result * @param {Index|Document|WorkerIndex=} index * @return {Resolver} * @constructor */ export default function Resolver(result, index){ if(!this || this.constructor !== Resolver){ return new Resolver(result, index); } // if(result && result.constructor === Resolver){ // return /** @type {Resolver} */ (result); // } let boost = 0; let promises; let query; let field; let highlight; let _await; let _return; if(result && result.index){ const options = /** @type {ResolverOptions} */ (result); index = options.index; boost = options.boost || 0; if((query = options.query)){ field = options.field || options.pluck; highlight = options.highlight; const resolve = options.resolve; const async = options.async || options.queue; options.resolve = false; options.highlight = ""; options.index = null; result = SUPPORT_ASYNC && async ? index.searchAsync(options) : index.search(options); options.resolve = resolve; options.highlight = highlight; options.index = index; result = result.result || result; } else{ result = []; } } if(result && result.then){ const self = this; result = result.then(function(result){ self.promises[0] = self.result = result.result || result; self.execute(); }); promises = [result]; result = []; _await = new Promise(function(resolve){ _return = resolve; }); } /** @type {Index|Document|WorkerIndex|null} */ this.index = index || null; /** @type {IntermediateSearchResults} */ this.result = /** @type {IntermediateSearchResults} */ (result) || []; /** @type {number} */ this.boostval = boost; /** @type {Array|IntermediateSearchResults|Function>} */ this.promises = promises || []; /** @type {Promise} */ this.await = _await || null; /** @type {Function} */ this.return = _return || null; if(SUPPORT_HIGHLIGHTING){ /** @type {HighlightOptions|null} */ this.highlight = /** @type {HighlightOptions|null} */ (highlight || null); /** @type {string} */ this.query = query || ""; /** @type {string} */ this.field = field || ""; } } /** * @param {number} limit */ Resolver.prototype.limit = function(limit){ if(this.await){ const self = this; this.promises.push(function(){ return self.limit(limit).result; }); } else{ if(this.result.length){ /** @type {IntermediateSearchResults} */ const final = []; for(let j = 0, ids; j < this.result.length; j++){ if((ids = this.result[j])){ if(ids.length <= limit){ final[j] = ids; limit -= ids.length; if(!limit) break; } else{ final[j] = ids.slice(0, limit); break; } } } this.result = final; } } return this; }; /** * @param {number} offset */ Resolver.prototype.offset = function(offset){ if(this.await){ const self = this; this.promises.push(function(){ return self.offset(offset).result; }); } else{ if(this.result.length){ /** @type {IntermediateSearchResults} */ const final = []; for(let j = 0, ids; j < this.result.length; j++){ if((ids = this.result[j])){ if(ids.length <= offset){ offset -= ids.length; } else{ final[j] = ids.slice(offset); offset = 0; } } } this.result = final; } } return this; }; /** * @param {number} boost */ Resolver.prototype.boost = function(boost){ if(this.await){ const self = this; this.promises.push(function(){ return self.boost(boost).result; }); } else{ this.boostval += boost; } return this; }; /** * @param {boolean=} _skip_callback * @this {Resolver} */ Resolver.prototype.execute = function(_skip_callback){ let result = this.result; // temporary reset async state and restore when // at least one unresolved promise was found let execute = this.await; this.await = null; for(let i = 0, promise; i < this.promises.length; i++){ if((promise = this.promises[i])){ if(typeof promise === "function"){ result = promise(); this.promises[i] = result = result.result || result; i--; } else if(promise._fn){ result = promise._fn(); this.promises[i] = result = result.result || result; i--; } else if(promise.then){ return this.await = execute; } } } const fn = this.return; //this.result = result; this.promises = []; this.return = null; //this.await = null; // return final result _skip_callback || fn(result); return result; }; /** * @param {number|ResolverOptions=} limit * @param {number=} offset * @param {boolean=} enrich * @param {string|HighlightOptions|boolean=} highlight * @param {boolean=} _resolved */ Resolver.prototype.resolve = function(limit, offset, enrich, highlight, _resolved){ let result = this.await ? this.execute(true) : this.result; if(result.then){ const self = this; return result.then(function(){ return self.resolve(limit, offset, enrich, highlight, _resolved); }); } if(result.length){ if(typeof limit === "object"){ highlight = SUPPORT_DOCUMENT && SUPPORT_STORE && SUPPORT_HIGHLIGHTING && (limit.highlight || this.highlight); enrich = SUPPORT_DOCUMENT && SUPPORT_STORE && (!!highlight || limit.enrich); offset = limit.offset; limit = limit.limit; } else{ highlight = SUPPORT_DOCUMENT && SUPPORT_STORE && SUPPORT_HIGHLIGHTING && (highlight || this.highlight); enrich = SUPPORT_DOCUMENT && SUPPORT_STORE && (!!highlight || enrich); } result = _resolved ? (enrich ? apply_enrich.call( /** @type {Document} */ (this.index), /** @type {SearchResults} */ (result) ) : result) : default_resolver.call(this.index, result, limit || 100, offset, enrich); } return this.finalize(result, highlight); }; Resolver.prototype.finalize = function(result, highlight){ if(result.then){ const self = this; return result.then(function(result){ return self.finalize(result, highlight); }); } if(DEBUG){ if(highlight && !this.query){ console.warn('There was no query specified for highlighting. Please specify a query within the highlight resolver stage like { query: "...", highlight: ... }.'); } } if(SUPPORT_DOCUMENT && SUPPORT_STORE && SUPPORT_HIGHLIGHTING && highlight && result.length && this.query){ result = highlight_fields(this.query, result, this.index.index, this.field, highlight); } const fn = this.return; this.index = this.result = this.promises = this.await = this.return = null; if(SUPPORT_HIGHLIGHTING){ this.highlight = null; this.query = this.field = ""; } fn && fn(result); return result; } ================================================ FILE: src/serialize.js ================================================ // COMPILER BLOCK --> import { SUPPORT_STORE, SUPPORT_TAGS, SUPPORT_WORKER } from "./config.js"; import { IntermediateSearchResults } from "./type.js"; // <-- COMPILER BLOCK import Index from "./index.js"; import Document from "./document.js"; import { KeystoreMap, KeystoreSet } from "./keystore.js"; import { is_string } from "./common.js"; const chunk_size_reg = 250000; const chunk_size_map = 5000; const chunk_size_ctx = 1000; /** * @param {Map|KeystoreMap} map * @param {number=} size * @return {Array} */ function map_to_json(map, size = 0){ let chunk = []; let json = []; if(size){ size = chunk_size_map * (chunk_size_reg / size) | 0; } for(const item of map.entries()){ json.push(item); if(json.length === size){ chunk.push(json); json = []; } } json.length && chunk.push(json); return chunk; } /** * @param {Array} json * @param {Map|KeystoreMap} map * @return {Map|KeystoreMap} */ function json_to_map(json, map){ map || (map = new Map()); for(let i = 0, entry; i < json.length; i++) { entry = json[i]; map.set(entry[0], entry[1]); } return /** @type {Map} */ (map); } /** * @param {Map>|KeystoreMap>} ctx * @param {number=} size * @return {Array} */ function ctx_to_json(ctx, size = 0){ let chunk = []; let json = []; if(size){ size = chunk_size_ctx * (chunk_size_reg / size) | 0; } for(const item of ctx.entries()){ const key = item[0]; const value = item[1]; json.push([key, map_to_json(value)[0] || []]); if(json.length === size){ chunk.push(json); json = []; } } json.length && chunk.push(json); return chunk; } /** * @param {Array} json * @param {Map>|KeystoreMap>} ctx * @return {Map>|KeystoreMap>} */ function json_to_ctx(json, ctx){ ctx || (ctx = new Map()); for(let i = 0, entry, map; i < json.length; i++) { entry = json[i]; map = ctx.get(entry[0]); ctx.set(entry[0], json_to_map(entry[1], map)); } return ctx; } /** * @param { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } reg * @return {Array>} */ function reg_to_json(reg){ let chunk = []; let json = []; for(const key of reg.keys()){ json.push(key); if(json.length === chunk_size_reg){ chunk.push(json); json = []; } } json.length && chunk.push(json); return chunk; } /** * @param {Array} json * @param { * Set| * Map>| * KeystoreSet| * KeystoreMap> * } reg * @return { * Set| * KeystoreSet * } */ function json_to_reg(json, reg){ reg || (reg = new Set()); for(let i = 0; i < json.length; i++) { reg.add(json[i]); } return /** @type {Set} */ (reg); } /** * * @param {function(string, string):Promise|void} callback * @param {string|null|void} field * @param {string} key * @param {Array|null} chunk * @param {number} index_doc * @param {number} index_obj * @param {number=} index_prt * @this {Index|Document} * @return {Promise} */ function save(callback, field, key, chunk, index_doc, index_obj, index_prt = 0){ const is_arr = chunk && chunk.constructor === Array; const data = is_arr ? chunk.shift() : chunk; if(!data){ return this.export( callback, field, index_doc, index_obj + 1 ); } const res = callback( (field ? field + "." : "") + (index_prt + 1) + "." + key, JSON.stringify(data) ); if(res && res["then"]){ const self = this; return res["then"](function(){ return save.call(self, callback, field, key, is_arr ? chunk : null, index_doc, index_obj, index_prt + 1 ); }); } return save.call(this, callback, field, key, is_arr ? chunk : null, index_doc, index_obj, index_prt + 1 ); } /** * @param {function(string,string):Promise|void} callback * @param {!string|null=} _field * @param {number=} _index_doc * @param {number=} _index_obj * @this {Index} */ export function exportIndex(callback, _field, _index_doc = 0, _index_obj = 0){ let key, chunk; switch(_index_obj){ case 0: key = "reg"; chunk = reg_to_json(this.reg); break; case 1: // todo key = "cfg"; chunk = null; break; case 2: key = "map"; chunk = map_to_json(this.map, this.reg.size); break; case 3: key = "ctx"; chunk = ctx_to_json(this.ctx, this.reg.size); break; default: return; } return save.call(this, callback, _field, key, chunk, _index_doc, _index_obj ); } /** * @param {string} key * @param {string|Array=} data * @this Index */ export function importIndex(key, data){ if(!data){ return; } if(typeof data === "string"){ data = /** @type {Array} */( JSON.parse(/** @type {string} */(data)) ); } const split = key.split("."); if(split[split.length - 1] === "json"){ split.pop(); } if(split.length === 3){ split.shift(); } key = split.length > 1 ? split[1] : split[0]; switch(key){ case "cfg": // todo break; case "reg": // fast update isn't supported by export/import this.fastupdate = false; this.reg = json_to_reg(/** @type {Array} */ (data), this.reg); break; case "map": this.map = json_to_map(data, this.map); break; case "ctx": this.ctx = json_to_ctx(data, this.ctx); break; } } /** * @param {function(string,string):Promise|void} callback * @param {string|null=} _field * @param {number=} _index_doc * @param {number=} _index_obj * @this {Document} */ export function exportDocument(callback, _field, _index_doc = 0, _index_obj = 0){ if(_index_doc < this.field.length){ const field = this.field[_index_doc]; const idx = this.index.get(field); // start from index 1, because document indexes does not additionally store register const res = idx.export(callback, field, _index_doc, _index_obj = 1); if(res && res["then"]){ const self = this; return res["then"](function(){ return self.export(callback, field, _index_doc + 1); }); } return this.export(callback, field, _index_doc + 1); } else{ let key, chunk; switch(_index_obj){ case 0: key = "reg"; chunk = reg_to_json(this.reg); _field = null; break; case SUPPORT_TAGS && 1: key = "tag"; chunk = this.tag && ctx_to_json(this.tag, this.reg.size); _field = null; break; case SUPPORT_STORE && 2: key = "doc"; chunk = this.store && map_to_json( /** @type {Map} */ (this.store) ); _field = null; break; // case 3: // // key = "cfg"; // chunk = null; // _field = null; // break; default: return; } return save.call(this, callback, _field, key, /** @type {Array|null} */ (chunk || null), _index_doc, _index_obj ); } } /** * @param {!string} key * @param {string|Array} data * @this {Document} */ export function importDocument(key, data){ const split = key.split("."); if(split[split.length - 1] === "json"){ split.pop(); } const field = split.length > 2 ? split[0] : ""; const ref = split.length > 2 ? split[2] : split[1]; // trigger the import for worker field indexes if(SUPPORT_WORKER && this.worker && field){ return this.index.get(field).import(key); } if(!data){ return; } if(typeof data === "string"){ data = /** @type {Array} */( JSON.parse(/** @type {string} */(data)) ); } if(!field){ switch(ref){ case "reg": // fast update isn't supported by export/import this.fastupdate = false; this.reg = json_to_reg(/** @type {Array} */ (data), this.reg); for(let i = 0, idx; i < this.field.length; i++){ idx = this.index.get(this.field[i]); idx.fastupdate = false; idx.reg = this.reg; } // trigger the import for worker field indexes if(SUPPORT_WORKER && this.worker){ const promises = []; const self = this; for(const index of this.index.values()){ // const ref = item[0]; // const index = item[1]; promises.push(index.import(key)); //this.index.get(field).import(key); } return Promise.all(promises); } break; case "tag": this.tag = json_to_ctx(data, this.tag); break; case "doc": this.store = json_to_map(data, this.store); break; case "cfg": break; } } else{ return this.index.get(field).import(ref, data); } } /* reg: "1,2,3,4,5,6,7,8,9" map: "gulliver:1,2,3|4,5,6|7,8,9;" ctx: "gulliver+travel:1,2,3|4,5,6|7,8,9;" */ /** * @this {Index} * @param {boolean} withFunctionWrapper * @return {string} */ export function serialize(withFunctionWrapper = true){ let reg = ''; let map = ''; let ctx = ''; if(this.reg.size){ let type; for(const key of this.reg.keys()){ type || (type = typeof key); reg += (reg ? ',' : '') + (type === "string" ? '"' + key + '"' : key); } reg = 'index.reg=new Set([' + reg + ']);'; map = parse_map(this.map, type); map = "index.map=new Map([" + map + "]);"; for(const context of this.ctx.entries()){ const key_ctx = context[0]; const value_ctx = context[1]; let ctx_map = parse_map(value_ctx, type); ctx_map = "new Map([" + ctx_map + "])"; ctx_map = '["' + key_ctx + '",' + ctx_map + ']'; ctx += (ctx ? ',' : '') + ctx_map; } ctx = "index.ctx=new Map([" + ctx + "]);"; } return withFunctionWrapper ? "function inject(index){" + reg + map + ctx + "}" : reg + map + ctx } function parse_map(map, type){ let result = ''; for(const item of map.entries()){ const key = item[0]; const value = item[1]; let res = ''; for(let i = 0, ids; i < value.length; i++){ ids = value[i] || ['']; let str = ''; for(let j = 0; j < ids.length; j++){ str += (str ? ',' : '') + (type === "string" ? '"' + ids[j] + '"' : ids[j]); } str = '[' + str + ']'; res += (res ? ',' : '') + str; } res = '["' + key + '",[' + res + ']]'; result += (result ? ',' : '') + res; } return result; } ================================================ FILE: src/type.js ================================================ // When you are looking for type definitions which fully describes the usage take a look into the index.d.ts file. // Some of the types here aren't supposed to be used as public, they might be defined just for internal state. import Index from "./index.js"; import Document from "./document.js"; import WorkerIndex from "./worker.js"; import Encoder from "./encoder.js"; import StorageInterface from "./db/interface.js"; /** * @typedef {{ * preset: (string|undefined), * context: (IndexOptions|undefined), * encoder: (Encoder|Function|Object|undefined), * encode: (function(string):Array|undefined), * resolution: (number|undefined), * tokenize: (string|undefined), * fastupdate: (boolean|undefined), * score: (function():number|undefined), * keystore: (number|undefined), * rtl: (boolean|undefined), * cache: (number|boolean|undefined), * db: (StorageInterface|undefined), * commit: (boolean|undefined), * worker: (string|undefined), * config: (string|undefined), * priority: (number|undefined), * export: (Function|undefined), * import: (Function|undefined) * }} */ export let IndexOptions = {}; /** * @typedef {{ * preset: (string|undefined), * context: (IndexOptions|undefined), * encoder: (Encoder|Function|Object|undefined), * encode: (Function|undefined), * resolution: (number|undefined), * tokenize: (string|undefined), * fastupdate: (boolean|undefined), * score: (Function|undefined), * keystore: (number|undefined), * rtl: (boolean|undefined), * cache: (number|boolean|undefined), * db: (StorageInterface|undefined), * commit: (boolean|undefined), * config: (string|undefined), * priority: (number|undefined), * field: (string|undefined), * filter: (Function|undefined), * custom: (Function|undefined) * }} */ export let FieldOptions = {}; /** * @typedef {{ * context: (IndexOptions|undefined), * encoder: (Encoder|Function|Object|undefined), * encode: (Function|undefined), * resolution: (number|undefined), * tokenize: (string|undefined), * fastupdate: (boolean|undefined), * score: (Function|undefined), * keystore: (number|undefined), * rtl: (boolean|undefined), * cache: (number|boolean|undefined), * db: (StorageInterface|undefined), * doc: (DocumentDescriptor|Array|undefined), * document: (DocumentDescriptor|Array|undefined), * worker: (boolean|string|undefined), * priority: (number|undefined), * export: (Function|undefined), * import: (Function|undefined) * }} */ export let DocumentOptions = {}; /** * @typedef {{ * depth: (number|undefined), * bidirectional: (boolean|undefined), * resolution: (number|undefined), * }} */ export let ContextOptions = {}; /** * @typedef {{ * id: (string|undefined), * field: (string|Array|FieldOptions|Array|undefined), * index: (string|Array|FieldOptions|Array|undefined), * tag: (string|Array|TagOptions|Array|undefined), * store: (string|Array|StoreOptions|Array|boolean|undefined), * }} */ export let DocumentDescriptor = {}; /** * @typedef {{ * field: string, * filter: ((function(string):boolean)|undefined), * custom: ((function(string):string)|undefined), * db: (StorageInterface|undefined), * }} */ export let TagOptions = {}; /** * @typedef {{ * field: string, * filter: ((function(string):boolean)|undefined), * custom: ((function(string):string)|undefined) * }} */ export let StoreOptions = {}; /** * @typedef {{ * query: (string|undefined), * limit: (number|undefined), * offset: (number|undefined), * resolution: (number|undefined), * context: (boolean|undefined), * suggest: (boolean|undefined), * resolve: (boolean|undefined), * enrich: (boolean|undefined), * cache: (boolean|undefined) * }} */ export let SearchOptions = {}; /** * @typedef {{ * query: (string|undefined), * limit: (number|undefined), * offset: (number|undefined), * resolution: (number|undefined), * context: (boolean|undefined), * suggest: (boolean|undefined), * resolve: (boolean|undefined), * enrich: (boolean|undefined), * cache: (boolean|undefined), * tag: (Object|Array>|undefined), * field: (Array|Array|DocumentSearchOptions|string|undefined), * index: (Array|Array|DocumentSearchOptions|string|undefined), * pluck: (string|DocumentSearchOptions|undefined), * merge: (boolean|undefined), * highlight: (HighlightOptions|string|undefined) * }} */ export let DocumentSearchOptions = {}; /** * @typedef Array * @global */ export let SearchResults = []; /** * @typedef Array * @global */ export let IntermediateSearchResults = []; /** * @typedef Array<{ * id: (number|string), * doc: (Object|null), * highlight: (string|undefined) * }> */ export let EnrichedSearchResults = []; /** * @typedef Array<{ * field: (string|undefined), * tag: (string|undefined), * result: SearchResults * }> */ export let DocumentSearchResults = []; /** * @typedef Array<{ * field: (string|undefined), * tag: (string|undefined), * result: EnrichedSearchResults * }> */ export let EnrichedDocumentSearchResults = []; /** * @typedef {{ * id: (number|string), * doc: (Object|null|undefined), * field: (Array|undefined), * tag: (Array|undefined), * highlight: (Object|undefined) * }} */ export let MergedDocumentSearchEntry = {}; /** * @typedef Array */ export let MergedDocumentSearchResults = []; /** * @typedef {{ * letter: (boolean|undefined), * number: (boolean|undefined), * symbol: (boolean|undefined), * punctuation: (boolean|undefined), * control: (boolean|undefined), * char: (string|Array|undefined) * }} */ export let EncoderSplitOptions = {}; /** * @typedef {{ * rtl: (boolean|undefined), * dedupe: (boolean|undefined), * include: (EncoderSplitOptions|undefined), * exclude: (EncoderSplitOptions|undefined), * split: (string|boolean|RegExp|undefined), * numeric: (boolean|undefined), * normalize: (boolean|(function(string):string)|undefined), * prepare: ((function(string):string)|undefined), * finalize: ((function(Array):(Array|void))|undefined), * filter: (Set|function(string):boolean|undefined), * matcher: (Map|undefined), * mapper: (Map|undefined), * stemmer: (Map|undefined), * replacer: (Array|undefined), * minlength: (number|undefined), * maxlength: (number|undefined), * cache: (boolean|undefined) * }} */ export let EncoderOptions = {}; /** * @typedef {{ * name: (string|undefined), * field: (string|undefined), * type: (string|undefined), * db: (StorageInterface|undefined) * }} */ export let PersistentOptions = {}; /** * @typedef {{ * index: (Index|Document|WorkerIndex|undefined), * query: (string|undefined), * pluck: (string|undefined), * field: (string|undefined), * limit: (number|undefined), * offset: (number|undefined), * boost: (number|undefined), * enrich: (boolean|undefined), * highlight: (HighlightOptions|string|undefined), * resolve: (boolean|undefined), * suggest: (boolean|undefined), * cache: (boolean|undefined), * async: (boolean|undefined), * queue: (boolean|undefined), * and: (ResolverOptions|Array|undefined), * or: (ResolverOptions|Array|undefined), * xor: (ResolverOptions|Array|undefined), * not: (ResolverOptions|Array|undefined) * }} */ export let ResolverOptions = {}; /** * @typedef {{ * before: (number|undefined), * after: (number|undefined), * total: (number|undefined) * }} */ export let HighlightBoundaryOptions = {}; /** * @typedef {{ * template: string, * pattern: (string|boolean|undefined) * }} */ export let HighlightEllipsisOptions = {}; /** * @typedef {{ * template: string, * boundary: (HighlightBoundaryOptions|number|undefined), * clip: (boolean|undefined), * merge: (boolean|undefined), * ellipsis: (HighlightEllipsisOptions|string|boolean|undefined) * }} */ export let HighlightOptions = {}; ================================================ FILE: src/worker/handler.js ================================================ // COMPILER BLOCK --> import { DEBUG } from "../config.js"; // <-- COMPILER BLOCK import Index from "../index.js"; import { IndexOptions } from "../type.js"; /** @type Index */ let index; /** @type {IndexOptions} */ let options; export default async function(data) { data = data["data"]; const task = data["task"]; const id = data["id"]; let args = data["args"]; switch(task){ case "init": options = /** @type {IndexOptions} */ (data["options"] || {}); let filepath = options.config; if(filepath){ // compiler fix options = options; // will be replaced after build with the line below because // there is an issue with closure compiler dynamic import options=(await import(filepath))["default"]; } const factory = data["factory"]; if(factory){ // export the FlexSearch global payload to "self" Function("return " + factory)()(self); index = new self["FlexSearch"]["Index"](options); // destroy the exported payload delete self["FlexSearch"]; } else{ index = new Index(options); } postMessage({ "id": id }); break; default: let message; if(task === "export"){ if(DEBUG){ if(!options.export || typeof options.export !== "function"){ throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"export\"."); } } // skip non-field indexes if(!args[1]) args = null; else{ args[0] = options.export; args[2] = 0; args[3] = 1; // skip reg } } if(task === "import"){ if(DEBUG){ if(!options.import || typeof options.import !== "function"){ throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"import\"."); } } if(args[0]){ const data = await options.import.call(index, args[0]); index.import(args[0], data); } } else{ message = args && index[task].apply(index, args); if(message && message.then){ message = await message; } if(message && message.await){ message = await message.await; } if(task === "search" && message.result){ message = message.result; } } postMessage( task === "search" ? { "id": id, "msg": message } : { "id": id } ); } }; ================================================ FILE: src/worker/node.js ================================================ /* * Node.js Worker (CommonJS) * This file is a standalone file and isn't being a part of the build/bundle */ const { parentPort } = require("worker_threads"); //const { join } = require("path"); // Test Path //const { Index } = require("../../dist/flexsearch.bundle.min.js"); const { Index } = require("flexsearch"); /** @type Index */ let index; /** @type {IndexOptions} */ let options; parentPort.on("message", async function(data){ const task = data["task"]; const id = data["id"]; let args = data["args"]; switch(task){ case "init": options = data["options"] || {}; // load extern field configuration let filepath = options["config"]; if(filepath){ options = Object.assign({}, options, require(filepath)); delete options.worker; } index = new Index(options); //index.db && await index.db; parentPort.postMessage({ "id": id }); break; default: let message; if(task === "export"){ if(!options.export || typeof options.export !== "function"){ throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"export\"."); } // skip non-field indexes if(!args[1]) args = null; else{ args[0] = options.export; args[2] = 0; args[3] = 1; // skip reg } } if(task === "import"){ if(!options.import || typeof options.import !== "function"){ throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"import\"."); } if(args[0]){ const data = await options.import.call(index, args[0]); index.import(args[0], data); } } else{ message = args && index[task].apply(index, args); if(message && message.then){ message = await message; } if(message && message.await){ message = await message.await; } if(task === "search" && message.result){ message = message.result; } } parentPort.postMessage( task === "search" ? { "id": id, "msg": message } : { "id": id } ); } }); ================================================ FILE: src/worker/node.mjs ================================================ /* * Node.js Worker (Standalone, ESM) * This file is a standalone file and isn't being a part of the build/bundle */ import { parentPort } from "worker_threads"; import { join } from "path"; // Test Path //import Index from "../../src/index.js"; //import { Index } from "../../dist/flexsearch.bundle.module.min.js"; import { Index } from "flexsearch"; /** @type Index */ let index; /** @type {IndexOptions} */ let options; parentPort.on("message", async function(data){ const task = data["task"]; const id = data["id"]; let args = data["args"]; switch(task){ case "init": options = data["options"] || {}; // load extern field configuration let filepath = options["config"]; if(filepath){ filepath = join("file://", filepath); options = Object.assign({}, options, (await import(filepath))["default"]); delete options.worker; } index = new Index(options); //index.db && await index.db; parentPort.postMessage({ "id": id }); break; default: let message; if(task === "export"){ if(!options.export || typeof options.export !== "function"){ throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"export\"."); } // skip non-field indexes if(!args[1]) args = null; else{ args[0] = options.export; args[2] = 0; args[3] = 1; // skip reg } } if(task === "import"){ if(!options.import || typeof options.import !== "function"){ throw new Error("Either no extern configuration provided for the Worker-Index or no method was defined on the config property \"import\"."); } if(args[0]){ const data = await options.import.call(index, args[0]); index.import(args[0], data); } } else{ message = args && index[task].apply(index, args); if(message && message.then){ message = await message; } if(message && message.await){ message = await message.await; } if(task === "search" && message.result){ message = message.result; } } parentPort.postMessage( task === "search" ? { "id": id, "msg": message } : { "id": id } ); } }); ================================================ FILE: src/worker/worker.js ================================================ import handler from "./handler.js"; onmessage = handler; ================================================ FILE: src/worker.js ================================================ // COMPILER BLOCK --> import { DEBUG, SUPPORT_ASYNC, SUPPORT_CACHE, SUPPORT_WORKER } from "./config.js"; // <-- COMPILER BLOCK import { IndexOptions } from "./type.js"; import { create_object } from "./common.js"; import { searchCache } from "./cache.js"; import handler from "./worker/handler.js"; import apply_async from "./async.js"; import Encoder from "./encoder.js"; let pid = 0; /** * @param {IndexOptions=} options * @param {Encoder=} encoder * @constructor */ export default function WorkerIndex(options = /** @type IndexOptions */ ({}), encoder){ if(!this || this.constructor !== WorkerIndex) { return new WorkerIndex(options); } // the factory is the outer wrapper from the build // it uses "self" as a trap for node.js let factory = typeof self !== "undefined" ? self["_factory"] : typeof window !== "undefined" ? window["_factory"] : null; if(factory){ factory = factory.toString(); } const is_node_js = typeof window === "undefined" /*&& self["exports"]*/; const _self = this; /** * @this {WorkerIndex} */ function init(worker){ this.worker = worker; this.resolver = create_object(); if(!this.worker){ if(DEBUG){ console.warn("Worker is not available on this platform. Please report on Github: https://github.com/nextapps-de/flexsearch/issues"); } return; } function onmessage(msg){ msg = msg["data"] || msg; const id = msg["id"]; const res = id && _self.resolver[id]; if(res){ res(msg["msg"]); delete _self.resolver[id]; } } is_node_js ? this.worker["on"]("message", onmessage) : this.worker.onmessage = onmessage; if(options.config){ // when extern configuration needs to be loaded, // it needs to return a promise to await for return new Promise(function(resolve){ if(pid > 1e9) pid = 0; _self.resolver[++pid] = function(){ resolve(_self); }; _self.worker.postMessage({ "id": pid, "task": "init", "factory": factory, "options": options }); }); } if(SUPPORT_ASYNC){ this.priority = options.priority || 4; } // assign encoder for result highlighting this.encoder = encoder || null; // initialize worker index this.worker.postMessage({ "task": "init", "factory": factory, "options": options }); return this; } const worker = create(factory, is_node_js, options.worker); return worker.then ? worker.then(function(worker){ return init.call(_self, worker); }) : init.call(this, worker); } if(SUPPORT_WORKER){ register("add"); register("append"); register("search"); register("update"); register("remove"); register("clear"); register("export"); register("import"); if(SUPPORT_CACHE){ WorkerIndex.prototype.searchCache = searchCache; } if(SUPPORT_ASYNC){ apply_async(WorkerIndex.prototype); } } function register(key){ WorkerIndex.prototype[key] = function(){ const self = this; const args = [].slice.call(arguments); const arg = args[args.length - 1]; let callback; if(typeof arg === "function"){ callback = arg; args.pop(); } const promise = new Promise(function(resolve){ if(key === "export" && typeof args[0] === "function"){ // remove function handler args[0] = null; } if(pid > 1e9) pid = 0; self.resolver[++pid] = resolve; self.worker.postMessage({ "task": key, "id": pid, "args": args }); }); if(callback){ promise.then(callback); return this; } else{ return promise; } }; } function create(factory, is_node_js, worker_path){ return is_node_js ? // if anyone asks me what JS has delivered the past 10 years, // those are the lines I definitively show up first ^^ typeof module !== "undefined" // This eval will be removed when compiling // The issue is that this will not build by Closure Compiler ? (0,eval)('new(require("worker_threads")["Worker"])(__dirname+"/worker/node.js")') // this will need to remove in CommonJS builds, // otherwise the module is treated as ESM by Node.js automatic detection // the path src/worker/node.mjs is located at dist/node/node.mjs // The issue is that this will not build by Babel Compiler : import("worker_threads").then(function(worker){return new worker["Worker"](import.meta.dirname+"/worker/node.mjs")}) :( factory ? new window.Worker(URL.createObjectURL( new Blob( ["onmessage=" + handler.toString()], { "type": "text/javascript" } ) )) : new window.Worker( typeof worker_path === "string" ? worker_path // when loaded from /src/ folder the worker file is located at /worker/worker.js : import.meta.url.replace("/worker.js", "/worker/worker.js") .replace("flexsearch.bundle.module.min.js", "module/worker/worker.js") .replace("flexsearch.bundle.module.min.mjs", "module/worker/worker.js")/*"worker/worker.js"*/ , { type: "module" } ) ); } ================================================ FILE: task/babel.bundle.json ================================================ { "plugins": [ ["conditional-compile", { "dropDebugger": true, "define": { "RELEASE": "module", "DEBUG": false, "PROFILER": false, "POLYFILL": false, "SUPPORT_WORKER": true, "SUPPORT_ENCODER": true, "SUPPORT_CHARSET": true, "SUPPORT_CACHE": true, "SUPPORT_ASYNC": true, "SUPPORT_STORE": true, "SUPPORT_SUGGESTION": true, "SUPPORT_SERIALIZE": true, "SUPPORT_DOCUMENT": true, "SUPPORT_TAGS": true, "SUPPORT_PERSISTENT": true, "SUPPORT_KEYSTORE": true, "SUPPORT_COMPRESSION": true, "SUPPORT_RESOLVER": true, "SUPPORT_HIGHLIGHTING": true } }], "babel-plugin-minify-constant-folding", "babel-plugin-minify-dead-code-elimination", "babel-plugin-minify-flip-comparisons", "babel-plugin-minify-guarded-expressions", "babel-plugin-minify-infinity", "babel-plugin-minify-replace", "babel-plugin-minify-type-constructors", "babel-plugin-transform-member-expression-literals", "babel-plugin-transform-merge-sibling-variables", "babel-plugin-transform-minify-booleans", "babel-plugin-transform-property-literals", "babel-plugin-transform-simplify-comparison-operators", "babel-plugin-transform-undefined-to-void" ], "ignore": [ "config.js" ], "minified": false, "compact": false, "comments": true } ================================================ FILE: task/babel.debug.json ================================================ { "plugins": [ ["conditional-compile", { "dropDebugger": true, "define": { "RELEASE": "module", "DEBUG": true, "PROFILER": false, "POLYFILL": false, "SUPPORT_WORKER": true, "SUPPORT_ENCODER": true, "SUPPORT_CHARSET": true, "SUPPORT_CACHE": true, "SUPPORT_ASYNC": true, "SUPPORT_STORE": true, "SUPPORT_SUGGESTION": true, "SUPPORT_SERIALIZE": true, "SUPPORT_DOCUMENT": true, "SUPPORT_TAGS": true, "SUPPORT_PERSISTENT": true, "SUPPORT_KEYSTORE": true, "SUPPORT_COMPRESSION": true, "SUPPORT_RESOLVER": true, "SUPPORT_HIGHLIGHTING": true } }], "babel-plugin-minify-constant-folding", "babel-plugin-minify-dead-code-elimination", "babel-plugin-minify-flip-comparisons", "babel-plugin-minify-guarded-expressions", "babel-plugin-minify-infinity", "babel-plugin-minify-replace", "babel-plugin-minify-type-constructors", "babel-plugin-transform-member-expression-literals", "babel-plugin-transform-merge-sibling-variables", "babel-plugin-transform-minify-booleans", "babel-plugin-transform-property-literals", "babel-plugin-transform-simplify-comparison-operators", "babel-plugin-transform-undefined-to-void" ], "ignore": [ "config.js" ], "minified": false, "compact": false, "comments": true } ================================================ FILE: task/babel.js ================================================ const child_process = require("child_process"); const fs = require("fs"); const debug = process.argv[2] && process.argv[2].toLowerCase().includes("debug=true"); const minify = process.argv[2] && process.argv[2].toLowerCase().includes("release=min"); console.log("Start build ....."); console.log('Bundle: ' + ('module' /* 'custom' */) + (debug ? ":debug" : (minify ? ":min" : ""))); fs.existsSync("tmp") && fs.rmSync("tmp/", { recursive: true }); fs.mkdirSync("tmp"); fs.existsSync("dist") || fs.mkdirSync("dist"); (async function(){ let files = await fs.promises.readdir("./src/"); files.forEach(function(file){ if(file.endsWith(".js")){ let src = fs.readFileSync("src/" + file, "utf8"); src = src.replace(/\/\/ COMPILER BLOCK -->(.*)<-- COMPILER BLOCK/gs, ""); if(file === "worker.js"){ // add the eval wrapper #1 src = src.replace("import.meta.url", '(1,eval)("import.meta.url")'); // add the eval wrapper #2 src = src.replace( /[: ]+import\("worker_threads"\)[^}]+}\)/g, `: (0,eval)('import("worker_threads").then(function(worker){return new worker["Worker"]((1,eval)(\\"import.meta.dirname\\")+"/worker/node.mjs")})')` ); } let tmp; while(tmp !== src){ tmp = src; // remove comments, keep annotations src = src.replace(/[^:]\/\/(.*)(\r\n|\r|\n|$)/g, "$2"); src = src.replace(/^\/\/(.*)(\r\n|\r|\n|$)/g, "$2"); src = src.replace(/\/\*[^*](.*)\*\//g, ""); } fs.writeFileSync("tmp/" + file, src); } }); fs.existsSync("./tmp/db") || fs.mkdirSync("./tmp/db/"); fs.existsSync("./tmp/lang") || fs.mkdirSync("./tmp/lang/"); fs.existsSync("./tmp/charset") || fs.mkdirSync("./tmp/charset/"); const dirs = [ "db/", "db/clickhouse", "db/indexeddb", "db/mongodb", "db/postgres", "db/redis", "db/sqlite", "document", "index", "resolve", "worker", "lang", "charset/", "charset/latin" ]; for(let i = 0, path; i < dirs.length; i++){ path = dirs[i]; fs.existsSync("./tmp/" + path + "/") || fs.mkdirSync("./tmp/" + path + "/"); files = await fs.promises.readdir("./src/" + path + "/"); files.forEach(function(file){ if(file.endsWith(".old.js")) return; if(file.endsWith(".wip.js")) return; if(file.endsWith(".js")){ let src = fs.readFileSync("src/" + path + "/" + file, "utf8"); src = src.replace(/\/\/ COMPILER BLOCK -->(.*)<-- COMPILER BLOCK/gs, ""); if(path.startsWith("db/")){ src = src.replace(/import \{[^}]+} from "\.\.\/(\.\.\/)?type\.js";/, ''); } if(file === "handler.js"){ // add the eval wrapper src = src.replace('options=(await import(filepath))["default"];', '//options=(await import(filepath))["default"];'); } let tmp; while(tmp !== src){ tmp = src; // remove comments, keep annotations src = src.replace(/[^:]\/\/(.*)(\r\n|\r|\n|$)/g, "$2"); src = src.replace(/^\/\/(.*)(\r\n|\r|\n|$)/g, "$2"); src = src.replace(/\/\*[^*](.*)\*\//g, ""); } fs.writeFileSync("tmp/" + path + "/" + file, src); } }); } //fs.copyFileSync("src/db/interface.js", "tmp/db/interface.js"); fs.copyFileSync("task/babel." + (debug ? "debug": (minify ? "min" : "bundle")) + ".json", "tmp/.babelrc"); fs.existsSync("dist/module" + (debug ? "-debug" : (minify ? "-min" : ""))) && fs.rmSync("dist/module" + (debug ? "-debug" : (minify ? "-min" : "")), { recursive: true }); fs.mkdirSync("dist/module" + (debug ? "-debug" : (minify ? "-min" : ""))); exec("npx babel tmp -d dist/module" + (debug ? "-debug" : (minify ? "-min --minified --compact true" : "")) + " --config-file tmp/.babelrc && exit 0", function(){ console.log("Build Complete."); // fix babel compiler dynamic import let content = fs.readFileSync("dist/module" + (debug ? "-debug" : (minify ? "-min" : "")) + "/worker/handler.js", "utf8"); content = content.replace('//options=(await import(filepath))["default"];', 'options=(await import(filepath))["default"];'); fs.writeFileSync("dist/module" + (debug ? "-debug" : (minify ? "-min" : "")) + "/worker/handler.js", content); // fix babel compiler dynamic import content = fs.readFileSync("dist/module" + (debug ? "-debug" : (minify ? "-min" : "")) + "/worker.js", "utf8"); // replace the eval wrapper content = content.replace(/\(0, eval\)/g, "(0,eval)"); content = content.replace( `(0,eval)('import("worker_threads").then(function(worker){return new worker["Worker"]((1,eval)(\\"import.meta.dirname\\")+"/worker/node.mjs")})')`, `import("worker_threads").then(function(worker){return new worker["Worker"](import.meta.dirname+"/../node/node.mjs")})` ); content = content.replace( `(0,eval)("import(\\"worker_threads\\").then(function(worker){return new worker[\\"Worker\\"]((1,eval)(\\"import.meta.dirname\\")+\\"/worker/node.mjs\\")})")`, `import("worker_threads").then(function(worker){return new worker["Worker"](import.meta.dirname+"/../node/node.mjs")})` ); content = content.replace( `(0,eval)("new(require(\\"worker_threads\\")[\\"Worker\\"])(__dirname+\\"/worker/node.js\\")")`, `new(require("worker_threads")["Worker"])(__dirname+"/worker/node.js")` ); //content = content.replace(/\(1,eval\)\('([^']+)'\)/g, "import.meta.dirname"); content = content.replace('(1,eval)("import.meta.url")', 'import.meta.url'); content = content.replace('(1,eval)("import.meta.url")', 'import.meta.url'); content = content.replace('(0,eval)("import.meta.url")', 'import.meta.url'); content = content.replace('(0,eval)("import.meta.url")', 'import.meta.url'); content = content.replace('(1,eval)("import.meta.dirname")', 'import.meta.dirname'); fs.writeFileSync("dist/module" + (debug ? "-debug" : (minify ? "-min" : "")) + "/worker.js", content); }); }()); function exec(prompt, callback){ const child = child_process.exec(prompt, function(err, stdout, stderr){ if(err){ console.error(err); } else{ callback && callback(); } }); child.stdout.pipe(process.stdout); child.stderr.pipe(process.stderr); } ================================================ FILE: task/babel.min.json ================================================ { "plugins": [ ["conditional-compile", { "dropDebugger": true, "define": { "RELEASE": "module", "DEBUG": false, "PROFILER": false, "POLYFILL": false, "SUPPORT_WORKER": true, "SUPPORT_ENCODER": true, "SUPPORT_CHARSET": true, "SUPPORT_CACHE": true, "SUPPORT_ASYNC": true, "SUPPORT_STORE": true, "SUPPORT_SUGGESTION": true, "SUPPORT_SERIALIZE": true, "SUPPORT_DOCUMENT": true, "SUPPORT_TAGS": true, "SUPPORT_PERSISTENT": true, "SUPPORT_KEYSTORE": true, "SUPPORT_COMPRESSION": true, "SUPPORT_RESOLVER": true, "SUPPORT_HIGHLIGHTING": true } }], "babel-plugin-minify-constant-folding", "babel-plugin-minify-dead-code-elimination", "babel-plugin-minify-flip-comparisons", "babel-plugin-minify-guarded-expressions", "babel-plugin-minify-infinity", "babel-plugin-minify-mangle-names", "babel-plugin-minify-replace", "babel-plugin-minify-simplify", "babel-plugin-minify-type-constructors", "babel-plugin-transform-member-expression-literals", "babel-plugin-transform-merge-sibling-variables", "babel-plugin-transform-minify-booleans", "babel-plugin-transform-property-literals", "babel-plugin-transform-simplify-comparison-operators", "babel-plugin-transform-undefined-to-void" ], "ignore": [ "config.js" ], "minified": true, "compact": true, "comments": false } ================================================ FILE: task/build.js ================================================ const child_process = require("child_process"); const fs = require("fs"); console.log("Start build ....."); fs.existsSync("tmp") && fs.rmSync("tmp/", { recursive: true }); fs.mkdirSync("tmp"); fs.existsSync("dist") || fs.mkdirSync("dist"); let supported_lang = [ 'en', 'de', 'fr' ]; let supported_charset = { 'latin': ["exact", "default", "simple", "balance", "advanced", "extra", "soundex"], 'cjk': ["default"], 'cyrillic': ["default"], 'arabic': ["default"], }; let flag_str = ""; let language_out; let use_polyfill; let formatting; let compilation_level; let options = (function(argv){ const arr = {}; let count = 0; argv.forEach(function(val, index) { if(++count > 2){ index = val.split("="); val = index[1]; index = index[0].toUpperCase(); if(index === "LANGUAGE_OUT"){ language_out = val; } else if(index === "POLYFILL"){ use_polyfill = val !== "false"; } else{ if(val === "false") val = false; arr[index] = val; } } }); console.log('Release: ' + (arr['RELEASE'] || 'custom') + (arr['DEBUG'] ? ":debug" : "")); return arr; })(process.argv); let release = options["RELEASE"].toLowerCase(); const light_version = (release === "light") || (process.argv[2] === "--light"); const es5_version = (release === "es5") || (process.argv[2] === "--es5"); const module_version = (release === "module") || (process.argv[2] === "--module"); let parameter = (function(opt){ let parameter = ''; for(let index in opt){ if(opt.hasOwnProperty(index)){ //if(release !== "lang"){ parameter += ' --' + index + '=' + opt[index]; //} } } return parameter; })({ compilation_level: release === "bundle.profiler" ? "SIMPLE" : "ADVANCED", //"WHITESPACE" use_types_for_optimization: true, generate_exports: true, export_local_property_definitions: true, //language_in: "ECMASCRIPT_2017", language_out: language_out || "ECMASCRIPT_2020", process_closure_primitives: true, summary_detail_level: 3, warning_level: "VERBOSE", //emit_use_strict: true, // release !== "lang",, strict_mode_input: true, //assume_function_wrapper: true, //transform_amd_modules: true, process_common_js_modules: false, module_resolution: "BROWSER", //dependency_mode: "SORT_ONLY", //js_module_root: "./", entry_point: release === "lang" ? "./tmp/lang.js" : "./tmp/bundle.js", //manage_closure_dependencies: true, dependency_mode: "PRUNE", // PRUNE_LEGACY rewrite_polyfills: use_polyfill || false, //isolation_mode: "IIFE", //output_wrapper: /*release === "lang" ? "%output%" :*/ "\"(function(self){%output%}(this));\"" //formatting: "PRETTY_PRINT" }); if(options["DEBUG"]){ parameter += ' --formatting=PRETTY_PRINT'; } if(!release.endsWith(".module") || release === "lang"){ //parameter += ' --isolation_mode=IIFE'; parameter += ' --output_wrapper=\"(function(self){%output%}(this||self));\"'; parameter += ' --emit_use_strict=true'; } const custom = (!release || release.startsWith("custom")) && hashCode(parameter + flag_str + JSON.stringify(options)).replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); if(custom){ release || (options["RELEASE"] = release = "custom"); } if(release === "custom"){ if(typeof options["DEBUG"] === "undefined"){ options["DEBUG"] = false; } if(typeof options["PROFILER"] === "undefined"){ options["PROFILER"] = false; } if(typeof options["POLYFILL"] === "undefined"){ options["POLYFILL"] = false; } } if(release === "lang"){ //const charsets = Object.keys(supported_charset); //fs.existsSync("tmp/lang/") || fs.mkdirSync("tmp/lang/"); fs.cpSync("src/", "tmp/", { recursive: true }); fs.copyFileSync("src/db/interface.js", "tmp/db/interface.js"); // add the eval wrapper let content = fs.readFileSync("tmp/worker/handler.js", "utf8"); content = content.replace('options = (await import(filepath))["default"];', '//options = (await import(filepath))["default"];'); fs.writeFileSync("tmp/worker/handler.js", content); // add the eval wrapper content = fs.readFileSync("tmp/worker.js", "utf8"); content = content.replace("import.meta.url", '(1,eval)("import.meta.url")'); fs.writeFileSync("tmp/worker.js", content); (function next(x, y, z){ if(x < supported_lang.length){ (function(lang){ //fs.copyFileSync("src/lang/" + lang + ".js", "tmp/lang/" + lang + ".js"); //console.log(lang) content = fs.readFileSync("tmp/type.js", "utf8"); content = content.replace('import Index from "./index.js";', '') .replace('import WorkerIndex from "./worker.js";', '') .replace('import Document from "./document.js";', '') .replace('import Encoder from "./encoder.js";', '') .replace('import StorageInterface from "./db/interface.js";', ''); fs.writeFileSync("tmp/type.js", content); fs.writeFileSync("tmp/lang.js", ` import { EncoderOptions, EncoderSplitOptions } from "./type.js"; import lang from "./lang/${lang}.js"; /** @export */ EncoderOptions.rtl; /** @export */ EncoderOptions.dedupe; /** @export */ EncoderOptions.split; /** @export */ EncoderOptions.include; /** @export */ EncoderOptions.exclude; /** @export */ EncoderOptions.prepare; /** @export */ EncoderOptions.finalize; /** @export */ EncoderOptions.filter; /** @export */ EncoderOptions.matcher; /** @export */ EncoderOptions.mapper; /** @export */ EncoderOptions.stemmer; /** @export */ EncoderOptions.replacer; /** @export */ EncoderOptions.minlength; /** @export */ EncoderOptions.maxlength; /** @export */ EncoderOptions.cache; /** @export */ EncoderSplitOptions.letter; /** @export */ EncoderSplitOptions.number; /** @export */ EncoderSplitOptions.symbol; /** @export */ EncoderSplitOptions.punctuation; /** @export */ EncoderSplitOptions.control; /** @export */ EncoderSplitOptions.char; if(typeof module !== "undefined" && module["exports"]) module["exports"] = lang; else if(self["FlexSearch"]) self["FlexSearch"]["Language"]['${lang}'] = lang; `); const executable = process.platform === "win32" ? "\"node_modules/google-closure-compiler-windows/compiler.exe\"" : process.platform === "darwin" ? "\"node_modules/google-closure-compiler-osx/compiler\"" : "java -jar node_modules/google-closure-compiler-java/compiler.jar"; exec(executable + parameter + " --js='tmp/lang.js' --js='tmp/lang/*.js' --js='tmp/type.js'" + flag_str + " --js_output_file='dist/lang/" + lang + ".min.js' && exit 0", function(){ console.log("Build Complete: " + lang + ".min.js"); next(++x, y, z); }); })(supported_lang[x]); } // else if(y < charsets.length){ // // const charset = charsets[y]; // const variants = supported_charset[charset]; // // if(z < variants.length){ // // (function(charset, variant){ // // fs.writeFileSync("tmp/" + charset + "_" + variant + ".js", ` // import charset from "../src/lang/${charset}/${variant}.js"; // /*try{if(module)self=module}catch(e){}*/ // self["FlexSearch"]["registerCharset"]("${charset}:${variant}", charset); // `); // // exec("java -jar node_modules/google-closure-compiler-java/compiler.jar" + parameter + " --entry_point='tmp/" + charset + "_" + variant + ".js' --js='tmp/" + charset + "_" + variant + ".js' --js='src/**.js'" + flag_str + " --js_output_file='dist/lang/" + charset + "/" + variant + ".min.js' && exit 0", function(){ // // console.log("Build Complete: " + charset + "/" + variant + ".min.js"); // next(x, y, ++z); // }); // // })(charset, variants[z]); // } // else{ // // next(x, ++y, 0); // } // } }(0, 0, 0)); } else (async function(){ const files = await fs.promises.readdir("./src/"); // const files = [ // "async.js", // "cache.js", // "common.js", // "compress.js", // "config.js", // "document.js", // "encoder.js", // "engine.js", // "global.js", // "index.js", // "intersect.js", // "keystore.js", // "lang.js", // "polyfill.js", // "preset.js", // "resolver.js", // "serialize.js", // "type.js", // "bundle.js" // ]; files.forEach(function(file){ if(file === "config.js"){ let src = String(fs.readFileSync("src/" + file)); if(custom){ let defaults = src.split(/export const /); defaults.unshift(); defaults = defaults.filter(str => /^(SUPPORT|RELEASE)/.test(str)).map(str => str.replace(/=[\s\S]+/, "").trim()); for(let i = 0, opt; i < defaults.length; i++){ opt = defaults[i]; options[opt] = typeof options[opt] === "undefined" ? false : options[opt]; } } for(let opt in options){ src = src.replace(new RegExp('(export const ' + opt + ' = )(")?[^";]+(")?;'), "$1$2" + options[opt] + "$3;"); } fs.writeFileSync("tmp/" + file, src); } else{ if(file.endsWith(".js")){ if(language_out === "ECMASCRIPT5_STRICT" && file === "keystore.js"){ let content = fs.readFileSync("src/" + file, "utf8"); content = content.substring(0, content.indexOf("function lcg64")); content += "function lcg64(str){ throw new Error('The keystore is limited to 32 for EcmaScript5'); }"; fs.writeFileSync("tmp/keystore.js", content // "/** @constructor */ export function KeystoreMap(arg){};" + // "/** @constructor */ export function KeystoreSet(arg){};" + // "/** @constructor */ export function KeystoreArray(arg){}; KeystoreArray.prototype.push = function(arg){};" ); } else if(file === "worker.js"){ let content = fs.readFileSync("src/" + file, "utf8"); // add the eval wrapper #1 content = content.replace("import.meta.url", '(1,eval)("import.meta.url")'); // add the eval wrapper #2 content = content.replace( /[: ]+import\("worker_threads"\)[^}]+}\)/g, `: (0,eval)('import("worker_threads").then(function(worker){return new worker["Worker"]((1,eval)(\\"import.meta.dirname\\")+"/node/node.mjs")})')` ); fs.writeFileSync("tmp/worker.js", content) } else{ fs.copyFileSync("src/" + file, "tmp/" + file); } } } }); fs.existsSync("tmp/db/") || fs.mkdirSync("tmp/db/"); fs.cpSync("src/lang/", "tmp/lang/", { recursive: true }); fs.cpSync("src/worker/", "tmp/worker/", { recursive: true }); fs.cpSync("src/db/indexeddb/", "tmp/db/indexeddb/", { recursive: true }); fs.copyFileSync("src/db/interface.js", "tmp/db/interface.js"); fs.cpSync("src/index/", "tmp/index/", { recursive: true }); fs.cpSync("src/document/", "tmp/document/", { recursive: true }); fs.cpSync("src/resolve/", "tmp/resolve/", { recursive: true }); fs.cpSync("src/charset/", "tmp/charset/", { recursive: true }); // add the eval wrapper let content = fs.readFileSync("tmp/worker/handler.js", "utf8"); content = content.replace('options=(await import(filepath))["default"];', '//options=(await import(filepath))["default"];'); fs.writeFileSync("tmp/worker/handler.js", content); const filename = "dist/flexsearch." + (release + (custom ? "." + custom : "")) + (options["DEBUG"] ? ".debug" : ".min") + ".js"; const executable = process.platform === "win32" ? "\"node_modules/google-closure-compiler-windows/compiler.exe\"" : process.platform === "darwin" ? "\"node_modules/google-closure-compiler-osx/compiler\"" : "java -jar node_modules/google-closure-compiler-java/compiler.jar"; exec(executable + parameter + " --js='tmp/**.js' --js='!tmp/**/node.js' --js='!tmp/**/node.mjs'" + flag_str + " --js_output_file='" + filename + "' && exit 0", function(){ let build = fs.readFileSync(filename); let preserve = fs.readFileSync("src/index.js", "utf8"); const package_json = require("../package.json"); let name = ( custom ? release.replace("custom.", "Custom/") : light_version ? "Light" + (release === "light.module" ? "/Module" : "") : es5_version ? "ES5" : "Bundle" + (release === "bundle.module" ? "/Module" : "") ); if(custom) name += "/" + custom; if(options["DEBUG"]) name += "/Debug"; preserve = preserve.replace("* FlexSearch.js", "* FlexSearch.js v" + package_json.version + " (" + name + ")" ); build = preserve.substring(0, preserve.indexOf('*/') + 2) + "\n" + build; if(release === "bundle.module" || release === "light.module" || release === "compact.module" || release === "custom.module"){ // export default { // Index: O, // Encoder: H, // Charset: M, // Language: ma, // Document: Y, // Worker: W, // Resolver: null, // IndexedDB: null // }; const pos_start = build.indexOf("window.FlexSearch"); const pos_end = build.indexOf("};", pos_start) + 2; let part = build.substring(build.indexOf("{", pos_start) + 1, pos_end - 2); part = part.split(","); part = part.map(entry => "export const " + entry.replace(":", "=")); part = part.join(";") + ";"; // part = "export const Index=FlexSearch.Index;" + // "export const Charset=FlexSearch.Charset;" + // "export const Encoder=FlexSearch.Encoder;" + // "export const Document=FlexSearch.Document;" + // "export const Worker=FlexSearch.Worker;" + // "export const Resolver=FlexSearch.Resolver;" + // "export const IndexedDB=FlexSearch.IndexedDB;" + // "export const Language={};"; // part = "export default FlexSearch;" + part; //console.log(build.substring(pos_start - 50, pos_start) + part + build.substring(pos_end)) //build = build.substring(0, pos_start) + part + build.substring(pos_end); build = build.replace(/window\.FlexSearch(\s+)?=(\s+)?/, "export default ") + part; //build = build.replace(/self\.FlexSearch(\s+)?=(\s+)?/, "export default "); // replace the eval wrapper build = build.replace(/\(1,eval\)\('([^']+)'\)/g, "import.meta.dirname"); build = build.replace('(0,eval)("import.meta.url")', 'import.meta.url'); build = build.replace('(1,eval)("import.meta.url")', 'import.meta.url'); build = build.replace('(1,eval)("import.meta.dirname")', 'import.meta.dirname'); build = build.replace( `: (0,eval)('import("worker_threads").then(function(worker){return new worker["Worker"]((1,eval)(\\"import.meta.dirname\\")+"/node/node.mjs")})')`, `: import("worker_threads").then(function(worker){return new worker["Worker"](import.meta.dirname+"/node/node.mjs")})` ); //build = build.replace(/\(function\(self\)\{/, "const FlexSearch=(function(self){"); } else{ // add the eval wrapper #3 build = build.replace( `(0,eval)('new(require("worker_threads")["Worker"])(__dirname+"/worker/node.js")')`, `new(require("worker_threads")["Worker"])(__dirname+"/node/node.js")` ); } // fix closure compiler dynamic import build = build.replace(/\(([a-z])=([a-z]).config\)&&\(([a-z])=([a-z])\)/, "($1=$2.config)&&($3=(await import($4))[\"default\"])"); if(options["SUPPORT_WORKER"]){ build = build.replace("(function(self){'use strict';", "(function _f(self){'use strict';if(typeof module!=='undefined')self=module;else if(typeof process !== 'undefined')self=process;self._factory=_f;"); //build = build.replace("(function(self){", "(function _f(self){if(typeof module!=='undefined')self=module;else if(typeof process !== 'undefined')self=process;self._factory=_f;"); } // replace the eval wrapper build = build.replace(/\(0,eval\)\('([^']+)'\)/g, "$1"); if(build.includes("console.log")) console.warn("\n!!! Console was used in build !!!\n"); fs.writeFileSync(filename, build); fs.existsSync("dist/node/") || fs.mkdirSync("dist/node/"); fs.copyFileSync("src/worker/node.js", "dist/node/node.js"); fs.copyFileSync("src/worker/node.mjs", "dist/node/node.mjs"); fs.existsSync("dist/flexsearch.bundle.module.min.js") && fs.copyFileSync("dist/flexsearch.bundle.module.min.js", "dist/flexsearch.bundle.module.min.mjs"); fs.existsSync("dist/flexsearch.bundle.module.debug.js") && fs.copyFileSync("dist/flexsearch.bundle.module.debug.js", "dist/flexsearch.bundle.module.debug.mjs"); console.log("Saved to " + filename); console.log("Build Complete."); }); }()); // function hashCode(str) { // // let hash = 0, i, chr; // // if(str.length === 0){ // // return hash; // } // // for(i = 0; i < str.length; i++){ // // chr = str.charCodeAt(i); // hash = (hash << 5) - hash + chr; // } // // hash = Math.abs(hash) >> 0; // // return hash.toString(16).substring(0, 5); // } function hashCode(str) { console.log(str) let range = 2 ** 16 - 1; let crc = 0, bit = 16 + 1; for(let i = 0; i < str.length; i++) { crc = (crc * bit ^ str.charCodeAt(i)) & range; } return crc.toString(36).substring(0, 5); } function exec(prompt, callback){ const child = child_process.exec(prompt, function(err, stdout, stderr){ if(err){ console.error(err); } else{ if(callback){ callback(); } } }); child.stdout.pipe(process.stdout); child.stderr.pipe(process.stderr); } // https://github.com/KimlikDAO/kimlikdao-js/tree/ana/kdjs ================================================ FILE: test/.c8rc.json ================================================ { "reporter": ["text", "text-summary", "html"], "reports-dir": "./test/report/", "branches": 80, "functions": 80, "lines": 80, "statements": 80, "src": [ "src" ], "include": [ "src/**" ], "exclude": [ "test/*.js", "src/compress.js", "src/type.js", "src/profiler.js", "src/db/indexeddb/*", "src/worker/node.js", "src/worker/worker.js" ] } ================================================ FILE: test/async.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; console.log("--RELEASE-------------"); console.log(env ? "dist/" + env + ".js" : "src/bundle.js") console.log("----------------------"); let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; if(!build_light) describe("Add (Async)", function(){ it("Should have been added asynchronously to the index", async function(){ const index = new Index(/*{ priority: 4 }*/); let duration = 0; let time = Date.now(); setTimeout(function(){ duration = Date.now() - time; }); for(let i = 0; i < 1000; i++){ await index.addAsync(i, "foo"); if(duration) break; } expect(duration).to.equal(0); for(let i = 0; i < 999999999; i++){ await index.addAsync(i, "foo"); if(duration){ break; } } expect(duration).to.closeTo(50, 5); }); it("Should have been added asynchronously to the index (priority: 1)", async function(){ const index = new Index({ priority: 1 }); let duration = 0; let time = Date.now(); setTimeout(function(){ duration = Date.now() - time; }); for(let i = 0; i < 1000; i++){ await index.addAsync(i, "foo"); if(duration) break; } expect(duration).to.closeTo(0, 4); for(let i = 0; i < 999999999; i++){ await index.addAsync(i, "foo"); if(duration){ break; } } expect(duration).to.closeTo(4, 3); }); it("Should have been added asynchronously to the index (priority: 9)", async function(){ const index = new Index({ priority: 9 }); let duration = 0; let time = Date.now(); setTimeout(function(){ duration = Date.now() - time; }); for(let i = 0; i < 1000; i++){ await index.addAsync(i, "foo"); if(duration) break; } expect(duration).to.equal(0); for(let i = 0; i < 999999999; i++){ await index.addAsync(i, "foo"); if(duration){ break; } } expect(duration).to.closeTo(250, 25); }); }); ================================================ FILE: test/basic.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; describe("Initialize", function(){ const index = new Index(); it("Should have proper constructor", function(){ expect(index).to.be.an.instanceOf(Index); }); it("Should have all provided methods", function(){ expect(index).to.respondTo("search"); expect(index).to.respondTo("add"); expect(index).to.respondTo("append"); expect(index).to.respondTo("update"); expect(index).to.respondTo("remove"); expect(index).to.respondTo("clear"); expect(index).to.respondTo("cleanup"); expect(index).to.respondTo("contain"); expect(index).to.hasOwnProperty("map"); expect(index).to.hasOwnProperty("ctx"); expect(index).to.hasOwnProperty("reg"); if(!build_light){ expect(index).to.respondTo("searchAsync"); expect(index).to.respondTo("addAsync"); expect(index).to.respondTo("appendAsync"); expect(index).to.respondTo("updateAsync"); expect(index).to.respondTo("removeAsync"); expect(index).to.respondTo("export"); expect(index).to.respondTo("import"); expect(index).to.respondTo("serialize"); } }); it("Should have the default options", function(){ expect(index.resolution).to.equal(9); expect(index.depth).to.equal(0); expect(index.fastupdate).to.equal(false); }); it("Should have the default Encoder", function(){ const encoder = new Encoder(Charset.Default); expect(index.tokenize).to.equal("strict"); expect(typeof index.encoder.normalize).to.equal(typeof encoder.normalize); index.encoder.normalize = encoder.normalize; expect(index.encoder).to.eql(encoder); expect(index.encoder.minlength).to.equal(1); expect(index.encoder.maxlength).to.equal(1024); expect(index.encoder.rtl).to.equal(false); expect(index.encoder.numeric).to.equal(true); expect(index.encoder.dedupe).to.equal(true); }); }); describe("Add", function(){ it("Should have been properly added to the index", function(){ const index = new Index(); index.add(0, "foo"); index.add(2, "bar"); index.add(1, "FooBar"); index.add(3, "Some 'short' content."); expect(Array.from(index.reg.keys())).to.have.members([0, 1, 2, 3]); expect(Array.from(index.map.keys())).to.have.members(["fo", "bar", "fobar", "some", "short", "content"]); expect(index.ctx.size).to.equal(0); expect(index.reg.size).to.equal(4); }); it("Should have been numeric content properly added to the index (Triplets)", function(){ let index = new Index(); index.add(0, "TEST-123456789123456789"); index.add(1, "T10030"); index.add(2, "T10030T10030"); index.add(3, "1443-AB14345-1778"); expect(Array.from(index.reg.keys())).to.have.members([0, 1, 2, 3]); expect(Array.from(index.map.keys())).to.have.members([ "test", "123", "456", "789", "t", "10", "30", // id 2 was already completely added, split: "t", "10", "30", "t", "10", "30" "14", "3", "ab", "143", "45", "17", "8" ]); expect(index.ctx.size).to.equal(0); expect(index.reg.size).to.equal(4); // disabled deduplication index.clear(); index.encoder.dedupe = false; index.add(0, "TEST-123456789123456789"); index.add(1, "T10030"); index.add(2, "T10030T10030"); index.add(3, "1443-AB14345-1778"); expect(Array.from(index.reg.keys())).to.have.members([0, 1, 2, 3]); expect(Array.from(index.map.keys())).to.have.members([ "test", "123", "456", "789", "t", "100", "30", // id 2 was already completely added, split: "t", "100", "30", "t", "100", "30" "144", "3", "ab", "143", "45", "177", "8" ]); expect(index.ctx.size).to.equal(0); expect(index.reg.size).to.equal(4); }); it("Should not have been added to the index (Parameter)", function(){ const index = new Index(); index.add("foo"); index.add(3); index.add(null, "foobar"); index.add(void 0, "foobar"); index.add(3, null); index.add(3, false); expect(index.reg.size).to.equal(0); }); it("Should not have been added to the index (Empty)", function(){ const index = new Index(); index.add(1, ""); index.add(2, " "); index.add(3, " "); index.add(4, " - "); index.add(5, ` ... - : , <-- `); expect(index.reg.size).to.equal(0); }); }); describe("Append", function(){ it("Should have been properly appended to the index", function(){ const index = new Index(); index.append(0, "foo"); index.append(2, "bar"); index.append(1, "FooBar"); index.append(3, "Some 'short' content."); expect(Array.from(index.reg.keys())).to.have.members([0, 1, 2, 3]); expect(Array.from(index.map.keys())).to.have.members(["fo", "bar", "fobar", "some", "short", "content"]); expect(index.ctx.size).to.equal(0); expect(index.reg.size).to.equal(4); }); it("Should have been properly appended by score", function(){ let index = new Index(); index.add(0, "foo A B C D E F bar"); index.add(1, "foo A B C bar D E F"); index.add(2, "foo bar A B C D E F"); expect(index.search("foo")).to.eql([0, 1, 2]); expect(index.search("bar")).to.eql([2, 1, 0]); expect(index.reg.size).to.equal(3); expect(index.map.size).to.equal(8); expect(index.ctx.size).to.equal(0); index.append(0, "bar"); index.append(0, "foo"); index.append(0, "bar"); expect(index.search("foo")).to.eql([0, 1, 2]); expect(index.search("bar")).to.eql([0, 2, 1]); expect(index.reg.size).to.equal(3); expect(index.map.size).to.equal(8); expect(index.ctx.size).to.equal(0); index.remove(2).remove(1).remove(0); expect(index.reg.size).to.equal(0); expect(index.map.size).to.equal(0); expect(index.ctx.size).to.equal(0); index = new Index({ context: true }); index.add(0, "foo A B C D E F bar"); index.add(1, "foo A B C bar D E F"); index.add(2, "foo bar A B C D E F"); expect(index.search("foo")).to.eql([0, 1, 2]); expect(index.search("bar")).to.eql([2, 1, 0]); expect(index.reg.size).to.equal(3); expect(index.map.size).to.equal(8); expect(index.ctx.size).to.equal(7); index.append(0, "bar"); index.append(0, "foo"); index.append(0, "bar"); expect(index.search("foo")).to.eql([0, 1, 2]); expect(index.search("bar")).to.eql([0, 2, 1]); expect(index.reg.size).to.equal(3); expect(index.map.size).to.equal(8); expect(index.ctx.size).to.equal(7); index.remove(2).remove(1).remove(0); expect(index.reg.size).to.equal(0); expect(index.map.size).to.equal(0); expect(index.ctx.size).to.equal(0); }); it("Should not have been appended to the index (Parameter)", function(){ const index = new Index(); index.append("foo"); index.append(3); index.append(null, "foobar"); index.append(void 0, "foobar"); index.append(3, null); index.append(3, false); expect(index.reg.size).to.equal(0); }); it("Should not have been appended to the index (Empty)", function(){ const index = new Index(); index.append(1, ""); index.append(2, " "); index.append(3, " "); index.append(4, " - "); index.append(5, ` ... - : , <-- `); expect(index.reg.size).to.equal(0); }); }); describe("Search (Sync)", function(){ it("Should have been matched properly", function(){ const index = new Index(); index.add(0, "foo"); index.add(1, "bar"); index.add(2, "FooBar"); index.add(3, "Some 'short' content."); index.add(4, "Foo Bar"); expect(index.search("foo")).to.have.members([0, 4]); expect(index.search("bar")).to.include(1, 4); expect(index.search("foobar")).to.include(2); expect(index.search("short 'content'")).to.include(3); expect(index.search("foo foo")).to.have.members([0, 4]); expect(index.search("foo foo bar foo bar")).to.have.members([4]); }); it("Should have been applied limit/offset properly", function(){ const index = new Index(); for(let i = 0; i < 10; i++){ index.add(i, "foo"); } expect(index.search("foo", 99)).to.have.members([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); expect(index.search("foo", 3)).to.have.members([0, 1, 2]); expect(index.search("foo", { limit: 3 })).to.have.members([0, 1, 2]); expect(index.search("foo", { limit: 3, offset: 3 })).to.have.members([3, 4, 5]); expect(index.search("foo", { limit: 3, offset: 9 })).to.have.members([9]); expect(index.search("foo", { limit: 3, offset: 10 })).to.have.members([]); expect(index.search({ query: "foo", limit: 1 })).to.include(0); }); }); describe("Search Scoring", function(){ it("Should have been matched properly", function(){ const index = new Index(); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); let result = index.search("cats cute"); expect(result.length).to.equal(7); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = index.search("cute cats"); expect(result.length).to.equal(7); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = index.search("cute"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = index.search("cute dogs cats"); expect(result.length).to.equal(1); expect(result).to.eql([1]); result = index.search("cute dogs cats", { suggest: true }); expect(result).to.eql([1, 6, 5, 4, 3, 2, 0]); result = index.search("undefined cute undefined dogs undefined cats undefined", { suggest: true }); expect(result).to.eql([1, 6, 5, 4, 3, 2, 0]); result = index.search("cute cat"); expect(result.length).to.equal(0); }); }); describe("Tokenizer", function(){ it("Should have been \"forward\" tokenized properly", function(){ const index = new Index({ tokenize: "forward" }); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); let result = index.search("cat cute"); expect(result.length).to.equal(7); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = index.search("cute cat"); expect(result.length).to.equal(7); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = index.search("cute"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); }); it("Should have been \"reverse\" tokenized properly", function(){ const index = new Index({ tokenize: "reverse", resolution: 12 }); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); let result = index.search("ats ute"); expect(result.length).to.equal(7); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = index.search("ute ats"); expect(result.length).to.equal(7); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = index.search("cute"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); }); it("Should have been \"full\" tokenized properly", function(){ const index = new Index({ tokenize: "full", resolution: 12 }); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); let result = index.search("at ut"); expect(result.length).to.equal(7); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = index.search("ut at"); expect(result.length).to.equal(7); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = index.search("cute"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); }); }); describe("Search: Suggestion", function(){ it("Should have been provide suggestions properly", function(){ const index = new Index({ tokenize: "forward" }); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', // <-- dogs 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); let result = index.search("cute dog or cute cat or nothing", { suggest: true }); expect(result.length).to.equal(7); expect(result).to.eql([1, 6, 5, 4, 3, 2, 0]); result = index.search("nothing or cute cat or cute dog", { suggest: true }); expect(result.length).to.equal(7); expect(result).to.eql([1, 6, 5, 4, 3, 2, 0]); result = index.search("cute"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); }); }); describe("Update (Sync)", function(){ it("Should have been updated to the index", function(){ const index = new Index({ fastupdate: true, tokenize: "reverse" }); index.add(1, "foo"); index.add(2, "bar"); index.add(3, "foobar"); index.update(1, "bar"); index.update(2, "foobar"); index.update(3, "foo"); expect(index.reg.size).to.equal(3); expect(index.search("foo")).to.eql([2, 3]); expect(index.search("bar")).to.eql([1, 2]); expect(index.search("bar")).to.not.include(3); expect(index.search("foobar")).to.eql([2]); index.update(1, "bar"); index.update(2, "foobar"); index.update(3, "foo"); index.update(4, "new"); index.update(5, "foo"); index.update(5); index.update(6, "foo"); index.update(6, null); index.update(7, "foo"); index.update(7, false); expect(index.reg.size).to.equal(4); expect(index.search("foo")).to.eql([2, 3]); expect(index.search("bar")).to.eql([1, 2]); expect(index.search("bar")).to.not.include(3); expect(index.search("foobar")).to.eql([2]); expect(index.search("new")).to.eql([4]); }); it("Should not have been updated to the index", function(){ const index = new Index({ tokenize: "bidirectional" }); index.add(1, "bar"); index.add(2, "foobar"); index.add(3, "foo"); index.update("foo"); index.update(null, "foobar"); index.update(void 0, "foobar"); expect(index.reg.size).to.equal(3); expect(index.search("foo")).to.eql([2, 3]); expect(index.search("bar")).to.eql([1, 2]); expect(index.search("bar")).to.not.include(3); expect(index.search("foobar")).to.eql([2]); }); }); describe("Remove (Sync)", function(){ it("Should have been removed from the index", function(){ const index = new Index({ fastupdate: true, tokenize: "reverse" }); index.add(1, "bar"); index.add(2, "foobar"); index.add(3, "foo"); index.remove(2); index.remove(1); index.remove(3); index.remove(4); expect(index.reg.size).to.equal(0); expect(index.search("foo")).to.have.lengthOf(0); expect(index.search("bar")).to.have.lengthOf(0); expect(index.search("foobar")).to.have.lengthOf(0); }); }); describe("Reserved Words", function(){ it("Should have reserved properties taken into account", function(){ const index = new Index({ encode: function(str){ return [str]; }, tokenize: "strict" }); let array = Object.getOwnPropertyNames({}.__proto__); array = array.concat(Object.getOwnPropertyNames(index)); array = array.concat(Object.getOwnPropertyNames(index.map)); array.includes("prototype") || array.push("prototype"); array.includes("constructor") || array.push("constructor"); array.includes("__proto__") || array.push("__proto__"); array.includes("concat") || array.push("concat"); array.includes("hasOwnProperty") || array.push("hasOwnProperty"); array.includes("length") || array.push("length"); array.includes("ctx") || array.push("ctx"); for(let i = 0; i < array.length; i++){ index.add(i, array[i]); } for(let i = 0; i < array.length; i++){ expect(index.search(array[i])).to.eql([i]); } }); }); describe("Presets", function(){ it("Should have been properly initialized", function(){ expect(Index("memory").resolution).to.equal(1); expect(Index("performance").resolution).to.equal(3); expect(Index("match").resolution).to.equal(9); expect(Index("score").resolution).to.equal(9); }); it("Should have been properly extended", function(){ let index = Index({ preset: "memory" }); index.add(0, "foobar"); expect(index.search("bar")).to.have.lengthOf(0); index = Index({ preset: "memory", tokenize: "reverse" }); index.add(0, "foobar"); expect(index.search("bar")).to.eql([0]); index = Index("match"); index.add(0, "foobar"); expect(index.search("oba")).to.eql([0]); }); }); ================================================ FILE: test/cache.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; if(!build_light) describe("Cache", function(){ it("Should have been cached and sorted by popularity/latest", function(){ const limit = 0; const index = new Index({ tokenize: "reverse", cache: 2, limit }); index.add(0, "foo") .add(1, "bar") .add(2, "foobar"); expect(index.searchCache("foo")).to.eql([0, 2]); expect(index.searchCache("bar")).to.eql([1, 2]); expect(index.searchCache("foobar")).to.eql([2]); expect(index.cache.cache.size).to.equal(2); env || expect(index.cache.last).to.equal("foobar" + limit); expect(Array.from(index.cache.cache.values()).pop()).to.eql([2]); expect(index.searchCache("foobar")).to.eql([2]); expect(index.searchCache("bar")).to.eql([1, 2]); expect(index.searchCache("foo")).to.eql([0, 2]); expect(index.cache.cache.size).to.equal(2); env || expect(index.cache.last).to.equal("foo" + limit); expect(Array.from(index.cache.cache.values()).pop()).to.eql([0, 2]); expect(index.searchCache("bar")).to.eql([1, 2]); expect(Array.from(index.cache.cache.values()).pop()).to.eql([1, 2]); }); it("Should have been synchronized properly", function(){ const index = new Index({ tokenize: "reverse", cache: 2 }); index.add(0, "foo") .add(1, "bar") .add(2, "foobar"); expect(index.searchCache("foo")).to.eql([0, 2]); expect(index.cache.cache.size).to.equal(1); expect(index.searchCache("bar")).to.eql([1, 2]); expect(index.cache.cache.size).to.equal(2); index.remove(2).update(1, "foo").add(3, "foobar"); // 2 was removed expect(index.cache.cache.size).to.equal(1); // 2 was removed, 3 was added, the cache takes the original reference // that's why this was automatically added here // it does not need to invalidate for this reason expect(Array.from(index.cache.cache.values()).pop()).to.eql([0, 1, 3]); expect(index.searchCache("foo")).to.eql([0, 1, 3]); expect(index.search("foo")).to.eql([0, 1, 3]); expect(Array.from(index.cache.cache.values()).pop()).to.eql([0, 1, 3]); expect(index.searchCache("bar")).to.eql([3]); expect(index.search("bar")).to.eql([3]); expect(Array.from(index.cache.cache.values()).pop()).to.eql([3]); expect(index.searchCache("foobar")).to.eql([3]); expect(index.search("foobar")).to.eql([3]); expect(Array.from(index.cache.cache.values()).pop()).to.eql([3]); expect(index.searchCache("foo")).to.eql([0, 1, 3]); expect(Array.from(index.cache.cache.values()).pop()).to.eql([0, 1, 3]); }); }); ================================================ FILE: test/context.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; describe("Context", function(){ it("Should have been added properly to the context (bidirectional enabled)", function(){ let index = new Index({ tokenize: "strict", context: { depth: 2 } }); index.add(0, "zero one two three four five six seven eight nine ten"); expect(index.reg.size).to.equal(1); expect(index.search("zero one")).to.include(0); expect(index.search("zero two")).to.include(0); // breaks the context chain: expect(index.search("zero three").length).to.equal(0); expect(index.search("zero three", { suggest: true })).to.include(0); // breaks the context chain: expect(index.search("three seven").length).to.equal(0); expect(index.search("three seven", { suggest: true })).to.include(0); expect(index.search("three five seven")).to.include(0); // bidirectional: expect(index.search("eight six four")).to.include(0); expect(index.search("seven five three")).to.include(0); expect(index.search("three foobar seven").length).to.equal(0); expect(index.search("three foobar seven", { suggest: true })).to.include(0); expect(index.search("seven foobar three").length).to.equal(0); expect(index.search("seven foobar three", { suggest: true })).to.include(0); expect(index.search("eight ten")).to.include(0); expect(index.search("ten nine seven eight six five three four two zero one")).to.include(0); index.add(1, "1 2 3 1 4 2 5 1"); expect(index.search("1")).to.include(1); expect(index.search("1 5")).to.include(1); expect(index.search("2 4 1")).to.include(1); // disable bidirectional index = new Index({ tokenize: "strict", context: { depth: 2, bidirectional: false } }); index.add(0, "zero one two three four five six seven eight nine ten"); expect(index.search("ten nine seven eight six five three four two zero one").length).to.equal(0); }); it("Should have been added properly to the context when bidirectional was disabled", function(){ let index = new Index({ tokenize: "strict", context: { depth: 2, bidirectional: false } }); index.add(1, "1 2 3 4 5 6 7 8 9"); expect(index.search("3 1").length).to.equal(0); expect(index.search("4 3 2").length).to.equal(0); }); it("Should have been added properly when dupes will break the context chain", function(){ const index = new Index({ context: { depth: 2 } }); index.add(1, "1 2 3 4 5 6 7 8 1 2 9"); expect(index.search("1 9")).to.include(1); expect(index.search("1 2 9")).to.include(1); expect(index.search("9 1 2")).to.include(1); // todo shuffled chain: //expect(index.search("1 3 9")).to.include(1); expect(index.search("3 1 9")).to.include(1); expect(index.search("9 1 3")).to.include(1); // todo shuffled chain: //expect(index.search("9 3 1")).to.include(1); }); it("Should have been added properly when dupes will break the context chain", function(){ // the default scoring is quite capable let index = new Index(); index.add(1, "1 A B C D E F 2 G H I J K L 3"); index.add(2, "A B C D E F G H I J 1 2 3 K L"); let result = index.search("1 2 3"); expect(result[0]).to.equal(2); result = index.search("3 2 1"); expect(result[0]).to.equal(2); // from here it starts index = new Index(); index.add(1, "1 A B C D 2 E F G H I 3 J K L"); index.add(2, "A B C D E F G H I J 1 2 3 K L"); result = index.search("1 2 3"); expect(result[0]).to.equal(1); index = new Index({ context: true }); index.add(1, "1 A B C D 2 E F G H I 3 J K L"); index.add(2, "A B C D E F G H I J 1 2 3 K L"); result = index.search("1 2 3"); expect(result[0]).to.equal(2); result = index.search("1 2 3", { context: false }); expect(result[0]).to.equal(1); }); it("Should have been handled properly the context chain (term deduplication)", function(){ let index = new Index({ context: true }); index.add(1, "A A B B C C A A B B C C"); let result = index.search("A"); expect(result).to.eql([1]); result = index.search("A A"); expect(result).to.eql([1]); result = index.search("A A A"); expect(result).to.eql([1]); result = index.search("A B A"); expect(result).to.eql([1]); result = index.search("A B B"); expect(result).to.eql([1]); result = index.search("B A B"); expect(result).to.eql([1]); result = index.search("B A A"); expect(result).to.eql([1]); result = index.search("C C B B A A"); expect(result).to.eql([1]); }); }); ================================================ FILE: test/debug.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; describe("Debug", function(){ }); ================================================ FILE: test/document.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; if(!build_light) describe("Document (Multi-Field Search)", function(){ const data = [{ id: 2, data: { title: "Title 3", body: "Body 3", cat: "A" } },{ id: 1, data: { title: "Title 2", body: "Body 2", cat: "B" } },{ id: 0, data: { title: "Title 1", body: "Body 1", cat: "A" } }]; const update = [{ id: 0, data: { title: "Foo 1", body: "Bar 1" } },{ id: 1, data: { title: "Foo 2", body: "Bar 2" } },{ id: 2, data: { title: "Foo 3", body: "Bar 3" } }]; it("Should have been indexed properly", function(){ const document = new Document({ document: { id: "id", field: [ "data:title", "data:body" ] } }); const document_with_store = new Document({ keystore: 2, fastupdate: true, document: { store: true, id: "id", field: [ "data:title", "data:body" ] } }); for(let i = 0; i < data.length; i++){ document.add(data[i]); document_with_store.add(data[i]); } expect(document.index.size).to.equal(2); expect(document.reg.size).to.equal(3); // Registry Sharing expect(document.index.get("data:title").reg).to.equal(document.reg); expect(document.index.get("data:title").reg).to.not.equal(document_with_store.reg); expect(document.search({ query: "title" })).to.eql([{ field: "data:title", result: [2, 1, 0] }]); expect(document.search({ query: "title", field: "data:title" })).to.eql([{ field: "data:title", result: [2, 1, 0] }]); expect(document_with_store.search({ query: "title", pluck: "data:title", enrich: true }).map(res => res.doc)).to.eql(data); expect(document.search({ field: "data:body", query: "title" })).to.have.lengthOf(0) expect(document.search({ field: "data:title", query: "body" })).to.have.lengthOf(0); expect(document.search({ field: "data:body", query: "body" })).to.eql([{ field: "data:body", result: [2, 1, 0] }]); expect(document.search({ field: ["data:title"], query: "title" })).to.eql([{ field: "data:title", result: [2, 1, 0] }]); expect(document.search({ field: ["data:title", "data:body"], query: "body" })).to.eql([{ field: "data:body", result: [2, 1, 0] }]); expect(document.search({ field: ["data:body", "data:title"], query: "title" })).to.eql([{ field: "data:title", result: [2, 1, 0] }]); expect(document.search({ field: ["data:title", "data:body"], query: "body" })).to.eql([{ field: "data:body", result: [2, 1, 0] }]); expect(document.search({ field: ["data:body", "data:title"], query: "title" })).to.eql([{ field: "data:title", result: [2, 1, 0] }]); expect(document.search("body", { field: "data:body" })).to.eql([{ field: "data:body", result: [2, 1, 0] }]); expect(document.search("title", { field: ["data:title"] })).to.eql([{ field: "data:title", result: [2, 1, 0] }]); expect(document.search({ query: "body" })).to.eql([{ field: "data:body", result: [2, 1, 0] }]); expect(document.search("title")).to.eql([{ field: "data:title", result: [2, 1, 0] }]); expect(document.search([{ field: "data:title", query: "body" },{ field: "data:body", query: "body" }])).to.eql([{ field: "data:body", result: [2, 1, 0] }]); // --------------------------------------- for(let i = 0; i < update.length; i++){ document.add(update[i]); document_with_store.add(update[i]); } expect(document.search("foo")).to.eql([{ field: "data:title", result: [0, 1, 2] }]); expect(document.search("bar")).to.eql([{ field: "data:body", result: [0, 1, 2] }]); expect(document.search("foo bar", { suggest: true })).to.eql([{ field: "data:title", result: [0, 1, 2] },{ field: "data:body", result: [0, 1, 2] }]); expect(document.search("foo bar", { suggest: true, merge: true })).to.eql([ { id: 0, field: [ 'data:title', 'data:body' ] }, { id: 1, field: [ 'data:title', 'data:body' ] }, { id: 2, field: [ 'data:title', 'data:body' ] } ]); expect(document_with_store.search({ query: "foo", pluck: "data:title", enrich: true }).map(res => res.doc)).to.eql(update); expect(document_with_store.search({ query: "bar", pluck: "data:body", enrich: true }).map(res => res.doc)).to.eql(update); // --------------------------------------- for(let i = 0; i < update.length; i++){ document.remove(update[i]); document_with_store.remove(update[i]); } expect(document.reg.size).to.equal(0); expect(document.index.get("data:title").reg.size).to.equal(0); expect(document.index.get("data:body").reg.size).to.equal(0); expect(document.index.get("data:title").map.size).to.equal(0); expect(document.index.get("data:body").map.size).to.equal(0); expect(document_with_store.store.size).to.equal(0); expect(document_with_store.search({ query: "foo", })).to.eql([]); expect(document_with_store.search({ query: "bar" })).to.eql([]); }); it("Should have been unique results", function(){ const document = new Document({ document: { id: "id", field: ["field1", "field2"] } }); const data = [{ id: 1, field1: "phrase", field2: "phrase next" },{ id: 2, field1: "phrase next", field2: "phrase" }]; for(let i = 0; i < data.length; i++){ document.add(data[i]); } expect(document.search("phrase")).to.eql([{ field: "field1", result: [1, 2] },{ field: "field2", result: [1, 2] }]); expect(document.search("phrase", { suggest: true })).to.eql([{ field: "field1", result: [1, 2] },{ field: "field2", result: [1, 2] }]); }); it("Should have been sorted properly by number of field count matches", function(){ const document = new Document({ document: { id: "id", field: ["field1", "field2"] } }); const data = [{ id: 1, field1: "phrase", field2: "phrase next" },{ id: 2, field1: "phrase next", field2: "phrase" }]; for(let i = 0; i < data.length; i++){ document.add(data[i]); } expect(document.search("phrase", { suggest: true, merge: true })).to.eql([ { id: 1, field: [ 'field1', 'field2' ] }, { id: 2, field: [ 'field1', 'field2' ] } ]); expect(document.search("phrase next", { suggest: true, merge: true })).to.eql([ { id: 2, field: [ 'field1', 'field2' ] }, { id: 1, field: [ 'field1', 'field2' ] } ]); }); it("Should not have been shared the Encoder", function(){ const document = new Document({ document: { id: "id", field: ["field1", "field2"] } }); expect(document.index.get("field1").encoder).not.to.equal( document.index.get("field2").encoder ); }); it("Should have been shared the Encoder", function(){ const document = new Document({ encoder: new Encoder(), document: { id: "id", field: ["field1", "field2"] } }); expect(document.index.get("field1").encoder).to.equal( document.index.get("field2").encoder ); }); it("Should have been applied limit/offset properly", function(){ const document = new Document({ document: { store: true, id: "id", field: [ "data:title", "data:body" ] } }); for(let i = 0; i < data.length; i++){ document.add(data[i]); } expect(document.search({ query: "title", pluck: "data:title", enrich: true, suggest: true, limit: 2 }).map(res => res.doc)).to.eql([data[0], data[1]]); expect(document.search({ query: "body", pluck: "data:body", enrich: true, suggest: true, limit: 1, offset: 1 }).map(res => res.doc)).to.eql([data[1]]); expect(document.search({ query: "title", suggest: true, limit: 1, offset: 3 })).to.eql([]); expect(document.search({ query: "title", suggest: true, offset: 3 })).to.eql([]); }); it("Merge Results", function(){ const document = new Document({ document: { store: [ "data:title" ], id: "id", field: [ "data:title", "data:body" ], tag: "data:cat" } }); for(let i = 0; i < data.length; i++){ document.add(data[i]); } expect(document.search({ query: "title", enrich: false, suggest: true, merge: true })).to.eql([ { id: 2, field: [ 'data:title' ] }, { id: 1, field: [ 'data:title' ] }, { id: 0, field: [ 'data:title' ] } ]); expect(document.search({ query: "title", enrich: true, suggest: true, merge: true }).map(res => res.doc)).to.eql([ { data: { title: data[0].data.title }}, { data: { title: data[1].data.title }}, { data: { title: data[2].data.title }} ]); expect(document.search({ query: "title", tag: { "data:cat": "A" }, enrich: true, suggest: true, merge: true }).map(res => res.doc)).to.eql([ { data: { title: data[0].data.title }}, { data: { title: data[2].data.title }} ]); }); it("Using BigInt", function(){ const document = new Document({ document: { store: "data:title", id: "id", field: [ "data:title", "data:body" ], tag: "data:cat" } }); for(let i = 0, tmp; i < data.length; i++){ tmp = Object.assign({}, data[i], { id: BigInt(i + 1) }); document.add(tmp); } expect(document.search({ query: "title", tag: { "data:cat": "A" }, merge: true })).to.eql([ { id: BigInt(1), field: [ "data:title" ] }, { id: BigInt(3), field: [ "data:title" ] } ]); }); it("Custom Document Store", function(){ const document = new Document({ document: { store: [ "data:title" ], id: "id", field: [ "data:title", "data:body" ] } }); for(let i = 0; i < data.length; i++){ document.add(data[i]); } expect(document.search({ query: "title", enrich: true, suggest: true, merge: true }).map(res => res.doc)).to.eql([ { data: { title: data[0].data.title }}, { data: { title: data[1].data.title }}, { data: { title: data[2].data.title }} ]); }); }); ================================================ FILE: test/document.tag.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; if(!build_light) describe("Documents: Tag-Search", function(){ const data = [{ id: 2, data: { title: "Title 3", body: "Body 3", cat: "A" } },{ id: 1, data: { title: "Title 2", body: "Body 2", cat: "B" } },{ id: 0, data: { title: "Title 1", body: "Body 1", cat: "A" } }]; const update = [{ id: 0, data: { title: "Foo 1", body: "Bar 1", cat: "B" } },{ id: 1, data: { title: "Foo 2", body: "Bar 2", cat: "A" } },{ id: 2, data: { title: "Foo 3", body: "Bar 3", cat: "B" } }]; it("Should have been indexed properly (tag)", function(){ const document = new Document({ document: { id: "id", field: ["data:body", "data:title"], tag: "data:cat" } }); const document_with_store = new Document({ document: { store: true, id: "id", field: ["data:body", "data:title"], tag: "data:cat" } }); for(let i = 0; i < data.length; i++){ document.add(data[i]); document_with_store.add(data[i]); } expect(document.index.size).to.equal(2); expect(document.tag.size).to.equal(1); expect(document.reg.size).to.equal(3); expect(document_with_store.store.size).to.equal(3); expect(document.search({ query: "title" })).to.eql([{ field: "data:title", result: [2, 1, 0] }]); expect(document.search({ query: "title", tag: { "data:cat": "A" } })).to.eql([{ field: "data:title", result: [2, 0] }]); expect(document.search({ query: "body", tag: { "data:cat": "B" } })).to.eql([{ field: "data:body", result: [1] }]); expect(document.search({ query: "title", tag: [ { "data:cat": "A" }, { "data:cat": "B" } ] })).to.eql([{ field: "data:title", result: [2, 1, 0] }]); expect(document.search({ query: "body title", suggest: true, tag: [ { "data:cat": "A" }, { "data:cat": "B" } ] })).to.eql([{ field: "data:body", result: [2, 1, 0] },{ field: "data:title", result: [2, 1, 0] }]); // todo suggestions should return all results like one below expect(document.search({ query: "body title", suggest: true, tag: [ { "data:cat": "C" }, // not exists { "data:cat": "B" } ] })).to.eql([{ field: "data:body", result: [1] },{ field: "data:title", result: [1] }]); // suggestions on expect(document.search({ query: "body title", suggest: true, tag: [ { "data:cat": "C" } // not exists ] })).to.eql([{ field: "data:body", result: [2, 1, 0] },{ field: "data:title", result: [2, 1, 0] }]); // suggestions off expect(document.search({ query: "body title", tag: [ { "data:cat": "C" } // not exists ] })).to.eql([]); // --------------------------------------- for(let i = 0; i < update.length; i++){ document.add(update[i]); document_with_store.add(update[i]); } expect(document.search("foo")).to.eql([{ field: "data:title", result: [0, 1, 2] }]); expect(document.search({ query: "foo", tag: { "data:cat": "A" } })).to.eql([{ field: "data:title", result: [1] }]); expect(document.search({ query: "bar", tag: { "data:cat": "B" } })).to.eql([{ field: "data:body", result: [0, 2] }]); // --------------------------------------- for(let i = 0; i < update.length; i++){ document.remove(update[i]); document_with_store.remove(update[i]); } expect(document.reg.size).to.equal(0); expect(document.index.get("data:title").reg.size).to.equal(0); expect(document.index.get("data:body").reg.size).to.equal(0); expect(document.index.get("data:title").map.size).to.equal(0); expect(document.index.get("data:body").map.size).to.equal(0); expect(document_with_store.store.size).to.equal(0); expect(document_with_store.search({ query: "foo", })).to.eql([]); expect(document_with_store.search({ query: "bar" })).to.eql([]); }); it("Should have been cleared everything properly", function(){ const document = new Document({ cache: true, context: { depth: 1 }, document: { store: true, id: "id", field: [ { field: "data:body", context: { depth: 1 } }, { field: "data:title", context: { depth: 1 } } ], tag: "data:cat" } }); for(let i = 0; i < data.length; i++){ document.add(data[i]); document.searchCache(data[i].data.title) } expect(document.reg.size).to.equal(3); expect(document.tag.get("data:cat").size).to.equal(2); expect(document.tag.get("data:cat").get("A").length).to.equal(2); expect(document.tag.get("data:cat").get("B").length).to.equal(1); expect(document.store.size).to.equal(3); expect(document.cache.cache.size).to.equal(3); expect(document.index.get("data:title").reg.size).to.equal(3); expect(document.index.get("data:title").map.size).to.equal(4); // 4 terms expect(document.index.get("data:title").ctx.size).to.equal(1); // 1 keyword expect(document.index.get("data:title").ctx.get("title").size).to.equal(3); // 3 context terms expect(document.index.get("data:body").reg.size).to.equal(3); expect(document.index.get("data:body").map.size).to.equal(4); // 4 terms expect(document.index.get("data:body").ctx.size).to.equal(1); // 1 keyword expect(document.index.get("data:body").ctx.get("body").size).to.equal(3); // 3 context terms document.clear(); expect(document.reg.size).to.equal(0); expect(document.tag.get("data:cat").size).to.equal(0); expect(document.store.size).to.equal(0); expect(document.cache.cache.size).to.equal(0); expect(document.index.get("data:title").reg.size).to.equal(0); expect(document.index.get("data:title").map.size).to.equal(0); expect(document.index.get("data:title").ctx.size).to.equal(0); expect(document.index.get("data:body").reg.size).to.equal(0); expect(document.index.get("data:body").map.size).to.equal(0); expect(document.index.get("data:body").ctx.size).to.equal(0); }); }); ================================================ FILE: test/encoder.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; describe("Encoder", function(){ it("Should have been properly normalized", function(){ const index = new Index(); expect(index.encoder.normalize).to.equal(true); index.add(1, "La Bamba"); index.add(2, "La Bohème"); expect(index.search("la boheme")).to.eql([2]); expect(index.search("la boheme", { suggest: true })).to.eql([2, 1]); }); it("Should have been properly added a custom encoder", function(){ const encode = str => str.toLowerCase().split(/\s+/); const index = new Index({ encoder: encode }); expect(index.encoder.encode).to.eql(encode); }); it("Should have been properly added a custom encode (alternative)", function(){ const encode = str => str.toLowerCase().split(/\s+/); const index = new Index({ encode }); expect(index.encoder.encode).to.eql(encode); }); }); describe("Encoder: Charset", function(){ it("Should have been encoded properly: Default", function(){ let index = new Index({ encoder: Charset.Default }); expect(index.encoder.encode("Björn-Phillipp Mayer")).to.eql( ["bjorn", "philip", "mayer"] ); index = new Index(); expect(index.encoder.encode("Björn-Phillipp Mayer")).to.eql( ["bjorn", "philip", "mayer"] ); }); if(!build_light){ it("Should have been encoded properly: Exact", function(){ const index = new Index({ encoder: Charset.Exact }); expect(index.encoder.encode("Björn-Phillipp Mayer")).to.eql( ["Björn", "Phillipp", "Mayer"] ); }); it("Should have been encoded properly: Normalize", function(){ const index = new Index({ encoder: Charset.Normalize }); expect(index.encoder.encode("Björn-Phillipp Mayer")).to.eql( index.encoder.encode("bjorn/philip mayer") ); }); it("Should have been encoded properly: LatinBalance", function(){ const index = new Index({ encoder: Charset.LatinBalance }); expect(index.encoder.encode("Björn-Phillipp Mayer")).to.eql( index.encoder.encode("bjorn philip mair") ); }); it("Should have been encoded properly: LatinAdvanced", function(){ const index = new Index({ encoder: Charset.LatinAdvanced }); expect(index.encoder.encode("Björn-Phillipp Mayer")).to.eql( index.encoder.encode("bjoern filip mair") ); }); it("Should have been encoded properly: LatinExtra", function(){ const index = new Index({ encoder: Charset.LatinExtra }); expect(index.encoder.encode("Björn-Phillipp Mayer")).to.eql( index.encoder.encode("bjorm filib mayr") ); }); it("Should have been encoded properly: LatinSoundex", function(){ const index = new Index({ encoder: Charset.LatinSoundex }); expect(index.encoder.encode("Björn-Phillipp Mayer")).to.eql( index.encoder.encode("bjoernsen philippo mayr") ); }); } it("Should have been encoded properly: Custom Encoder", function(){ function test_encoder(str){ return "-[" + str.toUpperCase() + "]-"; } const index = new Index({ encoder: test_encoder }); expect(index.encoder.encode("Björn-Phillipp Mayer")).to.eql("-[BJÖRN-PHILLIPP MAYER]-"); }); }); describe("Encoder: CJK Charset", function(){ it("Should have been tokenized properly", function(){ const index = Index({ encoder: Charset.CJK }); index.add(0, "서울시가 잠이 든 시간에 아무 말, 미뤄, 미뤄"); expect(index.search("든")).to.include(0); expect(index.search("시간에")).to.include(0); index.add(1, "一个单词"); expect(index.search("一个")).to.include(1); expect(index.search("单词")).to.include(1); expect(index.search("词单")).to.include(1); }); it("Should have been tokenized properly", function(){ const index = Index({ encoder: Charset.CJK }); index.add(1 , "多大的;多少平"); const result = index.search('是多少平的', { suggest: true }); expect(result).to.include(1); }); }); describe("Encoder: Cyrillic Charset", function(){ it("Should have been tokenized properly", function(){ const index = Index({ tokenize: "forward" }); index.add(0, "Фообар"); expect(index.search("Фообар")).to.include(0); expect(index.search("Фоо")).to.include(0); }); }); describe("Encoder: Arabic Charset", function(){ it("Should have been tokenized properly", function(){ let index = Index({ tokenize: "forward" }); index.add(0, "لكن لا بد أن أوضح لك أن كل"); expect(index.search("بد أن")).to.include(0); expect(index.search("أو")).to.include(0); index = Index({ tokenize: "reverse" }); index.add(0, "لكن لا بد أن أوضح لك أن كل"); expect(index.search("ضح")).to.include(0); }); }); describe("Encoder: Greek Charset", function(){ it("Should have been tokenized properly", function(){ const index = Index({ tokenize: "forward" }); index.add(0, "Μήγαρις ἔχω ἄλλο στὸ νοῦ μου πάρεξ ἐλευθερία καὶ γλώσσα"); expect(index.search("Μηγαρις εχω αλλο στο νου μου παρε ελευθ και γλωσσα")).to.include(0); }); }); describe("Encoder: Right-to-Left", function(){ it("Should have been scored properly", function(){ let index = new Index({ tokenize: "forward", rtl: true }); index.add(0, "54321 4 3 2 0"); index.add(1, "0 2 3 4 54321"); index.add(2, "0 2 3 4 12345"); expect(index.search("5")).to.eql([2]); expect(index.search("1")).to.eql([1, 0]); index = new Index({ tokenize: "reverse", rtl: true }); index.add(0, "54321 4 3 2 1 0"); index.add(1, "0 1 2 3 4 54321"); index.add(2, "0 1 2 3 4 12345"); expect(index.search("5")).to.eql([2, 1, 0]); }); }); describe("Filter", function(){ it("Should have been filtered properly", function(){ let encoder = new Encoder({ filter: ["in", "the"] }); let index = new Index({ tokenize: "strict", encoder: encoder }); index.add(0, "Today in the morning."); expect(index.search("today in the morning.")).to.include(0); expect(index.search("today morning")).to.include(0); expect(index.search("in the")).to.have.length(0); index = new Index({ tokenize: "strict", encoder: encoder, context: true }); index.add(0, "Today in the morning."); expect(index.search("today morning")).to.include(0); encoder = new Encoder(); encoder.addFilter("in"); index = new Index({ tokenize: "strict", encoder: encoder }); index.encoder.addFilter("the"); index.add(0, "Today in the morning."); expect(index.search("in the")).to.have.length(0); // extend index.encoder.assign({ filter: ["morning"] }); index.add(0, "Today in the morning."); expect(index.search("in the")).to.have.length(0); expect(index.search("morning")).to.have.length(0); expect(index.search("Today")).to.eql([0]); }); it("Should have been filtered properly (custom function)", function(){ const encoder = new Encoder({ filter: function(word){ return word.length > 3; } }); const index = new Index({ tokenize: "strict", encoder: encoder }); index.add(0, "Today in the morning."); expect(index.search("today in the morning.")).to.include(0); expect(index.search("today morning")).to.include(0); expect(index.search("in the")).to.have.length(0); encoder.assign({ filter: function(word){ return word.length > 3 && word !== "today"; } }); index.add(0, "Today in the morning."); expect(index.search("today in the morning.")).to.include(0); expect(index.search("today morning")).to.include(0); expect(index.search("in the")).to.have.length(0); expect(index.search("today")).to.have.length(0); }); it("Should have been filtered properly (finalize)", function(){ const encoder = new Encoder({ finalize: function(word){ return word.filter(t => t.length > 3); } }); const index = new Index({ tokenize: "strict", encoder: encoder }); index.add(0, "Today in the morning."); expect(index.search("today in the morning.")).to.include(0); expect(index.search("today morning")).to.include(0); expect(index.search("in the")).to.have.length(0); // extend encoder.assign({ finalize: function(word){ return word.filter(t => t.length > 5); } }); expect(index.search("today in the morning.")).to.include(0); expect(index.search("today morning")).to.include(0); expect(index.search("in the")).to.have.length(0); expect(index.search("today")).to.have.length(0); }); it("Should have been filtered properly (minlength)", function(){ const encoder = new Encoder({ minlength: 4 }); const index = new Index({ tokenize: "strict", encoder: encoder }); index.add(0, "Today in the morning."); expect(index.search("today in the morning.")).to.include(0); expect(index.search("today morning")).to.include(0); expect(index.search("in the")).to.have.length(0); }); }); describe("Stemmer", function(){ it("Should have been stemmed properly", function(){ const encoder = new Encoder({ stemmer: new Map([ ["ization", "ize"], ["tional", "tion"] ]) }); const index = new Index({ tokenize: "strict", encoder: encoder }); index.add(0, "Just a multinational colonization."); expect(index.search("Just a multinational colonization.")).to.include(0); expect(index.search("multinational colonization")).to.include(0); expect(index.search("multination colonize")).to.include(0); // extend encoder.assign({ stemmer: new Map([ ["licate", "e"] ]) }); index.add(0, "Just a duplicate multinational colonization."); expect(index.search("Just a multinational colonization.")).to.include(0); expect(index.search("multinational colonization")).to.include(0); expect(index.search("multination colonize")).to.include(0); expect(index.search("dupe")).to.include(0); }); // it("Should have been stemmed properly (custom function)", function(){ // // var stems = { // "ization": "ize", // "tional": "tion" // }; // // var index = new FlexSearch({ // tokenize: "strict", // stemmer: function(word){ // return stems[word] || word; // } // }); // // index.add(0, "Just a multinational colonization."); // // expect(index.length).to.equal(1); // expect(index.search("Just a multinational colonization.")).to.include(0); // expect(index.search("multinational colonization")).to.include(0); // expect(index.search("tional tion")).to.have.length(0); // }); // }); // // // describe("Custom Language", function(){ // // it("Should have been applied properly", function(){ // // var index = new FlexSearch({ // tokenize: "reverse", // filter: ["a", "an"], // stemmer: { // "ization": "ize", // "tional": "tion" // } // }); // // index.add(0, "Just a multinational colonization."); // // expect(index.length).to.equal(1); // expect(index.search("Just a multinational colonization.")).to.include(0); // expect(index.search("Just an multinational colonization.")).to.include(0); // expect(index.search("multinational colonization")).to.include(0); // expect(index.search("tional tion")).to.have.length(0); // // FlexSearch.registerLanguage("custom", { // filter: ["a", "an"], // stemmer: { // "ization": "ize", // "tional": "tion" // } // }); // // index = new FlexSearch({ // tokenize: "reverse", // lang: "custom" // }); // // index.add(0, "Just a multinational colonization."); // // expect(index.length).to.equal(1); // expect(index.search("Just a multinational colonization.")).to.include(0); // expect(index.search("Just an multinational colonization.")).to.include(0); // expect(index.search("multinational colonization")).to.include(0); // expect(index.search("tional tion")).to.have.length(0); // }); }); describe("Mapper", function(){ it("Should have been applied custom Mapper properly", function(){ const index = new Index({ tokenize: "forward", encoder: new Encoder({ numeric: false, dedupe: false, mapper: new Map([ ["1", "a"], ["2", "b"], ["3", "c"], ["4", "d"], ["5", "d"], ["6", "d"], ["7", "e"], ["8", "f"] ]) }) }); index.add(0, "12345678"); expect(index.search("12345678")).to.eql([0]); expect(index.search("abcd")).to.eql([0]); expect(index.encoder.encode("12345678")).to.eql(["abcdddef"]); // extend index.encoder.assign({ mapper: new Map([ ["1", "x"], ["2", "y"], ["3", "z"], ["7", "x"], ["8", "y"] ]) }); index.add(0, "12345678"); expect(index.search("12345678")).to.eql([0]); expect(index.search("xyzd")).to.eql([0]); expect(index.encoder.encode("12345678")).to.eql(["xyzdddxy"]); }); }); describe("Matcher", function(){ it("Should have been applied custom Matcher properly", function(){ const index = new Index({ tokenize: "forward", encoder: new Encoder({ numeric: false, dedupe: false, matcher: new Map([ ["1", "a"], ["2", "b"], ["3", "c"], ["456", "d"], ["7", "e"], ["8", "f"] ]) }) }); index.add(0, "12345678"); expect(index.search("12345678")).to.eql([0]); expect(index.search("abcd")).to.eql([0]); expect(index.encoder.encode("12345678")).to.eql(["abcdef"]); // extend index.encoder.assign({ matcher: new Map([ ["1", "x"], ["456", "ddd"], ["8", "y"] ]) }); index.add(0, "12345678"); expect(index.search("12345678")).to.eql([0]); expect(index.search("xbcd")).to.eql([0]); expect(index.encoder.encode("12345678")).to.eql(["xbcdddey"]); }); }); describe("Replacer", function(){ it("Should have been applied custom Replacer properly", function(){ const index = new Index({ tokenize: "forward", encoder: new Encoder({ numeric: false, dedupe: false, replacer: [ "1", "a", "2", "b", "3", "c", /[456]/g, "d", "7", "e", "8", "f" ] }) }); index.add(0, "12345678"); expect(index.search("12345678")).to.eql([0]); expect(index.search("abcd")).to.eql([0]); expect(index.encoder.encode("12345678")).to.eql(["abcdddef"]); // extend index.encoder.assign({ replacer: [ "a", "1", "b", "2", "c", "3", "e", "7", "f", "8" ] }); index.add(0, "12345678"); expect(index.search("12345678")).to.eql([0]); expect(index.search("123d")).to.eql([0]); expect(index.search("abcd")).to.eql([0]); expect(index.encoder.encode("12345678")).to.eql(["123ddd78"]); }); }); ================================================ FILE: test/highlight.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; if(!build_light) describe("Result Highlighting", function(){ it("Should have been highlighted results properly (Document)", function(){ // some test data const data = [{ "id": 1, "title": "Carmencita" },{ "id": 2, "title": "Le clown et ses chiens" }]; // create the document index const index = new Document({ cache: true, document: { store: true, index: [{ field: "title", tokenize: "forward", encoder: Charset.LatinBalance }] } }); // add test data for(let i = 0; i < data.length; i++){ index.add(data[i]); } // perform a query let result = index.searchCache({ query: "karmen or clown or not found", suggest: true, enrich: true, // highlight template // $1 is a placeholder for the matched partial highlight: "$1" }); expect(result[0].result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' },{ id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); // perform a query on cache result = index.searchCache({ query: "karmen or clown or not found", suggest: true, enrich: true, // highlight template // $1 is a placeholder for the matched partial highlight: "$1" }); expect(result[0].result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' },{ id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); // perform a query using pluck result = index.search({ query: "karmen or clown or not found", suggest: true, enrich: true, pluck: "title", // highlight template // $1 is a placeholder for the matched partial highlight: "$1" }); expect(result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' },{ id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); }); if(!build_compact) it("Should have been highlighted results properly (Document Worker)", async function(){ // some test data const data = [{ "id": 1, "title": "Carmencita" },{ "id": 2, "title": "Le clown et ses chiens" }]; // create the document index const index = await new Document({ cache: true, worker: true, document: { store: true, index: [{ field: "title", tokenize: "forward", encoder: Charset.LatinBalance }] } }); // add test data for(let i = 0; i < data.length; i++){ await index.add(data[i]); } // perform a query let result = await index.searchCache({ query: "karmen or clown or not found", suggest: true, enrich: true, // highlight template // $1 is a placeholder for the matched partial highlight: "$1" }); expect(result[0].result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' },{ id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); // perform a query on cache result = await index.searchCache({ query: "karmen or clown or not found", suggest: true, enrich: true, // highlight template // $1 is a placeholder for the matched partial highlight: "$1" }); expect(result[0].result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' },{ id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); // perform a query using pluck result = await index.search({ query: "karmen or clown or not found", suggest: true, enrich: true, pluck: "title", // highlight template // $1 is a placeholder for the matched partial highlight: "$1" }); expect(result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' },{ id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); }); it("Should have been highlighted results by boundary properly", function(){ const data = [{ "id": 1, "title": "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." },{ "id": 2, "title": "Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. At vero eos et accusam et justo duo dolores et ea rebum. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." }]; const index = new Document({ document: { store: true, index: [{ field: "title", tokenize: "full", encoder: Charset.LatinBalance }] } }); // add test data for(let i = 0; i < data.length; i++){ index.add(data[i]); } const tmp = index.search({ query: "sit amet", pluck: "title", highlight: { template: "$1", boundary: 80, clip: true } }); // ------------------------------ let result = index.search({ query: "undefined akusam undefined", suggest: true, enrich: true, highlight: { // highlight template // $1 is a placeholder for the matched partial template: "$1", boundary: 49 } }); expect(result[0].result).to.eql([{ id: 2, doc: data[1], highlight: "...t. At vero eos et accusam et justo duo dolo..." },{ id: 1, doc: data[0], highlight: "...a. At vero eos et accusam et justo duo dolo..." }]); expect(result[0].result[0].highlight.length).to.equal(49 + (3 + 4)); expect(result[0].result[1].highlight.length).to.equal(49 + (3 + 4)); // ------------------------------ result = index.search({ query: "undefined akusam undefined", suggest: true, enrich: true, highlight: { // highlight template // $1 is a placeholder for the matched partial template: "$1", ellipsis: "", boundary: 49 } }); expect(result[0].result).to.eql([{ id: 2, doc: data[1], highlight: "amet. At vero eos et accusam et justo duo dolores" },{ id: 1, doc: data[0], highlight: "ptua. At vero eos et accusam et justo duo dolores" }]); expect(result[0].result[0].highlight.length).to.equal(49 + (3 + 4)); expect(result[0].result[1].highlight.length).to.equal(49 + (3 + 4)); // ------------------------------ result = index.search({ query: "undefined akusam undefined", suggest: true, enrich: true, highlight: { // highlight template // $1 is a placeholder for the matched partial template: "$1", clip: false, boundary: 49 } }); expect(result[0].result).to.eql([{ id: 2, doc: data[1], highlight: "... At vero eos et accusam et justo duo ..." },{ id: 1, doc: data[0], highlight: "... At vero eos et accusam et justo duo ..." }]); expect(result[0].result[0].highlight.length).to.below(49 + (3 + 4)); expect(result[0].result[1].highlight.length).to.below(49 + (3 + 4)); // ------------------------------ result = index.search({ query: "akusam", enrich: true, highlight: { template: "$1", boundary: 5 } }); expect(result[0].result[0].highlight).to.equal("...accusam..."); expect(result[0].result[1].highlight).to.equal("...accusam..."); // ------------------------------ result = index.search({ query: "akusam", enrich: true, highlight: { template: "$1", clip: false, ellipsis: "", boundary: 5 } }); expect(result[0].result[0].highlight).to.equal("accusam"); expect(result[0].result[1].highlight).to.equal("accusam"); // ------------------------------ result = index.search({ query: "akusam", enrich: true, highlight: { template: "$1", clip: false, ellipsis: "", boundary: { before: 50, after: 50, total: 5 } } }); expect(result[0].result[0].highlight).to.equal("accusam"); expect(result[0].result[1].highlight).to.equal("accusam"); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: false, ellipsis: "", boundary: 5 } }); expect(result[0].result[0].highlight).to.equal("accusam"); expect(result[0].result[1].highlight).to.equal("accusam"); // ------------------------------ result = index.search({ query: "akus", enrich: true, highlight: { template: "$1", clip: false, ellipsis: "", boundary: 5 } }); expect(result[0].result[0].highlight).to.equal("accusam"); expect(result[0].result[1].highlight).to.equal("accusam"); // ------------------------------ result = index.search({ query: "cusa", enrich: true, highlight: { template: "$1", clip: false, ellipsis: "", boundary: 5 } }); expect(result[0].result[0].highlight).to.equal("accusam"); expect(result[0].result[1].highlight).to.equal("accusam"); // ------------------------------ result = index.search({ query: "cusa", enrich: true, highlight: { template: "$1", ellipsis: "...", clip: false, boundary: 5 } }); expect(result[0].result[0].highlight).to.equal("...accusam..."); expect(result[0].result[1].highlight).to.equal("...accusam..."); // ------------------------------ result = index.search({ query: "cusa", enrich: true, highlight: { template: "$1", ellipsis: "...", clip: true, boundary: 5 } }); expect(result[0].result[0].highlight).to.equal("...accusam..."); expect(result[0].result[1].highlight).to.equal("...accusam..."); // ------------------------------ result = index.search({ query: "akusam", enrich: true, highlight: { template: "$1", clip: true, ellipsis: false, boundary: { before: 5, after: 5 } } }); expect(result[0].result[0].highlight).to.equal("s et accusam et j"); expect(result[0].result[1].highlight).to.equal("s et accusam et j"); // ------------------------------ result = index.search({ query: "akusam", enrich: true, highlight: { template: "$1", clip: true, ellipsis: "", boundary: { before: 0, after: 0 } } }); expect(result[0].result[0].highlight).to.equal("accusam"); expect(result[0].result[1].highlight).to.equal("accusam"); // ------------------------------ result = index.search({ query: "akusam", enrich: true, highlight: { template: "$1", clip: true, ellipsis: "", boundary: { before: 5, total: 50 } } }); expect(result[0].result[0].highlight).to.equal("s et accusam et justo duo dolores et ea rebum. Lor"); expect(result[0].result[1].highlight).to.equal("s et accusam et justo duo dolores et ea rebum. Ste"); // ------------------------------ result = index.search({ query: "akusam", enrich: true, highlight: { template: "$1", clip: true, ellipsis: "", boundary: { after: 5, total: 50 } } }); expect(result[0].result[0].highlight).to.equal("et. At vero eos et accusam et j"); expect(result[0].result[1].highlight).to.equal("ua. At vero eos et accusam et j"); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", boundary: 51 } }); expect(result[0].result[0].highlight).to.equal("... eos et accusam et justo duo dolores et ea re..."); expect(result[0].result[1].highlight).to.equal("... eos et accusam et justo duo dolores et ea re..."); expect(result[0].result[0].highlight.length).to.equal(51 + 3 * (3 + 4)); expect(result[0].result[1].highlight.length).to.equal(51 + 3 * (3 + 4)); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: false, ellipsis: "...", boundary: 51 } }); expect(result[0].result[0].highlight).to.equal("... eos et accusam et justo duo dolores et ea ..."); expect(result[0].result[1].highlight).to.equal("... eos et accusam et justo duo dolores et ea ..."); expect(result[0].result[0].highlight.length).to.below(51 + 3 * (3 + 4)); expect(result[0].result[1].highlight.length).to.below(51 + 3 * (3 + 4)); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: false, ellipsis: "", boundary: 52 } }); expect(result[0].result[0].highlight).to.equal("vero eos et accusam et justo duo dolores et ea "); expect(result[0].result[1].highlight).to.equal("vero eos et accusam et justo duo dolores et ea "); expect(result[0].result[0].highlight.length).to.below(51 + 3 * (3 + 4)); expect(result[0].result[1].highlight.length).to.below(51 + 3 * (3 + 4)); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: false, ellipsis: "", boundary: 7 + 1 + 5 + 1 + 7 } }); expect(result[0].result[0].highlight).to.equal("accusam justo dolores"); expect(result[0].result[1].highlight).to.equal("accusam justo dolores"); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: false, ellipsis: "", boundary: 20 } }); expect(result[0].result[0].highlight).to.equal("accusam justo"); expect(result[0].result[1].highlight).to.equal("accusam justo"); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: false, merge: true, ellipsis: "", boundary: 21 } }); expect(result[0].result[0].highlight).to.equal("accusam justo dolores"); expect(result[0].result[1].highlight).to.equal("accusam justo dolores"); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: false, ellipsis: "...", boundary: 18 } }); expect(result[0].result[0].highlight).to.equal("...accusam..."); expect(result[0].result[1].highlight).to.equal("...accusam..."); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: false, ellipsis: "...", boundary: 7 + 5 + (3 * 3) + 1 } }); expect(result[0].result[0].highlight).to.equal("...accusam...justo..."); expect(result[0].result[1].highlight).to.equal("...accusam...justo..."); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: false, ellipsis: "...", boundary: 7 + 5 + (3 * 3) + 1 - 1 } }); expect(result[0].result[0].highlight).to.equal("...accusam..."); expect(result[0].result[1].highlight).to.equal("...accusam..."); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: true, ellipsis: "...", boundary: 7 + 5 + (3 * 3) + 1 - 1 } }); expect(result[0].result[0].highlight).to.equal("...accusam..."); expect(result[0].result[1].highlight).to.equal("...accusam..."); // ------------------------------ result = index.search({ query: "undefined aku undefined usam undefined akusam undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: false, ellipsis: "...", boundary: 7 + 5 + (3 * 3) + 1 } }); expect(result[0].result[0].highlight).to.equal("... et accusam et ..."); expect(result[0].result[1].highlight).to.equal("... et accusam et ..."); expect(result[0].result[0].highlight.length).to.below(7 + 5 + (3 * 3) + 1 + 7); expect(result[0].result[1].highlight.length).to.below(7 + 5 + (3 * 3) + 1 + 7); // ------------------------------ result = index.search({ query: "undefined akusam undefined justo undefined dolores undefined", suggest: true, enrich: true, highlight: { template: "$1", clip: false, ellipsis: "...", boundary: 50 } }); expect(result[0].result[0].highlight).to.equal("... eos et accusam et justo duo dolores et ea ..."); expect(result[0].result[1].highlight).to.equal("... eos et accusam et justo duo dolores et ea ..."); // ------------------------------ result = index.search({ query: "undefined akusam undefined labore undefined sanktus undefined", suggest: true, enrich: true, highlight: { template: "$1", boundary: 100 } }); expect(result[0].result).to.eql([{ id: 2, doc: data[1], highlight: "...sea takimata sanctus est Lorem ipsu...eos et accusam et justo...invidunt ut labore et dolore..." },{ id: 1, doc: data[0], highlight: "...invidunt ut labore et dolore magn...eos et accusam et justo...sea takimata sanctus est Lorem..." }]); // ------------------------------ result = index.search({ query: "undefined akusam undefined labore undefined sanktus undefined", suggest: true, enrich: true, highlight: { template: "$1", boundary: { before: 15, after: 15, total: 100 } } }); expect(result[0].result).to.eql([{ id: 2, doc: data[1], highlight: "...sea takimata sanctus est Lorem ipsu...eos et accusam et justo...invidunt ut labore et dolore..." },{ id: 1, doc: data[0], highlight: "...invidunt ut labore et dolore magn...eos et accusam et justo...sea takimata sanctus est Lorem..." }]); // ------------------------------ result = index.search({ query: "sit amet", pluck: "title", highlight: { template: "$1", boundary: 80, clip: true } }); expect(result[0].highlight).to.eql("...dolor sit amet, consetetur sadipscing...r, sed diam...sed diam...sit amet."); expect(result[1].highlight).to.eql("...or sit amet. At...sit amet, consetetur sadipscing...sed diam...sed diam..."); expect(result[0].highlight.length).to.below(80 + (7 * 7)); expect(result[1].highlight.length).to.below(80 + (7 * 7)); // ------------------------------ result = index.search({ query: "sit amet", pluck: "title", highlight: { template: "$1", boundary: 32, clip: true } }); expect(result[0].highlight).to.eql("...sit amet, consetetur..."); expect(result[1].highlight).to.eql("...sit amet....sit amet,..."); expect(result[0].highlight.length).to.below(80 + (7 * 7)); expect(result[1].highlight.length).to.below(80 + (7 * 7)); // ------------------------------ result = index.search({ query: "sit amet", pluck: "title", highlight: { template: "$1", boundary: 32, ellipsis: { template: "$1", pattern: "..." }, clip: true } }); expect(result[0].highlight).to.eql("...sit amet, consetetur..."); expect(result[1].highlight).to.eql("...sit amet....sit amet,..."); expect(result[0].highlight.length).to.below(80 + (7 * 7)); expect(result[1].highlight.length).to.below(80 + (7 * 7)); }); it("Should have been highlighted merged results properly", function(){ // some test data const data = [{ "id": 1, "title": "Carmencita", "description": "Description: Carmencita" },{ "id": 2, "title": "Le clown et ses chiens", "description": "Description: Le clown et ses chiens" }]; // create the document index const index = new Document({ encoder: Charset.LatinBalance, document: { store: true, index: [{ field: "title", tokenize: "forward" },{ field: "description", tokenize: "forward" }] } }); // add test data for(let i = 0; i < data.length; i++){ index.add(data[i]); } let result = index.search({ query: "karmen or clown or not found", suggest: true, enrich: true, merge: true, highlight: "$1" }); expect(result).to.eql([{ id: 1, doc: data[0], field: ["title", "description"], highlight: { "title": 'Carmencita', "description": 'Description: Carmencita', } },{ id: 2, doc: data[1], field: ["title", "description"], highlight: { "title": 'Le clown et ses chiens', "description": 'Description: Le clown et ses chiens', } }]); }); it("Should have been highlighted results properly (#480)", function(){ const index = new Document({ document: { store: true, index: [{ field: "title", tokenize: "forward", encoder: Charset.LatinDefault },{ field: "text", tokenize: "forward", encoder: Charset.LatinDefault }] } }); let all_docs = [{ id: "469", title: "Polygon", text: "A polygon is a two dimensional figure" },{ id: "888", title: "Spain", text: "Spain is a country." },{ id: "473", text: "Madrid (pronounced: “mah-drid or /məˈdrɪd/) is the capital and largest city of Spain. Madrid is in the middle of Spain, in the Community of Madrid. The Community is a large area that includes the city as well as small towns and villages outside the city. 7 million people live in the Community. More than 3 million live in the city itself. It is the largest city of Spain and, at 655 m (2,100 ft) above sea level, the second highest capital in Europe (after the Andorran capital Andorra la Vella). It is the second largest city in the European Union. As it is the capital city, Madrid is where the monarch lives and also where the government meets. Madrid is the financial centre of Spain. Many large businesses have their main offices there. It has four important footballs teams, Real Madrid, Atlético Madrid, Getafe, and Rayo Vallecano. People who live in Madrid are called madrileños. Madrid was ruled by the Romans from the 2nd century. After AD 711 it was occupied by the Moors. In 1083 Spain was ruled again by Spaniards. Catholic kings ruled the country. By the mid-16th century it had become the capital of a very large empire. Spain was ruled by monarchs from the House of Habsburg, then the House of Bourbon. After the Spanish Civil War it was ruled by a dictator until the mid-1970s when it became a democracy. Although it is a modern city, a lot of its history can be seen and felt as one walks along the streets and in the large squares of the city. There are beautiful parks, famous buildings, art galleries and concert halls. == History == During the history of Spain many different people have lived there. Madrid's name comes from the Arabic word magerit, meaning “place of many streams\\\". The Phoenicians came in 1100 BC, followed by Carthaginians, Romans, Vandals, Visigoths and Moors. It was not until 1492, when the Catholic Monarchs got power, that Spain became a united country. establishments in Europe Category:Establishments in Spain", title: "Madrid" }]; for(let i = 0; i < all_docs.length; i++){ index.add(all_docs[i]); } let result = index.search({ query: "spain", suggest: true, enrich: true, highlight: "$1" }); expect(result.length).to.equal(2); expect(result[0]).to.eql({ field: "title", result: [{ id: all_docs[1].id, doc: all_docs[1], highlight: 'Spain' }] }); expect(result[1]).to.eql({ field: "text", result: [{ id: all_docs[1].id, doc: all_docs[1], highlight: all_docs[1].text.replace(/(spain)/gi, "$1") },{ id: all_docs[2].id, doc: all_docs[2], highlight: all_docs[2].text.replace(/(spain)/gi, "$1") }] }); }); it("Should have been highlighted results properly (#489)", async function(){ const index = new Document({ encoder: Charset.Normalize, document: { store: true, index: [ { field: "title", tokenize: "forward" }, { field: "content", tokenize: "forward" } ], }, }); await index.addAsync({ id: 1, title: "Tips For Decorating Easter Eggs", content: `Published: April 14, 2025 From bold color choices to intricate patterns, there are many ways to make your springtime holiday decorations stand out from the rest. The Onion shares tips for dyeing Easter eggs.` }); const search = await index.search("h", { suggest: true, enrich: true, highlight: `$1` }); expect(search.length).to.equal(1); expect(search[0].result.length).to.equal(1); expect(search[0].result[0].highlight).to.equal('Published: April 14, 2025 From bold color choices to intricate patterns, there are many ways to make your springtime holiday decorations stand out from the rest. The Onion shares tips for dyeing Easter eggs.'); }); it("Should have been highlighted results properly (#508)", async function(){ const index = new Document({ encoder: Charset.Normalize, document: { store: true, index: [ { field: "title", tokenize: "forward" }, { field: "content", tokenize: "forward" } ], }, }); await index.addAsync({ id: 1, title: "Tips For Decorating Easter Eggs", content: `Published: April 14, 2025 From bold color choices to intricate patterns, there are many ways to make your springtime holiday decorations stand out from the rest. The Onion shares tips for dyeing Easter eggs.` }); const search = await index.search("h", { suggest: true, enrich: true, highlight: { template: "$1", merge: true, clip: false, boundary: { before: 200, after: 100, total: 400 } } }); expect(search.length).to.equal(1); expect(search[0].result.length).to.equal(1); expect(search[0].result[0].highlight).to.equal('Published: April 14, 2025 From bold color choices to intricate patterns, there are many ways to make your springtime holiday decorations stand out from the rest. The Onion shares tips for dyeing Easter eggs.'); }); }); ================================================ FILE: test/issues.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver, IndexedDB } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; const EnglishPreset = (await import("../src/lang/en.js")).default; describe("Github Issues", function(){ if(!build_light && !build_compact) it("#48", async function(){ const fs = await new Document({ encoder: Charset.LatinExtra, resolution: 9, context: { depth: 4 }, worker: true, cache: true, doc: { id: "id", field: [ "intent", "text" ] } }); const doc = [{ id: 0, intent: "intent", text: "text" },{ id: 1, intent: "intent", text: "howdy - how are you doing" }]; for(let i = 0; i < doc.length; i++){ await fs.add(doc[i]); } expect(await fs.search("howdy")).to.eql([{ field: "text", result: [1] }]); expect(await fs.search("howdy -")).to.eql([{ field: "text", result: [1] }]); // terminate workers fs.index.get("intent").worker.terminate(); fs.index.get("text").worker.terminate(); }); if(!build_light) it("#54", function(){ const index = new Document({ doc: { id: "id", field: ["title", "content"] } }); const docs = [{ id: 1, title: "Roaming Inquiry", content: "Some content" }, { id: 2, title: "New Service", content: "This is not roaming-inquiry" }]; for(let i = 0; i < docs.length; i++){ index.add(docs[i]); } expect(index.search("roaming")).to.eql([{ field: "title", result: [1] },{ field: "content", result: [2] }]); }); it("#486", function(){ const encoder = new Encoder(Charset.LatinDefault, EnglishPreset); const index = new Index({ tokenize: "full", encoder }); index.add(1, "user is not working, but users is working"); const userResult = index.search("user"); const usersResult = index.search("users"); expect(userResult).to.eql([1]); expect(usersResult).to.eql([1]); }); if(!build_light && !build_compact) it("#499", function(){ const DocumentIndexConfig = { document: { id: "id", store: true, // fuzzy search fields index: [ { field: "className", tokenize: "forward", }, ], // These get tagged as their specific key tag: [ { field: "language", }, { field: "feedbackScore", custom: (data) => Number(data.avgInstructorScore) >= 4 ? ">=4" : false, }, ], }, }; const index = new Document(DocumentIndexConfig); let all_docs = [{ "id": "1234", "className": "My Class Name", "language": "German", "avgInstructorScore": 4.69, }]; for (const doc of all_docs) { index.add(doc); } const resolveOptions = { enrich: true, limit: index.store.size, }; //console.log(index.search({ tag: { feedbackScore: ">=4", language: "German" }, resolve: true })) //console.log(index.search({ tag: { feedbackScore: ">=4", language: "German" }, resolve: true, enrich: true })) //console.log(index.search({ tag: { feedbackScore: ">=4", language: "German" }, resolve: false }).resolve({ enrich: true })) let results = index.search({ tag: { feedbackScore: ">=4", language: "German" }, resolve: false }) .resolve(resolveOptions); expect(results).to.eql([{ id: '1234', doc: { id: '1234', className: 'My Class Name', language: 'German', avgInstructorScore: 4.69 } }]); results = index.search({ query: "class", field: "className", resolve: false }) .and({ tag: { feedbackScore: ">=4", language: "German" } }) .resolve(resolveOptions); expect(results).to.eql([{ id: '1234', doc: { id: '1234', className: 'My Class Name', language: 'German', avgInstructorScore: 4.69 } }]); results = index.search({ tag: { feedbackScore: ">=4" }, resolve: false }) .and({ tag: { language: "German" } }) .resolve(resolveOptions); expect(results).to.eql([{ id: '1234', doc: { id: '1234', className: 'My Class Name', language: 'German', avgInstructorScore: 4.69 } }]); results = index.search({ query: "class", field: "className", resolve: false }) .and({ tag: { feedbackScore: ">=4", language: "German" }, resolve: true, enrich: true }); expect(results).to.eql([{ id: '1234', doc: { id: '1234', className: 'My Class Name', language: 'German', avgInstructorScore: 4.69 } }]); }); if(!build_light && !build_compact) it("#500", function(){ const indexableFields = ['field1', 'field2']; const searchIndex = new Document({ document: { store: true, id: '_id', index: indexableFields.map(f => ({field: f, tokenize: 'full', encoder: Charset.LatinExtra})), }, }); searchIndex.add({ _id: '123', field1: '1234', field2: '123 b', }); const submitSearch = query => { // Since there are subfields to account for, build up the query one field at a time let res = searchIndex.search({ query, field: "field1", resolve: false }); // Combine the queries with "or" and "resolve" them to get the results res = res.or({ query, field: "field2" }); res = res.resolve(); return res; }; // console.log(submitSearch('123')); // console.log(submitSearch('1234')); // console.log(submitSearch('123 b')); expect(submitSearch('123')).to.eql(["123"]); expect(submitSearch('1234')).to.eql(["123"]); expect(submitSearch('123 b')).to.eql(["123"]); const submitSearch2 = query => { // Since there are subfields to account for, build up the query one field at a time return searchIndex.search({ query, field: "field1", resolve: false }).or({ query, field: "field2", resolve: true }); }; expect(submitSearch2('123')).to.eql(["123"]); expect(submitSearch2('1234')).to.eql(["123"]); expect(submitSearch2('123 b')).to.eql(["123"]); const submitSearch3 = query => { // Since there are subfields to account for, build up the query one field at a time let res1 = searchIndex.search({ query, field: "field1", resolve: false }); let res2 = searchIndex.search({ query, field: "field2", resolve: false }); // Combine the queries with "or" and "resolve" them to get the results return res1.or(res2).resolve({ enrich: true }); }; expect(submitSearch3('123')).to.eql([{ "id": "123", "doc": { "_id": "123", "field1": "1234", "field2": "123 b" } }]); expect(submitSearch3('1234')).to.eql([{ "id": "123", "doc": { "_id": "123", "field1": "1234", "field2": "123 b" } }]); expect(submitSearch3('123 b')).to.eql([{ "id": "123", "doc": { "_id": "123", "field1": "1234", "field2": "123 b" } }]); }); if(!build_light) it("#503", function(){ const DOCS = { "./doc-1.txt": ` Floor Stream Banana `, "./doc-2.txt": ` Banana Listen Floor ` }; class FlexSearchService { constructor(){ const encoder = new Encoder(Charset.Normalize, { prepare: EnglishPreset.prepare, filter: EnglishPreset.filter, }); this.index = new Document({ // enable when frequently update existing contents fastupdate: false, document: { id: 'id', index: ['displayName', 'body', 'descriptionShort'], // add tags to the index tag: ['tags'] }, // favor forward tokenizer instead of full on large inputs will reduce memory tokenize: 'forward', encoder }); } updateIndexWithDocuments(documents) { documents.forEach((document) => { const { path } = document; const body = DOCS[path]; //fs.readFileSync(path, 'utf-8'); this.index.add({ ...document, body }); }); } searchDocuments({ query, limit = 100, offset = 0 }) { return this.index.search(query, { limit, offset, merge: true, }); } } const flexSearchService = new FlexSearchService(); const mockdocs = [ { id: 1, path: './doc-1.txt', displayName: 'Document 1', descriptionShort: 'Document 1 short', tags: ['tag-1'], }, { id: 2, path: './doc-2.txt', displayName: 'Document 2', descriptionShort: 'Document 2 short', tags: ['tag-2', 'tag-3'], }, ]; flexSearchService.updateIndexWithDocuments(mockdocs); expect( flexSearchService.searchDocuments({ query: 'Floor' }) ).to.eql([ { id: 1, field: [ 'body' ] }, { id: 2, field: [ 'body' ] } ]); flexSearchService.updateIndexWithDocuments([mockdocs[1]]); expect( flexSearchService.searchDocuments({ query: 'Floor' }) ).to.eql([ { id: 1, field: [ 'body' ] }, { id: 2, field: [ 'body' ] } ]); flexSearchService.updateIndexWithDocuments([mockdocs[0]]); expect( flexSearchService.searchDocuments({ query: 'Floor' }) ).to.eql([ { id: 1, field: [ 'body' ] }, { id: 2, field: [ 'body' ] } ]); expect( flexSearchService.searchDocuments({ query: 'Banana' }) ).to.eql([ { id: 2, field: [ 'body' ] }, { id: 1, field: [ 'body' ] } ]); flexSearchService.updateIndexWithDocuments([mockdocs[1]]); expect( flexSearchService.searchDocuments({ query: 'Floor' }) ).to.eql([ { id: 1, field: [ 'body' ] }, { id: 2, field: [ 'body' ] } ]); flexSearchService.updateIndexWithDocuments([mockdocs[0]]); expect( flexSearchService.searchDocuments({ query: 'Floor' }) ).to.eql([ { id: 1, field: [ 'body' ] }, { id: 2, field: [ 'body' ] } ]); flexSearchService.index.remove(mockdocs[0]); expect( flexSearchService.searchDocuments({ query: 'Floor' }) ).to.eql([ { id: 2, field: [ 'body' ] } ]); flexSearchService.index.remove(mockdocs[1]); expect( flexSearchService.searchDocuments({ query: 'Floor' }) ).to.eql([]); }); if(!build_light) it("#504", function(){ const searchIndex = new Document({ tokenize: "forward", document: { id: "id", index: ["name", "shortName"], store: true, }, }); searchIndex.add({ id: 1, name: "a name", shortName: "" // Or undefined }); const result = searchIndex.search({ query: "name" }); expect(result).to.eql([ { field: 'name', result: [ 1 ] } ]); }); if(!build_light && !build_compact) it("#506", async function(){ const data = [{ "id": "ab105.49", "text": "/anytime_", "tag": "block", "site": "docs" },{ "id": "cfc6c.50", "text": "Test the cluster and health-checker setup locally:", "tag": "block", "site": "docs" }]; const index = new Document({ document: { id: 'id', index: 'text', store: [ 'text', 'tag', 'site' ], tag: ['tag', 'site'] }, tokenize: 'reverse', encoder: Charset.LatinAdvanced }); data.forEach(item => index.add(item)); const result = new Resolver({ index: index, query: "test", field: 'text', tag: { site: 'docs' }, limit: 35, highlight: "$1" }).resolve(); expect(result[1].highlight).to.eql("Test the cluster and health-checker setup locally:"); }); // TODO https://jsfiddle.net/u9x6L0mw/2/ if(!build_light) it("#514", function(){ const data = [ { "id": 1, "title": "Carmencita" }, { "id": 2, "title": "en-US.json" } ]; const index = new Document({ document: { store: true, index: [{ field: "title", tokenize: "full", encoder: Charset.Default }] } }); data.forEach(item => index.add(item)); const result = index.search({ query: 'en', enrich: true, highlight: "$1" }); expect(result[0]).to.eql({ field: "title", result: [ { id: 1, doc: data[0], highlight: "Carmencita" }, { id: 2, doc: data[1], highlight: "en-US.json" } ] }); }); if(!build_light) it("#517", function(){ const data = [ { "document_id": 0, "title": "Call Sammy on 9944", "content": "", "contact_id": "" }, { "document_id": 1, "title": "Call Sammy on 9944343432", "content": "", "contact_id": "" }, { "document_id": 2, "title": "Call Jimmy on 9941343432", "content": "", "contact_id": "" }, { "document_id": 3, "title": "Call Jimmy on 9941", "content": "", "contact_id": "" } ]; const encoder = new Encoder(Charset.LatinDefault, EnglishPreset, { numeric: false }); const index = new Document({ document: { id: "document_id", store: true, index: [{ field: "title", tokenize: "full", encoder }] } }); data.forEach(item => index.add(item)); const result = index.search({ query: "432", pluck: "title", enrich: true }); expect(result).to.eql([{ "id": 1, "doc": data[1] },{ "id": 2, "doc": data[2] }]); }); if(!build_light) it("#521", async function(){ const data = [{ "id": 1, "title": "One", "score": null, },{ "id": 2, "title": "Two", "score": null, }]; const index = new Document({ document: { id: "id", store: true, index: [{ field: "title", tokenize: "forward" }], tag: [{ field: "scoredValue", custom: (data) => Number(data.score) > 1 ? ">1" : false }] } }); data.forEach(item => index.add(item)); await index.export(function(key, data){ if(key === "1.tag"){ expect(data).to.eql('[["scoredValue",[]]]'); } }); }); // TODO if(!build_light) it.skip("#523", function(){ const data = [ { "id": 1, "title": "REQ-1" }, { "id": 2, "title": "REQ2" }, { "id": 3, "title": "REQFOOBAR" } ]; const index = new Document({ document: { store: true, index: [{ field: "title", tokenize: "forward", encoder: { dedupe: true } }] } }); data.forEach(item => index.add(item)); const result = index.search({ query: "req", pluck: "title", highlight: "$1" }); expect(result[0].highlight).to.eql("REQ-1"); expect(result[1].highlight).to.eql("REQ2"); expect(result[2].highlight).to.eql("REQFOOBAR"); }); if(!build_light) it("#524", function(){ const testData = [ // First 10 notes on page2 that match "g" (but we want page1) { id: '1', content: 'green apple', parentId: 'page2' }, { id: '2', content: 'great day', parentId: 'page2' }, { id: '3', content: 'good morning', parentId: 'page2' }, { id: '4', content: 'big dog', parentId: 'page2' }, { id: '5', content: 'long night', parentId: 'page2' }, { id: '6', content: 'huge garden', parentId: 'page2' }, { id: '7', content: 'large group', parentId: 'page2' }, { id: '8', content: 'big game', parentId: 'page2' }, { id: '9', content: 'great goal', parentId: 'page2' }, { id: '10', content: 'good girl', parentId: 'page2' }, // The note we want is at position 11 (on page1) { id: '11', content: 'glasgow coma scale', parentId: 'page1' }, // More notes on page2 { id: '12', content: 'big green', parentId: 'page2' }, { id: '13', content: 'great god', parentId: 'page2' }, ]; const index = new Document({ tokenize: 'forward', encoder: { minlength: 1 }, document: { id: 'id', tag: 'parentId', index: ['content'], store: true } }); testData.forEach(item => { index.add(item); }); expect(testData.length).to.equal(13); expect(testData.filter(d => d.content.includes('g')).length).to.equal(13); expect(testData.filter(d => d.content.includes('g') && d.parentId === 'page1').length).to.equal(1); expect(testData.filter(d => d.content.includes('g') && d.parentId === 'page2').length).to.equal(12); expect(index.search("g")[0]?.result?.length || 0).to.equal(11); expect(index.search('g', { tag: { parentId: 'page1' } })[0]?.result?.length || 0).to.equal(1); expect(index.search('g', { limit: 5 })[0]?.result?.length || 0).to.equal(5); expect(index.search('g', { limit: 5, tag: { parentId: 'page1' } })[0]?.result?.length || 0).to.equal(1); expect(index.search('g', { limit: 20, offset: 0, tag: { parentId: 'page1' } })[0]?.result?.length || 0).to.equal(1); }); }); ================================================ FILE: test/keystore.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const build_es5 = !env || env.includes("es5"); const Charset = _Charset || (await import("../src/charset.js")).default; if(!build_light && !build_compact) describe("Keystore", function(){ it("Should have applied the keystore properly", function(){ const index = new Index({ fastupdate: true, keystore: build_es5 ? 32 : 64, context: true }); for(let i = 0; i < 100; i++){ index.add(i, "foo bar"); } expect(index.map.size).to.equal(2); expect(index.map.get("fo")[0].length).to.equal(100); expect(index.ctx.get("fo").get("bar")[0].length).to.equal(100); expect(index.reg.size).to.equal(100); for(let i = 0; i < 100; i++){ index.add(i, "foobar"); } expect(index.map.size).to.equal(3); index.cleanup(); expect(index.map.size).to.equal(1); expect(index.map.get("fo")).to.be.undefined; expect(index.map.get("fobar")[0].length).to.equal(100); expect(index.ctx.get("fo")).to.be.undefined; expect(index.reg.size).to.equal(100); for(let i = 0; i < 50; i++){ index.remove(i); } expect(index.map.size).to.equal(1); expect(index.map.get("fobar")[0].length).to.equal(50); expect(index.reg.size).to.equal(50); index.clear(); expect(index.map.size).to.equal(0); expect(index.ctx.size).to.equal(0); expect(index.reg.size).to.equal(0); }); // todo increase memory it("Should have extended the default limit", function(){ const index = new Index({ fastupdate: true, //encode: str => str, keystore: 16, context: true }); // this.slow(30000); // this.timeout(60000); //const encoded = ["foo", "bar"]; index.add(0, "foo bar"); index.add(1, "foo bar"); index.add(2, "foo bar"); let foo = index.map.get("fo"); let bar = index.ctx.get("fo").get("bar"); foo[0].length = 2**31 - 10; bar[0].length = 2**31 - 10; for(let i = foo[0].length; i <= 2**31 + 9; i++){ index.add(i, "foo bar"); } expect(index.map.get("fo")[0].length).to.equal(2**31 + 10); expect(index.ctx.get("fo").get("bar")[0].length).to.equal(2**31 + 10); //expect(index.reg.size).to.equal(2**31 + 1); expect(index.search("fo bar", 3)).to.eql([0, 1, 2]); index.clear(); expect(index.map.size).to.equal(0); expect(index.ctx.size).to.equal(0); expect(index.reg.size).to.equal(0); }); }); ================================================ FILE: test/misc/reporter.js ================================================ const libCoverage = require('istanbul-lib-coverage'); const { createReporter } = require('istanbul-api'); const coverage_1 = require('./.nyc_output/coverage.json'); const coverage_2 = require('./.nyc_output/coverage2.json'); const normalizeJestCoverage = (obj) => { const result = obj; Object.entries(result).forEach(([k, v]) => { if (v.data) result[k] = v.data; }); return result; }; const map = libCoverage.createCoverageMap(); map.merge(normalizeJestCoverage(coverage_1)); map.merge(normalizeJestCoverage(coverage_2)); const reporter = createReporter(); reporter.addAll(['html', 'json', 'lcov', 'text']); reporter.write(map); ================================================ FILE: test/misc/runner.js ================================================ ================================================ FILE: test/package.json ================================================ { "name": "flexsearch-test", "type": "module", "scripts": { "test": "mocha ./*.js --exit", "test:ts": "npx tsc --noEmit --lib esnext ./types.ts", "test:github": "npm run test:ts && mocha ./*.js --exit flexsearch.light.min && mocha ./*.js --exit flexsearch.light.module.min && mocha ./*.js --exit flexsearch.compact.min && mocha ./*.js --exit flexsearch.compact.module.min", "test:coverage": "c8 -c ./.c8rc.json mocha ./*.js --exit", "test:keystore": "set NODE_OPTIONS=--max-old-space-size=16000 && c8 -c ./.c8rc.json mocha ./keystore.js --exit", "test:db": "c8 -c ./.c8rc.json mocha ./persistent.*.js --exit", "test:all": "npm run test:ts && npx mocha ./*.js --exit module/bundle && npx mocha ./*.js --exit module-debug/bundle && npx mocha ./*.js --exit module-min/bundle && npx mocha ./*.js --exit flexsearch.bundle.debug && npx mocha ./*.js --exit flexsearch.bundle.min && npx mocha ./*.js --exit flexsearch.bundle.module.debug && npx mocha ./*.js --exit flexsearch.bundle.module.min && npx mocha ./*.js --exit flexsearch.compact.debug && npx mocha ./*.js --exit flexsearch.compact.min && npx mocha ./*.js --exit flexsearch.compact.module.debug && npx mocha ./*.js --exit flexsearch.compact.module.min && npx mocha ./*.js --exit flexsearch.es5.debug && npx mocha ./*.js --exit flexsearch.es5.min && npx mocha ./*.js --exit flexsearch.light.debug && npx mocha ./*.js --exit flexsearch.light.min && npx mocha ./*.js --exit flexsearch.light.module.debug && npx mocha ./*.js --exit flexsearch.light.module.min" }, "devDependencies": { "c8": "^10.1.3", "chai": "^5.2.0", "clickhouse": "^2.6.0", "flexsearch": "^0.8.205", "mocha": "^11.1.0", "mongodb": "^6.13.0", "pg-promise": "^11.10.2", "redis": "^5.1.0", "rollup": "^4.35.0", "sqlite3": "^5.1.7", "typescript": "^5.8.3" } } ================================================ FILE: test/persistent.clickhouse.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; import tests from "./persistent.js"; if(!build_light && !build_compact){ describe("Persistent: Clickhouse", async function(){ await tests("Clickhouse"); }); } ================================================ FILE: test/persistent.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; export default async function(DBClass){ let DB; if(DBClass === "Clickhouse"){ DB = (await import(env ? "../dist/module/db/clickhouse/index.js" : "../src/db/clickhouse/index.js")).default; } if(DBClass === "Redis"){ DB = (await import(env ? "../dist/module/db/redis/index.js" : "../src/db/redis/index.js")).default; } if(DBClass === "Mongo"){ DB = (await import(env ? "../dist/module/db/mongodb/index.js" : "../src/db/mongodb/index.js")).default; } if(DBClass === "SQLite"){ DB = (await import(env ? "../dist/module/db/sqlite/index.js" : "../src/db/sqlite/index.js")).default; } if(DBClass === "Postgres"){ DB = (await import(env ? "../dist/module/db/postgres/index.js" : "../src/db/postgres/index.js")).default; } const data = [{ "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] },{ "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }]; it("Should have created the instance properly", async function(){ // create DB instance with namespace const db = new DB("test-store", { type: "integer" }); expect(db).to.respondTo("mount"); expect(db).to.respondTo("close"); expect(db).to.hasOwnProperty("id"); expect(db).to.hasOwnProperty("field"); expect(db).to.hasOwnProperty("db"); // create a simple index which can store id-content-pairs let index = new Index({ tokenize: "strict" }); // mount database to the index await index.mount(db); expect(index.db).to.equal(db); await index.destroy(); expect(index.db).to.equal(db); // mount database to the index await db.mount(index); expect(index.db).to.equal(db); //await index.clear(); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index for(let i = 0; i < data.length; i++){ index.add(i, data[i]); } expect(index.reg.size).to.equal(7); expect(index.map.size).not.to.equal(0); await index.commit(); expect(index.reg.size).to.equal(0); expect(index.map.size).to.equal(0); let result = await index.search("cats cute"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = await index.search("cute cats"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = await index.search("cute"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = await index.search("cute dogs cats"); expect(result).to.eql([1]); result = await index.search("cute dogs cats", { suggest: true }); expect(result).to.eql([1, 6, 5, 4, 3, 2, 0]); // Redis lacks of its own union feature, because it didn't provide // a way to order results by count of union matches if(DBClass === "Redis"){ result = await index.search("undefined cute undefined dogs undefined cats undefined", { suggest: true }); expect(result).to.eql([6, 5, 1, 4, 3, 2, 0]); } else{ result = await index.search("undefined cute undefined dogs undefined cats undefined", { suggest: true }); expect(result).to.eql([1, 6, 5, 4, 3, 2, 0]); } result = await index.search("cute cat"); expect(result.length).to.equal(0); await index.destroy(); await index.db.close(); }); it("Should have created the instance properly (Context Search)", async function(){ // create DB instance with namespace const db = new DB("test-store", { type: "integer" }); expect(db).to.respondTo("mount"); expect(db).to.respondTo("close"); expect(db).to.hasOwnProperty("id"); expect(db).to.hasOwnProperty("field"); expect(db).to.hasOwnProperty("db"); // create a simple index which can store id-content-pairs let index = new Index({ tokenize: "strict", //context: true }); // mount database to the index await index.mount(db); expect(index.db).to.equal(db); //await index.destroy(); //expect(index.db).to.equal(db); // mount database to the index //await db.mount(index); //expect(index.db).to.equal(db); await index.clear(); // some test data const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; // add data to the index for(let i = 0; i < data.length; i++){ index.add(i, data[i]); } expect(index.reg.size).to.equal(7); expect(index.map.size).not.to.equal(0); await index.commit(); expect(index.reg.size).to.equal(0); expect(index.map.size).to.equal(0); let result = await index.search("cats cute"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = await index.search("cute cats"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = await index.search("cute dogs cats"); expect(result).to.eql([1]); result = await index.search("cute"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); // Redis lacks of its own union feature, because it didn't provide // a way to order results by count of union matches if(DBClass === "Redis"){ result = await index.search("undefined cute undefined dogs undefined cats undefined", { suggest: true }); expect(result).to.eql([6, 5, 1, 4, 3, 2, 0]); } else{ result = await index.search("undefined cute undefined dogs undefined cats undefined", { suggest: true }); expect(result).to.eql([1, 6, 5, 4, 3, 2, 0]); } result = await index.search("cute cat"); expect(result.length).to.equal(0); await index.destroy(); await index.db.close(); }); it("Documents", async function(){ // create DB instance with namespace const db = new DB("my-store"); // create the document index const document = new Document({ encoder: Charset.LatinBalance, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward" },{ field: "originalTitle", tokenize: "forward" }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // mount database to the index await document.mount(db); await document.clear(); expect(document.index.get("primaryTitle").db).to.be.instanceof(db.constructor); expect(document.index.get("originalTitle").db).to.be.instanceof(db.constructor); expect(document.index.get("startYear").db).to.be.instanceof(db.constructor); expect(document.index.get("genres").db).to.be.instanceof(db.constructor); // add test data for(let i = 0; i < data.length; i++){ document.add(data[i]); } expect(document.index.get("primaryTitle").reg.size).to.equal(2); expect(document.index.get("primaryTitle").map.size).to.equal(25); expect(document.index.get("originalTitle").reg.size).to.equal(2); expect(document.index.get("originalTitle").map.size).to.equal(25); // tag pseudo indexes (persistent only) expect(document.index.get("startYear").reg.size).to.equal(2); expect(document.index.get("startYear").map.size).to.equal(0); expect(document.index.get("genres").reg.size).to.equal(2); expect(document.index.get("genres").map.size).to.equal(0); expect(document.reg.size).to.equal(2); expect(document.store.size).to.equal(2); expect(document.tag.size).to.equal(2); expect(document.tag.get("startYear").size).to.equal(2); expect(document.tag.get("genres").size).to.equal(3); // transfer changes in bulk await document.commit(); expect(document.index.get("primaryTitle").reg.size).to.equal(0); expect(document.index.get("primaryTitle").map.size).to.equal(0); expect(document.index.get("originalTitle").reg.size).to.equal(0); expect(document.index.get("originalTitle").map.size).to.equal(0); expect(document.index.get("startYear").reg.size).to.equal(0); expect(document.index.get("startYear").map.size).to.equal(0); expect(document.index.get("genres").reg.size).to.equal(0); expect(document.index.get("genres").map.size).to.equal(0); expect(document.reg.size).to.equal(0); expect(document.store.size).to.equal(0); expect(document.tag.size).to.equal(2); expect(document.tag.get("startYear").size).to.equal(0); expect(document.tag.get("genres").size).to.equal(0); expect(await document.contain(data[0]["tconst"])).to.equal(true); let result = await document.search({ query: "karmen" }); expect(result).to.eql([ { field: 'primaryTitle', result: [ data[0]["tconst"] ] }, { field: 'originalTitle', result: [ data[0]["tconst"] ] } ]); result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, }); expect(result).to.eql([ { field: 'primaryTitle', result: [ data[0]["tconst"] ] }, { field: 'originalTitle', result: [ data[0]["tconst"] ] } ]); result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, enrich: true }); expect(result).to.eql([{ field: "primaryTitle", result: [{ id: data[0]["tconst"], doc: data[0] }] },{ field: "originalTitle", result: [{ id: data[0]["tconst"], doc: data[0] }] }]); result = await document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true }); expect(result).to.eql([ { field: 'primaryTitle', result: [{ id: data[0]["tconst"], doc: data[0] }] }, { field: 'originalTitle', result: [{ id: data[0]["tconst"], doc: data[0] }] } ]); result = await document.search({ query: "karmen or clown or nothing", suggest: true, enrich: true, merge: true }); expect(result).to.deep.contain({ id: 'tt0000001', doc: data[0], field: [ 'primaryTitle', 'originalTitle' ] }); expect(result).to.deep.contain({ id: 'tt0000002', doc: data[1], field: [ 'primaryTitle', 'originalTitle' ] }); await document.clear(); result = await document.search({ query: "karmen or clown or nothing", suggest: true, enrich: true, merge: true }); expect(result).to.eql([]); for(const index of document.index.values()){ index.destroy(); index.db.close(); } }); it("Result Highlighting", async function(){ // some test data const data = [{ "id": 1, "title": "Carmencita" },{ "id": 2, "title": "Le clown et ses chiens" }]; // create the document index const document = new Document({ cache: true, db: new DB("test-highlight", { type: "Integer" }), document: { store: true, index: [{ field: "title", tokenize: "forward", encoder: Charset.LatinBalance }] } }); //await document.mount(db); await document.db; // add test data for(let i = 0; i < data.length; i++){ document.add(data[i]); } await document.commit(); // perform a query let result = await document.searchCache({ query: "karmen or clown or not found", suggest: true, highlight: "$1" }); expect(result[0].result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' },{ id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); // perform a query on cache result = await document.searchCache({ query: "karmen or clown or not found", suggest: true, highlight: "$1" }); expect(result[0].result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' }, { id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); // perform a query using pluck result = await document.search({ query: "karmen or clown or not found", suggest: true, field: "title", highlight: "$1" }); expect(result[0].result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' },{ id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); for(const index of document.index.values()){ index.destroy(); index.db.close(); } }); it("Resolver (Persistent)", async function(){ // some test data const data = [{ "id": 1, "title": "Carmencita", "description": "Description: Carmencita" },{ "id": 2, "title": "Le clown et ses chiens", "description": "Description: Le clown et ses chiens" }]; // create the document index const document = new Document({ db: new DB("test-store", { type: "integer" }), encoder: Charset.LatinBalance, document: { store: true, index: [{ field: "title", tokenize: "forward" },{ field: "description", tokenize: "forward" }] } }); await document.db; // add test data for(let i = 0; i < data.length; i++){ document.add(data[i]); } await document.commit(); let result = new Resolver({ index: document, query: "not found", field: "description" }); expect(result).to.be.instanceof(Resolver); result = result.and({ query: "karmen or clown", pluck: "title", suggest: true, enrich: true, resolve: true, highlight: "$1" }); expect(result).to.be.instanceof(Promise); expect(await result).to.eql([{ id: 1, doc: data[0], highlight: "Carmencita" },{ id: 2, doc: data[1], highlight: "Le clown et ses chiens" }]); // ----------------------------------- result = new Resolver({ index: document, query: "not found", field: "description" }); expect(result).to.be.instanceof(Resolver); result = result.or({ query: "karmen or clown", pluck: "title", suggest: true }).resolve({ enrich: true, highlight: "$1" }); expect(result).to.be.instanceof(Promise); expect(await result).to.eql([{ id: 1, doc: data[0], highlight: "Carmencita" },{ id: 2, doc: data[1], highlight: "Le clown et ses chiens" }]); for(const index of document.index.values()){ index.destroy(); index.db.close(); } }); it("Should have been resolved a Resolver properly (Async)", async function(){ const db = new DB("test-store", { type: "integer" }); const index = new Index({ tokenize: "reverse" }); await index.mount(db); await index.clear(); index.add(1, "foo"); await index.addAsync(2, "bar"); index.add(3, "FooBar"); await index.commit(); let resolver = new Resolver({ index: index, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); let tmp = resolver.await; resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); expect((await tmp)[0]).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); tmp = resolver.await; resolver = resolver.resolve({ limit: 1, offset: 1 }); expect(resolver).to.be.instanceof(Promise); expect((await resolver)[0]).oneOf([3, 1, 2]); expect((await resolver)[1]).to.be.undefined; expect((await tmp)[0]).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, query: "bar" }).and({ async: true, query: "foo", suggest: true }); expect(resolver).to.be.instanceof(Resolver); resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, query: "bar" }).and({ async: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, cache: true, query: "bar" }).and({ async: true, cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, cache: true, query: "bar" }).and({ async: true, cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, async: false, cache: true, query: "bar" }).and({ async: true, cache: false, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, cache: true, query: "bar" }).and({ async: false, cache: false, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); await index.destroy(); await index.db.close(); }); it("Should have been resolved a Resolver properly (Document Persistent)", async function(){ // create DB instance with namespace const db = new DB("my-store"); // create the document index const document = new Document({ encoder: Charset.LatinBalance, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward" },{ field: "originalTitle", tokenize: "forward" }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // mount database to the index await document.mount(db); await document.clear(); // add test data for(let i = 0; i < data.length; i++){ document.add(data[i]); } // transfer changes in bulk await document.commit(); let resolver = new Resolver({ index: document, query: "karmen or clown or nothing", field: "primaryTitle", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); let tmp = resolver.await; resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members(["tt0000001", "tt0000002"]); expect((await tmp)[0]).to.have.members(["tt0000001"]); // ----------------------------------- resolver = new Resolver({ index: document, async: true, query: "karmen or clown or nothing", field: "primaryTitle", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); tmp = resolver.await; resolver = resolver.resolve({ enrich: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([{ id: data[0].tconst, doc: data[0] }, { id: data[1].tconst, doc: data[1] }]); expect((await tmp)[0]).to.have.members(["tt0000001"]); // ----------------------------------- resolver = new Resolver({ index: document, async: true, query: "karmen or clown or nothing", field: "primaryTitle", suggest: true }).or({ index: document, queue: true, query: "karmen or clown or nothing", pluck: "primaryTitle", suggest: true, enrich: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([{ id: data[0].tconst, doc: data[0] }, { id: data[1].tconst, doc: data[1] }]); // ----------------------------------- resolver = new Resolver({ index: document, async: true, query: "karmen or clown or nothing", pluck: "primaryTitle", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); resolver = resolver.resolve({ limit: 1, offset: 1 }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql(["tt0000002"]); // ----------------------------------- resolver = new Resolver({ index: document, async: true, query: "karmen", pluck: "primaryTitle" }).or({ queue: true, cache: true, query: "clown", field: "originalTitle" }).and({ async: true, query: "not found", pluck: "originalTitle", suggest: true }); expect(resolver).to.be.instanceof(Resolver); resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql(["tt0000001", "tt0000002"]); // ----------------------------------- resolver = new Resolver({ index: document, async: true, // TODO //cache: true, query: "karmen", pluck: "primaryTitle" }).or({ and: [{ async: true, //cache: true, query: "not found", pluck: "originalTitle", suggest: true },{ queue: true, //cache: true, query: "clown", field: "originalTitle", suggest: true }] }).resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql(["tt0000001", "tt0000002"]); for(const index of document.index.values()){ index.destroy(); index.db.close(); } }); it("Should have been resolved a Resolver properly (Queue)", async function(){ const db = new DB("test-store", { type: "integer" }); const index = new Index({ tokenize: "reverse" }); await index.mount(db); await index.clear(); index.add(1, "foo"); await index.addAsync(2, "bar"); index.add(3, "FooBar"); await index.commit(); let resolver = new Resolver({ index: index, queue: true, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); let tmp = resolver.await; resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); expect((await tmp)[0]).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); tmp = resolver.await; resolver = resolver.resolve({ limit: 1, offset: 1 }); expect(resolver).to.be.instanceof(Promise); expect((await resolver)[0]).oneOf([3, 1, 2]); expect((await resolver)[1]).to.be.undefined; expect((await tmp)[0]).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, query: "bar" }).limit(3).and({ queue: true, query: "foo", suggest: true }).limit(3); expect(resolver).to.be.instanceof(Resolver); resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, query: "bar" }).boost(2).and({ queue: true, query: "foo", suggest: true }).offset(1).limit(1); expect(resolver).to.be.instanceof(Resolver); resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([1]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, query: "bar" }).and({ async: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, cache: true, query: "bar" }).and({ queue: true, cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, cache: true, query: "bar" }).and({ async: true, cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, async: false, cache: true, query: "bar" }).and({ queue: true, cache: false, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, cache: true, query: "bar" }).and({ async: false, cache: false, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members([3, 1, 2]); await index.destroy(); await index.db.close(); }); it("#504", async function(){ const document = new Document({ tokenize: "forward", commit: true, document: { id: "id", index: ["name", "shortName"], store: true, }, }); const db = new DB("mystore", { type: "integer" }); await document.mount(db); await document.clear(); document.add({ id: 1, name: "a name", shortName: "" // Or undefined }); await document.commit(); const result = await document.search({ query: "name" }); expect(result).to.eql([ { field: 'name', result: [ 1 ] } ]); for(const index of document.index.values()){ index.destroy(); index.db.close(); } }); } ================================================ FILE: test/persistent.mongo.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; import tests from "./persistent.js"; if(!build_light && !build_compact){ describe("Persistent: Mongo", async function(){ await tests("Mongo"); }); } ================================================ FILE: test/persistent.postgres.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; import tests from "./persistent.js"; if(!build_light && !build_compact){ describe("Persistent: Postgres", async function(){ await tests("Postgres"); }); } ================================================ FILE: test/persistent.redis.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; import tests from "./persistent.js"; if(!build_light && !build_compact){ describe("Persistent: Redis", async function(){ await tests("Redis"); }); } ================================================ FILE: test/persistent.sqlite.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; import tests from "./persistent.js"; if(!build_light && !build_compact){ describe("Persistent: SQLite", async function(){ await tests("SQLite"); }); } ================================================ FILE: test/resolver.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; if(!build_light && !build_compact) describe("Resolver", function(){ it("Should have been created a Resolver properly", function(){ let index = new Index({ tokenize: "reverse" }); index.add(1, "foo"); index.add(2, "bar"); index.add(3, "FooBar"); let resolver = index.search("foo bar", { resolve: false, suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver).to.respondTo("and"); expect(resolver).to.respondTo("or"); expect(resolver).to.respondTo("xor"); expect(resolver).to.respondTo("not"); expect(resolver).to.respondTo("boost"); expect(resolver).to.respondTo("limit"); expect(resolver).to.respondTo("offset"); expect(resolver).to.respondTo("resolve"); expect(resolver.result).to.eql([[3, 1, 2]]); }); it("Should have been created a Resolver properly (alternative)", function(){ const index = new Index({ tokenize: "reverse" }); index.add(1, "foo"); index.add(2, "bar"); index.add(3, "FooBar"); let resolver = new Resolver({ index: index, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([[3, 1, 2]]); }); it("Should have been created a Resolver properly (alternative, Async)", async function(){ const index = new Index({ tokenize: "reverse" }); index.add(1, "foo"); await index.addAsync(2, "bar"); index.add(3, "FooBar"); let resolver = new Resolver({ index: index, async: true, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, query: "foo bar", suggest: true }); expect(await resolver.await).to.eql([[3, 1, 2]]); expect(resolver.await).to.eql(null); }); it("Should have been created a Resolver properly (alternative, Queue)", async function(){ const index = new Index({ tokenize: "reverse" }); index.add(1, "foo"); await index.addAsync(2, "bar"); index.add(3, "FooBar"); let resolver = new Resolver({ index: index, queue: true, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, query: "foo bar", suggest: true }); expect(await resolver.await).to.eql([[3, 1, 2]]); expect(resolver.await).to.eql(null); }); it("Should have been created a Resolver properly (alternative, Worker)", async function(){ const index = await new Worker({ tokenize: "reverse" }); await index.add(1, "foo"); await index.addAsync(2, "bar"); await index.add(3, "FooBar"); let resolver = new Resolver({ index: index, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 1, 2]); // ----------------------------------- resolver = new Resolver({ index: index, query: "foo bar", suggest: true }); expect(await resolver.await).to.eql([[3, 1, 2]]); expect(resolver.await).to.eql(null); }); it("Should have been resolved a Resolver properly", function(){ const index = new Index({ tokenize: "reverse" }); index.add(1, "foo"); index.add(2, "bar"); index.add(3, "FooBar"); let result = new Resolver({ index: index, query: "foo bar", suggest: true }).resolve(); expect(result.length).to.equal(3); expect(result).to.eql([3, 1, 2]); // ----------------------------------- result = new Resolver({ index: index, query: "foo bar", suggest: true }).resolve({ limit: 1, offset: 1 }); expect(result.length).to.equal(1); expect(result).to.eql([1]); // ----------------------------------- result = new Resolver({ index: index, query: "bar" }).and({ index: index, query: "foo", suggest: true, resolve: true }); expect(result.length).to.equal(3); expect(result).to.eql([3, 2, 1]); }); it("Should have been resolved a Resolver properly (Async)", async function(){ const index = new Index({ tokenize: "reverse" }); index.add(1, "foo"); await index.addAsync(2, "bar"); index.add(3, "FooBar"); let resolver = new Resolver({ index: index, async: true, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); let tmp = resolver.await; resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 1, 2]); expect(await tmp).to.eql([[3, 1, 2]]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); tmp = resolver.await; resolver = resolver.resolve({ limit: 1, offset: 1 }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([1]); expect(await tmp).to.eql([[3, 1, 2]]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, query: "bar" }).and({ async: true, query: "foo", suggest: true }); expect(resolver).to.be.instanceof(Resolver); resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, query: "bar" }).and({ async: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, cache: true, query: "bar" }).and({ async: true, cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, cache: true, query: "bar" }).and({ async: true, cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, async: false, cache: true, query: "bar" }).and({ async: true, cache: false, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, cache: true, query: "bar" }).and({ async: false, cache: false, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); }); it("Should have been resolved a Resolver properly (Queue)", async function(){ const index = new Index({ tokenize: "reverse" }); index.add(1, "foo"); await index.addAsync(2, "bar"); index.add(3, "FooBar"); let resolver = new Resolver({ index: index, queue: true, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); let tmp = resolver.await; resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 1, 2]); expect(await tmp).to.eql([[3, 1, 2]]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); tmp = resolver.await; resolver = resolver.resolve({ limit: 1, offset: 1 }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([1]); expect(await tmp).to.eql([[3, 1, 2]]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, query: "bar" }).and({ queue: true, query: "foo", suggest: true }); expect(resolver).to.be.instanceof(Resolver); resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, query: "bar" }).and({ async: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, cache: true, query: "bar" }).and({ queue: true, cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, cache: true, query: "bar" }).and({ async: true, cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, async: false, cache: true, query: "bar" }).and({ queue: true, cache: false, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, cache: true, query: "bar" }).and({ async: false, cache: false, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); }); it("Should have been resolved a Resolver properly (Worker)", async function(){ const index = await new Worker({ tokenize: "reverse" }); await index.add(1, "foo"); await index.addAsync(2, "bar"); await index.add(3, "FooBar"); let resolver = new Resolver({ index: index, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); let tmp = resolver.await; resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 1, 2]); expect(await tmp).to.eql([[3, 1, 2]]); // ----------------------------------- resolver = new Resolver({ index: index, query: "foo bar", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); tmp = resolver.await; resolver = resolver.resolve({ limit: 1, offset: 1 }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([1]); expect(await tmp).to.eql([[3, 1, 2]]); // ----------------------------------- resolver = new Resolver({ index: index, query: "bar", }).and({ query: "foo", suggest: true }); expect(resolver).to.be.instanceof(Resolver); resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, query: "bar", }).and({ query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, cache: true, query: "bar" }).and({ async: true, cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, async: true, cache: true, query: "bar" }).and({ async: true, cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, queue: true, cache: true, query: "bar" }).and({ queue: true, cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, cache: true, query: "bar" }).and({ cache: true, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, cache: true, query: "bar" }).and({ cache: false, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); // ----------------------------------- resolver = new Resolver({ index: index, cache: true, query: "bar" }).and({ cache: false, query: "foo", suggest: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([3, 2, 1]); }); it("Should have been apply \"and\" properly", function(){ const index = new Index({ tokenize: "forward" }); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', // <-- dogs 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); let resolver = new Resolver({ index: index, query: "cat" }); expect(resolver.result).to.eql([[0, 1, 2, 3, 4, 5, 6]]); resolver = resolver.and({ index: index, query: "cute" }); expect(resolver.result).to.eql([ void 0, [6], [5], [4], [3], [2], [1], [0] ]); resolver = resolver.and({ query: "dog" }); expect(resolver.result).to.eql([ void 0, void 0, void 0, void 0, void 0, void 0, [1] ]); resolver = resolver.and({ index: index, query: "fish", suggest: true }); expect(resolver.result).to.eql([ void 0, void 0, void 0, void 0, void 0, void 0, [1] ]); resolver = resolver.and({ index: index, query: "bird" }); expect(resolver.result).to.eql([]); resolver = resolver.and({ query: "dog", suggest: true }); expect(resolver.result).to.eql([ void 0, void 0, void 0, void 0, void 0, [1] ]); }); it("Should have been apply \"or\" properly", function(){ const index = new Index({ tokenize: "forward" }); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', // <-- dogs 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); function create(){ return new Resolver({ index: index, query: "cat" }).and({ query: "cute", }); } let resolver = create(); expect(resolver.result).to.eql([void 0,[6], [5], [4], [3], [2], [1], [0]]); // todo resolver = create().or([{ query: "fish" },{ query: "dog" },{ query: "horse" }]); expect(resolver.result).to.eql([[6, 5, 4], [3, 2, 1, 0]]); resolver = create().or({ query: "fish" },{ index: index, query: "dog" },{ query: "horse" }); expect(resolver.result).to.eql([[6, 5, 4], [3, 2, 1, 0]]); resolver = create().or({ query: "fish" }).or({ query: "dog" }).or({ index: index, query: "horse" }).or({ query: "dog" }).or({ query: "horse" }); expect(resolver.result).to.eql([[6, 5, 4, 3, 2, 1, 0]]); resolver = create().or(new Resolver({ index: index, query: "dog", }).and({ query: "cute" })); expect(resolver.result).to.eql([[6], [5, 4], [3, 2], [1, 0]]); resolver = create().or({ and: [{ query: "dog", },{ query: "cute" }] }); expect(resolver.result).to.eql([[6], [5, 4], [3, 2], [1, 0]]); }); it("Should have been apply \"or\" properly (Async)", async function(){ const index = new Index({ tokenize: "forward" }); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', // <-- dogs 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); function create(){ return new Resolver({ index: index, async: true, query: "cat" }).and({ async: true, query: "cute", }); } let resolver = create(); expect(await resolver.await).to.eql([void 0,[6], [5], [4], [3], [2], [1], [0]]); // todo resolver = create().or([{ query: "fish", async: true },{ query: "dog", async: false },{ query: "horse", async: true }]); expect(await resolver.await).to.eql([[6, 5, 4], [3, 2, 1, 0]]); resolver = create().or({ query: "fish", async: true },{ index: index, query: "dog", async: false },{ query: "horse", async: true }); expect(await resolver.await).to.eql([[6, 5, 4], [3, 2, 1, 0]]); resolver = create().or({ query: "fish" }).or({ query: "dog" }).or({ query: "horse", async: true }).or({ query: "dog" }).or({ query: "horse", async: true }); expect(await resolver.await).to.eql([[6, 5, 4, 3, 2, 1, 0]]); resolver = create().or(new Resolver({ index: index, query: "dog", }).and({ query: "cute", async: true })); expect(await resolver.await).to.eql([[6], [5, 4], [3, 2], [1, 0]]); resolver = create().or({ and: [{ query: "dog", async: true },{ query: "cute" }] }); expect(await resolver.await).to.eql([[6], [5, 4], [3, 2], [1, 0]]); }); it("Should have been apply \"or\" properly (Worker)", async function(){ const index = await new Worker({ tokenize: "forward" }); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', // <-- dogs 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); function create(){ return new Resolver({ index: index, async: true, query: "cat" }).and({ async: true, query: "cute", }); } let resolver = create(); expect(await resolver.await).to.eql([void 0,[6], [5], [4], [3], [2], [1], [0]]); // todo resolver = create().or([{ query: "fish", async: true },{ query: "dog", async: false },{ query: "horse", async: true }]); expect(await resolver.await).to.eql([[6, 5, 4], [3, 2, 1, 0]]); resolver = create().or({ query: "fish", async: true },{ index: index, query: "dog", async: false },{ query: "horse", async: true }); expect(await resolver.await).to.eql([[6, 5, 4], [3, 2, 1, 0]]); resolver = create().or({ query: "fish" }).or({ query: "dog" }).or({ query: "horse", async: true }).or({ query: "dog" }).or({ query: "horse", async: true }); expect(await resolver.await).to.eql([[6, 5, 4, 3, 2, 1, 0]]); resolver = create().or(new Resolver({ index: index, query: "dog", }).and({ query: "cute", async: true })); expect(await resolver.await).to.eql([[6], [5, 4], [3, 2], [1, 0]]); resolver = create().or({ and: [{ query: "dog", async: true },{ query: "cute" }] }); expect(await resolver.await).to.eql([[6], [5, 4], [3, 2], [1, 0]]); }); it("Should have been apply \"xor\" properly", function(){ const index = new Index(); index.add(1, "foo foo"); index.add(2, "bar bar"); index.add(3, "foo bar"); index.add(4, "bar foo"); let resolver = new Resolver({ index: index, query: "foo" }).xor({ index: index, query: "bar" }); expect(resolver.result).to.eql([[1, 2]]); }); it("Should have been apply \"not\" properly", function(){ const index = new Index({ tokenize: "forward" }); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', // <-- dogs 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); let resolver = new Resolver({ index: index, query: "cute" }).not({ index: index, query: "cat" }); expect(resolver.result).to.eql([]); resolver = new Resolver({ index: index, query: "cute" }).not({ index: index, query: "dog" }); expect(resolver.result).to.eql([ void 0, [6], [5], [4], [3], [2], void 0, // dogs [0] ]); }); it("Should have been apply \"limit\" and \"offset\" properly", function(){ const index = new Index({ tokenize: "forward" }); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', // <-- dogs 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); let resolver = new Resolver({ index: index, query: "cute" }).limit(3); expect(resolver.result).to.eql([ void 0, [6], [5], [4] ]); resolver = new Resolver({ index: index, query: "cute" }).offset(3).limit(2); expect(resolver.result).to.eql([ void 0, void 0, // offset +1 void 0, // offset +2 void 0, // offset +3 [3], [2] ]); }); it("Should have been apply \"boost\" properly", function(){ const index = new Index({ tokenize: "forward" }); [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', // <-- dogs 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ].forEach((item, id) => { index.add(id, item); }); let resolver = new Resolver({ index: index, query: "dog" }).boost(0).or({ query: "cat" }); expect(resolver.result).to.eql([ [ 0, 2, 3, 4, 5, 6 ], void 0, [ 1 ] ]); resolver = new Resolver({ index: index, query: "dog" }).boost(1).or({ index: index, query: "cat" }); expect(resolver.result).to.eql([ void 0, [ 0, 2, 3, 4, 5, 6 ], [ 1 ] ]); resolver = new Resolver({ index: index, query: "dog" }).boost(2).or({ query: "cat" }); expect(resolver.result).to.eql([ void 0, void 0, [ 1, 0, 2, 3, 4, 5, 6 ] ]); resolver = new Resolver({ index: index, query: "dog" }).boost(3).or({ index: index, query: "cat" }); expect(resolver.result).to.eql([ void 0, void 0, [ 1 ], [ 0, 2, 3, 4, 5, 6 ] ]); }); it("Should have been applied on Documents properly", function(){ // some test data const data = [{ "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] },{ "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }]; // create the document index const index = new Document({ encoder: Charset.LatinBalance, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward" },{ field: "originalTitle", tokenize: "forward" }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // add test data for(let i = 0; i < data.length; i++){ index.add(data[i]); } // perform a query + enrich results let result = new Resolver({ index: index, query: "karmen", pluck: "primaryTitle" }).or({ query: "clown", pluck: "primaryTitle" }).and({ index: index, query: "karmen", field: "primaryTitle", suggest: true }).not({ query: "clown", pluck: "primaryTitle", enrich: true, resolve: true }); expect(result).to.eql([{ id: 'tt0000001', doc: data[0] }]); result = new Resolver({ index: index, query: "karmen", pluck: "primaryTitle" }).or({ index: index, query: "clown", pluck: "primaryTitle" }).and({ query: "not found", field: "primaryTitle", suggest: true }).resolve({ enrich: true, resolve: true }); expect(result).to.eql([{ id: 'tt0000001', doc: data[0] },{ id: 'tt0000002', doc: data[1] }]); }); it("Should have been applied on Documents properly (Async)", async function(){ // some test data const data = [{ "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] },{ "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }]; // create the document index const index = new Document({ encoder: Charset.LatinBalance, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward" },{ field: "originalTitle", tokenize: "forward" }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // add test data for(let i = 0; i < data.length; i++){ await index.addAsync(data[i]); } // perform a query + enrich results let result = new Resolver({ queue: true, index: index, query: "karmen", pluck: "primaryTitle" }).or({ async: true, query: "clown", pluck: "primaryTitle", }).and({ index: index, query: "karmen", field: "primaryTitle", suggest: true }).not({ queue: true, query: "clown", pluck: "primaryTitle", enrich: true, resolve: true }); expect(result).to.be.instanceof(Promise); expect(await result).to.eql([{ id: 'tt0000001', doc: data[0] }]); result = new Resolver({ index: index, query: "karmen", pluck: "primaryTitle" }).or({ queue: true, index: index, query: "clown", pluck: "primaryTitle", }).and({ async: true, query: "not found", field: "primaryTitle", suggest: true }).resolve({ enrich: true, resolve: true }); expect(result).to.be.instanceof(Promise); expect(await result).to.eql([{ id: 'tt0000001', doc: data[0] },{ id: 'tt0000002', doc: data[1] }]); }); }); ================================================ FILE: test/scoring.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; describe("Scoring", function(){ it("Should have been sorted by relevance properly", function(){ let index = new Index({ tokenize: "strict", resolution: 10 }); index.add(0, "1 2 3 2 4 1 5 3"); index.add(1, "zero one two three four five six seven eight nine ten"); index.add(2, "four two zero one three ten five seven eight six nine"); expect(index.search("1")).to.eql([0]); expect(index.search("one")).to.eql([1, 2]); expect(index.search("one two")).to.eql([1, 2]); expect(index.search("four one")).to.eql([2, 1]); index = new Index({ tokenize: "strict", context: { depth: 3, bidirectional: false } }); index.add(0, "1 2 3 2 4 1 5 3"); index.add(1, "zero one two three four five six seven eight nine ten"); index.add(2, "four two zero one three ten five seven eight six nine"); expect(index.search("1")).to.eql([0]); expect(index.search("one")).to.eql([1, 2]); expect(index.search("one two")).to.eql([1]); // 2 => no bi-directional expect(index.search("four one")).to.eql([2]); // 1 => no bi-directional index = new Index({ tokenize: "strict", context: { depth: 3, bidirectional: true } }); index.add(0, "1 2 3 2 4 1 5 3"); index.add(1, "zero one two three four five six seven eight nine ten"); index.add(2, "five two zero one three four ten seven eight six nine"); expect(index.search("1 3 4")).to.eql([0]); expect(index.search("1 5 3 4")).to.eql([0]); expect(index.search("1 3 4 7")).to.have.lengthOf(0); expect(index.search("one")).to.eql([1, 2]); expect(index.search("one three")).to.eql([1, 2]); expect(index.search("three one")).to.eql([1, 2]); expect(index.search("zero five one ten")).to.eql([2]); expect(index.search("zero two one three two five")).to.eql([1]); expect(index.search("one zero two one zero three")).to.eql([1, 2]); // todo context chain //expect(index.search("zero two one three two five")).to.eql([1, 2]); }); }); describe("Suggestions", function(){ it("Should have been suggested properly by relevance", function(){ let index = new Index({ tokenize: "strict" }); index.add(0, "1 2 3 2 4 1 5 3"); index.add(1, "zero one two three four five six seven eight nine ten"); index.add(2, "four two zero one three ten five seven eight six nine"); expect(index.search("1 3 4 7", { suggest: false })).to.have.lengthOf(0); expect(index.search("1 3 4 7", { suggest: true })).to.eql([0]); expect(index.search("1 3 9 7", { suggest: true })).to.eql([0]); expect(index.search("foobar one two", { suggest: true })).to.eql([1, 2]); expect(index.search("foobar one four", { suggest: true })).to.eql([2, 1]); expect(index.search("one foobar two", { suggest: true })).to.eql([1, 2]); expect(index.search("one two foobar", { suggest: true })).to.eql([1, 2]); expect(index.search("zero one foobar two foobar", { suggest: true })).to.eql([1, 2]); }); it("Should have been suggested properly by context", function(){ let index = new Index({ tokenize: "strict", context: { depth: 3, bidirectional: true } }); index.add(1, "zero one two three four five six seven eight nine ten"); index.add(2, "four two zero one three ten five seven eight six nine"); expect(index.search("foobar one", { suggest: true })).to.eql([1, 2]); expect(index.search("foobar two", { suggest: true })).to.eql([2, 1]); expect(index.search("foobar foobar foobar one foobar two foobar foobar", { suggest: true })).to.eql([1, 2]); expect(index.search("foobar foobar foobar two foobar one foobar foobar", { suggest: true })).to.eql([1, 2]); expect(index.search("foobar one two", { suggest: true })).to.eql([1, 2]); expect(index.search("one foobar two", { suggest: true })).to.eql([1, 2]); expect(index.search("one two foobar", { suggest: true })).to.eql([1, 2]); expect(index.search("foobar one foobar two foobar", { suggest: true })).to.eql([1, 2]); expect(index.search("zero one foobar two foobar", { suggest: true })).to.eql([1, 2]); }); }); ================================================ FILE: test/serialize.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const build_es5 = !env || env.includes("es5"); const Charset = _Charset || (await import("../src/charset.js")).default; if(!build_light) describe("Export / Import", function(){ it("Should have been exported properly", function(){ let index = new Index({ tokenize: "forward" }); index.add(0, "foo bar foobar"); index.add(1, "bar foo foobar"); index.add(2, "foobar foo bar"); expect(index.reg.size).to.equal(3); expect(index.map.size).to.equal(8); expect(index.search("foobar")).to.eql([2, 0, 1]); const payload = new Map(); index.export(function(key, value){ payload.set(key, value); }); expect(payload).to.eql(new Map([ ['1.reg', '[0,1,2]'], ['1.map', '[["f",[[0,2],[1]]],["fo",[[0,2],[1]]],["b",[[1],[0],[2]]],["ba",[[1],[0],[2]]],["bar",[[1],[0],[2]]],["fob",[[2],null,[0,1]]],["foba",[[2],null,[0,1]]],["fobar",[[2],null,[0,1]]]]'] ])); index = new Index({ tokenize: "forward" }); for(const [key, value] of payload){ index.import(key, value); } expect(index.reg.size).to.equal(3); expect(index.map.size).to.equal(8); expect(index.search("foobar")).to.eql([2, 0, 1]); }); it("Should have been exported properly (Context)", function(){ let index = new Index({ context: true }); index.add(0, "foo bar foobar"); index.add(1, "bar foo foobar"); index.add(2, "foobar foo bar"); expect(index.reg.size).to.equal(3); expect(index.map.size).to.equal(3); expect(index.ctx.size).to.equal(2); expect(index.search("foobar")).to.eql([2, 0, 1]); const payload = new Map(); index.export(function(key, value){ payload.set(key, value); }); expect(payload).to.eql(new Map([ ['1.reg', '[0,1,2]'], ['1.map', '[["fo",[[0],[1,2]]],["bar",[[1],[0],[2]]],["fobar",[[2],null,[0,1]]]]'], ['1.ctx', '[["fo",[["bar",[[0,1],[2]]]]],["fobar",[["bar",[null,[0]]],["fo",[[2],[1]]]]]]'] ])); index = new Index({ context: true }); for(const [key, value] of payload){ index.import(key, value); } expect(index.reg.size).to.equal(3); expect(index.map.size).to.equal(3); expect(index.ctx.size).to.equal(2); expect(index.search("foobar")).to.eql([2, 0, 1]); }); it("Should have been serialized properly (Fast-Boot)", function(){ let index = new Index({ context: true, keystore: build_es5 ? 32 : 64 }); index.add(0, "foo bar foobar"); index.add(1, "bar foo foobar"); index.add(2, "foobar foo bar"); const fn_string = index.serialize(false); const inject = new Function("index", fn_string); let index2 = new Index({ context: true }); inject(index2); expect(index2.reg.size).to.equal(3); expect(index2.map.size).to.equal(3); expect(index2.ctx.size).to.equal(2); expect(normalize_map(index2.map)).to.eql(normalize_map(index.map)); expect(normalize_ctx(index2.ctx)).to.eql(normalize_ctx(index.ctx)); expect(Array.from(index2.reg.entries())).to.eql(Array.from(index.reg.entries())); expect(index2.search("foobar")).to.eql([2, 0, 1]); let index3 = new Index({ context: true }); expect(index3.serialize()).to.equal("function inject(index){}"); }); }); if(!build_light) describe("Document Export/Import", function(){ const data = [{ "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] },{ "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }]; it("Should have been exported Document-Index properly", function(){ const config = { document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward", encoder: Charset.LatinBalance },{ field: "originalTitle", tokenize: "forward", encoder: Charset.LatinBalance }], tag: [{ field: "startYear" },{ field: "genres" }] } }; let document = new Document(config); for(let i = 0; i < data.length; i++){ document.add(data[i]); } let result = document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true }); expect(result).to.eql([ { field: 'primaryTitle', result: [{ id: data[0]["tconst"], doc: data[0], }] }, { field: 'originalTitle', result: [{ id: data[0]["tconst"], doc: data[0], }] } ]); const payload = new Map(); document.export(function(key, data){ payload.set(key, data); }); document = new Document(config); for(const [key, value] of payload){ document.import(key, value); } result = document.search({ query: "karmen", tag: { "startYear": "1894", "genres": [ "Documentary", "Short" ] }, suggest: true, enrich: true }); expect(result).to.eql([ { field: 'primaryTitle', result: [{ id: data[0]["tconst"], doc: data[0], }] }, { field: 'originalTitle', result: [{ id: data[0]["tconst"], doc: data[0], }] } ]); }); }); function normalize_map(map){ return Array.from(map.entries()).map(item => { item[1].forEach((res, i) => res.length || delete item[1][i]); return item; }); } function normalize_ctx(ctx){ return Array.from(ctx.entries()).map(item => { item[1] = normalize_map(item[1]); return item; }); } ================================================ FILE: test/tokenize.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; describe("Tokenizer", function(){ it("Should have been added properly to the index: Strict", function(){ let index = new Index(/*{ tokenize: "strict" }*/); index.add(0, "björn phillipp mayer"); expect(index.search("björn phillipp")).to.include(0); expect(index.search("björn mayer")).to.include(0); index = new Index({ tokenize: "strict" }); index.add(0, "björn phillipp mayer"); expect(index.search("björn phillipp")).to.include(0); expect(index.search("björn mayer")).to.include(0); }); it("Should have been added properly to the index: Tolerant", function(){ let index = new Index({ tokenize: "tolerant" }); index.add(0, "björn phillipp mayer"); expect(index.search("björn phillipp")).to.include(0); expect(index.search("bjönr mayre")).to.include(0); expect(index.search("bjön maer")).to.include(0); expect(index.search("börn myaer")).to.include(0); }); it("Should have been added properly to the index: Forward", function(){ let index = new Index({ tokenize: "forward" }); index.add(0, "björn phillipp mayer"); expect(index.search("bjö phil may")).to.have.lengthOf(1); expect(index.search("bjö phil may")).to.include(0); }); it("Should have been added properly to the index: Reverse", function(){ let index = new Index({ tokenize: "reverse" }); index.add(0, "björn phillipp mayer"); expect(index.search("jörn phil er")).to.have.lengthOf(1); expect(index.search("jörn lipp er")).to.have.lengthOf(1); expect(index.search("jörn lipp er")).to.include(0); }); it("Should have been added properly to the index: Full", function(){ let index = new Index({ tokenize: "full" }); index.add(0, "björn phillipp mayer"); expect(index.search("jör illi may")).to.have.lengthOf(1); expect(index.search("jör illi may")).to.include(0); }); }); ================================================ FILE: test/types.ts ================================================ import { Document, Index, Worker, Resolver, Charset, IndexedDB } from "flexsearch"; import { DefaultDocumentSearchResults, StorageInterface, DefaultSearchResults, EnrichedDocumentSearchResults, MergedDocumentSearchResults, EnrichedResults, DocumentData } from "flexsearch"; test_index(); test_document(); test_persistent(); test_worker(); async function test_index() { const index = new Index(); const idx1: DefaultSearchResults = index.search({ cache: true }); const idx2: Promise = index.searchAsync({ cache: true }); const idx3: Resolver = index.search({ resolve: false }); const index2 = new Index({ resolve: false }); const idx4: Resolver = index2.search({}); const idx5: Resolver = idx3.and({}, {}).limit(100).or({}, {}).boost(2).xor({}, { resolve: false }).not({}, {}).offset(10); const idx6: DefaultSearchResults = idx5.resolve({ limit: 10, offset: 2 }); const idx7: DefaultSearchResults = idx5.and({}, { resolve: true }); const idx8: DefaultSearchResults = index.searchCache({}); const idx9: Promise = index.searchCacheAsync({}); (function(){ const api1: Index = index.clear(); const api2: boolean = index.contain(1); const api3: Index = index.add(1, ""); const api4: Index = index.remove(1); const api5: Index = index.update(1, ""); }()); const index3: Index = new Index({ db: new IndexedDB("my-store") }); const db1: Promise = index3.db; const db2: IndexedDB = await index3.db; const idx12: Promise = index3.search({ cache: true }); const idx13: Promise = index3.searchAsync({ cache: true }, function(res: DefaultSearchResults){}); const idx14: Promise = index3.commit(); const idx15: Promise = index3.mount(db2); const idx16: Promise = index3.searchCache({}); const idx17: Promise = index3.searchCacheAsync({}); const idx18: Promise = index3.search({ resolve: true, cache: true }); const idx19: Promise = index3.searchAsync({ cache: true }); const idx20: Resolver = index2.searchCache({}); const idx21: Resolver = index2.searchCacheAsync({}); const idx22: Resolver = idx5.and({ async: true }); const idx23: Promise = idx5.and({ queue: true }).resolve(); const idx24: Promise = idx5.xor({ async: true, resolve: true }); const idx25: Promise = idx5.and({}).and({ queue: true }).and({ resolve: true }); const idx26: Resolver = index3.search({ resolve: false }); const idx27: Promise = index3.search({ resolve: false }).and({}).resolve(); const idx28: Promise = index3.search({ resolve: false }).and({ resolve: true }); (function(){ const api1: Promise = index3.clear(); const api2: Promise = index3.contain(1); const api3: Index = index3.add(1, ""); const api4: Index = index3.remove(1); const api5: Index = index3.update(1, ""); }()); const index4 = await new Worker(); const idx31: Promise = index4.search({ cache: true }); const idx32: Promise = index4.searchAsync({ cache: true }, function(res: DefaultSearchResults){}); const idx33: Promise = index4.searchCache({}); const idx34: Promise = index4.searchCacheAsync({}); const idx35: Promise = index4.search({ resolve: true, cache: true }); const idx36: Promise = index4.searchAsync({ cache: true }); const idx37: Resolver = index4.search({ resolve: false }); const idx38: Promise = index4.search({ resolve: false }).and({}).resolve(); const idx39: Promise = index4.search({ resolve: false }).and({ resolve: true }); const api5: Promise = index4.clear(); const api6: boolean = index4.contain(1); const res1: Resolver = new Resolver({ index }); const res2: Resolver = res1.and({}, { index }).limit(100); const res3: DefaultSearchResults = res2.resolve(); const res4: Resolver = new Resolver({ index: index3 }); const res5: Resolver = res4.and({}); const res6: Resolver = res4.and({ index: index3 }); const res7: Promise = (await res6).limit(100).resolve(); const res8: Resolver = index3.search({ resolve: false }); const res9: Resolver = res4.and({ async: true, cache: true }); const res10: Promise = res4.or({ async: true, cache: true }).resolve(); const res11: Promise = res4.not({ queue: true, cache: true, resolve: true }); // @ts-expect-error const idx_err1 = index.search({ highlight: true }); // @ts-expect-error const idx_err2 = index.search({ pluck: true }); // @ts-expect-error const idx_err3 = index.search({ enrich: true }); // @ts-expect-error const idx_err4 = index.search({ merge: true }); // @ts-expect-error const index5 = new Index({ document: {} }); // @ts-expect-error const index6 = new Index({ worker: true }); // @ts-expect-error const idx_err5: DefaultSearchResults = idx5.resolve({}, { boost: 1 }); // @ts-expect-error const idx_err6: DefaultSearchResults = idx5.resolve({}, { enrich: true }); // @ts-expect-error const idx_err7: DefaultSearchResults = idx5.resolve({}, { highlight: true }); // @ts-expect-error const idx_err8: DefaultSearchResults = idx5.and({}, { resolve: true }).limit(100); // @ts-expect-error const idx_err9: Resolver = index2.search({ cache: true }); // @ts-expect-error const idx_err10: Promise = index2.searchAsync({ cache: true }); // @ts-expect-error const idx_err11: Resolver = index3.search({ resolve: false, cache: true }); // @ts-expect-error const idx_err12: Promise = index3.searchAsync({ resolve: false, cache: true }); // @ts-expect-deprecation const idx_err13: DefaultSearchResults = index.search("query", 100); // @ts-expect-error const idx_err14: Index = index.add(1); // @ts-expect-error const idx_err15: Index = index.add(""); // @ts-expect-error const idx_err15: Index = index.add({}); // @ts-expect-error const idx_err16: Index = index.remove(); // @ts-expect-error const idx_err17: Index = index.remove({}); } async function test_document() { type doctype = { id: number, title: string, description: string, tags: string[] }; const document = new Document({ encoder: "LatinBalance", resolution: 9, context: false, document: { id: "id", store: [ "title", "description" ], index: [ "title", "description" ], tag: [ "tags" ] }, }); type doctype2 = { id: number, meta: { title: string, description: string, tags: string[] } }; const document2 = new Document({ document: { id: "id", store: [{ field: "meta:title", filter: function(data){ return true; } },{ field: "meta:description" },{ field: "custom", custom: function(data){ return false; } }], index: [{ field: "meta:title", filter: function(data){ return true; } },{ field: "meta:description", encoder: "LatinBalance", resolution: 9, context: false, },{ field: "custom", custom: function(data){ return data.meta.title + " " + data.meta.description; } }], tag: [{ field: "meta:tags", filter: function(data){ return true; } },{ field: "custom", custom: function(data){ return "tag"; } }] }, }); const doc1: DefaultDocumentSearchResults = document.search({ cache: true }); const doc2: EnrichedDocumentSearchResults = document.search({ enrich: true }); const doc3: MergedDocumentSearchResults = document.search({ merge: true }); const doc4: EnrichedDocumentSearchResults = document.search({ highlight: { template: "" } }); const doc5: Promise = document.searchAsync({}); const doc6: DefaultSearchResults = document.search({ resolve: false }).resolve(); const doc7: DefaultDocumentSearchResults = document.search({ field: "title" }); const doc8: DefaultSearchResults = document2.search({ pluck: "meta:title" }); const doc9: DefaultDocumentSearchResults = document.searchCache({}); const doc10: Promise = document.searchAsync({ cache: true }); const doc11: Promise = document.searchCacheAsync({}); const doc13: DefaultDocumentSearchResults = document.search({ resolve: true }); const doc14: Resolver = document.search({ resolve: false }); const doc15: DefaultSearchResults = doc14.resolve({}); const doc16: DefaultSearchResults = doc14.and({ resolve: true }); const doc17: EnrichedResults = doc14.resolve({ enrich: true }); const doc18: EnrichedResults = doc14.and({ resolve: true, enrich: true }); const doc19: Resolver = doc14.and({ index: document, field: "title" }); const doc20: Resolver = doc19.or({ index: document2, pluck: "meta:title" }); const doc21: DefaultSearchResults = doc20.resolve(); const doc22: EnrichedResults = doc20.resolve({ enrich: true }); // highlight within the last resolver stage is work in progress: const doc23: EnrichedResults = doc20.and({ resolve: true, highlight: { template: "", boundary: {} } }); const doc24: Resolver = new Resolver({ index: document2 }); const doc25: EnrichedResults = doc24.and({}, { index: document, resolve: true, enrich: true }); const doc26: EnrichedResults = doc24.and({}, { index: document2 }).resolve({ enrich: true }); // highlight within the last resolver stage is work in progress: const doc27: EnrichedResults = doc24.and({}, { index: document2, resolve: true, highlight: "" }); const doc28: DefaultSearchResults = document2.search({ pluck: { field: "meta:title", limit: 10 } }); const doc30: EnrichedResults = document2.search({ highlight: true, enrich: true, pluck: { field: "meta:title"} }); const doc31: MergedDocumentSearchResults = document2.search({ highlight: true, merge: true }); const doc32: string = document.search({ highlight: true, merge: true })[0].highlight.title; const doc33: string = document2.search({ highlight: true, merge: true })[0].highlight["meta:title"]; const doc34: Resolver = document.searchCacheAsync({ resolve: false }); const doc35: Resolver = doc24.and({ async: true, cache: true }); const doc36: Promise = doc24.and({ queue: true, cache: true }).resolve(); const doc37: Promise = doc24.and({ queue: true, cache: true }).resolve({ enrich: true }); const doc38: Promise = doc24.and({ queue: true }, { enrich: true, highlight: true, resolve: true }); const doc39: Promise = doc14.or({ queue: true, cache: true, query: "", highlight: "" }).not({}).resolve(); const doc40: EnrichedResults = doc24.or({ cache: true, query: "", highlight: "" }).not({}).resolve(); const doc41: EnrichedResults = doc24.or({ cache: true, query: "" }).not({}).resolve({ highlight: "" }); (function(){ const api1: Document = document.clear(); const api2: boolean = document.contain(1); const api3: Document = document.add(1, { id: 1, tags: [""], title: "", description: "" }); const api4: Document = document.add({ id: 1, tags: [""], title: "", description: "" }); const api5: Document = document.remove(1); const api6: Document = document.remove({ id: 1, tags: [""], title: "", description: "" }); const api7: Document = document.update(1, { id: 1, tags: [""], title: "", description: "" }); const api8: Document = document.update({ id: 1, tags: [""], title: "", description: "" }); }()); //const err0: EnrichedResults = doc24.resolve({ highlight: "" }); // @ts-expect-error const err1: DocumentData = doc1[0].result[0].doc; const err2: DocumentData = doc2[0].result[0].doc; const err3: DocumentData = doc3[0].doc; // @ts-expect-error const err4: DefaultSearchResults = document2.search({ pluck: "title" }); // @ts-expect-error const err5: DefaultSearchResults = document.search("test", {}); // @ts-expect-error const err6: Resolver = document.search({}); // @ts-expect-error const err7: Resolver = document.search({ resolve: true }); // @ts-expect-error const err8: DefaultDocumentSearchResults = document.searchAsync({}); // @ts-expect-error const err9: DefaultDocumentSearchResults = await document.searchAsync({ pluck: "title" }); // @ts-expect-error const err10: DefaultDocumentSearchResults = await document.searchAsync({ enrich: true }); // @ts-expect-error const err11: EnrichedDocumentSearchResults = document.search({ highlight: {} }); // @ts-expect-error const err12: string = document2.search({ highlight: true, merge: true })[0].highlight; // @ts-expect-error const err13: string = document.search({ highlight: true, merge: true })[0].highlight.title2; } async function test_persistent() { type doctype = { id: number, title: string, description: string, tags: string[] }; const document = new Document({ db: new IndexedDB("my-store"), encoder: Charset.LatinBalance, resolution: 9, context: false, document: { id: "id", store: [ "title", "description" ], index: [ "title", "description" ], tag: [ "tags" ] }, }); await document.mount(new IndexedDB("my-store")); type doctype2 = { id: number, meta: { title: string, description: string, tags: string[] } }; const document2 = new Document({ db: new IndexedDB("my-store"), encoder: Charset.Normalize, document: { id: "id", store: [{ field: "meta:title", filter: function(data){ return true; } },{ field: "meta:description" },{ field: "custom", custom: function(data){ return false; } }], index: [{ field: "meta:title", filter: function(data){ return true; } },{ field: "meta:description", encoder: "LatinBalance", resolution: 9, context: false, },{ field: "custom", custom: function(data){ return data.meta.title + " " + data.meta.description; } }], tag: [{ field: "meta:tags", filter: function(data){ return true; } },{ field: "custom", custom: function(data){ return "tag"; } }] }, }); await document2.mount(new IndexedDB("my-store")); const doc0: Promise = document.search({ cache: true }); const doc1: DefaultDocumentSearchResults = await doc0; const doc2: Promise = document.search({ enrich: true }); const doc3: Promise = document.search({ merge: true }); const doc4: Promise = document.search({ highlight: { template: "" } }); const doc5: Promise = document.searchAsync({}); const doc6: Promise = (await document.search({ resolve: false })).resolve(); const doc7: Promise = document.search({ field: "title" }); const doc8: Promise = document2.search({ pluck: "meta:title" }); const doc9: Promise = document.searchCache({}); const doc10: Promise = document.searchAsync({ cache: true }); const doc11: Promise = document.searchCacheAsync({}); const doc13: Promise = document.search({ resolve: true }); const doc14: Resolver = document.search({ resolve: false }); const doc15: Promise = doc14.resolve({}); const doc16: Promise = doc14.and({ resolve: true }); const doc17: Promise = doc14.resolve({ enrich: true }); const doc18: Promise = doc14.and({ resolve: true, enrich: true }); const doc19: Resolver = doc14.and({ index: document, field: "title" }); const doc20: Resolver = doc19.or({ index: document2, pluck: "meta:title" }); const doc21: Promise = doc20.resolve(); const doc22: Promise = doc20.resolve({ enrich: true }); // highlight within the last resolver stage is work in progress: const doc23: Promise = doc20.and({ resolve: true, highlight: { template: "", boundary: {} } }); const doc24: Resolver = new Resolver({ index: document2 }); const doc25: Promise = doc24.and({}, { index: document, resolve: true, enrich: true }); const doc26: Promise = doc24.and({}, { index: document2 }).resolve({ enrich: true }); // highlight within the last resolver stage is work in progress: const doc27: Promise = doc24.and({}, { index: document2, resolve: true, highlight: "" }); const doc28: Promise = document2.search({ pluck: { field: "meta:title", limit: 10 } }); const doc30: Promise = document2.search({ highlight: true, enrich: true, pluck: { field: "meta:title"} }); const doc31: Promise = document2.search({ highlight: true, merge: true }); const doc32: string = (await document.search({ highlight: true, merge: true }))[0].highlight.title; const doc33: string = (await document2.search({ highlight: true, merge: true }))[0].highlight["meta:title"]; const doc34: Resolver = document.searchCacheAsync({ resolve: false }); const doc35: Resolver = doc24.and({ async: true, cache: true }); const doc36: Promise = doc24.and({ queue: true, cache: true }).resolve(); const doc37: Promise = doc24.and({ queue: true, cache: true }).resolve({ enrich: true }); const doc38: Promise = doc24.and({ queue: true }, { enrich: true, highlight: true, resolve: true }); const doc39: Promise = doc14.or({ queue: true, cache: true, query: "", highlight: "" }).not({}).resolve(); const doc40: Promise = doc24.or({ cache: true, query: "", highlight: "" }).not({}).resolve(); const doc41: Promise = doc24.or({ cache: true, query: "" }).not({}).resolve({ highlight: "" }); const doc42: Resolver = doc24.and({ cache: true }); const doc43: Promise = doc24.and({ cache: true }).resolve(); const doc44: Promise = doc24.and({ cache: true }).resolve({ enrich: true }); const doc45: Promise = doc24.and({}, { enrich: true, highlight: true, resolve: true }); (function(){ const api1: Promise = document.clear(); const api2: Promise = document.contain(1); const api3: Document = document.add(1, { id: 1, tags: [""], title: "", description: "" }); const api4: Document = document.add({ id: 1, tags: [""], title: "", description: "" }); const api5: Document = document.remove(1); const api6: Document = document.remove({ id: 1, tags: [""], title: "", description: "" }); const api7: Document = document.update(1, { id: 1, tags: [""], title: "", description: "" }); const api8: Document = document.update({ id: 1, tags: [""], title: "", description: "" }); const api9: Promise = document.commit(); }()); // @ts-expect-error const err1: DocumentData = doc1[0].result[0].doc; const err2: DocumentData = doc2[0].result[0].doc; const err3: DocumentData = doc3[0].doc; // @ts-expect-error const err4: DefaultSearchResults = document2.search({ pluck: "title" }); // @ts-expect-error const err5: DefaultSearchResults = document.search("test", {}); // @ts-expect-error const err6: Resolver = document.search({}); // @ts-expect-error const err7: Resolver = document.search({ resolve: true }); // @ts-expect-error const err8: DefaultDocumentSearchResults = document.searchAsync({}); // @ts-expect-error const err9: DefaultDocumentSearchResults = await document.searchAsync({ pluck: "title" }); // @ts-expect-error const err10: DefaultDocumentSearchResults = await document.searchAsync({ enrich: true }); // @ts-expect-error const err11: EnrichedDocumentSearchResults = document.search({ highlight: {} }); // @ts-expect-error const err12: string = (await document2.search({ highlight: true, merge: true }))[0].highlight; // @ts-expect-error const err13: string = (await document.search({ highlight: true, merge: true }))[0].highlight.title2; // @ts-expect-error const err14: EnrichedResults = doc24.resolve({ enrich: true }); } async function test_worker() { type doctype = { id: number, title: string, description: string, tags: string[] }; const document = new Document({ worker: true, encoder: "LatinBalance", resolution: 9, context: false, document: { id: "id", store: [ "title", "description" ], index: [ "title", "description" ], tag: [ "tags" ] }, }); type doctype2 = { id: number, meta: { title: string, description: string, tags: string[] } }; const document2 = new Document({ worker: true, document: { id: "id", store: [{ field: "meta:title", filter: function(data){ return true; } },{ field: "meta:description" },{ field: "custom", custom: function(data){ return false; } }], index: [{ field: "meta:title", filter: function(data){ return true; } },{ field: "meta:description", encoder: "LatinBalance", resolution: 9, context: false, },{ field: "custom", custom: function(data){ return data.meta.title + " " + data.meta.description; } }], tag: [{ field: "meta:tags", filter: function(data){ return true; } },{ field: "custom", custom: function(data){ return "tag"; } }] }, }); const doc0: Promise = document.search({ cache: true }); const doc1: DefaultDocumentSearchResults = await doc0; const doc2: Promise = document.search({ enrich: true }); const doc3: Promise = document.search({ merge: true }); const doc4: Promise = document.search({ highlight: { template: "" } }); const doc5: Promise = document.searchAsync({}); const doc6: Promise = (await document.search({ resolve: false })).resolve(); const doc7: Promise = document.search({ field: "title" }); const doc8: Promise = document2.search({ pluck: "meta:title" }); const doc9: Promise = document.searchCache({}); const doc10: Promise = document.searchAsync({ cache: true }); const doc11: Promise = document.searchCacheAsync({}); const doc13: Promise = document.search({ resolve: true }); const doc14: Resolver = document.search({ resolve: false }); const doc15: Promise = doc14.resolve({}); const doc16: Promise = doc14.and({ resolve: true }); const doc17: Promise = doc14.resolve({ enrich: true }); const doc18: Promise = doc14.and({ resolve: true, enrich: true }); const doc19: Resolver = doc14.and({ index: document, field: "title" }); const doc20: Resolver = doc19.or({ index: document2, pluck: "meta:title" }); const doc21: Promise = doc20.resolve(); const doc22: Promise = doc20.resolve({ enrich: true }); // highlight within the last resolver stage is work in progress: const doc23: Promise = doc20.and({ resolve: true, highlight: { template: "", boundary: {} } }); const doc24: Resolver = new Resolver({ index: document2 }); const doc25: Promise = doc24.and({}, { index: document, resolve: true, enrich: true }); const doc26: Promise = doc24.and({}, { index: document2 }).resolve({ enrich: true }); // highlight within the last resolver stage is work in progress: const doc27: Promise = doc24.and({}, { index: document2, resolve: true, highlight: "" }); const doc28: Promise = document2.search({ pluck: { field: "meta:title", limit: 10 } }); const doc30: Promise = document2.search({ highlight: true, enrich: true, pluck: { field: "meta:title"} }); const doc31: Promise = document2.search({ highlight: true, merge: true }); const doc32: string = (await document.search({ highlight: true, merge: true }))[0].highlight.title; const doc33: string = (await document2.search({ highlight: true, merge: true }))[0].highlight["meta:title"]; const doc34: Resolver = document.searchCacheAsync({ resolve: false }); const doc35: Resolver = doc24.and({ async: true, cache: true }); const doc36: Promise = doc24.and({ queue: true, cache: true }).resolve(); const doc37: Promise = doc24.and({ queue: true, cache: true }).resolve({ enrich: true }); const doc38: Promise = doc24.and({ queue: true }, { enrich: true, highlight: true, resolve: true }); const doc39: Promise = doc14.or({ queue: true, cache: true, query: "", highlight: "" }).not({}).resolve(); const doc40: Promise = doc24.or({ cache: true, query: "", highlight: "" }).not({}).resolve(); const doc41: Promise = doc24.or({ cache: true, query: "" }).not({}).resolve({ highlight: "" }); const doc42: Resolver = doc24.and({ cache: true }); const doc43: Promise = doc24.and({ cache: true }).resolve(); const doc44: Promise = doc24.and({ cache: true }).resolve({ enrich: true }); const doc45: Promise = doc24.and({}, { enrich: true, highlight: true, resolve: true }); (function(){ const api1: Promise = document.clear(); const api2: boolean = document.contain(1); const api3: Promise> = document.add(1, { id: 1, tags: [""], title: "", description: "" }); const api4: Promise> = document.add({ id: 1, tags: [""], title: "", description: "" }); const api5: Promise> = document.remove(1); const api6: Promise> = document.remove({ id: 1, tags: [""], title: "", description: "" }); const api7: Promise> = document.update(1, { id: 1, tags: [""], title: "", description: "" }); const api8: Promise> = document.update({ id: 1, tags: [""], title: "", description: "" }); }()); // @ts-expect-error const err1: DocumentData = doc1[0].result[0].doc; const err2: DocumentData = doc2[0].result[0].doc; const err3: DocumentData = doc3[0].doc; // @ts-expect-error const err4: DefaultSearchResults = document2.search({ pluck: "title" }); // @ts-expect-error const err5: DefaultSearchResults = document.search("test", {}); // @ts-expect-error const err6: Resolver = document.search({}); // @ts-expect-error const err7: Resolver = document.search({ resolve: true }); // @ts-expect-error const err8: DefaultDocumentSearchResults = document.searchAsync({}); // @ts-expect-error const err9: DefaultDocumentSearchResults = await document.searchAsync({ pluck: "title" }); // @ts-expect-error const err10: DefaultDocumentSearchResults = await document.searchAsync({ enrich: true }); // @ts-expect-error const err11: EnrichedDocumentSearchResults = document.search({ highlight: {} }); // @ts-expect-error const err12: string = (await document2.search({ highlight: true, merge: true }))[0].highlight; // @ts-expect-error const err13: string = (await document.search({ highlight: true, merge: true }))[0].highlight.title2; // @ts-expect-error const err14: EnrichedResults = doc24.resolve({ enrich: true }); } ================================================ FILE: test/worker.js ================================================ global.self = global; const env = process.argv[process.argv.length - 1] === "--exit" ? "" : process.argv[process.argv.length - 1]; import { expect } from "chai"; let FlexSearch = await import(env ? "../dist/" + env + ".js" : "../src/bundle.js"); if(FlexSearch.default) FlexSearch = FlexSearch.default; if(FlexSearch.FlexSearch) FlexSearch = FlexSearch.FlexSearch; const { Index, Document, Worker: WorkerIndex, Charset: _Charset, Encoder, Resolver } = FlexSearch; const build_light = env && env.includes("light"); const build_compact = env && env.includes("compact"); const build_esm = !env || env.startsWith("module"); const Charset = _Charset || (await import("../src/charset.js")).default; if(!build_light && !build_compact) describe("Worker", function(){ let index; afterEach(function() { index && index.worker.terminate(); }); it("Should have the proper basic functionality", async function(){ index = await new WorkerIndex({ encoder: "LatinAdvanced", tokenize: "forward" }); const data = [ 'cats abcd efgh ijkl mnop qrst uvwx cute', 'cats abcd efgh ijkl mnop dogs cute', 'cats abcd efgh ijkl mnop cute', 'cats abcd efgh ijkl cute', 'cats abcd efgh cute', 'cats abcd cute', 'cats cute' ]; for(let i = 0; i < data.length; i++){ await index.addAsync(i, data[i]); } expect(index.reg).to.be.undefined; expect(index.map).to.be.undefined; let result = await index.search("cat cute"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = await index.search("cute cat"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = await index.search("cute"); expect(result).to.eql([6, 5, 4, 3, 2, 1, 0]); result = await index.search("cudi tok-kat"); expect(result).to.eql([1]); }); it("Should update the index contents properly", async function(){ index = await new WorkerIndex({ tokenize: "full" }); await index.add(1, "foo"); await index.add(2, "bar"); await index.add(3, "foobar"); await index.update(1, "bar"); await index.update(2, "foobar"); await index.update(3, "foo"); expect(await index.search("foo")).to.have.members([2, 3]); expect(await index.search("bar")).to.have.members([1, 2]); expect(await index.search("bar")).to.not.include(3); expect(await index.search("foobar")).to.have.members([2]); expect(await index.search("oba")).to.have.members([2]); await index.update(1, "bar"); await index.update(2, "foobar"); await index.update(3, "foo"); expect(await index.search("foo")).to.have.members([2, 3]); expect(await index.search("bar")).to.have.members([1, 2]); expect(await index.search("bar")).to.not.include(3); expect(await index.search("foobar")).to.have.members([2]); expect(await index.search("oba")).to.have.members([2]); }); it("Should have been removed from the index", async function(){ index = await new WorkerIndex({ tokenize: "full" }); await index.add(1, "bar"); await index.add(2, "foobar"); await index.add(3, "foo"); await index.remove(2); await index.remove(1); await index.remove(3); await index.remove(4); expect(await index.search("foo")).to.have.lengthOf(0); expect(await index.search("bar")).to.have.lengthOf(0); expect(await index.search("foobar")).to.have.lengthOf(0); }); it("Result Highlighting", async function(){ // some test data const data = [{ "id": 1, "title": "Carmencita" },{ "id": 2, "title": "Le clown et ses chiens" }]; // create the document index const document = await new Document({ worker: true, document: { store: true, index: [{ field: "title", tokenize: "forward", encoder: Charset.LatinBalance }] } }); // add test data for(let i = 0; i < data.length; i++){ await document.add(data[i]); } // perform a query let result = await document.searchCache({ query: "karmen or clown or not found", suggest: true, highlight: "$1" }); expect(result[0].result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' },{ id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); // perform a query on cache result = await document.searchCache({ query: "karmen or clown or not found", suggest: true, highlight: "$1" }); expect(result[0].result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' }, { id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); // perform a query using pluck result = await document.search({ query: "karmen or clown or not found", suggest: true, field: "title", highlight: "$1" }); expect(result[0].result).to.eql([{ id: 1, doc: data[0], highlight: 'Carmencita' },{ id: 2, doc: data[1], highlight: 'Le clown et ses chiens' }]); }); it("Should have been resolved a Resolver properly (Document Worker)", async function(){ const data = [{ "tconst": "tt0000001", "titleType": "short", "primaryTitle": "Carmencita", "originalTitle": "Carmencita", "isAdult": 0, "startYear": "1894", "endYear": "", "runtimeMinutes": "1", "genres": [ "Documentary", "Short" ] },{ "tconst": "tt0000002", "titleType": "short", "primaryTitle": "Le clown et ses chiens", "originalTitle": "Le clown et ses chiens", "isAdult": 0, "startYear": "1892", "endYear": "", "runtimeMinutes": "5", "genres": [ "Animation", "Short" ] }]; // create the document index const document = await new Document({ worker: true, encoder: Charset.LatinBalance, document: { id: "tconst", store: true, index: [{ field: "primaryTitle", tokenize: "forward" },{ field: "originalTitle", tokenize: "forward" }], tag: [{ field: "startYear" },{ field: "genres" }] } }); // add test data for(let i = 0; i < data.length; i++){ await document.addAsync(data[i]); } let resolver = new Resolver({ index: document, query: "karmen or clown or nothing", field: "primaryTitle", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); let tmp = resolver.await; resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.have.members(["tt0000001", "tt0000002"]); expect((await tmp)[0]).to.have.members(["tt0000001"]); // ----------------------------------- resolver = new Resolver({ index: document, async: true, query: "karmen or clown or nothing", field: "primaryTitle", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); tmp = resolver.await; resolver = resolver.resolve({ enrich: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([{ id: data[0].tconst, doc: data[0] }, { id: data[1].tconst, doc: data[1] }]); expect((await tmp)[0]).to.have.members(["tt0000001"]); // ----------------------------------- resolver = new Resolver({ index: document, async: true, query: "karmen or clown or nothing", field: "primaryTitle", suggest: true }).or({ index: document, queue: true, query: "karmen or clown or nothing", pluck: "primaryTitle", suggest: true, enrich: true, resolve: true }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql([{ id: data[0].tconst, doc: data[0] }, { id: data[1].tconst, doc: data[1] }]); // ----------------------------------- resolver = new Resolver({ index: document, async: true, query: "karmen or clown or nothing", pluck: "primaryTitle", suggest: true }); expect(resolver).to.be.instanceof(Resolver); expect(resolver.result).to.eql([]); expect(resolver.await).to.be.instanceof(Promise); resolver = resolver.resolve({ limit: 1, offset: 1 }); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql(["tt0000002"]); // ----------------------------------- resolver = new Resolver({ index: document, async: true, query: "karmen", pluck: "primaryTitle" }).or({ queue: true, cache: true, query: "clown", field: "originalTitle" }).and({ async: true, query: "not found", pluck: "originalTitle", suggest: true }); expect(resolver).to.be.instanceof(Resolver); resolver = resolver.resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql(["tt0000001", "tt0000002"]); // ----------------------------------- resolver = new Resolver({ index: document, async: true, cache: true, query: "karmen", pluck: "primaryTitle" }).or({ and: [{ async: true, cache: true, query: "not found", pluck: "originalTitle", suggest: true },{ queue: true, cache: true, query: "clown", field: "originalTitle", suggest: true }] }).resolve(); expect(resolver).to.be.instanceof(Promise); expect(await resolver).to.eql(["tt0000001", "tt0000002"]); }); });