@docstack/pouchdb-adapter-googledrive 0.1.5 → 0.1.6
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/CHANGELOG.md +180 -0
- package/LICENSE +10 -0
- package/README.md +52 -1
- package/lib/adapter.js +307 -116
- package/lib/client.d.ts +2 -0
- package/lib/client.js +6 -4
- package/lib/drive.d.ts +136 -2
- package/lib/drive.js +647 -90
- package/lib/types.d.ts +34 -4
- package/package.json +4 -2
- package/.env.example +0 -9
package/lib/drive.d.ts
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
import { GoogleDriveAdapterOptions, ChangeEntry, IndexEntry } from './types';
|
|
1
|
+
import { GoogleDriveAdapterOptions, ChangeEntry, IndexEntry, FilePointer } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Sequence numbers are `tick * SEQ_SLOTS + writerSlot`.
|
|
4
|
+
*
|
|
5
|
+
* Writers mint them blind to one another - there is no lock and no compare-and-swap -
|
|
6
|
+
* so two clients reading the same counter will always be able to derive the same next
|
|
7
|
+
* tick. Reserving the low digits for a per-writer slot means they can share a tick and
|
|
8
|
+
* still never share a *sequence number*, which is the part that matters: `_changes`
|
|
9
|
+
* filters on `seq > since`, so a second document sharing a checkpointed sequence
|
|
10
|
+
* number is never emitted to a replication target again.
|
|
11
|
+
*
|
|
12
|
+
* A million slots keeps the chance that two concurrent writers hash to the same one
|
|
13
|
+
* near 1 in 20,000 for a ten-client fleet, and leaves room for 9e9 ticks inside
|
|
14
|
+
* Number.MAX_SAFE_INTEGER.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SEQ_SLOTS = 1000000;
|
|
17
|
+
/** Stable slot for a writer id. FNV-1a, folded into the slot space. */
|
|
18
|
+
export declare function writerSlotFor(writerId: string): number;
|
|
19
|
+
/** The writer id embedded in a change-log filename, if the name carries one.
|
|
20
|
+
* New format: changes-<seq>-<writerId>-<random>.ndjson (4 dash-parts).
|
|
21
|
+
* Old format: changes-<seq>-<random>.ndjson (3 parts) - no id to extract. */
|
|
22
|
+
export declare function writerIdFromLogName(name: string): string | null;
|
|
2
23
|
/**
|
|
3
24
|
* DriveHandler - Lazy Loading Implementation
|
|
4
25
|
*
|
|
@@ -22,6 +43,27 @@ export declare class DriveHandler {
|
|
|
22
43
|
private metaMd5;
|
|
23
44
|
private metaModifiedTime;
|
|
24
45
|
private localDocsEtag;
|
|
46
|
+
private metaFileId;
|
|
47
|
+
/** Identifies this handler among the writers sharing a folder. Change-log
|
|
48
|
+
* filenames carry it, so two writers can never produce the same name and an
|
|
49
|
+
* orphaned log can be traced back to whoever wrote it. Not readonly: if the
|
|
50
|
+
* folder shows another writer whose id hashes to our sequence slot, we re-roll
|
|
51
|
+
* to a free one before minting anything (see rerollIfSlotContested). */
|
|
52
|
+
private writerId;
|
|
53
|
+
/** Change-log file ids this handler wrote, minus any a compaction has retired.
|
|
54
|
+
* Drive API v3 has no compare-and-swap (ETags were dropped), so another
|
|
55
|
+
* writer's read-modify-write of _meta.json can drop a log id that landed in
|
|
56
|
+
* between. Anything still in here but missing from the remote changeLogIds was
|
|
57
|
+
* dropped that way and gets put back - see reconcileOwnLogs(). */
|
|
58
|
+
private ownLogIds;
|
|
59
|
+
/** This writer's reservation in the low digits of every sequence number it mints.
|
|
60
|
+
* Derived from writerId; changes only when writerId is re-rolled off a
|
|
61
|
+
* contested slot. */
|
|
62
|
+
private writerSlot;
|
|
63
|
+
/** Serializes this handler's own _meta.json read-modify-write cycles. Says
|
|
64
|
+
* nothing about other clients - that is what commitMeta's verify pass is for -
|
|
65
|
+
* but stops one handler racing itself when several writes are in flight. */
|
|
66
|
+
private metaLock;
|
|
25
67
|
private index;
|
|
26
68
|
private docCache;
|
|
27
69
|
private pendingChanges;
|
|
@@ -48,6 +90,15 @@ export declare class DriveHandler {
|
|
|
48
90
|
* Index -> Cache -> Fetch
|
|
49
91
|
*/
|
|
50
92
|
get(id: string): Promise<any | null>;
|
|
93
|
+
/**
|
|
94
|
+
* Fetch a specific (possibly non-winning/conflicting) revision's body by its
|
|
95
|
+
* own tracked location - used by _get(opts.rev)/_bulkGet when the requested
|
|
96
|
+
* rev isn't the current winner. Deliberately doesn't touch docCache (keyed
|
|
97
|
+
* per-id, not per-rev - a conflict fetch is rare enough not to warrant a
|
|
98
|
+
* per-rev cache key) and doesn't force `_rev` to the index's winning rev the
|
|
99
|
+
* way get() does.
|
|
100
|
+
*/
|
|
101
|
+
getRevisionBody(id: string, rev: string, location: FilePointer): Promise<any | null>;
|
|
51
102
|
/** Generic Download with Caching and Parsing */
|
|
52
103
|
private fetchFile;
|
|
53
104
|
/** Get multiple docs (Atomic-ish) used for _allDocs */
|
|
@@ -62,21 +113,104 @@ export declare class DriveHandler {
|
|
|
62
113
|
appendChanges(changes: ChangeEntry[]): Promise<void>;
|
|
63
114
|
private appendLocalDocs;
|
|
64
115
|
private tryAppendChanges;
|
|
116
|
+
/** Forget a change log we uploaded but never managed to reference from
|
|
117
|
+
* _meta.json. Nothing points at it, so it is safe to remove. */
|
|
118
|
+
private discardLog;
|
|
65
119
|
/** Update Index with a new change */
|
|
66
120
|
private updateIndex;
|
|
67
121
|
private checkConflicts;
|
|
68
122
|
/** Compact: Create SnapshotIndex + SnapshotData */
|
|
69
123
|
compact(): Promise<void>;
|
|
70
|
-
|
|
124
|
+
/** Run `fn` after every meta mutation this handler has already queued has
|
|
125
|
+
* settled. Never call this from inside a commitMeta modifier - it would wait on
|
|
126
|
+
* itself. */
|
|
127
|
+
private withMetaLock;
|
|
128
|
+
private backoff;
|
|
129
|
+
/** Locate _meta.json and read its body straight from Drive, past every cache. */
|
|
130
|
+
private readRemoteMeta;
|
|
131
|
+
/** Read a known _meta.json by id - no folder listing, no cache. Used by the
|
|
132
|
+
* post-write verification pass, which only needs the body. */
|
|
133
|
+
private readMetaBody;
|
|
134
|
+
private adoptMeta;
|
|
135
|
+
/** True when the folder holds changes this handler has not replayed - another
|
|
136
|
+
* writer appended, or compacted, since our last load. */
|
|
137
|
+
private hasUnprocessedLogs;
|
|
138
|
+
/** True when a change log we wrote has fallen out of `changeLogIds` without a
|
|
139
|
+
* compaction retiring it - i.e. someone else's write dropped it. */
|
|
140
|
+
private hasOrphanedOwnLogs;
|
|
141
|
+
/** Put back any such log. Returns `latest` by identity when there is nothing to
|
|
142
|
+
* repair, so callers can tell the two cases apart. */
|
|
143
|
+
private reconcileOwnLogs;
|
|
144
|
+
/**
|
|
145
|
+
* Read-modify-write `_meta.json`.
|
|
146
|
+
*
|
|
147
|
+
* `modify` receives the current remote metadata (with any of our dropped logs
|
|
148
|
+
* already restored, which `repaired` reports) and returns the value to write, or
|
|
149
|
+
* null to abandon the commit because its assumptions no longer hold. This method
|
|
150
|
+
* also returns null when it runs out of attempts. Either way nothing durable has
|
|
151
|
+
* changed and the caller must not act as though it had.
|
|
152
|
+
*/
|
|
153
|
+
private commitMeta;
|
|
154
|
+
/** Create _meta.json for a folder that has none. Two clients opening the same
|
|
155
|
+
* empty folder both end up here and Drive will happily keep two files with the
|
|
156
|
+
* same name, after which every client picks between them at random. Settle it
|
|
157
|
+
* deterministically instead: lowest file id wins, the loser deletes its own and
|
|
158
|
+
* adopts the winner. */
|
|
159
|
+
private ensureMetaFile;
|
|
71
160
|
private findOrCreateFolder;
|
|
161
|
+
/** Every change log in the folder, whatever _meta.json has to say about them.
|
|
162
|
+
*
|
|
163
|
+
* The folder is the authority on which change logs exist; _meta.json is only a
|
|
164
|
+
* cache of that, and a lossy one - it is a whole-file read-modify-write with no
|
|
165
|
+
* compare-and-swap behind it, so a writer whose metadata lands and is then
|
|
166
|
+
* overwritten by a slower writer loses its reference. The file is still right
|
|
167
|
+
* here. Listing for it is what stops a lost update from becoming a lost
|
|
168
|
+
* document. */
|
|
169
|
+
private listChangeLogs;
|
|
170
|
+
/**
|
|
171
|
+
* Give up a sequence slot another writer is already using.
|
|
172
|
+
*
|
|
173
|
+
* Slots make sequence collisions structurally impossible only between writers on
|
|
174
|
+
* *different* slots; two ids hashing to the same slot are back to the dense
|
|
175
|
+
* allocation this scheme replaced. The filenames the folder listing hands us
|
|
176
|
+
* carry every writer's id, so a contested slot is visible - and since this
|
|
177
|
+
* handler re-rolls before minting anything against what it just saw, the
|
|
178
|
+
* exposure shrinks from "the whole session" to "rival's first log not yet
|
|
179
|
+
* visible in a listing".
|
|
180
|
+
*
|
|
181
|
+
* Logs already written keep their old name and numbers; ownLogIds tracks file
|
|
182
|
+
* ids, not names, so nothing else cares.
|
|
183
|
+
*/
|
|
184
|
+
private rerollIfSlotContested;
|
|
185
|
+
/** Every file in the folder with this name. Drive allows duplicates, so this is
|
|
186
|
+
* how the callers that care (see ensureMetaFile) find out there are any. */
|
|
187
|
+
private findFiles;
|
|
72
188
|
private findFile;
|
|
73
189
|
private downloadJson;
|
|
74
190
|
private downloadFileAny;
|
|
75
191
|
private downloadNdjson;
|
|
76
192
|
private writeChangeFile;
|
|
193
|
+
/** Write `meta` to _meta.json. `target` is the file to write, as already
|
|
194
|
+
* located by the caller; pass null to force creation, or omit it to look the
|
|
195
|
+
* file up. */
|
|
77
196
|
private saveMeta;
|
|
78
197
|
private countTotalChanges;
|
|
79
198
|
private cleanupOldFiles;
|
|
199
|
+
/**
|
|
200
|
+
* Watch _meta.json for writes by other clients.
|
|
201
|
+
*
|
|
202
|
+
* This is the only thing that makes `db.changes({ live: true })` fire for a
|
|
203
|
+
* *remote* write: on a change it calls load(), which replays the newly
|
|
204
|
+
* referenced logs and emits exactly those through notifyListeners. Without it a
|
|
205
|
+
* client only ever hears about what it wrote itself, so connect-and-read works
|
|
206
|
+
* and continuous sync between two connected clients does not.
|
|
207
|
+
*
|
|
208
|
+
* Change is detected by md5Checksum, falling back to modifiedTime. There is
|
|
209
|
+
* deliberately no ETag comparison: Drive API v3 has none (see
|
|
210
|
+
* docs/adr/0001-metadata-writes-without-compare-and-swap.md), so that branch
|
|
211
|
+
* could only ever compare '' against '' - and sitting first in the chain, it
|
|
212
|
+
* shadowed the two comparisons that do work.
|
|
213
|
+
*/
|
|
80
214
|
private startPolling;
|
|
81
215
|
private notifyListeners;
|
|
82
216
|
onChange(cb: (changes: Record<string, any>) => void): () => void;
|