@digital-science-dsl/dimensions-analytics-mcp 0.5.3
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.
- package/CHANGELOG.md +61 -0
- package/README.md +152 -0
- package/bin/dimensions-analytics-mcp-http.js +2 -0
- package/bin/dimensions-analytics-mcp.js +2 -0
- package/dist/client/auth/config-loaders.d.ts +22 -0
- package/dist/client/auth/config-loaders.d.ts.map +1 -0
- package/dist/client/auth/config-loaders.js +68 -0
- package/dist/client/auth/config-loaders.js.map +1 -0
- package/dist/client/auth/decorators/index.d.ts +7 -0
- package/dist/client/auth/decorators/index.d.ts.map +1 -0
- package/dist/client/auth/decorators/index.js +6 -0
- package/dist/client/auth/decorators/index.js.map +1 -0
- package/dist/client/auth/decorators/with-caching.d.ts +81 -0
- package/dist/client/auth/decorators/with-caching.d.ts.map +1 -0
- package/dist/client/auth/decorators/with-caching.js +168 -0
- package/dist/client/auth/decorators/with-caching.js.map +1 -0
- package/dist/client/auth/factory.d.ts +13 -0
- package/dist/client/auth/factory.d.ts.map +1 -0
- package/dist/client/auth/factory.js +29 -0
- package/dist/client/auth/factory.js.map +1 -0
- package/dist/client/auth/index.d.ts +7 -0
- package/dist/client/auth/index.d.ts.map +1 -0
- package/dist/client/auth/index.js +6 -0
- package/dist/client/auth/index.js.map +1 -0
- package/dist/client/auth/jwt-utils.d.ts +17 -0
- package/dist/client/auth/jwt-utils.d.ts.map +1 -0
- package/dist/client/auth/jwt-utils.js +33 -0
- package/dist/client/auth/jwt-utils.js.map +1 -0
- package/dist/client/auth/node.d.ts +16 -0
- package/dist/client/auth/node.d.ts.map +1 -0
- package/dist/client/auth/node.js +15 -0
- package/dist/client/auth/node.js.map +1 -0
- package/dist/client/auth/providers/index.d.ts +2 -0
- package/dist/client/auth/providers/index.d.ts.map +1 -0
- package/dist/client/auth/providers/index.js +2 -0
- package/dist/client/auth/providers/index.js.map +1 -0
- package/dist/client/auth/providers/jwt-auth-provider.d.ts +70 -0
- package/dist/client/auth/providers/jwt-auth-provider.d.ts.map +1 -0
- package/dist/client/auth/providers/jwt-auth-provider.js +134 -0
- package/dist/client/auth/providers/jwt-auth-provider.js.map +1 -0
- package/dist/client/auth/schemas.d.ts +19 -0
- package/dist/client/auth/schemas.d.ts.map +1 -0
- package/dist/client/auth/schemas.js +23 -0
- package/dist/client/auth/schemas.js.map +1 -0
- package/dist/client/auth/types.d.ts +25 -0
- package/dist/client/auth/types.d.ts.map +1 -0
- package/dist/client/auth/types.js +9 -0
- package/dist/client/auth/types.js.map +1 -0
- package/dist/client/command.d.ts +44 -0
- package/dist/client/command.d.ts.map +1 -0
- package/dist/client/command.js +23 -0
- package/dist/client/command.js.map +1 -0
- package/dist/client/config/index.d.ts +11 -0
- package/dist/client/config/index.d.ts.map +1 -0
- package/dist/client/config/index.js +8 -0
- package/dist/client/config/index.js.map +1 -0
- package/dist/client/config/loader.d.ts +27 -0
- package/dist/client/config/loader.d.ts.map +1 -0
- package/dist/client/config/loader.js +128 -0
- package/dist/client/config/loader.js.map +1 -0
- package/dist/client/config/node.d.ts +18 -0
- package/dist/client/config/node.d.ts.map +1 -0
- package/dist/client/config/node.js +17 -0
- package/dist/client/config/node.js.map +1 -0
- package/dist/client/config/schemas.d.ts +23 -0
- package/dist/client/config/schemas.d.ts.map +1 -0
- package/dist/client/config/schemas.js +24 -0
- package/dist/client/config/schemas.js.map +1 -0
- package/dist/client/config/types.d.ts +37 -0
- package/dist/client/config/types.d.ts.map +1 -0
- package/dist/client/config/types.js +6 -0
- package/dist/client/config/types.js.map +1 -0
- package/dist/client/config/unified-loader.d.ts +41 -0
- package/dist/client/config/unified-loader.d.ts.map +1 -0
- package/dist/client/config/unified-loader.js +86 -0
- package/dist/client/config/unified-loader.js.map +1 -0
- package/dist/client/config.d.ts +43 -0
- package/dist/client/config.d.ts.map +1 -0
- package/dist/client/config.js +56 -0
- package/dist/client/config.js.map +1 -0
- package/dist/client/deployment-config.d.ts +36 -0
- package/dist/client/deployment-config.d.ts.map +1 -0
- package/dist/client/deployment-config.js +79 -0
- package/dist/client/deployment-config.js.map +1 -0
- package/dist/client/errors.d.ts +180 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +253 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/http-client.d.ts +215 -0
- package/dist/client/http-client.d.ts.map +1 -0
- package/dist/client/http-client.js +419 -0
- package/dist/client/http-client.js.map +1 -0
- package/dist/client/index.d.ts +28 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +18 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/internal-dsl-client.d.ts +30 -0
- package/dist/client/internal-dsl-client.d.ts.map +1 -0
- package/dist/client/internal-dsl-client.js +101 -0
- package/dist/client/internal-dsl-client.js.map +1 -0
- package/dist/client/rate-limiter.d.ts +79 -0
- package/dist/client/rate-limiter.d.ts.map +1 -0
- package/dist/client/rate-limiter.js +118 -0
- package/dist/client/rate-limiter.js.map +1 -0
- package/dist/client/resolve-api-key-user.d.ts +24 -0
- package/dist/client/resolve-api-key-user.d.ts.map +1 -0
- package/dist/client/resolve-api-key-user.js +80 -0
- package/dist/client/resolve-api-key-user.js.map +1 -0
- package/dist/client/types.d.ts +44 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +10 -0
- package/dist/client/types.js.map +1 -0
- package/dist/dsl/client.d.ts +330 -0
- package/dist/dsl/client.d.ts.map +1 -0
- package/dist/dsl/client.js +433 -0
- package/dist/dsl/client.js.map +1 -0
- package/dist/dsl/commands/apply-filters.d.ts +15 -0
- package/dist/dsl/commands/apply-filters.d.ts.map +1 -0
- package/dist/dsl/commands/apply-filters.js +29 -0
- package/dist/dsl/commands/apply-filters.js.map +1 -0
- package/dist/dsl/commands/facet/index.d.ts +6 -0
- package/dist/dsl/commands/facet/index.d.ts.map +1 -0
- package/dist/dsl/commands/facet/index.js +6 -0
- package/dist/dsl/commands/facet/index.js.map +1 -0
- package/dist/dsl/commands/facet/schemas.d.ts +71 -0
- package/dist/dsl/commands/facet/schemas.d.ts.map +1 -0
- package/dist/dsl/commands/facet/schemas.js +84 -0
- package/dist/dsl/commands/facet/schemas.js.map +1 -0
- package/dist/dsl/commands/functions/ClassifyCommand.d.ts +96 -0
- package/dist/dsl/commands/functions/ClassifyCommand.d.ts.map +1 -0
- package/dist/dsl/commands/functions/ClassifyCommand.js +95 -0
- package/dist/dsl/commands/functions/ClassifyCommand.js.map +1 -0
- package/dist/dsl/commands/functions/ExtractAffiliationsCommand.d.ts +86 -0
- package/dist/dsl/commands/functions/ExtractAffiliationsCommand.d.ts.map +1 -0
- package/dist/dsl/commands/functions/ExtractAffiliationsCommand.js +131 -0
- package/dist/dsl/commands/functions/ExtractAffiliationsCommand.js.map +1 -0
- package/dist/dsl/commands/functions/ExtractConceptsCommand.d.ts +73 -0
- package/dist/dsl/commands/functions/ExtractConceptsCommand.d.ts.map +1 -0
- package/dist/dsl/commands/functions/ExtractConceptsCommand.js +110 -0
- package/dist/dsl/commands/functions/ExtractConceptsCommand.js.map +1 -0
- package/dist/dsl/commands/functions/ExtractGrantsCommand.d.ts +66 -0
- package/dist/dsl/commands/functions/ExtractGrantsCommand.d.ts.map +1 -0
- package/dist/dsl/commands/functions/ExtractGrantsCommand.js +87 -0
- package/dist/dsl/commands/functions/ExtractGrantsCommand.js.map +1 -0
- package/dist/dsl/commands/functions/index.d.ts +10 -0
- package/dist/dsl/commands/functions/index.d.ts.map +1 -0
- package/dist/dsl/commands/functions/index.js +10 -0
- package/dist/dsl/commands/functions/index.js.map +1 -0
- package/dist/dsl/commands/index.d.ts +8 -0
- package/dist/dsl/commands/index.d.ts.map +1 -0
- package/dist/dsl/commands/index.js +7 -0
- package/dist/dsl/commands/index.js.map +1 -0
- package/dist/dsl/commands/validate-input.d.ts +18 -0
- package/dist/dsl/commands/validate-input.d.ts.map +1 -0
- package/dist/dsl/commands/validate-input.js +27 -0
- package/dist/dsl/commands/validate-input.js.map +1 -0
- package/dist/dsl/create-client.d.ts +32 -0
- package/dist/dsl/create-client.d.ts.map +1 -0
- package/dist/dsl/create-client.js +58 -0
- package/dist/dsl/create-client.js.map +1 -0
- package/dist/dsl/fluent-query-builder.d.ts +439 -0
- package/dist/dsl/fluent-query-builder.d.ts.map +1 -0
- package/dist/dsl/fluent-query-builder.js +534 -0
- package/dist/dsl/fluent-query-builder.js.map +1 -0
- package/dist/dsl/index.d.ts +32 -0
- package/dist/dsl/index.d.ts.map +1 -0
- package/dist/dsl/index.js +31 -0
- package/dist/dsl/index.js.map +1 -0
- package/dist/dsl/pagination.d.ts +82 -0
- package/dist/dsl/pagination.d.ts.map +1 -0
- package/dist/dsl/pagination.js +135 -0
- package/dist/dsl/pagination.js.map +1 -0
- package/dist/dsl/query-builder.d.ts +456 -0
- package/dist/dsl/query-builder.d.ts.map +1 -0
- package/dist/dsl/query-builder.js +994 -0
- package/dist/dsl/query-builder.js.map +1 -0
- package/dist/dsl/response-parser.d.ts +111 -0
- package/dist/dsl/response-parser.d.ts.map +1 -0
- package/dist/dsl/response-parser.js +210 -0
- package/dist/dsl/response-parser.js.map +1 -0
- package/dist/dsl/schema/cache.d.ts +52 -0
- package/dist/dsl/schema/cache.d.ts.map +1 -0
- package/dist/dsl/schema/cache.js +97 -0
- package/dist/dsl/schema/cache.js.map +1 -0
- package/dist/dsl/schema/extract.d.ts +18 -0
- package/dist/dsl/schema/extract.d.ts.map +1 -0
- package/dist/dsl/schema/extract.js +41 -0
- package/dist/dsl/schema/extract.js.map +1 -0
- package/dist/dsl/schema/index.d.ts +13 -0
- package/dist/dsl/schema/index.d.ts.map +1 -0
- package/dist/dsl/schema/index.js +12 -0
- package/dist/dsl/schema/index.js.map +1 -0
- package/dist/dsl/schema/load.d.ts +41 -0
- package/dist/dsl/schema/load.d.ts.map +1 -0
- package/dist/dsl/schema/load.js +119 -0
- package/dist/dsl/schema/load.js.map +1 -0
- package/dist/dsl/schema/store.d.ts +137 -0
- package/dist/dsl/schema/store.d.ts.map +1 -0
- package/dist/dsl/schema/store.js +204 -0
- package/dist/dsl/schema/store.js.map +1 -0
- package/dist/dsl/schema/structured-entities.d.ts +21 -0
- package/dist/dsl/schema/structured-entities.d.ts.map +1 -0
- package/dist/dsl/schema/structured-entities.js +39 -0
- package/dist/dsl/schema/structured-entities.js.map +1 -0
- package/dist/dsl/schema/summary.d.ts +51 -0
- package/dist/dsl/schema/summary.d.ts.map +1 -0
- package/dist/dsl/schema/summary.js +61 -0
- package/dist/dsl/schema/summary.js.map +1 -0
- package/dist/dsl/schema/types.d.ts +36 -0
- package/dist/dsl/schema/types.d.ts.map +1 -0
- package/dist/dsl/schema/types.js +6 -0
- package/dist/dsl/schema/types.js.map +1 -0
- package/dist/dsl/schema/validation.d.ts +21 -0
- package/dist/dsl/schema/validation.d.ts.map +1 -0
- package/dist/dsl/schema/validation.js +44 -0
- package/dist/dsl/schema/validation.js.map +1 -0
- package/dist/dsl/types/buckets.d.ts +18 -0
- package/dist/dsl/types/buckets.d.ts.map +1 -0
- package/dist/dsl/types/buckets.js +6 -0
- package/dist/dsl/types/buckets.js.map +1 -0
- package/dist/dsl/types/common.d.ts +91 -0
- package/dist/dsl/types/common.d.ts.map +1 -0
- package/dist/dsl/types/common.js +6 -0
- package/dist/dsl/types/common.js.map +1 -0
- package/dist/dsl/types/entities.d.ts +35 -0
- package/dist/dsl/types/entities.d.ts.map +1 -0
- package/dist/dsl/types/entities.js +11 -0
- package/dist/dsl/types/entities.js.map +1 -0
- package/dist/dsl/types/index.d.ts +12 -0
- package/dist/dsl/types/index.d.ts.map +1 -0
- package/dist/dsl/types/index.js +7 -0
- package/dist/dsl/types/index.js.map +1 -0
- package/dist/dsl/types/return-clauses.d.ts +107 -0
- package/dist/dsl/types/return-clauses.d.ts.map +1 -0
- package/dist/dsl/types/return-clauses.js +22 -0
- package/dist/dsl/types/return-clauses.js.map +1 -0
- package/dist/dsl/types/special-functions.d.ts +175 -0
- package/dist/dsl/types/special-functions.d.ts.map +1 -0
- package/dist/dsl/types/special-functions.js +25 -0
- package/dist/dsl/types/special-functions.js.map +1 -0
- package/dist/dsl/types/vocabulary.d.ts +21 -0
- package/dist/dsl/types/vocabulary.d.ts.map +1 -0
- package/dist/dsl/types/vocabulary.js +44 -0
- package/dist/dsl/types/vocabulary.js.map +1 -0
- package/dist/dsl/usage-policy.d.ts +87 -0
- package/dist/dsl/usage-policy.d.ts.map +1 -0
- package/dist/dsl/usage-policy.js +133 -0
- package/dist/dsl/usage-policy.js.map +1 -0
- package/dist/dsl/utils/escape.d.ts +24 -0
- package/dist/dsl/utils/escape.d.ts.map +1 -0
- package/dist/dsl/utils/escape.js +124 -0
- package/dist/dsl/utils/escape.js.map +1 -0
- package/dist/dsl/utils/sanitize.d.ts +13 -0
- package/dist/dsl/utils/sanitize.d.ts.map +1 -0
- package/dist/dsl/utils/sanitize.js +19 -0
- package/dist/dsl/utils/sanitize.js.map +1 -0
- package/dist/dsl/utils/string-utils.d.ts +35 -0
- package/dist/dsl/utils/string-utils.d.ts.map +1 -0
- package/dist/dsl/utils/string-utils.js +145 -0
- package/dist/dsl/utils/string-utils.js.map +1 -0
- package/dist/dsl/utils/validate-field-name.d.ts +11 -0
- package/dist/dsl/utils/validate-field-name.d.ts.map +1 -0
- package/dist/dsl/utils/validate-field-name.js +18 -0
- package/dist/dsl/utils/validate-field-name.js.map +1 -0
- package/dist/examples/dsl-examples.d.ts +11 -0
- package/dist/examples/dsl-examples.d.ts.map +1 -0
- package/dist/examples/dsl-examples.js +49 -0
- package/dist/examples/dsl-examples.js.map +1 -0
- package/dist/examples/usage-scenarios.d.ts +22 -0
- package/dist/examples/usage-scenarios.d.ts.map +1 -0
- package/dist/examples/usage-scenarios.js +173 -0
- package/dist/examples/usage-scenarios.js.map +1 -0
- package/dist/funder-org-names.d.ts +12 -0
- package/dist/funder-org-names.d.ts.map +1 -0
- package/dist/funder-org-names.js +30 -0
- package/dist/funder-org-names.js.map +1 -0
- package/dist/http-main.d.ts +7 -0
- package/dist/http-main.d.ts.map +1 -0
- package/dist/http-main.js +36 -0
- package/dist/http-main.js.map +1 -0
- package/dist/http.d.ts +29 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +173 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/kwq/build-query.d.ts +26 -0
- package/dist/kwq/build-query.d.ts.map +1 -0
- package/dist/kwq/build-query.js +37 -0
- package/dist/kwq/build-query.js.map +1 -0
- package/dist/kwq/concept-groups.d.ts +49 -0
- package/dist/kwq/concept-groups.d.ts.map +1 -0
- package/dist/kwq/concept-groups.js +55 -0
- package/dist/kwq/concept-groups.js.map +1 -0
- package/dist/kwq/dsl-assembler.d.ts +28 -0
- package/dist/kwq/dsl-assembler.d.ts.map +1 -0
- package/dist/kwq/dsl-assembler.js +124 -0
- package/dist/kwq/dsl-assembler.js.map +1 -0
- package/dist/kwq/ensemble.d.ts +38 -0
- package/dist/kwq/ensemble.d.ts.map +1 -0
- package/dist/kwq/ensemble.js +107 -0
- package/dist/kwq/ensemble.js.map +1 -0
- package/dist/kwq/index.d.ts +16 -0
- package/dist/kwq/index.d.ts.map +1 -0
- package/dist/kwq/index.js +14 -0
- package/dist/kwq/index.js.map +1 -0
- package/dist/kwq/types.d.ts +120 -0
- package/dist/kwq/types.d.ts.map +1 -0
- package/dist/kwq/types.js +11 -0
- package/dist/kwq/types.js.map +1 -0
- package/dist/lambda.d.ts +10 -0
- package/dist/lambda.d.ts.map +1 -0
- package/dist/lambda.js +4 -0
- package/dist/lambda.js.map +1 -0
- package/dist/main.d.ts +8 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +33 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp/batch-fetch.d.ts +89 -0
- package/dist/mcp/batch-fetch.d.ts.map +1 -0
- package/dist/mcp/batch-fetch.js +178 -0
- package/dist/mcp/batch-fetch.js.map +1 -0
- package/dist/mcp/examples/dsl-examples.d.ts +21 -0
- package/dist/mcp/examples/dsl-examples.d.ts.map +1 -0
- package/dist/mcp/examples/dsl-examples.js +61 -0
- package/dist/mcp/examples/dsl-examples.js.map +1 -0
- package/dist/mcp/examples/usage-scenarios.d.ts +20 -0
- package/dist/mcp/examples/usage-scenarios.d.ts.map +1 -0
- package/dist/mcp/examples/usage-scenarios.js +169 -0
- package/dist/mcp/examples/usage-scenarios.js.map +1 -0
- package/dist/mcp/export-format.d.ts +43 -0
- package/dist/mcp/export-format.d.ts.map +1 -0
- package/dist/mcp/export-format.js +92 -0
- package/dist/mcp/export-format.js.map +1 -0
- package/dist/mcp/funder-org-names.d.ts +12 -0
- package/dist/mcp/funder-org-names.d.ts.map +1 -0
- package/dist/mcp/funder-org-names.js +30 -0
- package/dist/mcp/funder-org-names.js.map +1 -0
- package/dist/mcp/http-server.d.ts +20 -0
- package/dist/mcp/http-server.d.ts.map +1 -0
- package/dist/mcp/http-server.js +118 -0
- package/dist/mcp/http-server.js.map +1 -0
- package/dist/mcp/middleware/field-aliases.d.ts +97 -0
- package/dist/mcp/middleware/field-aliases.d.ts.map +1 -0
- package/dist/mcp/middleware/field-aliases.js +208 -0
- package/dist/mcp/middleware/field-aliases.js.map +1 -0
- package/dist/mcp/resources/schema.d.ts +13 -0
- package/dist/mcp/resources/schema.d.ts.map +1 -0
- package/dist/mcp/resources/schema.js +262 -0
- package/dist/mcp/resources/schema.js.map +1 -0
- package/dist/mcp/schema/context.d.ts +10 -0
- package/dist/mcp/schema/context.d.ts.map +1 -0
- package/dist/mcp/schema/context.js +6 -0
- package/dist/mcp/schema/context.js.map +1 -0
- package/dist/mcp/schema/index.d.ts +8 -0
- package/dist/mcp/schema/index.d.ts.map +1 -0
- package/dist/mcp/schema/index.js +6 -0
- package/dist/mcp/schema/index.js.map +1 -0
- package/dist/mcp/server.d.ts +56 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +168 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/shared-schema.d.ts +13 -0
- package/dist/mcp/shared-schema.d.ts.map +1 -0
- package/dist/mcp/shared-schema.js +28 -0
- package/dist/mcp/shared-schema.js.map +1 -0
- package/dist/mcp/tools/analytics-filters.d.ts +18 -0
- package/dist/mcp/tools/analytics-filters.d.ts.map +1 -0
- package/dist/mcp/tools/analytics-filters.js +35 -0
- package/dist/mcp/tools/analytics-filters.js.map +1 -0
- package/dist/mcp/tools/analytics.d.ts +16 -0
- package/dist/mcp/tools/analytics.d.ts.map +1 -0
- package/dist/mcp/tools/analytics.js +381 -0
- package/dist/mcp/tools/analytics.js.map +1 -0
- package/dist/mcp/tools/concept-profile.d.ts +14 -0
- package/dist/mcp/tools/concept-profile.d.ts.map +1 -0
- package/dist/mcp/tools/concept-profile.js +130 -0
- package/dist/mcp/tools/concept-profile.js.map +1 -0
- package/dist/mcp/tools/dataset-profile.d.ts +15 -0
- package/dist/mcp/tools/dataset-profile.d.ts.map +1 -0
- package/dist/mcp/tools/dataset-profile.js +194 -0
- package/dist/mcp/tools/dataset-profile.js.map +1 -0
- package/dist/mcp/tools/fetch-search-pages.d.ts +15 -0
- package/dist/mcp/tools/fetch-search-pages.d.ts.map +1 -0
- package/dist/mcp/tools/fetch-search-pages.js +237 -0
- package/dist/mcp/tools/fetch-search-pages.js.map +1 -0
- package/dist/mcp/tools/functions.d.ts +14 -0
- package/dist/mcp/tools/functions.d.ts.map +1 -0
- package/dist/mcp/tools/functions.js +87 -0
- package/dist/mcp/tools/functions.js.map +1 -0
- package/dist/mcp/tools/impact-chain.d.ts +15 -0
- package/dist/mcp/tools/impact-chain.d.ts.map +1 -0
- package/dist/mcp/tools/impact-chain.js +96 -0
- package/dist/mcp/tools/impact-chain.js.map +1 -0
- package/dist/mcp/tools/kwq.d.ts +19 -0
- package/dist/mcp/tools/kwq.d.ts.map +1 -0
- package/dist/mcp/tools/kwq.js +156 -0
- package/dist/mcp/tools/kwq.js.map +1 -0
- package/dist/mcp/tools/lookup.d.ts +14 -0
- package/dist/mcp/tools/lookup.d.ts.map +1 -0
- package/dist/mcp/tools/lookup.js +146 -0
- package/dist/mcp/tools/lookup.js.map +1 -0
- package/dist/mcp/tools/policy-profile.d.ts +15 -0
- package/dist/mcp/tools/policy-profile.d.ts.map +1 -0
- package/dist/mcp/tools/policy-profile.js +174 -0
- package/dist/mcp/tools/policy-profile.js.map +1 -0
- package/dist/mcp/tools/profile.d.ts +14 -0
- package/dist/mcp/tools/profile.d.ts.map +1 -0
- package/dist/mcp/tools/profile.js +349 -0
- package/dist/mcp/tools/profile.js.map +1 -0
- package/dist/mcp/tools/query.d.ts +16 -0
- package/dist/mcp/tools/query.d.ts.map +1 -0
- package/dist/mcp/tools/query.js +75 -0
- package/dist/mcp/tools/query.js.map +1 -0
- package/dist/mcp/tools/schema.d.ts +23 -0
- package/dist/mcp/tools/schema.d.ts.map +1 -0
- package/dist/mcp/tools/schema.js +124 -0
- package/dist/mcp/tools/schema.js.map +1 -0
- package/dist/mcp/tools/search-entity-metadata.d.ts +18 -0
- package/dist/mcp/tools/search-entity-metadata.d.ts.map +1 -0
- package/dist/mcp/tools/search-entity-metadata.js +221 -0
- package/dist/mcp/tools/search-entity-metadata.js.map +1 -0
- package/dist/mcp/tools/search-input.d.ts +58 -0
- package/dist/mcp/tools/search-input.d.ts.map +1 -0
- package/dist/mcp/tools/search-input.js +76 -0
- package/dist/mcp/tools/search-input.js.map +1 -0
- package/dist/mcp/tools/search.d.ts +24 -0
- package/dist/mcp/tools/search.d.ts.map +1 -0
- package/dist/mcp/tools/search.js +115 -0
- package/dist/mcp/tools/search.js.map +1 -0
- package/dist/mcp/utils.d.ts +82 -0
- package/dist/mcp/utils.d.ts.map +1 -0
- package/dist/mcp/utils.js +126 -0
- package/dist/mcp/utils.js.map +1 -0
- package/dist/middleware/field-aliases.d.ts +97 -0
- package/dist/middleware/field-aliases.d.ts.map +1 -0
- package/dist/middleware/field-aliases.js +208 -0
- package/dist/middleware/field-aliases.js.map +1 -0
- package/dist/resources/schema.d.ts +13 -0
- package/dist/resources/schema.d.ts.map +1 -0
- package/dist/resources/schema.js +204 -0
- package/dist/resources/schema.js.map +1 -0
- package/dist/schema/context.d.ts +10 -0
- package/dist/schema/context.d.ts.map +1 -0
- package/dist/schema/context.js +6 -0
- package/dist/schema/context.js.map +1 -0
- package/dist/schema/index.d.ts +9 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +6 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/load.d.ts +43 -0
- package/dist/schema/load.d.ts.map +1 -0
- package/dist/schema/load.js +139 -0
- package/dist/schema/load.js.map +1 -0
- package/dist/schema/store.d.ts +112 -0
- package/dist/schema/store.d.ts.map +1 -0
- package/dist/schema/store.js +173 -0
- package/dist/schema/store.js.map +1 -0
- package/dist/schema/structured-entities.d.ts +21 -0
- package/dist/schema/structured-entities.d.ts.map +1 -0
- package/dist/schema/structured-entities.js +39 -0
- package/dist/schema/structured-entities.js.map +1 -0
- package/dist/schema/types.d.ts +36 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +6 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/server.d.ts +57 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +144 -0
- package/dist/server.js.map +1 -0
- package/dist/telemetry.d.ts +172 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +233 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/tools/analytics-filters.d.ts +18 -0
- package/dist/tools/analytics-filters.d.ts.map +1 -0
- package/dist/tools/analytics-filters.js +35 -0
- package/dist/tools/analytics-filters.js.map +1 -0
- package/dist/tools/analytics.d.ts +16 -0
- package/dist/tools/analytics.d.ts.map +1 -0
- package/dist/tools/analytics.js +381 -0
- package/dist/tools/analytics.js.map +1 -0
- package/dist/tools/concept-profile.d.ts +14 -0
- package/dist/tools/concept-profile.d.ts.map +1 -0
- package/dist/tools/concept-profile.js +130 -0
- package/dist/tools/concept-profile.js.map +1 -0
- package/dist/tools/dataset-profile.d.ts +15 -0
- package/dist/tools/dataset-profile.d.ts.map +1 -0
- package/dist/tools/dataset-profile.js +194 -0
- package/dist/tools/dataset-profile.js.map +1 -0
- package/dist/tools/functions.d.ts +14 -0
- package/dist/tools/functions.d.ts.map +1 -0
- package/dist/tools/functions.js +294 -0
- package/dist/tools/functions.js.map +1 -0
- package/dist/tools/impact-chain.d.ts +15 -0
- package/dist/tools/impact-chain.d.ts.map +1 -0
- package/dist/tools/impact-chain.js +96 -0
- package/dist/tools/impact-chain.js.map +1 -0
- package/dist/tools/kwq.d.ts +19 -0
- package/dist/tools/kwq.d.ts.map +1 -0
- package/dist/tools/kwq.js +156 -0
- package/dist/tools/kwq.js.map +1 -0
- package/dist/tools/lookup.d.ts +14 -0
- package/dist/tools/lookup.d.ts.map +1 -0
- package/dist/tools/lookup.js +213 -0
- package/dist/tools/lookup.js.map +1 -0
- package/dist/tools/policy-profile.d.ts +15 -0
- package/dist/tools/policy-profile.d.ts.map +1 -0
- package/dist/tools/policy-profile.js +174 -0
- package/dist/tools/policy-profile.js.map +1 -0
- package/dist/tools/profile.d.ts +14 -0
- package/dist/tools/profile.d.ts.map +1 -0
- package/dist/tools/profile.js +349 -0
- package/dist/tools/profile.js.map +1 -0
- package/dist/tools/query.d.ts +16 -0
- package/dist/tools/query.d.ts.map +1 -0
- package/dist/tools/query.js +53 -0
- package/dist/tools/query.js.map +1 -0
- package/dist/tools/schema.d.ts +23 -0
- package/dist/tools/schema.d.ts.map +1 -0
- package/dist/tools/schema.js +124 -0
- package/dist/tools/schema.js.map +1 -0
- package/dist/tools/search-entity-metadata.d.ts +18 -0
- package/dist/tools/search-entity-metadata.d.ts.map +1 -0
- package/dist/tools/search-entity-metadata.js +221 -0
- package/dist/tools/search-entity-metadata.js.map +1 -0
- package/dist/tools/search.d.ts +23 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +110 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/solr-fields.d.ts +39 -0
- package/dist/tools/solr-fields.d.ts.map +1 -0
- package/dist/tools/solr-fields.js +40 -0
- package/dist/tools/solr-fields.js.map +1 -0
- package/dist/utils.d.ts +70 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +104 -0
- package/dist/utils.js.map +1 -0
- package/package.json +41 -0
- package/src/client/auth/config-loaders.ts +81 -0
- package/src/client/auth/decorators/index.ts +7 -0
- package/src/client/auth/decorators/with-caching.ts +237 -0
- package/src/client/auth/factory.ts +34 -0
- package/src/client/auth/index.ts +6 -0
- package/src/client/auth/jwt-utils.ts +36 -0
- package/src/client/auth/node.ts +16 -0
- package/src/client/auth/providers/index.ts +1 -0
- package/src/client/auth/providers/jwt-auth-provider.ts +153 -0
- package/src/client/auth/schemas.ts +25 -0
- package/src/client/auth/types.ts +30 -0
- package/src/client/command.ts +54 -0
- package/src/client/config/index.ts +15 -0
- package/src/client/config/loader.ts +155 -0
- package/src/client/config/node.ts +22 -0
- package/src/client/config/schemas.ts +28 -0
- package/src/client/config/types.ts +39 -0
- package/src/client/config/unified-loader.ts +118 -0
- package/src/client/config.ts +69 -0
- package/src/client/deployment-config.ts +98 -0
- package/src/client/errors.ts +295 -0
- package/src/client/http-client.ts +577 -0
- package/src/client/index.ts +68 -0
- package/src/client/internal-dsl-client.ts +128 -0
- package/src/client/rate-limiter.ts +139 -0
- package/src/client/resolve-api-key-user.ts +99 -0
- package/src/client/types.ts +44 -0
- package/src/dsl/client.ts +538 -0
- package/src/dsl/commands/apply-filters.ts +35 -0
- package/src/dsl/commands/facet/index.ts +12 -0
- package/src/dsl/commands/facet/schemas.ts +101 -0
- package/src/dsl/commands/functions/ClassifyCommand.ts +129 -0
- package/src/dsl/commands/functions/ExtractAffiliationsCommand.ts +163 -0
- package/src/dsl/commands/functions/ExtractConceptsCommand.ts +141 -0
- package/src/dsl/commands/functions/ExtractGrantsCommand.ts +112 -0
- package/src/dsl/commands/functions/index.ts +31 -0
- package/src/dsl/commands/index.ts +29 -0
- package/src/dsl/commands/validate-input.ts +29 -0
- package/src/dsl/create-client.ts +80 -0
- package/src/dsl/fluent-query-builder.ts +684 -0
- package/src/dsl/index.ts +206 -0
- package/src/dsl/pagination.ts +202 -0
- package/src/dsl/query-builder.ts +1120 -0
- package/src/dsl/response-parser.ts +299 -0
- package/src/dsl/schema/cache.ts +124 -0
- package/src/dsl/schema/extract.ts +44 -0
- package/src/dsl/schema/index.ts +50 -0
- package/src/dsl/schema/load.ts +160 -0
- package/src/dsl/schema/store.ts +253 -0
- package/src/dsl/schema/structured-entities.ts +44 -0
- package/src/dsl/schema/summary.ts +94 -0
- package/src/dsl/schema/types.ts +40 -0
- package/src/dsl/schema/validation.ts +56 -0
- package/src/dsl/types/buckets.ts +22 -0
- package/src/dsl/types/common.ts +99 -0
- package/src/dsl/types/entities.ts +43 -0
- package/src/dsl/types/index.ts +63 -0
- package/src/dsl/types/return-clauses.ts +147 -0
- package/src/dsl/types/special-functions.ts +220 -0
- package/src/dsl/types/vocabulary.ts +82 -0
- package/src/dsl/usage-policy.ts +219 -0
- package/src/dsl/utils/escape.ts +123 -0
- package/src/dsl/utils/sanitize.ts +21 -0
- package/src/dsl/utils/string-utils.ts +147 -0
- package/src/dsl/utils/validate-field-name.ts +19 -0
- package/src/http-main.ts +41 -0
- package/src/index.ts +14 -0
- package/src/main.ts +40 -0
- package/src/mcp/batch-fetch.ts +255 -0
- package/src/mcp/examples/dsl-examples.ts +66 -0
- package/src/mcp/examples/usage-scenarios.ts +185 -0
- package/src/mcp/export-format.ts +113 -0
- package/src/mcp/funder-org-names.ts +30 -0
- package/src/mcp/http-server.ts +148 -0
- package/src/mcp/middleware/field-aliases.ts +274 -0
- package/src/mcp/resources/schema.ts +364 -0
- package/src/mcp/schema/context.ts +11 -0
- package/src/mcp/schema/index.ts +38 -0
- package/src/mcp/server.ts +247 -0
- package/src/mcp/shared-schema.ts +34 -0
- package/src/mcp/tools/analytics-filters.ts +46 -0
- package/src/mcp/tools/analytics.ts +453 -0
- package/src/mcp/tools/fetch-search-pages.ts +329 -0
- package/src/mcp/tools/functions.ts +105 -0
- package/src/mcp/tools/lookup.ts +182 -0
- package/src/mcp/tools/query.ts +91 -0
- package/src/mcp/tools/schema.ts +147 -0
- package/src/mcp/tools/search-entity-metadata.ts +253 -0
- package/src/mcp/tools/search-input.ts +87 -0
- package/src/mcp/tools/search.ts +168 -0
- package/src/mcp/utils.ts +199 -0
- package/test/client/auth/config-loaders.test.ts +92 -0
- package/test/client/auth/decorators/with-caching.test.ts +482 -0
- package/test/client/auth/factory.test.ts +68 -0
- package/test/client/auth/providers/jwt-auth-provider.test.ts +398 -0
- package/test/client/auth/types.test.ts +18 -0
- package/test/client/command.test.ts +163 -0
- package/test/client/config/loader.test.ts +377 -0
- package/test/client/config/unified-loader.test.ts +136 -0
- package/test/client/deployment-config.test.ts +69 -0
- package/test/client/errors/index.test.ts +408 -0
- package/test/client/helpers/mock-fetch.ts +42 -0
- package/test/client/http-client.test.ts +1148 -0
- package/test/client/integration/auth-providers.integration.test.ts +186 -0
- package/test/client/internal-dsl-client.test.ts +76 -0
- package/test/client/rate-limiter.test.ts +204 -0
- package/test/client/resolve-api-key-user.test.ts +87 -0
- package/test/dsl/client.special-functions.test.ts +387 -0
- package/test/dsl/client.test.ts +457 -0
- package/test/dsl/commands/apply-filters.test.ts +147 -0
- package/test/dsl/commands/functions/ClassifyCommand.test.ts +170 -0
- package/test/dsl/commands/functions/ExtractAffiliationsCommand.test.ts +306 -0
- package/test/dsl/commands/functions/ExtractConceptsCommand.test.ts +136 -0
- package/test/dsl/commands/functions/ExtractGrantsCommand.test.ts +202 -0
- package/test/dsl/commands/search/filters.test.ts +42 -0
- package/test/dsl/fluent-query-builder.test.ts +865 -0
- package/test/dsl/integration/client.integration.test.ts +294 -0
- package/test/dsl/integration/facet-api.integration.test.ts +276 -0
- package/test/dsl/integration/field-existence.integration.test.ts +148 -0
- package/test/dsl/integration/fluent-api-docs-examples.integration.test.ts +988 -0
- package/test/dsl/integration/fluent-api.integration.test.ts +615 -0
- package/test/dsl/integration/search-helpers.ts +27 -0
- package/test/dsl/integration/special-functions.integration.test.ts +224 -0
- package/test/dsl/integration/test-config.ts +52 -0
- package/test/dsl/pagination.test.ts +86 -0
- package/test/dsl/query-builder.test.ts +1775 -0
- package/test/dsl/response-parser.test.ts +520 -0
- package/test/dsl/schema/cache.test.ts +53 -0
- package/test/dsl/schema/load.test.ts +165 -0
- package/test/dsl/schema/summary.test.ts +61 -0
- package/test/dsl/schema/validation.test.ts +46 -0
- package/test/dsl/time-series.test.ts +135 -0
- package/test/dsl/unnest.test.ts +151 -0
- package/test/dsl/usage-policy.test.ts +100 -0
- package/test/dsl/utils/escape.property.test.ts +109 -0
- package/test/dsl/utils/escape.test.ts +140 -0
- package/test/dsl/utils/validate-field-name.property.test.ts +99 -0
- package/test/dsl/utils/validate-field-name.test.ts +55 -0
- package/test/examples/dsl-examples.test.ts +29 -0
- package/test/examples/usage-scenarios.test.ts +90 -0
- package/test/fixtures/describe-schema.json +4000 -0
- package/test/funder-org-names.test.ts +20 -0
- package/test/helpers/schema-fixture.ts +18 -0
- package/test/helpers/tool-test-harness.ts +114 -0
- package/test/integration/assertions.ts +263 -0
- package/test/integration/env.ts +108 -0
- package/test/integration/harness.ts +307 -0
- package/test/integration/hosted-client.ts +56 -0
- package/test/integration/hosted-mocks.ts +81 -0
- package/test/integration/hosted-run.ts +86 -0
- package/test/integration/hosted.e2e.test.ts +101 -0
- package/test/integration/reporter.ts +100 -0
- package/test/integration/run.ts +74 -0
- package/test/integration/suites/analytics.integration.ts +72 -0
- package/test/integration/suites/functions.integration.ts +37 -0
- package/test/integration/suites/hosted-smoke.integration.ts +38 -0
- package/test/integration/suites/index.ts +22 -0
- package/test/integration/suites/lookup.integration.ts +57 -0
- package/test/integration/suites/query.integration.ts +49 -0
- package/test/integration/suites/resources.integration.ts +68 -0
- package/test/integration/suites/search.integration.ts +80 -0
- package/test/integration/types.ts +92 -0
- package/test/integration/verify-usage-scenarios.ts +162 -0
- package/test/mcp/export-format.test.ts +67 -0
- package/test/mcp/http-server.test.ts +80 -0
- package/test/middleware/field-aliases.test.ts +287 -0
- package/test/resources/schema.test.ts +28 -0
- package/test/schema/describe-contract.test.ts +24 -0
- package/test/schema/store.test.ts +45 -0
- package/test/server.test.ts +170 -0
- package/test/tools/analytics-filters.test.ts +35 -0
- package/test/tools/analytics.test.ts +419 -0
- package/test/tools/fetch-search-pages.test.ts +200 -0
- package/test/tools/functions.test.ts +153 -0
- package/test/tools/lookup.test.ts +296 -0
- package/test/tools/query.test.ts +100 -0
- package/test/tools/schema.test.ts +37 -0
- package/test/tools/search.test.ts +415 -0
- package/test/utils.test.ts +45 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,1775 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { ValidationError } from "../../src/client/index.js";
|
|
3
|
+
import { QueryBuilder } from "../../src/dsl/query-builder.js";
|
|
4
|
+
|
|
5
|
+
describe("QueryBuilder", () => {
|
|
6
|
+
let builder: QueryBuilder;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
builder = new QueryBuilder();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("simple search queries", () => {
|
|
13
|
+
it("builds a simple search query for publications", () => {
|
|
14
|
+
const query = builder.search("publications").for("machine learning").build();
|
|
15
|
+
|
|
16
|
+
expect(query).toBe('search publications for "machine learning"');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("builds a search query for grants", () => {
|
|
20
|
+
const query = builder.search("grants").for("cancer research").build();
|
|
21
|
+
|
|
22
|
+
expect(query).toBe('search grants for "cancer research"');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("builds a search query for researchers", () => {
|
|
26
|
+
const query = builder.search("researchers").for("smith").build();
|
|
27
|
+
|
|
28
|
+
expect(query).toBe('search researchers for "smith"');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("queries with specific fields", () => {
|
|
33
|
+
it("returns specified fields", () => {
|
|
34
|
+
const query = builder
|
|
35
|
+
.search("publications")
|
|
36
|
+
.for("covid")
|
|
37
|
+
.fields(["id", "title", "doi", "year"])
|
|
38
|
+
.build();
|
|
39
|
+
|
|
40
|
+
// API requires fields separated by '+' not ','
|
|
41
|
+
expect(query).toBe('search publications for "covid" return publications[id+title+doi+year]');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("returns all fields with empty array", () => {
|
|
45
|
+
const query = builder.search("publications").for("quantum").fields([]).build();
|
|
46
|
+
|
|
47
|
+
expect(query).toBe('search publications for "quantum"');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("queries with where clauses", () => {
|
|
52
|
+
it("adds single where clause with equals", () => {
|
|
53
|
+
const query = builder.search("publications").for("climate").where("year", "=", 2023).build();
|
|
54
|
+
|
|
55
|
+
expect(query).toBe('search publications for "climate" where year = 2023');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("adds single where clause with string value", () => {
|
|
59
|
+
const query = builder.search("publications").for("AI").where("type", "=", "article").build();
|
|
60
|
+
|
|
61
|
+
expect(query).toBe('search publications for "AI" where type = "article"');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("adds multiple where clauses", () => {
|
|
65
|
+
const query = builder
|
|
66
|
+
.search("publications")
|
|
67
|
+
.for("genetics")
|
|
68
|
+
.where("year", ">=", 2020)
|
|
69
|
+
.where("year", "<=", 2024)
|
|
70
|
+
.build();
|
|
71
|
+
|
|
72
|
+
expect(query).toBe('search publications for "genetics" where year >= 2020 and year <= 2024');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("supports not equals operator", () => {
|
|
76
|
+
const query = builder
|
|
77
|
+
.search("publications")
|
|
78
|
+
.for("physics")
|
|
79
|
+
.where("type", "!=", "preprint")
|
|
80
|
+
.build();
|
|
81
|
+
|
|
82
|
+
expect(query).toBe('search publications for "physics" where type != "preprint"');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("supports greater than operator", () => {
|
|
86
|
+
const query = builder
|
|
87
|
+
.search("publications")
|
|
88
|
+
.for("biology")
|
|
89
|
+
.where("times_cited", ">", 100)
|
|
90
|
+
.build();
|
|
91
|
+
|
|
92
|
+
expect(query).toBe('search publications for "biology" where times_cited > 100');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("supports less than operator", () => {
|
|
96
|
+
const query = builder
|
|
97
|
+
.search("grants")
|
|
98
|
+
.for("energy")
|
|
99
|
+
.where("funding_usd", "<", 1000000)
|
|
100
|
+
.build();
|
|
101
|
+
|
|
102
|
+
expect(query).toBe('search grants for "energy" where funding_usd < 1000000');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("queries with sort", () => {
|
|
107
|
+
it("sorts ascending by default", () => {
|
|
108
|
+
const query = builder.search("publications").for("neuroscience").sort("year").build();
|
|
109
|
+
|
|
110
|
+
// API requires return clause before sort
|
|
111
|
+
expect(query).toBe(
|
|
112
|
+
'search publications for "neuroscience" return publications sort by year asc',
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("sorts descending when specified", () => {
|
|
117
|
+
const query = builder
|
|
118
|
+
.search("publications")
|
|
119
|
+
.for("chemistry")
|
|
120
|
+
.sort("times_cited", "desc")
|
|
121
|
+
.build();
|
|
122
|
+
|
|
123
|
+
expect(query).toBe(
|
|
124
|
+
'search publications for "chemistry" return publications sort by times_cited desc',
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("queries with limit", () => {
|
|
130
|
+
it("adds limit clause", () => {
|
|
131
|
+
const query = builder.search("publications").for("math").limit(50).build();
|
|
132
|
+
|
|
133
|
+
// API requires return clause before limit
|
|
134
|
+
expect(query).toBe('search publications for "math" return publications limit 50');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("queries with skip for pagination", () => {
|
|
139
|
+
it("adds skip clause", () => {
|
|
140
|
+
const query = builder.search("publications").for("astronomy").skip(100).build();
|
|
141
|
+
|
|
142
|
+
// API requires return clause before skip
|
|
143
|
+
expect(query).toBe('search publications for "astronomy" return publications skip 100');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("combines skip and limit for pagination", () => {
|
|
147
|
+
const query = builder.search("publications").for("ecology").skip(50).limit(25).build();
|
|
148
|
+
|
|
149
|
+
// API requires limit before skip
|
|
150
|
+
expect(query).toBe('search publications for "ecology" return publications limit 25 skip 50');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("complex queries", () => {
|
|
155
|
+
it("combines all clauses in correct order", () => {
|
|
156
|
+
const query = builder
|
|
157
|
+
.search("publications")
|
|
158
|
+
.for("deep learning")
|
|
159
|
+
.where("year", ">=", 2020)
|
|
160
|
+
.where("type", "=", "article")
|
|
161
|
+
.fields(["id", "title", "authors"])
|
|
162
|
+
.sort("times_cited", "desc")
|
|
163
|
+
.skip(0)
|
|
164
|
+
.limit(100)
|
|
165
|
+
.build();
|
|
166
|
+
|
|
167
|
+
// API requires: fields with '+', limit before skip
|
|
168
|
+
expect(query).toBe(
|
|
169
|
+
'search publications for "deep learning" where year >= 2020 and type = "article" return publications[id+title+authors] sort by times_cited desc limit 100 skip 0',
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("error cases", () => {
|
|
175
|
+
it("throws when building without entity", () => {
|
|
176
|
+
expect(() => builder.build()).toThrow("Entity type must be specified");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it.each([
|
|
180
|
+
"publications",
|
|
181
|
+
"grants",
|
|
182
|
+
"researchers",
|
|
183
|
+
"patents",
|
|
184
|
+
"clinical_trials",
|
|
185
|
+
"datasets",
|
|
186
|
+
"policy_documents",
|
|
187
|
+
"organizations",
|
|
188
|
+
] as const)("builds valid query without for() for %s", (entity) => {
|
|
189
|
+
expect(builder.search(entity).build()).toBe(`search ${entity}`);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it.each([
|
|
193
|
+
"publications",
|
|
194
|
+
"grants",
|
|
195
|
+
"researchers",
|
|
196
|
+
"patents",
|
|
197
|
+
"clinical_trials",
|
|
198
|
+
"datasets",
|
|
199
|
+
"policy_documents",
|
|
200
|
+
"organizations",
|
|
201
|
+
] as const)("builds query without for() but with where clause for %s", (entity) => {
|
|
202
|
+
expect(builder.search(entity).where("year", "=", 2024).build()).toBe(
|
|
203
|
+
`search ${entity} where year = 2024`,
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Note: "*" is NOT a wildcard in the Dimensions DSL — it is sent as a literal string.
|
|
208
|
+
// To search all records, omit the for() call and use where() conditions instead.
|
|
209
|
+
it("builds query with for('*') as literal", () => {
|
|
210
|
+
expect(builder.search("publications").for("*").build()).toBe('search publications for "*"');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("throws for invalid entity type", () => {
|
|
214
|
+
expect(() => builder.search("invalid_entity" as never)).toThrow("Invalid entity type");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("throws for invalid operator", () => {
|
|
218
|
+
expect(() =>
|
|
219
|
+
builder
|
|
220
|
+
.search("publications")
|
|
221
|
+
.for("test")
|
|
222
|
+
.where("year", "like" as never, 2020),
|
|
223
|
+
).toThrow("Invalid operator");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("throws for negative limit", () => {
|
|
227
|
+
expect(() => builder.search("publications").for("test").limit(-1)).toThrow(
|
|
228
|
+
"Limit must be non-negative",
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("throws for negative skip", () => {
|
|
233
|
+
expect(() => builder.search("publications").for("test").skip(-1)).toThrow(
|
|
234
|
+
"Skip must be non-negative",
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("throws when search index is set without search terms", () => {
|
|
239
|
+
expect(() => builder.search("publications").in("title_abstract_only").build()).toThrow(
|
|
240
|
+
"Search index requires search terms: call for() before in()",
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("allows in() with for()", () => {
|
|
245
|
+
expect(builder.search("publications").for("test").in("title_abstract_only").build()).toBe(
|
|
246
|
+
'search publications in title_abstract_only for "test"',
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("allows in() set to full_data without for()", () => {
|
|
251
|
+
expect(builder.search("publications").in("full_data").build()).toBe("search publications");
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe("reset functionality", () => {
|
|
256
|
+
it("resets builder state", () => {
|
|
257
|
+
builder.search("publications").for("initial query");
|
|
258
|
+
|
|
259
|
+
builder.reset();
|
|
260
|
+
|
|
261
|
+
expect(() => builder.build()).toThrow("Entity type must be specified");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("allows building new query after reset", () => {
|
|
265
|
+
builder.search("publications").for("first query");
|
|
266
|
+
|
|
267
|
+
builder.reset();
|
|
268
|
+
|
|
269
|
+
const query = builder.search("grants").for("new query").build();
|
|
270
|
+
|
|
271
|
+
expect(query).toBe('search grants for "new query"');
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("entity types", () => {
|
|
276
|
+
it.each([
|
|
277
|
+
"publications",
|
|
278
|
+
"grants",
|
|
279
|
+
"researchers",
|
|
280
|
+
"patents",
|
|
281
|
+
"clinical_trials",
|
|
282
|
+
"datasets",
|
|
283
|
+
"policy_documents",
|
|
284
|
+
"organizations",
|
|
285
|
+
] as const)("supports %s entity type", (entity) => {
|
|
286
|
+
const query = builder.search(entity).for("test").build();
|
|
287
|
+
expect(query).toBe(`search ${entity} for "test"`);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe("queries with extended operators", () => {
|
|
292
|
+
it("supports partial match operator (~)", () => {
|
|
293
|
+
const query = builder
|
|
294
|
+
.search("grants")
|
|
295
|
+
.for("cancer")
|
|
296
|
+
.where("research_orgs.name", "~", "National Blood")
|
|
297
|
+
.build();
|
|
298
|
+
|
|
299
|
+
expect(query).toBe('search grants for "cancer" where research_orgs.name ~ "National Blood"');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("supports lucene field search operator (@)", () => {
|
|
303
|
+
const query = builder
|
|
304
|
+
.search("publications")
|
|
305
|
+
.for("medicine")
|
|
306
|
+
.where("mesh_terms", "@", "(human OR animal)")
|
|
307
|
+
.build();
|
|
308
|
+
|
|
309
|
+
expect(query).toBe(
|
|
310
|
+
'search publications for "medicine" where mesh_terms @ "(human OR animal)"',
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("supports is empty operator", () => {
|
|
315
|
+
const query = builder.search("researchers").for("smith").whereEmpty("orcid_id").build();
|
|
316
|
+
|
|
317
|
+
expect(query).toBe('search researchers for "smith" where orcid_id is empty');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("supports is not empty operator", () => {
|
|
321
|
+
const query = builder.search("publications").for("covid").whereNotEmpty("doi").build();
|
|
322
|
+
|
|
323
|
+
expect(query).toBe('search publications for "covid" where doi is not empty');
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("combines emptiness checks with other conditions", () => {
|
|
327
|
+
const query = builder
|
|
328
|
+
.search("publications")
|
|
329
|
+
.for("CRISPR")
|
|
330
|
+
.where("year", ">=", 2020)
|
|
331
|
+
.whereNotEmpty("doi")
|
|
332
|
+
.build();
|
|
333
|
+
|
|
334
|
+
expect(query).toBe(
|
|
335
|
+
'search publications for "CRISPR" where year >= 2020 and doi is not empty',
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe("queries with list and range filters", () => {
|
|
341
|
+
it("supports list filter with strings", () => {
|
|
342
|
+
const query = builder
|
|
343
|
+
.search("publications")
|
|
344
|
+
.for("research")
|
|
345
|
+
.whereIn("type", ["article", "review", "preprint"])
|
|
346
|
+
.build();
|
|
347
|
+
|
|
348
|
+
expect(query).toBe(
|
|
349
|
+
'search publications for "research" where type in ["article", "review", "preprint"]',
|
|
350
|
+
);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("supports list filter with numbers", () => {
|
|
354
|
+
const query = builder
|
|
355
|
+
.search("publications")
|
|
356
|
+
.for("data")
|
|
357
|
+
.whereIn("year", [2020, 2021, 2022])
|
|
358
|
+
.build();
|
|
359
|
+
|
|
360
|
+
expect(query).toBe('search publications for "data" where year in [2020, 2021, 2022]');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("supports range filter with numbers", () => {
|
|
364
|
+
const query = builder
|
|
365
|
+
.search("publications")
|
|
366
|
+
.for("climate")
|
|
367
|
+
.whereRange("year", 2018, 2023)
|
|
368
|
+
.build();
|
|
369
|
+
|
|
370
|
+
expect(query).toBe('search publications for "climate" where year in [2018:2023]');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("supports range filter with dates", () => {
|
|
374
|
+
const query = builder
|
|
375
|
+
.search("publications")
|
|
376
|
+
.for("covid")
|
|
377
|
+
.whereRange("date", "2020-01-01", "2020-12-31")
|
|
378
|
+
.build();
|
|
379
|
+
|
|
380
|
+
expect(query).toBe(
|
|
381
|
+
'search publications for "covid" where date in ["2020-01-01":"2020-12-31"]',
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("validates list filter max items", () => {
|
|
386
|
+
const items = Array.from({ length: 401 }, (_, i) => `item${i}`);
|
|
387
|
+
expect(() => builder.search("publications").for("test").whereIn("id", items)).toThrow(
|
|
388
|
+
"List filter cannot exceed 400 items",
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("validates list filter has at least one value", () => {
|
|
393
|
+
expect(() => builder.search("publications").for("test").whereIn("type", [])).toThrow(
|
|
394
|
+
"List filter must have at least one value",
|
|
395
|
+
);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("supports mixed string and number values in list filter", () => {
|
|
399
|
+
const query = builder
|
|
400
|
+
.search("publications")
|
|
401
|
+
.for("test")
|
|
402
|
+
.whereIn("field", ["string", 123, "another"])
|
|
403
|
+
.build();
|
|
404
|
+
|
|
405
|
+
expect(query).toBe(
|
|
406
|
+
'search publications for "test" where field in ["string", 123, "another"]',
|
|
407
|
+
);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("validates range start is not greater than end for numbers", () => {
|
|
411
|
+
expect(() =>
|
|
412
|
+
builder.search("publications").for("test").whereRange("year", 2023, 2020),
|
|
413
|
+
).toThrow("Range start must not be greater than end");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("allows string ranges without validation", () => {
|
|
417
|
+
const query = builder
|
|
418
|
+
.search("publications")
|
|
419
|
+
.for("test")
|
|
420
|
+
.whereRange("date", "2023-01-01", "2020-01-01")
|
|
421
|
+
.build();
|
|
422
|
+
|
|
423
|
+
// String ranges are not validated (dates may have different formats)
|
|
424
|
+
expect(query).toBe(
|
|
425
|
+
'search publications for "test" where date in ["2023-01-01":"2020-01-01"]',
|
|
426
|
+
);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
describe("combined filter types", () => {
|
|
431
|
+
it("combines all filter types in correct order", () => {
|
|
432
|
+
const query = builder
|
|
433
|
+
.search("publications")
|
|
434
|
+
.for("AI research")
|
|
435
|
+
.where("year", ">=", 2020)
|
|
436
|
+
.where("title", "~", "neural")
|
|
437
|
+
.where("mesh_terms", "@", "(human OR mouse)")
|
|
438
|
+
.whereNotEmpty("doi")
|
|
439
|
+
.whereIn("type", ["article", "review"])
|
|
440
|
+
.whereRange("times_cited", 10, 100)
|
|
441
|
+
.fields(["id", "title"])
|
|
442
|
+
.build();
|
|
443
|
+
|
|
444
|
+
expect(query).toBe(
|
|
445
|
+
'search publications for "AI research" ' +
|
|
446
|
+
'where year >= 2020 and title ~ "neural" and mesh_terms @ "(human OR mouse)" ' +
|
|
447
|
+
'and doi is not empty and type in ["article", "review"] and times_cited in [10:100] ' +
|
|
448
|
+
"return publications[id+title]",
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
describe("queries with search indexes", () => {
|
|
454
|
+
it("searches in title_abstract_only index", () => {
|
|
455
|
+
const query = builder
|
|
456
|
+
.search("publications")
|
|
457
|
+
.in("title_abstract_only")
|
|
458
|
+
.for("CRISPR gene editing")
|
|
459
|
+
.build();
|
|
460
|
+
|
|
461
|
+
expect(query).toBe('search publications in title_abstract_only for "CRISPR gene editing"');
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("searches in authors index", () => {
|
|
465
|
+
const query = builder.search("publications").in("authors").for("Jennifer A Doudna").build();
|
|
466
|
+
|
|
467
|
+
expect(query).toBe('search publications in authors for "Jennifer A Doudna"');
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it("searches in concepts index", () => {
|
|
471
|
+
const query = builder.search("publications").in("concepts").for("machine learning").build();
|
|
472
|
+
|
|
473
|
+
expect(query).toBe('search publications in concepts for "machine learning"');
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it("combines index with where clauses", () => {
|
|
477
|
+
const query = builder
|
|
478
|
+
.search("publications")
|
|
479
|
+
.in("title_only")
|
|
480
|
+
.for("graphene")
|
|
481
|
+
.where("year", ">=", 2020)
|
|
482
|
+
.build();
|
|
483
|
+
|
|
484
|
+
expect(query).toBe('search publications in title_only for "graphene" where year >= 2020');
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it("validates search index", () => {
|
|
488
|
+
expect(() =>
|
|
489
|
+
builder
|
|
490
|
+
.search("publications")
|
|
491
|
+
.in("invalid_index" as never)
|
|
492
|
+
.for("test"),
|
|
493
|
+
).toThrow("Invalid search index");
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
describe("queries with boolean logic", () => {
|
|
498
|
+
it("supports explicit AND connector", () => {
|
|
499
|
+
const query = builder
|
|
500
|
+
.search("publications")
|
|
501
|
+
.for("research")
|
|
502
|
+
.where("year", ">=", 2020)
|
|
503
|
+
.and()
|
|
504
|
+
.where("type", "=", "article")
|
|
505
|
+
.build();
|
|
506
|
+
|
|
507
|
+
expect(query).toBe(
|
|
508
|
+
'search publications for "research" where year >= 2020 and type = "article"',
|
|
509
|
+
);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("explicit AND produces same result as implicit AND", () => {
|
|
513
|
+
const explicitAnd = new QueryBuilder()
|
|
514
|
+
.search("publications")
|
|
515
|
+
.for("test")
|
|
516
|
+
.where("year", "=", 2020)
|
|
517
|
+
.and()
|
|
518
|
+
.where("type", "=", "article")
|
|
519
|
+
.build();
|
|
520
|
+
|
|
521
|
+
const implicitAnd = new QueryBuilder()
|
|
522
|
+
.search("publications")
|
|
523
|
+
.for("test")
|
|
524
|
+
.where("year", "=", 2020)
|
|
525
|
+
.where("type", "=", "article")
|
|
526
|
+
.build();
|
|
527
|
+
|
|
528
|
+
expect(explicitAnd).toBe(implicitAnd);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("supports OR conditions", () => {
|
|
532
|
+
const query = builder
|
|
533
|
+
.search("publications")
|
|
534
|
+
.for("research")
|
|
535
|
+
.where("type", "=", "article")
|
|
536
|
+
.or()
|
|
537
|
+
.where("type", "=", "review")
|
|
538
|
+
.build();
|
|
539
|
+
|
|
540
|
+
expect(query).toBe(
|
|
541
|
+
'search publications for "research" where type = "article" or type = "review"',
|
|
542
|
+
);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it("supports NOT conditions", () => {
|
|
546
|
+
const query = builder
|
|
547
|
+
.search("publications")
|
|
548
|
+
.for("cancer")
|
|
549
|
+
.where("year", ">=", 2020)
|
|
550
|
+
.not()
|
|
551
|
+
.where("open_access", "=", "closed")
|
|
552
|
+
.build();
|
|
553
|
+
|
|
554
|
+
expect(query).toBe(
|
|
555
|
+
'search publications for "cancer" where year >= 2020 not open_access = "closed"',
|
|
556
|
+
);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it("supports parenthesized expressions", () => {
|
|
560
|
+
const query = builder
|
|
561
|
+
.search("publications")
|
|
562
|
+
.for("AI")
|
|
563
|
+
.openGroup()
|
|
564
|
+
.where("type", "=", "article")
|
|
565
|
+
.or()
|
|
566
|
+
.where("type", "=", "review")
|
|
567
|
+
.closeGroup()
|
|
568
|
+
.where("year", ">=", 2020)
|
|
569
|
+
.build();
|
|
570
|
+
|
|
571
|
+
expect(query).toBe(
|
|
572
|
+
'search publications for "AI" where (type = "article" or type = "review") and year >= 2020',
|
|
573
|
+
);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it("supports nested parentheses", () => {
|
|
577
|
+
const query = builder
|
|
578
|
+
.search("grants")
|
|
579
|
+
.for("energy")
|
|
580
|
+
.openGroup()
|
|
581
|
+
.openGroup()
|
|
582
|
+
.where("start_year", ">", 2020)
|
|
583
|
+
.where("funding_usd", ">", 1000000)
|
|
584
|
+
.closeGroup()
|
|
585
|
+
.or()
|
|
586
|
+
.where("funder_org_acronym", "=", "NIH")
|
|
587
|
+
.closeGroup()
|
|
588
|
+
.where("active_status", "=", "active")
|
|
589
|
+
.build();
|
|
590
|
+
|
|
591
|
+
expect(query).toBe(
|
|
592
|
+
'search grants for "energy" where ((start_year > 2020 and funding_usd > 1000000) or funder_org_acronym = "NIH") and active_status = "active"',
|
|
593
|
+
);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it("throws on unbalanced parentheses (unclosed)", () => {
|
|
597
|
+
expect(() =>
|
|
598
|
+
builder.search("publications").for("test").openGroup().where("year", "=", 2020).build(),
|
|
599
|
+
).toThrow("Unbalanced parentheses");
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
it("throws on unbalanced parentheses (extra close)", () => {
|
|
603
|
+
expect(() =>
|
|
604
|
+
builder.search("publications").for("test").where("year", "=", 2020).closeGroup(),
|
|
605
|
+
).toThrow("No group to close");
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it("combines OR with whereIn", () => {
|
|
609
|
+
const query = builder
|
|
610
|
+
.search("publications")
|
|
611
|
+
.for("data")
|
|
612
|
+
.whereIn("type", ["article", "review"])
|
|
613
|
+
.or()
|
|
614
|
+
.where("year", ">", 2022)
|
|
615
|
+
.build();
|
|
616
|
+
|
|
617
|
+
expect(query).toBe(
|
|
618
|
+
'search publications for "data" where type in ["article", "review"] or year > 2022',
|
|
619
|
+
);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it("combines OR with whereNotEmpty", () => {
|
|
623
|
+
const query = builder
|
|
624
|
+
.search("publications")
|
|
625
|
+
.for("research")
|
|
626
|
+
.whereNotEmpty("doi")
|
|
627
|
+
.or()
|
|
628
|
+
.whereNotEmpty("pmid")
|
|
629
|
+
.build();
|
|
630
|
+
|
|
631
|
+
expect(query).toBe(
|
|
632
|
+
'search publications for "research" where doi is not empty or pmid is not empty',
|
|
633
|
+
);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it("supports deeply nested groups (3+ levels)", () => {
|
|
637
|
+
const query = builder
|
|
638
|
+
.search("publications")
|
|
639
|
+
.for("complex query")
|
|
640
|
+
.openGroup()
|
|
641
|
+
.openGroup()
|
|
642
|
+
.openGroup()
|
|
643
|
+
.where("year", ">=", 2020)
|
|
644
|
+
.or()
|
|
645
|
+
.where("year", "<=", 2010)
|
|
646
|
+
.closeGroup()
|
|
647
|
+
.where("type", "=", "article")
|
|
648
|
+
.closeGroup()
|
|
649
|
+
.or()
|
|
650
|
+
.where("times_cited", ">", 100)
|
|
651
|
+
.closeGroup()
|
|
652
|
+
.where("open_access", "=", "all")
|
|
653
|
+
.build();
|
|
654
|
+
|
|
655
|
+
expect(query).toBe(
|
|
656
|
+
'search publications for "complex query" where (((year >= 2020 or year <= 2010) and type = "article") or times_cited > 100) and open_access = "all"',
|
|
657
|
+
);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it("supports 4 levels of nesting with mixed operators", () => {
|
|
661
|
+
const query = builder
|
|
662
|
+
.search("grants")
|
|
663
|
+
.for("research funding")
|
|
664
|
+
.openGroup()
|
|
665
|
+
.openGroup()
|
|
666
|
+
.openGroup()
|
|
667
|
+
.openGroup()
|
|
668
|
+
.where("funding_usd", ">", 1000000)
|
|
669
|
+
.closeGroup()
|
|
670
|
+
.or()
|
|
671
|
+
.where("funder_org_acronym", "=", "NIH")
|
|
672
|
+
.closeGroup()
|
|
673
|
+
.where("start_year", ">=", 2020)
|
|
674
|
+
.closeGroup()
|
|
675
|
+
.not()
|
|
676
|
+
.where("active_status", "=", "closed")
|
|
677
|
+
.closeGroup()
|
|
678
|
+
.build();
|
|
679
|
+
|
|
680
|
+
expect(query).toBe(
|
|
681
|
+
'search grants for "research funding" where ((((funding_usd > 1000000) or funder_org_acronym = "NIH") and start_year >= 2020) not active_status = "closed")',
|
|
682
|
+
);
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
describe("queries with count function", () => {
|
|
687
|
+
it("filters by count equals", () => {
|
|
688
|
+
const query = builder
|
|
689
|
+
.search("publications")
|
|
690
|
+
.for("research")
|
|
691
|
+
.whereCount("researchers", "=", 1)
|
|
692
|
+
.build();
|
|
693
|
+
|
|
694
|
+
expect(query).toBe('search publications for "research" where count(researchers) = 1');
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it("filters by count greater than", () => {
|
|
698
|
+
const query = builder
|
|
699
|
+
.search("publications")
|
|
700
|
+
.for("collaboration")
|
|
701
|
+
.whereCount("research_org_countries", ">", 1)
|
|
702
|
+
.build();
|
|
703
|
+
|
|
704
|
+
expect(query).toBe(
|
|
705
|
+
'search publications for "collaboration" where count(research_org_countries) > 1',
|
|
706
|
+
);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it("filters for hyper-authorship", () => {
|
|
710
|
+
const query = builder
|
|
711
|
+
.search("publications")
|
|
712
|
+
.for("physics")
|
|
713
|
+
.whereCount("researchers", ">=", 25)
|
|
714
|
+
.build();
|
|
715
|
+
|
|
716
|
+
expect(query).toBe('search publications for "physics" where count(researchers) >= 25');
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
it("combines count with other conditions", () => {
|
|
720
|
+
const query = builder
|
|
721
|
+
.search("publications")
|
|
722
|
+
.for("science")
|
|
723
|
+
.where("year", ">=", 2020)
|
|
724
|
+
.whereCount("researchers", ">", 5)
|
|
725
|
+
.build();
|
|
726
|
+
|
|
727
|
+
expect(query).toBe(
|
|
728
|
+
'search publications for "science" where year >= 2020 and count(researchers) > 5',
|
|
729
|
+
);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it("combines count with OR operator", () => {
|
|
733
|
+
const query = builder
|
|
734
|
+
.search("publications")
|
|
735
|
+
.for("research")
|
|
736
|
+
.whereCount("researchers", ">", 5)
|
|
737
|
+
.or()
|
|
738
|
+
.whereCount("research_org_countries", ">", 2)
|
|
739
|
+
.build();
|
|
740
|
+
|
|
741
|
+
expect(query).toBe(
|
|
742
|
+
'search publications for "research" where count(researchers) > 5 or count(research_org_countries) > 2',
|
|
743
|
+
);
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
it("combines count with NOT operator", () => {
|
|
747
|
+
const query = builder
|
|
748
|
+
.search("publications")
|
|
749
|
+
.for("research")
|
|
750
|
+
.whereCount("researchers", ">=", 1)
|
|
751
|
+
.not()
|
|
752
|
+
.whereCount("research_org_countries", "=", 1)
|
|
753
|
+
.build();
|
|
754
|
+
|
|
755
|
+
expect(query).toBe(
|
|
756
|
+
'search publications for "research" where count(researchers) >= 1 not count(research_org_countries) = 1',
|
|
757
|
+
);
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it("combines count with parenthesized boolean expressions", () => {
|
|
761
|
+
const query = builder
|
|
762
|
+
.search("publications")
|
|
763
|
+
.for("collaboration")
|
|
764
|
+
.where("year", ">=", 2020)
|
|
765
|
+
.openGroup()
|
|
766
|
+
.whereCount("researchers", ">", 10)
|
|
767
|
+
.or()
|
|
768
|
+
.whereCount("research_org_countries", ">", 3)
|
|
769
|
+
.closeGroup()
|
|
770
|
+
.build();
|
|
771
|
+
|
|
772
|
+
expect(query).toBe(
|
|
773
|
+
'search publications for "collaboration" where year >= 2020 and (count(researchers) > 10 or count(research_org_countries) > 3)',
|
|
774
|
+
);
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
describe("facet return clauses", () => {
|
|
779
|
+
it("generates basic facet return clause", () => {
|
|
780
|
+
const query = builder.search("publications").for("CRISPR").returnFacet("year").build();
|
|
781
|
+
|
|
782
|
+
expect(query).toBe('search publications for "CRISPR" return year');
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it("generates facet return clause with limit", () => {
|
|
786
|
+
const query = builder
|
|
787
|
+
.search("publications")
|
|
788
|
+
.for("CRISPR")
|
|
789
|
+
.returnFacet("funders", { limit: 10 })
|
|
790
|
+
.build();
|
|
791
|
+
|
|
792
|
+
expect(query).toBe('search publications for "CRISPR" return funders limit 10');
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it("generates multiple facet return clauses", () => {
|
|
796
|
+
const query = builder
|
|
797
|
+
.search("publications")
|
|
798
|
+
.for("CRISPR")
|
|
799
|
+
.returnFacet("year")
|
|
800
|
+
.returnFacet("funders", { limit: 10 })
|
|
801
|
+
.build();
|
|
802
|
+
|
|
803
|
+
expect(query).toBe('search publications for "CRISPR" return year return funders limit 10');
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it("generates aggregated facet return clause", () => {
|
|
807
|
+
const query = builder
|
|
808
|
+
.search("publications")
|
|
809
|
+
.for("CRISPR")
|
|
810
|
+
.returnAggregate("funders", ["rcr_avg"])
|
|
811
|
+
.build();
|
|
812
|
+
|
|
813
|
+
expect(query).toBe('search publications for "CRISPR" return funders aggregate rcr_avg');
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
it("generates aggregated facet with multiple indicators", () => {
|
|
817
|
+
const query = builder
|
|
818
|
+
.search("publications")
|
|
819
|
+
.for("CRISPR")
|
|
820
|
+
.returnAggregate("funders", ["rcr_avg", "altmetric_median"])
|
|
821
|
+
.build();
|
|
822
|
+
|
|
823
|
+
expect(query).toBe(
|
|
824
|
+
'search publications for "CRISPR" return funders aggregate rcr_avg, altmetric_median',
|
|
825
|
+
);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
it("generates aggregated facet with sort", () => {
|
|
829
|
+
const query = builder
|
|
830
|
+
.search("publications")
|
|
831
|
+
.for("CRISPR")
|
|
832
|
+
.returnAggregate("funders", ["rcr_avg"], {
|
|
833
|
+
sortBy: "rcr_avg",
|
|
834
|
+
sortOrder: "desc",
|
|
835
|
+
})
|
|
836
|
+
.build();
|
|
837
|
+
|
|
838
|
+
expect(query).toBe(
|
|
839
|
+
'search publications for "CRISPR" return funders aggregate rcr_avg sort by rcr_avg desc',
|
|
840
|
+
);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it("generates aggregated facet with sort by count", () => {
|
|
844
|
+
const query = builder
|
|
845
|
+
.search("publications")
|
|
846
|
+
.for("CRISPR")
|
|
847
|
+
.returnAggregate("funders", ["rcr_avg"], {
|
|
848
|
+
sortBy: "count",
|
|
849
|
+
sortOrder: "asc",
|
|
850
|
+
})
|
|
851
|
+
.build();
|
|
852
|
+
|
|
853
|
+
expect(query).toBe(
|
|
854
|
+
'search publications for "CRISPR" return funders aggregate rcr_avg sort by count asc',
|
|
855
|
+
);
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
it("generates aggregated facet with limit", () => {
|
|
859
|
+
const query = builder
|
|
860
|
+
.search("publications")
|
|
861
|
+
.for("CRISPR")
|
|
862
|
+
.returnAggregate("funders", ["rcr_avg"], { limit: 10 })
|
|
863
|
+
.build();
|
|
864
|
+
|
|
865
|
+
expect(query).toBe(
|
|
866
|
+
'search publications for "CRISPR" return funders aggregate rcr_avg limit 10',
|
|
867
|
+
);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
it("generates aggregated facet with all options", () => {
|
|
871
|
+
const query = builder
|
|
872
|
+
.search("publications")
|
|
873
|
+
.for("CRISPR")
|
|
874
|
+
.returnAggregate("funders", ["rcr_avg", "altmetric_median"], {
|
|
875
|
+
sortBy: "rcr_avg",
|
|
876
|
+
sortOrder: "desc",
|
|
877
|
+
limit: 10,
|
|
878
|
+
})
|
|
879
|
+
.build();
|
|
880
|
+
|
|
881
|
+
expect(query).toBe(
|
|
882
|
+
'search publications for "CRISPR" return funders aggregate rcr_avg, altmetric_median sort by rcr_avg desc limit 10',
|
|
883
|
+
);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
it("combines entity return with facet return", () => {
|
|
887
|
+
const query = builder
|
|
888
|
+
.search("publications")
|
|
889
|
+
.for("CRISPR")
|
|
890
|
+
.fields(["id", "title"])
|
|
891
|
+
.limit(100)
|
|
892
|
+
.returnFacet("year")
|
|
893
|
+
.build();
|
|
894
|
+
|
|
895
|
+
expect(query).toBe(
|
|
896
|
+
'search publications for "CRISPR" return publications[id+title] limit 100 return year',
|
|
897
|
+
);
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
it("combines entity return with aggregated facet", () => {
|
|
901
|
+
const query = builder
|
|
902
|
+
.search("publications")
|
|
903
|
+
.for("CRISPR")
|
|
904
|
+
.where("year", ">=", 2020)
|
|
905
|
+
.fields(["id", "title"])
|
|
906
|
+
.returnAggregate("funders", ["rcr_avg"], {
|
|
907
|
+
sortBy: "rcr_avg",
|
|
908
|
+
limit: 10,
|
|
909
|
+
})
|
|
910
|
+
.build();
|
|
911
|
+
|
|
912
|
+
expect(query).toBe(
|
|
913
|
+
'search publications for "CRISPR" where year >= 2020 return publications[id+title] return funders aggregate rcr_avg sort by rcr_avg desc limit 10',
|
|
914
|
+
);
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
it("generates grant facet with funding aggregation", () => {
|
|
918
|
+
const query = builder
|
|
919
|
+
.search("grants")
|
|
920
|
+
.for("cancer")
|
|
921
|
+
.returnAggregate("funders", ["funding"], {
|
|
922
|
+
sortBy: "funding",
|
|
923
|
+
sortOrder: "desc",
|
|
924
|
+
limit: 10,
|
|
925
|
+
})
|
|
926
|
+
.build();
|
|
927
|
+
|
|
928
|
+
expect(query).toBe(
|
|
929
|
+
'search grants for "cancer" return funders aggregate funding sort by funding desc limit 10',
|
|
930
|
+
);
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
it("resets facet clauses on reset", () => {
|
|
934
|
+
builder.search("publications").for("test").returnFacet("year");
|
|
935
|
+
|
|
936
|
+
builder.reset();
|
|
937
|
+
|
|
938
|
+
expect(() => builder.build()).toThrow("Entity type must be specified");
|
|
939
|
+
});
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
describe("queries with similar_documents()", () => {
|
|
943
|
+
it("builds query with similar_documents function", () => {
|
|
944
|
+
const query = builder
|
|
945
|
+
.search("publications")
|
|
946
|
+
.forSimilar("Machine learning for drug discovery")
|
|
947
|
+
.build();
|
|
948
|
+
|
|
949
|
+
expect(query).toBe(
|
|
950
|
+
'search publications for similar_documents("Machine learning for drug discovery")',
|
|
951
|
+
);
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
it("escapes quotes in similar text", () => {
|
|
955
|
+
const query = builder.search("publications").forSimilar('Text with "quotes" inside').build();
|
|
956
|
+
|
|
957
|
+
expect(query).toBe(
|
|
958
|
+
'search publications for similar_documents("Text with \\"quotes\\" inside")',
|
|
959
|
+
);
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
it("normalizes newlines in similar text", () => {
|
|
963
|
+
const query = builder
|
|
964
|
+
.search("publications")
|
|
965
|
+
.forSimilar("Line one\nLine two\nLine three")
|
|
966
|
+
.build();
|
|
967
|
+
|
|
968
|
+
expect(query).toBe(
|
|
969
|
+
'search publications for similar_documents("Line one\\nLine two\\nLine three")',
|
|
970
|
+
);
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
it("combines similar_documents with where clause", () => {
|
|
974
|
+
const query = builder
|
|
975
|
+
.search("publications")
|
|
976
|
+
.forSimilar("Spinal cord injury macrophage polarization")
|
|
977
|
+
.where("year", ">", 2015)
|
|
978
|
+
.build();
|
|
979
|
+
|
|
980
|
+
expect(query).toBe(
|
|
981
|
+
'search publications for similar_documents("Spinal cord injury macrophage polarization") where year > 2015',
|
|
982
|
+
);
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
it("combines similar_documents with limit", () => {
|
|
986
|
+
const query = builder
|
|
987
|
+
.search("publications")
|
|
988
|
+
.forSimilar("Cancer immunotherapy")
|
|
989
|
+
.limit(10)
|
|
990
|
+
.build();
|
|
991
|
+
|
|
992
|
+
expect(query).toBe(
|
|
993
|
+
'search publications for similar_documents("Cancer immunotherapy") return publications limit 10',
|
|
994
|
+
);
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
it("combines similar_documents with fields", () => {
|
|
998
|
+
const query = builder
|
|
999
|
+
.search("publications")
|
|
1000
|
+
.forSimilar("Neural networks deep learning")
|
|
1001
|
+
.fields(["id", "title", "year"])
|
|
1002
|
+
.build();
|
|
1003
|
+
|
|
1004
|
+
expect(query).toBe(
|
|
1005
|
+
'search publications for similar_documents("Neural networks deep learning") return publications[id+title+year]',
|
|
1006
|
+
);
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
it("works with grants entity", () => {
|
|
1010
|
+
const query = builder
|
|
1011
|
+
.search("grants")
|
|
1012
|
+
.forSimilar("Cancer immunotherapy research")
|
|
1013
|
+
.where("start_year", ">=", 2020)
|
|
1014
|
+
.limit(5)
|
|
1015
|
+
.build();
|
|
1016
|
+
|
|
1017
|
+
expect(query).toBe(
|
|
1018
|
+
'search grants for similar_documents("Cancer immunotherapy research") where start_year >= 2020 return grants limit 5',
|
|
1019
|
+
);
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
it("resets similar text on reset", () => {
|
|
1023
|
+
builder.search("publications").forSimilar("test text");
|
|
1024
|
+
builder.reset();
|
|
1025
|
+
|
|
1026
|
+
expect(() => builder.build()).toThrow("Entity type must be specified");
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
describe("facet return validation", () => {
|
|
1031
|
+
it("throws ValidationError on empty field name", () => {
|
|
1032
|
+
expect(() => builder.search("publications").for("test").returnFacet("")).toThrow(
|
|
1033
|
+
ValidationError,
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
try {
|
|
1037
|
+
builder.search("publications").for("test").returnFacet("");
|
|
1038
|
+
} catch (e) {
|
|
1039
|
+
expect((e as ValidationError).message).toBe("Invalid field name: ");
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
it("throws ValidationError on whitespace-only field name", () => {
|
|
1044
|
+
expect(() => builder.search("publications").for("test").returnFacet(" ")).toThrow(
|
|
1045
|
+
ValidationError,
|
|
1046
|
+
);
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
it("throws ValidationError on negative facet limit", () => {
|
|
1050
|
+
expect(() =>
|
|
1051
|
+
builder.search("publications").for("test").returnFacet("year", { limit: -1 }),
|
|
1052
|
+
).toThrow(ValidationError);
|
|
1053
|
+
|
|
1054
|
+
try {
|
|
1055
|
+
builder.search("publications").for("test").returnFacet("year", { limit: -1 });
|
|
1056
|
+
} catch (e) {
|
|
1057
|
+
expect((e as ValidationError).message).toBe("Facet limit must be non-negative");
|
|
1058
|
+
expect((e as ValidationError).details?.limit).toBe(-1);
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
it("allows zero facet limit", () => {
|
|
1063
|
+
const query = builder
|
|
1064
|
+
.search("publications")
|
|
1065
|
+
.for("test")
|
|
1066
|
+
.returnFacet("year", { limit: 0 })
|
|
1067
|
+
.build();
|
|
1068
|
+
|
|
1069
|
+
expect(query).toContain("return year limit 0");
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
it("throws ValidationError on empty field name for aggregate", () => {
|
|
1073
|
+
expect(() =>
|
|
1074
|
+
builder.search("publications").for("test").returnAggregate("", ["rcr_avg"]),
|
|
1075
|
+
).toThrow(ValidationError);
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
it("throws ValidationError on whitespace-only field name for aggregate", () => {
|
|
1079
|
+
expect(() =>
|
|
1080
|
+
builder.search("publications").for("test").returnAggregate(" ", ["rcr_avg"]),
|
|
1081
|
+
).toThrow(ValidationError);
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
it("throws ValidationError on empty indicators array", () => {
|
|
1085
|
+
expect(() =>
|
|
1086
|
+
builder.search("publications").for("test").returnAggregate("funders", []),
|
|
1087
|
+
).toThrow(ValidationError);
|
|
1088
|
+
|
|
1089
|
+
try {
|
|
1090
|
+
builder.search("publications").for("test").returnAggregate("funders", []);
|
|
1091
|
+
} catch (e) {
|
|
1092
|
+
expect((e as ValidationError).message).toBe("Aggregate must have at least one indicator");
|
|
1093
|
+
expect((e as ValidationError).details?.field).toBe("funders");
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
it("throws ValidationError on negative aggregate limit", () => {
|
|
1098
|
+
expect(() =>
|
|
1099
|
+
builder
|
|
1100
|
+
.search("publications")
|
|
1101
|
+
.for("test")
|
|
1102
|
+
.returnAggregate("funders", ["rcr_avg"], { limit: -1 }),
|
|
1103
|
+
).toThrow(ValidationError);
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
it("throws ValidationError on invalid sortBy value", () => {
|
|
1107
|
+
try {
|
|
1108
|
+
builder
|
|
1109
|
+
.search("publications")
|
|
1110
|
+
.for("test")
|
|
1111
|
+
.returnAggregate("funders", ["rcr_avg"], { sortBy: "invalid" });
|
|
1112
|
+
} catch (e) {
|
|
1113
|
+
expect(e).toBeInstanceOf(ValidationError);
|
|
1114
|
+
expect((e as ValidationError).message).toContain("sortBy must be one of");
|
|
1115
|
+
expect((e as ValidationError).details?.sortBy).toBe("invalid");
|
|
1116
|
+
expect((e as ValidationError).details?.validSortTargets).toEqual(["rcr_avg", "count"]);
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
it("throws ValidationError on empty indicator string", () => {
|
|
1121
|
+
expect(() =>
|
|
1122
|
+
builder.search("publications").for("test").returnAggregate("funders", [""]),
|
|
1123
|
+
).toThrow(ValidationError);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
it("throws ValidationError on whitespace-only indicator", () => {
|
|
1127
|
+
expect(() =>
|
|
1128
|
+
builder.search("publications").for("test").returnAggregate("funders", ["rcr_avg", " "]),
|
|
1129
|
+
).toThrow(ValidationError);
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
it("throws ValidationError on sortBy not matching any indicator", () => {
|
|
1133
|
+
expect(() =>
|
|
1134
|
+
builder
|
|
1135
|
+
.search("publications")
|
|
1136
|
+
.for("test")
|
|
1137
|
+
.returnAggregate("funders", ["rcr_avg", "altmetric_median"], {
|
|
1138
|
+
sortBy: "times_cited",
|
|
1139
|
+
}),
|
|
1140
|
+
).toThrow(ValidationError);
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
it("allows sortBy count", () => {
|
|
1144
|
+
const query = builder
|
|
1145
|
+
.search("publications")
|
|
1146
|
+
.for("test")
|
|
1147
|
+
.returnAggregate("funders", ["rcr_avg"], { sortBy: "count" })
|
|
1148
|
+
.build();
|
|
1149
|
+
|
|
1150
|
+
expect(query).toContain("sort by count");
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
it("allows sortBy matching an indicator", () => {
|
|
1154
|
+
const query = builder
|
|
1155
|
+
.search("publications")
|
|
1156
|
+
.for("test")
|
|
1157
|
+
.returnAggregate("funders", ["rcr_avg", "altmetric_median"], {
|
|
1158
|
+
sortBy: "altmetric_median",
|
|
1159
|
+
})
|
|
1160
|
+
.build();
|
|
1161
|
+
|
|
1162
|
+
expect(query).toContain("sort by altmetric_median");
|
|
1163
|
+
});
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
describe("grouped returns", () => {
|
|
1167
|
+
it("generates correct DSL for grouped entity return", () => {
|
|
1168
|
+
const query = builder
|
|
1169
|
+
.search("publications")
|
|
1170
|
+
.for("test")
|
|
1171
|
+
.returnGrouped("docs", { fields: ["id", "title"] })
|
|
1172
|
+
.build();
|
|
1173
|
+
|
|
1174
|
+
expect(query).toContain('return in "docs" publications[id+title]');
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
it("generates correct DSL for grouped facet return", () => {
|
|
1178
|
+
const query = builder
|
|
1179
|
+
.search("publications")
|
|
1180
|
+
.for("test")
|
|
1181
|
+
.fields(["id", "title"])
|
|
1182
|
+
.returnGroupedFacet("facets", "funders")
|
|
1183
|
+
.build();
|
|
1184
|
+
|
|
1185
|
+
expect(query).toContain("return publications[id+title]");
|
|
1186
|
+
expect(query).toContain('return in "facets" funders');
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
it("generates correct DSL for grouped aggregated facet", () => {
|
|
1190
|
+
const query = builder
|
|
1191
|
+
.search("publications")
|
|
1192
|
+
.for("test")
|
|
1193
|
+
.returnGroupedAggregate("metrics", "funders", ["rcr_avg", "funding_usd"], {
|
|
1194
|
+
sortBy: "rcr_avg",
|
|
1195
|
+
sortOrder: "desc",
|
|
1196
|
+
})
|
|
1197
|
+
.build();
|
|
1198
|
+
|
|
1199
|
+
expect(query).toContain(
|
|
1200
|
+
'return in "metrics" funders aggregate rcr_avg, funding_usd sort by rcr_avg desc',
|
|
1201
|
+
);
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
it("supports multiple grouped returns", () => {
|
|
1205
|
+
const query = builder
|
|
1206
|
+
.search("publications")
|
|
1207
|
+
.for("test")
|
|
1208
|
+
.returnGrouped("docs", { fields: ["id", "title"] })
|
|
1209
|
+
.returnGroupedFacet("years", "year", { limit: 10 })
|
|
1210
|
+
.returnGroupedAggregate("orgs", "research_orgs", ["rcr_avg"])
|
|
1211
|
+
.build();
|
|
1212
|
+
|
|
1213
|
+
expect(query).toContain('return in "docs" publications[id+title]');
|
|
1214
|
+
expect(query).toContain('return in "years" year limit 10');
|
|
1215
|
+
expect(query).toContain('return in "orgs" research_orgs aggregate rcr_avg');
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
it("rejects empty group name", () => {
|
|
1219
|
+
expect(() => {
|
|
1220
|
+
builder
|
|
1221
|
+
.search("publications")
|
|
1222
|
+
.for("test")
|
|
1223
|
+
.returnGrouped("", { fields: ["id"] })
|
|
1224
|
+
.build();
|
|
1225
|
+
}).toThrow(ValidationError);
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
it("rejects whitespace-only group name", () => {
|
|
1229
|
+
expect(() => {
|
|
1230
|
+
builder
|
|
1231
|
+
.search("publications")
|
|
1232
|
+
.for("test")
|
|
1233
|
+
.returnGrouped(" ", { fields: ["id"] })
|
|
1234
|
+
.build();
|
|
1235
|
+
}).toThrow(ValidationError);
|
|
1236
|
+
});
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
describe("limit(0) with facets (facet-only queries)", () => {
|
|
1240
|
+
it("skips entity return when limit(0) with facets and no fields", () => {
|
|
1241
|
+
const query = builder
|
|
1242
|
+
.search("publications")
|
|
1243
|
+
.for("CRISPR")
|
|
1244
|
+
.limit(0)
|
|
1245
|
+
.returnFacet("year")
|
|
1246
|
+
.build();
|
|
1247
|
+
expect(query).toBe('search publications for "CRISPR" return year');
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
it("skips entity return when limit(0) with time-series", () => {
|
|
1251
|
+
const query = builder
|
|
1252
|
+
.search("publications")
|
|
1253
|
+
.for("CRISPR")
|
|
1254
|
+
.limit(0)
|
|
1255
|
+
.returnCitationsPerYear(2010, 2023)
|
|
1256
|
+
.build();
|
|
1257
|
+
expect(query).toBe('search publications for "CRISPR" return citations_per_year(2010, 2023)');
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
it("throws when limit(0) with fields", () => {
|
|
1261
|
+
expect(() =>
|
|
1262
|
+
builder
|
|
1263
|
+
.search("publications")
|
|
1264
|
+
.for("CRISPR")
|
|
1265
|
+
.fields(["id"])
|
|
1266
|
+
.limit(0)
|
|
1267
|
+
.returnFacet("year")
|
|
1268
|
+
.build(),
|
|
1269
|
+
).toThrow(ValidationError);
|
|
1270
|
+
|
|
1271
|
+
try {
|
|
1272
|
+
builder
|
|
1273
|
+
.search("publications")
|
|
1274
|
+
.for("CRISPR")
|
|
1275
|
+
.fields(["id"])
|
|
1276
|
+
.limit(0)
|
|
1277
|
+
.returnFacet("year")
|
|
1278
|
+
.build();
|
|
1279
|
+
} catch (e) {
|
|
1280
|
+
expect((e as ValidationError).message).toContain("limit(0) cannot be used with fields()");
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
it("keeps entity return when limit > 0 with facets", () => {
|
|
1285
|
+
const query = builder
|
|
1286
|
+
.search("publications")
|
|
1287
|
+
.for("CRISPR")
|
|
1288
|
+
.limit(10)
|
|
1289
|
+
.returnFacet("year")
|
|
1290
|
+
.build();
|
|
1291
|
+
expect(query).toBe(
|
|
1292
|
+
'search publications for "CRISPR" return publications limit 10 return year',
|
|
1293
|
+
);
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
it("throws when limit(0) without facets", () => {
|
|
1297
|
+
expect(() => builder.search("publications").for("CRISPR").limit(0).build()).toThrow(
|
|
1298
|
+
ValidationError,
|
|
1299
|
+
);
|
|
1300
|
+
|
|
1301
|
+
try {
|
|
1302
|
+
builder.search("publications").for("CRISPR").limit(0).build();
|
|
1303
|
+
} catch (e) {
|
|
1304
|
+
expect((e as ValidationError).message).toContain("limit(0) requires at least one facet");
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
it("skips entity return when limit(0) with grouped returns", () => {
|
|
1309
|
+
const query = builder
|
|
1310
|
+
.search("publications")
|
|
1311
|
+
.for("CRISPR")
|
|
1312
|
+
.limit(0)
|
|
1313
|
+
.returnGroupedFacet("facets", "year")
|
|
1314
|
+
.build();
|
|
1315
|
+
expect(query).toBe('search publications for "CRISPR" return in "facets" year');
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
it("skips entity return when limit(0) with multiple facets", () => {
|
|
1319
|
+
const query = builder
|
|
1320
|
+
.search("publications")
|
|
1321
|
+
.for("CRISPR")
|
|
1322
|
+
.limit(0)
|
|
1323
|
+
.returnFacet("year")
|
|
1324
|
+
.returnFacet("funders", { limit: 10 })
|
|
1325
|
+
.build();
|
|
1326
|
+
expect(query).toBe('search publications for "CRISPR" return year return funders limit 10');
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
it("skips entity return AND skip when limit(0) with skip and facets", () => {
|
|
1330
|
+
const query = builder
|
|
1331
|
+
.search("publications")
|
|
1332
|
+
.for("CRISPR")
|
|
1333
|
+
.limit(0)
|
|
1334
|
+
.skip(10)
|
|
1335
|
+
.returnFacet("year")
|
|
1336
|
+
.build();
|
|
1337
|
+
// skip should also be suppressed since there's no entity return
|
|
1338
|
+
expect(query).toBe('search publications for "CRISPR" return year');
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
it("skips entity return AND sort when limit(0) with sort and facets", () => {
|
|
1342
|
+
const query = builder
|
|
1343
|
+
.search("publications")
|
|
1344
|
+
.for("CRISPR")
|
|
1345
|
+
.limit(0)
|
|
1346
|
+
.sort("year", "desc")
|
|
1347
|
+
.returnFacet("funders")
|
|
1348
|
+
.build();
|
|
1349
|
+
// sort should also be suppressed since there's no entity return
|
|
1350
|
+
expect(query).toBe('search publications for "CRISPR" return funders');
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
it("skips entity return AND all modifiers when limit(0) with sort+skip and facets", () => {
|
|
1354
|
+
const query = builder
|
|
1355
|
+
.search("publications")
|
|
1356
|
+
.for("CRISPR")
|
|
1357
|
+
.limit(0)
|
|
1358
|
+
.sort("times_cited", "desc")
|
|
1359
|
+
.skip(50)
|
|
1360
|
+
.returnFacet("year")
|
|
1361
|
+
.returnFacet("funders", { limit: 5 })
|
|
1362
|
+
.build();
|
|
1363
|
+
// All modifiers should be suppressed since there's no entity return
|
|
1364
|
+
expect(query).toBe('search publications for "CRISPR" return year return funders limit 5');
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
it("keeps entity return and skip when skip(0) without limit(0)", () => {
|
|
1368
|
+
const query = builder
|
|
1369
|
+
.search("publications")
|
|
1370
|
+
.for("CRISPR")
|
|
1371
|
+
.skip(0)
|
|
1372
|
+
.returnFacet("year")
|
|
1373
|
+
.build();
|
|
1374
|
+
// skip(0) alone should still trigger entity return (skip=0 is redundant but valid)
|
|
1375
|
+
expect(query).toBe('search publications for "CRISPR" return publications skip 0 return year');
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
it("keeps all modifiers when limit > 0 with facets", () => {
|
|
1379
|
+
const query = builder
|
|
1380
|
+
.search("publications")
|
|
1381
|
+
.for("CRISPR")
|
|
1382
|
+
.limit(10)
|
|
1383
|
+
.skip(5)
|
|
1384
|
+
.sort("year", "desc")
|
|
1385
|
+
.returnFacet("funders")
|
|
1386
|
+
.build();
|
|
1387
|
+
expect(query).toBe(
|
|
1388
|
+
'search publications for "CRISPR" return publications sort by year desc limit 10 skip 5 return funders',
|
|
1389
|
+
);
|
|
1390
|
+
});
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
describe("DSL injection prevention", () => {
|
|
1394
|
+
it("escapes injection payload in where() string value", () => {
|
|
1395
|
+
const query = builder
|
|
1396
|
+
.search("publications")
|
|
1397
|
+
.for("test")
|
|
1398
|
+
.where("doi", "=", 'test" return publications limit 9999 //')
|
|
1399
|
+
.build();
|
|
1400
|
+
|
|
1401
|
+
expect(query).toBe(
|
|
1402
|
+
'search publications for "test" where doi = "test\\" return publications limit 9999 //"',
|
|
1403
|
+
);
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
it("escapes injection payload in for() search terms", () => {
|
|
1407
|
+
const query = builder
|
|
1408
|
+
.search("publications")
|
|
1409
|
+
.for('test" return publications limit 9999 //')
|
|
1410
|
+
.build();
|
|
1411
|
+
|
|
1412
|
+
expect(query).toBe('search publications for "test\\" return publications limit 9999 //"');
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
it("escapes injection payload in whereIn() string values", () => {
|
|
1416
|
+
const query = builder
|
|
1417
|
+
.search("publications")
|
|
1418
|
+
.for("test")
|
|
1419
|
+
.whereIn("type", ['article" or 1=1 //', "review"])
|
|
1420
|
+
.build();
|
|
1421
|
+
|
|
1422
|
+
expect(query).toBe(
|
|
1423
|
+
'search publications for "test" where type in ["article\\" or 1=1 //", "review"]',
|
|
1424
|
+
);
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
it("escapes injection payload in whereRange() string values", () => {
|
|
1428
|
+
const query = builder
|
|
1429
|
+
.search("publications")
|
|
1430
|
+
.for("test")
|
|
1431
|
+
.whereRange("date", '2020-01-01" or 1=1 //', "2020-12-31")
|
|
1432
|
+
.build();
|
|
1433
|
+
|
|
1434
|
+
expect(query).toBe(
|
|
1435
|
+
'search publications for "test" where date in ["2020-01-01\\" or 1=1 //":"2020-12-31"]',
|
|
1436
|
+
);
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
it("escapes backslashes in where() to prevent escape bypass", () => {
|
|
1440
|
+
const query = builder
|
|
1441
|
+
.search("publications")
|
|
1442
|
+
.for("test")
|
|
1443
|
+
.where("doi", "=", 'test\\" return publications limit 9999 //')
|
|
1444
|
+
.build();
|
|
1445
|
+
|
|
1446
|
+
expect(query).toContain('test\\\\\\"');
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
it("escapes newlines in for() search terms", () => {
|
|
1450
|
+
const query = builder.search("publications").for("line1\nline2").build();
|
|
1451
|
+
|
|
1452
|
+
expect(query).toBe('search publications for "line1\\nline2"');
|
|
1453
|
+
});
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
describe("field name validation", () => {
|
|
1457
|
+
const injectionPayloads = [
|
|
1458
|
+
"year >= 2020 return publications limit 1 //",
|
|
1459
|
+
'year" return publications //',
|
|
1460
|
+
"field\nreturn publications",
|
|
1461
|
+
"field; DROP TABLE",
|
|
1462
|
+
"field name",
|
|
1463
|
+
"",
|
|
1464
|
+
"123field",
|
|
1465
|
+
"field--comment",
|
|
1466
|
+
];
|
|
1467
|
+
|
|
1468
|
+
for (const payload of injectionPayloads) {
|
|
1469
|
+
it(`rejects field name: ${JSON.stringify(payload)}`, () => {
|
|
1470
|
+
expect(() => builder.search("publications").where(payload, "=", "value")).toThrow(
|
|
1471
|
+
ValidationError,
|
|
1472
|
+
);
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
const validFields = ["year", "times_cited", "authors.first_name", "FOR_first", "mesh_terms"];
|
|
1477
|
+
|
|
1478
|
+
for (const field of validFields) {
|
|
1479
|
+
it(`accepts valid field name: ${field}`, () => {
|
|
1480
|
+
expect(() => builder.search("publications").where(field, "=", "test")).not.toThrow();
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
it("validates field in whereEmpty()", () => {
|
|
1485
|
+
expect(() => builder.search("publications").whereEmpty("bad field")).toThrow(ValidationError);
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
it("validates field in whereNotEmpty()", () => {
|
|
1489
|
+
expect(() => builder.search("publications").whereNotEmpty("bad\nfield")).toThrow(
|
|
1490
|
+
ValidationError,
|
|
1491
|
+
);
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
it("validates field in whereIn()", () => {
|
|
1495
|
+
expect(() => builder.search("publications").whereIn("bad field", ["a"])).toThrow(
|
|
1496
|
+
ValidationError,
|
|
1497
|
+
);
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
it("validates field in whereRange()", () => {
|
|
1501
|
+
expect(() => builder.search("publications").whereRange("bad field", "1", "2")).toThrow(
|
|
1502
|
+
ValidationError,
|
|
1503
|
+
);
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
it("validates field in whereCount()", () => {
|
|
1507
|
+
expect(() => builder.search("publications").whereCount("bad field", ">", 5)).toThrow(
|
|
1508
|
+
ValidationError,
|
|
1509
|
+
);
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
it("validates field in sort()", () => {
|
|
1513
|
+
expect(() => builder.search("publications").sort("bad field")).toThrow(ValidationError);
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
it("validates field in fields()", () => {
|
|
1517
|
+
expect(() => builder.search("publications").fields(["id", "bad field"])).toThrow(
|
|
1518
|
+
ValidationError,
|
|
1519
|
+
);
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
it("validates field in fieldsWithUnnest() regular fields", () => {
|
|
1523
|
+
expect(() =>
|
|
1524
|
+
builder.search("publications").fieldsWithUnnest(["bad field"], ["researchers"]),
|
|
1525
|
+
).toThrow(ValidationError);
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
it("validates field in fieldsWithUnnest() unnest fields", () => {
|
|
1529
|
+
expect(() => builder.search("publications").fieldsWithUnnest(["id"], ["bad field"])).toThrow(
|
|
1530
|
+
ValidationError,
|
|
1531
|
+
);
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
it("validates field in addUnnest()", () => {
|
|
1535
|
+
expect(() => builder.search("publications").addUnnest("bad field")).toThrow(ValidationError);
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
it("validates field in returnFacet()", () => {
|
|
1539
|
+
expect(() => builder.search("publications").returnFacet("bad field")).toThrow(
|
|
1540
|
+
ValidationError,
|
|
1541
|
+
);
|
|
1542
|
+
});
|
|
1543
|
+
|
|
1544
|
+
it("validates field in returnAggregate()", () => {
|
|
1545
|
+
expect(() => builder.search("publications").returnAggregate("bad field", ["count"])).toThrow(
|
|
1546
|
+
ValidationError,
|
|
1547
|
+
);
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
it("validates fields in returnGrouped()", () => {
|
|
1551
|
+
expect(() =>
|
|
1552
|
+
builder.search("publications").returnGrouped("g", { fields: ["bad field"] }),
|
|
1553
|
+
).toThrow(ValidationError);
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
it("validates sortField in returnGrouped()", () => {
|
|
1557
|
+
expect(() =>
|
|
1558
|
+
builder.search("publications").returnGrouped("g", { sortField: "bad field" }),
|
|
1559
|
+
).toThrow(ValidationError);
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
it("validates field in returnGroupedFacet()", () => {
|
|
1563
|
+
expect(() => builder.search("publications").returnGroupedFacet("g", "bad field")).toThrow(
|
|
1564
|
+
ValidationError,
|
|
1565
|
+
);
|
|
1566
|
+
});
|
|
1567
|
+
|
|
1568
|
+
it("validates field in returnGroupedAggregate()", () => {
|
|
1569
|
+
expect(() =>
|
|
1570
|
+
builder.search("publications").returnGroupedAggregate("g", "bad field", ["count"]),
|
|
1571
|
+
).toThrow(ValidationError);
|
|
1572
|
+
});
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
describe("search text length with sanitization", () => {
|
|
1576
|
+
it("accepts text at limit after zero-width char stripping", () => {
|
|
1577
|
+
// 10000 real chars + 100 zero-width chars = 10100 raw length
|
|
1578
|
+
// After sanitization: 10000 chars — should pass
|
|
1579
|
+
const text = "a".repeat(10_000) + "\u200B".repeat(100);
|
|
1580
|
+
expect(() => new QueryBuilder().search("publications").for(text)).not.toThrow();
|
|
1581
|
+
});
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
describe("clone", () => {
|
|
1585
|
+
it("creates an independent copy with the same state", () => {
|
|
1586
|
+
builder.search("publications").for("test").where("year", ">=", 2020);
|
|
1587
|
+
const cloned = builder.clone();
|
|
1588
|
+
|
|
1589
|
+
expect(cloned.build()).toBe(builder.build());
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
it("does not affect the original when the clone is mutated", () => {
|
|
1593
|
+
builder.search("publications").for("test");
|
|
1594
|
+
const cloned = builder.clone();
|
|
1595
|
+
cloned.where("year", ">=", 2020);
|
|
1596
|
+
|
|
1597
|
+
expect(builder.build()).toBe('search publications for "test"');
|
|
1598
|
+
expect(cloned.build()).toBe('search publications for "test" where year >= 2020');
|
|
1599
|
+
});
|
|
1600
|
+
|
|
1601
|
+
it("does not affect the clone when the original is mutated", () => {
|
|
1602
|
+
builder.search("publications").for("test");
|
|
1603
|
+
const cloned = builder.clone();
|
|
1604
|
+
builder.where("type", "=", "article");
|
|
1605
|
+
|
|
1606
|
+
expect(cloned.build()).toBe('search publications for "test"');
|
|
1607
|
+
expect(builder.build()).toBe('search publications for "test" where type = "article"');
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
it("clones facet clauses independently", () => {
|
|
1611
|
+
builder.search("publications").for("test").returnFacet("year");
|
|
1612
|
+
const cloned = builder.clone();
|
|
1613
|
+
cloned.returnFacet("funders");
|
|
1614
|
+
|
|
1615
|
+
expect(builder.build()).toBe('search publications for "test" return year');
|
|
1616
|
+
expect(cloned.build()).toBe('search publications for "test" return year return funders');
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1619
|
+
it("clones fields independently", () => {
|
|
1620
|
+
builder.search("publications").for("test").fields(["id", "title"]);
|
|
1621
|
+
const cloned = builder.clone();
|
|
1622
|
+
cloned.fields(["id", "title", "doi"]);
|
|
1623
|
+
|
|
1624
|
+
expect(builder.build()).toBe('search publications for "test" return publications[id+title]');
|
|
1625
|
+
expect(cloned.build()).toBe(
|
|
1626
|
+
'search publications for "test" return publications[id+title+doi]',
|
|
1627
|
+
);
|
|
1628
|
+
});
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
describe("queries with complex()", () => {
|
|
1632
|
+
it("builds query with complex function", () => {
|
|
1633
|
+
const query = builder.search("publications").forComplex("quantum networking", 3).build();
|
|
1634
|
+
|
|
1635
|
+
expect(query).toBe('search publications for complex("quantum networking", 3)');
|
|
1636
|
+
});
|
|
1637
|
+
|
|
1638
|
+
it("escapes quotes in complex phrase", () => {
|
|
1639
|
+
const query = builder.search("publications").forComplex('phrase with "quotes"', 5).build();
|
|
1640
|
+
|
|
1641
|
+
expect(query).toBe('search publications for complex("phrase with \\"quotes\\"", 5)');
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
it("combines complex with where clause", () => {
|
|
1645
|
+
const query = builder
|
|
1646
|
+
.search("publications")
|
|
1647
|
+
.forComplex("CRISPR gene editing", 5)
|
|
1648
|
+
.where("year", ">", 2020)
|
|
1649
|
+
.build();
|
|
1650
|
+
|
|
1651
|
+
expect(query).toBe(
|
|
1652
|
+
'search publications for complex("CRISPR gene editing", 5) where year > 2020',
|
|
1653
|
+
);
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
it("combines complex with in index", () => {
|
|
1657
|
+
const query = builder
|
|
1658
|
+
.search("publications")
|
|
1659
|
+
.in("title_abstract_only")
|
|
1660
|
+
.forComplex("quantum networking", 3)
|
|
1661
|
+
.build();
|
|
1662
|
+
|
|
1663
|
+
expect(query).toBe(
|
|
1664
|
+
'search publications in title_abstract_only for complex("quantum networking", 3)',
|
|
1665
|
+
);
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
it("combines complex with limit and fields", () => {
|
|
1669
|
+
const query = builder
|
|
1670
|
+
.search("publications")
|
|
1671
|
+
.forComplex("quantum networking", 3)
|
|
1672
|
+
.fields(["id", "title"])
|
|
1673
|
+
.limit(10)
|
|
1674
|
+
.build();
|
|
1675
|
+
|
|
1676
|
+
expect(query).toBe(
|
|
1677
|
+
'search publications for complex("quantum networking", 3) return publications[id+title] limit 10',
|
|
1678
|
+
);
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
it("throws on maxDist < 1", () => {
|
|
1682
|
+
expect(() => builder.search("publications").forComplex("test", 0)).toThrow(ValidationError);
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1685
|
+
it("resets complex fields on reset", () => {
|
|
1686
|
+
builder.search("publications").forComplex("test", 3);
|
|
1687
|
+
builder.reset();
|
|
1688
|
+
|
|
1689
|
+
expect(() => builder.build()).toThrow("Entity type must be specified");
|
|
1690
|
+
});
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
describe("queries with min_should_match()", () => {
|
|
1694
|
+
it("builds query with min_should_match function", () => {
|
|
1695
|
+
const query = builder
|
|
1696
|
+
.search("publications")
|
|
1697
|
+
.forMinShouldMatch("quantum OR optical networking", 2)
|
|
1698
|
+
.build();
|
|
1699
|
+
|
|
1700
|
+
expect(query).toBe(
|
|
1701
|
+
'search publications for min_should_match("quantum OR optical networking", 2)',
|
|
1702
|
+
);
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
it("escapes quotes in min_should_match phrase", () => {
|
|
1706
|
+
const query = builder
|
|
1707
|
+
.search("publications")
|
|
1708
|
+
.forMinShouldMatch('phrase with "quotes"', 2)
|
|
1709
|
+
.build();
|
|
1710
|
+
|
|
1711
|
+
expect(query).toBe('search publications for min_should_match("phrase with \\"quotes\\"", 2)');
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
it("combines min_should_match with where clause", () => {
|
|
1715
|
+
const query = builder
|
|
1716
|
+
.search("publications")
|
|
1717
|
+
.forMinShouldMatch("machine learning deep neural", 2)
|
|
1718
|
+
.where("year", ">=", 2020)
|
|
1719
|
+
.build();
|
|
1720
|
+
|
|
1721
|
+
expect(query).toBe(
|
|
1722
|
+
'search publications for min_should_match("machine learning deep neural", 2) where year >= 2020',
|
|
1723
|
+
);
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
it("combines min_should_match with in index", () => {
|
|
1727
|
+
const query = builder
|
|
1728
|
+
.search("publications")
|
|
1729
|
+
.in("title_only")
|
|
1730
|
+
.forMinShouldMatch("quantum optical", 1)
|
|
1731
|
+
.build();
|
|
1732
|
+
|
|
1733
|
+
expect(query).toBe(
|
|
1734
|
+
'search publications in title_only for min_should_match("quantum optical", 1)',
|
|
1735
|
+
);
|
|
1736
|
+
});
|
|
1737
|
+
|
|
1738
|
+
it("combines min_should_match with limit", () => {
|
|
1739
|
+
const query = builder
|
|
1740
|
+
.search("publications")
|
|
1741
|
+
.forMinShouldMatch("quantum OR optical networking", 2)
|
|
1742
|
+
.limit(5)
|
|
1743
|
+
.build();
|
|
1744
|
+
|
|
1745
|
+
expect(query).toBe(
|
|
1746
|
+
'search publications for min_should_match("quantum OR optical networking", 2) return publications limit 5',
|
|
1747
|
+
);
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
it("throws on min < 1", () => {
|
|
1751
|
+
expect(() => builder.search("publications").forMinShouldMatch("test", 0)).toThrow(
|
|
1752
|
+
ValidationError,
|
|
1753
|
+
);
|
|
1754
|
+
});
|
|
1755
|
+
|
|
1756
|
+
it("resets min_should_match fields on reset", () => {
|
|
1757
|
+
builder.search("publications").forMinShouldMatch("test", 2);
|
|
1758
|
+
builder.reset();
|
|
1759
|
+
|
|
1760
|
+
expect(() => builder.build()).toThrow("Entity type must be specified");
|
|
1761
|
+
});
|
|
1762
|
+
});
|
|
1763
|
+
|
|
1764
|
+
describe("queries with full_data_exact index", () => {
|
|
1765
|
+
it("builds query with full_data_exact index", () => {
|
|
1766
|
+
const query = builder.search("publications").in("full_data_exact").for("BRCA1").build();
|
|
1767
|
+
|
|
1768
|
+
expect(query).toBe('search publications in full_data_exact for "BRCA1"');
|
|
1769
|
+
});
|
|
1770
|
+
|
|
1771
|
+
it("validates full_data_exact as a valid index", () => {
|
|
1772
|
+
expect(() => builder.search("publications").in("full_data_exact")).not.toThrow();
|
|
1773
|
+
});
|
|
1774
|
+
});
|
|
1775
|
+
});
|