yanki
Version:
A CLI tool and TypeScript library for syncing Markdown to Anki flashcards.
1,628 lines (1,442 loc) • 52.3 kB
TypeScript
/**
Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
@category Type
*/
type Primitive =
| null
| undefined
| string
| number
| boolean
| symbol
| bigint;
/**
Returns a boolean for whether the given type is `any`.
@link https://stackoverflow.com/a/49928360/1490091
Useful in type utilities, such as disallowing `any`s to be passed to a function.
@example
```
import type {IsAny} from 'type-fest';
const typedObject = {a: 1, b: 2} as const;
const anyObject: any = {a: 1, b: 2};
function get<O extends (IsAny<O> extends true ? {} : Record<string, number>), K extends keyof O = keyof O>(object: O, key: K) {
return object[key];
}
const typedA = get(typedObject, 'a');
//=> 1
const anyA = get(anyObject, 'a');
//=> any
```
@category Type Guard
@category Utilities
*/
type IsAny<T> = 0 extends 1 & NoInfer<T> ? true : false;
/**
Returns a boolean for whether the given key is an optional key of type.
This is useful when writing utility types or schema validators that need to differentiate `optional` keys.
@example
```
import type {IsOptionalKeyOf} from 'type-fest';
type User = {
name: string;
surname: string;
luckyNumber?: number;
};
type Admin = {
name: string;
surname?: string;
};
type T1 = IsOptionalKeyOf<User, 'luckyNumber'>;
//=> true
type T2 = IsOptionalKeyOf<User, 'name'>;
//=> false
type T3 = IsOptionalKeyOf<User, 'name' | 'luckyNumber'>;
//=> boolean
type T4 = IsOptionalKeyOf<User | Admin, 'name'>;
//=> false
type T5 = IsOptionalKeyOf<User | Admin, 'surname'>;
//=> boolean
```
@category Type Guard
@category Utilities
*/
type IsOptionalKeyOf<Type extends object, Key extends keyof Type> =
IsAny<Type | Key> extends true ? never
: Key extends keyof Type
? Type extends Record<Key, Type[Key]>
? false
: true
: false;
/**
Extract all optional keys from the given type.
This is useful when you want to create a new type that contains different type values for the optional keys only.
@example
```
import type {OptionalKeysOf, Except} from 'type-fest';
type User = {
name: string;
surname: string;
luckyNumber?: number;
};
const REMOVE_FIELD = Symbol('remove field symbol');
type UpdateOperation<Entity extends object> = Except<Partial<Entity>, OptionalKeysOf<Entity>> & {
[Key in OptionalKeysOf<Entity>]?: Entity[Key] | typeof REMOVE_FIELD;
};
const update1: UpdateOperation<User> = {
name: 'Alice',
};
const update2: UpdateOperation<User> = {
name: 'Bob',
luckyNumber: REMOVE_FIELD,
};
```
@category Utilities
*/
type OptionalKeysOf<Type extends object> =
Type extends unknown // For distributing `Type`
? (keyof {[Key in keyof Type as
IsOptionalKeyOf<Type, Key> extends false
? never
: Key
]: never
}) & keyof Type // Intersect with `keyof Type` to ensure result of `OptionalKeysOf<Type>` is always assignable to `keyof Type`
: never; // Should never happen
/**
Extract all required keys from the given type.
This is useful when you want to create a new type that contains different type values for the required keys only or use the list of keys for validation purposes, etc...
@example
```
import type {RequiredKeysOf} from 'type-fest';
declare function createValidation<
Entity extends object,
Key extends RequiredKeysOf<Entity> = RequiredKeysOf<Entity>,
>(field: Key, validator: (value: Entity[Key]) => boolean): (entity: Entity) => boolean;
type User = {
name: string;
surname: string;
luckyNumber?: number;
};
const validator1 = createValidation<User>('name', value => value.length < 25);
const validator2 = createValidation<User>('surname', value => value.length < 25);
// @ts-expect-error
const validator3 = createValidation<User>('luckyNumber', value => value > 0);
// Error: Argument of type '"luckyNumber"' is not assignable to parameter of type '"name" | "surname"'.
```
@category Utilities
*/
type RequiredKeysOf<Type extends object> =
Type extends unknown // For distributing `Type`
? Exclude<keyof Type, OptionalKeysOf<Type>>
: never; // Should never happen
/**
Returns a boolean for whether the given type is `never`.
@link https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919
@link https://stackoverflow.com/a/53984913/10292952
@link https://www.zhenghao.io/posts/ts-never
Useful in type utilities, such as checking if something does not occur.
@example
```
import type {IsNever, And} from 'type-fest';
type A = IsNever<never>;
//=> true
type B = IsNever<any>;
//=> false
type C = IsNever<unknown>;
//=> false
type D = IsNever<never[]>;
//=> false
type E = IsNever<object>;
//=> false
type F = IsNever<string>;
//=> false
```
@example
```
import type {IsNever} from 'type-fest';
type IsTrue<T> = T extends true ? true : false;
// When a distributive conditional is instantiated with `never`, the entire conditional results in `never`.
type A = IsTrue<never>;
// ^? type A = never
// If you don't want that behaviour, you can explicitly add an `IsNever` check before the distributive conditional.
type IsTrueFixed<T> =
IsNever<T> extends true ? false : T extends true ? true : false;
type B = IsTrueFixed<never>;
// ^? type B = false
```
@category Type Guard
@category Utilities
*/
type IsNever<T> = [T] extends [never] ? true : false;
/**
An if-else-like type that resolves depending on whether the given `boolean` type is `true` or `false`.
Use-cases:
- You can use this in combination with `Is*` types to create an if-else-like experience. For example, `If<IsAny<any>, 'is any', 'not any'>`.
Note:
- Returns a union of if branch and else branch if the given type is `boolean` or `any`. For example, `If<boolean, 'Y', 'N'>` will return `'Y' | 'N'`.
- Returns the else branch if the given type is `never`. For example, `If<never, 'Y', 'N'>` will return `'N'`.
@example
```
import type {If} from 'type-fest';
type A = If<true, 'yes', 'no'>;
//=> 'yes'
type B = If<false, 'yes', 'no'>;
//=> 'no'
type C = If<boolean, 'yes', 'no'>;
//=> 'yes' | 'no'
type D = If<any, 'yes', 'no'>;
//=> 'yes' | 'no'
type E = If<never, 'yes', 'no'>;
//=> 'no'
```
@example
```
import type {If, IsAny, IsNever} from 'type-fest';
type A = If<IsAny<unknown>, 'is any', 'not any'>;
//=> 'not any'
type B = If<IsNever<never>, 'is never', 'not never'>;
//=> 'is never'
```
@example
```
import type {If, IsEqual} from 'type-fest';
type IfEqual<T, U, IfBranch, ElseBranch> = If<IsEqual<T, U>, IfBranch, ElseBranch>;
type A = IfEqual<string, string, 'equal', 'not equal'>;
//=> 'equal'
type B = IfEqual<string, number, 'equal', 'not equal'>;
//=> 'not equal'
```
Note: Sometimes using the `If` type can make an implementation non–tail-recursive, which can impact performance. In such cases, it’s better to use a conditional directly. Refer to the following example:
@example
```
import type {If, IsEqual, StringRepeat} from 'type-fest';
type HundredZeroes = StringRepeat<'0', 100>;
// The following implementation is not tail recursive
type Includes<S extends string, Char extends string> =
S extends `${infer First}${infer Rest}`
? If<IsEqual<First, Char>,
'found',
Includes<Rest, Char>>
: 'not found';
// Hence, instantiations with long strings will fail
// @ts-expect-error
type Fails = Includes<HundredZeroes, '1'>;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Error: Type instantiation is excessively deep and possibly infinite.
// However, if we use a simple conditional instead of `If`, the implementation becomes tail-recursive
type IncludesWithoutIf<S extends string, Char extends string> =
S extends `${infer First}${infer Rest}`
? IsEqual<First, Char> extends true
? 'found'
: IncludesWithoutIf<Rest, Char>
: 'not found';
// Now, instantiations with long strings will work
type Works = IncludesWithoutIf<HundredZeroes, '1'>;
//=> 'not found'
```
@category Type Guard
@category Utilities
*/
type If<Type extends boolean, IfBranch, ElseBranch> =
IsNever<Type> extends true
? ElseBranch
: Type extends true
? IfBranch
: ElseBranch;
/**
Matches any primitive, `void`, `Date`, or `RegExp` value.
*/
type BuiltIns = Primitive | void | Date | RegExp;
/**
Test if the given function has multiple call signatures.
Needed to handle the case of a single call signature with properties.
Multiple call signatures cannot currently be supported due to a TypeScript limitation.
@see https://github.com/microsoft/TypeScript/issues/29732
*/
type HasMultipleCallSignatures<T extends (...arguments_: any[]) => unknown> =
T extends {(...arguments_: infer A): unknown; (...arguments_: infer B): unknown}
? B extends A
? A extends B
? false
: true
: true
: false;
/**
Useful to flatten the type output to improve type hints shown in editors. And also to transform an interface into a type to aide with assignability.
@example
```
import type {Simplify} from 'type-fest';
type PositionProps = {
top: number;
left: number;
};
type SizeProps = {
width: number;
height: number;
};
// In your editor, hovering over `Props` will show a flattened object with all the properties.
type Props = Simplify<PositionProps & SizeProps>;
```
Sometimes it is desired to pass a value as a function argument that has a different type. At first inspection it may seem assignable, and then you discover it is not because the `value`'s type definition was defined as an interface. In the following example, `fn` requires an argument of type `Record<string, unknown>`. If the value is defined as a literal, then it is assignable. And if the `value` is defined as type using the `Simplify` utility the value is assignable. But if the `value` is defined as an interface, it is not assignable because the interface is not sealed and elsewhere a non-string property could be added to the interface.
If the type definition must be an interface (perhaps it was defined in a third-party npm package), then the `value` can be defined as `const value: Simplify<SomeInterface> = ...`. Then `value` will be assignable to the `fn` argument. Or the `value` can be cast as `Simplify<SomeInterface>` if you can't re-declare the `value`.
@example
```
import type {Simplify} from 'type-fest';
interface SomeInterface {
foo: number;
bar?: string;
baz: number | undefined;
}
type SomeType = {
foo: number;
bar?: string;
baz: number | undefined;
};
const literal = {foo: 123, bar: 'hello', baz: 456};
const someType: SomeType = literal;
const someInterface: SomeInterface = literal;
declare function fn(object: Record<string, unknown>): void;
fn(literal); // Good: literal object type is sealed
fn(someType); // Good: type is sealed
// @ts-expect-error
fn(someInterface); // Error: Index signature for type 'string' is missing in type 'someInterface'. Because `interface` can be re-opened
fn(someInterface as Simplify<SomeInterface>); // Good: transform an `interface` into a `type`
```
@link https://github.com/microsoft/TypeScript/issues/15300
@see {@link SimplifyDeep}
@category Object
*/
type Simplify<T> = {[KeyType in keyof T]: T[KeyType]} & {};
/**
Omit any index signatures from the given object type, leaving only explicitly defined properties.
This is the counterpart of `PickIndexSignature`.
Use-cases:
- Remove overly permissive signatures from third-party types.
This type was taken from this [StackOverflow answer](https://stackoverflow.com/a/68261113/420747).
It relies on the fact that an empty object (`{}`) is assignable to an object with just an index signature, like `Record<string, unknown>`, but not to an object with explicitly defined keys, like `Record<'foo' | 'bar', unknown>`.
(The actual value type, `unknown`, is irrelevant and could be any type. Only the key type matters.)
```
const indexed: Record<string, unknown> = {}; // Allowed
// @ts-expect-error
const keyed: Record<'foo', unknown> = {}; // Error
// => TS2739: Type '{}' is missing the following properties from type 'Record<"foo" | "bar", unknown>': foo, bar
```
Instead of causing a type error like the above, you can also use a [conditional type](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html) to test whether a type is assignable to another:
```
type Indexed = {} extends Record<string, unknown>
? '✅ `{}` is assignable to `Record<string, unknown>`'
: '❌ `{}` is NOT assignable to `Record<string, unknown>`';
// => '✅ `{}` is assignable to `Record<string, unknown>`'
type Keyed = {} extends Record<'foo' | 'bar', unknown>
? '✅ `{}` is assignable to `Record<\'foo\' | \'bar\', unknown>`'
: '❌ `{}` is NOT assignable to `Record<\'foo\' | \'bar\', unknown>`';
// => "❌ `{}` is NOT assignable to `Record<'foo' | 'bar', unknown>`"
```
Using a [mapped type](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#further-exploration), you can then check for each `KeyType` of `ObjectType`...
```
type OmitIndexSignature<ObjectType> = {
[KeyType in keyof ObjectType // Map each key of `ObjectType`...
]: ObjectType[KeyType]; // ...to its original value, i.e. `OmitIndexSignature<Foo> == Foo`.
};
```
...whether an empty object (`{}`) would be assignable to an object with that `KeyType` (`Record<KeyType, unknown>`)...
```
type OmitIndexSignature<ObjectType> = {
[KeyType in keyof ObjectType
// Is `{}` assignable to `Record<KeyType, unknown>`?
as {} extends Record<KeyType, unknown>
? never // ✅ `{}` is assignable to `Record<KeyType, unknown>`
: KeyType // ❌ `{}` is NOT assignable to `Record<KeyType, unknown>`
]: ObjectType[KeyType];
};
```
If `{}` is assignable, it means that `KeyType` is an index signature and we want to remove it. If it is not assignable, `KeyType` is a "real" key and we want to keep it.
@example
```
import type {OmitIndexSignature} from 'type-fest';
type Example = {
// These index signatures will be removed.
[x: string]: any;
[x: number]: any;
[x: symbol]: any;
[x: `head-${string}`]: string;
[x: `${string}-tail`]: string;
[x: `head-${string}-tail`]: string;
[x: `${bigint}`]: string;
[x: `embedded-${number}`]: string;
// These explicitly defined keys will remain.
foo: 'bar';
qux?: 'baz';
};
type ExampleWithoutIndexSignatures = OmitIndexSignature<Example>;
// => { foo: 'bar'; qux?: 'baz' | undefined; }
```
@see {@link PickIndexSignature}
@category Object
*/
type OmitIndexSignature<ObjectType> = {
[KeyType in keyof ObjectType as {} extends Record<KeyType, unknown>
? never
: KeyType]: ObjectType[KeyType];
};
/**
Pick only index signatures from the given object type, leaving out all explicitly defined properties.
This is the counterpart of `OmitIndexSignature`.
@example
```
import type {PickIndexSignature} from 'type-fest';
declare const symbolKey: unique symbol;
type Example = {
// These index signatures will remain.
[x: string]: unknown;
[x: number]: unknown;
[x: symbol]: unknown;
[x: `head-${string}`]: string;
[x: `${string}-tail`]: string;
[x: `head-${string}-tail`]: string;
[x: `${bigint}`]: string;
[x: `embedded-${number}`]: string;
// These explicitly defined keys will be removed.
['kebab-case-key']: string;
[symbolKey]: string;
foo: 'bar';
qux?: 'baz';
};
type ExampleIndexSignature = PickIndexSignature<Example>;
// {
// [x: string]: unknown;
// [x: number]: unknown;
// [x: symbol]: unknown;
// [x: `head-${string}`]: string;
// [x: `${string}-tail`]: string;
// [x: `head-${string}-tail`]: string;
// [x: `${bigint}`]: string;
// [x: `embedded-${number}`]: string;
// }
```
@see {@link OmitIndexSignature}
@category Object
*/
type PickIndexSignature<ObjectType> = {
[KeyType in keyof ObjectType as {} extends Record<KeyType, unknown>
? KeyType
: never]: ObjectType[KeyType];
};
// Merges two objects without worrying about index signatures.
type SimpleMerge<Destination, Source> = {
[Key in keyof Destination as Key extends keyof Source ? never : Key]: Destination[Key];
} & Source;
/**
Merge two types into a new type. Keys of the second type overrides keys of the first type.
@example
```
import type {Merge} from 'type-fest';
type Foo = {
[x: string]: unknown;
[x: number]: unknown;
foo: string;
bar: symbol;
};
type Bar = {
[x: number]: number;
[x: symbol]: unknown;
bar: Date;
baz: boolean;
};
export type FooBar = Merge<Foo, Bar>;
// => {
// [x: string]: unknown;
// [x: number]: number;
// [x: symbol]: unknown;
// foo: string;
// bar: Date;
// baz: boolean;
// }
```
@category Object
*/
type Merge<Destination, Source> =
Simplify<
SimpleMerge<PickIndexSignature<Destination>, PickIndexSignature<Source>>
& SimpleMerge<OmitIndexSignature<Destination>, OmitIndexSignature<Source>>
>;
/**
Merges user specified options with default options.
@example
```
type PathsOptions = {maxRecursionDepth?: number; leavesOnly?: boolean};
type DefaultPathsOptions = {maxRecursionDepth: 10; leavesOnly: false};
type SpecifiedOptions = {leavesOnly: true};
type Result = ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, SpecifiedOptions>;
//=> {maxRecursionDepth: 10; leavesOnly: true}
```
@example
```
// Complains if default values are not provided for optional options
type PathsOptions = {maxRecursionDepth?: number; leavesOnly?: boolean};
type DefaultPathsOptions = {maxRecursionDepth: 10};
type SpecifiedOptions = {};
type Result = ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, SpecifiedOptions>;
// ~~~~~~~~~~~~~~~~~~~
// Property 'leavesOnly' is missing in type 'DefaultPathsOptions' but required in type '{ maxRecursionDepth: number; leavesOnly: boolean; }'.
```
@example
```
// Complains if an option's default type does not conform to the expected type
type PathsOptions = {maxRecursionDepth?: number; leavesOnly?: boolean};
type DefaultPathsOptions = {maxRecursionDepth: 10; leavesOnly: 'no'};
type SpecifiedOptions = {};
type Result = ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, SpecifiedOptions>;
// ~~~~~~~~~~~~~~~~~~~
// Types of property 'leavesOnly' are incompatible. Type 'string' is not assignable to type 'boolean'.
```
@example
```
// Complains if an option's specified type does not conform to the expected type
type PathsOptions = {maxRecursionDepth?: number; leavesOnly?: boolean};
type DefaultPathsOptions = {maxRecursionDepth: 10; leavesOnly: false};
type SpecifiedOptions = {leavesOnly: 'yes'};
type Result = ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, SpecifiedOptions>;
// ~~~~~~~~~~~~~~~~
// Types of property 'leavesOnly' are incompatible. Type 'string' is not assignable to type 'boolean'.
```
*/
type ApplyDefaultOptions<
Options extends object,
Defaults extends Simplify<Omit<Required<Options>, RequiredKeysOf<Options>> & Partial<Record<RequiredKeysOf<Options>, never>>>,
SpecifiedOptions extends Options,
> =
If<IsAny<SpecifiedOptions>, Defaults,
If<IsNever<SpecifiedOptions>, Defaults,
Simplify<Merge<Defaults, {
[Key in keyof SpecifiedOptions
as Key extends OptionalKeysOf<Options> ? undefined extends SpecifiedOptions[Key] ? never : Key : Key
]: SpecifiedOptions[Key]
}> & Required<Options>>>>;
/**
@see {@link PartialDeep}
*/
type PartialDeepOptions = {
/**
Whether to affect the individual elements of arrays and tuples.
@default false
*/
readonly recurseIntoArrays?: boolean;
/**
Allows `undefined` values in non-tuple arrays.
- When set to `true`, elements of non-tuple arrays can be `undefined`.
- When set to `false`, only explicitly defined elements are allowed in non-tuple arrays, ensuring stricter type checking.
@default false
@example
You can allow `undefined` values in non-tuple arrays by passing `{recurseIntoArrays: true; allowUndefinedInNonTupleArrays: true}` as the second type argument:
```
import type {PartialDeep} from 'type-fest';
type Settings = {
languages: string[];
};
declare const partialSettings: PartialDeep<Settings, {recurseIntoArrays: true; allowUndefinedInNonTupleArrays: true}>;
partialSettings.languages = [undefined]; // OK
```
*/
readonly allowUndefinedInNonTupleArrays?: boolean;
};
type DefaultPartialDeepOptions = {
recurseIntoArrays: false;
allowUndefinedInNonTupleArrays: false;
};
/**
Create a type from another type with all keys and nested keys set to optional.
Use-cases:
- Merging a default settings/config object with another object, the second object would be a deep partial of the default object.
- Mocking and testing complex entities, where populating an entire object with its keys would be redundant in terms of the mock or test.
@example
```
import type {PartialDeep} from 'type-fest';
let settings = {
textEditor: {
fontSize: 14,
fontColor: '#000000',
fontWeight: 400,
},
autocomplete: false,
autosave: true,
};
const applySavedSettings = (savedSettings: PartialDeep<typeof settings>) => (
{...settings, ...savedSettings, textEditor: {...settings.textEditor, ...savedSettings.textEditor}}
);
settings = applySavedSettings({textEditor: {fontWeight: 500}});
```
By default, this does not affect elements in array and tuple types. You can change this by passing `{recurseIntoArrays: true}` as the second type argument:
```
import type {PartialDeep} from 'type-fest';
type Shape = {
dimensions: [number, number];
};
const partialShape: PartialDeep<Shape, {recurseIntoArrays: true}> = {
dimensions: [], // OK
};
partialShape.dimensions = [15]; // OK
```
@see {@link PartialDeepOptions}
@category Object
@category Array
@category Set
@category Map
*/
type PartialDeep<T, Options extends PartialDeepOptions = {}> =
_PartialDeep<T, ApplyDefaultOptions<PartialDeepOptions, DefaultPartialDeepOptions, Options>>;
type _PartialDeep<T, Options extends Required<PartialDeepOptions>> = T extends BuiltIns | ((new (...arguments_: any[]) => unknown))
? T
: T extends Map<infer KeyType, infer ValueType>
? PartialMapDeep<KeyType, ValueType, Options>
: T extends Set<infer ItemType>
? PartialSetDeep<ItemType, Options>
: T extends ReadonlyMap<infer KeyType, infer ValueType>
? PartialReadonlyMapDeep<KeyType, ValueType, Options>
: T extends ReadonlySet<infer ItemType>
? PartialReadonlySetDeep<ItemType, Options>
: T extends (...arguments_: any[]) => unknown
? IsNever<keyof T> extends true
? T // For functions with no properties
: HasMultipleCallSignatures<T> extends true
? T
: ((...arguments_: Parameters<T>) => ReturnType<T>) & PartialObjectDeep<T, Options>
: T extends object
? T extends ReadonlyArray<infer ItemType> // Test for arrays/tuples, per https://github.com/microsoft/TypeScript/issues/35156
? Options['recurseIntoArrays'] extends true
? ItemType[] extends T // Test for arrays (non-tuples) specifically
? readonly ItemType[] extends T // Differentiate readonly and mutable arrays
? ReadonlyArray<_PartialDeep<Options['allowUndefinedInNonTupleArrays'] extends false ? ItemType : ItemType | undefined, Options>>
: Array<_PartialDeep<Options['allowUndefinedInNonTupleArrays'] extends false ? ItemType : ItemType | undefined, Options>>
: PartialObjectDeep<T, Options> // Tuples behave properly
: T // If they don't opt into array testing, just use the original type
: PartialObjectDeep<T, Options>
: unknown;
/**
Same as `PartialDeep`, but accepts only `Map`s and as inputs. Internal helper for `PartialDeep`.
*/
type PartialMapDeep<KeyType, ValueType, Options extends Required<PartialDeepOptions>> = {} & Map<_PartialDeep<KeyType, Options>, _PartialDeep<ValueType, Options>>;
/**
Same as `PartialDeep`, but accepts only `Set`s as inputs. Internal helper for `PartialDeep`.
*/
type PartialSetDeep<T, Options extends Required<PartialDeepOptions>> = {} & Set<_PartialDeep<T, Options>>;
/**
Same as `PartialDeep`, but accepts only `ReadonlyMap`s as inputs. Internal helper for `PartialDeep`.
*/
type PartialReadonlyMapDeep<KeyType, ValueType, Options extends Required<PartialDeepOptions>> = {} & ReadonlyMap<_PartialDeep<KeyType, Options>, _PartialDeep<ValueType, Options>>;
/**
Same as `PartialDeep`, but accepts only `ReadonlySet`s as inputs. Internal helper for `PartialDeep`.
*/
type PartialReadonlySetDeep<T, Options extends Required<PartialDeepOptions>> = {} & ReadonlySet<_PartialDeep<T, Options>>;
/**
Same as `PartialDeep`, but accepts only `object`s as inputs. Internal helper for `PartialDeep`.
*/
type PartialObjectDeep<ObjectType extends object, Options extends Required<PartialDeepOptions>> = {
[KeyType in keyof ObjectType]?: _PartialDeep<ObjectType[KeyType], Options>
};
//#region src/types/deck.d.ts
type DeckStats = {
deck_id: number;
learn_count: number;
name: string;
new_count: number;
review_count: number;
total_in_deck: number;
};
type DeckConfig = {
autoplay: boolean;
dyn: 1 | false;
id: number;
lapse: {
delays: number[];
leechAction: number;
leechFails: number;
minInt: number;
mult: number;
};
maxTaken: number;
mod: number;
name: string;
new: {
bury: boolean;
delays: number[];
initialFactor: number;
ints: number[];
order: number;
perDay: number;
separate: boolean;
};
replayq: boolean;
rev: {
bury: boolean;
ease4: number;
fuzz: number;
ivlFct: number;
maxIvl: number;
minSpace: number;
perDay: number;
};
timer: number;
usn: number;
};
type DeckRequests = Request<'changeDeck', 6, {
cards: number[];
deck: string;
}> | Request<'cloneDeckConfigId', 6, {
cloneFrom: number;
name: string;
}, false | number> | Request<'createDeck', 6, {
deck: string;
}, number> | Request<'deckNames', 6, never, string[]> | Request<'deckNamesAndIds', 6, never, Record<string, number>> | Request<'deleteDecks', 6, {
cardsToo: true;
decks: string[];
}> | Request<'getDeckConfig', 6, {
deck: string;
}, DeckConfig> | Request<'getDecks', 6, Record<'cards', number[]>, Record<string, number[]>> | Request<'getDeckStats', 6, {
decks: string[];
}, Record<string, DeckStats>> | Request<'removeDeckConfigId', 6, {
configId: number;
}, boolean> | Request<'saveDeckConfig', 6, {
config: DeckConfig;
}, boolean> | Request<'setDeckConfigId', 6, {
configId: number;
decks: string[];
}, boolean>;
//#endregion
//#region src/types/note.d.ts
type NoteModel = 'Basic' | 'Basic (and reversed card)' | 'Basic (type in the answer)' | 'Cloze' | (string & {});
type NoteMedia = {
data?: string;
fields?: string[];
filename: string;
path?: string;
skipHash?: string;
url?: string;
};
type Note = {
audio?: NoteMedia[];
deckName: string;
fields: Record<string, string>;
modelName: NoteModel;
picture?: NoteMedia[];
tags?: string[];
video?: NoteMedia[];
};
type NoteWithCreationOptions = Note & {
options?: {
allowDuplicate?: boolean;
duplicateScope?: 'deck' | (string & {});
duplicateScopeOptions?: {
checkAllModels?: boolean;
checkChildren?: boolean;
deckName?: null | string;
};
};
};
type NoteRequests = Request<'addNote', 6, {
note: NoteWithCreationOptions;
}, null | number> | Request<'addNotes', 6, {
notes: NoteWithCreationOptions[];
}, Array<null | string> | null> | Request<'addTags', 6, {
notes: number[];
tags: string;
}> | Request<'canAddNotes', 6, {
notes: NoteWithCreationOptions[];
}, boolean[]> | Request<'canAddNotesWithErrorDetail', 6, {
notes: NoteWithCreationOptions[];
}, Array<{
canAdd: false;
error: string;
} | {
canAdd: true;
}>> | Request<'clearUnusedTags', 6, never, string[]> | Request<'deleteNotes', 6, {
notes: number[];
}> | Request<'findNotes', 6, {
query: string;
}, number[]> | Request<'getNoteTags', 6, {
note: number;
}, string[]> | Request<'getTags', 6, never, string[]> | Request<'notesInfo', 6, {
notes: number[];
}, Array<{
cards: number[];
fields: Record<string, {
order: number;
value: string;
}>;
mod: number;
modelName: string;
noteId: number;
profile: string;
tags: string[];
}>> | Request<'notesModTime', 6, {
notes: number[];
}, Array<{
mod: number;
noteId: number;
}>> | Request<'removeEmptyNotes', 6> | Request<'removeTags', 6, {
notes: number[];
tags: string;
}> | Request<'replaceTags', 6, {
notes: number[];
replace_with_tag: string;
tag_to_replace: string;
}> | Request<'replaceTagsInAllNotes', 6, {
replace_with_tag: string;
tag_to_replace: string;
}> | Request<'updateNote', 6, {
note: {
audio?: NoteMedia[];
fields: Record<string, string>;
id: number;
picture?: NoteMedia[];
tags?: string[];
video?: NoteMedia[];
} | {
fields?: Record<string, string>;
id: number;
tags: string[];
};
}> | Request<'updateNoteFields', 6, {
note: {
audio?: NoteMedia[];
fields: Record<string, string>;
id: number;
picture?: NoteMedia[];
video?: NoteMedia[];
};
}> | Request<'updateNoteModel', 6, {
note: {
fields: Record<string, string>;
id: number;
modelName: string;
tags: string[];
};
}> | Request<'updateNoteTags', 6, {
note: number;
tags: string[];
}>;
//#endregion
//#region src/types/graphical.d.ts
type GraphicalRequests = Request<'guiAddCards', 6, {
note: Note;
}, number> | Request<'guiAnswerCard', 6, {
ease: number;
}, boolean> | Request<'guiBrowse', 6, {
query: string;
reorderCards?: {
columnId: CardBrowserColumns;
order: 'ascending' | 'descending';
};
}, number[]> | Request<'guiCheckDatabase', 6, never, true> | Request<'guiCurrentCard', 6, never, CardInfo | null> | Request<'guiDeckBrowser', 6> | Request<'guiDeckOverview', 6, {
name: string;
}, boolean> | Request<'guiDeckReview', 6, {
name: string;
}, boolean> | Request<'guiEditNote', 6, {
note: number;
}> | Request<'guiExitAnki', 6> | Request<'guiImportFile', 6, {
path: string;
}> | Request<'guiPlayAudio', 6, never, true> | Request<'guiSelectCard', 6, {
card: number;
}, boolean> | Request<'guiSelectedNotes', 6, never, number[]> | Request<'guiSelectNote', 6, {
note: number;
}, boolean> | Request<'guiShowAnswer', 6, never, boolean> | Request<'guiShowQuestion', 6, never, boolean> | Request<'guiStartCardTimer', 6, never, true> | Request<'guiUndo', 6, never, boolean>;
//#endregion
//#region src/types/media.d.ts
type MediaRequests = Request<'deleteMediaFile', 6, {
filename: string;
}> | Request<'getMediaDirPath', 6, never, string> | Request<'getMediaFilesNames', 6, {
pattern: string;
}, string[]> | Request<'retrieveMediaFile', 6, {
filename: string;
}, false | string> | Request<'storeMediaFile', 6, {
data?: string;
deleteExisting?: boolean;
filename: string;
path?: string;
url?: string;
}, string>;
//#endregion
//#region src/types/miscellaneous.d.ts
type MiscellaneousRequests = Request<'apiReflect', 6, {
actions: null | string[];
scopes: Array<'actions'>;
}, {
actions: string[];
scopes: string[];
}> | Request<'exportPackage', 6, {
deck: string;
includeSched?: boolean;
path: string;
}, boolean> | Request<'getActiveProfile', 6, never, string> | Request<'getProfiles', 6, never, string[]> | Request<'importPackage', 6, {
path: string;
}, boolean> | Request<'loadProfile', 6, {
name: string;
}, true> | Request<'multi', 6,
// Crazy, have to call this experimental
{
actions: Array<{
action: Requests['action'];
params?: Requests['params'];
version?: number;
}>;
}, Array<Requests['response'] | {
error: null | string;
result: Requests['response'];
}>> | Request<'reloadCollection', 6> | Request<'requestPermission', 6, never, {
permission: 'denied';
} | {
permission: 'granted';
requireApiKey: boolean;
version: boolean;
}> | Request<'sync', 6> | Request<'version', 6, never, number>;
//#endregion
//#region src/types/model.d.ts
type ModelField = {
collapsed: boolean;
description: string;
excludeFromSearch: boolean;
font: string;
id: number;
name: string;
ord: number;
plainText: boolean;
preventDeletion: boolean;
rtl: boolean;
size: number;
sticky: boolean;
tag: null;
};
type ModelTemplate = {
afmt: string;
bafmt: string;
bfont: string;
bqfmt: string;
bsize: number;
did: null;
id: number;
name: string;
ord: number;
qfmt: string;
};
type Model = {
css: string;
did: null;
flds: ModelField[];
id: number;
latexPost: string;
latexPre: string;
latexsvg: boolean;
mod: number;
name: string;
originalStockKind: number;
req: Array<[number, string, number[]]>;
sortf: number;
tmpls: ModelTemplate[];
type: number;
usn: number;
};
type ModelToCreate = {
cardTemplates: Array<{
[key: string]: string;
Back: string;
Front: string;
}>;
css?: string;
inOrderFields: string[];
isCloze?: boolean;
modelName: string;
};
type ModelRequests = Request<'createModel', 6, ModelToCreate, Model> | Request<'findAndReplaceInModels', 6, {
model: {
back: boolean;
css: boolean;
fieldText: string;
front: boolean;
modelName: string;
replaceText: string;
};
}, number> | Request<'findModelsById', 6, {
modelIds: number[];
}, Model[]> | Request<'findModelsByName', 6, {
modelNames: string[];
}, Model[]> | Request<'modelFieldAdd', 6, {
fieldName: string;
index: number;
modelName: string;
}> | Request<'modelFieldDescriptions', 6, {
description: string;
fieldName: string;
modelName: string;
}, boolean> | Request<'modelFieldFonts', 6, {
modelName: string;
}, Record<string, {
font: string;
size: number;
}>> | Request<'modelFieldNames', 6, {
modelName: string;
}, string[]> | Request<'modelFieldRemove', 6, {
fieldName: string;
modelName: string;
}> | Request<'modelFieldRename', 6, {
modelName: string;
newFieldName: string;
oldFieldName: string;
}> | Request<'modelFieldReposition', 6, {
fieldName: string;
index: number;
modelName: string;
}> | Request<'modelFieldSetDescription', 6, {
fieldName: string;
index: number;
modelName: string;
}> | Request<'modelFieldSetFont', 6, {
fieldName: string;
font: string;
modelName: string;
}> | Request<'modelFieldSetFontSize', 6, {
fieldName: string;
fontSize: number;
modelName: string;
}> | Request<'modelFieldsOnTemplates', 6, {
modelName: string;
}, Record<string, [string[], string[]]>> | Request<'modelNames', 6, never, string[]> | Request<'modelNamesAndIds', 6, never, Record<string, number>> | Request<'modelStyling', 6, {
modelName: string;
}, {
css: string;
}> | Request<'modelTemplateAdd', 6, {
modelName: string;
template: {
[key: string]: string;
Back: string;
Front: string;
};
}> | Request<'modelTemplateRemove', 6, {
modelName: string;
templateName: string;
}> | Request<'modelTemplateRename', 6, {
modelName: string;
newTemplateName: string;
oldTemplateName: string;
}> | Request<'modelTemplateReposition', 6, {
index: number;
modelName: string;
templateName: string;
}> | Request<'modelTemplates', 6, {
modelName: string;
}, Record<string, {
[key: string]: string;
Back: string;
Front: string;
}>> | Request<'updateModelStyling', 6, {
model: {
css: string;
name: string;
};
}> | Request<'updateModelTemplates', 6, {
model: {
name: string;
templates: Record<string, {
Back?: string;
Front?: string;
}>;
};
}>;
//#endregion
//#region src/types/statistic.d.ts
type ReviewStatisticTuple = [reviewTime: number, cardID: number, usn: number, buttonPressed: number, newInterval: number, previousInterval: number, newFactor: number, reviewDuration: number, reviewType: number];
type StatisticRequests = Request<'cardReviews', 6, {
deck: string;
startID: number;
}, ReviewStatisticTuple[]> | Request<'getCollectionStatsHTML', 6, {
wholeCollection: boolean;
}, string> | Request<'getLatestReviewID', 6, {
deck: string;
}, number> | Request<'getNumCardsReviewedByDay', 6, never, Array<[string, number]>> | Request<'getNumCardsReviewedToday', 6, never, number> | Request<'getReviewsOfCards', 6, {
cards: string[];
}, Record<string, Array<{
/** ButtonPressed */
ease: number;
/** NewFactor */
factor: number;
/** ReviewTime */
id: number;
/** NewInterval */
ivl: number;
/** PreviousInterval */
lastIvl: number;
/** ReviewDuration */
time: number;
/** ReviewType */
type: number;
/** Usn */
usn: number;
}>>> | Request<'insertReviews', 6, {
reviews: ReviewStatisticTuple[];
}>;
//#endregion
//#region src/types/shared.d.ts
/**
* Abstract wrapper over an Anki Connect action / response
*/
type Request<Action extends string, Version extends AnkiConnectVersion, Params = never, Result = null> = {
action: Action;
params: Params;
response: {
error: null | string;
result: Result;
};
version: Version;
};
/**
* Requests
*/
type Requests = CardRequests | DeckRequests | GraphicalRequests | MediaRequests | MiscellaneousRequests | ModelRequests | NoteRequests | StatisticRequests;
type AnkiConnectVersion = 6;
type ParamsForAction<T extends Requests['action']> = Extract<Requests, {
action: T;
}>['params'];
//#endregion
//#region src/types/card.d.ts
type CardBrowserColumns = 'answer' | 'cardDue' | 'cardEase' | 'cardIvl' | 'cardLapses' | 'cardMod' | 'cardReps' | 'deck' | 'note' | 'noteCrt' | 'noteFld' | 'noteMod' | 'noteTags' | 'question' | 'template' | (string & {});
type CardValueKeys = 'data' | 'did' | 'due' | 'factor' | 'flags' | 'id' | 'ivl' | 'lapses' | 'left' | 'mod' | 'odid' | 'odue' | 'ord' | 'queue' | 'reps' | 'type' | 'usn';
type CardInfo = {
answer: string;
buttons?: number[];
cardId: number;
css: string;
deckName: string;
due: number;
fieldOrder: number;
fields: Record<string, {
order: number;
value: string;
}>;
interval: number;
lapses: number;
left: number;
mod: number;
modelName: string;
nextReviews: string[];
note: number;
ord: number;
question: string;
queue: number;
reps: number;
template: string;
type: number;
};
type CardRequests = Request<'answerCards', 6, {
answers: Array<{
cardId: number;
ease: number;
}>;
}, boolean[]> | Request<'areDue', 6, {
cards: number[];
}, boolean[]> | Request<'areSuspended', 6, {
cards: number[];
}, Array<boolean | null>> | Request<'cardsInfo', 6, {
cards: number[];
}, CardInfo[]> | Request<'cardsModTime', 6, {
cards: number[];
}, {
cardId: number;
mod: number;
}> | Request<'cardsToNotes', 6, {
cards: number[];
}, number[]> | Request<'findCards', 6, {
query: string;
}, number[]> | Request<'forgetCards', 6, {
cards: number[];
}> | Request<'getEaseFactors', 6, {
cards: number[];
}, number[]> | Request<'getIntervals', 6, {
cards: number[];
complete?: boolean;
}, number[] | number[][]> | Request<'relearnCards', 6, {
cards: number[];
}> | Request<'setDueDate', 6, {
cards: number[];
days: string;
}, boolean> | Request<'setEaseFactors', 6, {
cards: number[];
easeFactors: number[];
}, boolean[]> | Request<'setSpecificValueOfCard', 6, {
card: number;
keys: CardValueKeys[];
newValues: string[];
}, boolean[]> | Request<'suspend', 6, {
cards: number[];
}, boolean> | Request<'suspended', 6, {
card: number;
}, boolean> | Request<'unsuspend', 6, {
cards: number[];
}, boolean>;
//#endregion
//#region src/client.d.ts
/**
* Subset of built-in Fetch interface that's actually used by Anki, for ease of
* external re-implementation when passing a custom fetch function to
* YankiClient.
*/
type YankiFetchAdapter = (input: string, init?: {
body?: string;
headers?: Record<string, string>;
method?: string;
mode?: RequestMode;
}) => Promise<undefined | {
headers: Headers | Record<string, string>;
json(): Promise<any>;
status: number;
}>;
/** Optional options to pass when instantiating a new YankiConnect instance. */
type YankiConnectOptions = {
/**
* Attempt to open the desktop Anki.app if it's not already running.
*
* - `true` will always attempt to open Anki _when a request is made_. This
* might introduce significant latency on the first launch.
* - `false` will never attempt to open Anki. Requests will fail until
* something or someone else opens the Anki app.
* - `immediately` is a special option that will open Anki when the client is
* instantiated.
*
* The Anki desktop app must be running for the client and the underlying
* Anki-Connect service to work.
*
* Currently supported on macOS only.
*
* The client does not attempt to close the app.
* @default false
*/
autoLaunch: 'immediately' | boolean;
/**
* Advanced option to customize the resource fetch implementation used to make requests to Anki-Connect.
*
* Note that the signature reflects the subset of the built-in Fetch interface that's actually used by yanki-connect.
*
* The exact signature of this option is subject to change in the future.
* @default fetch
*/
fetchAdapter: undefined | YankiFetchAdapter;
/**
* Host where the Anki-Connect service is running.
* @default 'http://127.0.0.1'
*/
host: string;
/**
* Anki-Connect security key (optional)
* @default undefined
*/
key: string | undefined;
/**
* Port where the Anki-Connect service is running.
* @default 8765
*/
port: number;
/**
* Anki-Connect API version.
*
* Only API version 6 is supported for now.
* @default 6
*/
version: AnkiConnectVersion;
};
declare const yankiModels: [{
readonly cardTemplates: [{
readonly Back: "{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}";
readonly Front: "{{Front}}";
readonly YankiNamespace: "{{YankiNamespace}}";
}];
readonly inOrderFields: ["Front", "Back", "YankiNamespace"];
readonly modelName: "Yanki - Basic";
}, {
readonly cardTemplates: [{
readonly Back: "{{cloze:Front}}<br>\n{{Back}}";
readonly Front: "{{cloze:Front}}";
readonly YankiNamespace: "{{YankiNamespace}}";
}];
readonly inOrderFields: ["Front", "Back", "YankiNamespace"];
readonly isCloze: true;
readonly modelName: "Yanki - Cloze";
}, {
readonly cardTemplates: [{
readonly Back: "{{Front}}\n\n<hr id=answer>\n\n{{type:Back}}";
readonly Front: "{{Front}}\n\n{{type:Back}}";
readonly YankiNamespace: "{{YankiNamespace}}";
}];
readonly inOrderFields: ["Front", "Back", "YankiNamespace"];
readonly modelName: "Yanki - Basic (type in the answer)";
}, {
readonly cardTemplates: [{
readonly Back: "{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}{{#Extra}}\n\n<hr>\n\n{{Extra}}{{/Extra}}";
readonly Front: "{{Front}}";
readonly YankiNamespace: "{{YankiNamespace}}";
}, {
readonly Back: "{{FrontSide}}\n\n<hr id=answer>\n\n{{Front}}{{#Extra}}\n\n<hr>\n\n{{Extra}}{{/Extra}}";
readonly Front: "{{Back}}";
readonly YankiNamespace: "{{YankiNamespace}}";
}];
readonly inOrderFields: ["Front", "Back", "Extra", "YankiNamespace"];
readonly modelName: "Yanki - Basic (and reversed card with extra)";
}];
type YankiModelName = (typeof yankiModels)[number]['modelName'];
type YankiNote = Simplify<Omit<ParamsForAction<'addNote'>['note'], 'fields' | 'modelName' | 'options'> & {
cards?: number[];
fields: {
Back: string;
Extra?: string;
Front: string;
YankiNamespace: string;
};
modelName: YankiModelName;
noteId: number | undefined;
}>;
type FetchAdapter = YankiFetchAdapter;
type ManageFilenames = 'off' | 'prompt' | 'response';
type SyncMediaAssets = 'all' | 'local' | 'off' | 'remote';
type FileAdapter = {
readFile(filePath: string): Promise<string>;
readFileBuffer(filePath: string): Promise<Uint8Array>;
rename(oldPath: string, newPath: string): Promise<void>;
stat(filePath: string): Promise<{
ctimeMs: number;
mtimeMs: number;
size: number;
}>;
writeFile(filePath: string, data: string): Promise<void>;
};
type GlobalOptions = {
/**
* Used for wiki link resolution
*/
allFilePaths: string[];
ankiConnectOptions: YankiConnectOptions;
/**
* Automatically sync any changes to AnkiWeb after Yanki has finished syncing
* locally. If false, only local Anki data is updated and you must manually
* invoke a sync to AnkiWeb. This is the equivalent of pushing the "sync"
* button in the Anki app.
*/
ankiWeb: boolean;
/** Override where "/" should resolve to... useful in Obsidian to set the vault path as the "root" */
basePath: string | undefined;
/** Run Anki's "Check Database" command after sync updates that might produce card corruption */
checkDatabase: boolean;
cwd: string;
dryRun: boolean;
/**
* Exposed for Obsidian, currently only used for getting URL content hashes
* and inferring MIME types of URLs without extensions.
* Note that ankiConnectOptions ALSO has a fetch adapter option specifically
* for communicating with Anki-Connect.
*/
fetchAdapter: FetchAdapter | undefined;
fileAdapter: FileAdapter | undefined;
manageFilenames: ManageFilenames;
/** Only applies if manageFilenames is `true`. Will _not_ truncate user-specified file names in other cases. */
maxFilenameLength: number;
namespace: string;
/** Ensures that wiki-style links work correctly */
obsidianVault: string | undefined;
/** Exposed for testing only */
resolveUrls: boolean;
/**
* Whether to treat single newlines in Markdown as line breaks in the
* resulting HTML (Obsidian has an application-level setting for this)
*/
strictLineBreaks: boolean;
/** Only consider exact noteId matches between the local and remote copies to be equivalent, don't match local notes with "orphaned" remote notes based on content */
strictMatching: boolean;
/** Sync image, video, and audio assets to Anki's media storage system */
syncMediaAssets: SyncMediaAssets;
};
type CleanOptions = Pick<GlobalOptions, 'ankiConnectOptions' | 'ankiWeb' | 'dryRun' | 'namespace'>;
declare const defaultCleanOptions: CleanOptions;
type CleanResult = Simplify<Pick<GlobalOptions, 'ankiWeb' | 'dryRun' | 'namespace'> & {
deletedDecks: string[];
deletedMedia: string[];
deletedNotes: YankiNote[];
duration: number;
}>;
/**
* Deletes all remote notes in Anki associated with the given namespace.
*
* Use with significant caution. Mostly useful for testing.
* @returns The IDs of the notes that were deleted
* @throws {Error} If Anki is unreachable or another error occurs during deletion.
*/
declare function cleanNotes(options?: PartialDeep<CleanOptions>): Promise<CleanResult>;
declare function formatCleanResult(result: CleanResult, verbose?: boolean): string;
type ListOptions = Pick<GlobalOptions, 'ankiConnectOptions' | 'namespace'>;
declare const defaultListOptions: ListOptions;
type ListResult = {
duration: number;
namespace: string;
notes: YankiNote[];
};
/**
* Description List notes currently in Anki...
*/
declare function listNotes(options?: PartialDeep<ListOptions>): Promise<ListResult>;
declare function formatListResult(result: ListResult): string;
type LoadOptions = Pick<GlobalOptions, 'allFilePaths' | 'basePath' | 'fetchAdapter' | 'fileAdapter' | 'namespace' | 'obsidianVault' | 'strictLineBreaks' | 'syncMediaAssets'>;
type LocalNote = {
filePath: string;
filePathOriginal: string;
markdown: string;
note: YankiNote;
};
type RenameNotesOptions = Pick<GlobalOptions, 'dryRun' | 'fileAdapter' | 'manageFilenames' | 'maxFilenameLength'
/** Included because this can technically change the content of the "first line" of a card */
| 'strictLineBreaks'>;
type RenameFilesResult = {
dryRun: boolean;
notes: LocalNote[];
};
type RenameFilesOptions = Simplify<LoadOptions & RenameNotesOptions>;
declare const defaultRenameFilesOptions: RenameFilesOptions;
/**
* Currently used for testing and by `yanki-obsidian`.
*/
declare function renameFiles(allLocalFilePaths: string[], options: Partial<RenameFilesOptions>): Promise<RenameFilesResult>;
type SetStyleOptions = Pick<GlobalOptions, 'ankiConnectOptions' | 'ankiWeb' | 'dryRun'> & {
css: string;
};
declare const defaultSetStyleOptions: SetStyleOptions;
type SetStyleResult = Simplify<Pick<GlobalOptions, 'ankiWeb' | 'dryRun'> & {
duration: number;
models: Array<{
action: 'unchanged' | 'updated';
name: string;
}>;
}>;
type GetStyleOptions = Pick<GlobalOptions, 'ankiConnectOptions'>;
declare const defaultGetStyleOptions: GetStyleOptions;
declare function getStyle(options: PartialDeep<GetStyleOptions>): Promise<string>;
declare function setStyle(options?: PartialDeep<SetStyleOptions>): Promise<SetStyleResult>;
declare function formatSetStyleResult(result: SetStyleResult, verbose?: boolean): string;
type SyncedNote = {
action: 'ankiUnreachable' | 'created' | 'deleted' | 'matched' | 'unchanged' | 'updated';
note: YankiNote;
};
type SyncNotesOptions = Pick<GlobalOptions, 'ankiConnectOptions' | 'ankiWeb' | 'checkDatabase' | 'dryRun' | 'namespace' | 'strictMatching'>;
declare const defaultSyncNotesOptions: SyncNotesOptions;
type SyncNotesResult = Simplify<Pick<GlobalOptions, 'ankiWeb' | 'dryRun' | 'namespace'> & {
deletedDecks: string[];
deletedMedia: string[];
duration: number;
fixedDatabase: boolean;
synced: SyncedNote[];
}>;
/**
* Syncs local notes to Anki.
* @param allLocalNotes All the YankiNotes to sync
* @returns The synced notes (with new IDs where applicable), plus some stats
* about the sync
* @throws {Error} For various reasons...
*/
declare function syncNotes(allLocalNotes: YankiNote[], options?: PartialDeep<SyncNotesOptions>): Promise<SyncNotesResult>;
type SyncFilesOptions = Simplify<Pick<GlobalOptions, 'allFilePaths' | 'basePath' | 'fetchAdapter' | 'fileAdapter' | 'manageFilenames' | 'maxFilenameLength' | 'obsidianVault' | 'strictLineBreaks' | 'syncMediaAssets'> & SyncNotesOptions>;
declare const defaultSyncFilesOptions: SyncFilesOptions;
type SyncedFile = Simplify<SyncedNote & {
filePath: string | undefined;
filePathOriginal: string | undefin