Anki/ts/lib/runtime-require.ts
Damien Elmes 366a697b3a Add a runtime proof of concept
The API extractor integration isn't set up properly yet, so I had
to manually generate the d.ts bundle and copy it into the add-on:

cd ts/runtime
bazel run extract_api
cp ../../.bazel/bin/ts/runtime/extract_api.sh.runfiles/ankidesktop/ts/runtime/dist/anki.d.ts \
  $c/anki_new_format_pack/ts/anki-dts/runtime.d.ts
2022-04-03 23:37:35 +10:00

100 lines
2.7 KiB
TypeScript

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
/**
* Names of anki packages
*
* @privateRemarks
* Originally this was more strictly typed as a record:
* ```ts
* type AnkiPackages = {
* "anki/NoteEditor": NoteEditorPackage,
* }
* ```
* This would be very useful for `require`: the result could be strictly typed.
* However cross-module type imports currently don't work.
*/
type AnkiPackages =
| "anki/NoteEditor"
| "anki/PlainTextInput"
| "anki/TemplateButtons"
| "anki/packages"
| "anki/bridgecommand"
| "anki/shortcuts"
| "anki/theme"
| "anki/location"
| "anki/surround"
| "anki/runtime"
| "anki/ui";
type PackageDeprecation<T extends Record<string, unknown>> = {
[key in keyof T]?: string;
};
/// This can be extended to allow require() calls at runtime, for packages
/// that are not included at bundling time.
const runtimePackages: Partial<Record<AnkiPackages, Record<string, unknown>>> = {};
const prohibit = () => false;
/**
* Packages registered with this function escape the typing provided by `AnkiPackages`
*/
export function registerPackageRaw(
name: string,
entries: Record<string, unknown>,
): void {
runtimePackages[name] = entries;
}
export function registerPackage<
T extends AnkiPackages,
U extends Record<string, unknown>,
>(name: T, entries: U, deprecation?: PackageDeprecation<U>): void {
const pack = deprecation
? new Proxy(entries, {
set: prohibit,
defineProperty: prohibit,
deleteProperty: prohibit,
get: (target, name: string) => {
if (name in deprecation) {
console.log(`anki: ${name} is deprecated: ${deprecation[name]}`);
}
return target[name];
},
})
: entries;
registerPackageRaw(name, pack);
}
function require<T extends AnkiPackages>(name: T): Record<string, unknown> | undefined {
if (!(name in runtimePackages)) {
throw new Error(`Cannot require "${name}" at runtime.`);
} else {
return runtimePackages[name];
}
}
function listPackages(): string[] {
return Object.keys(runtimePackages);
}
function hasPackages(...names: string[]): boolean {
for (const name of names) {
if (!(name in runtimePackages)) {
return false;
}
}
return true;
}
// Export require() as a global.
Object.assign(window, { require });
registerPackage("anki/packages", {
// We also register require here, so add-ons can have a type-save variant of require (TODO, see AnkiPackages above)
require,
listPackages,
hasPackages,
});