rmapi-js 11.2.0 → 12.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/raw.d.ts CHANGED
@@ -25,11 +25,19 @@ export type RequestMethod = "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "OPTIO
25
25
  export type UploadMimeType = "application/pdf" | "application/epub+zip" | "folder";
26
26
  /** the schema version */
27
27
  export type SchemaVersion = 3 | 4;
28
- /** an simple entry without any extra information */
29
- export interface SimpleEntry {
30
- /** the document id */
28
+ /**
29
+ * a reference to stored cloud data: an id paired with the hash of its state
30
+ *
31
+ * This is the canonical way to point at something in the cloud. The `id` names
32
+ * *what*: a document's uuid at the high level, or a stored file name (like
33
+ * `<id>.content`) at the low level. The `hash` names *which version*, and
34
+ * changes every time that data mutates. Reads take one of these, and mutations
35
+ * return a fresh one with the new hash.
36
+ */
37
+ export interface ItemRef {
38
+ /** the id of the referenced data: a document uuid, or a stored file name */
31
39
  id: string;
32
- /** the document hash */
40
+ /** the hash of the referenced state */
33
41
  hash: string;
34
42
  }
35
43
  /**
@@ -40,13 +48,9 @@ export interface SimpleEntry {
40
48
  * files, the high level entry will have the same hash and id as the low-level
41
49
  * entry for that collection.
42
50
  */
43
- export interface RawEntry {
51
+ export interface RawEntry extends ItemRef {
44
52
  /** 80000000 for schema 3 collection type or 0 for schema 4 or schema 3 files or */
45
53
  type: 80000000 | 0;
46
- /** the hash of the collection this points to */
47
- hash: string;
48
- /** the unique id of the collection */
49
- id: string;
50
54
  /** the number of subfiles */
51
55
  subfiles: number;
52
56
  /** the total size of everything in the collection */
@@ -325,6 +329,8 @@ export interface LegacyDocumentContent extends CommonDocumentContent {
325
329
  * metadata about it.
326
330
  */
327
331
  export interface TemplateContent {
332
+ /** the template's own id, when present */
333
+ id?: string;
328
334
  /** the template name */
329
335
  name: string;
330
336
  /** the template's author */
@@ -334,7 +340,7 @@ export interface TemplateContent {
334
340
  /** category names this template belongs to (eg: "Planning", "Productivity") */
335
341
  categories: string[];
336
342
  /** labels associated with this template (eg: "Project management") */
337
- labels: string[];
343
+ labels?: string[];
338
344
  /** the orientation of this template */
339
345
  orientation: "portrait" | "landscape";
340
346
  /** semantic version for this template */
@@ -347,10 +353,15 @@ export interface TemplateContent {
347
353
  * - `rm2`: reMarkable 2
348
354
  * - `rmPP`: reMarkable Paper Pro
349
355
  */
350
- supportedScreens: ("rm2" | "rmPP")[];
351
- /** constant values used by the commands in `items` */
356
+ supportedScreens?: ("rm2" | "rmPP")[];
357
+ /**
358
+ * named constants used by the `items` DSL
359
+ *
360
+ * A value is either a literal number or an expression string that references
361
+ * other constants and `templateWidth`, e.g. `"templateWidth - (offsetX * 2)"`.
362
+ */
352
363
  constants?: {
353
- [name: string]: number;
364
+ [name: string]: number | string;
354
365
  }[];
355
366
  /** the template definition, an SVG-like DSL in JSON */
356
367
  items: object[];
@@ -368,7 +379,7 @@ export interface Metadata {
368
379
  /** [speculative] true if the item has been actually deleted */
369
380
  deleted?: boolean;
370
381
  /** the last modify time, the string of the epoch timestamp */
371
- lastModified: string;
382
+ lastModified?: string;
372
383
  /** the last opened epoch timestamp, isn't defined for CollectionType */
373
384
  lastOpened?: string;
374
385
  /** the last page opened, isn't defined for CollectionType, starts at 0*/
@@ -410,6 +421,10 @@ export interface Metadata {
410
421
  }
411
422
  /** parse and validate the json text of a `.metadata` file */
412
423
  export declare function parseMetadata(text: string): Metadata;
424
+ type AuthedFetch = (method: RequestMethod, url: string, init?: {
425
+ body?: string | Uint8Array;
426
+ headers?: Record<string, string>;
427
+ }) => Promise<Response>;
413
428
  /**
414
429
  * access to the low-level reMarkable api
415
430
  *
@@ -469,7 +484,10 @@ export declare function parseMetadata(text: string): Metadata;
469
484
  *
470
485
  * Generally all hashes are 64 character hex strings, and all ids are uuid4.
471
486
  */
472
- export interface RawRemarkableApi {
487
+ export declare class RawRemarkable {
488
+ #private;
489
+ constructor(authedFetch: AuthedFetch, cache: Map<string, string | null>, rawHost: string, uploadHost: string);
490
+ /** make an authorized request to remarkable */
473
491
  /**
474
492
  * gets the root hash and the current generation
475
493
  *
@@ -482,66 +500,62 @@ export interface RawRemarkableApi {
482
500
  /**
483
501
  * get the raw binary data associated with a hash
484
502
  *
485
- * @param fileName - the logical file name (`<id>.<ext>` for files, or
486
- * `<id>.docSchema` / `"root.docSchema"` for entry indexes). reMarkable
487
- * validates this against the rm-filename header.
488
- * @param hash - the hash to get the data for
503
+ * @param ref - a reference to the stored file. Its `id` is the logical file
504
+ * name (`<id>.<ext>` for files, or `<id>.docSchema` / `"root.docSchema"`
505
+ * for entry indexes), which reMarkable validates against the rm-filename
506
+ * header. Sub-entries from {@link getEntries | `getEntries`} can be passed
507
+ * directly.
489
508
  * @returns the data
490
509
  */
491
- getHash(fileName: string, hash: string): Promise<Uint8Array>;
510
+ getHash({ id: fileName, hash }: ItemRef): Promise<Uint8Array>;
492
511
  /**
493
512
  * get raw text data associated with a hash
494
513
  *
495
514
  * We assume text data are small, and so cache the entire text. If you want to
496
515
  * avoid this, use {@link getHash | `getHash`} combined with a TextDecoder.
497
-
498
- * @param fileName - the logical file name (see {@link getHash})
499
- * @param hash - the hash to get text for
516
+ *
517
+ * @param ref - a reference to the stored file (see {@link getHash})
500
518
  * @returns the text
501
519
  */
502
- getText(fileName: string, hash: string): Promise<string>;
520
+ getText({ id: fileName, hash }: ItemRef): Promise<string>;
503
521
  /**
504
522
  * get the entries associated with a list hash
505
523
  *
506
524
  * A list hash is the root hash, or any hash with the type 80000000. NOTE
507
525
  * these are hashed differently than files.
508
-
509
- * @param fileName - `"root.docSchema"` for the root, or `"<id>.docSchema"`
510
- * for a sub-document's entry index
511
- * @param hash - the hash to get entries for
526
+ *
527
+ * @param ref - a reference whose `id` is `"root.docSchema"` for the root, or
528
+ * `"<id>.docSchema"` for a sub-document's entry index
512
529
  * @returns the entries
513
530
  */
514
- getEntries(fileName: string, hash: string): Promise<Entries>;
531
+ getEntries(ref: ItemRef): Promise<Entries>;
515
532
  /**
516
533
  * get the parsed and validated `Content` of a content hash
517
534
  *
518
535
  * Use {@link getText | `getText`} combined with `JSON.parse` to bypass
519
536
  * validation
520
-
521
- * @param fileName - typically `"<id>.content"`
522
- * @param hash - the hash to get Content for
537
+ *
538
+ * @param ref - a reference to the stored file, typically `"<id>.content"`
523
539
  * @returns the content
524
540
  */
525
- getContent(fileName: string, hash: string): Promise<Content>;
541
+ getContent(ref: ItemRef): Promise<Content>;
526
542
  /**
527
543
  * get the parsed and validated `Metadata` of a metadata hash
528
544
  *
529
545
  * Use {@link getText | `getText`} combined with `JSON.parse` to bypass
530
546
  * validation
531
-
532
- * @param fileName - typically `"<id>.metadata"`
533
- * @param hash - the hash to get Metadata for
547
+ *
548
+ * @param ref - a reference to the stored file, typically `"<id>.metadata"`
534
549
  * @returns the metadata
535
550
  */
536
- getMetadata(fileName: string, hash: string): Promise<Metadata>;
551
+ getMetadata(ref: ItemRef): Promise<Metadata>;
537
552
  /**
538
553
  * get the parsed reMarkable lines (`.rm`) drawing of a page hash
539
-
540
- * @param fileName - typically `"<id>/<pageid>.rm"`
541
- * @param hash - the hash to get the page for
554
+ *
555
+ * @param ref - a reference to the stored file, typically `"<id>/<pageid>.rm"`
542
556
  * @returns the parsed page
543
557
  */
544
- getRm(fileName: string, hash: string): Promise<RmPage>;
558
+ getRm(ref: ItemRef): Promise<RmPage>;
545
559
  /**
546
560
  * the same as {@link putFile | `putFile`} but rendering an `RmPage` to `.rm`
547
561
  * bytes
@@ -585,7 +599,7 @@ export interface RawRemarkableApi {
585
599
  */
586
600
  putFile(fileName: string, bytes: Uint8Array): Promise<[RawEntry, Promise<void>]>;
587
601
  /** the same as {@link putFile | `putFile`} but with caching for text */
588
- putText(fileName: string, content: string): Promise<[RawEntry, Promise<void>]>;
602
+ putText(fileName: string, text: string): Promise<[RawEntry, Promise<void>]>;
589
603
  /** the same as {@link putText | `putText`} but with extra validation for Content */
590
604
  putContent(fileName: string, content: Content): Promise<[RawEntry, Promise<void>]>;
591
605
  /** the same as {@link putText | `putText`} but with extra validation for Metadata */
@@ -624,10 +638,10 @@ export interface RawRemarkableApi {
624
638
  * @param visibleName - the name of the file as it should appear on the reMarkable
625
639
  * @param bytes - the bytes of the file to upload
626
640
  * @param mime - the mime type of the file to upload
627
-
641
+
628
642
  * @returns a simple entry with the id and hash of the uploaded file
629
643
  */
630
- uploadFile(visibleName: string, bytes: Uint8Array, mime: UploadMimeType): Promise<SimpleEntry>;
644
+ uploadFile(visibleName: string, bytes: Uint8Array, mime: UploadMimeType): Promise<ItemRef>;
631
645
  /**
632
646
  * dump the current cache to a string to preserve between session
633
647
  *
@@ -637,30 +651,4 @@ export interface RawRemarkableApi {
637
651
  /** completely clear the cache */
638
652
  clearCache(): void;
639
653
  }
640
- type AuthedFetch = (method: RequestMethod, url: string, init?: {
641
- body?: string | Uint8Array;
642
- headers?: Record<string, string>;
643
- }) => Promise<Response>;
644
- export declare class RawRemarkable implements RawRemarkableApi {
645
- #private;
646
- constructor(authedFetch: AuthedFetch, cache: Map<string, string | null>, rawHost: string, uploadHost: string);
647
- /** make an authorized request to remarkable */
648
- getRootHash(): Promise<[string, number, SchemaVersion]>;
649
- getHash(fileName: string, hash: string): Promise<Uint8Array>;
650
- getText(fileName: string, hash: string): Promise<string>;
651
- getEntries(fileName: string, hash: string): Promise<Entries>;
652
- getContent(fileName: string, hash: string): Promise<Content>;
653
- getMetadata(fileName: string, hash: string): Promise<Metadata>;
654
- getRm(fileName: string, hash: string): Promise<RmPage>;
655
- putRm(fileName: string, page: RmPageV5): Promise<[RawEntry, Promise<void>]>;
656
- putRootHash(hash: string, generation: number, broadcast?: boolean): Promise<[string, number]>;
657
- putFile(fileName: string, bytes: Uint8Array): Promise<[RawEntry, Promise<void>]>;
658
- putText(fileName: string, text: string): Promise<[RawEntry, Promise<void>]>;
659
- putContent(fileName: string, content: Content): Promise<[RawEntry, Promise<void>]>;
660
- putMetadata(fileName: string, metadata: Metadata): Promise<[RawEntry, Promise<void>]>;
661
- putEntries(id: string, entries: readonly RawEntry[], schemaVersion: SchemaVersion): Promise<[RawEntry, Promise<void>]>;
662
- uploadFile(visibleName: string, bytes: Uint8Array, mime: UploadMimeType): Promise<SimpleEntry>;
663
- dumpCache(): string;
664
- clearCache(): void;
665
- }
666
- export {};
654
+ export type { RawRemarkable as RawRemarkableApi };
package/dist/raw.js CHANGED
@@ -174,19 +174,22 @@ const legacyDocumentContent = commonDocumentContent
174
174
  .passthrough();
175
175
  const templateContent = z
176
176
  .object({
177
+ id: z.string().optional(),
177
178
  name: z.string(),
178
179
  author: z.string(),
179
180
  iconData: z.string(),
180
181
  categories: z.array(z.string()),
181
- labels: z.array(z.string()),
182
+ labels: z.array(z.string()).optional(),
182
183
  orientation: z.enum(["portrait", "landscape"]),
183
184
  templateVersion: z.string(),
184
- supportedScreens: z.array(z.enum(["rm2", "rmPP"])),
185
- constants: z.array(z.record(z.string(), z.number().int())).optional(),
185
+ supportedScreens: z.array(z.enum(["rm2", "rmPP"])).optional(),
186
+ constants: z
187
+ .array(z.record(z.string(), z.union([z.number(), z.string()])))
188
+ .optional(),
186
189
  items: z.array(z.unknown()),
187
190
  formatVersion: z.number().int().nonnegative().optional(),
188
191
  })
189
- .strict();
192
+ .passthrough();
190
193
  // content payloads aren't discriminable (legacy/modern differ only by tags
191
194
  // element type), so this is an ordered union: the first matching variant wins
192
195
  const content = z.union([
@@ -198,7 +201,7 @@ const content = z.union([
198
201
  ]);
199
202
  const metadata = z
200
203
  .object({
201
- lastModified: z.string(),
204
+ lastModified: z.string().optional(),
202
205
  parent: z.string(),
203
206
  pinned: z.boolean(),
204
207
  type: z.enum(["DocumentType", "CollectionType", "TemplateType"]),
@@ -233,7 +236,7 @@ const rootHash = z
233
236
  schemaVersion: z.number().int().nonnegative(),
234
237
  })
235
238
  .passthrough();
236
- const nativeSimpleEntry = z
239
+ const nativeItemRef = z
237
240
  .object({
238
241
  docID: z.string(),
239
242
  hash: z.string(),
@@ -267,6 +270,65 @@ function parseRawEntryLine(line) {
267
270
  throw new Error(`line '${line}' was not formatted correctly`);
268
271
  }
269
272
  }
273
+ /**
274
+ * access to the low-level reMarkable api
275
+ *
276
+ * This class gives more granualar access to the reMarkable cloud, but is more
277
+ * dangerous.
278
+ *
279
+ * ## Overview
280
+ *
281
+ * reMarkable uses an immutable file system, where each file is referenced by
282
+ * the 32 byte sha256 hash of its contents. Each file also has an id used to
283
+ * keep track of updates, so to "update" a file, you upload a new file, and
284
+ * change the hash associated with it's id.
285
+ *
286
+ * Each "item" (a document or a collection) is actually a list of files.
287
+ * The whole reMarkable state is then a list of these lists. Finally, the hash
288
+ * of that list is called the rootHash. To update anything, you have to update
289
+ * the root hash to point to a new list of updated items.
290
+ *
291
+ * This can be dangerous, as corrupting the root hash can destroy all of your
292
+ * files. It is therefore highly recommended to save your current root hash
293
+ * ({@link getRootHash | `getRootHash`}) before using this api to attempt file
294
+ * writes, so you can recover a previous "snapshot" should anything go wrong.
295
+ *
296
+ * ## Items
297
+ *
298
+ * Each item is a collection of individual files. Using
299
+ * {@link getEntries | `getEntries`} on the root hash will give you a list
300
+ * entries that correspond to items. Using `getEntries` on any of those items
301
+ * will get you the files that make up that item.
302
+ *
303
+ * The documented files are:
304
+ * - `<docid>.pdf` - a raw pdf document
305
+ * - `<docid>.epub` - a raw epub document
306
+ * - `<docid>.content` - a json file roughly describing document properties (see {@link DocumentContent | `DocumentContent`})
307
+ * - `<docid>.metadata` - metadata about the document (see {@link Metadata | `Metadata`})
308
+ * - `<docid>.pagedata` - a text file where each line is the template of that page
309
+ * - `<docid>/<pageid>.rm` - [speculative] raw remarkable vectors, text, etc
310
+ * - `<docid>/<pageid>-metadata.json` - [speculative] metadata about the individual page
311
+ * - `<docid>.highlights/<pageid>.json` - [speculative] highlights on the page
312
+ *
313
+ * Some items will have both a `.pdf` and `.epub` file, likely due to preparing
314
+ * for export. Collections only have `.content` and `.metadata` files, with
315
+ * `.content` only containing tags.
316
+ *
317
+ * ## Caching
318
+ *
319
+ * Since everything is tied to the hash of it's contents, we can agressively
320
+ * cache results. We assume that text contents are "small" and so fully cache
321
+ * them, where as binary files we treat as large and only store that we know
322
+ * they exist to prevent future writes.
323
+ *
324
+ * By default, this only persists as long as the api instance is alive. However,
325
+ * for performance reasons, you should call {@link dumpCache | `dumpCache`} to
326
+ * persist the cache between sessions.
327
+ *
328
+ * @remarks
329
+ *
330
+ * Generally all hashes are 64 character hex strings, and all ids are uuid4.
331
+ */
270
332
  export class RawRemarkable {
271
333
  #authedFetch;
272
334
  #rawHost;
@@ -288,6 +350,14 @@ export class RawRemarkable {
288
350
  this.#uploadHost = uploadHost;
289
351
  }
290
352
  /** make an authorized request to remarkable */
353
+ /**
354
+ * gets the root hash and the current generation
355
+ *
356
+ * When calling `putRootHash`, you should pass the generation you got from
357
+ * this call. That way you tell reMarkable you're updating the previous state.
358
+ *
359
+ * @returns the root hash and the current generation
360
+ */
291
361
  async getRootHash() {
292
362
  const res = await this.#authedFetch("GET", `${this.#rawHost}/sync/v4/root`);
293
363
  const raw = await res.text();
@@ -312,7 +382,17 @@ export class RawRemarkable {
312
382
  const raw = await resp.arrayBuffer();
313
383
  return new Uint8Array(raw);
314
384
  }
315
- async getHash(fileName, hash) {
385
+ /**
386
+ * get the raw binary data associated with a hash
387
+ *
388
+ * @param ref - a reference to the stored file. Its `id` is the logical file
389
+ * name (`<id>.<ext>` for files, or `<id>.docSchema` / `"root.docSchema"`
390
+ * for entry indexes), which reMarkable validates against the rm-filename
391
+ * header. Sub-entries from {@link getEntries | `getEntries`} can be passed
392
+ * directly.
393
+ * @returns the data
394
+ */
395
+ async getHash({ id: fileName, hash }) {
316
396
  const cached = this.#cache.get(hash);
317
397
  if (cached != null) {
318
398
  const enc = new TextEncoder();
@@ -328,7 +408,16 @@ export class RawRemarkable {
328
408
  return res;
329
409
  }
330
410
  }
331
- async getText(fileName, hash) {
411
+ /**
412
+ * get raw text data associated with a hash
413
+ *
414
+ * We assume text data are small, and so cache the entire text. If you want to
415
+ * avoid this, use {@link getHash | `getHash`} combined with a TextDecoder.
416
+ *
417
+ * @param ref - a reference to the stored file (see {@link getHash})
418
+ * @returns the text
419
+ */
420
+ async getText({ id: fileName, hash }) {
332
421
  const cached = this.#cache.get(hash);
333
422
  if (cached != null) {
334
423
  return cached;
@@ -342,8 +431,18 @@ export class RawRemarkable {
342
431
  return res;
343
432
  }
344
433
  }
345
- async getEntries(fileName, hash) {
346
- const rawFile = await this.getText(fileName, hash);
434
+ /**
435
+ * get the entries associated with a list hash
436
+ *
437
+ * A list hash is the root hash, or any hash with the type 80000000. NOTE
438
+ * these are hashed differently than files.
439
+ *
440
+ * @param ref - a reference whose `id` is `"root.docSchema"` for the root, or
441
+ * `"<id>.docSchema"` for a sub-document's entry index
442
+ * @returns the entries
443
+ */
444
+ async getEntries(ref) {
445
+ const rawFile = await this.getText(ref);
347
446
  const [version, ...rest] = rawFile.slice(0, -1).split("\n");
348
447
  if (version === "3") {
349
448
  return { entries: rest.map(parseRawEntryLine) };
@@ -371,20 +470,50 @@ export class RawRemarkable {
371
470
  throw new Error(`schema version ${version} not supported`);
372
471
  }
373
472
  }
374
- async getContent(fileName, hash) {
375
- const raw = await this.getText(fileName, hash);
473
+ /**
474
+ * get the parsed and validated `Content` of a content hash
475
+ *
476
+ * Use {@link getText | `getText`} combined with `JSON.parse` to bypass
477
+ * validation
478
+ *
479
+ * @param ref - a reference to the stored file, typically `"<id>.content"`
480
+ * @returns the content
481
+ */
482
+ async getContent(ref) {
483
+ const raw = await this.getText(ref);
376
484
  const loaded = JSON.parse(raw);
377
485
  return content.parse(loaded);
378
486
  }
379
- async getMetadata(fileName, hash) {
380
- const raw = await this.getText(fileName, hash);
487
+ /**
488
+ * get the parsed and validated `Metadata` of a metadata hash
489
+ *
490
+ * Use {@link getText | `getText`} combined with `JSON.parse` to bypass
491
+ * validation
492
+ *
493
+ * @param ref - a reference to the stored file, typically `"<id>.metadata"`
494
+ * @returns the metadata
495
+ */
496
+ async getMetadata(ref) {
497
+ const raw = await this.getText(ref);
381
498
  const loaded = JSON.parse(raw);
382
499
  return metadata.parse(loaded);
383
500
  }
384
- async getRm(fileName, hash) {
385
- const bytes = await this.getHash(fileName, hash);
501
+ /**
502
+ * get the parsed reMarkable lines (`.rm`) drawing of a page hash
503
+ *
504
+ * @param ref - a reference to the stored file, typically `"<id>/<pageid>.rm"`
505
+ * @returns the parsed page
506
+ */
507
+ async getRm(ref) {
508
+ const bytes = await this.getHash(ref);
386
509
  return parseRm(bytes);
387
510
  }
511
+ /**
512
+ * the same as {@link putFile | `putFile`} but rendering an `RmPage` to `.rm`
513
+ * bytes
514
+ *
515
+ * Only version 3 and 5 pages can be rendered; version 6 pages are read-only.
516
+ */
388
517
  async putRm(fileName, page) {
389
518
  if (!fileName.endsWith(".rm")) {
390
519
  throw new Error(`fileName ${fileName} did not end with '.rm'`);
@@ -393,6 +522,25 @@ export class RawRemarkable {
393
522
  return await this.putFile(fileName, serializeRm(page));
394
523
  }
395
524
  }
525
+ /**
526
+ * update the current root hash
527
+ *
528
+ * This will fail if generation doesn't match the current server generation.
529
+ * This ensures that you are updating what you expect. IF you get a
530
+ * {@link GenerationError | `GenerationError`}, that indicates that the server
531
+ * was updated after you last got the generation. You should call
532
+ * {@link getRootHash | `getRootHash`} and then recompute the changes you want
533
+ * from the new root hash. If you ignore the update hash value and just call
534
+ * `putRootHash` again, you will overwrite the changes made by the other
535
+ * update.
536
+ *
537
+ * @param hash - the new root hash
538
+ * @param generation - the generation of the current root hash
539
+ * @param broadcast - [unknown] an option in the request
540
+ *
541
+ * @throws GenerationError if the generation doesn't match the current server generation
542
+ * @returns the new root hash and the new generation
543
+ */
396
544
  async putRootHash(hash, generation, broadcast = true) {
397
545
  if (!Number.isSafeInteger(generation)) {
398
546
  throw new Error(`generation ${generation} was not a safe integer`);
@@ -437,6 +585,20 @@ export class RawRemarkable {
437
585
  }
438
586
  }
439
587
  }
588
+ /**
589
+ * put a raw onto the server
590
+ *
591
+ * This returns the new expeced entry of the file you uploaded, and a promise
592
+ * to finish the upload successful. By splitting these two operations you can
593
+ * start using the uploaded entry while file finishes uploading.
594
+ *
595
+ * NOTE: This won't update the state of the reMarkable until this entry is
596
+ * incorporated into the root hash.
597
+ *
598
+ * @param fileName - the file name to upload (e.g. `<id>.pdf`)
599
+ * @param bytes - the bytes to upload
600
+ * @returns the new entry and a promise to finish the upload
601
+ */
440
602
  async putFile(fileName, bytes) {
441
603
  const hash = await digest(bytes);
442
604
  const res = {
@@ -448,6 +610,7 @@ export class RawRemarkable {
448
610
  };
449
611
  return [res, this.#putFile(fileName, hash, bytes)];
450
612
  }
613
+ /** the same as {@link putFile | `putFile`} but with caching for text */
451
614
  async putText(fileName, text) {
452
615
  const enc = new TextEncoder();
453
616
  const bytes = enc.encode(text);
@@ -460,6 +623,7 @@ export class RawRemarkable {
460
623
  }),
461
624
  ];
462
625
  }
626
+ /** the same as {@link putText | `putText`} but with extra validation for Content */
463
627
  async putContent(fileName, content) {
464
628
  if (!fileName.endsWith(".content")) {
465
629
  throw new Error(`fileName ${fileName} did not end with '.content'`);
@@ -468,6 +632,7 @@ export class RawRemarkable {
468
632
  return await this.putText(fileName, JSON.stringify(content));
469
633
  }
470
634
  }
635
+ /** the same as {@link putText | `putText`} but with extra validation for Metadata */
471
636
  async putMetadata(fileName, metadata) {
472
637
  if (!fileName.endsWith(".metadata")) {
473
638
  throw new Error(`fileName ${fileName} did not end with '.metadata'`);
@@ -476,6 +641,29 @@ export class RawRemarkable {
476
641
  return await this.putText(fileName, JSON.stringify(metadata));
477
642
  }
478
643
  }
644
+ /**
645
+ * put a set of entries to make an entry list file
646
+ *
647
+ * To fully upload an item:
648
+ * 1. upload all the constituent files and metadata
649
+ * 2. call this with all of the entries
650
+ * 3. append this entry to the root entry and call this again to update this root list
651
+ * 4. put the new root hash
652
+ *
653
+ * NOTE: reMarkable currently rejects newly written schema 3 root indexes
654
+ * with a 400 "Software must be updated" error, even for accounts that still
655
+ * report schema 3, so the root list should always be written as schema 4. A
656
+ * warning is logged if a schema 3 root index is written.
657
+ *
658
+ * @param id - the id of the list to upload - this should be the item id if
659
+ * uploading an item list, or "root" if uploading a new root list. Note the
660
+ * asymmetry with {@link getEntries | `getEntries`}: `getEntries` takes the
661
+ * full `"<id>.docSchema"` file name, whereas `putEntries` takes the bare id
662
+ * and appends `.docSchema` (and special-cases `"root"`) itself.
663
+ * @param entries - the entries to upload
664
+ *
665
+ * @returns the new list entry and a promise to finish the upload
666
+ */
479
667
  async putEntries(id, entries, schemaVersion) {
480
668
  if (id === "root" && schemaVersion === 3) {
481
669
  console.warn('writing a schema 3 root index, which reMarkable rejects with a 400 "Software must be updated" error; write the root index with schema version 4 instead');
@@ -520,6 +708,19 @@ export class RawRemarkable {
520
708
  };
521
709
  return [res, this.#putFile(`${id}.docSchema`, hash, entryBuff)];
522
710
  }
711
+ /**
712
+ * upload a file to the reMarkable cloud using the simple api
713
+ *
714
+ * This api is the same as used by the native reMarkable extension and works
715
+ * even if the backend schema version is version 4. Setting mime to "folder"
716
+ * allows folder creation.
717
+ *
718
+ * @param visibleName - the name of the file as it should appear on the reMarkable
719
+ * @param bytes - the bytes of the file to upload
720
+ * @param mime - the mime type of the file to upload
721
+
722
+ * @returns a simple entry with the id and hash of the uploaded file
723
+ */
523
724
  async uploadFile(visibleName, bytes, mime) {
524
725
  const enc = new TextEncoder();
525
726
  const meta = enc
@@ -534,12 +735,18 @@ export class RawRemarkable {
534
735
  },
535
736
  });
536
737
  const loaded = (await resp.json());
537
- const { docID, hash } = nativeSimpleEntry.parse(loaded);
738
+ const { docID, hash } = nativeItemRef.parse(loaded);
538
739
  return { id: docID, hash };
539
740
  }
741
+ /**
742
+ * dump the current cache to a string to preserve between session
743
+ *
744
+ * @returns a serialized version of the cache to pass to a new api instance
745
+ */
540
746
  dumpCache() {
541
747
  return JSON.stringify(Object.fromEntries(this.#cache));
542
748
  }
749
+ /** completely clear the cache */
543
750
  clearCache() {
544
751
  this.#cache.clear();
545
752
  }