anki/ts/lib/proto.ts
Damien Elmes 6941bccde4 Add support for proto3 optional scalars
Protobuf 3.15 introduced support for marking scalar fields like
uint32 as optional, and all of our tooling appears to support it
now. This allows us to use simple optional/null checks in our Rust/
TypeScript code, without having to resort to an inner message.

I had to apply a minor patch to protobufjs to get this working with
the json-module output; this has also been submitted upstream:
https://github.com/protobufjs/protobuf.js/pull/1693

I've modified CardStatsResponse as an example of the new syntax.

One thing to note: while the Rust and TypeScript bindings use optional/
null fields, as that is the norm in those languages, Google's Python
bindings are not very Pythonic. Referencing an optional field that is
missing will yield the default value, and a separate HasField() call
is required, eg:

```
>>> from anki.stats_pb2 import CardStatsResponse as R
... msg = R.FromString(b"")
... print(msg.first_review)
... print(msg.HasField("first_review"))
0
False
```
2022-02-27 19:42:06 +10:00

76 lines
2.1 KiB
TypeScript

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
/* eslint
@typescript-eslint/no-explicit-any: "off",
*/
import type { Message, rpc, RPCImpl, RPCImplCallback } from "protobufjs";
import { anki } from "./backend_proto";
import Cards = anki.cards;
import Collection = anki.collection;
import DeckConfig = anki.deckconfig;
import Decks = anki.decks;
import Generic = anki.generic;
import I18n = anki.i18n;
import Notes = anki.notes;
import Notetypes = anki.notetypes;
import Scheduler = anki.scheduler;
import Stats = anki.stats;
import Tags = anki.tags;
export { Cards, Collection, Decks, Generic, Notes };
export const empty = Generic.Empty.create();
async function serviceCallback(
method: rpc.ServiceMethod<Message<any>, Message<any>>,
requestData: Uint8Array,
callback: RPCImplCallback,
): Promise<void> {
const headers = new Headers();
headers.set("Content-type", "application/octet-stream");
const methodName = method.name[0].toLowerCase() + method.name.substring(1);
const path = `/_anki/${methodName}`;
try {
const result = await fetch(path, {
method: "POST",
headers,
body: requestData,
});
const blob = await result.blob();
const respBuf = await new Response(blob).arrayBuffer();
const uint8Array = new Uint8Array(respBuf);
callback(null, uint8Array);
} catch (error) {
console.log("error caught");
callback(error as Error, null);
}
}
export { DeckConfig };
export const deckConfig = DeckConfig.DeckConfigService.create(
serviceCallback as RPCImpl,
);
export { I18n };
export const i18n = I18n.I18nService.create(serviceCallback as RPCImpl);
export { Notetypes };
export const notetypes = Notetypes.NotetypesService.create(serviceCallback as RPCImpl);
export { Scheduler };
export const scheduler = Scheduler.SchedulerService.create(serviceCallback as RPCImpl);
export { Stats };
export const stats = Stats.StatsService.create(serviceCallback as RPCImpl);
export { Tags };
export const tags = Tags.TagsService.create(serviceCallback as RPCImpl);