diff --git a/.circleci/config.yml b/.circleci/config.yml index f28e253a8..06853ce76 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -477,6 +477,13 @@ commands: export DEPLOY_NPM_PASSWORD=$REPO_TYPEDB_PASSWORD bazel run --jobs=8 --define version=$(git rev-parse HEAD) //nodejs:deploy-npm -- snapshot + deploy-http-ts-npm-snapshot-unix: + steps: + - run: | + export DEPLOY_NPM_USERNAME=$REPO_TYPEDB_USERNAME + export DEPLOY_NPM_PASSWORD=$REPO_TYPEDB_PASSWORD + bazel run --define version=$(git rev-parse HEAD) //http-ts:deploy-npm -- snapshot + test-npm-snapshot-unix: steps: - run: | @@ -497,6 +504,14 @@ commands: export DEPLOY_NPM_TOKEN=$REPO_NPM_TOKEN bazel run --jobs=8 --define version=$(cat VERSION) //nodejs:deploy-npm --compilation_mode=opt -- release + deploy-http-ts-npm-release-unix: + steps: + - run: | + wget -q -O - https://cli-assets.heroku.com/apt/release.key | apt-key add - + wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - + export DEPLOY_NPM_TOKEN=$REPO_NPM_TOKEN + bazel run --define version=$(cat VERSION) //http-ts:deploy-npm --compilation_mode=opt -- release + jobs: ################# @@ -621,6 +636,7 @@ jobs: bazel-arch: amd64 - deploy-crate-snapshot-unix - deploy-maven-snapshot-unix + - deploy-http-ts-npm-snapshot-unix # - deploy-npm-snapshot-unix deploy-snapshot-dotnet-any: @@ -777,6 +793,7 @@ jobs: bazel-arch: amd64 - deploy-crate-release-unix - deploy-maven-release-unix + - deploy=http-ts-npm-release-unix # - deploy-npm-release-unix deploy-release-dotnet-any: diff --git a/.factory/automation.yml b/.factory/automation.yml index 06d2bc6be..a9c3d24fc 100644 --- a/.factory/automation.yml +++ b/.factory/automation.yml @@ -283,6 +283,8 @@ build: # tool/test/stop-cluster-servers.sh # exit $TEST_SUCCESS +# TODO: introduce integration/behaviour tests for http-ts driver + # test-nodejs-integration: # image: typedb-ubuntu-22.04 # dependencies: diff --git a/WORKSPACE b/WORKSPACE index 60b1ea36d..454881a6a 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -260,6 +260,14 @@ npm_translate_lock( load("@nodejs_npm//:repositories.bzl", "npm_repositories") npm_repositories() +npm_translate_lock( + name = "http-ts_npm", + pnpm_lock = "//http-ts:pnpm-lock.yaml", +) + +load("@http-ts_npm//:repositories.bzl", http_ts_npm_repositories = "npm_repositories") +http_ts_npm_repositories() + # Setup rules_ts load("@aspect_rules_ts//ts:repositories.bzl", "rules_ts_dependencies") diff --git a/http-ts/BUILD b/http-ts/BUILD new file mode 100644 index 000000000..ce022df0c --- /dev/null +++ b/http-ts/BUILD @@ -0,0 +1,94 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +exports_files([ + "pnpm-lock.yaml", + "package.json", + "tsconfig.json", +]) + +load("@typedb_bazel_distribution//npm:rules.bzl", "assemble_npm", "deploy_npm") +load("@typedb_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test") +load("@typedb_dependencies//distribution:deployment.bzl", "deployment") +load("@aspect_rules_ts//ts:defs.bzl", "ts_project") +load("@aspect_rules_js//js:defs.bzl", "js_binary") +load("@aspect_rules_js//npm:defs.bzl", "npm_link_package", "npm_package") +load("@aspect_bazel_lib//lib:jq.bzl", "jq") +load("@http-ts_npm//:defs.bzl", "npm_link_all_packages") +load("//nodejs:tool/typedoc/rules.bzl", "typedoc_docs") +load("//nodejs:docs_structure.bzl", "dir_mapping") +load("//tool/docs:nodejs/rules.bzl", "typedoc_to_adoc") + +npm_link_all_packages( + name = "node_modules", +) + +ts_project( + name = "driver-http-ts", + srcs = glob(["*.ts"]), + tsconfig = ":tsconfig.json", + declaration = True, + deps = [":node_modules/typescript"], + transpiler = "tsc", + visibility = ["//visibility:public"], + out_dir = "dist", +) + +jq( + name = "package", + srcs = ["package.json"], + filter = "|".join([ + # Don't directly reference $STAMP as it's only set when stamping + # This 'as' syntax results in $stamp being null in unstamped builds. + "$ARGS.named.STAMP as $stamp", + # Provide a default using the "alternative operator" in case $stamp is null. + ".version = ($stamp.STABLE_VERSION // \"0.0.0\")" + ]), +) + +npm_package( + name = "driver-http-ts-npm-package", + srcs = [":driver-http-ts", ":package", "README.md"], + include_runfiles = False, + replace_prefixes = { "dist/": "" }, + visibility = ["//visibility:public"], +) + +assemble_npm( + name= "assemble-npm", + target = ":driver-http-ts-npm-package", +) + +deploy_npm( + name = "deploy-npm", + target = ":assemble-npm", + snapshot = deployment['npm']['snapshot'], + release = deployment['npm']['release'], +) + +checkstyle_test( + name = "checkstyle", + include = glob([ + "*", + ]), + exclude = glob([ + "*.json", + "*.md", + "pnpm-lock.yaml", + ]), + license_type = "apache-header", +) diff --git a/http-ts/README.md b/http-ts/README.md new file mode 100644 index 000000000..0bd800353 --- /dev/null +++ b/http-ts/README.md @@ -0,0 +1,47 @@ +# TypeDB HTTP Typescript Driver + +## Driver Architecture + +To learn about how the TypeDB HTTP driver communicates with the TypeDB Server, +refer to the [HTTP API Reference](https://typedb.com/docs/reference/http-api). + +## API Reference + +To learn about the methods available for executing queries and retrieving their answers using Typescript, refer to +the [API Reference](https://typedb.com/docs/reference/http-drivers/typescript). + +## Install TypeDB HTTP Typescript Driver through NPM + +1. Install `typedb-driver-http` through npm: + +```bash +npm install typedb-driver-http +``` + +2. Make sure the [TypeDB Server](https://docs.typedb.com/docs/running-typedb/install-and-run#start-the-typedb-server) is + running. +3. Use TypeDB Driver in your program: + +```ts +import { TypeDBHttpDriver, isApiErrorResponse } from "typedb-driver-http"; + +const driver = new TypeDBHttpDriver({ + username: "admin", + password: "password", + addresses: [ "localhost:1729" ], +}); + +const transactionResponse = await driver.openTransaction("database-name", "read"); +if (isApiErrorResponse(transactionResponse)) throw transactionResponse.err; +const transactionId = transactionResponse.ok.transactionId; + +const answerResponse = await driver.query(transactionId, "match entity $x;"); +if (isApiErrorResponse(answerResponse)) throw answerResponse.err; +const answer = answerResponse.ok; + +if (answer.answerType === "conceptRows") { + answer.answers.forEach((row) => { + console.log(row.data) + }) +} +``` diff --git a/http-ts/concept.ts b/http-ts/concept.ts new file mode 100644 index 000000000..4e35c62fe --- /dev/null +++ b/http-ts/concept.ts @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export type TypeKind = "entityType" | "relationType" | "attributeType" | "roleType"; + +export type ThingKind = "entity" | "relation" | "attribute"; + +export type ValueKind = "value"; + +export type ValueType = "boolean" | "integer" | "double" | "decimal" | "date" | "datetime" | "datetime-tz" | "duration" | "string" | "struct"; + +export type EdgeKind = "isa" | "has" | "links" | "sub" | "owns" | "relates" | "plays" | "isaExact" | "subExact" | "assigned" | "argument"; + +export interface EntityType { + kind: "entityType"; + label: string; +} + +export interface RelationType { + kind: "relationType"; + label: string; +} + +export interface RoleType { + kind: "roleType"; + label: string; +} + +export type AttributeType = { + label: string; + kind: "attributeType"; + valueType: ValueType; +} + +export type Type = InstantiableType | RoleType; +export type InstantiableType = EntityType | RelationType | AttributeType; + +export interface Entity { + kind: "entity"; + iid: string; + type: EntityType; +} + +export interface Relation { + kind: "relation"; + iid: string; + type: RelationType; +} + +export interface Attribute { + kind: "attribute"; + iid: string; + value: any; + valueType: ValueType; + type: AttributeType; +} + +export interface Value { + kind: ValueKind; + value: any; + valueType: ValueType; +} + +export type Concept = Type | Entity | Relation | Attribute | Value; diff --git a/http-ts/index.ts b/http-ts/index.ts new file mode 100644 index 000000000..c96d8bb33 --- /dev/null +++ b/http-ts/index.ts @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { DriverParams, remoteOrigin } from "./params"; +import { + ApiErrorResponse, + ApiResponse, + DatabasesListResponse, + isApiError, + QueryResponse, + SignInResponse, + TransactionOpenResponse, + UsersListResponse, + VersionResponse +} from "./response"; + +const HTTP_UNAUTHORIZED = 401; + +export * from "./concept"; +export * from "./params"; +export * from "./query-structure"; +export * from "./response"; + +export class TypeDBHttpDriver { + + private token?: string; + + constructor(private params: DriverParams) {} + + getDatabases(): Promise> { + return this.apiGet(`/v1/databases`); + } + + getDatabase(name: String): Promise> { + return this.apiGet(`/v1/databases/${name}`); + } + + createDatabase(name: string): Promise { + return this.apiPost(`/v1/databases/${name}`, {}); + } + + deleteDatabase(name: string): Promise { + return this.apiDelete(`/v1/databases/${name}`); + } + + getDatabaseSchema(name: string): Promise> { + return this.apiGetString(`/v1/databases/${name}/schema`); + } + + getDatabaseTypeSchema(name: string): Promise> { + return this.apiGetString(`/v1/databases/${name}/type-schema`); + } + + getUsers(): Promise> { + return this.apiGet(`/v1/users`); + } + + getUser(username: string): Promise> { + return this.apiGet(`/v1/users/${username}`); + } + + createUser(username: string, password: string): Promise { + return this.apiPost(`/v1/users/${username}`, { password }); + } + + updateUser(username: string, password: string): Promise { + return this.apiPut(`/v1/users/${username}`, { password }); + } + + deleteUser(username: string): Promise { + return this.apiDelete(`/v1/users/${username}`); + } + + openTransaction(databaseName: string, transactionType: TransactionType, transactionOptions?: TransactionOptions): Promise> { + return this.apiPost(`/v1/transactions/open`, { databaseName, transactionType, transactionOptions }); + } + + commitTransaction(transactionId: string): Promise { + return this.apiPost(`/v1/transactions/${transactionId}/commit`, {}); + } + + closeTransaction(transactionId: string): Promise { + return this.apiPost(`/v1/transactions/${transactionId}/close`, {}); + } + + rollbackTransaction(transactionId: string): Promise { + return this.apiPost(`/v1/transactions/${transactionId}/rollback`, {}); + } + + query(transactionId: string, query: string, queryOptions?: QueryOptions): Promise> { + return this.apiPost(`/v1/transactions/${transactionId}/query`, { query, queryOptions }); + } + + oneShotQuery(query: string, commit: boolean, databaseName: string, transactionType: TransactionType, transactionOptions?: TransactionOptions, queryOptions?: QueryOptions) { + return this.apiPost(`/v1/query`, { query, commit, databaseName, transactionType, transactionOptions, queryOptions }); + } + + health(): Promise { + return this.apiGet(`/v1/health`); + } + + version(): Promise> { + return this.apiGet(`/v1/version`); + } + + private async apiGetString(path: string, options?: { headers?: Record }): Promise> { + return this.stringApiReq("GET", path, options); + } + + private async apiGet(path: string, options?: { headers?: Record }): Promise> { + return this.jsonApiReqWithoutBody("GET", path, options); + } + + private async apiDelete(path: string, options?: { headers?: Record }): Promise> { + return this.jsonApiReqWithoutBody("DELETE", path, options); + } + + private async apiPost(path: string, body: BODY, options?: { headers?: Record }): Promise> { + return this.jsonApiReq("POST", path, body, options); + } + + private async apiPut(path: string, body: BODY, options?: { headers?: Record }): Promise> { + return this.jsonApiReq("PUT", path, body, options); + } + + private async jsonApiReqWithoutBody(method: string, path: string, options?: { headers?: Record }): Promise> { + return this.jsonApiReq(method, path, undefined, options); + } + + private async jsonApiReq(method: string, path: string, body?: BODY, options?: { headers?: Record }): Promise> { + const resp = await this.apiReq(method, path, body, options); + if ("err" in resp) return resp; + const json = await this.jsonOrNull(resp); + if (resp.ok) return { ok: json as RES }; + else if (isApiError(json)) return { err: json, status: resp.status }; + else throw resp; + } + + private async stringApiReq(method: string, path: string, options?: { headers?: Record }): Promise> { + const resp = await this.apiReq(method, path, undefined, options); + if ("err" in resp) return resp; + if (resp.ok) return { ok: await this.stringOrNull(resp) }; + else { + const json = await this.jsonOrNull(resp); + if (isApiError(json)) return { err: json, status: resp.status }; + else throw resp; + } + } + + private async apiReq(method: string, path: string, body?: BODY, options?: { headers?: Record }): Promise { + const url = `${remoteOrigin(this.params)}${path}`; + let tokenResp = await this.getToken(); + if ("err" in tokenResp) return tokenResp; + let bodyString = undefined; + if (body !== undefined) bodyString = JSON.stringify(body) + let headers = Object.assign({ "Authorization": `Bearer ${tokenResp.ok.token}`, "Content-Type": "application/json" }, options?.headers || {}); + let resp = await fetch(url, { method, body: bodyString, headers }); + if (resp.status === HTTP_UNAUTHORIZED) { + tokenResp = await this.refreshToken(); + if ("err" in tokenResp) return tokenResp; + headers = Object.assign({ "Authorization": `Bearer ${tokenResp.ok.token}`, "Content-Type": "application/json" }, options?.headers || {}); + resp = await fetch(url, { method, body: bodyString, headers }); + } + return resp; + } + + private getToken(): Promise> { + if (this.token) { + const resp: ApiResponse ={ ok: { token: this.token } }; + return Promise.resolve(resp); + } else return this.refreshToken(); + } + + private async refreshToken(): Promise> { + const url = `${remoteOrigin(this.params)}/v1/signin`; + const body = { username: this.params.username, password: this.params.password }; + const resp = await fetch(url, { method: "POST", body: JSON.stringify(body), headers: { "Content-Type": "application/json" } }); + const json = await this.jsonOrNull(resp); + if (resp.ok) { + this.token = (json as SignInResponse).token; + return { ok: json }; + } else if (isApiError(json)) { + return { err: json, status: resp.status }; + } else throw resp; + } + + private async jsonOrNull(resp: Response) { + const contentLengthRaw = resp.headers.get("Content-Length"); + if (!contentLengthRaw) return null; + const contentLength = parseInt(contentLengthRaw || ""); + if (isNaN(contentLength)) throw `Received invalid Content-Length header: ${contentLengthRaw}`; + return contentLength > 0 ? await resp.json() : null; + } + + private async stringOrNull(resp: Response) { + const contentLengthRaw = resp.headers.get("Content-Length"); + if (!contentLengthRaw) return null; + const contentLength = parseInt(contentLengthRaw || ""); + if (isNaN(contentLength)) throw `Received invalid Content-Length header: ${contentLengthRaw}`; + return contentLength > 0 ? await resp.text() : null; + } +} + +export interface Database { + name: string; +} + +export type TransactionType = "read" | "write" | "schema"; + +export interface TransactionOptions { + schemaLockAcquireTimeoutMillis?: number; + transactionTimeoutMillis?: number; +} + +export interface QueryOptions { + includeInstanceTypes?: boolean; + answerCountLimit?: number; +} + +export interface User { + username: string; +} diff --git a/http-ts/package.json b/http-ts/package.json new file mode 100644 index 000000000..32d35bf95 --- /dev/null +++ b/http-ts/package.json @@ -0,0 +1,19 @@ +{ + "name": "typedb-driver-http", + "version": "0.0.0", + "description": "TypeDB HTTP Driver", + "author": "TypeDB", + "license": "Apache-2.0", + "homepage": "https://typedb.com", + "type": "module", + "main": "index.ts", + "repository": { + "type": "git", + "url": "https://github.com/typedb/typedb-driver" + }, + "keywords": ["database", "typedb"], + "packageManager": "pnpm@8.15.9", + "dependencies": { + "typescript": "^5.8.3" + } +} diff --git a/http-ts/params.ts b/http-ts/params.ts new file mode 100644 index 000000000..1107f6212 --- /dev/null +++ b/http-ts/params.ts @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface DriverParamsBasic { + username: string; + password: string; + addresses: string[]; +} + +export interface DriverParamsTranslated { + username: string; + password: string; + translatedAddresses: TranslatedAddress[]; +} + +export interface TranslatedAddress { + external: string; + internal: string; +} + +export type DriverParams = DriverParamsBasic | DriverParamsTranslated; + +export function isBasicParams(params: DriverParams): params is DriverParamsBasic { + return `addresses` in params; +} + +export function isTranslatedParams(params: DriverParams): params is DriverParamsTranslated { + return `translatedAddresses` in params; +} + +export function remoteOrigin(params: DriverParams) { + if (isBasicParams(params)) return `${params.addresses[0]}`; + else return `${params.translatedAddresses[0].external}`; +} diff --git a/http-ts/pnpm-lock.yaml b/http-ts/pnpm-lock.yaml new file mode 100644 index 000000000..afd9c1e24 --- /dev/null +++ b/http-ts/pnpm-lock.yaml @@ -0,0 +1,18 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: false + excludeLinksFromLockfile: false + +dependencies: + typescript: + specifier: ^5.8.3 + version: 5.8.3 + +packages: + + /typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + dev: false diff --git a/http-ts/query-structure.ts b/http-ts/query-structure.ts new file mode 100644 index 000000000..ef7dda737 --- /dev/null +++ b/http-ts/query-structure.ts @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Type, Value } from "./concept"; + +export type QueryVertexKind = "variable" | "label" | "value"; + +export interface QueryVertexVariable { + tag: "variable"; + id: string, +} + +export interface QueryVertexLabel { + tag: "label"; + type: Type; +} + +export interface QueryVertexValue { + tag: "value"; + value: Value; +} + +export type QueryVertex = QueryVertexVariable | QueryVertexLabel | QueryVertexValue; + +export type QueryStructure = { + blocks: { constraints: QueryConstraintAny[] }[], + variables: {[name: string]: QueryVariableInfo }, + outputs: string[], +}; + +export function get_variable_name(structure: QueryStructure, variable: QueryVertexVariable) : string | null { + return structure.variables[variable.id]?.name; +} + +export type QueryVariableInfo = { name: string | null }; + +export type QueryConstraintAny = QueryConstraintIsa | QueryConstraintIsaExact | QueryConstraintHas | QueryConstraintLinks | + QueryConstraintSub | QueryConstraintSubExact | QueryConstraintOwns | QueryConstraintRelates | QueryConstraintPlays | + QueryConstraintExpression | QueryConstraintFunction | QueryConstraintComparison | + QueryConstraintIs | QueryConstraintIid | QueryConstraintKind | QueryConstraintValue | QueryConstraintLabel; + +export type QueryConstraintSpan = { begin: number, end: number }; + +// Instance +export interface QueryConstraintIsa { + tag: "isa", + textSpan: QueryConstraintSpan, + + instance: QueryVertexVariable, + type: QueryVertexVariable | QueryVertexLabel, +} + +export interface QueryConstraintIsaExact { + tag: "isa!", + textSpan: QueryConstraintSpan, + + instance: QueryVertexVariable, + type: QueryVertexVariable | QueryVertexLabel, +} + +export interface QueryConstraintHas { + tag: "has", + textSpan: QueryConstraintSpan, + + owner: QueryVertexVariable + attribute: QueryVertexVariable, +} + + +export interface QueryConstraintLinks { + tag: "links", + textSpan: QueryConstraintSpan, + + relation: QueryVertexVariable, + player: QueryVertexVariable, + role: QueryVertexVariable | QueryVertexLabel, +} + +// Type +export interface QueryConstraintSub { + tag: "sub", + textSpan: QueryConstraintSpan, + + subtype: QueryVertexVariable | QueryVertexLabel, + supertype: QueryVertexVariable | QueryVertexLabel, +} + +export interface QueryConstraintSubExact { + tag: "sub!", + textSpan: QueryConstraintSpan, + + subtype: QueryVertexVariable | QueryVertexLabel, + supertype: QueryVertexVariable | QueryVertexLabel, +} + +export interface QueryConstraintOwns { + tag: "owns", + textSpan: QueryConstraintSpan, + + owner: QueryVertexVariable | QueryVertexLabel, + attribute: QueryVertexVariable | QueryVertexLabel, +} + +export interface QueryConstraintRelates { + tag: "relates", + textSpan: QueryConstraintSpan, + + relation: QueryVertexVariable | QueryVertexLabel, + role: QueryVertexVariable | QueryVertexLabel, +} + +export interface QueryConstraintPlays { + tag: "plays", + textSpan: QueryConstraintSpan, + + player: QueryVertexVariable | QueryVertexLabel, + role: QueryVertexVariable | QueryVertexLabel, +} + +// Function +export interface QueryConstraintExpression { + tag: "expression", + textSpan: QueryConstraintSpan, + + text: string, + arguments: QueryVertexVariable[], + assigned: QueryVertexVariable[], +} + +export interface QueryConstraintFunction { + tag: "functionCall", + textSpan: QueryConstraintSpan, + + name: string, + arguments: QueryVertexVariable[], + assigned: QueryVertexVariable[], +} + +export interface QueryConstraintComparison { + tag: "comparison", + textSpan: QueryConstraintSpan, + + lhs: QueryVertexVariable | QueryVertexValue, + rhs: QueryVertexVariable | QueryVertexValue, + comparator: string, +} + +export interface QueryConstraintIs { + tag: "is", + textSpan: QueryConstraintSpan, + + lhs: QueryVertexVariable, + rhs: QueryVertexVariable, +} + +export interface QueryConstraintIid { + tag: "iid", + textSpan: QueryConstraintSpan, + + concept: QueryVertexVariable, + iid: string, +} + +export interface QueryConstraintLabel { + tag: "label", + textSpan: QueryConstraintSpan, + + type: QueryVertexVariable, + label: string, +} + +export interface QueryConstraintValue { + tag: "value", + textSpan: QueryConstraintSpan, + + attributeType: QueryVertexVariable, + valueType: string, +} + +export interface QueryConstraintKind { + tag: "kind", + textSpan: QueryConstraintSpan, + + type: QueryVertexVariable, + kind: string, +} diff --git a/http-ts/response.ts b/http-ts/response.ts new file mode 100644 index 000000000..1495f73cd --- /dev/null +++ b/http-ts/response.ts @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Database, User } from "index"; +import { Concept } from "./concept"; +import { QueryStructure } from "./query-structure"; + +export interface SignInResponse { + token: string; +} + +export type Distribution = `TypeDB Cluster` | `TypeDB CE`; + +export interface VersionResponse { + distribution: Distribution; + version: string; +} + +export interface DatabasesListResponse { + databases: Database[]; +} + +export interface UsersListResponse { + users: User[]; +} + +export interface TransactionOpenResponse { + transactionId: string; +} + +export type QueryType = "read" | "write" | "schema"; + +export type AnswerType = "ok" | "conceptRows" | "conceptDocuments"; + +export interface ConceptRow { + [varName: string]: Concept | undefined; +} + +export interface ConceptRowAnswer { + involvedBlocks: number[]; + data: ConceptRow; +} + +export type ConceptDocument = Object; + +export type Answer = ConceptRowAnswer | ConceptDocument; + +export interface QueryResponseBase { + answerType: AnswerType; + queryType: QueryType; + comment: string | null; + query: QueryStructure | null; +} + +export interface OkQueryResponse extends QueryResponseBase { + answerType: "ok"; +} + +export interface ConceptRowsQueryResponse extends QueryResponseBase { + answerType: "conceptRows"; + answers: ConceptRowAnswer[]; +} + +export interface ConceptDocumentsQueryResponse extends QueryResponseBase { + answerType: "conceptDocuments"; + answers: ConceptDocument[]; +} + +export type QueryResponse = OkQueryResponse | ConceptRowsQueryResponse | ConceptDocumentsQueryResponse; + +export type ApiOkResponse = { ok: OK_RES }; + +export type ApiError = { code: string; message: string }; + +export interface ApiErrorResponse { + err: ApiError; + status: number; +} + +export function isApiError(err: any): err is ApiError { + return typeof err.code === "string" && typeof err.message === "string"; +} + +export type ApiResponse = ApiOkResponse | ApiErrorResponse; + +export function isOkResponse(res: ApiResponse): res is ApiOkResponse { + return "ok" in res; +} + +export function isApiErrorResponse(res: ApiResponse): res is ApiErrorResponse { + return "err" in res; +} diff --git a/http-ts/tsconfig.json b/http-ts/tsconfig.json new file mode 100644 index 000000000..6bc613b0d --- /dev/null +++ b/http-ts/tsconfig.json @@ -0,0 +1,69 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es2021", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "dist", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": false, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "rootDirs": [] /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "include": ["*.ts"] +}