The collection document (col.data)
Every table and column ankipack models, which columns hold raw protobuf and how to decode them, for anything the typed API does not cover.
The collection document (col.data)
col.data is the whole collection as rows, one type per table and one property
per column. It is the escape hatch for anything the typed API does not cover:
review history, card scheduling state, collection configuration, deck
descriptions, and any column ankipack has no method for.
For example, here is how you would suspend every card of one note:
import { readFile, writeFile } from "node:fs/promises";import initSqlJs from "sql.js";import { Collection } from "ankipack";const SQL = await initSqlJs();const col = Collection.open(await readFile("deck.apkg"), SQL);for (const card of col.data.cards.filter((c) => c.nid === 1700000000001)) { card.queue = -1;}await writeFile("deck.apkg", await col.toUint8Array(SQL));- Cards reach their note through
nid. Nothing gives you a note's cards directly, which is the shape of most work down here.
Nothing read is summarised or dropped, so an untouched round trip returns every row and every column value unchanged, protobuf blobs included, even where they were written by a newer Anki than ankipack models.
That holds for a schema 18 package. Opening a schema 11 one converts it first, so the round trip is equivalent rather than identical. See the format page.
Editing here is checked when the file is written rather than when the
assignment happens, and toUint8Array throws invalid-document naming the
offending row. The reasoning for that split is on the
validation page.
CollectionData
| Property | Type | Table |
|---|---|---|
col |
ColRow |
col, a single row |
notes |
NoteRow[] |
notes |
cards |
CardRow[] |
cards |
revlog |
RevlogRow[] |
revlog |
graves |
GraveRow[] |
graves |
deckConfig |
DeckConfigRow[] |
deck_config |
config |
ConfigRow[] |
config |
tags |
TagRow[] |
tags |
notetypes |
NotetypeRow[] |
notetypes |
fields |
FieldRow[] |
fields |
templates |
TemplateRow[] |
templates |
decks |
DeckRow[] |
decks |
media |
MediaFile[] |
The archive's media entries |
Column names are Anki's own, not renamed, so
Anki's own schema
is readable against this directly. It is schema11.sql plus the numbered
upgrade files rather than one schema 18 file. Two exceptions to the naming:
snake_case column names become camelCase properties, as in mtime_secs to
mtimeSecs, and the config table's KEY column is the key property.
Rows join on ids. NoteRow.mid names a NotetypeRow; FieldRow.ntid and
TemplateRow.ntid name the note type they belong to; CardRow.nid names its
note and CardRow.did its deck.
Columns that are not what they look like
A handful of columns carry encodings rather than plain values, and reaching for them naively produces wrong results rather than errors.
| Column | Looks like | Actually holds |
|---|---|---|
NoteRow.flds |
A string | Every field value, joined by U+001F |
NoteRow.tags |
A string | Space-delimited, with a leading and trailing space, or empty |
NoteRow.sfld |
Declared integer |
The sort field's text. Typed SortField, which is string | number |
DeckRow.name |
Foo::Bar |
Components separated by U+001F, not :: |
ColRow.conf, models, decks, dconf, tags |
Columns of their own | Vestigial at schema 18. Anki moved their contents into tables |
ConfigRow.val |
A value | Bytes, usually JSON, opaque by contract |
deckNames() converts DeckRow.name to the display form, and the typed API
accepts :: names everywhere.
Protobuf columns
Schema 18 stores structured settings as encoded protobuf. ankipack keeps them as bytes and decodes on demand, so an untouched value is re-encoded exactly as it arrived.
| Column | Message |
|---|---|
DeckConfigRow.config |
DeckConfig.Config |
NotetypeRow.config |
Notetype.Config |
FieldRow.config |
Notetype.Field.Config |
TemplateRow.config |
Notetype.Template.Config |
DeckRow.common |
Deck.Common |
DeckRow.kind |
Deck.KindContainer, a oneof wrapping Deck.Normal or Deck.Filtered |
TagRow.config |
Always null. Anki declares the column but defines no message for it |
Reading one needs a protobuf decoder. ankipack does not re-export the generated
message types, so decoding them yourself means depending on
@bufbuild/protobuf and on Anki's .proto definitions directly. For anything
that has a typed method, use the method: it re-encodes correctly and updates
the surrounding rows.
Cards, review log and graves
CardRow
The scheduling state.
| Column | Holds |
|---|---|
id, nid, did |
Card id, its note, its deck |
ord |
Which template of the note type this card is |
type, queue |
Anki's card state. Negative queue is out of circulation: -1 suspended, -2 and -3 buried |
due |
A position for new cards, a day number for review cards, a unix timestamp while learning |
ivl |
Interval in days |
factor |
Ease in permille |
reps, lapses, left |
Review counters and remaining learning steps |
odue, odid |
Original due and deck, for a card currently in a filtered deck |
mod, usn |
Modified time in seconds, and sync state. -1 means unsynced |
flags, data |
Flag colour, and a JSON string Anki uses for scheduler extras |
A card ankipack creates is unstudied: type and queue zero, ivl, factor,
reps, lapses, left, odue and odid all zero, usn at -1 so a
syncing client picks it up, and due set to its position in the new queue.
RevlogRow
One row per answered card.
| Column | Holds |
|---|---|
id |
The review's timestamp in milliseconds, and its primary key |
cid |
The card reviewed |
ease |
Which button was pressed, 1 to 4. 0 means a manual reschedule |
ivl, lastIvl |
Interval after and before, in days. Negative means seconds |
factor |
Ease factor after the review, in permille |
time |
Milliseconds the answer took |
type |
Review kind: learn, review, relearn, filtered, manual, rescheduled |
usn |
Sync state |
GraveRow
Deletions, so a syncing client does not resurrect them.
| Column | Holds |
|---|---|
oid |
The deleted object's id |
type |
0 for a card, 1 for a note, 2 for a deck |
usn |
Sync state |
Anki's .apkg importer never reads this table, so graves affect syncing and
.colpkg restores rather than anyone importing your deck.
What is not modelled
ColRow's JSON columns are carried verbatim rather than parsed. A collection
Anki made carries an empty string in each, because it creates every collection
at schema 11 and immediately upgrades, which blanks them. A package
ankipack builds carries "{}". Nothing at schema 18 reads either, so if you are
looking for a collection setting, it is a ConfigRow, not ColRow.conf.
The media archive is modelled as name and bytes only. Its sizes and SHA1s are computed at write time rather than stored.
Ça a marché sur votre configuration ?
Pas encore évalué