add a typescript library to access the backend
This commit is contained in:
parent
ac205eeb37
commit
6ebd972e4a
9
tslib/.eslintrc.js
Normal file
9
tslib/.eslintrc.js
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint"]
|
||||
};
|
5
tslib/.gitignore
vendored
Normal file
5
tslib/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
.build
|
||||
dist
|
||||
package-lock.json
|
||||
|
25
tslib/Makefile
Normal file
25
tslib/Makefile
Normal file
@ -0,0 +1,25 @@
|
||||
SHELL := bash
|
||||
.SHELLFLAGS := -eu -o pipefail -c
|
||||
.DELETE_ON_ERROR:
|
||||
MAKEFLAGS += --warn-undefined-variables
|
||||
MAKEFLAGS += --no-builtin-rules
|
||||
.SUFFIXES:
|
||||
|
||||
$(shell mkdir -p .build dist)
|
||||
|
||||
all: build
|
||||
|
||||
.build/deps: package.json
|
||||
npm i
|
||||
@touch $@
|
||||
|
||||
build: .build/deps dist/backend_pb.js
|
||||
npm run build
|
||||
|
||||
dist/backend_pb.js: .build/deps ../proto/backend.proto
|
||||
npm run proto
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
npm run lint
|
||||
npm run check-pretty
|
25
tslib/package.json
Normal file
25
tslib/package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "anki",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^2.11.0",
|
||||
"@typescript-eslint/parser": "^2.11.0",
|
||||
"eslint": "^6.7.2",
|
||||
"prettier": "^1.19.1",
|
||||
"typescript": "^3.7.3"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "npm run proto; npm run build",
|
||||
"build": "tsc --build tsconfig.json",
|
||||
"proto": "pbjs -t static-module ../proto/backend.proto -o dist/backend_pb.js; pbts dist/backend_pb.js -o dist/backend_pb.d.ts",
|
||||
"pretty": "prettier --write src/*.ts",
|
||||
"check-pretty": "prettier --check src/*.ts",
|
||||
"lint": "eslint --max-warnings=0 src/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"protobufjs": "^6.8.8"
|
||||
},
|
||||
"files": [
|
||||
"dist/*"
|
||||
]
|
||||
}
|
77
tslib/src/backend.ts
Normal file
77
tslib/src/backend.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { backend_proto as pb } from "../dist/backend_pb";
|
||||
import { expectNotNull } from "./tsutils";
|
||||
export { pb };
|
||||
|
||||
const BACKEND_URL = "http://localhost:3000/request";
|
||||
|
||||
function responseError(err: pb.BackendError): Error {
|
||||
switch (err.value) {
|
||||
case "invalidInput":
|
||||
return Error(`invalid input: ${err.invalidInput?.info}`);
|
||||
case "templateParse":
|
||||
return Error(`template parse failed: ${err.templateParse?.info}`);
|
||||
default:
|
||||
return Error("unexpected error response value");
|
||||
}
|
||||
}
|
||||
|
||||
/** Encode and send a request, decoding and returning the response.
|
||||
* Throws on error.
|
||||
*/
|
||||
async function webRequest(input: pb.IBackendInput): Promise<pb.BackendOutput> {
|
||||
// encode request
|
||||
const err = pb.BackendInput.verify(input);
|
||||
if (err) {
|
||||
throw Error(err);
|
||||
}
|
||||
const buf = pb.BackendInput.encode(input).finish();
|
||||
|
||||
// send to backend
|
||||
const resp = await fetch(BACKEND_URL, {
|
||||
method: "POST",
|
||||
body: buf,
|
||||
headers: {
|
||||
"Content-Type": "application/protobuf",
|
||||
"Accept": "application/protobuf",
|
||||
}
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw Error(`unexpected reply: ${resp.statusText}`);
|
||||
}
|
||||
|
||||
// get returned bytes
|
||||
const respBlob = await resp.blob();
|
||||
const respBuf = await new Response(respBlob).arrayBuffer();
|
||||
|
||||
// decode response, throwing on error/missing
|
||||
const result = pb.BackendOutput.decode(new Uint8Array(respBuf));
|
||||
if (result.value === undefined) {
|
||||
throw Error("Unexpected vaule in backend output.");
|
||||
} else if (result.value === "error") {
|
||||
throw responseError(result?.error as pb.BackendError);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deckTree(): Promise<pb.IDeckTreeNode[]> {
|
||||
const resp = await webRequest({
|
||||
deckTree: new pb.Empty()
|
||||
});
|
||||
return expectNotNull(resp?.deckTree?.top?.children);
|
||||
}
|
||||
|
||||
export async function findCards(search: string): Promise<number[]> {
|
||||
const resp = await webRequest({
|
||||
findCards: new pb.FindCardsIn({ search })
|
||||
});
|
||||
return expectNotNull(resp?.findCards?.cardIds) as number[];
|
||||
}
|
||||
|
||||
// just sort field for now
|
||||
export async function browserRows(cardIds: number[]): Promise<string[]> {
|
||||
const resp = await webRequest({
|
||||
browserRows: new pb.BrowserRowsIn({ cardIds })
|
||||
});
|
||||
return expectNotNull(resp?.browserRows?.sortFields);
|
||||
}
|
14
tslib/src/tsutils.ts
Normal file
14
tslib/src/tsutils.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/** Throws if argument is null/undefined. */
|
||||
export function expectNotNull<T>(val: T | null | undefined): T {
|
||||
if (val === null || val === undefined) {
|
||||
throw Error("Unexpected missing value.");
|
||||
}
|
||||
return val as T;
|
||||
}
|
||||
|
||||
//* Throws if argument is not truthy. */
|
||||
export function assert<T>(val: T): asserts val {
|
||||
if (!val) {
|
||||
throw Error("Assertion failed.");
|
||||
}
|
||||
}
|
24
tslib/tsconfig.json
Normal file
24
tslib/tsconfig.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
"target": "es6",
|
||||
"module": "es6",
|
||||
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
/* 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": true /* 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. */,
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
||||
"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'. */
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user