Skip to content

Commit fdf94ab

Browse files
authored
Merge pull request #13 from ember-nexus/feature/gh-6-implement-search-endpoint
add support for post search endpoint, closes #6
2 parents f651bd5 + e416541 commit fdf94ab

19 files changed

+185
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## Unreleased
8+
### Added
9+
- Add support for post search endpoint, closes #6.
810

911
## 0.1.0 - 2026-02-01
1012

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { FetchHelper, ServiceResolver } from '../../Service/index.js';
2+
import { LoggerInterface } from '../../Type/Definition/index.js';
3+
import { ParsedResponse } from '../../Type/Definition/Response/index.js';
4+
import { Step } from '../../Type/Definition/Search/Step/index.js';
5+
import { StepResult } from '../../Type/Definition/Search/StepResult/index.js';
6+
import { ServiceIdentifier } from '../../Type/Enum/index.js';
7+
8+
/**
9+
* The post search endpoint executes a search query and returns results.
10+
*
11+
* @see [Ember Nexus API: Search Endpoint](https://ember-nexus.github.io/api/#/api-endpoints/search/post-search)
12+
* @experimental
13+
*/
14+
class PostSearchEndpoint {
15+
static identifier: ServiceIdentifier = ServiceIdentifier.endpointSearchPostSearchEndpoint;
16+
constructor(
17+
private logger: LoggerInterface,
18+
private fetchHelper: FetchHelper,
19+
) {}
20+
21+
static constructFromServiceResolver(serviceResolver: ServiceResolver): PostSearchEndpoint {
22+
return new PostSearchEndpoint(
23+
serviceResolver.getServiceOrFail<LoggerInterface>(ServiceIdentifier.logger),
24+
serviceResolver.getServiceOrFail<FetchHelper>(ServiceIdentifier.serviceFetchHelper),
25+
);
26+
}
27+
28+
async postSearch(
29+
steps: Step[],
30+
parameters: null | Record<string, unknown> = null,
31+
debug: boolean = false,
32+
): Promise<ParsedResponse<StepResult[]>> {
33+
try {
34+
const url = this.fetchHelper.buildUrl('/search');
35+
this.logger.debug(`Executing HTTP POST request against URL: ${url}`);
36+
37+
const response = await fetch(
38+
url,
39+
this.fetchHelper.getDefaultPostOptions(
40+
JSON.stringify({
41+
steps: steps,
42+
parameters: parameters,
43+
debug: debug,
44+
}),
45+
),
46+
).catch((error) => this.fetchHelper.rethrowErrorAsNetworkError(error));
47+
48+
const rawData = await this.fetchHelper.parseJsonResponse(response);
49+
50+
if (!('results' in rawData)) {
51+
throw new Error("Search result did not contain property 'results'.");
52+
}
53+
54+
return {
55+
data: rawData.results as StepResult[],
56+
response: response,
57+
};
58+
} catch (error) {
59+
this.fetchHelper.logAndThrowError(error);
60+
}
61+
}
62+
}
63+
64+
export { PostSearchEndpoint };

core/src/Service/ApiWrapper.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
PostIndexEndpoint,
1818
PutElementEndpoint,
1919
} from '../Endpoint/Element/index.js';
20+
import { PostSearchEndpoint } from '../Endpoint/Search/PostSearchEndpoint.js';
2021
import {
2122
DeleteTokenEndpoint,
2223
GetMeEndpoint,
@@ -38,6 +39,8 @@ import {
3839
Uuid,
3940
} from '../Type/Definition/index.js';
4041
import { ParsedResponse } from '../Type/Definition/Response/index.js';
42+
import { Step } from '../Type/Definition/Search/Step/index.js';
43+
import { StepResult } from '../Type/Definition/Search/StepResult/index.js';
4144
import { ServiceIdentifier } from '../Type/Enum/index.js';
4245

4346
class ApiWrapper {
@@ -60,6 +63,7 @@ class ApiWrapper {
6063
private postTokenEndpoint: PostTokenEndpoint,
6164
private getTokenEndpoint: GetTokenEndpoint,
6265
private deleteTokenEndpoint: DeleteTokenEndpoint,
66+
private postSearchEndpoint: PostSearchEndpoint,
6367
private elementCache: ElementCache,
6468
private elementChildrenCache: ElementChildrenCache,
6569
private elementParentsCache: ElementParentsCache,
@@ -93,6 +97,7 @@ class ApiWrapper {
9397
serviceResolver.getServiceOrFail<PostTokenEndpoint>(ServiceIdentifier.endpointUserPostTokenEndpoint),
9498
serviceResolver.getServiceOrFail<GetTokenEndpoint>(ServiceIdentifier.endpointUserGetTokenEndpoint),
9599
serviceResolver.getServiceOrFail<DeleteTokenEndpoint>(ServiceIdentifier.endpointUserDeleteTokenEndpoint),
100+
serviceResolver.getServiceOrFail<PostSearchEndpoint>(ServiceIdentifier.endpointSearchPostSearchEndpoint),
96101
serviceResolver.getServiceOrFail<ElementCache>(ServiceIdentifier.cacheElement),
97102
serviceResolver.getServiceOrFail<ElementChildrenCache>(ServiceIdentifier.cacheElementChildren),
98103
serviceResolver.getServiceOrFail<ElementParentsCache>(ServiceIdentifier.cacheElementParents),
@@ -537,6 +542,18 @@ class ApiWrapper {
537542
public async deleteToken(): Promise<void> {
538543
await this.deleteTokenEndpoint.deleteToken();
539544
}
545+
546+
/**
547+
* @experimental
548+
*/
549+
public async postSearch(
550+
steps: Step[],
551+
parameters: null | Record<string, unknown> = null,
552+
debug: boolean = false,
553+
): Promise<StepResult[]> {
554+
const parsedResponse = await this.postSearchEndpoint.postSearch(steps, parameters, debug);
555+
return parsedResponse.data;
556+
}
540557
}
541558

542559
export { ApiWrapper };
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Step } from './Step.js';
2+
3+
interface CypherPathSubsetStep extends Step {
4+
type: 'cypher-path-subset';
5+
query: string;
6+
}
7+
8+
export { CypherPathSubsetStep };
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Step } from './Step.js';
2+
3+
interface ElasticsearchQueryDSLMixinStep extends Step {
4+
type: 'elasticsearch-query-dsl-mixin';
5+
query: Record<string, unknown>;
6+
parameters?: {
7+
nodeTypes?: string[];
8+
relationTypes?: string[];
9+
page?: number;
10+
pageSize?: number;
11+
minScore?: null | number;
12+
};
13+
}
14+
15+
export { ElasticsearchQueryDSLMixinStep };
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Step } from './Step.js';
2+
import { Uuid } from '../../Uuid.js';
3+
4+
interface ElementHydrationStep extends Step {
5+
type: 'element-hydration';
6+
query: {
7+
elementIds: Uuid[] | string;
8+
};
9+
}
10+
11+
export { ElementHydrationStep };
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
interface Step {
2+
type: string;
3+
query?: null | string | Record<string, unknown>;
4+
parameters?: Record<string, unknown>;
5+
}
6+
7+
export { Step };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './CypherPathSubsetStep.js';
2+
export * from './ElasticsearchQueryDSLMixinStep.js';
3+
export * from './ElementHydrationStep.js';
4+
export * from './Step.js';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Uuid } from '../../Uuid.js';
2+
3+
interface CypherPathSubsetStepResult {
4+
paths: {
5+
nodeIds: Uuid[];
6+
relationIds: Uuid[];
7+
};
8+
}
9+
10+
export { CypherPathSubsetStepResult };
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Uuid } from '../../Uuid.js';
2+
3+
interface ElasticsearchQueryDSLMixinStepResult {
4+
elements: {
5+
id: Uuid;
6+
type: string;
7+
metadata: {
8+
score: number;
9+
};
10+
}[];
11+
totalHits: number;
12+
maxScore: number;
13+
}
14+
15+
export { ElasticsearchQueryDSLMixinStepResult };

0 commit comments

Comments
 (0)