Result
Type 2: Result Extension (result)
Use when: Your extension is fundamentally a search engine over some data — documentation, bookmarks, contacts, files. Users type, see instant results, click a result to open a detail view.
How it works:
- Asyar maintains a hidden background iframe for your extension at all times (because
searchable: trueis required). - As the user types in the global search bar, Asyar sends an
asyar:search:requestmessage to your background iframe. - Your
search(query)method returnsExtensionResult[]. - Results appear directly in the global launcher search results.
- When the user selects a result,
action()fires. For Tier 2 (installed) extensions, theactionclosure cannot cross the iframe boundary — Asyar automatically navigates to theviewPathin the result instead.
⚠️ The
actionfunction onExtensionResultis ignored for installed extensions (Tier 2) because functions cannot be serialized overpostMessage. Always setviewPathon results to control navigation.
Manifest template:
{
"type": "result",
"searchable": true,
"main": "dist/index.js",
"defaultView": "DetailView",
"commands": [
{ "id": "search", "name": "Search My Data", "resultType": "view", "view": "DetailView" }
]
}
src/index.ts pattern:
import type {
Extension, ExtensionContext, ExtensionResult,
IExtensionManager, ILogService
} from 'asyar-sdk';
import DetailView from './DetailView.svelte';
const ITEMS = [
{ id: '1', title: 'Introduction', subtitle: 'Getting started guide' },
{ id: '2', title: 'API Reference', subtitle: 'Full API documentation' },
];
class MyExtension implements Extension {
private extensionManager?: IExtensionManager;
private logger?: ILogService;
async initialize(context: ExtensionContext): Promise<void> {
this.extensionManager = context.getService<IExtensionManager>('ExtensionManager');
this.logger = context.getService<ILogService>('LogService');
}
async activate(): Promise<void> {}
async deactivate(): Promise<void> {}
async viewActivated(viewId: string): Promise<void> {}
async viewDeactivated(viewId: string): Promise<void> {}
async search(query: string): Promise<ExtensionResult[]> {
const q = query.toLowerCase();
return ITEMS
.filter(item => !q || item.title.toLowerCase().includes(q))
.map(item => ({
score: 1,
title: item.title,
subtitle: item.subtitle,
type: 'view' as const,
// viewPath is used by Tier 2 extensions to navigate on selection.
// Pass context via URL params.
viewPath: `com.yourname.mydocs/DetailView?id=${item.id}`,
action: () => {
this.extensionManager?.navigateToView(
`com.yourname.mydocs/DetailView?id=${item.id}`
);
},
}));
}
async executeCommand(commandId: string): Promise<any> {
if (commandId === 'search') {
return { type: 'view', viewPath: 'com.yourname.mydocs/DetailView' };
}
}
onUnload = () => {};
}
export default new MyExtension();
export { DetailView };
Reading URL params in the detail view:
<!-- src/DetailView.svelte -->
<script lang="ts">
const params = new URLSearchParams(window.location.search);
let itemId = $state(params.get('id') ?? '');
let title = $state(params.get('title') ?? 'Detail');
</script>