nawasara / search
Cross-domain search orchestrator for the Nawasara command palette (⌘K). Discovers per-package search providers and aggregates results behind one permission-aware endpoint.
Requires
- php: ^8.1
- illuminate/support: ^10.0|^12.0
- nawasara/ui: *
- spatie/laravel-permission: ^6.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Cross-domain search orchestrator for Nawasara's command palette (⌘K). It collects providers from the data packages, filters them by the user's permissions, then runs them for each query.
Status v0.1.0
| Feature | Status |
|---|---|
SearchProvider contract |
ready |
| Provider discovery via container binding | ready |
| Permission filtering before a provider runs | ready |
| Search endpoint for the palette | ready |
| Cross-domain relevance ranking | not built yet |
| Separate search index (instead of live queries) | not built yet |
How it works
This package stores no data of its own. It is only a broker: each data package
registers its own provider, and SearchManager runs them all and merges the
results.
The pattern follows WorkspaceManager in nawasara/ui. A new package just
registers itself, without touching any code here.
Adding a provider from another package
Write a class that implements Nawasara\Search\Contracts\SearchProvider:
namespace Nawasara\Registry\Search; use Nawasara\Search\Contracts\SearchProvider; class OpdSearchProvider implements SearchProvider { public function key(): string { return 'opd'; } public function label(): string { return 'OPD'; } // null means visible to anyone who is logged in. public function permission(): ?string { return 'registry.opd.view'; } public function search(string $term, int $limit): array { return Opd::where('name', 'like', "%{$term}%") ->limit($limit) ->get() ->map(fn ($o) => [ 'label' => $o->name, 'sublabel' => $o->code, 'url' => route('nawasara-registry.opd.show', $o), ]) ->all(); } }
Then register it in your package's ServiceProvider through the
nawasara.search-providers binding.
search() runs on every keystroke
The palette calls it with a debounce, but it is still synchronous and still frequent. A slow provider slows down the whole palette, not just its own section.
So:
- Search on indexed columns.
LIKE %term%on an unindexed column scans the entire table every time someone types. - Respect
$limit. It is a hard cap, not a suggestion. - Do not eager-load relations the result never uses.
Permissions are checked before a provider runs
SearchManager checks permission() first and skips any provider the user is
not allowed to see, so its query never runs.
That is not only a performance win: search results reveal that data exists even when the content is not shown. Someone who types a name and sees "1 result in OPD" already knows the record is there.
Do not rely on filtering inside search() itself; that kind of filtering is
easy to miss later when the query changes.
Roadmap
- Cross-domain relevance ranking (results are currently grouped per provider)
- A separate search index for large tables
- Per-user search history
Author
Pringgo J. Saputro, Dinas Kominfo Kabupaten Ponorogo
License
MIT