From fee67327201aa3b94755057421f6a327ca96dede Mon Sep 17 00:00:00 2001 From: Jesse Wright Date: Fri, 10 Jun 2022 11:27:57 +1000 Subject: [PATCH 1/8] chore: convert files to ts --- .gitignore | 1 + package-lock.json | 13 +-- package.json | 1 + ...athResolver.js => AbstractPathResolver.ts} | 0 ...atorHandler.js => AsyncIteratorHandler.ts} | 0 ...ctionsHandler.js => CollectionsHandler.ts} | 0 ...PathResolver.js => ComplexPathResolver.ts} | 0 ...{ContextProvider.js => ContextProvider.ts} | 0 src/{DataHandler.js => DataHandler.ts} | 0 ...ionHandler.js => DeleteFunctionHandler.ts} | 0 ...QueryHandler.js => ExecuteQueryHandler.ts} | 0 ...nctionHandler.js => GetFunctionHandler.ts} | 0 ...ionHandler.js => InsertFunctionHandler.ts} | 0 src/{JSONLDResolver.js => JSONLDResolver.ts} | 0 ...ndler.js => MutationExpressionsHandler.ts} | 0 ...nHandler.js => MutationFunctionHandler.ts} | 0 ...ionHandler.js => PathExpressionHandler.ts} | 0 src/{PathFactory.js => PathFactory.ts} | 0 src/{PathProxy.js => PathProxy.ts} | 0 ...redicateHandler.js => PredicateHandler.ts} | 0 ...dicatesHandler.js => PredicatesHandler.ts} | 0 src/{PreloadHandler.js => PreloadHandler.ts} | 0 ...pertiesHandler.js => PropertiesHandler.ts} | 0 ...onHandler.js => ReplaceFunctionHandler.ts} | 0 ...nctionHandler.js => SetFunctionHandler.ts} | 0 src/{SortHandler.js => SortHandler.ts} | 0 src/{SparqlHandler.js => SparqlHandler.ts} | 0 ...lexHandler.js => StringToLDflexHandler.ts} | 0 src/{SubjectHandler.js => SubjectHandler.ts} | 0 ...{SubjectsHandler.js => SubjectsHandler.ts} | 0 src/{ThenHandler.js => ThenHandler.ts} | 0 src/{ToArrayHandler.js => ToArrayHandler.ts} | 0 src/{URIHandler.js => URIHandler.ts} | 0 ...{defaultHandlers.js => defaultHandlers.ts} | 2 +- src/{handlerUtil.js => handlerUtil.ts} | 0 src/{index.js => index.ts} | 2 +- src/{iterableUtils.js => iterableUtils.ts} | 0 src/{promiseUtils.js => promiseUtils.ts} | 0 src/{valueUtils.js => valueUtils.ts} | 0 tsconfig.json | 101 ++++++++++++++++++ 40 files changed, 112 insertions(+), 8 deletions(-) rename src/{AbstractPathResolver.js => AbstractPathResolver.ts} (100%) rename src/{AsyncIteratorHandler.js => AsyncIteratorHandler.ts} (100%) rename src/{CollectionsHandler.js => CollectionsHandler.ts} (100%) rename src/{ComplexPathResolver.js => ComplexPathResolver.ts} (100%) rename src/{ContextProvider.js => ContextProvider.ts} (100%) rename src/{DataHandler.js => DataHandler.ts} (100%) rename src/{DeleteFunctionHandler.js => DeleteFunctionHandler.ts} (100%) rename src/{ExecuteQueryHandler.js => ExecuteQueryHandler.ts} (100%) rename src/{GetFunctionHandler.js => GetFunctionHandler.ts} (100%) rename src/{InsertFunctionHandler.js => InsertFunctionHandler.ts} (100%) rename src/{JSONLDResolver.js => JSONLDResolver.ts} (100%) rename src/{MutationExpressionsHandler.js => MutationExpressionsHandler.ts} (100%) rename src/{MutationFunctionHandler.js => MutationFunctionHandler.ts} (100%) rename src/{PathExpressionHandler.js => PathExpressionHandler.ts} (100%) rename src/{PathFactory.js => PathFactory.ts} (100%) rename src/{PathProxy.js => PathProxy.ts} (100%) rename src/{PredicateHandler.js => PredicateHandler.ts} (100%) rename src/{PredicatesHandler.js => PredicatesHandler.ts} (100%) rename src/{PreloadHandler.js => PreloadHandler.ts} (100%) rename src/{PropertiesHandler.js => PropertiesHandler.ts} (100%) rename src/{ReplaceFunctionHandler.js => ReplaceFunctionHandler.ts} (100%) rename src/{SetFunctionHandler.js => SetFunctionHandler.ts} (100%) rename src/{SortHandler.js => SortHandler.ts} (100%) rename src/{SparqlHandler.js => SparqlHandler.ts} (100%) rename src/{StringToLDflexHandler.js => StringToLDflexHandler.ts} (100%) rename src/{SubjectHandler.js => SubjectHandler.ts} (100%) rename src/{SubjectsHandler.js => SubjectsHandler.ts} (100%) rename src/{ThenHandler.js => ThenHandler.ts} (100%) rename src/{ToArrayHandler.js => ToArrayHandler.ts} (100%) rename src/{URIHandler.js => URIHandler.ts} (100%) rename src/{defaultHandlers.js => defaultHandlers.ts} (98%) rename src/{handlerUtil.js => handlerUtil.ts} (100%) rename src/{index.js => index.ts} (97%) rename src/{iterableUtils.js => iterableUtils.ts} (100%) rename src/{promiseUtils.js => promiseUtils.ts} (100%) rename src/{valueUtils.js => valueUtils.ts} (100%) create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 6735e4eb..80805a1e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ coverage lib module node_modules +src/**.js diff --git a/package-lock.json b/package-lock.json index 7f5c0fd7..9f485c2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@rdfjs/data-model": "^1.3.4", + "@rdfjs/types": "^1.1.0", "jsonld-context-parser": "^2.1.5", "sparqlalgebrajs": "^4.0.1" }, @@ -7755,9 +7756,9 @@ } }, "node_modules/@rdfjs/types": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.0.1.tgz", - "integrity": "sha512-YxVkH0XrCNG3MWeZxfg596GFe+oorTVusmNxRP6ZHTsGczZ8AGvG3UchRNkg3Fy4MyysI7vBAA5YZbESL+VmHQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.1.0.tgz", + "integrity": "sha512-5zm8bN2/CC634dTcn/0AhTRLaQRjXDZs3QfcAsQKNturHT7XVWcKy/8p3P5gXl+YkZTAmy7T5M/LyiT/jbkENw==", "dependencies": { "@types/node": "*" } @@ -27589,9 +27590,9 @@ } }, "@rdfjs/types": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.0.1.tgz", - "integrity": "sha512-YxVkH0XrCNG3MWeZxfg596GFe+oorTVusmNxRP6ZHTsGczZ8AGvG3UchRNkg3Fy4MyysI7vBAA5YZbESL+VmHQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.1.0.tgz", + "integrity": "sha512-5zm8bN2/CC634dTcn/0AhTRLaQRjXDZs3QfcAsQKNturHT7XVWcKy/8p3P5gXl+YkZTAmy7T5M/LyiT/jbkENw==", "requires": { "@types/node": "*" } diff --git a/package.json b/package.json index 02fc3f18..8a8b7037 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ ], "dependencies": { "@rdfjs/data-model": "^1.3.4", + "@rdfjs/types": "^1.1.0", "jsonld-context-parser": "^2.1.5", "sparqlalgebrajs": "^4.0.1" }, diff --git a/src/AbstractPathResolver.js b/src/AbstractPathResolver.ts similarity index 100% rename from src/AbstractPathResolver.js rename to src/AbstractPathResolver.ts diff --git a/src/AsyncIteratorHandler.js b/src/AsyncIteratorHandler.ts similarity index 100% rename from src/AsyncIteratorHandler.js rename to src/AsyncIteratorHandler.ts diff --git a/src/CollectionsHandler.js b/src/CollectionsHandler.ts similarity index 100% rename from src/CollectionsHandler.js rename to src/CollectionsHandler.ts diff --git a/src/ComplexPathResolver.js b/src/ComplexPathResolver.ts similarity index 100% rename from src/ComplexPathResolver.js rename to src/ComplexPathResolver.ts diff --git a/src/ContextProvider.js b/src/ContextProvider.ts similarity index 100% rename from src/ContextProvider.js rename to src/ContextProvider.ts diff --git a/src/DataHandler.js b/src/DataHandler.ts similarity index 100% rename from src/DataHandler.js rename to src/DataHandler.ts diff --git a/src/DeleteFunctionHandler.js b/src/DeleteFunctionHandler.ts similarity index 100% rename from src/DeleteFunctionHandler.js rename to src/DeleteFunctionHandler.ts diff --git a/src/ExecuteQueryHandler.js b/src/ExecuteQueryHandler.ts similarity index 100% rename from src/ExecuteQueryHandler.js rename to src/ExecuteQueryHandler.ts diff --git a/src/GetFunctionHandler.js b/src/GetFunctionHandler.ts similarity index 100% rename from src/GetFunctionHandler.js rename to src/GetFunctionHandler.ts diff --git a/src/InsertFunctionHandler.js b/src/InsertFunctionHandler.ts similarity index 100% rename from src/InsertFunctionHandler.js rename to src/InsertFunctionHandler.ts diff --git a/src/JSONLDResolver.js b/src/JSONLDResolver.ts similarity index 100% rename from src/JSONLDResolver.js rename to src/JSONLDResolver.ts diff --git a/src/MutationExpressionsHandler.js b/src/MutationExpressionsHandler.ts similarity index 100% rename from src/MutationExpressionsHandler.js rename to src/MutationExpressionsHandler.ts diff --git a/src/MutationFunctionHandler.js b/src/MutationFunctionHandler.ts similarity index 100% rename from src/MutationFunctionHandler.js rename to src/MutationFunctionHandler.ts diff --git a/src/PathExpressionHandler.js b/src/PathExpressionHandler.ts similarity index 100% rename from src/PathExpressionHandler.js rename to src/PathExpressionHandler.ts diff --git a/src/PathFactory.js b/src/PathFactory.ts similarity index 100% rename from src/PathFactory.js rename to src/PathFactory.ts diff --git a/src/PathProxy.js b/src/PathProxy.ts similarity index 100% rename from src/PathProxy.js rename to src/PathProxy.ts diff --git a/src/PredicateHandler.js b/src/PredicateHandler.ts similarity index 100% rename from src/PredicateHandler.js rename to src/PredicateHandler.ts diff --git a/src/PredicatesHandler.js b/src/PredicatesHandler.ts similarity index 100% rename from src/PredicatesHandler.js rename to src/PredicatesHandler.ts diff --git a/src/PreloadHandler.js b/src/PreloadHandler.ts similarity index 100% rename from src/PreloadHandler.js rename to src/PreloadHandler.ts diff --git a/src/PropertiesHandler.js b/src/PropertiesHandler.ts similarity index 100% rename from src/PropertiesHandler.js rename to src/PropertiesHandler.ts diff --git a/src/ReplaceFunctionHandler.js b/src/ReplaceFunctionHandler.ts similarity index 100% rename from src/ReplaceFunctionHandler.js rename to src/ReplaceFunctionHandler.ts diff --git a/src/SetFunctionHandler.js b/src/SetFunctionHandler.ts similarity index 100% rename from src/SetFunctionHandler.js rename to src/SetFunctionHandler.ts diff --git a/src/SortHandler.js b/src/SortHandler.ts similarity index 100% rename from src/SortHandler.js rename to src/SortHandler.ts diff --git a/src/SparqlHandler.js b/src/SparqlHandler.ts similarity index 100% rename from src/SparqlHandler.js rename to src/SparqlHandler.ts diff --git a/src/StringToLDflexHandler.js b/src/StringToLDflexHandler.ts similarity index 100% rename from src/StringToLDflexHandler.js rename to src/StringToLDflexHandler.ts diff --git a/src/SubjectHandler.js b/src/SubjectHandler.ts similarity index 100% rename from src/SubjectHandler.js rename to src/SubjectHandler.ts diff --git a/src/SubjectsHandler.js b/src/SubjectsHandler.ts similarity index 100% rename from src/SubjectsHandler.js rename to src/SubjectsHandler.ts diff --git a/src/ThenHandler.js b/src/ThenHandler.ts similarity index 100% rename from src/ThenHandler.js rename to src/ThenHandler.ts diff --git a/src/ToArrayHandler.js b/src/ToArrayHandler.ts similarity index 100% rename from src/ToArrayHandler.js rename to src/ToArrayHandler.ts diff --git a/src/URIHandler.js b/src/URIHandler.ts similarity index 100% rename from src/URIHandler.js rename to src/URIHandler.ts diff --git a/src/defaultHandlers.js b/src/defaultHandlers.ts similarity index 98% rename from src/defaultHandlers.js rename to src/defaultHandlers.ts index 47d98e24..5f0eb360 100644 --- a/src/defaultHandlers.js +++ b/src/defaultHandlers.ts @@ -12,7 +12,7 @@ import PredicatesHandler from './PredicatesHandler'; import PreloadHandler from './PreloadHandler'; import PropertiesHandler from './PropertiesHandler'; import ReplaceFunctionHandler from './ReplaceFunctionHandler'; -import SetFunctionHandler from './SetFunctionHandler'; +import SetFunctionHandler from './SetFunctionHandler.ts'; import SortHandler from './SortHandler'; import SparqlHandler from './SparqlHandler'; import StringToLDflexHandler from './StringToLDflexHandler'; diff --git a/src/handlerUtil.js b/src/handlerUtil.ts similarity index 100% rename from src/handlerUtil.js rename to src/handlerUtil.ts diff --git a/src/index.js b/src/index.ts similarity index 97% rename from src/index.js rename to src/index.ts index c85abf03..e10e9fc4 100644 --- a/src/index.js +++ b/src/index.ts @@ -14,7 +14,7 @@ import PredicatesHandler from './PredicatesHandler'; import PreloadHandler from './PreloadHandler'; import PropertiesHandler from './PropertiesHandler'; import ReplaceFunctionHandler from './ReplaceFunctionHandler'; -import SetFunctionHandler from './SetFunctionHandler'; +import SetFunctionHandler from './SetFunctionHandler.ts'; import SortHandler from './SortHandler'; import SparqlHandler from './SparqlHandler'; import StringToLDflexHandler from './StringToLDflexHandler'; diff --git a/src/iterableUtils.js b/src/iterableUtils.ts similarity index 100% rename from src/iterableUtils.js rename to src/iterableUtils.ts diff --git a/src/promiseUtils.js b/src/promiseUtils.ts similarity index 100% rename from src/promiseUtils.js rename to src/promiseUtils.ts diff --git a/src/valueUtils.js b/src/valueUtils.ts similarity index 100% rename from src/valueUtils.js rename to src/valueUtils.ts diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..56794816 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,101 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} From d9d8bdaa2a12313702915fc9d5e516e4a72f9bd9 Mon Sep 17 00:00:00 2001 From: Jesse Wright Date: Fri, 10 Jun 2022 15:49:27 +1000 Subject: [PATCH 2/8] WIP --- package-lock.json | 19 ++++---- package.json | 3 +- src/AbstractPathResolver.ts | 14 +++--- src/AsyncIteratorHandler.ts | 3 +- src/ComplexPathResolver.ts | 9 ++-- src/ContextProvider.ts | 13 +++--- src/ExecuteQueryHandler.ts | 32 +++++++------ src/GetFunctionHandler.ts | 3 +- src/MutationExpressionsHandler.ts | 4 +- src/MutationFunctionHandler.ts | 16 ++++--- src/PathFactory.ts | 1 + src/PathProxy.ts | 6 ++- src/ToArrayHandler.ts | 3 +- src/URIHandler.ts | 3 +- src/defaultHandlers.ts | 2 +- src/index.ts | 2 +- src/iterableUtils.ts | 63 ++++++++++++++++++------- src/promiseUtils.ts | 2 +- src/types.ts | 76 +++++++++++++++++++++++++++++++ 19 files changed, 198 insertions(+), 76 deletions(-) create mode 100644 src/types.ts diff --git a/package-lock.json b/package-lock.json index 9f485c2c..4d1655e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,8 @@ "husky": "^8.0.1", "jest": "^28.0.3", "n3": "^1.12.2", - "semantic-release": "^19.0.2" + "semantic-release": "^19.0.2", + "typescript": "^4.7.3" } }, "node_modules/@ampproject/remapping": { @@ -21086,11 +21087,10 @@ } }, "node_modules/typescript": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", + "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -37769,11 +37769,10 @@ } }, "typescript": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", - "dev": true, - "peer": true + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", + "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", + "dev": true }, "uglify-js": { "version": "3.14.5", diff --git a/package.json b/package.json index 8a8b7037..3daafcef 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "husky": "^8.0.1", "jest": "^28.0.3", "n3": "^1.12.2", - "semantic-release": "^19.0.2" + "semantic-release": "^19.0.2", + "typescript": "^4.7.3" }, "scripts": { "build": "npm run build:lib && npm run build:module", diff --git a/src/AbstractPathResolver.ts b/src/AbstractPathResolver.ts index 31b52149..bfec5ca0 100644 --- a/src/AbstractPathResolver.ts +++ b/src/AbstractPathResolver.ts @@ -1,6 +1,8 @@ import ContextProvider from './ContextProvider'; import { lazyThenable } from './promiseUtils'; import { valueToTerm } from './valueUtils'; +import type { PathData } from './types' +import { JsonLdContext } from 'jsonld-context-parser' /** * Resolves property names of a path @@ -14,7 +16,7 @@ export default class AbstractPathResolver { return this._contextProvider._context; } - async extendContext(...contexts) { + async extendContext(...contexts: JsonLdContext[]) { await this._contextProvider.extendContext(...contexts); } @@ -22,7 +24,7 @@ export default class AbstractPathResolver { * Creates a new resolver for the given context(s). * @param arg Either a context provider *or* a context */ - constructor(arg, ...contexts) { + constructor(arg: JsonLdContext | ContextProvider, ...contexts: JsonLdContext[]) { if (arg instanceof ContextProvider) { this._contextProvider = arg; this.extendContext(...contexts); @@ -35,7 +37,7 @@ export default class AbstractPathResolver { /** * The JSON-LD resolver supports all string properties. */ - supports(property) { + supports(property: any): boolean { return typeof property === 'string'; } @@ -45,7 +47,7 @@ export default class AbstractPathResolver { * * Example usage: person.friends.firstName */ - resolve(property, pathData) { + resolve(property: string, pathData: PathData) { const predicate = lazyThenable(() => this.expandProperty(property)); const reverse = lazyThenable(() => this._context.then(({ contextRaw }) => contextRaw[property] && contextRaw[property]['@reverse'])); @@ -60,7 +62,7 @@ export default class AbstractPathResolver { * * Example usage: person.friends.location(place).firstName */ - apply(args, pathData, path) { + apply(args, pathData: PathData, path) { if (args.length === 0) { const { property } = pathData; throw new Error(`Specify at least one term when calling .${property}() on a path`); @@ -85,7 +87,7 @@ export default class AbstractPathResolver { return propertyCache && lazyThenable(async () => { // Preloading does not work with reversed predicates propertyCache = !(await reverse) && await propertyCache; - return propertyCache && propertyCache[(await predicate).value]; + return propertyCache?.[(await predicate).value]; }); } } diff --git a/src/AsyncIteratorHandler.ts b/src/AsyncIteratorHandler.ts index a246d811..e19d3631 100644 --- a/src/AsyncIteratorHandler.ts +++ b/src/AsyncIteratorHandler.ts @@ -1,4 +1,5 @@ import { iteratorFor } from './iterableUtils'; +import type { PathData } from './types' /** * AsyncIterator handler that yields either the subject or all results. @@ -9,7 +10,7 @@ import { iteratorFor } from './iterableUtils'; * - (optional) results on the path proxy */ export default class AsyncIteratorHandler { - handle({ subject }, pathProxy) { + handle({ subject }: PathData, pathProxy) { // Return a one-item iterator of the subject if present; // otherwise, return all results of this path return subject ? diff --git a/src/ComplexPathResolver.ts b/src/ComplexPathResolver.ts index 3ee9cd73..be4223b7 100644 --- a/src/ComplexPathResolver.ts +++ b/src/ComplexPathResolver.ts @@ -1,11 +1,12 @@ -import { translate, toSparql } from 'sparqlalgebrajs'; +import { translate, toSparql, Algebra, Factory } from 'sparqlalgebrajs'; import AbstractPathResolver from './AbstractPathResolver'; import { namedNode } from '@rdfjs/data-model'; +const factory = new Factory(); /** * Writes SPARQL algebra a complex SPARQL path */ -function writePathAlgebra(algebra) { +function writePathAlgebra(algebra: Algebra.Join | Algebra.Bgp | Algebra.Operation): string { if (algebra.type === 'join') return algebra.input.map(x => writePathAlgebra(x)).join('/'); // The algebra library turns sequential path expressions like @@ -27,7 +28,7 @@ function writePathAlgebra(algebra) { if (algebra.type === 'path') { // Note - this could be made cleaner if sparqlalgebrajs exported // the translatePathComponent function - let query = toSparql({ type: 'project', input: algebra }); + let query = toSparql(factory.createProject(algebra, [])); query = query.replace(/^SELECT WHERE \{ \?[0-9a-z]+ \(|\) \?[0-9a-z]+\. \}$/ig, ''); return query; } @@ -48,7 +49,7 @@ export default class ComplexPathResolver extends AbstractPathResolver { * 4. /((^[(<])|([)>]$))/ * Tests for '(', '<', at the start of a string and ')', '>' at the end of a string */ - supports(property) { + supports(property: any): boolean { return super.supports(property) && (/((^|[/|])[\^])|(([a-z:>)])[*+?])|([)>*+?]|[a-z]*[:][a-z]*)[|/]([<(^]|[a-z]*[:][a-z]*)|(((^[(<])|([)>]$)))/i) .test(property); diff --git a/src/ContextProvider.ts b/src/ContextProvider.ts index 5424d51a..f1365ce8 100644 --- a/src/ContextProvider.ts +++ b/src/ContextProvider.ts @@ -1,23 +1,24 @@ -import { ContextParser } from 'jsonld-context-parser'; +import { ContextParser, JsonLdContext, JsonLdContextNormalized } from 'jsonld-context-parser'; /** * Used to share context between multiple resolvers */ export default class ContextProvider { - _context = Promise.resolve({}); + private _context: Promise = Promise.resolve(); /** * Creates a new resolver for the given context(s). */ - constructor(...contexts) { + constructor(...contexts: JsonLdContext[]) { this.extendContext(...contexts); } /** * Extends the current context with the given context(s). */ - async extendContext(...contexts) { - await (this._context = this._context.then(({ contextRaw }) => - new ContextParser().parse([contextRaw, ...contexts]))); + async extendContext(...contexts: JsonLdContext[]) { + this._context = this._context.then(context => + new ContextParser().parse(context ? [context.getContextRaw(), ...contexts] : [...contexts]) + ) } } diff --git a/src/ExecuteQueryHandler.ts b/src/ExecuteQueryHandler.ts index c949b647..1bb4c311 100644 --- a/src/ExecuteQueryHandler.ts +++ b/src/ExecuteQueryHandler.ts @@ -1,3 +1,7 @@ +import { Bindings, Term } from '@rdfjs/types'; +import { streamToAsyncIterable } from './iterableUtils'; +import { PathData } from './types'; + /** * Executes the query represented by a path. * @@ -7,7 +11,7 @@ * - (optional) a resultsCache property on the path data */ export default class ExecuteQueryHandler { - async *handle(pathData, path) { + async *handle(pathData: PathData, path) { // Try to retrieve the result from cache const resultsCache = await pathData.resultsCache; if (resultsCache) { @@ -28,20 +32,18 @@ export default class ExecuteQueryHandler { return; // Extract the term from every query result - for await (const bindings of queryEngine.execute(query)) - yield this.extractTerm(bindings, pathData); - } + const resultsStream = streamToAsyncIterable(await queryEngine.queryBindings(query)); + + for await (const bindings of resultsStream) + yield pathData.extendPath({ subject: getSingleTerm(bindings) }, null); } + +} - /** - * Extracts the first term from a query result binding as a new path. - */ - extractTerm(binding, pathData) { - // Extract the first term from the binding map - if (binding.size !== 1) - throw new Error('Only single-variable queries are supported'); - const subject = binding.values().next().value; +function getSingleTerm(binding: Bindings): Term { + // Extract the first term from the binding map + if (binding.size === 1) + for (const subject of binding.values()) + return subject; - // Each result is a new path that starts from the term as subject - return pathData.extendPath({ subject }, null); - } + throw new Error('Only single-variable queries are supported'); } diff --git a/src/GetFunctionHandler.ts b/src/GetFunctionHandler.ts index aa772f1a..45bfe50e 100644 --- a/src/GetFunctionHandler.ts +++ b/src/GetFunctionHandler.ts @@ -1,5 +1,6 @@ import { isPlainObject, isAsyncIterable } from './valueUtils'; import { iterableToArray } from './iterableUtils'; +import { PathData } from './types'; /** * Returns a function that requests the values of multiple properties. @@ -13,7 +14,7 @@ import { iterableToArray } from './iterableUtils'; * Combinations of the above are possible by passing them in arrays. */ export default class GetFunctionHandler { - handle(pathData, path) { + handle(pathData: PathData, path) { return (...args) => this.readProperties(path, args.length === 1 ? args[0] : args, true); } diff --git a/src/MutationExpressionsHandler.ts b/src/MutationExpressionsHandler.ts index 3bfbbae2..32f7837e 100644 --- a/src/MutationExpressionsHandler.ts +++ b/src/MutationExpressionsHandler.ts @@ -1,3 +1,5 @@ +import { PathData } from "./types"; + /** * Traverses a path to collect mutationExpressions into an expression. * This is needed because mutations can be chained. @@ -6,7 +8,7 @@ * - a mutationExpressions property on the path proxy */ export default class MutationExpressionsHandler { - async handle(pathData) { + async handle(pathData: PathData) { const mutationExpressions = []; // Add all mutationExpressions to the path diff --git a/src/MutationFunctionHandler.ts b/src/MutationFunctionHandler.ts index 4472159c..6dcd1921 100644 --- a/src/MutationFunctionHandler.ts +++ b/src/MutationFunctionHandler.ts @@ -4,6 +4,7 @@ import { ensureArray, joinArrays, valueToTerm, hasPlainObjectArgs, isAsyncIterable, } from './valueUtils'; +import { PathData } from './types'; /** * Returns a function that, when called with arguments, @@ -21,16 +22,17 @@ import { * - a pathExpression property on the path proxy and all non-raw arguments. */ export default class MutationFunctionHandler { - constructor(mutationType, allowZeroArgs) { - this._mutationType = mutationType; - this._allowZeroArgs = allowZeroArgs; + constructor( + private mutationType?: 'INSERT' | 'DELETE', + private allowZeroArgs?: boolean + ) { } // Creates a function that performs a mutation - handle(pathData, path) { + handle(pathData: PathData, path) { return (...args) => { // Check if the given arguments are valid - if (!this._allowZeroArgs && !args.length) + if (!this.allowZeroArgs && !args.length) throw new Error('Mutation cannot be invoked without arguments'); // Create a lazy Promise to the mutation expressions @@ -75,14 +77,14 @@ export default class MutationFunctionHandler { // Create a mutation, unless no objects are affected (`null` means all) return objects !== null && objects.length === 0 ? {} : { - mutationType: this._mutationType, + mutationType: this.mutationType, conditions: conditions.slice(0, -1), predicateObjects: [{ predicate, reverse, objects }], }; } // Extracts individual objects from a set of values passed to a mutation function - async extractObjects(pathData, path, values) { + async extractObjects(pathData: PathData, path, values) { // If no specific values are specified, match all (represented by `null`) if (values.length === 0) return null; diff --git a/src/PathFactory.ts b/src/PathFactory.ts index ffdc67e8..1a169fdb 100644 --- a/src/PathFactory.ts +++ b/src/PathFactory.ts @@ -4,6 +4,7 @@ import ComplexPathResolver from './ComplexPathResolver'; import defaultHandlers from './defaultHandlers'; import { ContextParser } from 'jsonld-context-parser'; import ContextProvider from './ContextProvider'; +import { } from './types'; /** * A PathFactory creates paths with default settings. diff --git a/src/PathProxy.ts b/src/PathProxy.ts index 6cf8dd2b..7f1224ca 100644 --- a/src/PathProxy.ts +++ b/src/PathProxy.ts @@ -1,3 +1,5 @@ +import { PathData } from "./types"; + const EMPTY = Object.create(null); /** @@ -30,7 +32,7 @@ export default class PathProxy { /** * Creates a path Proxy with the given settings and internal data fields. */ - createPath(settings = {}, data) { + createPath(settings = {}, data): PathData { // The settings parameter is optional if (data === undefined) [data, settings] = [settings, {}]; @@ -60,7 +62,7 @@ export default class PathProxy { /** * Handles access to a property */ - get(pathData, property) { + get(pathData: PathData, property: string) { // Handlers provide functionality for a specific property, // so check if we find a handler first const handler = this._handlers[property]; diff --git a/src/ToArrayHandler.ts b/src/ToArrayHandler.ts index 9ed03c77..90066104 100644 --- a/src/ToArrayHandler.ts +++ b/src/ToArrayHandler.ts @@ -1,3 +1,4 @@ +import { PathData } from './types'; import { isAsyncIterable } from './valueUtils'; /** @@ -7,7 +8,7 @@ import { isAsyncIterable } from './valueUtils'; * - (optional) an iterable path */ export default class ToArrayHandler { - handle(pathData, path) { + handle(pathData: PathData, path) { return async map => { const items = []; if (isAsyncIterable(path)) { diff --git a/src/URIHandler.ts b/src/URIHandler.ts index 7a6943a2..4009864a 100644 --- a/src/URIHandler.ts +++ b/src/URIHandler.ts @@ -1,10 +1,11 @@ import { handler } from './handlerUtil'; +import { Term } from '@rdfjs/types' /** * Finds the index at which the break between the namespace and the * occurs - then execute a callback with this index as the second arg */ -function breakIndex(term, cb) { +function breakIndex(term: Term | undefined, cb: (str: string, i: number) => string): string | undefined { if (term?.termType !== 'NamedNode') return undefined; // Find the index of the last '#' or '/' if no '#' exists diff --git a/src/defaultHandlers.ts b/src/defaultHandlers.ts index 5f0eb360..47d98e24 100644 --- a/src/defaultHandlers.ts +++ b/src/defaultHandlers.ts @@ -12,7 +12,7 @@ import PredicatesHandler from './PredicatesHandler'; import PreloadHandler from './PreloadHandler'; import PropertiesHandler from './PropertiesHandler'; import ReplaceFunctionHandler from './ReplaceFunctionHandler'; -import SetFunctionHandler from './SetFunctionHandler.ts'; +import SetFunctionHandler from './SetFunctionHandler'; import SortHandler from './SortHandler'; import SparqlHandler from './SparqlHandler'; import StringToLDflexHandler from './StringToLDflexHandler'; diff --git a/src/index.ts b/src/index.ts index e10e9fc4..c85abf03 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,7 @@ import PredicatesHandler from './PredicatesHandler'; import PreloadHandler from './PreloadHandler'; import PropertiesHandler from './PropertiesHandler'; import ReplaceFunctionHandler from './ReplaceFunctionHandler'; -import SetFunctionHandler from './SetFunctionHandler.ts'; +import SetFunctionHandler from './SetFunctionHandler'; import SortHandler from './SortHandler'; import SparqlHandler from './SparqlHandler'; import StringToLDflexHandler from './StringToLDflexHandler'; diff --git a/src/iterableUtils.ts b/src/iterableUtils.ts index 6812f713..99cc191e 100644 --- a/src/iterableUtils.ts +++ b/src/iterableUtils.ts @@ -1,10 +1,10 @@ -const done = {}; +import { ResultStream } from '@rdfjs/types'; /** * Returns the elements of the iterable as an array */ -export async function iterableToArray(iterable) { - const items = []; +export async function iterableToArray(iterable: AsyncIterable): Promise { + const items: T[] = []; for await (const item of iterable) items.push(item); return items; @@ -13,23 +13,52 @@ export async function iterableToArray(iterable) { /** * Gets the first element of the iterable. */ -export function getFirstItem(iterable) { - const iterator = iterable[Symbol.asyncIterator](); - return iterator.next().then(item => item.value); +export async function getFirstItem(iterable: AsyncIterable): Promise { + for await (const item of iterable) + return item; + throw new Error('Expected iterable to contain at least one element'); } /** * Creates an async iterator with the item as only element. */ -export function iteratorFor(item) { - return { - async next() { - if (item !== done) { - const value = await item; - item = done; - return { value }; - } - return { done: true }; - }, - }; +export async function *iteratorFor(item: T): AsyncIterator { + return item +} + +/** + * Transforms the readable into an asynchronously iterable object + * From: https://github.com/LDflex/LDflex-Comunica/blob/cf3b74013fda96063b5edbffd14fa7214ad119a0/src/ComunicaEngine.ts#L161 + */ +export async function *streamToAsyncIterable(readable: ResultStream): AsyncIterableIterator { + let item: T | null; + let error: Error | undefined; + let done = false; + let cb = () => {}; + + function settlePromise() { + cb(); + } + + function finish(error?: Error) { + done = true; + error = error; + } + + readable.on('readable', settlePromise); + readable.on('error', finish); + readable.on('end', finish); + + while (!done) { + while ((item = readable.read()) !== null) + yield item; + await new Promise(res => { cb = res }); + } + + readable.removeListener('readable', settlePromise); + readable.removeListener('error', finish); + readable.removeListener('end', finish); + + if (error) + throw error; } diff --git a/src/promiseUtils.ts b/src/promiseUtils.ts index 3fb6ee48..52f19933 100644 --- a/src/promiseUtils.ts +++ b/src/promiseUtils.ts @@ -45,7 +45,7 @@ export function toIterablePromise(iterable) { * Returns a memoized version of the iterable * that can be iterated over as many times as needed. */ -export function memoizeIterable(iterable) { +export function memoizeIterable(iterable) { const cache = []; let iterator = iterable[Symbol.asyncIterator](); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..11943a2f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,76 @@ +import type { Bindings, Term, StringSparqlQueryable, BindingsResultSupport } from '@rdfjs/types'; +import { JsonLdContext, JsonLdContextNormalized } from "jsonld-context-parser"; + +export type MaybePromise = T | Promise + +export interface Settings { + context: JsonLdContext, + handlers?: LDflexProxyHandlers, + parsedContext?: MaybePromise, + resolvers?: Resolver[], + queryEngine: StringSparqlQueryable; +} + +export interface PathData { + settings: Settings; + resultsCache: MaybePromise>; + extendPath(pathData: PathData, path?: Data): Data; +} + + +// export interface Data { +// property: string | undefined; +// resultsCache?: Data; // TODO: Check - is this optional, +// results: Data; // TODO CHECK +// parent?: null, +// subject?: RDF.Quad_Subject, +// sparql?: string, +// predicate?: RDF.Quad_Predicate, +// proxy?: LDflexProxyHandlers, // TODO: Check +// settings: LDflexSettings, +// // TODO: Check below definition +// extendPath(pathData: Data, path?: Data): Data, +// [Symbol.asyncIterator]?: AsyncIterableIterator, // TODO: CHECK - is this optional +// //[x: string]: any // TODO FIX - make better for path property access +// finalClause?(v: string): [string, string, string] // Generates the final clause for a sparql query +// [x: string]: any; // TODO make this stricter? +// } + + + +export interface Resolver { + supports(...args: any[]): boolean; + resolve(property: string, pathData: PathData): PathData; +} + + +// export type MaybePromise = Promise | T +// export type MaybeFunction = T | (() => T) +// export type MaybeArray = T | T[] +// export type WithOptional = Omit & Partial>; + +// export type LDflexHandleFunction = (pathData: any, path: any) => any; + +// export interface LDflexHandler { +// handle : LDflexHandleFunction +// } + +// export type LDflexProxyHandlers = { +// readonly [x: string]: LDflexHandler; +// readonly [Symbol.asyncIterator]: AsyncIterableIterator; +// } | { +// readonly __esModule: () => undefined; +// }; + + +// export enum fltr { +// regex = 'regex', +// le = '<', +// ge = '>', +// leq = '<=', +// geq = '>=', +// exists = 'exists', +// notExists = 'notexists', +// eq = '=', +// neq = '!=' +// } From 4884074dd094be7cebed68813926d2e7c956e5c2 Mon Sep 17 00:00:00 2001 From: Jesse Wright Date: Sun, 12 Jun 2022 11:06:39 +1000 Subject: [PATCH 3/8] WIP --- src/AbstractPathResolver.ts | 9 ++++----- src/CollectionsHandler.ts | 2 +- src/ComplexPathResolver.ts | 21 +++++++++++---------- src/ContextProvider.ts | 8 +++----- src/JSONLDResolver.ts | 2 +- src/PathFactory.ts | 18 +++++++++++------- src/PathProxy.ts | 3 +++ src/SortHandler.ts | 8 ++++---- src/SparqlHandler.ts | 3 ++- src/defaultHandlers.ts | 14 +++++++++----- src/handlerUtil.ts | 4 +++- src/types.ts | 27 +++++++++++++++++---------- src/valueUtils.ts | 17 ++++++++++------- 13 files changed, 79 insertions(+), 57 deletions(-) diff --git a/src/AbstractPathResolver.ts b/src/AbstractPathResolver.ts index bfec5ca0..2eecfd64 100644 --- a/src/AbstractPathResolver.ts +++ b/src/AbstractPathResolver.ts @@ -10,14 +10,14 @@ import { JsonLdContext } from 'jsonld-context-parser' * @abstract */ export default class AbstractPathResolver { - _contextProvider = new ContextProvider(); + private _contextProvider = new ContextProvider(); get _context() { return this._contextProvider._context; } async extendContext(...contexts: JsonLdContext[]) { - await this._contextProvider.extendContext(...contexts); + return this._contextProvider.extendContext(...contexts); } /** @@ -49,8 +49,7 @@ export default class AbstractPathResolver { */ resolve(property: string, pathData: PathData) { const predicate = lazyThenable(() => this.expandProperty(property)); - const reverse = lazyThenable(() => this._context.then(({ contextRaw }) => - contextRaw[property] && contextRaw[property]['@reverse'])); + const reverse = lazyThenable(() => this._context.then(({ contextRaw }) => contextRaw[property]?.['@reverse'])); const resultsCache = this.getResultsCache(pathData, predicate, reverse); const newData = { property, predicate, resultsCache, reverse, apply: this.apply }; return pathData.extendPath(newData); @@ -72,7 +71,7 @@ export default class AbstractPathResolver { return path; } - async expandProperty(property) { + async expandProperty(property: string) { // JavaScript requires keys containing colons to be quoted, // so prefixed names would need to written as path['foaf:knows']. // We thus allow writing path.foaf_knows or path.foaf$knows instead. diff --git a/src/CollectionsHandler.ts b/src/CollectionsHandler.ts index 9eb42103..38663c2f 100644 --- a/src/CollectionsHandler.ts +++ b/src/CollectionsHandler.ts @@ -21,7 +21,7 @@ export function listHandler() { * @param {Boolean} set Emits set if True, array otherwise * @returns An handler that returns an RDF collection as an array or set */ -export function containerHandler(set) { +export function containerHandler(set?: boolean) { return handler((_, path) => async () => { let container = []; let elem; diff --git a/src/ComplexPathResolver.ts b/src/ComplexPathResolver.ts index be4223b7..496f554f 100644 --- a/src/ComplexPathResolver.ts +++ b/src/ComplexPathResolver.ts @@ -1,17 +1,18 @@ import { translate, toSparql, Algebra, Factory } from 'sparqlalgebrajs'; import AbstractPathResolver from './AbstractPathResolver'; import { namedNode } from '@rdfjs/data-model'; +import { IJsonLdContextNormalizedRaw } from 'jsonld-context-parser'; const factory = new Factory(); /** * Writes SPARQL algebra a complex SPARQL path */ function writePathAlgebra(algebra: Algebra.Join | Algebra.Bgp | Algebra.Operation): string { - if (algebra.type === 'join') + if (algebra.type === Algebra.types.JOIN) return algebra.input.map(x => writePathAlgebra(x)).join('/'); // The algebra library turns sequential path expressions like // foaf:friend/foaf:givenName into a bgp token rather than a path token - if (algebra.type === 'bgp' && + if (algebra.type === Algebra.types.BGP && algebra.patterns.every(quad => quad.predicate.termType === 'NamedNode') && algebra.patterns.length >= 0) { let lastObject = 's'; @@ -25,7 +26,7 @@ function writePathAlgebra(algebra: Algebra.Join | Algebra.Bgp | Algebra.Operatio return predicate; }).join('/'); } - if (algebra.type === 'path') { + if (algebra.type === Algebra.types.PATH) { // Note - this could be made cleaner if sparqlalgebrajs exported // the translatePathComponent function let query = toSparql(factory.createProject(algebra, [])); @@ -58,13 +59,13 @@ export default class ComplexPathResolver extends AbstractPathResolver { /** * Takes string and resolves it to a predicate or SPARQL path */ - async lookupProperty(property) { + async lookupProperty(property: string) { // Expand the property to a full IRI - const context = await this._context; - const prefixes = {}; - for (const key in context.contextRaw) { - if (typeof context.contextRaw[key] === 'string') - prefixes[key] = context.contextRaw[key]; + const context = (await this._context).getContextRaw(); + const prefixes: Record = {}; + for (const key in context) { + if (typeof context[key] === 'string') + prefixes[key] = context[key]; } // Wrap inside try/catch as 'translate' throws error on invalid paths let algebra; @@ -77,7 +78,7 @@ export default class ComplexPathResolver extends AbstractPathResolver { throw new Error(`The Complex Path Resolver cannot expand the '${property}' path`); } - if (algebra.input.type === 'bgp' && + if (algebra.input.type === Algebra.types.BGP && algebra.input.patterns.length === 1 && algebra.input.patterns[0].predicate.termType === 'NamedNode' && // Test to make sure the path is not an inverse path diff --git a/src/ContextProvider.ts b/src/ContextProvider.ts index f1365ce8..fe287a1e 100644 --- a/src/ContextProvider.ts +++ b/src/ContextProvider.ts @@ -4,21 +4,19 @@ import { ContextParser, JsonLdContext, JsonLdContextNormalized } from 'jsonld-co * Used to share context between multiple resolvers */ export default class ContextProvider { - private _context: Promise = Promise.resolve(); + _context: Promise; /** * Creates a new resolver for the given context(s). */ constructor(...contexts: JsonLdContext[]) { - this.extendContext(...contexts); + this._context = new ContextParser().parse([...contexts]); } /** * Extends the current context with the given context(s). */ async extendContext(...contexts: JsonLdContext[]) { - this._context = this._context.then(context => - new ContextParser().parse(context ? [context.getContextRaw(), ...contexts] : [...contexts]) - ) + this._context = this._context.then(context => new ContextParser().parse([context.getContextRaw(), ...contexts])) } } diff --git a/src/JSONLDResolver.ts b/src/JSONLDResolver.ts index 31ec17b2..a1110093 100644 --- a/src/JSONLDResolver.ts +++ b/src/JSONLDResolver.ts @@ -10,7 +10,7 @@ export default class JSONLDResolver extends AbstractPathResolver { /** * Expands a JSON property key into a full IRI. */ - async lookupProperty(property) { + async lookupProperty(property: string) { const context = await this._context; const expandedProperty = context.expandTerm(property, true); if (!ContextUtil.isValidIri(expandedProperty)) diff --git a/src/PathFactory.ts b/src/PathFactory.ts index 1a169fdb..d2aaa221 100644 --- a/src/PathFactory.ts +++ b/src/PathFactory.ts @@ -4,13 +4,18 @@ import ComplexPathResolver from './ComplexPathResolver'; import defaultHandlers from './defaultHandlers'; import { ContextParser } from 'jsonld-context-parser'; import ContextProvider from './ContextProvider'; -import { } from './types'; +import { handler } from './handlerUtil' +import { Handler, HandlerFunction, Settings } from './types'; /** * A PathFactory creates paths with default settings. */ export default class PathFactory { - constructor(settings, data) { + private _jsonldResolver?: JSONLDResolver; + private _settings: Settings; + public static defaultHandlers = defaultHandlers; + + constructor(settings: Partial, data) { // Store settings and data this._settings = settings = { ...settings }; this._data = data = { ...data }; @@ -28,8 +33,8 @@ export default class PathFactory { const contextProvider = new ContextProvider(settings.context); resolvers.push(new ComplexPathResolver(contextProvider)); resolvers.push(this._jsonldResolver = new JSONLDResolver(contextProvider)); - settings.parsedContext = new ContextParser().parse(settings.context) - .then(({ contextRaw }) => contextRaw); + // TODO: See why this isn't dealy with by the context provider + settings.parsedContext = new ContextParser().parse(settings.context).then(context => context.getContextRaw()); } else { settings.context = settings.parsedContext = {}; @@ -70,13 +75,12 @@ export default class PathFactory { return this._pathProxy.createPath({ ...this._settings, ...settings }, _data); } } -PathFactory.defaultHandlers = defaultHandlers; /** * Converts a handler function into a handler object. */ -export function toHandler(handle) { - return typeof handle.handle === 'function' ? handle : { handle }; +export function toHandler(handle: Handler | HandlerFunction): Handler { + return typeof handle.handle === 'function' ? handle : handler(handle); } /** diff --git a/src/PathProxy.ts b/src/PathProxy.ts index 7f1224ca..b3213504 100644 --- a/src/PathProxy.ts +++ b/src/PathProxy.ts @@ -24,6 +24,9 @@ const EMPTY = Object.create(null); * - extendPath, a method to create a child path with this path as parent */ export default class PathProxy { + private _handlers; + private _resolvers; + constructor({ handlers = EMPTY, resolvers = [] } = {}) { this._handlers = handlers; this._resolvers = resolvers; diff --git a/src/SortHandler.ts b/src/SortHandler.ts index 2da8314e..e522d55e 100644 --- a/src/SortHandler.ts +++ b/src/SortHandler.ts @@ -1,3 +1,5 @@ +import { Handler } from "./types"; + /** * Returns a function that creates a new path with the same values, * but sorted on the given property. @@ -7,10 +9,8 @@ * - a predicate on the path proxy * - a sort function on the path proxy (for multi-property sorting) */ -export default class SortHandler { - constructor(order = 'ASC') { - this.order = order; - } +export default class SortHandler implements Handler { + constructor(private order: 'ASC' | 'DESC' = 'ASC') {} handle(pathData, pathProxy) { return (...properties) => { diff --git a/src/SparqlHandler.ts b/src/SparqlHandler.ts index 01f215d6..6b8ccb9e 100644 --- a/src/SparqlHandler.ts +++ b/src/SparqlHandler.ts @@ -1,4 +1,5 @@ import { namedNode } from '@rdfjs/data-model'; +import * as RDF from '@rdfjs/types'; const NEEDS_ESCAPE = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/, ESCAPE_ALL = /["\\\t\n\r\b\f\u0000-\u0019]|[\ud800-\udbff][\udc00-\udfff]/g, @@ -209,7 +210,7 @@ function escapeCharacter(character) { // Skolemizes the given term if it is a blank node let skolemId = 0; -function skolemize(term) { +function skolemize(term: RDF.Term) { if (term.termType !== 'BlankNode') return term; if (!term.skolemized) diff --git a/src/defaultHandlers.ts b/src/defaultHandlers.ts index 47d98e24..5e67e9ea 100644 --- a/src/defaultHandlers.ts +++ b/src/defaultHandlers.ts @@ -23,6 +23,7 @@ import ToArrayHandler from './ToArrayHandler'; import { termToPrimitive } from './valueUtils'; import { handler } from './handlerUtil'; import { prefixHandler, namespaceHandler, fragmentHandler } from './URIHandler'; +import { Handler } from './types'; /** * A map with default property handlers. @@ -90,17 +91,20 @@ export default { }; // Creates a handler for the given RDF/JS Term property -function termPropertyHandler(property) { +function termPropertyHandler(property: string): Handler { // If a resolved subject is present, // behave as an RDF/JS term and synchronously expose the property; // otherwise, return a promise to the property value - return handler(({ subject }, path) => - subject && (property in subject) ? subject[property] : - path.then && path.then(term => term?.[property])); + return handler(({ subject }, path) => subject?.[property] ?? path.then?.(term => term?.[property])) + + // TODO: Delete this + // return handler(({ subject }, path) => + // subject && (property in subject) ? subject[property] : + // path.then && path.then(term => term?.[property])); } // Creates a handler that converts the subject into a primitive -function subjectToPrimitiveHandler() { +function subjectToPrimitiveHandler(): Handler { return handler(({ subject }) => () => typeof subject?.termType !== 'string' ? undefined : termToPrimitive(subject)); diff --git a/src/handlerUtil.ts b/src/handlerUtil.ts index a0cbb33e..90a76a71 100644 --- a/src/handlerUtil.ts +++ b/src/handlerUtil.ts @@ -1,4 +1,6 @@ +import { Handler, HandlerFunction } from "./types"; + // Creates a handler from the given function -export function handler(handle) { +export function handler(handle: T): Handler { return { handle }; } diff --git a/src/types.ts b/src/types.ts index 11943a2f..c35bc764 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,20 +1,32 @@ import type { Bindings, Term, StringSparqlQueryable, BindingsResultSupport } from '@rdfjs/types'; -import { JsonLdContext, JsonLdContextNormalized } from "jsonld-context-parser"; +import { JsonLdContext, IJsonLdContextNormalizedRaw } from "jsonld-context-parser"; export type MaybePromise = T | Promise +export type HandlerFunction = (pathData: PathData, path: any) => any; +export type Handler = { handle: T }; +export type Handlers = Record; + +export interface Resolver { + supports(...args: any[]): boolean; + resolve(property: string, pathData: PathData): PathData; +} + +export type Resolvers = Resolver[]; + export interface Settings { context: JsonLdContext, - handlers?: LDflexProxyHandlers, - parsedContext?: MaybePromise, + handlers?: Handlers, + parsedContext?: MaybePromise, resolvers?: Resolver[], queryEngine: StringSparqlQueryable; } export interface PathData { settings: Settings; - resultsCache: MaybePromise>; - extendPath(pathData: PathData, path?: Data): Data; + resultsCache?: MaybePromise>; + extendPath: HandlerFunction; // TODO: Check this + // extendPath(pathData: PathData, path?: Data): Data; } @@ -38,11 +50,6 @@ export interface PathData { -export interface Resolver { - supports(...args: any[]): boolean; - resolve(property: string, pathData: PathData): PathData; -} - // export type MaybePromise = Promise | T // export type MaybeFunction = T | (() => T) diff --git a/src/valueUtils.ts b/src/valueUtils.ts index ac08fbb6..76d2464a 100644 --- a/src/valueUtils.ts +++ b/src/valueUtils.ts @@ -1,4 +1,5 @@ import { namedNode, literal } from '@rdfjs/data-model'; +import * as RDF from '@rdfjs/types'; const xsd = 'http://www.w3.org/2001/XMLSchema#'; @@ -65,12 +66,12 @@ export function ensureArray(value) { } // Joins the arrays into a single array -export function joinArrays(arrays) { - return [].concat(...arrays); +export function joinArrays(arrays: T[][]): T[] { + return ([] as T[]).concat(...arrays); } // Ensures the value is an RDF/JS term -export function valueToTerm(value) { +export function valueToTerm(value: string | boolean | number | Date | RDF.Term): RDF.Term { switch (typeof value) { // strings case 'string': @@ -95,12 +96,12 @@ export function valueToTerm(value) { // other objects default: if (value) { - // RDF/JS Term - if (typeof value.termType === 'string') - return value; // Date if (value instanceof Date) return literal(value.toISOString(), xsdDateTimeTerm); + // RDF/JS Term + if (typeof value.termType === 'string') + return value; } } @@ -109,7 +110,9 @@ export function valueToTerm(value) { } // Converts the term into a primitive value -export function termToPrimitive(term) { +export function termToPrimitive(term: RDF.Literal): string | boolean | number | Date; +export function termToPrimitive(term: Exclude): string; +export function termToPrimitive(term: RDF.Term): string | boolean | number | Date { const { termType, value } = term; // Some literals convert into specific primitive values From 78e6d5814dd0d49c0ceb39a95a1547b741feed54 Mon Sep 17 00:00:00 2001 From: Jesse Wright Date: Sun, 12 Jun 2022 17:41:18 +1000 Subject: [PATCH 4/8] WIP --- notes.md | 3 +++ src/AbstractPathResolver.ts | 25 ++++++++++++++++--------- src/AsyncIteratorHandler.ts | 4 ++-- src/ComplexPathResolver.ts | 5 +++-- src/ContextProvider.ts | 20 ++++++++++++++++---- src/DataHandler.ts | 4 +++- src/DeleteFunctionHandler.ts | 3 ++- src/ExecuteQueryHandler.ts | 4 ++-- src/GetFunctionHandler.ts | 4 ++-- src/InsertFunctionHandler.ts | 3 ++- src/JSONLDResolver.ts | 8 ++++---- src/MutationExpressionsHandler.ts | 4 ++-- src/MutationFunctionHandler.ts | 4 ++-- src/PathExpressionHandler.ts | 4 +++- src/PathProxy.ts | 6 +++--- src/PredicateHandler.ts | 10 +++++++++- src/PredicatesHandler.ts | 6 ++++-- src/PreloadHandler.ts | 4 +++- src/PropertiesHandler.ts | 4 +++- src/ReplaceFunctionHandler.ts | 10 ++++++++-- src/SetFunctionHandler.ts | 3 ++- src/SparqlHandler.ts | 11 ++++++----- src/StringToLDflexHandler.ts | 4 +++- src/SubjectHandler.ts | 6 ++++-- src/SubjectsHandler.ts | 4 +++- src/ThenHandler.ts | 3 ++- src/ToArrayHandler.ts | 4 ++-- src/valueUtils.ts | 2 +- 28 files changed, 115 insertions(+), 57 deletions(-) create mode 100644 notes.md diff --git a/notes.md b/notes.md new file mode 100644 index 00000000..2f551720 --- /dev/null +++ b/notes.md @@ -0,0 +1,3 @@ +1. Should be able to have supports / requires on each handler to indicate, type-wise, when other handlers are required +2. Should work in terms of SPARQLALGEBRAJS (or something similar) +3. extendPath take, as a parameter, a function that mutates the algebra. diff --git a/src/AbstractPathResolver.ts b/src/AbstractPathResolver.ts index 2eecfd64..96a76e8b 100644 --- a/src/AbstractPathResolver.ts +++ b/src/AbstractPathResolver.ts @@ -1,22 +1,27 @@ import ContextProvider from './ContextProvider'; import { lazyThenable } from './promiseUtils'; import { valueToTerm } from './valueUtils'; -import type { PathData } from './types' -import { JsonLdContext } from 'jsonld-context-parser' +import type { MaybePromise, PathData, Resolver } from './types' +import { IExpandOptions, JsonLdContext } from 'jsonld-context-parser' +import * as RDF from '@rdfjs/types'; /** * Resolves property names of a path * to their corresponding IRIs through a JSON-LD context. * @abstract */ -export default class AbstractPathResolver { +export default abstract class AbstractPathResolver implements Resolver { private _contextProvider = new ContextProvider(); - get _context() { - return this._contextProvider._context; + expandTerm(term: string, expandVocab?: boolean, options?: IExpandOptions) { + return this._contextProvider.expandTerm(term, expandVocab, options); } - async extendContext(...contexts: JsonLdContext[]) { + getContextRaw() { + return this._contextProvider.getContextRaw(); + } + + extendContext(...contexts: JsonLdContext[]) { return this._contextProvider.extendContext(...contexts); } @@ -49,7 +54,7 @@ export default class AbstractPathResolver { */ resolve(property: string, pathData: PathData) { const predicate = lazyThenable(() => this.expandProperty(property)); - const reverse = lazyThenable(() => this._context.then(({ contextRaw }) => contextRaw[property]?.['@reverse'])); + const reverse = lazyThenable(() => this.getContextRaw().then(context => context[property]?.['@reverse'])) const resultsCache = this.getResultsCache(pathData, predicate, reverse); const newData = { property, predicate, resultsCache, reverse, apply: this.apply }; return pathData.extendPath(newData); @@ -71,7 +76,9 @@ export default class AbstractPathResolver { return path; } - async expandProperty(property: string) { + abstract lookupProperty(property: string): Promise; + + expandProperty(property: string): Promise { // JavaScript requires keys containing colons to be quoted, // so prefixed names would need to written as path['foaf:knows']. // We thus allow writing path.foaf_knows or path.foaf$knows instead. @@ -81,7 +88,7 @@ export default class AbstractPathResolver { /** * Gets the results cache for the given predicate. */ - getResultsCache(pathData, predicate, reverse) { + getResultsCache(pathData, predicate, reverse: MaybePromise) { let { propertyCache } = pathData; return propertyCache && lazyThenable(async () => { // Preloading does not work with reversed predicates diff --git a/src/AsyncIteratorHandler.ts b/src/AsyncIteratorHandler.ts index e19d3631..1c39656e 100644 --- a/src/AsyncIteratorHandler.ts +++ b/src/AsyncIteratorHandler.ts @@ -1,5 +1,5 @@ import { iteratorFor } from './iterableUtils'; -import type { PathData } from './types' +import type { Handler, PathData } from './types' /** * AsyncIterator handler that yields either the subject or all results. @@ -9,7 +9,7 @@ import type { PathData } from './types' * - (optional) a subject on the path proxy * - (optional) results on the path proxy */ -export default class AsyncIteratorHandler { +export default class AsyncIteratorHandler implements Handler { handle({ subject }: PathData, pathProxy) { // Return a one-item iterator of the subject if present; // otherwise, return all results of this path diff --git a/src/ComplexPathResolver.ts b/src/ComplexPathResolver.ts index 496f554f..a1b70419 100644 --- a/src/ComplexPathResolver.ts +++ b/src/ComplexPathResolver.ts @@ -2,6 +2,7 @@ import { translate, toSparql, Algebra, Factory } from 'sparqlalgebrajs'; import AbstractPathResolver from './AbstractPathResolver'; import { namedNode } from '@rdfjs/data-model'; import { IJsonLdContextNormalizedRaw } from 'jsonld-context-parser'; +import { Resolver } from './types'; const factory = new Factory(); /** @@ -36,7 +37,7 @@ function writePathAlgebra(algebra: Algebra.Join | Algebra.Bgp | Algebra.Operatio throw new Error(`Unhandled algebra ${algebra.type}`); } -export default class ComplexPathResolver extends AbstractPathResolver { +export default class ComplexPathResolver extends AbstractPathResolver implements Resolver { /** * Supports all strings that contain path modifiers. The regular * expression is testing for 4 main properties: @@ -61,7 +62,7 @@ export default class ComplexPathResolver extends AbstractPathResolver { */ async lookupProperty(property: string) { // Expand the property to a full IRI - const context = (await this._context).getContextRaw(); + const context = await this.getContextRaw(); const prefixes: Record = {}; for (const key in context) { if (typeof context[key] === 'string') diff --git a/src/ContextProvider.ts b/src/ContextProvider.ts index fe287a1e..7db9cd66 100644 --- a/src/ContextProvider.ts +++ b/src/ContextProvider.ts @@ -1,22 +1,34 @@ -import { ContextParser, JsonLdContext, JsonLdContextNormalized } from 'jsonld-context-parser'; +import { ContextParser, IExpandOptions, IJsonLdContextNormalizedRaw, JsonLdContext, JsonLdContextNormalized } from 'jsonld-context-parser'; /** * Used to share context between multiple resolvers */ export default class ContextProvider { - _context: Promise; + private _context: Promise; + private parser = new ContextParser(); /** * Creates a new resolver for the given context(s). */ constructor(...contexts: JsonLdContext[]) { - this._context = new ContextParser().parse([...contexts]); + this._context = this.parser.parse([...contexts]); } /** * Extends the current context with the given context(s). */ async extendContext(...contexts: JsonLdContext[]) { - this._context = this._context.then(context => new ContextParser().parse([context.getContextRaw(), ...contexts])) + this._context = this._context.then(context => this.parser.parse([context.getContextRaw(), ...contexts])) + } + + /** + * @return The raw inner context + */ + async getContextRaw(): Promise { + return (await this._context).getContextRaw(); + } + + async expandTerm(term: string, expandVocab?: boolean, options?: IExpandOptions) { + return (await this._context).expandTerm(term, expandVocab, options) } } diff --git a/src/DataHandler.ts b/src/DataHandler.ts index 8f3b043b..159f2135 100644 --- a/src/DataHandler.ts +++ b/src/DataHandler.ts @@ -1,3 +1,5 @@ +import { Handler } from "./types"; + /** * Resolves to the given item in the path data. * For example, new DataHandler({}, 'foo', 'bar') @@ -6,7 +8,7 @@ * Resolution can optionally be async, * and/or be behind a function call. */ -export default class DataHandler { +export default class DataHandler implements Handler { constructor(options, ...dataProperties) { this._isAsync = options.async; this._isFunction = options.function; diff --git a/src/DeleteFunctionHandler.ts b/src/DeleteFunctionHandler.ts index af7f7581..15f934b4 100644 --- a/src/DeleteFunctionHandler.ts +++ b/src/DeleteFunctionHandler.ts @@ -1,9 +1,10 @@ import MutationFunctionHandler from './MutationFunctionHandler'; +import { Handler } from './types'; /** * A MutationFunctionHandler for deletions. */ -export default class DeleteFunctionHandler extends MutationFunctionHandler { +export default class DeleteFunctionHandler extends MutationFunctionHandler implements Handler { constructor() { super('DELETE', true); } diff --git a/src/ExecuteQueryHandler.ts b/src/ExecuteQueryHandler.ts index 1bb4c311..96b6e121 100644 --- a/src/ExecuteQueryHandler.ts +++ b/src/ExecuteQueryHandler.ts @@ -1,6 +1,6 @@ import { Bindings, Term } from '@rdfjs/types'; import { streamToAsyncIterable } from './iterableUtils'; -import { PathData } from './types'; +import { Handler, PathData } from './types'; /** * Executes the query represented by a path. @@ -10,7 +10,7 @@ import { PathData } from './types'; * - a sparql property on the path proxy * - (optional) a resultsCache property on the path data */ -export default class ExecuteQueryHandler { +export default class ExecuteQueryHandler implements Handler { async *handle(pathData: PathData, path) { // Try to retrieve the result from cache const resultsCache = await pathData.resultsCache; diff --git a/src/GetFunctionHandler.ts b/src/GetFunctionHandler.ts index 45bfe50e..68c64691 100644 --- a/src/GetFunctionHandler.ts +++ b/src/GetFunctionHandler.ts @@ -1,6 +1,6 @@ import { isPlainObject, isAsyncIterable } from './valueUtils'; import { iterableToArray } from './iterableUtils'; -import { PathData } from './types'; +import { Handler, PathData } from './types'; /** * Returns a function that requests the values of multiple properties. @@ -13,7 +13,7 @@ import { PathData } from './types'; * - fn({ p1: null, p2: null }) returns { p1: path[p1], p2: path[p2] } * Combinations of the above are possible by passing them in arrays. */ -export default class GetFunctionHandler { +export default class GetFunctionHandler implements Handler { handle(pathData: PathData, path) { return (...args) => this.readProperties(path, args.length === 1 ? args[0] : args, true); diff --git a/src/InsertFunctionHandler.ts b/src/InsertFunctionHandler.ts index 4ec5c533..d423f220 100644 --- a/src/InsertFunctionHandler.ts +++ b/src/InsertFunctionHandler.ts @@ -1,9 +1,10 @@ import MutationFunctionHandler from './MutationFunctionHandler'; +import { Handler } from './types'; /** * A MutationFunctionHandler for insertions. */ -export default class InsertFunctionHandler extends MutationFunctionHandler { +export default class InsertFunctionHandler extends MutationFunctionHandler implements Handler { constructor() { super('INSERT', false); } diff --git a/src/JSONLDResolver.ts b/src/JSONLDResolver.ts index a1110093..6b77a97a 100644 --- a/src/JSONLDResolver.ts +++ b/src/JSONLDResolver.ts @@ -1,19 +1,19 @@ import { Util as ContextUtil } from 'jsonld-context-parser'; import { namedNode } from '@rdfjs/data-model'; import AbstractPathResolver from './AbstractPathResolver'; +import { Resolver } from './types'; /** * Resolves property names of a path * to their corresponding IRIs through a JSON-LD context. */ -export default class JSONLDResolver extends AbstractPathResolver { +export default class JSONLDResolver extends AbstractPathResolver implements Resolver { /** * Expands a JSON property key into a full IRI. */ async lookupProperty(property: string) { - const context = await this._context; - const expandedProperty = context.expandTerm(property, true); - if (!ContextUtil.isValidIri(expandedProperty)) + const expandedProperty = await this.expandTerm(property, true); + if (expandedProperty === null || !ContextUtil.isValidIri(expandedProperty)) throw new Error(`The JSON-LD context cannot expand the '${property}' property`); return namedNode(expandedProperty); } diff --git a/src/MutationExpressionsHandler.ts b/src/MutationExpressionsHandler.ts index 32f7837e..33922b32 100644 --- a/src/MutationExpressionsHandler.ts +++ b/src/MutationExpressionsHandler.ts @@ -1,4 +1,4 @@ -import { PathData } from "./types"; +import { Handler, PathData } from "./types"; /** * Traverses a path to collect mutationExpressions into an expression. @@ -7,7 +7,7 @@ import { PathData } from "./types"; * Requires: * - a mutationExpressions property on the path proxy */ -export default class MutationExpressionsHandler { +export default class MutationExpressionsHandler implements Handler { async handle(pathData: PathData) { const mutationExpressions = []; diff --git a/src/MutationFunctionHandler.ts b/src/MutationFunctionHandler.ts index 6dcd1921..5339ed39 100644 --- a/src/MutationFunctionHandler.ts +++ b/src/MutationFunctionHandler.ts @@ -4,7 +4,7 @@ import { ensureArray, joinArrays, valueToTerm, hasPlainObjectArgs, isAsyncIterable, } from './valueUtils'; -import { PathData } from './types'; +import { Handler, PathData } from './types'; /** * Returns a function that, when called with arguments, @@ -21,7 +21,7 @@ import { PathData } from './types'; * Requires: * - a pathExpression property on the path proxy and all non-raw arguments. */ -export default class MutationFunctionHandler { +export default class MutationFunctionHandler implements Handler { constructor( private mutationType?: 'INSERT' | 'DELETE', private allowZeroArgs?: boolean diff --git a/src/PathExpressionHandler.ts b/src/PathExpressionHandler.ts index b664b07f..4d8f1ae7 100644 --- a/src/PathExpressionHandler.ts +++ b/src/PathExpressionHandler.ts @@ -1,7 +1,9 @@ +import { Handler } from "./types"; + /** * Traverses a path to collect links and nodes into an expression. */ -export default class PathExpressionHandler { +export default class PathExpressionHandler implements Handler { async handle(pathData) { const segments = []; let current = pathData; diff --git a/src/PathProxy.ts b/src/PathProxy.ts index b3213504..cbb74283 100644 --- a/src/PathProxy.ts +++ b/src/PathProxy.ts @@ -1,4 +1,4 @@ -import { PathData } from "./types"; +import { Handlers, PathData, Resolvers } from "./types"; const EMPTY = Object.create(null); @@ -24,8 +24,8 @@ const EMPTY = Object.create(null); * - extendPath, a method to create a child path with this path as parent */ export default class PathProxy { - private _handlers; - private _resolvers; + private _handlers: Handlers; + private _resolvers: Resolvers; constructor({ handlers = EMPTY, resolvers = [] } = {}) { this._handlers = handlers; diff --git a/src/PredicateHandler.ts b/src/PredicateHandler.ts index 9e937fa7..22b64723 100644 --- a/src/PredicateHandler.ts +++ b/src/PredicateHandler.ts @@ -1,13 +1,21 @@ +import { Handler } from "./types"; + /** * Returns a new path starting from the predicate of the current path. * * Requires: * - (optional) a predicate property on the path data */ -export default class PredicateHandler { +export default class PredicateHandler implements Handler { handle(pathData) { const { predicate } = pathData; return !predicate ? undefined : Promise.resolve(predicate) .then(subject => pathData.extendPath({ subject }, null)); + + + // TODO: See if we can make it this + // const { predicate } = pathData; + // if (!predicate) return undefined; + // return pathData.extendPath({ subject: await predicate }, null); } } diff --git a/src/PredicatesHandler.ts b/src/PredicatesHandler.ts index d660dc21..e18833cf 100644 --- a/src/PredicatesHandler.ts +++ b/src/PredicatesHandler.ts @@ -1,8 +1,10 @@ +import { Handler, PathData } from "./types"; + /** * Queries for all predicates of a path subject */ -export default class PredicatesHandler { - handle(pathData) { +export default class PredicatesHandler implements Handler { + handle(pathData: PathData) { return pathData.extendPath({ distinct: true, select: '?predicate', diff --git a/src/PreloadHandler.ts b/src/PreloadHandler.ts index bb8a3b92..a43871ad 100644 --- a/src/PreloadHandler.ts +++ b/src/PreloadHandler.ts @@ -1,3 +1,5 @@ +import { Handler } from "./types"; + const VARIABLE = /(SELECT\s+)(\?\S+)/; const QUERY_TAIL = /\}[^}]*$/; @@ -12,7 +14,7 @@ const QUERY_TAIL = /\}[^}]*$/; * Creates: * - a resultsCache property on the path data */ -export default class PreloadHandler { +export default class PreloadHandler implements Handler { /** * Creates a preload function. */ diff --git a/src/PropertiesHandler.ts b/src/PropertiesHandler.ts index 37612e91..326481c4 100644 --- a/src/PropertiesHandler.ts +++ b/src/PropertiesHandler.ts @@ -3,14 +3,16 @@ */ import { JsonLdContextNormalized } from 'jsonld-context-parser'; import { toIterablePromise } from './promiseUtils'; +import { Handler } from './types'; -export default class PropertiesHandler { +export default class PropertiesHandler implements Handler { handle(pathData, path) { return toIterablePromise(this._handle(pathData, path)); } async* _handle(pathData, path) { const contextRaw = (await pathData.settings.parsedContext) || {}; + // TODO: See if we can just use the normalized context. This seems like unecessarily repeated behvaior const context = new JsonLdContextNormalized(contextRaw); for await (const predicate of path.predicates) yield context.compactIri(`${await predicate}`, true); diff --git a/src/ReplaceFunctionHandler.ts b/src/ReplaceFunctionHandler.ts index b0afaee2..d115bfd9 100644 --- a/src/ReplaceFunctionHandler.ts +++ b/src/ReplaceFunctionHandler.ts @@ -1,3 +1,5 @@ +import { Handler, PathData } from "./types"; + /** * Returns a function that deletes the given value * for the path, and then adds the given values to the path. @@ -6,8 +8,8 @@ * - a delete function on the path proxy. * - an add function on the path proxy. */ -export default class ReplaceFunctionHandler { - handle(pathData, path) { +export default class ReplaceFunctionHandler implements Handler { + handle(pathData: PathData, path) { return function (oldValue, ...newValues) { if (!oldValue || !newValues.length) throw new Error('Replacing values requires at least two arguments, old value followed by all new values'); @@ -15,3 +17,7 @@ export default class ReplaceFunctionHandler { }; } } + +// TODO: Implement the concept of a "dependent" set of handlers. +// in the case of this handler this means that the 'ADD' and 'DELETE' +// handlers would be the dependent handlers diff --git a/src/SetFunctionHandler.ts b/src/SetFunctionHandler.ts index b618809b..d6b64c19 100644 --- a/src/SetFunctionHandler.ts +++ b/src/SetFunctionHandler.ts @@ -1,4 +1,5 @@ import MutationFunctionHandler from './MutationFunctionHandler'; +import { Handler } from './types'; import { hasPlainObjectArgs } from './valueUtils'; /** @@ -9,7 +10,7 @@ import { hasPlainObjectArgs } from './valueUtils'; * - a delete function on the path proxy. * - an add function on the path proxy. */ -export default class SetFunctionHandler extends MutationFunctionHandler { +export default class SetFunctionHandler extends MutationFunctionHandler implements Handler { handle(pathData, path) { return (...args) => { // First, delete all existing values for the property/properties diff --git a/src/SparqlHandler.ts b/src/SparqlHandler.ts index 6b8ccb9e..6d277937 100644 --- a/src/SparqlHandler.ts +++ b/src/SparqlHandler.ts @@ -1,9 +1,10 @@ import { namedNode } from '@rdfjs/data-model'; import * as RDF from '@rdfjs/types'; +import { Handler } from './types'; const NEEDS_ESCAPE = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/, ESCAPE_ALL = /["\\\t\n\r\b\f\u0000-\u0019]|[\ud800-\udbff][\udc00-\udfff]/g, - ESCAPED_CHARS = { + ESCAPED_CHARS: Record = { '\\': '\\\\', '"': '\\"', '\t': '\\t', '\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f', }; @@ -14,7 +15,7 @@ const NEEDS_ESCAPE = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/, * Requires: * - a mutationExpressions or pathExpression property on the path proxy */ -export default class SparqlHandler { +export default class SparqlHandler implements Handler { async handle(pathData, path) { // First check if we have a mutation expression const mutationExpressions = await path.mutationExpressions; @@ -138,7 +139,7 @@ export default class SparqlHandler { // Creates a unique query variable within the given scope, based on the suggestion createVar(suggestion = '', scope) { let counter = 0; - let label = `?${suggestion.match(/[a-z0-9]*$/i)[0] || 'result'}`; + let label = `?${suggestion.match(/[a-z0-9]*$/i)?.[0] ?? 'result'}`; if (scope) { suggestion = label; while (scope[label]) @@ -189,9 +190,9 @@ export default class SparqlHandler { // Replaces a character by its escaped version // (borrowed from https://www.npmjs.com/package/n3) -function escapeCharacter(character) { +function escapeCharacter(character: string) { // Replace a single character by its escaped version - let result = ESCAPED_CHARS[character]; + let result: string | undefined = ESCAPED_CHARS[character]; if (result === undefined) { // Replace a single character with its 4-bit unicode escape sequence if (character.length === 1) { diff --git a/src/StringToLDflexHandler.ts b/src/StringToLDflexHandler.ts index 485391a3..015e3904 100644 --- a/src/StringToLDflexHandler.ts +++ b/src/StringToLDflexHandler.ts @@ -1,7 +1,9 @@ +import { Handler } from "./types"; + /** * Yields a function that interprets a string expression as an LDflex path. */ -export default class StringToLDflexHandler { +export default class StringToLDflexHandler implements Handler { handle(pathData, path) { // Resolves the given string expression against the LDflex object return (expression = '', ldflex = path) => { diff --git a/src/SubjectHandler.ts b/src/SubjectHandler.ts index 4a26d258..d3de8fcf 100644 --- a/src/SubjectHandler.ts +++ b/src/SubjectHandler.ts @@ -1,3 +1,5 @@ +import { Handler, PathData } from "./types"; + /** * Returns a new path starting from the subject of the current path. * @@ -5,8 +7,8 @@ * - (optional) a subject property on the path data * - (optional) a parent property on the path data */ -export default class SubjectHandler { - handle(pathData) { +export default class SubjectHandler implements Handler { + handle(pathData: PathData) { // Traverse parents until we find a subject let { subject, parent } = pathData; while (!subject && parent) diff --git a/src/SubjectsHandler.ts b/src/SubjectsHandler.ts index ed0899ca..bf8aa44a 100644 --- a/src/SubjectsHandler.ts +++ b/src/SubjectsHandler.ts @@ -1,7 +1,9 @@ +import { Handler } from "./types"; + /** * Queries for all subjects of a document */ -export default class SubjectsHandler { +export default class SubjectsHandler implements Handler { handle(pathData) { return pathData.extendPath({ distinct: true, diff --git a/src/ThenHandler.ts b/src/ThenHandler.ts index dc82d20f..515a1b64 100644 --- a/src/ThenHandler.ts +++ b/src/ThenHandler.ts @@ -1,5 +1,6 @@ import { getThen } from './promiseUtils'; import { getFirstItem } from './iterableUtils'; +import { Handler } from './types'; /** * Thenable handler that resolves to either the subject @@ -10,7 +11,7 @@ import { getFirstItem } from './iterableUtils'; * - (optional) a subject on the path proxy * - (optional) results on the path proxy */ -export default class ThenHandler { +export default class ThenHandler implements Handler { handle({ subject }, pathProxy) { // Resolve to either the subject (zero-length path) or the first result return subject ? diff --git a/src/ToArrayHandler.ts b/src/ToArrayHandler.ts index 90066104..1764eb4b 100644 --- a/src/ToArrayHandler.ts +++ b/src/ToArrayHandler.ts @@ -1,4 +1,4 @@ -import { PathData } from './types'; +import { Handler, PathData } from './types'; import { isAsyncIterable } from './valueUtils'; /** @@ -7,7 +7,7 @@ import { isAsyncIterable } from './valueUtils'; * Requires: * - (optional) an iterable path */ -export default class ToArrayHandler { +export default class ToArrayHandler implements Handler { handle(pathData: PathData, path) { return async map => { const items = []; diff --git a/src/valueUtils.ts b/src/valueUtils.ts index 76d2464a..1dcd653b 100644 --- a/src/valueUtils.ts +++ b/src/valueUtils.ts @@ -29,7 +29,7 @@ const xsdPrimitives = { }; // Checks whether the value is asynchronously iterable -export function isAsyncIterable(value) { +export function isAsyncIterable(value: any): value is AsyncIterator { return value && typeof value[Symbol.asyncIterator] === 'function'; } From 3e323e0ea68a463cb4d39a4c04b5490b2f27181c Mon Sep 17 00:00:00 2001 From: Jesse Wright Date: Sun, 12 Jun 2022 21:11:35 +1000 Subject: [PATCH 5/8] WIP --- src/AbstractPathResolver.ts | 2 +- src/ComplexPathResolver.ts | 3 ++- src/PathProxy.ts | 5 ++++- src/SortHandler.ts | 10 +++++----- src/SparqlHandler.ts | 2 +- src/types.ts | 19 +++++++++++++------ 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/AbstractPathResolver.ts b/src/AbstractPathResolver.ts index 96a76e8b..9cfb539b 100644 --- a/src/AbstractPathResolver.ts +++ b/src/AbstractPathResolver.ts @@ -93,7 +93,7 @@ export default abstract class AbstractPathResolver implements Resolver { return propertyCache && lazyThenable(async () => { // Preloading does not work with reversed predicates propertyCache = !(await reverse) && await propertyCache; - return propertyCache?.[(await predicate).value]; + return !(await reverse) && (await propertyCache)?.[(await predicate).value]; }); } } diff --git a/src/ComplexPathResolver.ts b/src/ComplexPathResolver.ts index a1b70419..3c5b04ef 100644 --- a/src/ComplexPathResolver.ts +++ b/src/ComplexPathResolver.ts @@ -3,6 +3,7 @@ import AbstractPathResolver from './AbstractPathResolver'; import { namedNode } from '@rdfjs/data-model'; import { IJsonLdContextNormalizedRaw } from 'jsonld-context-parser'; import { Resolver } from './types'; +import * as RDF from '@rdfjs/types'; const factory = new Factory(); /** @@ -60,7 +61,7 @@ export default class ComplexPathResolver extends AbstractPathResolver implements /** * Takes string and resolves it to a predicate or SPARQL path */ - async lookupProperty(property: string) { + async lookupProperty(property: string): Promise { // Expand the property to a full IRI const context = await this.getContextRaw(); const prefixes: Record = {}; diff --git a/src/PathProxy.ts b/src/PathProxy.ts index cbb74283..edc978f9 100644 --- a/src/PathProxy.ts +++ b/src/PathProxy.ts @@ -53,7 +53,7 @@ export default class PathProxy { // Add an extendPath method to create child paths if (!path.extendPath) { const pathProxy = this; - path.extendPath = function extendPath(newData, parent = this) { + path.extendPath = function extendPath(newData: PathData, parent = this) { return pathProxy.createPath(settings, { parent, extendPath, ...newData }); }; } @@ -74,6 +74,9 @@ export default class PathProxy { // Resolvers provide functionality for arbitrary properties, // so find a resolver that can handle this property + return this._resolvers.find(resolver => resolver.supports(property)) + ?.resolve(property, pathData, pathData.proxy) + for (const resolver of this._resolvers) { if (resolver.supports(property)) return resolver.resolve(property, pathData, pathData.proxy); diff --git a/src/SortHandler.ts b/src/SortHandler.ts index e522d55e..2728f59e 100644 --- a/src/SortHandler.ts +++ b/src/SortHandler.ts @@ -1,4 +1,4 @@ -import { Handler } from "./types"; +import { Handler, PathData } from "./types"; /** * Returns a function that creates a new path with the same values, @@ -12,8 +12,8 @@ import { Handler } from "./types"; export default class SortHandler implements Handler { constructor(private order: 'ASC' | 'DESC' = 'ASC') {} - handle(pathData, pathProxy) { - return (...properties) => { + handle(pathData: PathData, pathProxy: PathData['proxy']) { + return (...properties: string[]) => { // Do nothing if no sort properties were given if (properties.length === 0) return pathProxy; @@ -23,8 +23,8 @@ export default class SortHandler implements Handler { const { predicate } = pathProxy[property]; // Sort on the first property, and create paths for the next one - const childData = { property, predicate, sort: this.order }; - const childPath = pathData.extendPath(childData); + // const childData = { property, predicate, sort: this.order }; + const childPath = pathData.extendPath({ property, predicate, sort: this.order }); return rest.length === 0 ? childPath : childPath.sort(...rest); }; } diff --git a/src/SparqlHandler.ts b/src/SparqlHandler.ts index 6d277937..d2f90ab7 100644 --- a/src/SparqlHandler.ts +++ b/src/SparqlHandler.ts @@ -178,7 +178,7 @@ export default class SparqlHandler implements Handler { } // Creates triple patterns for the given subject, predicate, and objects - triplePatterns(subjectString, predicateTerm, objectStrings, reverse = false) { + triplePatterns(subjectString: string, predicateTerm: RDF.Term, objectStrings: string[], reverse = false) { let subjectStrings = [subjectString]; if (reverse) [subjectStrings, objectStrings] = [objectStrings, subjectStrings]; diff --git a/src/types.ts b/src/types.ts index c35bc764..8eebbaff 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,13 +3,13 @@ import { JsonLdContext, IJsonLdContextNormalizedRaw } from "jsonld-context-parse export type MaybePromise = T | Promise -export type HandlerFunction = (pathData: PathData, path: any) => any; +export type HandlerFunction = (pathData: PathData, path?: PathData['proxy']) => any; export type Handler = { handle: T }; export type Handlers = Record; export interface Resolver { supports(...args: any[]): boolean; - resolve(property: string, pathData: PathData): PathData; + resolve(property: string, pathData: PathData, proxy: PathData['proxy']): PathData; } export type Resolvers = Resolver[]; @@ -24,11 +24,18 @@ export interface Settings { export interface PathData { settings: Settings; - resultsCache?: MaybePromise>; - extendPath: HandlerFunction; // TODO: Check this - // extendPath(pathData: PathData, path?: Data): Data; + // resultsCache?: MaybePromise>; + // extendPath: HandlerFunction; // TODO: Check this + extendPath(pathData: PathData, path?: PathData): PathData; // TODO: Check this + proxy: any; + apply: any; + parent?: PathData; } - +// * - settings, an object that is passed on as-is to child paths +// * - proxy, a reference to the proxied object the user sees +// * - parent, a reference to the parent path +// * - apply, a function the will be invoked when the path is called as a function +// * - extendPath, a method to create a child path with this path as parent // export interface Data { // property: string | undefined; From 597ae228447945993ba7520de8d60c532465527c Mon Sep 17 00:00:00 2001 From: Jesse Wright Date: Sun, 12 Jun 2022 21:47:41 +1000 Subject: [PATCH 6/8] WIP --- src/AbstractPathResolver.ts | 2 +- src/ComplexPathResolver.ts | 2 +- src/DataHandler.ts | 3 +++ src/SubjectsHandler.ts | 10 ++++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/AbstractPathResolver.ts b/src/AbstractPathResolver.ts index 9cfb539b..1f78cf3c 100644 --- a/src/AbstractPathResolver.ts +++ b/src/AbstractPathResolver.ts @@ -92,7 +92,7 @@ export default abstract class AbstractPathResolver implements Resolver { let { propertyCache } = pathData; return propertyCache && lazyThenable(async () => { // Preloading does not work with reversed predicates - propertyCache = !(await reverse) && await propertyCache; + // propertyCache = !(await reverse) && await propertyCache; return !(await reverse) && (await propertyCache)?.[(await predicate).value]; }); } diff --git a/src/ComplexPathResolver.ts b/src/ComplexPathResolver.ts index 3c5b04ef..4a16f681 100644 --- a/src/ComplexPathResolver.ts +++ b/src/ComplexPathResolver.ts @@ -9,7 +9,7 @@ const factory = new Factory(); /** * Writes SPARQL algebra a complex SPARQL path */ -function writePathAlgebra(algebra: Algebra.Join | Algebra.Bgp | Algebra.Operation): string { +function writePathAlgebra(algebra: Algebra.Join | Algebra.Bgp | Algebra.Operation | Algebra.Path): string { if (algebra.type === Algebra.types.JOIN) return algebra.input.map(x => writePathAlgebra(x)).join('/'); // The algebra library turns sequential path expressions like diff --git a/src/DataHandler.ts b/src/DataHandler.ts index 159f2135..e5518212 100644 --- a/src/DataHandler.ts +++ b/src/DataHandler.ts @@ -9,6 +9,9 @@ import { Handler } from "./types"; * and/or be behind a function call. */ export default class DataHandler implements Handler { + private _isAsync: boolean; + private _isFunction: boolean; + constructor(options, ...dataProperties) { this._isAsync = options.async; this._isFunction = options.function; diff --git a/src/SubjectsHandler.ts b/src/SubjectsHandler.ts index bf8aa44a..52675eae 100644 --- a/src/SubjectsHandler.ts +++ b/src/SubjectsHandler.ts @@ -1,4 +1,14 @@ import { Handler } from "./types"; +import { Factory } from "sparqlalgebrajs"; +import { variable } from "@rdfjs/data-model"; +const { createDistinct, createProject, createBgp, createPattern } = new Factory(); + +createDistinct( + createProject( + createBgp([ createPattern(variable('s'), variable('p'), variable('o')) ]), + [ variable('s') ] + ) +) /** * Queries for all subjects of a document From ac96715db329b0ad8ffaef3a6671371929e9987b Mon Sep 17 00:00:00 2001 From: Jesse Wright Date: Mon, 13 Jun 2022 23:36:05 +1000 Subject: [PATCH 7/8] WIP --- notes.md | 5 + package-lock.json | 4 +- package.json | 4 +- sparqlBuilder/Untitled-1.ts | 466 +++++++++++++++++++++++++++++++++ sparqlBuilder/index.ts | 49 ++++ src/AbstractPathResolver.ts | 12 +- src/AsyncIteratorHandler.ts | 2 +- src/CollectionsHandler.ts | 2 + src/ComplexPathResolver.ts | 2 +- src/MutationFunctionHandler.ts | 31 ++- src/PathFactory.ts | 2 +- src/PathProxy.ts | 4 +- src/SparqlHandler.ts | 4 +- src/valueUtils.ts | 2 +- test.ts | 11 + 15 files changed, 585 insertions(+), 15 deletions(-) create mode 100644 sparqlBuilder/Untitled-1.ts create mode 100644 sparqlBuilder/index.ts create mode 100644 test.ts diff --git a/notes.md b/notes.md index 2f551720..bf05fd88 100644 --- a/notes.md +++ b/notes.md @@ -1,3 +1,8 @@ 1. Should be able to have supports / requires on each handler to indicate, type-wise, when other handlers are required 2. Should work in terms of SPARQLALGEBRAJS (or something similar) 3. extendPath take, as a parameter, a function that mutates the algebra. +4. The SPARQL builder that we make needs to be immutable as this is what enables branched paths. + + + +TO break down we need to go into JISON \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4d1655e0..6dbd505a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "@rdfjs/data-model": "^1.3.4", "@rdfjs/types": "^1.1.0", "jsonld-context-parser": "^2.1.5", - "sparqlalgebrajs": "^4.0.1" + "sparqlalgebrajs": "^4.0.1", + "sparqljs": "^3.5.2" }, "devDependencies": { "@babel/cli": "^7.16.0", @@ -22,6 +23,7 @@ "@babel/preset-env": "^7.16.4", "@comunica/actor-init-sparql-file": "^2.0.1", "@ldflex/comunica": "^4.0.0", + "@types/sparqljs": "^3.1.3", "eslint": "^8.4.0", "eslint-plugin-jest": "^26.0.0", "husky": "^8.0.1", diff --git a/package.json b/package.json index 3daafcef..983c453f 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "@rdfjs/data-model": "^1.3.4", "@rdfjs/types": "^1.1.0", "jsonld-context-parser": "^2.1.5", - "sparqlalgebrajs": "^4.0.1" + "sparqlalgebrajs": "^4.0.1", + "sparqljs": "^3.5.2" }, "devDependencies": { "@babel/cli": "^7.16.0", @@ -34,6 +35,7 @@ "@babel/preset-env": "^7.16.4", "@comunica/actor-init-sparql-file": "^2.0.1", "@ldflex/comunica": "^4.0.0", + "@types/sparqljs": "^3.1.3", "eslint": "^8.4.0", "eslint-plugin-jest": "^26.0.0", "husky": "^8.0.1", diff --git a/sparqlBuilder/Untitled-1.ts b/sparqlBuilder/Untitled-1.ts new file mode 100644 index 00000000..e07bd7b9 --- /dev/null +++ b/sparqlBuilder/Untitled-1.ts @@ -0,0 +1,466 @@ +import { namedNode } from '@rdfjs/data-model'; +import { Data, order } from '../types'; +import { variable } from '@rdfjs/data-model' +import * as RDF from 'rdf-js' +import { toSparql, Algebra, translate, Factory } from 'sparqlalgebrajs' + + +const factory = new Factory() + +enum order { + ASC = 'asc', + DESC = 'desc' +} + +function makeVariable(input: string | RDF.Variable): RDF.Variable +function makeVariable(input: string | RDF.Term): RDF.Term { + return typeof input === 'string' ? variable(input) : input +} + +class OperationBuilder { + private expressions: Algebra.Expression[] = [] + //private filter = [] + //private limit = [] + //private offset = [] + private patterns: Algebra.Pattern[] = [] + private variables: RDF.Variable[] = [] + distinct: boolean = false; + + algebra(): Algebra.Operation { + const bgp = factory.createBgp(this.patterns) + const project = factory.createProject(factory.createOrderBy(bgp, this.expressions), this.variables) + return translate(toSparql(this.distinct ? factory.createDistinct(project) : project)) + } + + sparql() { + return toSparql(this.algebra()) + } + // @ts-ignore + addPattern(subject: RDF.Quad_Subject, predicate: RDF.Quad_Predicate, object: RDF.Quad_Object, graph?: RDF.Quad_Graph, reverse: boolean = false) { + this.patterns.push(factory.createPattern( + reverse ? object : subject, + predicate, + reverse ? subject : object, + graph + ) + ) + } + + addVariable(variable: string | RDF.Variable) { + this.variables.push(makeVariable(variable)) + } + + addOrderBy(variable: string | RDF.Variable, ordering: order = order.ASC) { + // @ts-ignore + this.expressions.push( + ordering === order.DESC ? + // @ts-ignore + factory.createOperatorExpression(ordering, [factory.createTermExpression(makeVariable(variable))]) : + // @ts-ignore + factory.createTermExpression(makeVariable(variable)) + ) + } + // @ts-ignore + addFilter(filter, expression) { + // @ts-ignore + this.expressions.push(factory.createFilter( + filter, + expression + )) + } + +} + + + +// class SparqlBuilder { +// private _algebra: Algebra.Operation +// constructor(query: string | Algebra.Operation) { +// this._algebra = typeof query === 'string' ? translate(query) : query +// } + +// sparql() { +// return toSparql(this._algebra) +// } + +// algebra() { +// return this._algebra +// } + +// // } +// const factory = new Factory() + +// function makeVariable(input: string | RDF.Variable) { +// return typeof input === 'string' ? variable(input) : input +// } + +// class OperationBuilder { +// private expressions: Algebra.Expression[] = [] +// //private filter = [] +// //private limit = [] +// //private offset = [] +// private patterns: Algebra.Pattern[] = [] +// private variables: RDF.Variable[] = [] + + +// algebra(): Algebra.Operation { +// const bgp = factory.createBgp(this.patterns) +// // const filter = factory.createFilter(null, bgp) +// const orderBy = factory.createOrderBy(bgp, this.expressions) +// } + +// sparql() { +// toSparql(this.algebra()) +// } + +// addPattern(subject: RDF.Quad_Subject, predicate: RDF.Quad_Predicate, object: RDF.Quad_Object, graph?: RDF.Quad_Graph) { +// this.patterns.push(factory.createPattern(subject, predicate, object, graph)) +// } + +// addVariables(variable: string | RDF.Variable) { +// this.variables.push(makeVariable(variable)) +// } + +// addOrderBy(variable: string | RDF.Variable, ordering: order = order.ASC) { +// this.expressions.push(factory.createOperatorExpression(ordering, [factory.createTermExpression(makeVariable(variable))]) +// } +// } + + +// class OperationsHanlder { +// constructor(private operation: Algebra.Operation) {} + +// addExpression({ +// type, expression, operator, args +// }: { +// expression: Algebra.Expression +// }) { +// Algebra.expressionTypes +// } + +// } + + + +const NEEDS_ESCAPE = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/, + ESCAPE_ALL = /["\\\t\n\r\b\f\u0000-\u0019]|[\ud800-\udbff][\udc00-\udfff]/g, + ESCAPED_CHARS = { + '\\': '\\\\', '"': '\\"', '\t': '\\t', + '\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f', + }; + +/** + * Expresses a path or mutation as a SPARQL query. + * + * Requires: + * - a mutationExpressions or pathExpression property on the path proxy + */ +export default class SparqlHandler { + async handle(pathData: Data, path: Data) { + // First check if we have a mutation expression + const mutationExpressions = await path.mutationExpressions; + if (Array.isArray(mutationExpressions) && mutationExpressions.length) + // Remove empty results to prevent dangling semicolons + return mutationExpressions.map(e => this.mutationExpressionToQuery(e)).filter(Boolean).join('\n;\n'); + + // Otherwise, fall back to checking for a path expression + const pathExpression = await path.pathExpression; + if (!Array.isArray(pathExpression)) + throw new Error(`${pathData} has no pathExpression property`); + return this.pathExpressionToQuery(pathData, path, pathExpression); + } + + pathExpressionToQuery(pathData: Data, path: Data, pathExpression) { + const builder = new OperationBuilder() + let queryVar = '?subject' + + if (pathExpression.length < 2 && !pathData.finalClause) + throw new Error(`${pathData} should at least contain a subject and a predicate`); + + // Create triple patterns + if (pathExpression.length > 1) { + queryVar = this.createVar(pathData.property); + const lastIndex = pathExpression.length - 1; + let object = skolemize(root.subject); + let queryVar = object; + let allowValues = false; + pathExpression.forEach(({ predicate, reverse, sort, values }, index) => { + // Obtain components and generate triple pattern + const subject = object; + + // Use fixed object values values if they were specified + let objects; + if (values && values.length > 0) { + if (!allowValues) + throw new Error('Specifying fixed values is not allowed here'); + objects.forEach(obj => { builder.addPattern(subject, predicate, obj, null, reverse) }) + + objects = values.map(this.termToString); + allowValues = false; // disallow subsequent fixed values for this predicate + } + // Otherwise, use a variable subject + else { + object = index < lastIndex ? this.createVar(`v${index}`, scope) : lastVar; + objects = [object]; + allowValues = true; + } + + + // If the sort option was not set, use this object as a query variable + if (!sort) + queryVar = object; + // If sort was set, use this object as a sorting variable + else { + // TODO: handle when an object is used for sorting, and later also for querying + builder.addOrderBy(object, sort) + // TODO: use a descriptive lastVar in case of sorting + object = queryVar; + } + }); + } + + builder.addVariable(pathData.select ?? queryVar) + builder.distinct = pathData.distinct ?? false + + + + let queryVar = '?subject', sorts = [], clauses = []; + if (pathExpression.length > 1) { + queryVar = this.createVar(pathData.property); + ({ queryVar, sorts, clauses } = this.expressionToTriplePatterns(pathExpression, queryVar)); + } + if (pathData.finalClause) + clauses.push(pathData.finalClause(queryVar)); + + builder.addVariable() + + + + const factory = new Factory() + + factory.createBgp([ + factory.createPattern() + ]) + + + + + + factory.createOrderBy({ + + }) + + factory.createTermExpression(RDF.variable()) + + let algebra: Algebra.Operation = { + type: Algebra.types.PROJECT, + variables: [pathData.select ?? queryVar], + input: { + type: Algebra.types.ORDER_BY, + input: { + type: Algebra.types.BGP, + + }, + expressions: sorts.map(({order, variable}) => factory.createOrderBy({ + type: Algebra.types., + + }, [{ + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION + }]) + + + // ({ + // type: Algebra.types.EXPRESSION, + // expressionType: Algebra.expressionTypes.OPERATOR, + // operator: order, + // args: [{ + // type: Algebra.types.EXPRESSION, + // expressionType: Algebra.expressionTypes.TERM, + // term: variable + // }] + // }) + + + ) } + } + + + + algebra = pathData.distinct ? {type: Algebra.types.DISTINCT, input: algebra } : algebra + + + // Create SPARQL query body + const distinct = pathData.distinct ? 'DISTINCT ' : ''; + const select = `SELECT ${distinct}${pathData.select ? pathData.select : queryVar}`; + const where = ` WHERE {\n ${clauses.join('\n ')}\n}`; + const orderClauses = sorts.map(({ order, variable }) => `${order}(${variable})`); + const orderBy = orderClauses.length === 0 ? '' : `\nORDER BY ${orderClauses.join(' ')}`; + return `${select}${where}${orderBy}`; + } + + mutationExpressionToQuery({ mutationType, conditions, predicateObjects }) { + // If there are no mutations, there is no query + if (!mutationType || !conditions || predicateObjects && predicateObjects.length === 0) + return ''; + + // Create the WHERE clauses + const scope = {}; + let subject, where; + // If the only condition is a subject, we need no WHERE clause + if (conditions.length === 1) { + subject = this.termToString(conditions[0].subject); + where = []; + } + // Otherwise, create a WHERE clause from all conditions + else { + const lastPredicate = conditions[conditions.length - 1].predicate; + subject = this.createVar(lastPredicate.value, scope); + ({ queryVar: subject, clauses: where } = + this.expressionToTriplePatterns(conditions, subject, scope)); + } + + // Create the mutation clauses + const mutations = []; + for (const { predicate, reverse, objects } of predicateObjects) { + // Mutate either only the specified objects, or all of them + const objectStrings = objects ? + objects.map(o => this.termToString(o)) : + [this.createVar(predicate.value, scope)]; + // Generate a triple pattern for all subjects + mutations.push(...this.triplePatterns(subject, predicate, objectStrings, reverse)); + } + const mutationClauses = `{\n ${mutations.join('\n ')}\n}`; + + // Join clauses into a SPARQL query + return where.length === 0 ? + // If there are no WHERE clauses, just mutate raw data + `${mutationType} DATA ${mutationClauses}` : + // Otherwise, return a DELETE/INSERT ... WHERE ... query + `${mutationType} ${mutationClauses} WHERE {\n ${where.join('\n ')}\n}`; + } + + expressionToTriplePatterns([root, ...pathExpression], lastVar, scope = {}) { + const lastIndex = pathExpression.length - 1; + const clauses = []; + const sorts = []; + let object = this.termToString(skolemize(root.subject)); + let queryVar = object; + let allowValues = false; + pathExpression.forEach((segment, index) => { + // Obtain components and generate triple pattern + const subject = object; + const { predicate, reverse, sort, values } = segment; + + // Use fixed object values values if they were specified + let objects; + if (values && values.length > 0) { + if (!allowValues) + throw new Error('Specifying fixed values is not allowed here'); + objects = values.map(this.termToString); + allowValues = false; // disallow subsequent fixed values for this predicate + } + // Otherwise, use a variable subject + else { + object = index < lastIndex ? this.createVar(`v${index}`, scope) : lastVar; + objects = [object]; + allowValues = true; + } + clauses.push(...this.triplePatterns(subject, predicate, objects, reverse)); + + // If the sort option was not set, use this object as a query variable + if (!sort) { + queryVar = object; + } + // If sort was set, use this object as a sorting variable + else { + // TODO: handle when an object is used for sorting, and later also for querying + sorts.push({ variable: object, order: sort }); + // TODO: use a descriptive lastVar in case of sorting + object = queryVar; + } + }); + return { queryVar, sorts, clauses }; + } + + // Creates a unique query variable within the given scope, based on the suggestion + createVar(suggestion = '', scope?) { + let counter = 0; + let label = `?${suggestion.match(/[a-z0-9]*$/i)[0] || 'result'}`; + if (scope) { + suggestion = label; + while (scope[label]) + label = `${suggestion}_${counter++}`; + scope[label] = true; + } + return label; + } + + // Converts an RDFJS term to a string that we can use in a query + termToString(term) { + // Determine escaped value + let { value } = term; + if (NEEDS_ESCAPE.test(value)) + value = value.replace(ESCAPE_ALL, escapeCharacter); + + switch (term.termType) { + case 'NamedNode': + return `<${value}>`; + + case 'BlankNode': + return `_:${value}`; + + case 'Literal': + // Determine optional language or datatype + let suffix = ''; + if (term.language) + suffix = `@${term.language}`; + else if (term.datatype.value !== 'http://www.w3.org/2001/XMLSchema#string') + suffix = `^^<${term.datatype.value}>`; + return `"${value}"${suffix}`; + + default: + throw new Error(`Could not convert a term of type ${term.termType}`); + } + } + + // Creates triple patterns for the given subject, predicate, and objects + triplePatterns(subjectString, predicateTerm, objectStrings, reverse = false) { + let subjectStrings = [subjectString]; + if (reverse) + [subjectStrings, objectStrings] = [objectStrings, subjectStrings]; + const objects = objectStrings.join(', '); + return subjectStrings.map(s => `${s} <${predicateTerm.value}> ${objects}.`); + } +} + +// Replaces a character by its escaped version +// (borrowed from https://www.npmjs.com/package/n3) +function escapeCharacter(character) { + // Replace a single character by its escaped version + let result = ESCAPED_CHARS[character]; + if (result === undefined) { + // Replace a single character with its 4-bit unicode escape sequence + if (character.length === 1) { + result = character.charCodeAt(0).toString(16); + result = '\\u0000'.substr(0, 6 - result.length) + result; + } + // Replace a surrogate pair with its 8-bit unicode escape sequence + else { + result = ((character.charCodeAt(0) - 0xD800) * 0x400 + + character.charCodeAt(1) + 0x2400).toString(16); + result = '\\U00000000'.substr(0, 10 - result.length) + result; + } + } + return result; +} + +// Skolemizes the given term if it is a blank node +let skolemId = 0; +function skolemize(term: RDF.Term): RDF.Term { + if (term.termType !== 'BlankNode') + return term; + if (!term.skolemized) + term.skolemized = namedNode(`urn:ldflex:sk${skolemId++}`); + return term.skolemized; +} \ No newline at end of file diff --git a/sparqlBuilder/index.ts b/sparqlBuilder/index.ts new file mode 100644 index 00000000..987164ad --- /dev/null +++ b/sparqlBuilder/index.ts @@ -0,0 +1,49 @@ +import { toSparql, Algebra, translate, Factory } from 'sparqlalgebrajs' +import * as RDF from '@rdfjs/types'; +const { createSeq, createPath, createInv } = new Factory; + +const seq = createSeq( + [createSeq([]), createSeq([]), createInv(createSeq([]))], +) + +const path = createPath( + +) + + +path.subject + +class SparqlBuilder { + private factory: Factory; + private _variables?: RDF.Variable[]; + distinct = false; + + constructor(factory?: Factory) { + this.factory = factory || new Factory(); + } + + get variables(): RDF.Variable[] { + if (this._variables) + return this._variables; + + // TODO: Lazily calculate variables using the + // this.operation or this.pattern + throw new Error('Not Implemented'); + } + + get operation(): Algebra.Operation { + + } + + get select(): Algebra.Project { + return this.factory.createProject(this.operation, this.variables) + } + + get construct(): Algebra.Construct { + + } + + get ask() { + return this.factory.createAsk(this.operation); + } +} diff --git a/src/AbstractPathResolver.ts b/src/AbstractPathResolver.ts index 1f78cf3c..420ae975 100644 --- a/src/AbstractPathResolver.ts +++ b/src/AbstractPathResolver.ts @@ -4,6 +4,10 @@ import { valueToTerm } from './valueUtils'; import type { MaybePromise, PathData, Resolver } from './types' import { IExpandOptions, JsonLdContext } from 'jsonld-context-parser' import * as RDF from '@rdfjs/types'; +import { Parser } from 'sparqljs'; +const parser = new Parser() + +parser.parse /** * Resolves property names of a path @@ -82,17 +86,19 @@ export default abstract class AbstractPathResolver implements Resolver { // JavaScript requires keys containing colons to be quoted, // so prefixed names would need to written as path['foaf:knows']. // We thus allow writing path.foaf_knows or path.foaf$knows instead. + // TODO: Make sure this can be captured by the types system that we develop - or not return this.lookupProperty(property.replace(/^([a-z][a-z0-9]*)[_$]/i, '$1:')); } /** * Gets the results cache for the given predicate. + * TODO: If anything results cache's should be per SPARQL algebra + * rather than per predicate - furthermore this, in general, is better + * handled by the query engine. */ - getResultsCache(pathData, predicate, reverse: MaybePromise) { - let { propertyCache } = pathData; + getResultsCache({ propertyCache }: PathData, predicate: MaybePromise, reverse: MaybePromise) { return propertyCache && lazyThenable(async () => { // Preloading does not work with reversed predicates - // propertyCache = !(await reverse) && await propertyCache; return !(await reverse) && (await propertyCache)?.[(await predicate).value]; }); } diff --git a/src/AsyncIteratorHandler.ts b/src/AsyncIteratorHandler.ts index 1c39656e..17115120 100644 --- a/src/AsyncIteratorHandler.ts +++ b/src/AsyncIteratorHandler.ts @@ -10,7 +10,7 @@ import type { Handler, PathData } from './types' * - (optional) results on the path proxy */ export default class AsyncIteratorHandler implements Handler { - handle({ subject }: PathData, pathProxy) { + handle({ subject }: PathData, pathProxy: PathData['proxy']) { // Return a one-item iterator of the subject if present; // otherwise, return all results of this path return subject ? diff --git a/src/CollectionsHandler.ts b/src/CollectionsHandler.ts index 38663c2f..28d9d6b7 100644 --- a/src/CollectionsHandler.ts +++ b/src/CollectionsHandler.ts @@ -9,6 +9,7 @@ export function listHandler() { return handler((_, path) => async () => { let _path = await path; const list = []; + // TODO: Should also be doing a NamedNode termType check here while (_path && _path.value !== `${RDF}nil`) { list.push(_path[`${RDF}first`]); _path = await _path[`${RDF}rest`]; @@ -41,6 +42,7 @@ export function containerHandler(set?: boolean) { export function collectionHandler() { return handler((pathData, path) => async () => { // TODO: Handle cases where multiple classes may be present (e.g. if inferencing is on) + // can probably be done via. and ask query switch ((await path[`${RDF}type`])?.value) { case `${RDF}List`: return listHandler().handle(pathData, path)(); diff --git a/src/ComplexPathResolver.ts b/src/ComplexPathResolver.ts index 4a16f681..e2a9edf1 100644 --- a/src/ComplexPathResolver.ts +++ b/src/ComplexPathResolver.ts @@ -4,7 +4,7 @@ import { namedNode } from '@rdfjs/data-model'; import { IJsonLdContextNormalizedRaw } from 'jsonld-context-parser'; import { Resolver } from './types'; import * as RDF from '@rdfjs/types'; -const factory = new Factory(); +const { } = new Factory(); /** * Writes SPARQL algebra a complex SPARQL path diff --git a/src/MutationFunctionHandler.ts b/src/MutationFunctionHandler.ts index 5339ed39..7bbec80c 100644 --- a/src/MutationFunctionHandler.ts +++ b/src/MutationFunctionHandler.ts @@ -4,7 +4,7 @@ import { ensureArray, joinArrays, valueToTerm, hasPlainObjectArgs, isAsyncIterable, } from './valueUtils'; -import { Handler, PathData } from './types'; +import { Handler, MaybePromise, PathData } from './types'; /** * Returns a function that, when called with arguments, @@ -93,7 +93,7 @@ export default class MutationFunctionHandler implements Handler { const objects = []; for (const value of values) { if (!isAsyncIterable(value)) - // Add a (promise to) a single value + // Add a (promise to) a single value objects.push(await value); // Add multiple values from a path else @@ -102,3 +102,30 @@ export default class MutationFunctionHandler implements Handler { return objects.map(valueToTerm); } } + +async function extract(values: (MaybePromise> | MaybePromise)[]): Promise { + // If no specific values are specified, match all (represented by `null`) + if (values.length === 0) + return null; + + // Otherwise, expand singular values, promises, and paths + const objects: T[] = []; + for (const value of values) { + if (!isAsyncIterable(value)) + // Add a (promise to) a single value + objects.push(await value); + // Add multiple values from a path + else + objects.push(...(await iterableToArray(value))); + } + + return objects; +} + + +export async function iterableToArray(iterable: AsyncIterable): Promise { + const items: T[] = []; + for await (const item of iterable) + items.push(item); + return items; +} \ No newline at end of file diff --git a/src/PathFactory.ts b/src/PathFactory.ts index d2aaa221..60698efc 100644 --- a/src/PathFactory.ts +++ b/src/PathFactory.ts @@ -15,7 +15,7 @@ export default class PathFactory { private _settings: Settings; public static defaultHandlers = defaultHandlers; - constructor(settings: Partial, data) { + constructor(settings?: Partial, data) { // Store settings and data this._settings = settings = { ...settings }; this._data = data = { ...data }; diff --git a/src/PathProxy.ts b/src/PathProxy.ts index edc978f9..8f2ec79f 100644 --- a/src/PathProxy.ts +++ b/src/PathProxy.ts @@ -1,4 +1,4 @@ -import { Handlers, PathData, Resolvers } from "./types"; +import { Handlers, PathData, Resolvers, Settings } from "./types"; const EMPTY = Object.create(null); @@ -35,7 +35,7 @@ export default class PathProxy { /** * Creates a path Proxy with the given settings and internal data fields. */ - createPath(settings = {}, data): PathData { + createPath(settings: Settings = {}, data): PathData { // The settings parameter is optional if (data === undefined) [data, settings] = [settings, {}]; diff --git a/src/SparqlHandler.ts b/src/SparqlHandler.ts index d2f90ab7..af90fc1c 100644 --- a/src/SparqlHandler.ts +++ b/src/SparqlHandler.ts @@ -137,7 +137,7 @@ export default class SparqlHandler implements Handler { } // Creates a unique query variable within the given scope, based on the suggestion - createVar(suggestion = '', scope) { + createVar(suggestion = '', scope?: Record) { let counter = 0; let label = `?${suggestion.match(/[a-z0-9]*$/i)?.[0] ?? 'result'}`; if (scope) { @@ -150,7 +150,7 @@ export default class SparqlHandler implements Handler { } // Converts an RDFJS term to a string that we can use in a query - termToString(term) { + termToString(term: RDF.Term) { // Determine escaped value let { value } = term; if (NEEDS_ESCAPE.test(value)) diff --git a/src/valueUtils.ts b/src/valueUtils.ts index 1dcd653b..9f319fca 100644 --- a/src/valueUtils.ts +++ b/src/valueUtils.ts @@ -29,7 +29,7 @@ const xsdPrimitives = { }; // Checks whether the value is asynchronously iterable -export function isAsyncIterable(value: any): value is AsyncIterator { +export function isAsyncIterable(value: any): value is AsyncIterator { return value && typeof value[Symbol.asyncIterator] === 'function'; } diff --git a/test.ts b/test.ts new file mode 100644 index 00000000..b58fa479 --- /dev/null +++ b/test.ts @@ -0,0 +1,11 @@ +import { Parser } from 'sparqljs'; +const parser = new Parser({ + +}) + +const x = parser.parse(` +PREFIX ex: + +SELECT * WHERE { ?s ex:a/ex:b ?o } +`) +console.log(x) \ No newline at end of file From 4edff0e78efbd1a91803274e7abf51f1830131d5 Mon Sep 17 00:00:00 2001 From: Jesse Wright Date: Sat, 18 Jun 2022 16:41:17 +1000 Subject: [PATCH 8/8] WIP --- sparqlBuilder/index.ts | 22 +++++++++++++++++----- src/AbstractPathResolver.ts | 3 ++- src/DataHandler.ts | 31 +++++++++++++++++++------------ src/GetFunctionHandler.ts | 2 +- src/PreloadHandler.ts | 8 ++++---- src/types.ts | 1 + src/valueUtils.ts | 2 +- 7 files changed, 45 insertions(+), 24 deletions(-) diff --git a/sparqlBuilder/index.ts b/sparqlBuilder/index.ts index 987164ad..208b46d9 100644 --- a/sparqlBuilder/index.ts +++ b/sparqlBuilder/index.ts @@ -14,12 +14,12 @@ const path = createPath( path.subject class SparqlBuilder { - private factory: Factory; private _variables?: RDF.Variable[]; + private _template?: Algebra.Pattern[]; + distinct = false; - constructor(factory?: Factory) { - this.factory = factory || new Factory(); + constructor(private factory = new Factory()) { } get variables(): RDF.Variable[] { @@ -31,8 +31,20 @@ class SparqlBuilder { throw new Error('Not Implemented'); } - get operation(): Algebra.Operation { + get template(): Algebra.Pattern[] { + if (this._template) + return this._template; + + const { operation } = this; + if (operation.type === Algebra.types.BGP) { + return operation.patterns; + } + throw new Error('Not Implemented'); + } + + get operation(): Algebra.Operation { + throw new Error('Not Implemented'); } get select(): Algebra.Project { @@ -40,7 +52,7 @@ class SparqlBuilder { } get construct(): Algebra.Construct { - + return this.factory.createConstruct(this.operation, this.template); } get ask() { diff --git a/src/AbstractPathResolver.ts b/src/AbstractPathResolver.ts index 420ae975..51f54164 100644 --- a/src/AbstractPathResolver.ts +++ b/src/AbstractPathResolver.ts @@ -58,7 +58,8 @@ export default abstract class AbstractPathResolver implements Resolver { */ resolve(property: string, pathData: PathData) { const predicate = lazyThenable(() => this.expandProperty(property)); - const reverse = lazyThenable(() => this.getContextRaw().then(context => context[property]?.['@reverse'])) + const reverse = this.getContextRaw().then(context => context[property]?.['@reverse']); + // const reverse = lazyThenable(() => this.getContextRaw().then(context => context[property]?.['@reverse'])) const resultsCache = this.getResultsCache(pathData, predicate, reverse); const newData = { property, predicate, resultsCache, reverse, apply: this.apply }; return pathData.extendPath(newData); diff --git a/src/DataHandler.ts b/src/DataHandler.ts index e5518212..81faa775 100644 --- a/src/DataHandler.ts +++ b/src/DataHandler.ts @@ -1,5 +1,10 @@ import { Handler } from "./types"; +interface Options { + async?: boolean; + function?: boolean; +} + /** * Resolves to the given item in the path data. * For example, new DataHandler({}, 'foo', 'bar') @@ -11,35 +16,36 @@ import { Handler } from "./types"; export default class DataHandler implements Handler { private _isAsync: boolean; private _isFunction: boolean; + private _dataProperties: string[] - constructor(options, ...dataProperties) { - this._isAsync = options.async; - this._isFunction = options.function; + constructor(options: Options, ...dataProperties: string[]) { + this._isAsync = Boolean(options.async); + this._isFunction = Boolean(options.function); this._dataProperties = dataProperties; } - static sync(...dataProperties) { + static sync(...dataProperties: string[]) { return new DataHandler({ async: false }, ...dataProperties); } - static syncFunction(...dataProperties) { + static syncFunction(...dataProperties: string[]) { return new DataHandler({ async: false, function: true }, ...dataProperties); } - static async(...dataProperties) { + static async(...dataProperties: string[]) { return new DataHandler({ async: true }, ...dataProperties); } - static asyncFunction(...dataProperties) { + static asyncFunction(...dataProperties: string[]) { return new DataHandler({ async: true, function: true }, ...dataProperties); } // Resolves the data path, or returns a function that does so handle(pathData) { - return !this._isFunction ? - this._resolveDataPath(pathData) : - () => this._resolveDataPath(pathData); + return this._isFunction ? + () => this._resolveDataPath(pathData) : + this._resolveDataPath(pathData); } // Resolves the data path @@ -51,15 +57,16 @@ export default class DataHandler implements Handler { // Resolves synchronous property access _resolveSyncDataPath(data) { + return this._dataProperties.reduce(prop => data?.[prop]) for (const property of this._dataProperties) - data = data && data[property]; + data &&= data[property]; return data; } // Resolves asynchronous property access async _resolveAsyncDataPath(data) { for (const property of this._dataProperties) - data = data && await data[property]; + data &&= await data[property]; return data; } } diff --git a/src/GetFunctionHandler.ts b/src/GetFunctionHandler.ts index 68c64691..28d1c261 100644 --- a/src/GetFunctionHandler.ts +++ b/src/GetFunctionHandler.ts @@ -19,7 +19,7 @@ export default class GetFunctionHandler implements Handler { args.length === 1 ? args[0] : args, true); } - async readProperties(path, properties, wrapSingleValues = false) { + async readProperties(path, properties: string | AsyncIterable | string[], wrapSingleValues = false) { // Convert an async iterable to an array if (isAsyncIterable(properties)) properties = await iterableToArray(properties); diff --git a/src/PreloadHandler.ts b/src/PreloadHandler.ts index a43871ad..623441c4 100644 --- a/src/PreloadHandler.ts +++ b/src/PreloadHandler.ts @@ -1,4 +1,4 @@ -import { Handler } from "./types"; +import { Handler, PathData } from "./types"; const VARIABLE = /(SELECT\s+)(\?\S+)/; const QUERY_TAIL = /\}[^}]*$/; @@ -37,11 +37,11 @@ export default class PreloadHandler implements Handler { * Creates a cache for the results of * resolving the given predicates against the path. */ - async createResultsCache(predicates, pathData, path) { + async createResultsCache(predicates, pathData: PathData, path) { // Execute the preloading query const { query, vars, resultVar } = await this.createQuery(predicates, path); const { settings: { queryEngine } } = pathData; - const bindings = queryEngine.execute(query); + const bindings = await queryEngine.queryBindings(query); // Extract all results and their preloaded property values const resultsCache = {}; @@ -75,7 +75,7 @@ export default class PreloadHandler implements Handler { /** * Creates the query for preloading the given predicates on the path */ - async createQuery(predicates, path) { + async createQuery(predicates, path: PathData['proxy']) { // Obtain the query for the current path, and its main variable const parentQuery = await path.sparql; const variableMatch = VARIABLE.exec(parentQuery); diff --git a/src/types.ts b/src/types.ts index 8eebbaff..789a3f35 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,6 +24,7 @@ export interface Settings { export interface PathData { settings: Settings; + subject: Term; // resultsCache?: MaybePromise>; // extendPath: HandlerFunction; // TODO: Check this extendPath(pathData: PathData, path?: PathData): PathData; // TODO: Check this diff --git a/src/valueUtils.ts b/src/valueUtils.ts index 9f319fca..74ea4540 100644 --- a/src/valueUtils.ts +++ b/src/valueUtils.ts @@ -29,7 +29,7 @@ const xsdPrimitives = { }; // Checks whether the value is asynchronously iterable -export function isAsyncIterable(value: any): value is AsyncIterator { +export function isAsyncIterable(value: any): value is AsyncIterable { return value && typeof value[Symbol.asyncIterator] === 'function'; }