Giter VIP home page Giter VIP logo

laravel-scout-typesense-driver's Introduction

Typesense

Typesense is a fast, typo-tolerant search engine for building delightful search experiences.

An Open Source Algolia Alternative &
An Easier-to-Use ElasticSearch Alternative


Website | Documentation | Roadmap | Slack Community | Community Threads | Twitter


Typesense Demo

✨ Here are a couple of live demos that show Typesense in action on large datasets:

🗣️ 🎥 If you prefer watching videos:

Quick Links

Features

  • Typo Tolerance: Handles typographical errors elegantly, out-of-the-box.
  • Simple and Delightful: Simple to set-up, integrate with, operate and scale.
  • ⚡ Blazing Fast: Built in C++. Meticulously architected from the ground-up for low-latency (<50ms) instant searches.
  • Tunable Ranking: Easy to tailor your search results to perfection.
  • Sorting: Dynamically sort results based on a particular field at query time (helpful for features like "Sort by Price (asc)").
  • Faceting & Filtering: Drill down and refine results.
  • Grouping & Distinct: Group similar results together to show more variety.
  • Federated Search: Search across multiple collections (indices) in a single HTTP request.
  • Geo Search: Search and sort by results around a latitude/longitude or within a bounding box.
  • Vector Search: Index embeddings from your machine learning models in Typesense and do a nearest-neighbor search. Can be used to build similarity search, semantic search, visual search, recommendations, etc.
  • Semantic / Hybrid Search: Automatically generate embeddings from within Typesense using built-in models like S-BERT, E-5, etc or use OpenAI, PaLM API, etc, for both queries and indexed data. This allows you to send JSON data into Typesense and build an out-of-the-box semantic search + keyword search experience.
  • Conversational Search (Built-in RAG): Send questions to Typesense and have the response be a fully-formed sentence, based on the data you've indexed in Typesense. Think ChatGPT, but over your own data.
  • Image Search: Search through images using text descriptions of their contents, or perform similarity searches, using the CLIP model.
  • Voice Search: Capture and send query via voice recordings - Typesense will transcribe (via Whisper model) and provide search results.
  • Scoped API Keys: Generate API keys that only allow access to certain records, for multi-tenant applications.
  • JOINs: Connect one or more collections via common reference fields and join them during query time. This allows you to model SQL-like relationships elegantly.
  • Synonyms: Define words as equivalents of each other, so searching for a word will also return results for the synonyms defined.
  • Curation & Merchandizing: Boost particular records to a fixed position in the search results, to feature them.
  • Raft-based Clustering: Setup a distributed cluster that is highly available.
  • Seamless Version Upgrades: As new versions of Typesense come out, upgrading is as simple as swapping out the binary and restarting Typesense.
  • No Runtime Dependencies: Typesense is a single binary that you can run locally or in production with a single command.

Don't see a feature on this list? Search our issue tracker if someone has already requested it and add a comment to it explaining your use-case, or open a new issue if not. We prioritize our roadmap based on user feedback, so we'd love to hear from you.

Roadmap

Here's Typesense's public roadmap: https://github.com/orgs/typesense/projects/1.

The first column also explains how we prioritize features, how you can influence prioritization and our release cadence.

Benchmarks

  • A dataset containing 2.2 Million recipes (recipe names and ingredients):
    • Took up about 900MB of RAM when indexed in Typesense
    • Took 3.6mins to index all 2.2M records
    • On a server with 4vCPUs, Typesense was able to handle a concurrency of 104 concurrent search queries per second, with an average search processing time of 11ms.
  • A dataset containing 28 Million books (book titles, authors and categories):
    • Took up about 14GB of RAM when indexed in Typesense
    • Took 78mins to index all 28M records
    • On a server with 4vCPUs, Typesense was able to handle a concurrency of 46 concurrent search queries per second, with an average search processing time of 28ms.
  • With a dataset containing 3 Million products (Amazon product data), Typesense was able to handle a throughput of 250 concurrent search queries per second on an 8-vCPU 3-node Highly Available Typesense cluster.

We'd love to benchmark with larger datasets, if we can find large ones in the public domain. If you have any suggestions for structured datasets that are open, please let us know by opening an issue. We'd also be delighted if you're able to share benchmarks from your own large datasets. Please send us a PR!

Who's using this?

Typesense is used by a range of users across different domains and verticals.

On Typesense Cloud we serve more than 3 BILLION searches per month. Typesense's Docker images have been downloaded over 12M times.

We've recently started documenting who's using it in our Showcase. If you'd like to be included in the list, please feel free to edit SHOWCASE.md and send us a PR.

You'll also see a list of user logos on the Typesense Cloud home page.

Install

Option 1: You can download the binary packages that we publish for Linux (x86_64 & arm64) and Mac (x86_64).

Option 2: You can also run Typesense from our official Docker image.

Option 3: Spin up a managed cluster with Typesense Cloud:

Deploy with Typesense Cloud

Quick Start

Here's a quick example showcasing how you can create a collection, index a document and search it on Typesense.

Let's begin by starting the Typesense server via Docker:

docker run -p 8108:8108 -v/tmp/data:/data typesense/typesense:26.0 --data-dir /data --api-key=Hu52dwsas2AdxdE

We have API Clients in a couple of languages, but let's use the Python client for this example.

Install the Python client for Typesense:

pip install typesense

We can now initialize the client and create a companies collection:

import typesense

client = typesense.Client({
  'api_key': 'Hu52dwsas2AdxdE',
  'nodes': [{
    'host': 'localhost',
    'port': '8108',
    'protocol': 'http'
  }],
  'connection_timeout_seconds': 2
})

create_response = client.collections.create({
  "name": "companies",
  "fields": [
    {"name": "company_name", "type": "string" },
    {"name": "num_employees", "type": "int32" },
    {"name": "country", "type": "string", "facet": True }
  ],
  "default_sorting_field": "num_employees"
})

Now, let's add a document to the collection we just created:

document = {
 "id": "124",
 "company_name": "Stark Industries",
 "num_employees": 5215,
 "country": "USA"
}

client.collections['companies'].documents.create(document)

Finally, let's search for the document we just indexed:

search_parameters = {
  'q'         : 'stork',
  'query_by'  : 'company_name',
  'filter_by' : 'num_employees:>100',
  'sort_by'   : 'num_employees:desc'
}

client.collections['companies'].documents.search(search_parameters)

Did you notice the typo in the query text? No big deal. Typesense handles typographic errors out-of-the-box!

Step-by-step Walk-through

A step-by-step walk-through is available on our website here.

This will guide you through the process of starting up a Typesense server, indexing data in it and querying the data set.

API Documentation

Here's our official API documentation, available on our website: https://typesense.org/api.

If you notice any issues with the documentation or walk-through, please let us know or send us a PR here: https://github.com/typesense/typesense-website.

API Clients

While you can definitely use CURL to interact with Typesense Server directly, we offer official API clients to simplify using Typesense from your language of choice. The API Clients come built-in with a smart retry strategy to ensure that API calls made via them are resilient, especially in an HA setup.

If we don't offer an API client in your language, you can still use any popular HTTP client library to access Typesense's APIs directly.

Here are some community-contributed clients and integrations:

We welcome community contributions to add more official client libraries and integrations. Please reach out to us at [email protected] or open an issue on GitHub to collaborate with us on the architecture. 🙏

Framework Integrations

We also have the following framework integrations:

Postman Collection

We have a community-maintained Postman Collection here: https://github.com/typesense/postman.

Postman is an app that let's you perform HTTP requests by pointing and clicking, instead of having to type them out in the terminal. The Postman Collection above gives you template requests that you can import into Postman, to quickly make API calls to Typesense.

Search UI Components

You can use our InstantSearch.js adapter to quickly build powerful search experiences, complete with filtering, sorting, pagination and more.

Here's how: https://typesense.org/docs/guide/search-ui-components.html

FAQ

How does this differ from Elasticsearch?

Elasticsearch is a large piece of software, that takes non-trivial amount of effort to setup, administer, scale and fine-tune. It offers you a few thousand configuration parameters to get to your ideal configuration. So it's better suited for large teams who have the bandwidth to get it production-ready, regularly monitor it and scale it, especially when they have a need to store billions of documents and petabytes of data (eg: logs).

Typesense is built specifically for decreasing the "time to market" for a delightful search experience. It's a light-weight yet powerful & scaleable alternative that focuses on Developer Happiness and Experience with a clean well-documented API, clear semantics and smart defaults so it just works well out-of-the-box, without you having to turn many knobs.

Elasticsearch also runs on the JVM, which by itself can be quite an effort to tune to run optimally. Typesense, on the other hand, is a single light-weight self-contained native binary, so it's simple to setup and operate.

See a side-by-side feature comparison here.

How does this differ from Algolia?

Algolia is a proprietary, hosted, search-as-a-service product that works well, when cost is not an issue. From our experience, fast growing sites and apps quickly run into search & indexing limits, accompanied by expensive plan upgrades as they scale.

Typesense on the other hand is an open-source product that you can run on your own infrastructure or use our managed SaaS offering - Typesense Cloud. The open source version is free to use (besides of course your own infra costs). With Typesense Cloud we don't charge by records or search operations. Instead, you get a dedicated cluster and you can throw as much data and traffic at it as it can handle. You only pay a fixed hourly cost & bandwidth charges for it, depending on the configuration your choose, similar to most modern cloud platforms.

From a product perspective, Typesense is closer in spirit to Algolia than Elasticsearch. However, we've addressed some important limitations with Algolia:

Algolia requires separate indices for each sort order, which counts towards your plan limits. Most of the index settings like fields to search, fields to facet, fields to group by, ranking settings, etc are defined upfront when the index is created vs being able to set them on the fly at query time.

With Typesense, these settings can be configured at search time via query parameters which makes it very flexible and unlocks new use cases. Typesense is also able to give you sorted results with a single index, vs having to create multiple. This helps reduce memory consumption.

Algolia offers the following features that Typesense does not have currently: personalization & server-based search analytics. For analytics, you can still instrument your search on the client-side and send search metrics to your web analytics tool of choice.

We intend to bridge this gap in Typesense, but in the meantime, please let us know if any of these are a show stopper for your use case by creating a feature request in our issue tracker.

See a side-by-side feature comparison here.

Speed is great, but what about the memory footprint?

A fresh Typesense server will consume about 30 MB of memory. As you start indexing documents, the memory use will increase correspondingly. How much it increases depends on the number and type of fields you index.

We've strived to keep the in-memory data structures lean. To give you a rough idea: when 1 million Hacker News titles are indexed along with their points, Typesense consumes 165 MB of memory. The same size of that data on disk in JSON format is 88 MB. If you have any numbers from your own datasets that we can add to this section, please send us a PR!

Why the GPL license?

From our experience companies are generally concerned when libraries they use are GPL licensed, since library code is directly integrated into their code and will lead to derivative work and trigger GPL compliance. However, Typesense Server is server software and we expect users to typically run it as a separate daemon, and not integrate it with their own code. GPL covers and allows for this use case generously (eg: Linux is GPL licensed). Now, AGPL is what makes server software accessed over a network result in derivative work and not GPL. And for that reason we’ve opted to not use AGPL for Typesense.

Now, if someone makes modifications to Typesense server, GPL actually allows you to still keep the modifications to yourself as long as you don't distribute the modified code. So a company can for example modify Typesense server and run the modified code internally and still not have to open source their modifications, as long as they make the modified code available to everyone who has access to the modified software.

Now, if someone makes modifications to Typesense server and distributes the modifications, that's where GPL kicks in. Given that we’ve published our work to the community, we'd like for others' modifications to also be made open to the community in the spirit of open source. We use GPL for this purpose. Other licenses would allow our open source work to be modified, made closed source and distributed, which we want to avoid with Typesense for the project’s long term sustainability.

Here's more background on why GPL, as described by Discourse: https://meta.discourse.org/t/why-gnu-license/2531. Many of the points mentioned there resonate with us.

Now, all of the above only apply to Typesense Server. Our client libraries are indeed meant to be integrated into our users’ code and so they use Apache license.

So in summary, AGPL is what is usually problematic for server software and we’ve opted not to use it. We believe GPL for Typesense Server captures the essence of what we want for this open source project. GPL has a long history of successfully being used by popular open source projects. Our libraries are still Apache licensed.

If you have specifics that prevent you from using Typesense due to a licensing issue, we're happy to explore this topic further with you. Please reach out to us.

Support

👋 🌐 If you have general questions about Typesense, want to say hello or just follow along, we'd like to invite you to join our public Slack Community.

If you run into any problems or issues, please create a GitHub issue and we'll try our best to help.

We strive to provide good support through our issue trackers on GitHub. However, if you'd like to receive private & prioritized support with:

  • Guaranteed SLAs
  • Phone / video calls to discuss your specific use case and get recommendations on best practices
  • Private discussions over Slack
  • Guidance around scaling best practices
  • Prioritized feature requests

We offer Paid Support options described here.

Contributing

We are a lean team on a mission to democratize search and we'll take all the help we can get! If you'd like to get involved, here's information on where we could use your help: Contributing.md

Getting Latest Updates

If you'd like to get updates when we release new versions, click on the "Watch" button on the top and select "Releases only". GitHub will then send you notifications along with a changelog with each new release.

We also post updates to our Twitter account about releases and additional topics related to Typesense. Follow us here: @typesense.

👋 🌐 We'll also post updates on our Slack Community.

Build from source

We use Bazel to build Typesense.

Typesense requires the following dependencies:

  • C++11 compatible compiler (GCC >= 4.9.0, Apple Clang >= 8.0, Clang >= 3.9.0)
  • Snappy
  • zlib
  • OpenSSL (>=1.0.2)
  • curl
  • ICU

Please refer to the CI build steps for the latest set of dependencies.

Once you've installed them, run the following from the root of the repo:

bazel build //:typesense-server

The first build will take some time since other third-party libraries are pulled and built as part of the build process.


© 2016-present Typesense Inc.

laravel-scout-typesense-driver's People

Contributors

abdullahfaqeir avatar alexgschwend avatar arayiksmbatyan avatar bepsvpt avatar cephee avatar codacy-badger avatar felipehertzer avatar hi019 avatar jasonbosco avatar karakhanyans avatar laravel-shift avatar manavo avatar peterfox avatar wh5938316 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-scout-typesense-driver's Issues

Error importing document: Field `itemStocks` not found.

if ($this->itemStocks->isNotEmpty()) {
$extraFields['itemStocks'] = $this->itemStocks->map(function ($itemStock) {
return [
'available' => (int)$itemStock->available,
'stock_unit_code' => (string)$itemStock->stock_unit_code,
'source_warehouse_id' => (int)$itemStock->source_warehouse_id
];
})->toArray();
}

When im trying to import object like this i get error Error importing document: Field itemStocks not found.

How to fix it? I declared itemStocks in schema and gived type object[]

Laravel 10 Support

Description

Currently the package doesn't support Laravel 10

Steps to reproduce

Install typesense driver package in laravel 10 and I can't run composer update

Expected Behavior

Should be able to work in Laravel 10

Metadata

Typesense Version: 4

OS: MacOS

Undefined array key "code" error in Typesense.php:170 when updating searchable model

Description

When updating a searchable attribute for a searchable model, the searchable() update throws an error.

This issue occurs on the typesense driver 5.0.1 but not on 5.0.0.

The culprit seems to be in the Typesense\LaravelTypesense\Typesense class, in the importDocuments method, when the import method response structure does not match the looped check.

Is somehow the 5.0.1 version expected to work with a newer server version?

Steps to reproduce

To reproduce, update any searchable attribute for a searchable model.

Expected Behavior

Model updated/uploaded successfully to the typesense server, without errors.

Actual Behavior

Model updated/uploaded successfully to the typesense server, WITH an error.

[2021-12-03 14:53:48] local.ERROR: Undefined array key "code" {"userId":2,"exception":"[object] (ErrorException(code: 0): Undefined array key \"code\" at /home/bravo/_work/_proj/rolcrispa/vendor/typesense/laravel-scout-typesense-driver/src/Typesense.php:170)
[stacktrace]
#0 /home/redacted/vendor/typesense/laravel-scout-typesense-driver/src/Typesense.php(170): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError()
#1 /home/redacted/vendor/typesense/laravel-scout-typesense-driver/src/Engines/TypesenseEngine.php(86): Typesense\\LaravelTypesense\\Typesense->importDocuments()
#2 /home/redacted/vendor/laravel/scout/src/Searchable.php(62): Typesense\\LaravelTypesense\\Engines\\TypesenseEngine->update()
#3 /home/redacted/vendor/laravel/scout/src/Searchable.php(41): LaravelEnso\\Products\\Models\\Product->queueMakeSearchable()
#4 /home/redacted/vendor/laravel/framework/src/Illuminate/Macroable/Traits/Macroable.php(124): Illuminate\\Database\\Eloquent\\Collection->Laravel\\Scout\\{closure}()
#5 /home/redacted/vendor/laravel/scout/src/Searchable.php(169): Illuminate\\Support\\Collection->__call()
#6 /home/redacted/vendor/laravel/scout/src/ModelObserver.php(109): LaravelEnso\\Products\\Models\\Product->searchable()
#7 /home/redacted/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(528): Laravel\\Scout\\ModelObserver->saved()
#8 [internal function]: Illuminate\\Events\\Dispatcher->Illuminate\\Events\\{closure}()
#9 /home/redacted/vendor/laravel/framework/src/Illuminate/Database/DatabaseTransactionsManager.php(84): call_user_func()
#10 /home/redacted/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(529): Illuminate\\Database\\DatabaseTransactionsManager->addCallback()
#11 /home/redacted/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(424): Illuminate\\Events\\Dispatcher->Illuminate\\Events\\{closure}()
#12 /home/redacted/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(249): Illuminate\\Events\\Dispatcher->Illuminate\\Events\\{closure}()
#13 /home/redacted/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php(189): Illuminate\\Events\\Dispatcher->dispatch()
#14 /home/redacted/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1035): Illuminate\\Database\\Eloquent\\Model->fireModelEvent()
#15 /home/redacted/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1006): Illuminate\\Database\\Eloquent\\Model->finishSave()
#16 /home/redacted/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(885): Illuminate\\Database\\Eloquent\\Model->save()
#17 /home/redacted/vendor/laravel-enso/products/src/Http/Controllers/Update.php(14): Illuminate\\Database\\Eloquent\\Model->update()
#18 /home/redacted/vendor/laravel-enso/product-eav/src/Http/Controllers/Update.php(15): LaravelEnso\\Products\\Http\\Controllers\\Update->__invoke()
#19 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): LaravelEnso\\ProductEav\\Http\\Controllers\\Update->__invoke()
#20 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()
#21 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Route.php(262): Illuminate\\Routing\\ControllerDispatcher->dispatch()
#22 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()
#23 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Router.php(695): Illuminate\\Routing\\Route->run()
#24 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()
#25 /home/redacted/vendor/laravel-enso/localisation/src/Http/Middleware/SetLanguage.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#26 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): LaravelEnso\\Localisation\\Http\\Middleware\\SetLanguage->handle()
#27 /home/redacted/vendor/laravel-enso/permissions/src/Http/Middleware/VerifyRouteAccess.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#28 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): LaravelEnso\\Permissions\\Http\\Middleware\\VerifyRouteAccess->handle()2
#29 /home/redacted/vendor/laravel-enso/impersonate/src/Http/Middleware/Impersonate.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#30 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): LaravelEnso\\Impersonate\\Http\\Middleware\\Impersonate->handle()
#31 /home/redacted/vendor/laravel-enso/action-logger/src/Http/Middleware/ActionLogger.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#32 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): LaravelEnso\\ActionLogger\\Http\\Middleware\\ActionLogger->handle()
#33 /home/redacted/vendor/laravel-enso/core/src/Http/Middleware/VerifyActiveState.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#34 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): LaravelEnso\\Core\\Http\\Middleware\\VerifyActiveState->handle()
#35 /home/redacted/vendor/laravel-enso/control-panel-api/src/Http/Middleware/RequestMonitor.php(15): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#36 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): LaravelEnso\\ControlPanelApi\\Http\\Middleware\\RequestMonitor->handle()
#37 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#38 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()
#39 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(127): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#40 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(103): Illuminate\\Routing\\Middleware\\ThrottleRequests->handleRequest()
#41 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(55): Illuminate\\Routing\\Middleware\\ThrottleRequests->handleRequestUsingNamedLimiter()
#42 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Routing\\Middleware\\ThrottleRequests->handle()
#43 /home/redacted/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php(44): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#44 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Auth\\Middleware\\Authenticate->handle()
#45 /home/redacted/vendor/laravel/sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php(33): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#46 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Laravel\\Sanctum\\Http\\Middleware\\EnsureFrontendRequestsAreStateful->Laravel\\Sanctum\\Http\\Middleware\\{closure}()
#47 /home/redacted/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#48 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()
#49 /home/redacted/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#50 /home/redacted/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()
#51 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Session\\Middleware\\StartSession->handle()
#52 /home/redacted/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#53 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()
#54 /home/redacted/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#55 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()
#56 /home/redacted/vendor/laravel/sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php(26): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#57 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Laravel\\Sanctum\\Http\\Middleware\\EnsureFrontendRequestsAreStateful->Laravel\\Sanctum\\Http\\Middleware\\{closure}()
#58 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#59 /home/redacted/vendor/laravel/sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php(34): Illuminate\\Pipeline\\Pipeline->then()
#60 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Laravel\\Sanctum\\Http\\Middleware\\EnsureFrontendRequestsAreStateful->handle()
#61 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#62 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Router.php(697): Illuminate\\Pipeline\\Pipeline->then()
#63 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Router.php(672): Illuminate\\Routing\\Router->runRouteWithinStack()
#64 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Router.php(636): Illuminate\\Routing\\Router->runRoute()
#65 /home/redacted/vendor/laravel/framework/src/Illuminate/Routing/Router.php(625): Illuminate\\Routing\\Router->dispatchToRoute()
#66 /home/redacted/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(167): Illuminate\\Routing\\Router->dispatch()
#67 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()
#68 /home/redacted/vendor/sentry/sentry-laravel/src/Sentry/Laravel/Http/SetRequestIpMiddleware.php(45): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#69 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Sentry\\Laravel\\Http\\SetRequestIpMiddleware->handle()
#70 /home/redacted/vendor/sentry/sentry-laravel/src/Sentry/Laravel/Http/SetRequestMiddleware.php(42): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#71 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Sentry\\Laravel\\Http\\SetRequestMiddleware->handle()
#72 /home/redacted/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#73 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle()
#74 /home/redacted/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#75 /home/redacted/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#76 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()
#77 /home/redacted/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#78 /home/redacted/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#79 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()
#80 /home/redacted/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#81 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()
#82 /home/redacted/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#83 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()
#84 /home/redacted/vendor/fruitcake/laravel-cors/src/HandleCors.php(52): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#85 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fruitcake\\Cors\\HandleCors->handle()
#86 /home/redacted/vendor/fideloper/proxy/src/TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#87 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fideloper\\Proxy\\TrustProxies->handle()
#88 /home/redacted/vendor/laravel-enso/core/src/Http/Middleware/AuthorizationCookie.php(16): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#89 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): LaravelEnso\\Core\\Http\\Middleware\\AuthorizationCookie->handle()
#90 /home/redacted/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#91 /home/redacted/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(142): Illuminate\\Pipeline\\Pipeline->then()
#92 /home/redacted/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(111): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()
#93 /home/redacted/public/index.php(52): Illuminate\\Foundation\\Http\\Kernel->handle()
#94 /home/bravo/.config/composer/vendor/cpriego/valet-linux/server.php(232): require('...')
#95 {main}
"}

Metadata

Typsense Version: 0.21.0 (locally hosted)

OS: Linux Mint 19.3, Kernel Linux 5.4.0-90-generic x86_64

Other relevant versions:
laravel/framework v8.74.0
laravel/scout v9.3.2
typesense/laravel-scout-typesense-driver v5.0.1

Duplicate array keys in TypesenseEngine::buildSearchParams

Description

Keys are repeated: q, query_by, filter_by, per_page, page, highlight_start_tag, highlight_end_tag, exhaustive_search
in TypesenseEngine::buildSearchParams on line 230

    private function buildSearchParams(Builder $builder, int $page, int|null $perPage): array
    {
        $params = [
            'q' => $builder->query,
            'query_by' => implode(',', $builder->model->typesenseQueryBy()),
            'filter_by' => $this->filters($builder),
            'per_page' => $perPage,
            'page' => $page,
            'highlight_start_tag' => $this->startTag,
            'highlight_end_tag' => $this->endTag,
            'exhaustive_search' => $this->exhaustiveSearch,
            'q'                          => $builder->query,
            'query_by'                   => implode(',', $builder->model->typesenseQueryBy()),
            'filter_by'                  => $this->filters($builder),
            'per_page'                   => $perPage,
            'page'                       => $page,
            'highlight_start_tag'        => $this->startTag,
            'highlight_end_tag'          => $this->endTag,
            'snippet_threshold'          => $this->snippetThreshold,
            'exhaustive_search'          => $this->exhaustiveSearch,
            'use_cache'                  => $this->useCache,
            'cache_ttl'                  => $this->cacheTtl,
            'prioritize_exact_match'     => $this->prioritizeExactMatch,
            'enable_overrides'           => $this->enableOverrides,
            'highlight_affix_num_tokens' => $this->highlightAffixNumTokens,
        ];

       // etc...

}

Metadata

Typesense Version:0.25.2
laravel-scout-typesense-driver: 5.2.8

OS: Docker

Please add possibility to use Laravel Scout Builder ->options() method

Description

Currently we cannot use the Model::search('query')->options([]) feature, since the typesense engine class does not merge the builder options with the initial options. For example the Laravel scout algolia driver does enable this like this:

protected function performSearch(Builder $builder, array $options = [])
    {
        $algolia = $this->algolia->initIndex(
            $builder->index ?: $builder->model->searchableAs()
        );

        $options = array_merge($builder->options, $options);

        if ($builder->callback) {
            return call_user_func(
                $builder->callback,
                $algolia,
                $builder->query,
                $options
            );
        }

        return $algolia->search($builder->query, $options);
    }

Steps to reproduce

add options to the Builder->options() method and see that these options are not applied in the search query

Expected Behavior

The given options array in the options method on the Laravel Scout Builder class must be applied in the search query to typesense

Suggested Solution

Rewrite the current performSearch method of the Typesense engine class from:

protected function performSearch(Builder $builder, array $options = []): mixed
    {
        $documents = $this->typesense->getCollectionIndex($builder->model)
            ->getDocuments();
        if ($builder->callback) {
            return call_user_func($builder->callback, $documents, $builder->query, $options);
        }
        if(!$this->optionsMulti)
        {
            $documents = $this->typesense->getCollectionIndex($builder->model)
                ->getDocuments();
            if ($builder->callback) {
                return call_user_func($builder->callback, $documents, $builder->query, $options);
            }

            return $documents->search($options);
        } else {
            return $this->typesense->multiSearch(["searches" => $this->optionsMulti], $options);
        }
    }
TO:
protected function performSearch(Builder $builder, array $options = []): mixed
    {
        $options = array_merge($options, $builder->options); // newly added line

        $documents = $this->typesense->getCollectionIndex($builder->model)
            ->getDocuments();
        if ($builder->callback) {
            return call_user_func($builder->callback, $documents, $builder->query, $options);
        }
        if(!$this->optionsMulti)
        {
            $documents = $this->typesense->getCollectionIndex($builder->model)
                ->getDocuments();
            if ($builder->callback) {
                return call_user_func($builder->callback, $documents, $builder->query, $options);
            }

            return $documents->search($options);
        } else {
            return $this->typesense->multiSearch(["searches" => $this->optionsMulti], $options);
        }
    }

Error with deleting multiple documents

Description

Bulk deleting isn't working for me, I get an error like:

Typesense\Exceptions\ObjectNotFound: Could not find a filter field named `feedback.id` in the schema

My collection schema is:

[
  {
    "created_at":1602963950,
    "default_sorting_field":"last_reply_time",
    "fields":[
      {
        "facet":false,
        "index":true,
        "name":"application_id",
        "optional":false,
        "type":"int64"
      },
      {
        "facet":false,
        "index":true,
        "name":"message",
        "optional":true,
        "type":"string"
      },
      {
        "facet":false,
        "index":true,
        "name":"email",
        "optional":true,
        "type":"string"
      },
      {
        "facet":false,
        "index":true,
        "name":"last_reply_time",
        "optional":false,
        "type":"int64"
      },
      {
        "facet":false,
        "index":true,
        "name":"properties",
        "optional":true,
        "type":"string[]"
      },
      {
        "facet":false,
        "index":true,
        "name":"member_name",
        "optional":true,
        "type":"string"
      },
      {
        "facet":false,
        "index":true,
        "name":"member_email",
        "optional":true,
        "type":"string"
      },
      {
        "facet":false,
        "index":true,
        "name":"reply_messages",
        "optional":true,
        "type":"string[]"
      },
      {
        "facet":false,
        "index":true,
        "name":"reply_emails",
        "optional":true,
        "type":"string[]"
      }
    ],
    "name":"feedback",
    "num_documents":2459169,
    "num_memory_shards":4
  }
]

The issue might be that I haven't explicitly defined id as a field?

For what it's worth, even the example in the readme doesn't indicate that we should have id as a field.

Metadata

Typsense Version: 0.17.0 and 0.21.0 both tested

OS: Ubuntu 20.04.2 LTS

Not able to skip an object from being indexed

Description

Problems when trying to skip an object so it DOES NOT become part of the index. There's no apparent way to do it using the function toSearchableArray

Steps to reproduce

The problem seems to be the function toSearchableArray

if I want to bypass an object on that function (ie: I don't want it into the index):

1- Returning an empty array results in a malformed JASON error when syncing:

 Typesense\Exceptions\TypesenseClientError 

  Error importing document: Bad JSON: not a properly formed document.
  
  at vendor/typesense/laravel-scout-typesense-driver/src/Typesense.php:165

2- Returning FALSE results in an error of unexpected response type:

  TypeError 

  Typesense\Documents::Typesense\{closure}(): Argument #1 ($document) must be of type array, bool given

  at vendor/typesense/typesense-php/src/Documents.php:139

3- if I use the Scout method 'unsearchable()' function results in an ObjectNotFound exception:

 Typesense\Exceptions\ObjectNotFound 

  Could not find a document with id: 1105

  at vendor/typesense/typesense-php/src/ApiCall.php:347

Expected Behavior

Ideally, if we would like this to work like Algolia does with Laravel Scout, by using the unsearchable method and returning either an empty array or a false flag, we would be able to skip the object from being added to the index.

Actual Behavior

The import is interrupted with one of those exceptions and the collection is being indexed only up to the point of the first failure.

Metadata

Typsense Version: 0.2.22

OS: Ubuntu 22.04

Typesense scout import fails in Laravel 11

Description

My php artisan scout:import fails for large datasets in Laravel11. This issue is not seen with smaller dataset. May be less than 500.

Steps to reproduce

There is no issue when I was on Laravel 10. When I migrated to Laravel 11.X, this issue is seen.
Steps to reproduce:

  1. Create a table with larage data. May be 5000 or 10000.
  2. Do follow the steps of creating toSearchableArray() and Collection as per documentation.
  3. Use "php artisan scout:import "App\Models\YourModel"
  4. You will see error saying record already exist.
  5. On typesense server you will find first batch of initial 500 records already indexed.
  6. So while attempting next batch of index, it is not able to recognise the difference and hence considers documents alre
    scout-error

Expected Behavior

I should get my documents in batches. Atleast in my case it has been always in a batch of 500 documents.

Actual Behavior

I see only first 500 documents indexed but in next batch index it fails saying collection already exists. In laravel10, this was perfectly fine.

Metadata

Typesense Version: 0.26

OS: Ubuntu 22.04

Hotfix TypeError - Typesense\\Documents::import(): Argument #1 ($documents) must be of type string, array given.

Description

Afrer migrate from (v5.2.8 => v5.2.6) got the type error.
"Typesense\\Documents::import(): Argument #1 ($documents) must be of type string, array given, called in /var/www/artwin.io/vendor/typesense/laravel-scout-typesense-driver/src/Typesense.php on line 191" exception: "TypeError" file: "./vendor/typesense/typesense-php/src/Documents.php"

Metadata

Typesense Version: v5.2.8


Before and after method changes fallow up on the screenshots.

v5 2 6

v5 2 6 --- v5 2 8 v5 2 8

Import not working with array data.

When importing data that have array value, then throw this error -

Typesense\Documents::import(): Argument #1 ($documents) must be of type string, array given

But, After changing this line manually, it's working -

Paht: vendor/typesense/laravel-scout-typesense-driver/src/Typesense.php
Line: 189

$importedDocuments = $collectionIndex->getDocuments()
                                             ->import($documents, ['action' => $action]);

to

$importedDocuments = $collectionIndex->getDocuments()
                                             ->createMany($documents);

Typsense import errors fail silently

Description

Attempts to import a model, with an incorrect schema, does not return any errors to the user, despite the underlying JSON API call returning an error along with succcess field set to false. This occurs during batch import via php artisan scout:import and single model updates. No errors are returned to the user, and no indication is made of the import failing.

Steps to reproduce

Define a schema on the model like so:

public function getCollectionSchema(): array
    {
        return [
            'name' => $this->searchableAs(),
            'fields' => [
                [
                    'name' => 'id',
                    'type' => 'string',
                ]
            ],
            'default_sorting_field' => 'id',
        ];
    }

public function toSearchableArray()
{
  return [
        'id' => $this->id,
  ];
}

Bulk import the model via php artisan scout:import "App\SomeModel\Job"

Expected Behavior

The command should fail with an error indicating the failed result, along with the error reason.

Actual Behavior

Console reports success:

Imported [App\SomeModel\Job] models up to ID: 552

Yet, no models are actually imported in Typesense. Logging the underlying JSON response within this library, shows the following JSON being returned:

{"error":"Document's 'id' field should be a string.","success":false}

Metadata

Typsense Version: 0.21.0

OS: Ubuntu 20.04.3

Document not updated if 'afterCommit' is true

Description

The TypesenseEngine::update will check if the model is dirty, but if the 'afterCommit' is set to true in the config, the method will always skip.

Steps to reproduce

  • set scout.after_commit to true
  • trigger a save event on a Searchable model

Expected Behavior

TypesenseEngine::update should update the model, ignoring the 'isDirty' if 'afterCommit' is set to true.

Actual Behavior

TypesenseEngine::update skips the update.

Metadata

Typesense Version: 0.24.0

typesense/laravel-scout-typesense-driver: v5.2.0,
laravel/scout: v9.8.0

OS: Ubuntu 22.04

Model update resets document auto-generated embeddings

Description

In Typesense version 0.25 it's possible to define an embedding field. But since this field is auto-generated and is not stored locally, any update to the model will cause the embeddings field to reset.

Steps to reproduce

Schema:

    {
      "name": "embedding",
      "type": "float[]",
      "facet": false,
      "optional": false,
      "index": true,
      "sort": false,
      "infix": false,
      "locale": "",
      "embed": {
        "from": [
          "some_field"
        ],
        "model_config": {
          "model_name": "ts/paraphrase-multilingual-mpnet-base-v2"
        }
      },
      "num_dim": 768
    }

After I import the model, Typesense takes some time to generate embeddings. After that process, all documents will have the embedding field with array of 768 floats.

Then, If I call searchable() method on the model, the embedding field becomes empty.

Expected Behavior

embedding field should ether be updated if embed.from fields are changed, or be left unchanged.

Actual Behavior

the embedding field becomes empty

Metadata

Typesense Version: 0.25.0

OS: Ubuntu 20.04

Latest release doesn't have most recent fixes: Could not parse the filter query: unbalanced `&&` operands.

Description

I'm just doing what I think is the most basic usage of the library.

Model::search('myterm');

But i'm getting the following error:

"Could not parse the filter query: unbalanced && operands."

It looks like this was fixed a month ago: 7a9edba#diff-30cbc8fb802094edb92485725f009b63a895bcc546b84381b550883a17441129R266

However there hasn't been a new release since July so composer is still picking up the old version.

How can I get the latest version?

As it stands right now it seems like this package doesn't work out of the box.

I am on 5.1.0 right now.

Passing "empty" values to search query results in "Parameter `q` is required"

Description

When using certain values for query string, "Parameter q is required" is thrown. More exactly, any value that returns true from empty(...) does not work.

Steps to reproduce

For example, App\Models\Taxon::search("0")->get() throws an error.

Expected Behavior

Found values should be returned.

Actual Behavior

"Parameter q is required" is thrown.

Metadata

Typesense Version:
0.25.1

OS:

Culprit

https://github.com/typesense/laravel-scout-typesense-driver/blob/master/src/Engines/TypesenseEngine.php#L208 and https://github.com/typesense/laravel-scout-typesense-driver/blob/master/src/Engines/TypesenseEngine.php#L208 runs array_filter on parameters array, therefore, removing the "0" query param, and typesense request is then missing the q parameter.

Possible solution

Change array_filter to a stricter function, such as rejecting only null values.

Using Multi Search after merging to Laravel Scout

No idea if this is the right place to ask, but;

This driver has been merged upstream to the Laravel Scout repository, however it seems the Laravel team has no intention of implementing multi search for any of the drivers, including Typesense. How would you recommend one to do federated/multi search? Use the driver from this repository or?

Thanks in advance

Http\Discovery\NotFoundException No HTTPlug clients found.

I am currently experimenting with algolia, meilisearch and typsense, and typsense driver was the only one to fail after trying:

php artisan scout:import "App\Brand"

Http\Discovery\NotFoundException

No HTTPlug clients found. Make sure to install a package providing "php-http/client-implementation". Example: "php-http/guzzle6-adapter".

is this an expected behaviour?

Detail regarding composer installation

HI.. Try to install this package locally

through command:
composer require typesense/laravel-scout-typesense-driver

Error:

[InvalidArgumentException]
  Package typesense/laravel-scout-typesense-driver has a PHP requirement incompatible with your PHP version, PHP extensions and Composer version

Any information about what and which version I should fix?

Question : Use filter_by or complex query

Description

Hello everyone,

I would like to know if it is possible (if so, how?) to be able to use the filter_by in order to be able to add filter conditions such as
'price:<=25' ? As given as an example here in the Typesense documentation :
https://typesense.org/docs/0.22.2/api/documents.html#search and
https://typesense.org/docs/0.22.2/api/documents.html#search-parameters

Since it is not possible in Laravel Scout to use complex Where clause

Scout allows you to add simple "where" clauses to your search queries. Currently, these clauses only support basic numeric equality checks and are primarily useful for scoping search queries by an owner ID. Since a search index is not a relational database, more advanced "where" clauses are not currently supported

Thank you in advance for your answer

PHP 8.2 deprecation warnings

Description

PHP 8.2 throws deprecation warning on using ${var} in strings.

Steps to reproduce

Just use PHP 8.2

Expected Behavior

No error shown.

Actual Behavior

PHP message: PHP Deprecated:  Using ${var} in strings is deprecated, use {$var} instead in /var/www/starfish/vendor/typesense/laravel-scout-typesense-driver/src/Typesense.php on line 195

Metadata

Typesense Version: any
OS: linux

MultiSearch Example(s)

Wondering if anyone has any examples of utilizing MultiSearch functionality or whether or not it is even possible?

Unable to sort

Description

At present it doesn't seem possible to sort, or if it is it's not documented anywhere.

Steps to reproduce

Setup on a model in this example Post and try the following:

Post::search(['q' => $searchTerm, 'sort_by' => 'created_at:asc'])

Expected Behavior

I would expect the results to be ordered by the created_at attribute in ascending order

Actual Behavior

Instead the following error message is show:

Request Malformed: Parameter q is required.

Metadata

Typesense Driver Version: 5.2.4

Error in tinker when trying to access `\Typesense\LaravelTypesense\Typesense` singleton

Description

There's an error when trying to create/access the Typesense instance in the tinker console.

Although the library is working fine through the web app, it just fails here in the console.

Steps to reproduce

$ php artisan tinker
Psy Shell v0.10.8 (PHP 8.0.2 — cli) by Justin Hileman
>>> app()->make(\Typesense\LaravelTypesense\Typesense::class);
TypeError: Typesense\LaravelTypesense\TypesenseServiceProvider::Typesense\LaravelTypesense\{closure}(): Argument #1 ($client) must be of type Typesense\Client, Illuminate\Foundation\Application given, called in /home/vagrant/app/vendor/laravel/framework/src/Illuminate/Container/Container.php on line 869

Expected Behavior

I'd expect the Typesense class/singleton to be returned

Actual Behavior

I get the error returned above.

Metadata

Typsense Version: 0.21.0

OS: Ubuntu 18.04.5 LTS

Support "Scoped Search API Keys" to account for multi-tenant applications

Description

When using typesense with laravel-scout-typesense-driver in a tenancy-enabled application, results are not limited to the active tenant.

The Typsense docs propose using "Scoped Search API Keys" in that case, but as far as I can see, laravel-scout-typesense-driver provides no functionality to change the API Key on a per-instance basis, only for the whole app through config/scout.php

I am working on a workaround to supply the Tenant's ID in the data, to limit the results to the current user. But of course, a solution within the driver would be preferable.

Steps to reproduce

$t = TenancyUser::first(); tenancy()->initialize($t); _ModelName_::search('test')->paginate(1, page: 1)->total();

Expected Behavior

Returns number of results for ModelName in the active tenancy

Actual Behavior

Returns number of results for ModelName in ALL tenancy schemas

Metadata

Typesense Version: 0.23.1

OS: Docker image 'typesense/typesense:0.23.1' and 'postgres:13.5'

Cannot index collection with a soft deleted first model when scout.soft_delete is true

Description

When indexing with scout.soft_delete set to true index is not imported if the first model of the Collection is deleted

Steps to reproduce

use scout:import command to import a collection of models where the first one is deleted, when scout.soft_delete is true

Expected Behavior

The collection should be indexed

Actual Behavior

The collection is not indexed

Metadata

Typesense Version: 0.23.1

OS: Linux

We propose the following modification on line 171 of src/Engines/TypesenseEngine (the update method)
From this:
if (!$this->usesSoftDelete($models->first()) || is_null($models->first()?->deleted_at)) {
To this:
if (!$this->usesSoftDelete($models->first()) || is_null($models->first()?->deleted_at) || config('scout.soft_delete', false)) {

Pagination not working properly when using query callback

Description

It seems the TypesenseEngine doesn't correctly work with Scout's ->paginate, particularly when results exceeed 250 hits, as the total in the pagination gets set to the default perPage for some reason. It seems the MeiliSearch adapter also had this issue, see: laravel/scout#535

The workaround I have is to implement the pagination manually with:

$searchData = $query->query(fn ($search) => $search->with($relations))->get();
$paginatedSearchResults = new LengthAwarePaginator($searchData, $query->raw()['found'], $perPage);

Steps to reproduce

  1. Have a collection that exceeds 250 documents.
  2. Search against that collection utilising Scout and Typesense of course.
  3. Attempt to paginate that data with a query callback, for example with:
$query->query(fn ($search) => $search->with($relations))
                ->paginate($perPage)
                ->withQueryString();

Expected Behavior

Return a LengthAwarePaginator with 1278 total items.

Actual Behavior

Returns a LengthAwarePaginator with 15 total items.

Metadata

Typsense Version: v5.1.0-rc1

OS: MacOS 12.4 Monterey

Raw results from Typesense:

array:8 [▼
  "facet_counts" => []
  "found" => 1278
  "hits" => array:15 [▶]
  "out_of" => 171361
  "page" => 1
  "request_params" => array:3 [▶]
  "search_cutoff" => false
  "search_time_ms" => 1
]

Pagination without query callback works as expected:

Illuminate\Pagination\LengthAwarePaginator {#2096 ▼
  #items: Illuminate\Database\Eloquent\Collection {#2102 ▶}
  #perPage: 15
  #currentPage: 1
  #query: array:1 [▶]
  #fragment: null
  #pageName: "page"
  +onEachSide: 3
  #options: array:2 [▶]
  #total: 1278
  #lastPage: 86
}

Pagination with query callback does NOT work as expected:

Illuminate\Pagination\LengthAwarePaginator {#2431 ▼
  #items: Illuminate\Database\Eloquent\Collection {#2164 ▶}
  #perPage: 15
  #currentPage: 1
  #query: array:1 [▶]
  #fragment: null
  #pageName: "page"
  +onEachSide: 3
  #options: array:2 [▶]
  #total: 15
  #lastPage: 1
}

Exception when searching large datasets for common query

Description

When searching for a string that is relatively common throughout a collection of indexed data, the results can not be fetched or displayed because the search with the Scout driver results in the following exception:

Typesense\\Exceptions\\ObjectUnprocessable(code: 0): Only upto 250 hits can be fetched per page

Steps to reproduce

  1. Start a fresh Laravel project, install Scout and the Typesense Scout driver according to documentation
  2. Add a model and make it searchable
  3. Add a large dataset (~ 2000+ records with "lorem ipsum" text)
  4. Query the dataset and try to display the results in a paginated way:
$items = Item::search($request->input('query'))
    ->options(['query_by' => 'content'])->latest()
    ->paginate(perPage: 10, pageName: 'items')
    ->appends($request->only('query'));

Expected Behavior

I expect the search to succeed, even when there are more then 250 hits, because that's why I use pagination on my frontend.

Actual Behavior

Following exception is thrown:

Typesense\\Exceptions\\ObjectUnprocessable(code: 0): Only upto 250 hits can be fetched per page

Metadata

Laravel Version: 10.43.0
PHP Version: 8.2.12
Scout Version: 10.8.2
Typesense PHP SDK Version: 4.9.1
Typesense Version: 0.25.2
OS: Docker @ Apple Silicon

"Undefined property: Laravel\\Scout\\Builder::$whereIns",

Description

After uploading the file to Typesense, try to search over it using
MediaSource::search($request->input('search'))
->paginateRaw()
->getCollection()

Got error
"message": "Undefined property: Laravel\Scout\Builder::$whereIns",
vendor/typesense/laravel-scout-typesense-driver/src/Engines/TypesenseEngine.php"
"line": 396,

Don't update Typesense document if the model is soft-deleted

Description

Typesense driver does not check if the model is soft deleted and always updates the data when the model changes. This causes the soft-deleted document to be created in the collection.

Steps to reproduce

For example, in my case I have some searchable model with SoftDeletes trait and observer that clears some meta fields after the model is deleted. Something like that:

class MyModelObserver
{
    public function deleted(MyModel $model)
    {
        $model->update(['worker_id' => null, 'support_id' => null]);
    }
}

// ...

MyModel::delete(id); // The model is still in Typesense

I presume that if I do something like MyModel::withTrashed()->find(id)->update(['name' => 'test']), the model will be also pushed to the Typesense.

Expected Behavior

The Typesense driver should not update the remote document if the model is in soft-deleted state.

Actual Behavior

Typesense deletes the remote document and immediately restores it (because MyModelObserver calls the update method on the model.

Also note that worker_id and support_id is not even in the model schema (see #23).

Metadata

Typsense Version: 5.0

OS: Ubuntu 20.04

Updates needed to complete adoption to typesense github org

This repo was adopted from @AbdullahFaqeir's work here: https://github.com/devloopsnet/laravel-scout-typesense-engine.

The following tasks need to be done to complete the adoption:

  • Add LICENSE file (current license MIT)
  • Add credits to Abdullah Al-Faqeir and his company DevLoops for building the original version, in README
  • Change instances of Devloops in class names to Typesense
  • TypesenseServiceProvider.php has references to master_node and read_replica_nodes, which are old configuration parameters no longer supported in the latest version of Typesense Server. Needs to be switched to nodes and nearest_node
  • Publish new version of package under typesense namespace - @jasonbosco
  • Test that new published version works fine
  • Mark devloopsnet/laravel-typesense as abandoned in Packagist and list this package as its replacement
  • Update README.md in devloopsnet/laravel-scout-typesense-engine repo to point people to this repo: devloopsnet/laravel-scout-typesense-engine#17

$model->unsearchable(); does not work

Description

When I upload new document document content is extracted and indexed on typesense which is very good.
The issue is when I delete the document I'm falling into the observer delete event, doing :
$model->unsearchable(); and it seems it not unsearchable it from typesense

Steps to reproduce

Upload the document it will be indexed
Delete the document and try to search by some text which was in that text, you will see that that result would be the deleted document

Expected Behavior

When the document is deleted in goes to $model->unsearchable(), and it should be unindexed from typesense maybe delete from there. And when I search I should gen no results since the document was deleted

Error from Typesense on Update of Model Instance which is not in index

  • typesense/laravel-scout-typesense-driver: 5.2.2
  • Scout Version: 9.8.1
  • Scout Driver: Typesense
  • Laravel Version: 9.52.4
  • PHP Version: 8.1
  • Database Driver & Version: 10.2.44-MariaDB

Description:

If an instance of a model is created & persisted in DB and should not be searchable yet (e.g. the instance is not public yet) - everything works fine.

When the same instace is beeing saved again, an error occurs and the model cannot be saved.
I guess that the reason is, that on /vendor/laravel/scout/src/ModelObserver.php:102 the Method $model->wasSearchableBeforeUpdate() returns true, even though the instance of the model is not in the index yet.
The problem occurs for all Models in Index, as soon as the shouldBeSearchable() Method returns false (eg if the instance is not public yet).

ModelObserver.php:101ff

... if (! $model->shouldBeSearchable()) { if ($model->wasSearchableBeforeUpdate()) { $model->unsearchable(); } return; } ...

scout1

Undefined property: Laravel\Scout\Builder::$whereIns

Getting this error while running a simple query. There is already a issue logged with this same. But its closed without any answer. That's why i'm creating this issue.
If anyone can suggest any answer ASAP would be helpful

Undefined property: Laravel\Scout\Builder::$whereIns

Here is my query

$result = Todo::search('test')->get();

Grouping isn't supported by the map result function

Description

When utilising the groupBy macro, the map functin from the TypesenseEngine.php fails as the Typesense API returns grouped_hits and not a hits index.

My workaround was to implement a custom macro:

Builder::macro('getGroup', function () {
            $results = $this->engine()->search($this);

            $objectIds = collect($results['grouped_hits'] ?? $results['hits'])
                ->pluck(isset($results['grouped_hits']) ? 'group_key.0' : 'document.id')
                ->values()
                ->all();

            if (isset($results['grouped_hits'])) {
                return collect(array_values($objectIds));
            }

            return null;
        });

I then call this conditionally based on if I need a grouping to be applide with:

if ($groupBy) {
      return $query->getGroup();
}

This can be integrated to the map function quite easily but perhaps a seperate mapByGroup may be better.

Steps to reproduce

Perform a search with the ->groupBy() macro.

Expected Behavior

The TypesenseEngine should be able to map the grouped_hits index.

Actual Behavior

The TypesenseEngine fails to map the grouped_hits as it expects a hits index and therefore fails.

Metadata

Typsense Version: v5.1.0

OS: MacOS 12.4 Monterey

Error with `??` in `Typesense.php`

Description

Because of how the ?? check works, it will never actually try and load the document/collection in these places:

https://github.com/typesense/laravel-scout-typesense-driver/blob/master/src/Typesense.php#L89
https://github.com/typesense/laravel-scout-typesense-driver/blob/master/src/Typesense.php#L118
https://github.com/typesense/laravel-scout-typesense-driver/blob/master/src/Typesense.php#L163

The typesense library will always return the object, so the check for existence and then the null fallback are pointless, and actually breaks it.

I'll submit a PR for this tomorrow, unless someone beats me to it (@hi019?)

Connection refused when doing simple search

Description

Hi! im getting networkexception, but im not sure if this has something to do with me setting up from docker or something else.

Http\Client\Exception\NetworkException with message 'Failed to connect to localhost port 8108: Connection refused for "http://localhost:8108/collections/todo".'

Steps to reproduce

  1. added in my existing docker-compose.yml
...
  typsense:
    image: typesense/typesense:0.22.2
    container_name: typesense
    environment:
      TYPESENSE_DATA_DIR: /data
      TYPESENSE_API_KEY: xyz
    volumes:
      - /tmp/typesense-server-data:/data
    ports:
      - "8108:8108"
    restart: "no"
    networks:
      laravel:
...
  1. docker compose up -d, then curl http://localhost:8108/health which returned {"ok":true}

  2. Followed the readme instructions section:

  • dependencies (guzzle7 adapter and laravel-scout-typesense-driver)
  • service provider
  • artisan vendor:publish ...
  • config/scout.php
  • Searchable trait as well as TypesenseDocument interface
  • setting SCOUT_DRIVER value to typesense
  • php artisan scout:import App\\Models\\Todo

Expected Behavior

returning the searched results

Actual Behavior

After the installation and syncing, I tried this inside tinker: Todo::search('Test')->get(); then i get this

>>> Images::search('female')->get()
Http\Client\Exception\NetworkException with message 'Failed to connect to localhost port 8108: Connection refused for "http://localhost:8108/collections/todo".'

Metadata

Typsense Version: 0.22.2

OS: macos 12.4

Laravel v8

Suggestion: reduce unnecessary API calls

Typesense driver makes a lot of unnecessary requests to the server. Real life example: I have a collection with over 10000 documents in it. Each document is updated hourly. Each update changes one model attribute, which triggers 2 request to typesense server. That's 5.5 completely unnecessary requests per second.

20k documents will cause 11 requests per second, 100k documents - 55 requests per second.

I know that typesense server is fast and performant, but still, that requests congest the bandwidth, tcp connections and other resources.

1. Unnecessary updates

At the moment, typesence-driver updates the document on any model update, even if the changed field is not in the schema. This can cause performance problems if there are a lot of updates.

For example:

// MyModel.php

public function toSearchableArray(): array {
    return [
        'id' => (string) $this->id,
        'name' => $this->name,
    ];
}

public function getCollectionSchema(): array {
    return [
        'fields' => [[
            'name' => 'id',
            'type' => 'string',
        ],[
            'name' => 'name',
            'type' => 'string',
        ]],
    ];
}

Now, when I update any field in my model, typesense-driver will try to update the remote index:

MyModel::first()->update([
    'some_field' => 'test',
]);

// typesense-driver sends GET to /collections/my-model
// typesense-driver sends POST to /collections/my-model/documents/import

MyModel::first()->update([
    'some_date' => now(),
]);

// typesense-driver sends GET to /collections/my-model
// typesense-driver sends POST to /collections/my-model/documents/import

My suggestion is to check if the changed fields are in the schema before updating the document. For example, using the getChanges() model method.

The problem that I see here is how to handle computed attributes (ones that use appends array and getter function).

2. Unnecessary collection info retrieval

Upon every update, typesense driver makes a GET request to retrieve a collection info.

I suggest getting that info from schema (getCollectionSchema method).

Metadata

Typsense Version: 5.0

OS: Ubuntu 20.04

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.