@lde/search-api-graphql 0.12.3 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"build-schema.d.ts","sourceRoot":"","sources":["../src/build-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,aAAa,EAQd,MAAM,SAAS,CAAC;AACjB,OAAO,EAIL,KAAK,YAAY,EAEjB,KAAK,WAAW,EAChB,KAAK,YAAY,EAElB,MAAM,aAAa,CAAC;AAWrB,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAGvB,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B;;sDAEkD;IAClD,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,mFAAmF;IACnF,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACjE;AAED;2EAC2E;AAC3E,MAAM,WAAW,iBAAiB;IAChC;yCACqC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;uBACmB;IACnB,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,aAAa,KACnB,WAAW,CAAC;CAClB;AAED,MAAM,WAAW,yBAAyB;IACxC;;kEAE8D;IAC9D,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC7D,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC;;gDAE4C;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAkBD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,yBAA8B,GACtC,aAAa,CAoXf;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,yBAA8B,GACtC,MAAM,CAER"}
1
+ {"version":3,"file":"build-schema.d.ts","sourceRoot":"","sources":["../src/build-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,aAAa,EAQd,MAAM,SAAS,CAAC;AACjB,OAAO,EAIL,KAAK,YAAY,EAEjB,KAAK,WAAW,EAChB,KAAK,YAAY,EAElB,MAAM,aAAa,CAAC;AAYrB,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAGvB,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B;;sDAEkD;IAClD,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,mFAAmF;IACnF,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACjE;AAED;2EAC2E;AAC3E,MAAM,WAAW,iBAAiB;IAChC;yCACqC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;uBACmB;IACnB,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,aAAa,KACnB,WAAW,CAAC;CAClB;AAED,MAAM,WAAW,yBAAyB;IACxC;;kEAE8D;IAC9D,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC7D,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC;;gDAE4C;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAkBD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,yBAA8B,GACtC,aAAa,CA6Xf;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,yBAA8B,GACtC,MAAM,CAER"}
@@ -1,5 +1,5 @@
1
1
  import { GraphQLBoolean, GraphQLEnumType, GraphQLFloat, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString, printSchema, } from 'graphql';
2
- import { facetableFields, filterableFields, filterOperatorFor, isRangeFacet, outputFields, pageForOffset, sortableFields, unixSecondsToIso, } from '@lde/search/adapter';
2
+ import { facetableFields, filterableFields, filterOperatorFor, ID_FIELD, isRangeFacet, outputFields, pageForOffset, sortableFields, unixSecondsToIso, } from '@lde/search/adapter';
3
3
  import { defaultLanguageOrder, toLanguageStrings, } from './language.js';
4
4
  import { createFacetLoader } from './facet-batch.js';
5
5
  const nonNullListOf = (type) => new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(type)));
@@ -63,6 +63,16 @@ export function buildGraphQLSchema(schema, options = {}) {
63
63
  count: { type: new GraphQLNonNull(GraphQLInt) },
64
64
  },
65
65
  });
66
+ // The pagination actually applied (after queryDefaults), shared across every
67
+ // ‹Type›SearchResult so one client pager fragment serves all root types.
68
+ const paginationType = new GraphQLObjectType({
69
+ name: 'Pagination',
70
+ fields: {
71
+ total: { type: new GraphQLNonNull(GraphQLInt) },
72
+ page: { type: new GraphQLNonNull(GraphQLInt) },
73
+ perPage: { type: new GraphQLNonNull(GraphQLInt) },
74
+ },
75
+ });
66
76
  const sortDirection = new GraphQLEnumType({
67
77
  name: 'SortDirection',
68
78
  values: { ASC: { value: 'asc' }, DESC: { value: 'desc' } },
@@ -196,22 +206,22 @@ export function buildGraphQLSchema(schema, options = {}) {
196
206
  return fields;
197
207
  },
198
208
  });
199
- // A GraphQL input object must have at least one field, so a type with no
200
- // filterable fields gets no `where` arg at all rather than an invalid
201
- // empty input.
209
+ // Every type is filterable on `id`, so the input always has at least one
210
+ // field and always exists – no type is unaddressable by IRI, whatever it
211
+ // declares.
202
212
  const filterable = filterableFields(searchType);
203
- const whereInput = filterable.length === 0
204
- ? undefined
205
- : new GraphQLInputObjectType({
206
- name: `${typeName}Where`,
207
- fields: () => {
208
- const fields = {};
209
- for (const field of filterable) {
210
- fields[field.name] = { type: whereFieldType(field) };
211
- }
212
- return fields;
213
- },
214
- });
213
+ const whereInput = new GraphQLInputObjectType({
214
+ name: `${typeName}Where`,
215
+ fields: () => {
216
+ const fields = {
217
+ [ID_FIELD]: { type: stringFilter },
218
+ };
219
+ for (const field of filterable) {
220
+ fields[field.name] = { type: whereFieldType(field) };
221
+ }
222
+ return fields;
223
+ },
224
+ });
215
225
  const sortValues = {
216
226
  RELEVANCE: { value: 'relevance' },
217
227
  };
@@ -245,9 +255,7 @@ export function buildGraphQLSchema(schema, options = {}) {
245
255
  name: `${typeName}SearchResult`,
246
256
  fields: {
247
257
  items: { type: nonNullListOf(outputType) },
248
- total: { type: new GraphQLNonNull(GraphQLInt) },
249
- page: { type: new GraphQLNonNull(GraphQLInt) },
250
- perPage: { type: new GraphQLNonNull(GraphQLInt) },
258
+ pagination: { type: new GraphQLNonNull(paginationType) },
251
259
  // Resolved lazily, per selected key (skip-own-filter); the result object
252
260
  // (which carries the per-request facet loader) is the facets source.
253
261
  ...(facetsType && {
@@ -262,7 +270,7 @@ export function buildGraphQLSchema(schema, options = {}) {
262
270
  type: new GraphQLNonNull(resultType),
263
271
  args: {
264
272
  query: { type: GraphQLString },
265
- ...(whereInput && { where: { type: whereInput } }),
273
+ where: { type: whereInput },
266
274
  orderBy: { type: orderByInput },
267
275
  page: { type: GraphQLInt, defaultValue: 1 },
268
276
  perPage: { type: GraphQLInt, defaultValue: 20 },
@@ -279,9 +287,11 @@ export function buildGraphQLSchema(schema, options = {}) {
279
287
  });
280
288
  return {
281
289
  items: result.hits.map((hit) => ({ id: hit.id, ...hit.document })),
282
- total: result.total,
283
- page: pageForOffset(finalQuery.offset, finalQuery.limit),
284
- perPage: finalQuery.limit,
290
+ pagination: {
291
+ total: result.total,
292
+ page: pageForOffset(finalQuery.offset, finalQuery.limit),
293
+ perPage: finalQuery.limit,
294
+ },
285
295
  // Carried for the facet field resolvers (see facet-batch.ts).
286
296
  loadFacet: createFacetLoader(context.engine, searchType, finalQuery, context.onFacetError),
287
297
  };
@@ -362,6 +372,11 @@ function whereToFilters(where, searchType) {
362
372
  return [];
363
373
  }
364
374
  const filters = [];
375
+ // `id` first: it is declared by no type, so the field loop below never sees it.
376
+ const id = where[ID_FIELD];
377
+ if (id !== undefined && id !== null) {
378
+ filters.push({ field: ID_FIELD, in: id.in ?? [] });
379
+ }
365
380
  for (const field of filterableFields(searchType)) {
366
381
  const value = where[field.name];
367
382
  if (value === undefined || value === null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/search-api-graphql",
3
- "version": "0.12.3",
3
+ "version": "0.14.0",
4
4
  "description": "Engine- and domain-agnostic GraphQL surface for @lde/search: builds an executable GraphQLSchema from a whole SearchSchema at runtime (no codegen) – one root query field per SearchType – served by generic resolvers over any SearchEngine – and serves it as a framework-agnostic fetch handler with a self-contained playground, SDL endpoint, CORS and depth/cost limits. You supply the schema and per-type typeNames; it names neither your domain nor your engine.",
5
5
  "repository": {
6
6
  "url": "git+https://github.com/ldelements/lde.git",
@@ -28,7 +28,7 @@
28
28
  "@escape.tech/graphql-armor-cost-limit": "^2.4.3",
29
29
  "@escape.tech/graphql-armor-max-depth": "^2.4.2",
30
30
  "@graphql-yoga/render-graphiql": "^5.21.2",
31
- "@lde/search": "^0.12.3",
31
+ "@lde/search": "^0.13.0",
32
32
  "dataloader": "^2.2.3",
33
33
  "graphql": "^16.0.0",
34
34
  "graphql-yoga": "^5.21.2",