New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@hdelva/termennetwerk_client

Package Overview
Dependencies
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@hdelva/termennetwerk_client - npm Package Compare versions

Comparing version 2.0.5 to 3.0.0

img/query.svg

6

lib/examples/FuzzyAutoComplete.js

@@ -39,4 +39,4 @@ "use strict";

}
function lengthResult(expected, found) {
return -1 * found.length;
function lengthResult(expected, found, quad) {
return quad ? -1 * quad.object.value.length : -1 * found.length;
}

@@ -63,3 +63,3 @@ const fuzzyConfig = [

this.subEmitter = sorted;
this.subEmitter.on("data", (data) => this.emit("data", data));
this.subEmitter.on("data", (data, meta) => this.emit("data", data, meta));
this.subEmitter.on("end", (data) => this.emit("end", data));

@@ -66,0 +66,0 @@ this.subEmitter.on("reset", () => this.emit("reset"));

@@ -43,4 +43,4 @@ "use strict";

}
function lengthResult(expected, found) {
return -1 * found.length;
function lengthResult(expected, found, quad) {
return quad ? -1 * quad.object.value.length : -1 * found.length;
}

@@ -67,3 +67,3 @@ const strictConfig = [

this.subEmitter = sorted;
this.subEmitter.on("data", (data) => this.emit("data", data));
this.subEmitter.on("data", (data, meta) => this.emit("data", data, meta));
this.subEmitter.on("end", (data) => this.emit("end", data));

@@ -70,0 +70,0 @@ this.subEmitter.on("reset", () => this.emit("reset"));

@@ -1,1 +0,16 @@

export {};
export { default as FuzzyAutoComplete } from "./examples/FuzzyAutoComplete";
export { default as StrictAutoComplete } from "./examples/StrictAutoComplete";
export { default as QueryAgent } from "./QueryAgent";
export { default as QueryAggregator } from "./QueryAggregator";
export { default as ResultEmitter } from "./ResultEmitter";
export { default as ResultRanking } from "./ResultRanking";
export { default as ResultStore } from "./ResultStore";
export { default as ResultUniqueFilter } from "./ResultUniqueFilter";
export { default as SimilarityConfiguration } from "./similarity/SimilarityConfiguration";
export { default as NFKD } from "./normalizers/NFKD";
export { default as asymmetricDiceCoefficient } from "./similarity/asymmetricDiceCoefficient";
export { default as commonPrefixSimilarity } from "./similarity/commonPrefix";
export { default as fuzzyIndexSimilarity } from "./similarity/fuzzyIndex";
export { default as fuzzyPrefixSimilarity } from "./similarity/fuzzyPrefix";
export { default as strictPrefixSimilarity } from "./similarity/strictPrefix";
export { default as tokenwiseCompare } from "./similarity/tokenwise";

@@ -6,42 +6,35 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const FuzzyAutoComplete_1 = __importDefault(require("./examples/FuzzyAutoComplete"));
const StrictAutoComplete_1 = __importDefault(require("./examples/StrictAutoComplete"));
const QueryAgent_1 = __importDefault(require("./QueryAgent"));
const QueryAggregator_1 = __importDefault(require("./QueryAggregator"));
const ResultEmitter_1 = __importDefault(require("./ResultEmitter"));
const ResultRanking_1 = __importDefault(require("./ResultRanking"));
const ResultStore_1 = __importDefault(require("./ResultStore"));
const ResultUniqueFilter_1 = __importDefault(require("./ResultUniqueFilter"));
const asymmetricDiceCoefficient_1 = __importDefault(require("./similarity/asymmetricDiceCoefficient"));
const commonPrefix_1 = __importDefault(require("./similarity/commonPrefix"));
const fuzzyIndex_1 = __importDefault(require("./similarity/fuzzyIndex"));
const fuzzyPrefix_1 = __importDefault(require("./similarity/fuzzyPrefix"));
const SimilarityConfiguration_1 = __importDefault(require("./similarity/SimilarityConfiguration"));
const strictPrefix_1 = __importDefault(require("./similarity/strictPrefix"));
const tokenwise_1 = __importDefault(require("./similarity/tokenwise"));
const NFKD_1 = __importDefault(require("./normalizers/NFKD"));
module.exports = {
examples: {
StrictAutoComplete: StrictAutoComplete_1.default,
FuzzyAutoComplete: FuzzyAutoComplete_1.default,
},
components: {
QueryAgent: QueryAgent_1.default,
QueryAggregator: QueryAggregator_1.default,
ResultEmitter: ResultEmitter_1.default,
ResultRanking: ResultRanking_1.default,
ResultStore: ResultStore_1.default,
ResultUniqueFilter: ResultUniqueFilter_1.default,
SimilarityConfiguration: SimilarityConfiguration_1.default,
NFKD: NFKD_1.default,
},
similarityFunctions: {
asymmetricDiceCoefficient: asymmetricDiceCoefficient_1.default,
commonPrefixSimilarity: commonPrefix_1.default,
fuzzyIndexSimilarity: fuzzyIndex_1.default,
fuzzyPrefixSimilarity: fuzzyPrefix_1.default,
strictPrefixSimilarity: strictPrefix_1.default,
tokenwiseCompare: tokenwise_1.default,
}
};
exports.tokenwiseCompare = exports.strictPrefixSimilarity = exports.fuzzyPrefixSimilarity = exports.fuzzyIndexSimilarity = exports.commonPrefixSimilarity = exports.asymmetricDiceCoefficient = exports.NFKD = exports.SimilarityConfiguration = exports.ResultUniqueFilter = exports.ResultStore = exports.ResultRanking = exports.ResultEmitter = exports.QueryAggregator = exports.QueryAgent = exports.StrictAutoComplete = exports.FuzzyAutoComplete = void 0;
var FuzzyAutoComplete_1 = require("./examples/FuzzyAutoComplete");
Object.defineProperty(exports, "FuzzyAutoComplete", { enumerable: true, get: function () { return __importDefault(FuzzyAutoComplete_1).default; } });
var StrictAutoComplete_1 = require("./examples/StrictAutoComplete");
Object.defineProperty(exports, "StrictAutoComplete", { enumerable: true, get: function () { return __importDefault(StrictAutoComplete_1).default; } });
var QueryAgent_1 = require("./QueryAgent");
Object.defineProperty(exports, "QueryAgent", { enumerable: true, get: function () { return __importDefault(QueryAgent_1).default; } });
var QueryAggregator_1 = require("./QueryAggregator");
Object.defineProperty(exports, "QueryAggregator", { enumerable: true, get: function () { return __importDefault(QueryAggregator_1).default; } });
var ResultEmitter_1 = require("./ResultEmitter");
Object.defineProperty(exports, "ResultEmitter", { enumerable: true, get: function () { return __importDefault(ResultEmitter_1).default; } });
var ResultRanking_1 = require("./ResultRanking");
Object.defineProperty(exports, "ResultRanking", { enumerable: true, get: function () { return __importDefault(ResultRanking_1).default; } });
var ResultStore_1 = require("./ResultStore");
Object.defineProperty(exports, "ResultStore", { enumerable: true, get: function () { return __importDefault(ResultStore_1).default; } });
var ResultUniqueFilter_1 = require("./ResultUniqueFilter");
Object.defineProperty(exports, "ResultUniqueFilter", { enumerable: true, get: function () { return __importDefault(ResultUniqueFilter_1).default; } });
var SimilarityConfiguration_1 = require("./similarity/SimilarityConfiguration");
Object.defineProperty(exports, "SimilarityConfiguration", { enumerable: true, get: function () { return __importDefault(SimilarityConfiguration_1).default; } });
var NFKD_1 = require("./normalizers/NFKD");
Object.defineProperty(exports, "NFKD", { enumerable: true, get: function () { return __importDefault(NFKD_1).default; } });
var asymmetricDiceCoefficient_1 = require("./similarity/asymmetricDiceCoefficient");
Object.defineProperty(exports, "asymmetricDiceCoefficient", { enumerable: true, get: function () { return __importDefault(asymmetricDiceCoefficient_1).default; } });
var commonPrefix_1 = require("./similarity/commonPrefix");
Object.defineProperty(exports, "commonPrefixSimilarity", { enumerable: true, get: function () { return __importDefault(commonPrefix_1).default; } });
var fuzzyIndex_1 = require("./similarity/fuzzyIndex");
Object.defineProperty(exports, "fuzzyIndexSimilarity", { enumerable: true, get: function () { return __importDefault(fuzzyIndex_1).default; } });
var fuzzyPrefix_1 = require("./similarity/fuzzyPrefix");
Object.defineProperty(exports, "fuzzyPrefixSimilarity", { enumerable: true, get: function () { return __importDefault(fuzzyPrefix_1).default; } });
var strictPrefix_1 = require("./similarity/strictPrefix");
Object.defineProperty(exports, "strictPrefixSimilarity", { enumerable: true, get: function () { return __importDefault(strictPrefix_1).default; } });
var tokenwise_1 = require("./similarity/tokenwise");
Object.defineProperty(exports, "tokenwiseCompare", { enumerable: true, get: function () { return __importDefault(tokenwise_1).default; } });
//# sourceMappingURL=index.js.map

@@ -12,4 +12,5 @@ "use strict";

normalize(input) {
input = input.trim(); // get rid of whitespace
input = input.toLowerCase();
input = input.normalize("NFKD");
input = input.normalize("NFKD"); // normalize diacritics
input = input.replace(this.regex, '');

@@ -16,0 +17,0 @@ return input;

@@ -26,3 +26,3 @@ "use strict";

for (const source of this.subEmitters) {
source.on("data", (q) => self.emit("data", q));
source.on("data", (q, meta) => self.emit("data", q, meta));
source.on("end", (q) => self.processEnd(q));

@@ -29,0 +29,0 @@ }

@@ -7,3 +7,3 @@ "use strict";

* ("reset"): the Emitter reset its internal state, and listeners might want to do the same
* ("data", quad): this quad was found
* ("data", quad, metadata): this quad was found, with an optional ResultMetadata object
* ("end", query): this query has terminated

@@ -10,0 +10,0 @@ */

@@ -8,3 +8,4 @@ declare const SortedArray: any;

protected subEmitter: ResultEmitter;
protected activeQuery: string;
protected normalizedQuery: string;
protected rawQuery: string;
protected size: number;

@@ -18,4 +19,5 @@ protected currentBest: typeof SortedArray;

resolveSubject(uri: string): Quad[];
protected findOverlap(expected: string, found: string): Array<[number, number]>;
protected emitUpdate(): void;
}
export {};

@@ -17,2 +17,3 @@ "use strict";

const ResultEmitter_1 = __importDefault(require("./ResultEmitter"));
const ResultMetadata_1 = __importDefault(require("./ResultMetadata"));
/*

@@ -29,7 +30,8 @@ * Emits only the most relevant results from the subEmitter

this.similarityConfigurations = similarityConfigurations;
this.activeQuery = "";
this.normalizedQuery = "";
this.rawQuery = "";
this.currentBest = new SortedArray();
const self = this;
this.subEmitter = subEmitter;
this.subEmitter.on("data", (q) => self.processQuad(q));
this.subEmitter.on("data", (q, _) => self.processQuad(q)); // ignore existing metadata, we add our own
this.subEmitter.on("end", (uri) => self.emit("end", uri));

@@ -40,7 +42,13 @@ this.subEmitter.on("reset", () => self.emit("reset"));

return __awaiter(this, void 0, void 0, function* () {
this.emit("reset");
this.currentBest = new SortedArray();
input = this.normalizer.normalize(input);
this.activeQuery = input;
this.subEmitter.query(input);
const normalizedInput = this.normalizer.normalize(input);
if (this.normalizedQuery === input) {
this.emitUpdate();
}
else {
this.emit("reset");
this.currentBest = new SortedArray();
this.rawQuery = input;
this.normalizedQuery = normalizedInput;
this.subEmitter.query(input);
}
});

@@ -54,3 +62,4 @@ }

}
let value = this.normalizer.normalize(quad.object.value);
let rawValue = quad.object.value;
let normalizedValue = this.normalizer.normalize(rawValue);
let better = false;

@@ -62,3 +71,3 @@ let eligible = true;

// flip sign, because we order increasingly
let similarity = -1 * configuration.evaluate(this.activeQuery, value);
let similarity = -1 * configuration.evaluate(this.normalizedQuery, normalizedValue, quad);
// it's not similar enough to include in the results

@@ -82,6 +91,7 @@ if (isNaN(similarity)) {

}
// if all configured metrics are as good as the threshold value
if (eligible && similarityVector.length === this.similarityConfigurations.length) {
// all configured metrics are as good as the threshold value
const overlapVector = this.findOverlap(this.rawQuery, rawValue);
// add the string value and quad object as tie breakers
const fullVector = [...similarityVector, value, quad];
const fullVector = [...similarityVector, normalizedValue, overlapVector, quad];
let better = this.currentBest.contentCompare(thresholdVector, fullVector) > 0;

@@ -97,2 +107,31 @@ if (better || this.currentBest.length < this.size) {

}
findOverlap(expected, found) {
const result = [];
const expectedTokens = expected.split(/\s/);
const foundTokens = found.split(/\s/);
for (const expectedToken of expectedTokens) {
const normalizedExpected = this.normalizer.normalize(expectedToken);
for (const foundToken of foundTokens) {
const normalizedFound = this.normalizer.normalize(foundToken);
if (normalizedFound.startsWith(normalizedExpected)) {
let beginIndex = found.indexOf(foundToken);
let currentToken = this.normalizer.normalize(found[beginIndex]);
while (currentToken && currentToken !== normalizedFound[0]) {
beginIndex += 1;
currentToken = this.normalizer.normalize(found[beginIndex]);
}
let endIndex = beginIndex + normalizedExpected.length - 1;
currentToken = this.normalizer.normalize(found[endIndex]);
while (currentToken && currentToken !== normalizedFound[normalizedExpected.length - 1]) {
endIndex += 1;
currentToken = this.normalizer.normalize(found[endIndex]);
}
if (beginIndex < found.length && beginIndex <= endIndex) {
result.push([beginIndex, endIndex]);
}
}
}
}
return result;
}
emitUpdate() {

@@ -102,3 +141,6 @@ this.emit("reset");

for (const vector of output) {
this.emit("data", vector[vector.length - 1]);
const quad = vector[vector.length - 1];
const overlapVector = vector[vector.length - 2];
const similarityVector = vector.slice(0, vector.length - 2);
this.emit("data", quad, new ResultMetadata_1.default(overlapVector, similarityVector));
}

@@ -105,0 +147,0 @@ }

import { Quad } from "rdf-js";
import ResultEmitter from "./ResultEmitter";
import N3 = require('n3');
import ResultMetadata from "./ResultMetadata";
export default class ResultStore extends ResultEmitter {

@@ -10,3 +11,3 @@ protected subEmitter: ResultEmitter;

resolveSubject(uri: string): Quad[];
protected processQuad(quad: Quad): void;
protected processQuad(quad: Quad, meta: ResultMetadata): void;
}

@@ -27,3 +27,3 @@ "use strict";

const self = this;
this.subEmitter.on("data", (q) => self.processQuad(q));
this.subEmitter.on("data", (q, meta) => self.processQuad(q, meta));
this.subEmitter.on("end", (uri) => self.emit("end", uri));

@@ -42,3 +42,3 @@ this.subEmitter.on("reset", () => self.emit("reset"));

}
processQuad(quad) {
processQuad(quad, meta) {
this.store.addQuad(quad);

@@ -48,3 +48,3 @@ if (quad.object.termType == "Literal" && quad.subject.termType === "NamedNode") {

if (dataType == "http://www.w3.org/2001/XMLSchema#string" || dataType == "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString") {
this.emit("data", quad);
this.emit("data", quad, meta);
}

@@ -51,0 +51,0 @@ }

import { Quad } from "rdf-js";
import ResultEmitter from "./ResultEmitter";
import ResultMetadata from "./ResultMetadata";
export default class ResultUniqueFilter extends ResultEmitter {

@@ -9,3 +10,3 @@ protected subEmitter: ResultEmitter;

resolveSubject(uri: string): Quad[];
protected processQuad(quad: Quad): void;
protected processQuad(quad: Quad, meta: ResultMetadata): void;
}

@@ -26,3 +26,3 @@ "use strict";

const self = this;
this.subEmitter.on("data", (q) => self.processQuad(q));
this.subEmitter.on("data", (q, meta) => self.processQuad(q, meta));
this.subEmitter.on("end", (uri) => self.emit("end", uri));

@@ -41,3 +41,3 @@ this.subEmitter.on("reset", () => self.emit("reset"));

}
processQuad(quad) {
processQuad(quad, meta) {
const uri = quad.subject.value;

@@ -47,3 +47,3 @@ const value = quad.object.value;

this.known.add(uri + value);
this.emit("data", quad);
this.emit("data", quad, meta);
}

@@ -50,0 +50,0 @@ }

@@ -0,1 +1,2 @@

import { Quad } from "rdf-js";
import { FilterFunction, SimilarityFunction } from "./SimilarityFunction";

@@ -6,3 +7,3 @@ export default class SimilarityConfiguration {

constructor(similarity: SimilarityFunction, filter?: FilterFunction);
evaluate(expected: string, found: string): number;
evaluate(expected: string, found: string, quad?: Quad): number;
}

@@ -8,6 +8,6 @@ "use strict";

}
evaluate(expected, found) {
evaluate(expected, found, quad) {
// higher is better
// NaN indicates an ineligible result
const similarity = this.similarityFunction(expected, found);
const similarity = this.similarityFunction(expected, found, quad);
if (this.filterFunction) {

@@ -14,0 +14,0 @@ if (!this.filterFunction(expected, found, similarity)) {

@@ -1,2 +0,3 @@

export declare type SimilarityFunction = (expected: string, found: string) => number;
import { Quad } from "rdf-js";
export declare type SimilarityFunction = (expected: string, found: string, quad?: Quad) => number;
export declare type FilterFunction = (expected: string, found: string, similarity: number) => boolean;
{
"name": "@hdelva/termennetwerk_client",
"version": "2.0.5",
"version": "3.0.0",
"description": "Proof of concept of client-side autocompletion",

@@ -5,0 +5,0 @@ "main": "lib/index.js",

@@ -1,2 +0,132 @@

# tree_complete
Autocomplete over TREEs
# Termennetwerk Autocompletion Client
Imagine a jQuery-style autocompletion widget without hardcoded options, which can scale to millions of values. This project contains a proof of concept of such a client, and is structured as a toolbox to build your own clients.
## Installation
```npm i @hdelva/termennetwerk_client```
## Require
```import AutoComplete from "@hdelva/termennetwerk_client";```
### Using one of the preconfigured clients
```javascript
import AutoComplete from "@hdelva/termennetwerk_client";
import * as RdfString from "rdf-string";
// creates a client that traverses 4 datasets for the 10 best results
const client = new AutoComplete.examples.StrictAutoComplete([
"https://termen.opoi.org/nta",
"https://termen.opoi.org/vtmk",
"https://termen.opoi.org/cht",
"https://termen.opoi.org/rkdartists"
], 10);
client.on("data", (quad) => {
console.log(quad.subject.value);
})
client.on("reset", () => {
console.clear();
})
client.on("end", () => {
//
})
```
## Build your own
```javascript
import { similarityFunctions, components } from "@hdelva/termennetwerk_client";
function relationSimilarity(expected, found) {
return similarityFunctions.tokenwiseCompare(
similarityFunctions.fuzzyIndexSimilarity,
expected,
found,
);
}
function relationFilter(_, __, similarity) {
return similarity > 0.9;
}
function resultSimilarity(expected, found) {
return similarityFunctions.tokenwiseCompare(
similarityFunctions.asymmetricDiceCoefficient,
expected,
found,
);
}
function lengthResult(_, found) {
return -1 * found.length;
}
const resultConfigurations = [
new components.SimilarityConfiguration(resultSimilarity),
new components.SimilarityConfiguration(lengthResult),
]
const relationConfigurations = [
new components.SimilarityConfiguration(relationSimilarity, relationFilter),
]
export default class FuzzyAutoComplete extends components.ResultEmitter {
constructor(sources, size) {
super();
const agents = [];
for (const source of sources) {
agents.push(new components.QueryAgent(source, relationConfigurations));
}
const aggregator = new components.QueryAggregator(agents);
const store = new components.ResultStore(aggregator);
const filter = new components.ResultUniqueFilter(store);
const sorted = new components.ResultRanking(
size,
filter,
new components.NFKD(),
resultConfigurations
);
this.subEmitter = sorted;
this.subEmitter.on("data", (data) => this.emit("data", data));
this.subEmitter.on("end", (data) => this.emit("end", data));
this.subEmitter.on("reset", () => this.emit("reset"));
}
query(input) {
this.subEmitter.query(input);
}
resolveSubject(uri) {
return this.subEmitter.resolveSubject(uri);
}
}
```
## Structure
An autocompletion client is a combination of multiple components which all implement the same interface. Currently, these components are implemented:
* `QueryAgent`: used to traverse a single data source, looking for the requested query string
* `QueryAggregator`: merges the results from several other components
* `ResultRanking`: creates a top-n view of all discovered results
* `ResultStore`: maintains an in-memory RDF graph to provide additional context for the results
* `ResultUniqueFilter`: filters out duplicate results
Each component exposes two methods:`query` and `resolveSubject`. The `query` method does not return anything, instead it starts a sequence of asynchronous calls that will emit `data` events each time a relevant `Quad` is found. Optionally, all known information about a certain subject can be requested (by the subject's URI), in which case the components should return all known quads related to this subject.
Two components can be configured with sorting functions: `QueryAgent` and `ResultRanking`. The former uses the functions to determine the node traversal order, the second one uses them to sort the results. Optionally, an additional filter function can be added to these sorting functions, which sets a minimum similarity score for a relation/result to be considered useful.
The following image illustrates how the components are used in the preconfigured client:
![query svg](./img/query.svg)
Not shown in the image above are the additional optional `resolveSubject` calls, which are passed through to, and handled by, the `ResultStore`.

@@ -31,4 +31,4 @@ import IQueryEmitter from "../ResultEmitter";

function lengthResult(expected: string, found: string): number {
return -1 * found.length;
function lengthResult(expected: string, found: string, quad?: Quad): number {
return quad ? -1 * quad.object.value.length : -1 * found.length;
}

@@ -69,3 +69,3 @@

this.subEmitter.on("data", (data) => this.emit("data", data));
this.subEmitter.on("data", (data, meta) => this.emit("data", data, meta));
this.subEmitter.on("end", (data) => this.emit("end", data));

@@ -72,0 +72,0 @@ this.subEmitter.on("reset", () => this.emit("reset"));

@@ -36,4 +36,4 @@ import IQueryEmitter from "../ResultEmitter";

function lengthResult(expected: string, found: string): number {
return -1 * found.length;
function lengthResult(expected: string, found: string, quad?: Quad): number {
return quad ? -1 * quad.object.value.length : -1 * found.length;
}

@@ -74,3 +74,3 @@

this.subEmitter.on("data", (data) => this.emit("data", data));
this.subEmitter.on("data", (data, meta) => this.emit("data", data, meta));
this.subEmitter.on("end", (data) => this.emit("end", data));

@@ -77,0 +77,0 @@ this.subEmitter.on("reset", () => this.emit("reset"));

@@ -1,41 +0,18 @@

import FuzzyAutoComplete from "./examples/FuzzyAutoComplete";
import StrictAutoComplete from "./examples/StrictAutoComplete";
import QueryAgent from "./QueryAgent";
import QueryAggregator from "./QueryAggregator";
import ResultEmitter from "./ResultEmitter";
import ResultRanking from "./ResultRanking";
import ResultStore from "./ResultStore";
import ResultUniqueFilter from "./ResultUniqueFilter";
import asymmetricDiceCoefficient from "./similarity/asymmetricDiceCoefficient";
import commonPrefixSimilarity from "./similarity/commonPrefix";
import fuzzyIndexSimilarity from "./similarity/fuzzyIndex";
import fuzzyPrefixSimilarity from "./similarity/fuzzyPrefix";
import SimilarityConfiguration from "./similarity/SimilarityConfiguration";
import strictPrefixSimilarity from "./similarity/strictPrefix";
import tokenwiseCompare from "./similarity/tokenwise";
import NFKD from "./normalizers/NFKD";
export { default as FuzzyAutoComplete } from "./examples/FuzzyAutoComplete";
export { default as StrictAutoComplete } from "./examples/StrictAutoComplete";
module.exports = {
examples: {
StrictAutoComplete,
FuzzyAutoComplete,
},
components: {
QueryAgent,
QueryAggregator,
ResultEmitter,
ResultRanking,
ResultStore,
ResultUniqueFilter,
SimilarityConfiguration,
NFKD,
},
similarityFunctions: {
asymmetricDiceCoefficient,
commonPrefixSimilarity,
fuzzyIndexSimilarity,
fuzzyPrefixSimilarity,
strictPrefixSimilarity,
tokenwiseCompare,
}
}
export { default as QueryAgent } from "./QueryAgent";
export { default as QueryAggregator } from "./QueryAggregator";
export { default as ResultEmitter } from "./ResultEmitter";
export { default as ResultRanking } from "./ResultRanking";
export { default as ResultStore } from "./ResultStore";
export { default as ResultUniqueFilter } from "./ResultUniqueFilter";
export { default as SimilarityConfiguration } from "./similarity/SimilarityConfiguration";
export { default as NFKD } from "./normalizers/NFKD";
export { default as asymmetricDiceCoefficient } from "./similarity/asymmetricDiceCoefficient";
export { default as commonPrefixSimilarity } from "./similarity/commonPrefix";
export { default as fuzzyIndexSimilarity } from "./similarity/fuzzyIndex";
export { default as fuzzyPrefixSimilarity } from "./similarity/fuzzyPrefix";
export { default as strictPrefixSimilarity } from "./similarity/strictPrefix";
export { default as tokenwiseCompare } from "./similarity/tokenwise";

@@ -15,4 +15,5 @@ import INormalizer from "./INormalizer";

public normalize(input: string): string {
input = input.trim(); // get rid of whitespace
input = input.toLowerCase();
input = input.normalize("NFKD")
input = input.normalize("NFKD"); // normalize diacritics
input = input.replace(this.regex,'')

@@ -19,0 +20,0 @@ return input;

@@ -18,3 +18,3 @@ import ResultEmitter from "./ResultEmitter";

for (const source of this.subEmitters) {
source.on("data", (q) => self.emit("data", q));
source.on("data", (q, meta) => self.emit("data", q, meta));
source.on("end", (q) => self.processEnd(q));

@@ -21,0 +21,0 @@ }

@@ -7,3 +7,3 @@ import { EventEmitter } from "events";

* ("reset"): the Emitter reset its internal state, and listeners might want to do the same
* ("data", quad): this quad was found
* ("data", quad, metadata): this quad was found, with an optional ResultMetadata object
* ("end", query): this query has terminated

@@ -10,0 +10,0 @@ */

@@ -8,2 +8,3 @@ const SortedArray = require("collections/sorted-array");

import SimilarityConfiguration from "./similarity/SimilarityConfiguration";
import ResultMetadata from "./ResultMetadata";

@@ -17,3 +18,4 @@ /*

protected subEmitter: ResultEmitter;
protected activeQuery: string;
protected normalizedQuery: string;
protected rawQuery: string;
protected size: number;

@@ -37,3 +39,4 @@ protected currentBest: typeof SortedArray;

this.activeQuery = "";
this.normalizedQuery = "";
this.rawQuery = "";
this.currentBest = new SortedArray();

@@ -43,3 +46,3 @@

this.subEmitter = subEmitter;
this.subEmitter.on("data", (q) => self.processQuad(q));
this.subEmitter.on("data", (q, _) => self.processQuad(q)); // ignore existing metadata, we add our own
this.subEmitter.on("end", (uri) => self.emit("end", uri));

@@ -50,8 +53,13 @@ this.subEmitter.on("reset", () => self.emit("reset"));

public async query(input: string) {
this.emit("reset");
this.currentBest = new SortedArray();
input = this.normalizer.normalize(input);
const normalizedInput = this.normalizer.normalize(input);
this.activeQuery = input;
this.subEmitter.query(input);
if (this.normalizedQuery === input) {
this.emitUpdate();
} else {
this.emit("reset");
this.currentBest = new SortedArray();
this.rawQuery = input;
this.normalizedQuery = normalizedInput;
this.subEmitter.query(input);
}
}

@@ -66,3 +74,4 @@

let value = this.normalizer.normalize(quad.object.value);
let rawValue = quad.object.value;
let normalizedValue = this.normalizer.normalize(rawValue);

@@ -76,3 +85,3 @@ let better = false;

// flip sign, because we order increasingly
let similarity = -1 * configuration.evaluate(this.activeQuery, value);
let similarity = -1 * configuration.evaluate(this.normalizedQuery, normalizedValue, quad);

@@ -101,6 +110,8 @@ // it's not similar enough to include in the results

// if all configured metrics are as good as the threshold value
if (eligible && similarityVector.length === this.similarityConfigurations.length) {
// all configured metrics are as good as the threshold value
const overlapVector = this.findOverlap(this.rawQuery, rawValue);
// add the string value and quad object as tie breakers
const fullVector = [...similarityVector, value, quad];
const fullVector = [...similarityVector, normalizedValue, overlapVector, quad];

@@ -120,2 +131,39 @@ let better = this.currentBest.contentCompare(thresholdVector, fullVector) > 0;

protected findOverlap(expected: string, found: string): Array<[number, number]> {
const result: Array<[number, number]> = [];
const expectedTokens = expected.split(/\s/);
const foundTokens = found.split(/\s/);
for (const expectedToken of expectedTokens) {
const normalizedExpected = this.normalizer.normalize(expectedToken);
for (const foundToken of foundTokens) {
const normalizedFound = this.normalizer.normalize(foundToken);
if (normalizedFound.startsWith(normalizedExpected)) {
let beginIndex = found.indexOf(foundToken)
let currentToken = this.normalizer.normalize(found[beginIndex]);
while (currentToken && currentToken !== normalizedFound[0]) {
beginIndex += 1;
currentToken = this.normalizer.normalize(found[beginIndex]);
}
let endIndex = beginIndex + normalizedExpected.length - 1;
currentToken = this.normalizer.normalize(found[endIndex]);
while (currentToken && currentToken !== normalizedFound[normalizedExpected.length - 1]) {
endIndex += 1;
currentToken = this.normalizer.normalize(found[endIndex]);
}
if (beginIndex < found.length && beginIndex <= endIndex) {
result.push([beginIndex, endIndex]);
}
}
}
}
return result;
}
protected emitUpdate() {

@@ -125,5 +173,8 @@ this.emit("reset");

for (const vector of output) {
this.emit("data", vector[vector.length - 1]);
const quad = vector[vector.length - 1];
const overlapVector = vector[vector.length - 2];
const similarityVector = vector.slice(0, vector.length - 2);
this.emit("data", quad, new ResultMetadata(overlapVector, similarityVector));
}
}
}

@@ -5,2 +5,3 @@ import { Quad } from "rdf-js";

import N3 = require('n3');
import ResultMetadata from "./ResultMetadata";

@@ -21,3 +22,3 @@ /*

const self = this;
this.subEmitter.on("data", (q) => self.processQuad(q));
this.subEmitter.on("data", (q, meta) => self.processQuad(q, meta));
this.subEmitter.on("end", (uri) => self.emit("end", uri));

@@ -37,3 +38,3 @@ this.subEmitter.on("reset", () => self.emit("reset"));

protected processQuad(quad: Quad) {
protected processQuad(quad: Quad, meta: ResultMetadata) {
this.store.addQuad(quad);

@@ -44,3 +45,3 @@ if (quad.object.termType == "Literal" && quad.subject.termType === "NamedNode") {

if (dataType == "http://www.w3.org/2001/XMLSchema#string" || dataType == "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString") {
this.emit("data", quad);
this.emit("data", quad, meta);
}

@@ -47,0 +48,0 @@ }

import { Quad } from "rdf-js";
import ResultEmitter from "./ResultEmitter";
import ResultMetadata from "./ResultMetadata";

@@ -19,3 +20,3 @@ /*

const self = this;
this.subEmitter.on("data", (q) => self.processQuad(q));
this.subEmitter.on("data", (q, meta) => self.processQuad(q, meta));
this.subEmitter.on("end", (uri) => self.emit("end", uri));

@@ -35,3 +36,3 @@ this.subEmitter.on("reset", () => self.emit("reset"));

protected processQuad(quad: Quad) {
protected processQuad(quad: Quad, meta: ResultMetadata) {
const uri = quad.subject.value;

@@ -42,5 +43,5 @@ const value = quad.object.value;

this.known.add(uri + value);
this.emit("data", quad);
this.emit("data", quad, meta);
}
}
}

@@ -0,1 +1,2 @@

import { Quad } from "rdf-js";
import { FilterFunction, SimilarityFunction } from "./SimilarityFunction";

@@ -12,6 +13,6 @@

public evaluate(expected: string, found: string): number {
public evaluate(expected: string, found: string, quad?: Quad): number {
// higher is better
// NaN indicates an ineligible result
const similarity = this.similarityFunction(expected, found);
const similarity = this.similarityFunction(expected, found, quad);

@@ -18,0 +19,0 @@ if (this.filterFunction) {

@@ -0,5 +1,7 @@

import { Quad } from "rdf-js";
// bigger is better, NaN is ineligible
export type SimilarityFunction = (expected: string, found: string) => number;
export type SimilarityFunction = (expected: string, found: string, quad?: Quad) => number;
// return true if this similarity is good enough, false if not
export type FilterFunction = (expected: string, found: string, similarity: number) => boolean;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc