@alex-serban/domus-typesense
v0.2.17
Published
Typesense integration package for Domus CRM property search
Maintainers
Readme
@alex-serban/domus-typesense
Typesense integration package for Domus CRM property search with agency-scoped collections.
Installation
npm install @alex-serban/domus-typesenseFeatures
- ✅ Agency-scoped collections - One collection per agency (
properties-agency-{agencyId}) - ✅ Type-safe property search - Full TypeScript support
- ✅ React hooks -
usePropertySearchanduseFacetswith nuqs URL sync - ✅ Property operations - Export, upsert, delete properties
- ✅ Collection management - Create/delete collections per agency
- ✅ Dynamic facets - Real-time filter counts and range calculations
- ✅ Next.js integration - API route helpers included
Quick Start
1. Initialize Typesense Client
import { createTypesenseClient } from '@alex-serban/domus-typesense';
const client = createTypesenseClient({
nodes: [
{
host: 'your-typesense-host.com',
port: 443,
protocol: 'https',
},
],
apiKey: 'your-api-key',
connectionTimeoutSeconds: 2,
});2. Create Property Search Service
import { PropertySearchService } from '@alex-serban/domus-typesense';
const searchService = new PropertySearchService(client);3. Search Properties
const results = await searchService.search(agencyId, {
query: 'apartment',
types: ['apartment', 'house'],
scopes: ['SALE', 'RENT'],
cityIds: ['Iasi'],
minPrice: 50000,
maxPrice: 500000,
page: 1,
perPage: 20,
sortBy: 'price',
sortOrder: 'asc',
});
console.log(`Found ${results.found} properties`);
results.hits.forEach(property => {
console.log(property.name, property.price);
});4. Get Facets
const facets = await searchService.getFacets(agencyId, {
types: ['apartment'],
});
// Get available cities, types, scopes with counts
console.log(facets.cities); // [{ value: 'Iasi', count: 6 }, ...]
console.log(facets.types); // [{ value: 'apartment', count: 10 }, ...]
// Get price and sqm ranges
const priceRange = await searchService.getPriceRange(agencyId, filters);
const sqmRange = await searchService.getSqmRange(agencyId, filters);React Hooks
usePropertySearch
Search properties with automatic URL sync via nuqs:
import { usePropertySearch } from '@alex-serban/domus-typesense';
function SearchPage({ agencyId }: { agencyId: string }) {
const { results, loading, error, filters, updateFilters, refetch } =
usePropertySearch(agencyId, '/api/search-properties');
return (
<div>
{loading && <p>Loading...</p>}
{error && <p>Error: {error.message}</p>}
{results && (
<div>
<p>Found {results.found} properties</p>
{results.hits.map(property => (
<div key={property.id}>{property.name}</div>
))}
</div>
)}
<button onClick={() => updateFilters({ types: ['apartment'] })}>
Filter Apartments
</button>
</div>
);
}useFacets
Fetch dynamic facets with URL sync:
import { useFacets } from '@alex-serban/domus-typesense';
function Filters({ agencyId }: { agencyId: string }) {
const { facets, loading } = useFacets(agencyId);
if (!facets) return <div>Loading filters...</div>;
return (
<div>
<h3>Property Types</h3>
{facets.types.map(type => (
<label key={type.value}>
<input type="checkbox" />
{type.value} ({type.count})
</label>
))}
<h3>Price Range</h3>
<p>
{facets.priceRange.min} - {facets.priceRange.max} EUR
</p>
</div>
);
}Property Operations
Create Collection for Agency
import { createCollection } from '@alex-serban/domus-typesense';
await createCollection(client, agencyId);Export Properties
import { exportProperties } from '@alex-serban/domus-typesense';
const properties = [
{ property: apartmentData, type: 'apartment' },
{ property: houseData, type: 'house' },
];
const result = await exportProperties(client, agencyId, properties);
console.log(`Exported ${result.exported} properties, ${result.errors} errors`);Upsert Single Property
import { upsertProperty } from '@alex-serban/domus-typesense';
await upsertProperty(
client,
agencyId,
propertyWithRelations,
'apartment' // or 'house', 'commercial', 'industrial', 'land'
);Delete Property
import { deleteProperty } from '@alex-serban/domus-typesense';
await deleteProperty(client, agencyId, propertyId);Next.js API Routes
Use the provided helpers for API routes:
// app/api/search-properties/route.ts
import { handleSearchRequest } from '@alex-serban/domus-typesense';
import { propertySearchService } from '~/lib/typesense';
export async function POST(request: NextRequest) {
return handleSearchRequest(propertySearchService, request);
}
// app/api/search-properties/facets/route.ts
import { handleFacetsRequest } from '@alex-serban/domus-typesense';
import { propertySearchService } from '~/lib/typesense';
export async function GET(request: NextRequest) {
return handleFacetsRequest(propertySearchService, request);
}Property Types
The package supports 5 property types:
apartment- Apartmentshouse- Housescommercial- Commercial propertiesindustrial- Industrial propertiesland- Land plots
Each property type has:
- Common fields - Stored directly in Typesense (price, sqm, location, etc.)
- ExtraData - Type-specific fields stored as JSON string (rooms, bathrooms, balcony, etc.)
Parse ExtraData
import { parseExtraData } from '@alex-serban/domus-typesense';
const property = results.hits[0];
const extraData = parseExtraData(
property.extraData,
property.type as 'apartment' | 'house' | 'commercial' | 'industrial' | 'land'
);
console.log(extraData.rooms); // For apartments/houses
console.log(extraData.balcony); // For apartments
console.log(extraData.garage); // For housesCollection Names
Collections are automatically named per agency:
- Format:
properties-agency-{agencyId} - Example:
properties-agency-abc123
API Reference
PropertySearchService
search(agencyId: string, filters: PropertySearchFilters): Promise<PropertySearchResponse>
Search properties for an agency.
getFacets(agencyId: string, filters?: PropertySearchFilters): Promise<Facets>
Get available facets (types, scopes, cities, etc.) with counts.
getPriceRange(agencyId: string, filters?: PropertySearchFilters): Promise<{min: number, max: number}>
Get min/max price range for current filters.
getSqmRange(agencyId: string, filters?: PropertySearchFilters): Promise<{min: number, max: number}>
Get min/max sqm range for current filters.
License
MIT
