• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sajov / feathers-solr / 10839362906

12 Sep 2024 09:59PM UTC coverage: 100.0%. Remained the same
10839362906

Pull #303

github

web-flow
Merge 8f64c316b into d90addfe5
Pull Request #303: Bump @types/mocha from 10.0.7 to 10.0.8

119 of 119 branches covered (100.0%)

149 of 149 relevant lines covered (100.0%)

189.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/adapter.ts
1
import { BadRequest, MethodNotAllowed, NotFound } from '@feathersjs/errors'
1✔
2
import { _ } from '@feathersjs/commons'
1✔
3
import { httpClient } from './httpClient';
1✔
4
import { addIds } from './utils/addIds';
1✔
5
import { filterResolver } from './utils/filterResolver';
1✔
6
import { convertOperators } from './utils/convertOperators';
1✔
7
import {
1✔
8
  AdapterBase,
9
  select,
10
  AdapterParams,
11
  AdapterServiceOptions,
12
  PaginationOptions,
13
  AdapterQuery
14
} from '@feathersjs/adapter-commons'
15
import type { NullableId, Id, Paginated } from '@feathersjs/feathers'
16
import type { HttpClient, RequestOptions } from './httpClient';
17

18
export interface SolrAdapterOptions extends AdapterServiceOptions {
19
  host: string;
20
  core: string;
21
  commit?: {
22
    softCommit?: boolean;
23
    commitWithin?: number;
24
    overwrite?: boolean
25
  };
26
  queryHandler?: string;
27
  updateHandler?: string;
28
  defaultSearch?: any;
29
  defaultParams?: any;
30
  createUUID?: boolean;
31
  requestOptions?: RequestOptions['requestOptions'];
32
  escapeFn?: (key: string, value: any) => { key: string, value: any };
33
  logger?: (msg: any) => any;
34
}
35

36
export type SolrAdapterParams<Q = AdapterQuery> = AdapterParams<Q, Partial<SolrAdapterOptions>>
37
type SolrQueryParams = {}
38
type SolrQueryFacet = {}
39

40
export interface SolrQuery {
41
  query: string;
42
  fields: string;
43
  limit: number;
44
  offset: number;
45
  sort?: string;
46
  filter?: string[];
47
  params?: SolrQueryParams
48
  facet?: SolrQueryFacet
49
}
50

51
export class SolrAdapter<
1✔
52
  Result,
53
  Data = Partial<Result>,
54
  ServiceParams extends SolrAdapterParams<any> = SolrAdapterParams,
55
  PatchData = Partial<Data>
56
> extends AdapterBase<Result, Data, PatchData, ServiceParams, SolrAdapterOptions> {
57
  client: HttpClient;
58
  queryHandler: string;
59
  updateHandler: string;
60

61
  constructor(options: SolrAdapterOptions) {
62
    const { host, core, requestOptions, ...opts } = options;
15✔
63
    super(_.extend({
15✔
64
      id: 'id',
65
      commit: {
66
        softCommit: true,
67
        commitWithin: 10000,
68
        overwrite: true
69
      },
70
      queryHandler: '/query',
71
      updateHandler: '/update/json',
72
      defaultSearch: {},
73
      defaultParams: { echoParams: 'none' },
74
      createUUID: true,
75
      escapeFn: (key: string, value: any) => ({ key, value }),
434✔
76
      logger: (msg: any): any => msg
662✔
77
    }, opts));
78

79
    this.queryHandler = `/${core}${this.options.queryHandler}`;
15✔
80
    this.updateHandler = `/${core}${this.options.updateHandler}`;
15✔
81
    this.client = httpClient(host, requestOptions, this.options.logger)
15✔
82
  }
83

84
  filterQuery(id: NullableId | Id, params: ServiceParams) {
85
    const { paginate } = this.getOptions(params);
522✔
86
    const { $search, $params, $select, $filter, $facet, ...adapterQuery } = params.query || {};
522✔
87
    const { $skip, $sort, $limit, ...filter } = adapterQuery;
522✔
88

89
    return {
522✔
90
      query: {
91
        query: filterResolver.$search($search),
92
        fields: filterResolver.$select($select),
93
        limit: filterResolver.$limit($limit, paginate),
94
        offset: filterResolver.$skip($skip),
95
        filter: [
96
          ...(id ? convertOperators({ [this.options.id]: id }, this.options.escapeFn) : []),
522✔
97
          ...(!_.isEmpty(filter) ? convertOperators(filter, this.options.escapeFn) : []),
522✔
98
          ...($filter ? $filter : [])
522✔
99
        ],
100
        ...($sort && { sort: filterResolver.$sort($sort) }),
540✔
101
        ...($facet && { facet: $facet }),
525✔
102
        ...($params && { params: $params })
527✔
103
      },
104
      paginate
105
    };
106
  }
107

108
  async _getOrFind(id: NullableId | NullableId, params: ServiceParams) {
109
    if (id !== null) return this._get(id, params);
214✔
110

111
    return this._find(
22✔
112
      Object.assign(params, { paginate: false })
113
    );
114
  }
115

116
  async _get(id: Id | NullableId, params: ServiceParams = {} as ServiceParams): Promise<Result> {
8✔
117
    const { query } = this.filterQuery(id, params);
207✔
118
    const { response } = await this.client.post(this.queryHandler, { data: query })
207✔
119

120
    if (response.numFound === 0) throw new NotFound(`No record found for id '${id}'`);
207✔
121

122
    return response.docs[0];
189✔
123
  }
124

125
  async _find(params?: ServiceParams & { paginate?: PaginationOptions }): Promise<Paginated<Result>>
126
  async _find(params?: ServiceParams & { paginate: false }): Promise<Result[]>
127
  async _find(params?: ServiceParams): Promise<Paginated<Result> | Result[]>
128
  async _find(params: ServiceParams = {} as ServiceParams): Promise<Paginated<Result> | Result[]> {
1✔
129
    const { query, paginate } = this.filterQuery(null, params);
96✔
130
    const {
131
      responseHeader,
132
      response,
133
      grouped, ...additionalResponse
134
    } = await this.client.post(this.queryHandler, { data: query });
96✔
135
    const groupKey: string = grouped ? Object.keys(grouped)[0] : undefined;
96✔
136
    const data = response ? response.docs : grouped[groupKey].groups || grouped[groupKey].doclist.docs;
96✔
137

138
    if (!paginate) return data;
96✔
139

140
    return {
19✔
141
      QTime: responseHeader.QTime || 0,
29✔
142
      total: response ? response.numFound : grouped[groupKey].matches,
19✔
143
      limit: query.limit,
144
      skip: query.offset,
145
      data,
146
      ...additionalResponse
147
    }
148
  }
149

150
  async _create(data: Data, params?: ServiceParams): Promise<Result>
151
  async _create(data: Data[], params?: ServiceParams): Promise<Result[]>
152
  async _create(data: Data | Data[], _params?: ServiceParams): Promise<Result | Result[]>
153
  async _create(
154
    data: Data | Data[],
155
    params: ServiceParams = {} as ServiceParams
13✔
156
  ): Promise<Result | Result[]> {
157
  if (_.isEmpty(data)) throw new MethodNotAllowed('Data is empty');
164✔
158

159
    const sel = select(params, this.id);
161✔
160

161
    let dataToCreate: any | any[] = Array.isArray(data) ? [...data] : [{ ...data }];
161✔
162

163
    if (this.options.createUUID) {
161✔
164
      dataToCreate = addIds(dataToCreate, this.options.id);
160✔
165
    }
166

167
    await this.client.post(this.updateHandler, { data: dataToCreate, params: this.options.commit });
161✔
168

169
    return sel(Array.isArray(data) ? dataToCreate : dataToCreate[0]);
161✔
170
  }
171

172
  async _patch(id: null, data: PatchData, params?: ServiceParams): Promise<Result[]>
173
  async _patch(id: NullableId, data: PatchData, params?: ServiceParams): Promise<Result>
174
  async _patch(id: NullableId, data: PatchData, _params?: ServiceParams): Promise<Result | Result[]>
175
  async _patch(
176
    id: NullableId,
177
    _data: PatchData,
178
    params: ServiceParams = {} as ServiceParams
9✔
179
  ): Promise<Result | Result[]> {
180
    if (id === null && !this.allowsMulti('patch', params)) {
26✔
181
      throw new MethodNotAllowed('Can not patch multiple entries')
2✔
182
    }
183
    const sel = select(params, this.id);
24✔
184
    const response = await this._getOrFind(id, params);
24✔
185
    const dataToPatch = Array.isArray(response) ? response : [response];
20✔
186
    const patchData = dataToPatch.map((current: any) => ({
41✔
187
      [this.id]: current[this.id], ...Object.fromEntries(
188
        Object.entries(_data)
189
          .filter(([field]) => field !== this.id)
43✔
190
          .map(([field, value]) => (
191
            [field, _.isObject(value) ? value : value === undefined ? { set: null } : { set: value }]
41✔
192
          )
193
          )
194
      )
195
    }));
196

197
    await this.client.post(this.updateHandler, { data: patchData, params: this.options.commit });
20✔
198

199
    const ids = dataToPatch.map((d: any) => d[this.id]);
41✔
200
    const result = await this._find({ ...params, query: { id: { $in: ids } } });
20✔
201

202
    return sel(ids.length === 1 && Array.isArray(result) && result.length > 0 ? result[0] : result)
20✔
203
  }
204

205
  async _update(id: NullableId, data: Data, params: ServiceParams = {} as ServiceParams): Promise<Result> {
2✔
206
    if (id === null || Array.isArray(data)) {
11✔
207
      throw new BadRequest('You can not replace multiple instances. Did you mean \'patch\'?')
2✔
208
    }
209
    const sel = select(params, this.id);
9✔
210

211
    await this._getOrFind(id, params);
9✔
212

213
    await this.client.post(this.updateHandler, {
5✔
214
      data: [{ ...data, id }],
215
      params: this.options.commit
216
    });
217

218
    return this._getOrFind(id, params)
5✔
219
      .then(res => sel(_.omit(res, 'score', '_version_')));
5✔
220
  }
221

222
  async _remove(id: null, params?: ServiceParams): Promise<Result[]>
223
  async _remove(id: NullableId, params?: ServiceParams): Promise<Result>
224
  async _remove(id: NullableId, _params?: ServiceParams): Promise<Result | Result[]>
225
  async _remove(
226
    id: NullableId,
227
    params: ServiceParams = {} as ServiceParams
1✔
228
  ): Promise<Result | Result[]> {
229
    if (id === null && !this.allowsMulti('remove', params)) {
177✔
230
      throw new MethodNotAllowed('Can not remove multiple entries')
1✔
231
    }
232
    const sel = select(params, this.id);
176✔
233
    const dataToDelete = await this._getOrFind(id, params);
176✔
234
    const { query } = this.filterQuery(id, params);
169✔
235
    const queryToDelete = id ?
169✔
236
      { delete: id } :
237
      query.filter.length > 0 ?
14✔
238
        { delete: { query: query.filter.join(' AND ') } } :
239
        { delete: { query: '*:*' } };
240

241
    await this.client.post(this.updateHandler, { data: queryToDelete, params: this.options.commit });
169✔
242

243
    return sel(dataToDelete);
169✔
244
  }
245
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc