rmapi-js 12.0.2 → 13.0.0
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/index.d.ts +182 -36
- package/dist/index.js +730 -236
- package/dist/lru.d.ts +4 -4
- package/dist/lru.js +4 -4
- package/dist/raw.d.ts +172 -36
- package/dist/raw.js +220 -75
- package/dist/rm5.d.ts +1 -1
- package/dist/rm6.d.ts +38 -13
- package/dist/rm6.js +418 -39
- package/dist/rmapi-js.esm.min.js +13 -9
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
1
53
|
/**
|
|
2
54
|
* Create and interact with reMarkable cloud.
|
|
3
55
|
*
|
|
@@ -36,11 +88,12 @@
|
|
|
36
88
|
* The cloud api is essentially a collection of entries. Each entry has an id,
|
|
37
89
|
* which is a uuid4 and a hash, which indicates it's current state, and changes
|
|
38
90
|
* as the item mutates, where the id is constant. Most mutable operations take
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* the
|
|
91
|
+
* both, as an {@link ItemRef | `ItemRef`}: the id says which item, and the hash
|
|
92
|
+
* says which state you meant to change, so a conflicting update fails rather
|
|
93
|
+
* than overwriting. Each entry has a number of properties, but a key property
|
|
94
|
+
* is the `parent`, which represents its parent in the file structure. This will
|
|
95
|
+
* be another document id, or one of two special ids, "" (the empty string) for
|
|
96
|
+
* the root directory, or "trash" for the trash.
|
|
44
97
|
*
|
|
45
98
|
* Detailed information about the low-level storage an apis can be found in
|
|
46
99
|
* {@link RawRemarkableApi | `RawRemarkableApi`}.
|
|
@@ -56,11 +109,11 @@ import { v4 as uuid4 } from "uuid";
|
|
|
56
109
|
import { z } from "zod";
|
|
57
110
|
import { HashNotFoundError, ValidationError } from "./error.js";
|
|
58
111
|
import { LruCache } from "./lru.js";
|
|
59
|
-
import { parseMetadata, RawRemarkable, } from "./raw.js";
|
|
112
|
+
import { BYTES_PREFIX, CACHE_VERSION, parseMetadata, RawRemarkable, TEXT_PREFIX, } from "./raw.js";
|
|
60
113
|
export { deviceScreens, } from "./devices.js";
|
|
61
114
|
export { HashNotFoundError, ValidationError } from "./error.js";
|
|
62
115
|
export { decodeBrush, rmColors } from "./rm5.js";
|
|
63
|
-
export { crdtKey
|
|
116
|
+
export { crdtKey } from "./rm6.js";
|
|
64
117
|
const AUTH_HOST = "https://webapp-prod.cloud.remarkable.engineering";
|
|
65
118
|
const RAW_HOST = "https://eu.tectonic.remarkable.com";
|
|
66
119
|
const UPLOAD_HOST = "https://internal.cloud.remarkable.com";
|
|
@@ -71,7 +124,6 @@ const TRASH_ID = "trash";
|
|
|
71
124
|
/** the id of the root entry list */
|
|
72
125
|
const ROOT_LIST = "root";
|
|
73
126
|
/** the file name of the root entry index */
|
|
74
|
-
const ROOT_SCHEMA = `${ROOT_LIST}.docSchema`;
|
|
75
127
|
/** base backoff in milliseconds for transient request retries */
|
|
76
128
|
const TRANSIENT_BASE_MS = 200;
|
|
77
129
|
/** base backoff in milliseconds for generation-conflict retries */
|
|
@@ -90,27 +142,25 @@ function backoffMs(attempt, baseMs) {
|
|
|
90
142
|
return Math.random() * capped;
|
|
91
143
|
}
|
|
92
144
|
/**
|
|
93
|
-
* a mutex
|
|
145
|
+
* a mutex held for the scope of the acquiring block
|
|
94
146
|
*
|
|
95
|
-
* `
|
|
96
|
-
*
|
|
97
|
-
* `using` block would. Waiters are served in acquisition order.
|
|
147
|
+
* `await using lock = await mutex.lock()` releases when the block exits, by
|
|
148
|
+
* return or by throw. Waiters are served in acquisition order.
|
|
98
149
|
*/
|
|
99
150
|
class Mutex {
|
|
100
151
|
#tail = Promise.resolve();
|
|
101
|
-
async
|
|
152
|
+
async lock() {
|
|
102
153
|
const previous = this.#tail;
|
|
103
154
|
let release;
|
|
104
155
|
this.#tail = new Promise((resolve) => {
|
|
105
156
|
release = resolve;
|
|
106
157
|
});
|
|
107
158
|
await previous;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
159
|
+
return {
|
|
160
|
+
async [Symbol.asyncDispose]() {
|
|
161
|
+
release();
|
|
162
|
+
},
|
|
163
|
+
};
|
|
114
164
|
}
|
|
115
165
|
}
|
|
116
166
|
/** the ordered page ids of a document's content, from cPages or the legacy list */
|
|
@@ -215,12 +265,12 @@ class Remarkable {
|
|
|
215
265
|
#schemaVersion;
|
|
216
266
|
/** serializes root updates on this instance so they don't self-conflict */
|
|
217
267
|
#rootMutex = new Mutex();
|
|
218
|
-
constructor(sessionToken, rawHost, uploadHost, cache, maxGenerationRetries, maxTransientRetries) {
|
|
268
|
+
constructor(sessionToken, rawHost, uploadHost, cache, maxGenerationRetries, maxTransientRetries, maxCachedBytes) {
|
|
219
269
|
this.#sessionToken = sessionToken;
|
|
220
270
|
this.#cache = cache;
|
|
221
271
|
this.#maxGenerationRetries = maxGenerationRetries;
|
|
222
272
|
this.#maxTransientRetries = maxTransientRetries;
|
|
223
|
-
this.raw = new RawRemarkable((method, url, { body, headers } = {}) => this.#authedFetch(url, { method, body, headers }), cache, rawHost, uploadHost);
|
|
273
|
+
this.raw = new RawRemarkable((method, url, { body, headers } = {}) => this.#authedFetch(url, { method, body, headers }), cache, rawHost, uploadHost, maxCachedBytes);
|
|
224
274
|
}
|
|
225
275
|
async #getRootHash(refresh = false) {
|
|
226
276
|
if (refresh || this.#lastHashGen === undefined) {
|
|
@@ -257,10 +307,12 @@ class Remarkable {
|
|
|
257
307
|
* before this so retries reuse the same (cached) blobs.
|
|
258
308
|
*/
|
|
259
309
|
async #withRetry(op) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
310
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
311
|
+
try {
|
|
312
|
+
// hold the root lock across the whole read-merge-write so concurrent
|
|
313
|
+
// mutators serialize instead of sharing a generation and forcing each
|
|
314
|
+
// other into avoidable conflicts
|
|
315
|
+
const _lock = __addDisposableResource(env_1, await this.#rootMutex.lock(), true);
|
|
264
316
|
for (let attempt = 0;; attempt++) {
|
|
265
317
|
try {
|
|
266
318
|
return await op();
|
|
@@ -276,8 +328,15 @@ class Remarkable {
|
|
|
276
328
|
}
|
|
277
329
|
}
|
|
278
330
|
}
|
|
279
|
-
|
|
280
|
-
|
|
331
|
+
catch (e_1) {
|
|
332
|
+
env_1.error = e_1;
|
|
333
|
+
env_1.hasError = true;
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
const result_1 = __disposeResources(env_1);
|
|
337
|
+
if (result_1)
|
|
338
|
+
await result_1;
|
|
339
|
+
}
|
|
281
340
|
}
|
|
282
341
|
/**
|
|
283
342
|
* splice an already-uploaded item entry into the root
|
|
@@ -290,13 +349,28 @@ class Remarkable {
|
|
|
290
349
|
await this.#withRetry(async () => {
|
|
291
350
|
const [rootHash, generation] = await this.#getRootHash();
|
|
292
351
|
const { entries } = await this.raw.getEntries({
|
|
293
|
-
id:
|
|
352
|
+
id: ROOT_LIST,
|
|
294
353
|
hash: rootHash,
|
|
295
354
|
});
|
|
296
355
|
entries.push(entry);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
356
|
+
let newRoot;
|
|
357
|
+
{
|
|
358
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
359
|
+
try {
|
|
360
|
+
const rootEntry = __addDisposableResource(env_2, await this.raw.putEntries(ROOT_LIST, entries, 4), true);
|
|
361
|
+
newRoot = rootEntry.hash;
|
|
362
|
+
}
|
|
363
|
+
catch (e_2) {
|
|
364
|
+
env_2.error = e_2;
|
|
365
|
+
env_2.hasError = true;
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
const result_2 = __disposeResources(env_2);
|
|
369
|
+
if (result_2)
|
|
370
|
+
await result_2;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
await this.#putRootHash(newRoot, generation);
|
|
300
374
|
});
|
|
301
375
|
}
|
|
302
376
|
async #authedFetch(url, { body, method = "POST", headers = {}, }) {
|
|
@@ -345,11 +419,9 @@ class Remarkable {
|
|
|
345
419
|
}
|
|
346
420
|
}
|
|
347
421
|
}
|
|
348
|
-
async #convertEntry(
|
|
349
|
-
const {
|
|
350
|
-
|
|
351
|
-
hash,
|
|
352
|
-
});
|
|
422
|
+
async #convertEntry(ref) {
|
|
423
|
+
const { id, hash } = ref;
|
|
424
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
353
425
|
const metaEnt = entries.find((ent) => ent.id.endsWith(".metadata"));
|
|
354
426
|
const contentEnt = entries.find((ent) => ent.id.endsWith(".content"));
|
|
355
427
|
if (metaEnt === undefined) {
|
|
@@ -423,17 +495,20 @@ class Remarkable {
|
|
|
423
495
|
* @returns a list of all items with some metadata
|
|
424
496
|
*/
|
|
425
497
|
async listItems(refresh = false) {
|
|
426
|
-
const ids = await this.
|
|
498
|
+
const ids = await this.listRefs(refresh);
|
|
427
499
|
return await Promise.all(ids.map((id) => this.#convertEntry(id)));
|
|
428
500
|
}
|
|
429
501
|
/**
|
|
430
|
-
*
|
|
502
|
+
* list a reference to every item, backed by the low level api
|
|
503
|
+
*
|
|
504
|
+
* Unlike {@link listItems | `listItems`} this doesn't read each item's
|
|
505
|
+
* metadata, so it's cheaper but only gives you ids and hashes.
|
|
431
506
|
*
|
|
432
507
|
* @param refresh - if true, refresh the root hash before listing
|
|
433
508
|
*/
|
|
434
|
-
async
|
|
509
|
+
async listRefs(refresh = false) {
|
|
435
510
|
const [hash] = await this.#getRootHash(refresh);
|
|
436
|
-
const { entries } = await this.raw.getEntries({ id:
|
|
511
|
+
const { entries } = await this.raw.getEntries({ id: ROOT_LIST, hash });
|
|
437
512
|
return entries.map(({ id, hash }) => ({ id, hash }));
|
|
438
513
|
}
|
|
439
514
|
/**
|
|
@@ -444,14 +519,12 @@ class Remarkable {
|
|
|
444
519
|
* the low-level api to get the raw text of the `.content` file in the
|
|
445
520
|
* `RawEntry` for this hash.
|
|
446
521
|
*
|
|
447
|
-
* @param ref - a reference to the item (e.g. from `listItems` or `
|
|
522
|
+
* @param ref - a reference to the item (e.g. from `listItems` or `listRefs`)
|
|
448
523
|
* @returns the content
|
|
449
524
|
*/
|
|
450
|
-
async getContent(
|
|
451
|
-
const {
|
|
452
|
-
|
|
453
|
-
hash,
|
|
454
|
-
});
|
|
525
|
+
async getContent(ref) {
|
|
526
|
+
const { hash } = ref;
|
|
527
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
455
528
|
const cont = entries.find((e) => e.id.endsWith(".content"));
|
|
456
529
|
if (cont === undefined) {
|
|
457
530
|
throw new Error(`couldn't find contents for hash ${hash}`);
|
|
@@ -468,14 +541,12 @@ class Remarkable {
|
|
|
468
541
|
* the low-level api to get the raw text of the `.metadata` file in the
|
|
469
542
|
* `RawEntry` for this hash.
|
|
470
543
|
*
|
|
471
|
-
* @param ref - a reference to the item (e.g. from `listItems` or `
|
|
544
|
+
* @param ref - a reference to the item (e.g. from `listItems` or `listRefs`)
|
|
472
545
|
* @returns the metadata
|
|
473
546
|
*/
|
|
474
|
-
async getMetadata(
|
|
475
|
-
const {
|
|
476
|
-
|
|
477
|
-
hash,
|
|
478
|
-
});
|
|
547
|
+
async getMetadata(ref) {
|
|
548
|
+
const { hash } = ref;
|
|
549
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
479
550
|
const meta = entries.find((e) => e.id.endsWith(".metadata"));
|
|
480
551
|
if (meta === undefined) {
|
|
481
552
|
throw new Error(`couldn't find metadata for hash ${hash}`);
|
|
@@ -492,11 +563,9 @@ class Remarkable {
|
|
|
492
563
|
* @param ref - a reference to the document (e.g. from `listItems`)
|
|
493
564
|
* @returns the pdf bytes
|
|
494
565
|
*/
|
|
495
|
-
async getPdf(
|
|
496
|
-
const {
|
|
497
|
-
|
|
498
|
-
hash,
|
|
499
|
-
});
|
|
566
|
+
async getPdf(ref) {
|
|
567
|
+
const { hash } = ref;
|
|
568
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
500
569
|
const pdf = entries.find((e) => e.id.endsWith(".pdf"));
|
|
501
570
|
if (pdf === undefined) {
|
|
502
571
|
throw new Error(`couldn't find pdf for hash ${hash}`);
|
|
@@ -513,11 +582,9 @@ class Remarkable {
|
|
|
513
582
|
* @param ref - a reference to the document (e.g. from `listItems`)
|
|
514
583
|
* @returns the epub bytes
|
|
515
584
|
*/
|
|
516
|
-
async getEpub(
|
|
517
|
-
const {
|
|
518
|
-
|
|
519
|
-
hash,
|
|
520
|
-
});
|
|
585
|
+
async getEpub(ref) {
|
|
586
|
+
const { hash } = ref;
|
|
587
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
521
588
|
const epub = entries.find((e) => e.id.endsWith(".epub"));
|
|
522
589
|
if (epub === undefined) {
|
|
523
590
|
throw new Error(`couldn't find epub for hash ${hash}`);
|
|
@@ -526,6 +593,95 @@ class Remarkable {
|
|
|
526
593
|
return await this.raw.getHash(epub);
|
|
527
594
|
}
|
|
528
595
|
}
|
|
596
|
+
#rmPageFile = {
|
|
597
|
+
name: (docId, pageId) => `${docId}/${pageId}.rm`,
|
|
598
|
+
read: (entry) => this.raw.getRm(entry),
|
|
599
|
+
write: (fileName, page) => this.raw.putRm(fileName, page),
|
|
600
|
+
};
|
|
601
|
+
#highlightPageFile = {
|
|
602
|
+
name: (docId, pageId) => `${docId}.highlights/${pageId}.json`,
|
|
603
|
+
read: (entry) => this.raw.getHighlights(entry),
|
|
604
|
+
write: (fileName, highlights) => this.raw.putHighlights(fileName, highlights),
|
|
605
|
+
};
|
|
606
|
+
#pageMetadataFile = {
|
|
607
|
+
name: (docId, pageId) => `${docId}/${pageId}-metadata.json`,
|
|
608
|
+
read: (entry) => this.raw.getPageMetadata(entry),
|
|
609
|
+
write: (fileName, meta) => this.raw.putPageMetadata(fileName, meta),
|
|
610
|
+
};
|
|
611
|
+
async #getPageFile(ref, pageId, file) {
|
|
612
|
+
const { id } = ref;
|
|
613
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
614
|
+
const content = await this.getContent(ref);
|
|
615
|
+
if (!pageOrder(content).includes(pageId)) {
|
|
616
|
+
throw new Error(`document ${id} has no page ${pageId}`);
|
|
617
|
+
}
|
|
618
|
+
const entry = entries.find((ent) => ent.id === file.name(id, pageId));
|
|
619
|
+
if (entry === undefined) {
|
|
620
|
+
return undefined;
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
return await file.read(entry);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async #getPageFiles(ref, file) {
|
|
627
|
+
const { id } = ref;
|
|
628
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
629
|
+
const content = await this.getContent(ref);
|
|
630
|
+
const byName = new Map(entries.map((entry) => [entry.id, entry]));
|
|
631
|
+
const found = pageOrder(content)
|
|
632
|
+
.map((pageId) => [pageId, byName.get(file.name(id, pageId))])
|
|
633
|
+
.filter((pair) => pair[1] !== undefined);
|
|
634
|
+
const parsed = await Promise.all(found.map(([, entry]) => file.read(entry)));
|
|
635
|
+
return new Map(found.map(([pageId], index) => [pageId, parsed[index]]));
|
|
636
|
+
}
|
|
637
|
+
async #putPageFilesRaw(ref, pages, file, schemaVersion) {
|
|
638
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
639
|
+
try {
|
|
640
|
+
const { id, hash } = ref;
|
|
641
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
642
|
+
const contentEntry = entries.find((ent) => ent.id.endsWith(".content"));
|
|
643
|
+
if (contentEntry === undefined) {
|
|
644
|
+
throw new Error(`couldn't find contents for hash ${hash}`);
|
|
645
|
+
}
|
|
646
|
+
const content = await this.raw.getContent(contentEntry);
|
|
647
|
+
const order = new Set(pageOrder(content));
|
|
648
|
+
for (const pageId of pages.keys()) {
|
|
649
|
+
if (!order.has(pageId)) {
|
|
650
|
+
throw new Error(`document ${id} has no page ${pageId}`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const uploads = __addDisposableResource(env_3, new AsyncDisposableStack(), true);
|
|
654
|
+
const written = await Promise.all([...pages].map(([pageId, value]) => file.write(file.name(id, pageId), value)));
|
|
655
|
+
for (const pageEntry of written) {
|
|
656
|
+
uploads.use(pageEntry);
|
|
657
|
+
const pageInd = entries.findIndex((ent) => ent.id === pageEntry.id);
|
|
658
|
+
if (pageInd === -1) {
|
|
659
|
+
entries.push(pageEntry);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
entries[pageInd] = pageEntry;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return await this.raw.putEntries(id, entries, schemaVersion);
|
|
666
|
+
}
|
|
667
|
+
catch (e_3) {
|
|
668
|
+
env_3.error = e_3;
|
|
669
|
+
env_3.hasError = true;
|
|
670
|
+
}
|
|
671
|
+
finally {
|
|
672
|
+
const result_3 = __disposeResources(env_3);
|
|
673
|
+
if (result_3)
|
|
674
|
+
await result_3;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async #putPageFiles(ref, pages, file, refresh) {
|
|
678
|
+
if (pages.size === 0) {
|
|
679
|
+
return ref;
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
return await this.#editEntry(ref, refresh, (item, schemaVersion) => this.#putPageFilesRaw(item, pages, file, schemaVersion));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
529
685
|
/**
|
|
530
686
|
* get a single page's parsed reMarkable lines (`.rm`) drawing
|
|
531
687
|
*
|
|
@@ -537,22 +693,7 @@ class Remarkable {
|
|
|
537
693
|
* @throws if `pageId` is not a page of the document
|
|
538
694
|
*/
|
|
539
695
|
async getRmPage(ref, pageId) {
|
|
540
|
-
|
|
541
|
-
const { entries } = await this.raw.getEntries({
|
|
542
|
-
id: `${id}.docSchema`,
|
|
543
|
-
hash,
|
|
544
|
-
});
|
|
545
|
-
const content = await this.getContent(ref);
|
|
546
|
-
if (!pageOrder(content).includes(pageId)) {
|
|
547
|
-
throw new Error(`document ${id} has no page ${pageId}`);
|
|
548
|
-
}
|
|
549
|
-
const entry = entries.find((ent) => ent.id === `${id}/${pageId}.rm`);
|
|
550
|
-
if (entry === undefined) {
|
|
551
|
-
return undefined;
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
return await this.raw.getRm(entry);
|
|
555
|
-
}
|
|
696
|
+
return await this.#getPageFile(ref, pageId, this.#rmPageFile);
|
|
556
697
|
}
|
|
557
698
|
/**
|
|
558
699
|
* get every drawn page of a document, parsed, keyed by page id
|
|
@@ -566,18 +707,243 @@ class Remarkable {
|
|
|
566
707
|
* @returns the drawn pages, keyed by page id, in document order
|
|
567
708
|
*/
|
|
568
709
|
async getRmPages(ref) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
710
|
+
return await this.#getPageFiles(ref, this.#rmPageFile);
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* write a single page's reMarkable lines (`.rm`) drawing
|
|
714
|
+
*
|
|
715
|
+
* @param ref - a reference to the document
|
|
716
|
+
* @param pageId - the id of the page, from the document's `.content` page list
|
|
717
|
+
* @param page - the drawing to write, replacing any already there
|
|
718
|
+
* @throws GenerationError if the generation doesn't match the current server generation
|
|
719
|
+
* @throws if `pageId` is not a page of the document
|
|
720
|
+
* @returns a reference to the updated document, with its new hash
|
|
721
|
+
*/
|
|
722
|
+
async putRmPage(ref, pageId, page, refresh = false) {
|
|
723
|
+
return await this.putRmPages(ref, new Map([[pageId, page]]), refresh);
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* write several pages' reMarkable lines (`.rm`) drawings in one commit
|
|
727
|
+
*
|
|
728
|
+
* @param ref - a reference to the document
|
|
729
|
+
* @param pages - the drawings to write, keyed by page id, replacing any
|
|
730
|
+
* already on those pages and leaving every other page alone
|
|
731
|
+
* @throws GenerationError if the generation doesn't match the current server generation
|
|
732
|
+
* @throws if any key is not a page of the document
|
|
733
|
+
* @returns a reference to the updated document, with its new hash
|
|
734
|
+
*/
|
|
735
|
+
async putRmPages(ref, pages, refresh = false) {
|
|
736
|
+
return await this.#putPageFiles(ref, pages, this.#rmPageFile, refresh);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* get a single page's text highlights
|
|
740
|
+
*
|
|
741
|
+
* These are separate from the highlighter strokes drawn in a `.rm` scene.
|
|
742
|
+
*
|
|
743
|
+
* @param ref - a reference to the document
|
|
744
|
+
* @param pageId - the id of the page, from the document's `.content` page list
|
|
745
|
+
* @returns the page's highlights, or `undefined` if the page has none
|
|
746
|
+
* @throws if `pageId` is not a page of the document
|
|
747
|
+
*/
|
|
748
|
+
async getHighlights(ref, pageId) {
|
|
749
|
+
return await this.#getPageFile(ref, pageId, this.#highlightPageFile);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* get every highlighted page of a document, keyed by page id
|
|
753
|
+
*
|
|
754
|
+
* @param ref - a reference to the document
|
|
755
|
+
* @returns the highlights in page order, omitting pages with none
|
|
756
|
+
*/
|
|
757
|
+
async getHighlightPages(ref) {
|
|
758
|
+
return await this.#getPageFiles(ref, this.#highlightPageFile);
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* write a single page's text highlights, replacing any already there
|
|
762
|
+
*
|
|
763
|
+
* @param ref - a reference to the document
|
|
764
|
+
* @param pageId - the id of the page, from the document's `.content` page list
|
|
765
|
+
* @param highlights - the highlights to write
|
|
766
|
+
* @throws GenerationError if the generation doesn't match the current server generation
|
|
767
|
+
* @throws if `pageId` is not a page of the document
|
|
768
|
+
* @returns a reference to the updated document, with its new hash
|
|
769
|
+
*/
|
|
770
|
+
async putHighlights(ref, pageId, highlights, refresh = false) {
|
|
771
|
+
return await this.putHighlightPages(ref, new Map([[pageId, highlights]]), refresh);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* write several pages' text highlights in one commit
|
|
775
|
+
*
|
|
776
|
+
* @param ref - a reference to the document
|
|
777
|
+
* @param pages - the highlights to write, keyed by page id, replacing any
|
|
778
|
+
* already on those pages and leaving every other page alone
|
|
779
|
+
* @throws GenerationError if the generation doesn't match the current server generation
|
|
780
|
+
* @throws if any key is not a page of the document
|
|
781
|
+
* @returns a reference to the updated document, with its new hash
|
|
782
|
+
*/
|
|
783
|
+
async putHighlightPages(ref, pages, refresh = false) {
|
|
784
|
+
return await this.#putPageFiles(ref, pages, this.#highlightPageFile, refresh);
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* get a template attached to an item as a `.template` sidecar
|
|
788
|
+
*
|
|
789
|
+
* This is distinct from a {@link TemplateEntry | `TemplateEntry`} (whose
|
|
790
|
+
* template is its `.content`); collections and documents can carry a template
|
|
791
|
+
* this way.
|
|
792
|
+
*
|
|
793
|
+
* @param ref - a reference to the item
|
|
794
|
+
* @returns the template content, or `undefined` if the item has no `.template`
|
|
795
|
+
*/
|
|
796
|
+
async getTemplate(ref) {
|
|
797
|
+
const { id } = ref;
|
|
798
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
799
|
+
const entry = entries.find((e) => e.id === `${id}.template`);
|
|
800
|
+
if (entry === undefined) {
|
|
801
|
+
return undefined;
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
return await this.raw.getTemplate(entry);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* attach a template to an item as a `.template` sidecar
|
|
809
|
+
*
|
|
810
|
+
* @param ref - a reference to the item
|
|
811
|
+
* @param template - the template to attach, replacing any already there
|
|
812
|
+
* @throws GenerationError if the generation doesn't match the current server generation
|
|
813
|
+
* @returns a reference to the updated item, with its new hash
|
|
814
|
+
*/
|
|
815
|
+
async putTemplate(ref, template, refresh = false) {
|
|
816
|
+
return await this.#editEntry(ref, refresh, async (item, schemaVersion) => {
|
|
817
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
818
|
+
try {
|
|
819
|
+
const { id } = item;
|
|
820
|
+
const { entries } = await this.raw.getEntries(item);
|
|
821
|
+
const templateEntry = __addDisposableResource(env_4, await this.raw.putTemplate(`${id}.template`, template), true);
|
|
822
|
+
const ind = entries.findIndex((ent) => ent.id === templateEntry.id);
|
|
823
|
+
if (ind === -1) {
|
|
824
|
+
entries.push(templateEntry);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
entries[ind] = templateEntry;
|
|
828
|
+
}
|
|
829
|
+
return await this.raw.putEntries(id, entries, schemaVersion);
|
|
830
|
+
}
|
|
831
|
+
catch (e_4) {
|
|
832
|
+
env_4.error = e_4;
|
|
833
|
+
env_4.hasError = true;
|
|
834
|
+
}
|
|
835
|
+
finally {
|
|
836
|
+
const result_4 = __disposeResources(env_4);
|
|
837
|
+
if (result_4)
|
|
838
|
+
await result_4;
|
|
839
|
+
}
|
|
573
840
|
});
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* get a document's per-page template names
|
|
844
|
+
*
|
|
845
|
+
* The `.pagedata` file lists one template name per page, in page order (an
|
|
846
|
+
* empty string for a page with no template).
|
|
847
|
+
*
|
|
848
|
+
* @param ref - a reference to the document
|
|
849
|
+
* @returns the per-page template names, or `undefined` if the document has
|
|
850
|
+
* no `.pagedata`
|
|
851
|
+
*/
|
|
852
|
+
async getPagedata(ref) {
|
|
853
|
+
const { id } = ref;
|
|
854
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
855
|
+
const entry = entries.find((e) => e.id === `${id}.pagedata`);
|
|
856
|
+
if (entry === undefined) {
|
|
857
|
+
return undefined;
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
const lines = (await this.raw.getText(entry)).split("\n");
|
|
861
|
+
if (lines.at(-1) === "")
|
|
862
|
+
lines.pop();
|
|
863
|
+
return lines;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* set a document's per-page template names
|
|
868
|
+
*
|
|
869
|
+
* @param ref - a reference to the document
|
|
870
|
+
* @param templates - one template name per page, in page order, an empty
|
|
871
|
+
* string for a page with no template
|
|
872
|
+
* @throws GenerationError if the generation doesn't match the current server generation
|
|
873
|
+
* @returns a reference to the updated document, with its new hash
|
|
874
|
+
*/
|
|
875
|
+
async putPagedata(ref, templates, refresh = false) {
|
|
876
|
+
return await this.#editEntry(ref, refresh, async (item, schemaVersion) => {
|
|
877
|
+
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
878
|
+
try {
|
|
879
|
+
const { id } = item;
|
|
880
|
+
const { entries } = await this.raw.getEntries(item);
|
|
881
|
+
const pagedataEntry = __addDisposableResource(env_5, await this.raw.putPagedata(`${id}.pagedata`, templates), true);
|
|
882
|
+
const ind = entries.findIndex((ent) => ent.id === pagedataEntry.id);
|
|
883
|
+
if (ind === -1) {
|
|
884
|
+
entries.push(pagedataEntry);
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
entries[ind] = pagedataEntry;
|
|
888
|
+
}
|
|
889
|
+
return await this.raw.putEntries(id, entries, schemaVersion);
|
|
890
|
+
}
|
|
891
|
+
catch (e_5) {
|
|
892
|
+
env_5.error = e_5;
|
|
893
|
+
env_5.hasError = true;
|
|
894
|
+
}
|
|
895
|
+
finally {
|
|
896
|
+
const result_5 = __disposeResources(env_5);
|
|
897
|
+
if (result_5)
|
|
898
|
+
await result_5;
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* get a single page's layer metadata
|
|
904
|
+
*
|
|
905
|
+
* @param ref - a reference to the document
|
|
906
|
+
* @param pageId - the id of the page, from the document's `.content` page list
|
|
907
|
+
* @returns the page's layer metadata, or `undefined` if the page has none
|
|
908
|
+
* @throws if `pageId` is not a page of the document
|
|
909
|
+
*/
|
|
910
|
+
async getPageMetadata(ref, pageId) {
|
|
911
|
+
return await this.#getPageFile(ref, pageId, this.#pageMetadataFile);
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* get every page's layer metadata, keyed by page id
|
|
915
|
+
*
|
|
916
|
+
* @param ref - a reference to the document
|
|
917
|
+
* @returns the layer metadata in page order, omitting pages with none
|
|
918
|
+
*/
|
|
919
|
+
async getPageMetadataPages(ref) {
|
|
920
|
+
return await this.#getPageFiles(ref, this.#pageMetadataFile);
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* write a single page's layer metadata, replacing any already there
|
|
924
|
+
*
|
|
925
|
+
* @param ref - a reference to the document
|
|
926
|
+
* @param pageId - the id of the page, from the document's `.content` page list
|
|
927
|
+
* @param meta - the layer metadata to write
|
|
928
|
+
* @throws GenerationError if the generation doesn't match the current server generation
|
|
929
|
+
* @throws if `pageId` is not a page of the document
|
|
930
|
+
* @returns a reference to the updated document, with its new hash
|
|
931
|
+
*/
|
|
932
|
+
async putPageMetadata(ref, pageId, meta, refresh = false) {
|
|
933
|
+
return await this.putPageMetadataPages(ref, new Map([[pageId, meta]]), refresh);
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* write several pages' layer metadata in one commit
|
|
937
|
+
*
|
|
938
|
+
* @param ref - a reference to the document
|
|
939
|
+
* @param pages - the layer metadata to write, keyed by page id, replacing any
|
|
940
|
+
* already on those pages and leaving every other page alone
|
|
941
|
+
* @throws GenerationError if the generation doesn't match the current server generation
|
|
942
|
+
* @throws if any key is not a page of the document
|
|
943
|
+
* @returns a reference to the updated document, with its new hash
|
|
944
|
+
*/
|
|
945
|
+
async putPageMetadataPages(ref, pages, refresh = false) {
|
|
946
|
+
return await this.#putPageFiles(ref, pages, this.#pageMetadataFile, refresh);
|
|
581
947
|
}
|
|
582
948
|
/**
|
|
583
949
|
* get a document's entire contents as a zip archive
|
|
@@ -591,11 +957,8 @@ class Remarkable {
|
|
|
591
957
|
*
|
|
592
958
|
* @param ref - a reference to the document (e.g. from `listItems`)
|
|
593
959
|
*/
|
|
594
|
-
async getDocumentArchive(
|
|
595
|
-
const { entries } = await this.raw.getEntries(
|
|
596
|
-
id: `${id}.docSchema`,
|
|
597
|
-
hash,
|
|
598
|
-
});
|
|
960
|
+
async getDocumentArchive(ref) {
|
|
961
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
599
962
|
const zip = new JSZip();
|
|
600
963
|
for (const entry of entries) {
|
|
601
964
|
// TODO if this is .metadata we might want to assert type === "DocumentType"
|
|
@@ -610,16 +973,16 @@ class Remarkable {
|
|
|
610
973
|
* as a blob, and commits a new document into the root.
|
|
611
974
|
*
|
|
612
975
|
* @remarks
|
|
613
|
-
* This is an experimental feature.
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
976
|
+
* This is an experimental feature. A fresh document id is generated, so
|
|
977
|
+
* re-uploading to the same account doesn't collide with the original. Like
|
|
978
|
+
* the other low-level puts, this may throw a
|
|
979
|
+
* {@link GenerationError | `GenerationError`} if the generation is stale,
|
|
980
|
+
* requiring a retry.
|
|
618
981
|
*
|
|
619
982
|
* @param buffer - the archive bytes, as returned by `getDocumentArchive`
|
|
620
|
-
* @param options - overrides for parent
|
|
983
|
+
* @param options - overrides for parent and visible name
|
|
621
984
|
*/
|
|
622
|
-
async putDocumentArchive(buffer, { refresh = false, parent, visibleName
|
|
985
|
+
async putDocumentArchive(buffer, { refresh = false, parent, visibleName } = {}) {
|
|
623
986
|
if (parent !== undefined && parent && !idReg.test(parent)) {
|
|
624
987
|
throw new ValidationError(parent, idReg, "parent must be a valid document id");
|
|
625
988
|
}
|
|
@@ -633,7 +996,7 @@ class Remarkable {
|
|
|
633
996
|
if (oldId.includes("/")) {
|
|
634
997
|
throw new Error(`unexpected nested .metadata path '${metaPath}'`);
|
|
635
998
|
}
|
|
636
|
-
const newId =
|
|
999
|
+
const newId = uuid4();
|
|
637
1000
|
// rewrite the old document id prefix on every archived file to the new id,
|
|
638
1001
|
// patching the .metadata as we pass it (parent/name/lastModified). the
|
|
639
1002
|
// blobs don't depend on the generation, so upload the rewritten files and
|
|
@@ -659,9 +1022,27 @@ class Remarkable {
|
|
|
659
1022
|
}
|
|
660
1023
|
return this.raw.putFile(newPath, bytes);
|
|
661
1024
|
}));
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1025
|
+
let docEntry;
|
|
1026
|
+
{
|
|
1027
|
+
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
1028
|
+
try {
|
|
1029
|
+
const uploads = __addDisposableResource(env_6, new AsyncDisposableStack(), true);
|
|
1030
|
+
for (const entry of fileUploads) {
|
|
1031
|
+
uploads.use(entry);
|
|
1032
|
+
}
|
|
1033
|
+
const indexEntry = __addDisposableResource(env_6, await this.raw.putEntries(newId, fileUploads, schemaVersion), true);
|
|
1034
|
+
docEntry = indexEntry;
|
|
1035
|
+
}
|
|
1036
|
+
catch (e_6) {
|
|
1037
|
+
env_6.error = e_6;
|
|
1038
|
+
env_6.hasError = true;
|
|
1039
|
+
}
|
|
1040
|
+
finally {
|
|
1041
|
+
const result_6 = __disposeResources(env_6);
|
|
1042
|
+
if (result_6)
|
|
1043
|
+
await result_6;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
665
1046
|
await this.#commit(docEntry);
|
|
666
1047
|
return { id: newId, hash: docEntry.hash };
|
|
667
1048
|
}
|
|
@@ -715,24 +1096,35 @@ class Remarkable {
|
|
|
715
1096
|
// themselves don't depend on the generation, so upload them once and let
|
|
716
1097
|
// #commit retry only the root merge
|
|
717
1098
|
const [, , schemaVersion] = await this.#getRootHash(refresh);
|
|
718
|
-
const [[contentEntry, uploadContent], [metadataEntry, uploadMetadata], [pagedataEntry, uploadPagedata], [fileEntry, uploadFile],] = await Promise.all([
|
|
719
|
-
this.raw.putContent(`${id}.content`, content),
|
|
720
|
-
this.raw.putMetadata(`${id}.metadata`, metadata),
|
|
721
|
-
this.raw.putText(`${id}.pagedata`, "\n"),
|
|
722
|
-
this.raw.putFile(`${id}.${fileType}`, buffer),
|
|
723
|
-
]);
|
|
724
|
-
const [collectionEntry, uploadCollection] = await this.raw.putEntries(id, [contentEntry, metadataEntry, pagedataEntry, fileEntry], schemaVersion);
|
|
725
1099
|
// TODO we could return a full entry here, but we should probably decide
|
|
726
1100
|
// what that should be, e.g. we could return more fields than the standard
|
|
727
1101
|
// entry. Same for putFolder
|
|
728
1102
|
// TODO we should also decide if the api should take hashes or ids...
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1103
|
+
let collectionEntry;
|
|
1104
|
+
{
|
|
1105
|
+
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
1106
|
+
try {
|
|
1107
|
+
const contentReq = this.raw.putContent(`${id}.content`, content);
|
|
1108
|
+
const metadataReq = this.raw.putMetadata(`${id}.metadata`, metadata);
|
|
1109
|
+
const pagedataReq = this.raw.putFile(`${id}.pagedata`, new TextEncoder().encode("\n"));
|
|
1110
|
+
const fileReq = this.raw.putFile(`${id}.${fileType}`, buffer);
|
|
1111
|
+
const contentEntry = __addDisposableResource(env_7, await contentReq, true);
|
|
1112
|
+
const metadataEntry = __addDisposableResource(env_7, await metadataReq, true);
|
|
1113
|
+
const pagedataEntry = __addDisposableResource(env_7, await pagedataReq, true);
|
|
1114
|
+
const fileEntry = __addDisposableResource(env_7, await fileReq, true);
|
|
1115
|
+
const indexEntry = __addDisposableResource(env_7, await this.raw.putEntries(id, [contentEntry, metadataEntry, pagedataEntry, fileEntry], schemaVersion), true);
|
|
1116
|
+
collectionEntry = indexEntry;
|
|
1117
|
+
}
|
|
1118
|
+
catch (e_7) {
|
|
1119
|
+
env_7.error = e_7;
|
|
1120
|
+
env_7.hasError = true;
|
|
1121
|
+
}
|
|
1122
|
+
finally {
|
|
1123
|
+
const result_7 = __disposeResources(env_7);
|
|
1124
|
+
if (result_7)
|
|
1125
|
+
await result_7;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
736
1128
|
await this.#commit(collectionEntry);
|
|
737
1129
|
return { id, hash: collectionEntry.hash };
|
|
738
1130
|
}
|
|
@@ -818,12 +1210,27 @@ class Remarkable {
|
|
|
818
1210
|
// the blobs don't depend on the generation, so upload them once and let
|
|
819
1211
|
// #commit retry only the root merge
|
|
820
1212
|
const [, , schemaVersion] = await this.#getRootHash(refresh);
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1213
|
+
let collectionEntry;
|
|
1214
|
+
{
|
|
1215
|
+
const env_8 = { stack: [], error: void 0, hasError: false };
|
|
1216
|
+
try {
|
|
1217
|
+
const contentReq = this.raw.putContent(`${id}.content`, content);
|
|
1218
|
+
const metadataReq = this.raw.putMetadata(`${id}.metadata`, metadata);
|
|
1219
|
+
const contentEntry = __addDisposableResource(env_8, await contentReq, true);
|
|
1220
|
+
const metadataEntry = __addDisposableResource(env_8, await metadataReq, true);
|
|
1221
|
+
const indexEntry = __addDisposableResource(env_8, await this.raw.putEntries(id, [contentEntry, metadataEntry], schemaVersion), true);
|
|
1222
|
+
collectionEntry = indexEntry;
|
|
1223
|
+
}
|
|
1224
|
+
catch (e_8) {
|
|
1225
|
+
env_8.error = e_8;
|
|
1226
|
+
env_8.hasError = true;
|
|
1227
|
+
}
|
|
1228
|
+
finally {
|
|
1229
|
+
const result_8 = __disposeResources(env_8);
|
|
1230
|
+
if (result_8)
|
|
1231
|
+
await result_8;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
827
1234
|
await this.#commit(collectionEntry);
|
|
828
1235
|
return { id, hash: collectionEntry.hash };
|
|
829
1236
|
}
|
|
@@ -866,49 +1273,87 @@ class Remarkable {
|
|
|
866
1273
|
return await this.raw.uploadFile(visibleName, new Uint8Array(0), "folder");
|
|
867
1274
|
}
|
|
868
1275
|
/** edit just a content entry */
|
|
869
|
-
async #editContentRaw(
|
|
870
|
-
const {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1276
|
+
async #editContentRaw(ref, update, schemaVersion) {
|
|
1277
|
+
const env_9 = { stack: [], error: void 0, hasError: false };
|
|
1278
|
+
try {
|
|
1279
|
+
const { id } = ref;
|
|
1280
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
1281
|
+
const contInd = entries.findIndex((ent) => ent.id.endsWith(".content"));
|
|
1282
|
+
const contEntry = entries[contInd];
|
|
1283
|
+
if (contEntry === undefined) {
|
|
1284
|
+
throw new Error("internal error: couldn't find content in entry hash");
|
|
1285
|
+
}
|
|
1286
|
+
const cont = await this.raw.getContent(contEntry);
|
|
1287
|
+
Object.assign(cont, update);
|
|
1288
|
+
const newContEntry = __addDisposableResource(env_9, await this.raw.putContent(contEntry.id, cont), true);
|
|
1289
|
+
entries[contInd] = newContEntry;
|
|
1290
|
+
return await this.raw.putEntries(id, entries, schemaVersion);
|
|
1291
|
+
}
|
|
1292
|
+
catch (e_9) {
|
|
1293
|
+
env_9.error = e_9;
|
|
1294
|
+
env_9.hasError = true;
|
|
1295
|
+
}
|
|
1296
|
+
finally {
|
|
1297
|
+
const result_9 = __disposeResources(env_9);
|
|
1298
|
+
if (result_9)
|
|
1299
|
+
await result_9;
|
|
878
1300
|
}
|
|
879
|
-
const cont = await this.raw.getContent(contEntry);
|
|
880
|
-
Object.assign(cont, update);
|
|
881
|
-
const [newContEntry, uploadCont] = await this.raw.putContent(contEntry.id, cont);
|
|
882
|
-
entries[contInd] = newContEntry;
|
|
883
|
-
const [result, uploadEntries] = await this.raw.putEntries(id, entries, schemaVersion);
|
|
884
|
-
const upload = Promise.all([uploadCont, uploadEntries]);
|
|
885
|
-
return [result, upload];
|
|
886
1301
|
}
|
|
887
|
-
/**
|
|
888
|
-
|
|
1302
|
+
/**
|
|
1303
|
+
* rewrite one item's files and splice the result into the root
|
|
1304
|
+
*
|
|
1305
|
+
* `edit` receives a ref to the item and the schema version, and returns its
|
|
1306
|
+
* new entry plus a promise for the uploads. Everything generation-dependent
|
|
1307
|
+
* lives here, so writers only describe the file change.
|
|
1308
|
+
*/
|
|
1309
|
+
async #editEntry(ref, refresh, edit) {
|
|
889
1310
|
return await this.#withRetry(async () => {
|
|
890
1311
|
const [rootHash, generation, schemaVersion] = await this.#getRootHash(refresh);
|
|
891
1312
|
const { entries } = await this.raw.getEntries({
|
|
892
|
-
id:
|
|
1313
|
+
id: ROOT_LIST,
|
|
893
1314
|
hash: rootHash,
|
|
894
1315
|
});
|
|
895
|
-
const hashInd = entries.findIndex((ent) => ent.hash === hash);
|
|
1316
|
+
const hashInd = entries.findIndex((ent) => ent.id === ref.id && ent.hash === ref.hash);
|
|
896
1317
|
const hashEnt = entries[hashInd];
|
|
897
1318
|
if (hashEnt === undefined) {
|
|
898
|
-
throw new HashNotFoundError(hash);
|
|
1319
|
+
throw new HashNotFoundError(ref.hash);
|
|
899
1320
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1321
|
+
let newRoot;
|
|
1322
|
+
let newHash;
|
|
1323
|
+
{
|
|
1324
|
+
const env_10 = { stack: [], error: void 0, hasError: false };
|
|
1325
|
+
try {
|
|
1326
|
+
const newEnt = __addDisposableResource(env_10, await edit({ id: hashEnt.id, hash: ref.hash }, schemaVersion), true);
|
|
1327
|
+
entries[hashInd] = newEnt;
|
|
1328
|
+
const rootEntry = __addDisposableResource(env_10, await this.raw.putEntries(ROOT_LIST, entries, 4), true);
|
|
1329
|
+
newRoot = rootEntry.hash;
|
|
1330
|
+
newHash = newEnt.hash;
|
|
1331
|
+
}
|
|
1332
|
+
catch (e_10) {
|
|
1333
|
+
env_10.error = e_10;
|
|
1334
|
+
env_10.hasError = true;
|
|
1335
|
+
}
|
|
1336
|
+
finally {
|
|
1337
|
+
const result_10 = __disposeResources(env_10);
|
|
1338
|
+
if (result_10)
|
|
1339
|
+
await result_10;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
await this.#putRootHash(newRoot, generation);
|
|
1343
|
+
return { id: hashEnt.id, hash: newHash };
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
/** fully sync a content edit */
|
|
1347
|
+
async #editContent(ref, update, expectedType, refresh) {
|
|
1348
|
+
return await this.#editEntry(ref, refresh, async (item, schemaVersion) => {
|
|
1349
|
+
const [newEnt, meta] = await Promise.all([
|
|
1350
|
+
this.#editContentRaw(item, update, schemaVersion),
|
|
1351
|
+
this.getMetadata(item),
|
|
903
1352
|
]);
|
|
904
1353
|
if (meta.type !== expectedType) {
|
|
905
|
-
throw new Error(`expected type ${expectedType} but got ${meta.type} for hash ${hash}`);
|
|
1354
|
+
throw new Error(`expected type ${expectedType} but got ${meta.type} for hash ${item.hash}`);
|
|
906
1355
|
}
|
|
907
|
-
|
|
908
|
-
const [rootEntry, uploadRoot] = await this.raw.putEntries(ROOT_LIST, entries, 4);
|
|
909
|
-
await Promise.all([uploadEnt, uploadRoot]);
|
|
910
|
-
await this.#putRootHash(rootEntry.hash, generation);
|
|
911
|
-
return { id: hashEnt.id, hash: newEnt.hash };
|
|
1356
|
+
return newEnt;
|
|
912
1357
|
});
|
|
913
1358
|
}
|
|
914
1359
|
/**
|
|
@@ -924,7 +1369,7 @@ class Remarkable {
|
|
|
924
1369
|
* @returns a reference to the updated entry, with its new hash
|
|
925
1370
|
*/
|
|
926
1371
|
async updateDocument(ref, content, refresh = false) {
|
|
927
|
-
return await this.#editContent(ref
|
|
1372
|
+
return await this.#editContent(ref, content, "DocumentType", refresh);
|
|
928
1373
|
}
|
|
929
1374
|
/**
|
|
930
1375
|
* update content metadata for a collection
|
|
@@ -939,7 +1384,7 @@ class Remarkable {
|
|
|
939
1384
|
* @returns a reference to the updated entry, with its new hash
|
|
940
1385
|
*/
|
|
941
1386
|
async updateCollection(ref, content, refresh = false) {
|
|
942
|
-
return await this.#editContent(ref
|
|
1387
|
+
return await this.#editContent(ref, content, "CollectionType", refresh);
|
|
943
1388
|
}
|
|
944
1389
|
/**
|
|
945
1390
|
* update content metadata for a template
|
|
@@ -954,47 +1399,38 @@ class Remarkable {
|
|
|
954
1399
|
* @returns a reference to the updated entry, with its new hash
|
|
955
1400
|
*/
|
|
956
1401
|
async updateTemplate(ref, content, refresh = false) {
|
|
957
|
-
return await this.#editContent(ref
|
|
1402
|
+
return await this.#editContent(ref, content, "TemplateType", refresh);
|
|
958
1403
|
}
|
|
959
|
-
async #editMetaRaw(
|
|
960
|
-
const {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}
|
|
969
|
-
const meta = await this.raw.getMetadata(metaEntry);
|
|
970
|
-
Object.assign(meta, update);
|
|
971
|
-
meta.version = (meta.version ?? 0) + 1;
|
|
972
|
-
meta.metadatamodified = true;
|
|
973
|
-
const [newMetaEntry, uploadMeta] = await this.raw.putMetadata(metaEntry.id, meta);
|
|
974
|
-
entries[metaInd] = newMetaEntry;
|
|
975
|
-
const [result, uploadEntries] = await this.raw.putEntries(id, entries, schemaVersion);
|
|
976
|
-
const upload = Promise.all([uploadMeta, uploadEntries]);
|
|
977
|
-
return [result, upload];
|
|
978
|
-
}
|
|
979
|
-
async #editMeta(hash, update, refresh = false) {
|
|
980
|
-
return await this.#withRetry(async () => {
|
|
981
|
-
const [rootHash, generation, schemaVersion] = await this.#getRootHash(refresh);
|
|
982
|
-
const { entries } = await this.raw.getEntries({
|
|
983
|
-
id: ROOT_SCHEMA,
|
|
984
|
-
hash: rootHash,
|
|
985
|
-
});
|
|
986
|
-
const hashInd = entries.findIndex((ent) => ent.hash === hash);
|
|
987
|
-
const hashEnt = entries[hashInd];
|
|
988
|
-
if (hashEnt === undefined) {
|
|
989
|
-
throw new HashNotFoundError(hash);
|
|
1404
|
+
async #editMetaRaw(ref, update, schemaVersion) {
|
|
1405
|
+
const env_11 = { stack: [], error: void 0, hasError: false };
|
|
1406
|
+
try {
|
|
1407
|
+
const { id } = ref;
|
|
1408
|
+
const { entries } = await this.raw.getEntries(ref);
|
|
1409
|
+
const metaInd = entries.findIndex((ent) => ent.id.endsWith(".metadata"));
|
|
1410
|
+
const metaEntry = entries[metaInd];
|
|
1411
|
+
if (metaEntry === undefined) {
|
|
1412
|
+
throw new Error("internal error: couldn't find metadata in entry hash");
|
|
990
1413
|
}
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
await this
|
|
996
|
-
|
|
997
|
-
|
|
1414
|
+
const meta = await this.raw.getMetadata(metaEntry);
|
|
1415
|
+
Object.assign(meta, update);
|
|
1416
|
+
meta.version = (meta.version ?? 0) + 1;
|
|
1417
|
+
meta.metadatamodified = true;
|
|
1418
|
+
const newMetaEntry = __addDisposableResource(env_11, await this.raw.putMetadata(metaEntry.id, meta), true);
|
|
1419
|
+
entries[metaInd] = newMetaEntry;
|
|
1420
|
+
return await this.raw.putEntries(id, entries, schemaVersion);
|
|
1421
|
+
}
|
|
1422
|
+
catch (e_11) {
|
|
1423
|
+
env_11.error = e_11;
|
|
1424
|
+
env_11.hasError = true;
|
|
1425
|
+
}
|
|
1426
|
+
finally {
|
|
1427
|
+
const result_11 = __disposeResources(env_11);
|
|
1428
|
+
if (result_11)
|
|
1429
|
+
await result_11;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
async #editMeta(ref, update, refresh = false) {
|
|
1433
|
+
return await this.#editEntry(ref, refresh, (item, schemaVersion) => this.#editMetaRaw(item, update, schemaVersion));
|
|
998
1434
|
}
|
|
999
1435
|
/**
|
|
1000
1436
|
* move an entry
|
|
@@ -1012,7 +1448,7 @@ class Remarkable {
|
|
|
1012
1448
|
if (!idReg.test(parent)) {
|
|
1013
1449
|
throw new ValidationError(parent, idReg, "parent must be a valid document id");
|
|
1014
1450
|
}
|
|
1015
|
-
return await this.#editMeta(ref
|
|
1451
|
+
return await this.#editMeta(ref, { parent }, refresh);
|
|
1016
1452
|
}
|
|
1017
1453
|
/**
|
|
1018
1454
|
* delete an entry
|
|
@@ -1039,7 +1475,7 @@ class Remarkable {
|
|
|
1039
1475
|
* @returns a reference to the renamed entry, with its new hash
|
|
1040
1476
|
*/
|
|
1041
1477
|
async rename(ref, visibleName, refresh = false) {
|
|
1042
|
-
return await this.#editMeta(ref
|
|
1478
|
+
return await this.#editMeta(ref, { visibleName }, refresh);
|
|
1043
1479
|
}
|
|
1044
1480
|
/**
|
|
1045
1481
|
* star or unstar an entry
|
|
@@ -1053,7 +1489,7 @@ class Remarkable {
|
|
|
1053
1489
|
* @returns a reference to the updated entry, with its new hash
|
|
1054
1490
|
*/
|
|
1055
1491
|
async star(ref, starred, refresh = false) {
|
|
1056
|
-
return await this.#editMeta(ref
|
|
1492
|
+
return await this.#editMeta(ref, { pinned: starred }, refresh);
|
|
1057
1493
|
}
|
|
1058
1494
|
/**
|
|
1059
1495
|
* move many entries
|
|
@@ -1074,27 +1510,56 @@ class Remarkable {
|
|
|
1074
1510
|
return await this.#withRetry(async () => {
|
|
1075
1511
|
const [rootHash, generation, schemaVersion] = await this.#getRootHash(refresh);
|
|
1076
1512
|
const { entries } = await this.raw.getEntries({
|
|
1077
|
-
id:
|
|
1513
|
+
id: ROOT_LIST,
|
|
1078
1514
|
hash: rootHash,
|
|
1079
1515
|
});
|
|
1080
|
-
const
|
|
1516
|
+
const wanted = new Set(refs.map((ref) => `${ref.id}\0${ref.hash}`));
|
|
1517
|
+
const found = new Set();
|
|
1081
1518
|
const toUpdate = [];
|
|
1082
1519
|
const newEntries = [];
|
|
1083
1520
|
for (const entry of entries) {
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1521
|
+
const key = `${entry.id}\0${entry.hash}`;
|
|
1522
|
+
if (wanted.has(key)) {
|
|
1523
|
+
toUpdate.push(entry);
|
|
1524
|
+
found.add(key);
|
|
1525
|
+
}
|
|
1526
|
+
else {
|
|
1527
|
+
newEntries.push(entry);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
for (const ref of refs) {
|
|
1531
|
+
if (!found.has(`${ref.id}\0${ref.hash}`)) {
|
|
1532
|
+
throw new HashNotFoundError(ref.hash);
|
|
1533
|
+
}
|
|
1086
1534
|
}
|
|
1087
|
-
const resolved = await Promise.all(toUpdate.map((
|
|
1088
|
-
const uploads = [];
|
|
1535
|
+
const resolved = await Promise.all(toUpdate.map((entry) => this.#editMetaRaw(entry, { parent }, schemaVersion)));
|
|
1089
1536
|
const result = [];
|
|
1090
|
-
for (const [i,
|
|
1537
|
+
for (const [i, newEnt] of resolved.entries()) {
|
|
1091
1538
|
newEntries.push(newEnt);
|
|
1092
|
-
uploads.push(upload);
|
|
1093
1539
|
result.push({ id: toUpdate[i].id, hash: newEnt.hash });
|
|
1094
1540
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1541
|
+
let newRoot;
|
|
1542
|
+
{
|
|
1543
|
+
const env_12 = { stack: [], error: void 0, hasError: false };
|
|
1544
|
+
try {
|
|
1545
|
+
const docs = __addDisposableResource(env_12, new AsyncDisposableStack(), true);
|
|
1546
|
+
for (const entry of resolved) {
|
|
1547
|
+
docs.use(entry);
|
|
1548
|
+
}
|
|
1549
|
+
const rootEntry = __addDisposableResource(env_12, await this.raw.putEntries(ROOT_LIST, newEntries, 4), true);
|
|
1550
|
+
newRoot = rootEntry.hash;
|
|
1551
|
+
}
|
|
1552
|
+
catch (e_12) {
|
|
1553
|
+
env_12.error = e_12;
|
|
1554
|
+
env_12.hasError = true;
|
|
1555
|
+
}
|
|
1556
|
+
finally {
|
|
1557
|
+
const result_12 = __disposeResources(env_12);
|
|
1558
|
+
if (result_12)
|
|
1559
|
+
await result_12;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
await this.#putRootHash(newRoot, generation);
|
|
1098
1563
|
return result;
|
|
1099
1564
|
});
|
|
1100
1565
|
}
|
|
@@ -1145,15 +1610,15 @@ class Remarkable {
|
|
|
1145
1610
|
// should only go one step) to track all hashes encountered
|
|
1146
1611
|
// NOTE that we could increase the cache in this process, or it's possible
|
|
1147
1612
|
// for other calls to increase the cache with misc values.
|
|
1148
|
-
const base = await this.raw.getEntries({ id:
|
|
1613
|
+
const base = await this.raw.getEntries({ id: ROOT_LIST, hash: rootHash });
|
|
1149
1614
|
let entries = [base.entries];
|
|
1150
1615
|
let nextEntries = [];
|
|
1151
1616
|
while (entries.length) {
|
|
1152
1617
|
for (const entryList of entries) {
|
|
1153
|
-
for (const
|
|
1154
|
-
toDelete.delete(hash);
|
|
1155
|
-
if (subfiles > 0) {
|
|
1156
|
-
nextEntries.push(this.raw.getEntries(
|
|
1618
|
+
for (const entry of entryList) {
|
|
1619
|
+
toDelete.delete(entry.hash);
|
|
1620
|
+
if (entry.subfiles > 0) {
|
|
1621
|
+
nextEntries.push(this.raw.getEntries(entry));
|
|
1157
1622
|
}
|
|
1158
1623
|
}
|
|
1159
1624
|
}
|
|
@@ -1175,7 +1640,46 @@ class Remarkable {
|
|
|
1175
1640
|
this.raw.clearCache();
|
|
1176
1641
|
}
|
|
1177
1642
|
}
|
|
1178
|
-
|
|
1643
|
+
/** the default {@link RemarkableSessionOptions.maxCachedBytes} */
|
|
1644
|
+
const MAX_CACHED_BYTES = 1024 * 1024;
|
|
1645
|
+
const cacheEntries = z.record(z.string(), z.string().nullable());
|
|
1646
|
+
/**
|
|
1647
|
+
* a dumped cache, either the tagged envelope or the original bare mapping
|
|
1648
|
+
*
|
|
1649
|
+
* The original format had no version, so its absence marks untagged text.
|
|
1650
|
+
*/
|
|
1651
|
+
const cacheDump = z
|
|
1652
|
+
.object({
|
|
1653
|
+
version: z.literal(CACHE_VERSION),
|
|
1654
|
+
entries: cacheEntries,
|
|
1655
|
+
})
|
|
1656
|
+
.or(cacheEntries.transform((entries) => ({ version: undefined, entries })));
|
|
1657
|
+
/** decode a dumped cache into the byte map the api holds */
|
|
1658
|
+
function decodeCache(dumped) {
|
|
1659
|
+
const parsed = cacheDump.safeParse(dumped);
|
|
1660
|
+
if (!parsed.success) {
|
|
1661
|
+
throw new Error(`cache was neither a version ${CACHE_VERSION} dump nor the original mapping of hashes to text. Either construct the api without a cache, or fix its format.`);
|
|
1662
|
+
}
|
|
1663
|
+
const { version, entries } = parsed.data;
|
|
1664
|
+
const enc = new TextEncoder();
|
|
1665
|
+
return Object.entries(entries).map(([hash, value]) => {
|
|
1666
|
+
if (value === null) {
|
|
1667
|
+
return [hash, null];
|
|
1668
|
+
}
|
|
1669
|
+
else if (version === undefined) {
|
|
1670
|
+
return [hash, enc.encode(value)];
|
|
1671
|
+
}
|
|
1672
|
+
else if (value.startsWith(BYTES_PREFIX)) {
|
|
1673
|
+
return [hash, Uint8Array.fromBase64(value.slice(1))];
|
|
1674
|
+
}
|
|
1675
|
+
else if (value.startsWith(TEXT_PREFIX)) {
|
|
1676
|
+
return [hash, enc.encode(value.slice(1))];
|
|
1677
|
+
}
|
|
1678
|
+
else {
|
|
1679
|
+
throw new Error(`cache entry ${hash} wasn't tagged '${TEXT_PREFIX}' or '${BYTES_PREFIX}'. Either construct the api without a cache, or fix its format.`);
|
|
1680
|
+
}
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1179
1683
|
/**
|
|
1180
1684
|
* Exchange a device token for a session token.
|
|
1181
1685
|
*
|
|
@@ -1204,17 +1708,12 @@ export async function auth(deviceToken, { authHost = AUTH_HOST } = {}) {
|
|
|
1204
1708
|
* @param sessionToken - the session token used for authorization
|
|
1205
1709
|
* @returns an api instance
|
|
1206
1710
|
*/
|
|
1207
|
-
export function session(sessionToken, { rawHost = RAW_HOST, uploadHost = UPLOAD_HOST, cache, maxCacheSize = Infinity, maxGenerationRetries = 10, maxTransientRetries = 3, } = {}) {
|
|
1208
|
-
const
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
? new Map(entries)
|
|
1214
|
-
: new LruCache(maxCacheSize, entries);
|
|
1215
|
-
return new Remarkable(sessionToken, rawHost, uploadHost, cacheMap, maxGenerationRetries, maxTransientRetries);
|
|
1216
|
-
}
|
|
1217
|
-
throw new Error("cache was not a valid cache (json string mapping); your cache must be corrupted somehow. Either initialize remarkable without a cache, or fix its format.");
|
|
1711
|
+
export function session(sessionToken, { rawHost = RAW_HOST, uploadHost = UPLOAD_HOST, cache, maxCacheSize = Infinity, maxCachedBytes = MAX_CACHED_BYTES, maxGenerationRetries = 10, maxTransientRetries = 3, } = {}) {
|
|
1712
|
+
const entries = decodeCache(JSON.parse(cache ?? "{}"));
|
|
1713
|
+
const cacheMap = maxCacheSize === Infinity
|
|
1714
|
+
? new Map(entries)
|
|
1715
|
+
: new LruCache(maxCacheSize, entries);
|
|
1716
|
+
return new Remarkable(sessionToken, rawHost, uploadHost, cacheMap, maxGenerationRetries, maxTransientRetries, maxCachedBytes);
|
|
1218
1717
|
}
|
|
1219
1718
|
/**
|
|
1220
1719
|
* create an instance of the api
|
|
@@ -1227,14 +1726,9 @@ export function session(sessionToken, { rawHost = RAW_HOST, uploadHost = UPLOAD_
|
|
|
1227
1726
|
* @returns an api instance
|
|
1228
1727
|
*/
|
|
1229
1728
|
export async function remarkable(deviceToken, options = {}) {
|
|
1230
|
-
|
|
1729
|
+
// forward everything but the auth option, so a new session option can't be
|
|
1730
|
+
// dropped here by omission
|
|
1731
|
+
const { authHost, ...sessionOptions } = options ?? {};
|
|
1231
1732
|
const sessionToken = await auth(deviceToken, { authHost });
|
|
1232
|
-
return session(sessionToken,
|
|
1233
|
-
rawHost,
|
|
1234
|
-
uploadHost,
|
|
1235
|
-
cache,
|
|
1236
|
-
maxCacheSize,
|
|
1237
|
-
maxGenerationRetries,
|
|
1238
|
-
maxTransientRetries,
|
|
1239
|
-
});
|
|
1733
|
+
return session(sessionToken, sessionOptions);
|
|
1240
1734
|
}
|