selfstore 1.8.7 → 1.8.9

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.
@@ -1,12 +1,12 @@
1
- import { B as BackupTarget } from './store-BiQ-BuVl.js';
2
- export { A as AuthExpiredError, a as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, b as LocalStoreOptions, c as LocalStoreState, M as MirrorState, d as Mode, e as PasswordCheck, f as PasswordPolicy, g as PasswordRequirement, P as PeerSource, h as PeerState, R as RESERVED_COLLECTION_PREFIX, i as RESERVED_STORE_MODES, j as RecipientStanza, k as ReplicaState, S as SelfstoreError, l as SelfstoreErrorCode, m as Severity, n as Snapshot, o as SnapshotFile, p as StatusAction, q as StatusDescriptor, r as StatusInput, s as StorageState, t as StoreError, u as StoreGroupConfig, v as SyncJournalEntry, w as SyncSource, T as TargetKind, x as checkPasswordPolicy, y as createLocalStore, z as deriveStatus, C as errorLabelKey, D as isAuthExpired, F as isReservedStoreMode, G as isSelfstoreError } from './store-BiQ-BuVl.js';
1
+ import { B as BackupTarget } from './store-CiLoRJmy.js';
2
+ export { A as AuthExpiredError, a as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, b as LocalStoreOptions, c as LocalStoreState, M as MirrorState, d as Mode, e as PasswordCheck, f as PasswordPolicy, g as PasswordRequirement, P as PeerSource, h as PeerState, R as RESERVED_COLLECTION_PREFIX, i as RESERVED_STORE_MODES, j as RecipientStanza, k as ReplicaState, l as SelfstoreError, m as SelfstoreErrorCode, n as Severity, o as Snapshot, p as SnapshotFile, q as StatusAction, r as StatusDescriptor, s as StatusInput, t as StorageState, S as StoreError, u as StoreGroupConfig, v as SyncJournalEntry, w as SyncSource, T as TargetKind, x as checkPasswordPolicy, y as createLocalStore, z as deriveStatus, C as errorLabelKey, D as isAuthExpired, F as isReservedStoreMode, G as isSelfstoreError } from './store-CiLoRJmy.js';
3
3
  import { K as KV } from './cache-ilsK3Tki.js';
4
4
  export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-ilsK3Tki.js';
5
- export { G as GisDriveAuthOptions, a as GisTokenPersistence, d as datedName, e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, b as inspect, c as isEncrypted, p as pickFromDisk, s as saveToDisk } from './local-VLHtF3vT.js';
6
- import { c as connect$2, f as fromSession$2, a as connect$3, b as fromSession$3, p as peer } from './s3-DvsnA-FK.js';
7
- export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-DvsnA-FK.js';
8
- import { c as connect$1, f as fromSession$1, p as preview, a as adopt, b as findOrCreateOwnFile, l as listBackups, d as createBackup, e as deleteBackup, r as renameBackup, g as account } from './drive-CTYvGg_7.js';
9
- export { h as DriveAccount, i as DriveAuth, j as DriveBackupInfo, D as DriveOptions } from './drive-CTYvGg_7.js';
5
+ export { G as GisDriveAuthOptions, a as GisTokenPersistence, d as datedName, e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, b as inspect, c as isEncrypted, p as pickFromDisk, s as saveToDisk } from './local-CldSa9EY.js';
6
+ import { c as connect$2, f as fromSession$2, a as connect$3, b as fromSession$3, p as peer } from './s3-DgyTwMRc.js';
7
+ export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-DgyTwMRc.js';
8
+ import { c as connect$1, f as fromSession$1, p as preview, b as adopt, d as findOrCreateOwnFile, l as listBackups, e as createBackup, g as deleteBackup, r as renameBackup, h as account } from './drive-B2fm71qD.js';
9
+ export { i as DriveAccount, a as DriveAuth, j as DriveBackupInfo, D as DriveOptions } from './drive-B2fm71qD.js';
10
10
  import './sync.js';
11
11
 
12
12
  /**
package/dist/backups.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { B as BackupsHost } from './manager-DZ1c_aSS.js';
2
- export { a as BackupFileInfo, b as BackupOwner, c as BackupRow, d as BackupsErrorCode, e as BackupsKeys, f as BackupsManager, g as BackupsNaming, h as BackupsSnapshot, K as KnownBackups, i as createBackupsManager } from './manager-DZ1c_aSS.js';
3
- import { D as DriveOptions } from './drive-CTYvGg_7.js';
1
+ import { B as BackupsHost } from './manager-BqzTVaep.js';
2
+ export { a as BackupFileInfo, b as BackupOwner, c as BackupRow, d as BackupsErrorCode, e as BackupsKeys, f as BackupsManager, g as BackupsNaming, h as BackupsSnapshot, K as KnownBackups, i as createBackupsManager } from './manager-BqzTVaep.js';
3
+ import { D as DriveOptions } from './drive-B2fm71qD.js';
4
4
  import './cache-ilsK3Tki.js';
5
- import './store-BiQ-BuVl.js';
5
+ import './store-CiLoRJmy.js';
6
6
  import './sync.js';
7
7
  import './machine-ALMkT3mk.js';
8
8
 
@@ -1,7 +1,7 @@
1
- import { t as StoreError, B as BackupTarget, L as LocalStore, k as ReplicaState } from './store-BiQ-BuVl.js';
1
+ import { S as StoreError, B as BackupTarget, L as LocalStore } from './store-CiLoRJmy.js';
2
2
  import { K as KV } from './cache-ilsK3Tki.js';
3
- import { i as DriveAuth } from './drive-CTYvGg_7.js';
4
- import { W as WebdavConfig, S as S3Config } from './s3-DvsnA-FK.js';
3
+ import { a as DriveAuth } from './drive-B2fm71qD.js';
4
+ import { W as WebdavConfig, S as S3Config } from './s3-DgyTwMRc.js';
5
5
  import { F as FlowStore } from './machine-ALMkT3mk.js';
6
6
 
7
7
  type ConnectKind = 'drive' | 'file' | 'webdav' | 's3';
@@ -189,179 +189,4 @@ interface ConnectFlow extends FlowStore<ConnectSnapshot> {
189
189
  }
190
190
  declare function connectFlow(store: StoreLike, targets: ConnectTargets, options?: ConnectFlowOptions): ConnectFlow;
191
191
 
192
- /** The one replica this flow manages, as its id on the engine. */
193
- declare const REPLICA_ID = "replica";
194
- type ReplicaStep = 'idle' | 'choose' | 'form-webdav' | 'form-s3';
195
- interface ReplicaSnapshot {
196
- step: ReplicaStep;
197
- /** The kinds the host offers, in ConnectTargets order. */
198
- kinds: ConnectKind[];
199
- busy: boolean;
200
- error: StoreError | null;
201
- /** The live engine entry (label, lastPublishAt, lastError), null when none. */
202
- replica: ReplicaState | null;
203
- }
204
- interface ReplicaFlowOptions {
205
- /** File name of the copy on its destination. Default: the primary backup
206
- * name with `.replica` before the extension ("App.zip" -> "App.replica.zip"),
207
- * which a same-account Drive never confuses with the primary file and the
208
- * backups panel never lists as a backup of its own. */
209
- fileName?: string;
210
- /** Rebuild the target of a custom Connector kind on restore() (built-in
211
- * kinds rebuild by themselves). Without it, custom kinds are not restored. */
212
- restoreTarget?: (kind: ConnectKind) => Promise<BackupTarget | null>;
213
- }
214
- interface ReplicaFlow extends FlowStore<ReplicaSnapshot> {
215
- /** Show the destination choice (from the user's gesture). */
216
- open(): void;
217
- /** Back to idle; never an error. */
218
- cancel(): void;
219
- /** Run a destination (inside the user's click - pickers and consent popups
220
- * open from it). `webdav: true` / `s3: true` route to their form step. */
221
- pick(kind: ConnectKind): void;
222
- /** 'form-webdav' step: submit the server config. */
223
- submitWebdav(config: WebdavConfig): void;
224
- /** 'form-s3' step: submit the bucket config. */
225
- submitS3(config: S3Config): void;
226
- /** Detach the copy and forget its session. Never deletes remote data. */
227
- remove(): Promise<void>;
228
- /** Silent reboot: re-attach the copy recorded by a past session, if any.
229
- * Never throws - an unreachable destination retries at the next boot. */
230
- restore(): Promise<void>;
231
- /** Detach from the engine (the replica itself stays attached). */
232
- dispose(): void;
233
- }
234
- declare function replicaFlow(store: StoreLike, targets: ConnectTargets, options?: ReplicaFlowOptions): ReplicaFlow;
235
-
236
- type ShareLevel = 'read' | 'write';
237
- interface ShareLinkInfo {
238
- /** Engine-scoped stable id (revoke keys on it). */
239
- id: string;
240
- /** The invitation URL to hand out. */
241
- url: string;
242
- level: ShareLevel;
243
- }
244
- interface ShareMemberInfo {
245
- /** Engine-scoped stable id (removeMember keys on it). */
246
- id: string;
247
- label?: string;
248
- /** This row is the local user. */
249
- self?: boolean;
250
- /** This row owns the share (the app may render a badge, the flow does not care). */
251
- owner?: boolean;
252
- }
253
- /** The app-side port the flow drives. Implementations wrap whatever transport
254
- * the app shares through; every method may throw (SelfstoreError preferred,
255
- * anything is normalized). */
256
- interface ShareEngine {
257
- /** The current truth: active links and members. */
258
- list(): Promise<{
259
- links: ShareLinkInfo[];
260
- members: ShareMemberInfo[];
261
- }>;
262
- createLink(opts: {
263
- level: ShareLevel;
264
- }): Promise<ShareLinkInfo>;
265
- revokeLink(id: string): Promise<void>;
266
- /** Optional: engines without member management simply omit it. */
267
- removeMember?(id: string): Promise<void>;
268
- /** Optional: END the whole share in one gesture - every link dead, every
269
- * member out. Engines whose transport has such a move (an invitation to
270
- * kill, a file to re-privatize) expose it; the widget renders the action
271
- * only then. */
272
- revokeAll?(): Promise<void>;
273
- }
274
- type ShareBusy = 'refresh' | 'create' | 'revoke' | 'remove' | 'revoke-all' | null;
275
- interface ShareSnapshot {
276
- links: readonly ShareLinkInfo[];
277
- members: readonly ShareMemberInfo[];
278
- /** The operation in flight, or null. */
279
- busy: ShareBusy;
280
- /** The last refresh failed: the lists shown are the last-known good ones. */
281
- stale: boolean;
282
- /** The last operation's problem (the lists are never cleared by an error). */
283
- error: StoreError | null;
284
- /** True once the engine offers removeMember (render the row action at all). */
285
- canRemoveMembers: boolean;
286
- /** True once the engine offers revokeAll (render the end-the-share action). */
287
- canRevokeAll: boolean;
288
- }
289
- interface ShareFlow extends FlowStore<ShareSnapshot> {
290
- /** Re-list from the engine. Failure keeps the last-known lists (stale). */
291
- refresh(): Promise<void>;
292
- /** Create an invitation link. Resolves to it, or null when refused (an
293
- * operation was already in flight). */
294
- createLink(opts: {
295
- level: ShareLevel;
296
- }): Promise<ShareLinkInfo | null>;
297
- /** Revoke a link. The link leaves the snapshot only once the engine
298
- * confirmed. Resolves to whether it did. */
299
- revokeLink(id: string): Promise<boolean>;
300
- /** Remove a member (never yourself - refused up front). Resolves to whether
301
- * the engine confirmed. */
302
- removeMember(id: string): Promise<boolean>;
303
- /** End the whole share: every link and member leaves the snapshot only
304
- * once the engine confirmed. Resolves to whether it did. */
305
- revokeAll(): Promise<boolean>;
306
- }
307
- declare function shareFlow(engine: ShareEngine, options?: {
308
- deadlineMs?: number;
309
- }): ShareFlow;
310
-
311
- /** What the user is about to join - enough to render an honest question. */
312
- interface JoinPreview {
313
- /** The share's display name, when the link carries one. */
314
- label?: string;
315
- /** Who shared it (a name, an email - whatever the engine knows). */
316
- from?: string;
317
- level?: ShareLevel;
318
- }
319
- /** How joining ended, other than plain failure:
320
- * 'joined' - this device now follows the share.
321
- * 'mismatch' - this device already follows a DIFFERENT share: joining a
322
- * second one is refused (leave the first, or use another
323
- * device/profile).
324
- * 'no-invite' - the invitation is spent, expired, or aimed at another
325
- * account (switching accounts may be the fix). */
326
- type JoinOutcome = 'joined' | 'mismatch' | 'no-invite';
327
- /** The app-side port: how a link is read and honoured on this transport. */
328
- interface JoinEngine {
329
- /** Read-only look at the invitation. Must change NOTHING. */
330
- preview(link: string): Promise<JoinPreview>;
331
- /** Consume the invitation and wire the share. Resolve null when the user
332
- * backed out of a popup the engine had to open (an account chooser, a
333
- * consent screen): the flow returns to 'ready' silently - backing out of
334
- * a human gesture is not an error, here as everywhere in these flows. */
335
- join(link: string): Promise<JoinOutcome | null>;
336
- /** Optional: re-run the account chooser (the invite may belong to another
337
- * account). The flow re-previews after it. */
338
- switchAccount?(): Promise<void>;
339
- }
340
- type JoinStep = 'previewing' | 'ready' | 'joining' | 'joined' | 'mismatch' | 'no-invite' | 'error';
341
- interface JoinSnapshot {
342
- /** The invitation this flow was opened with - held here so the UI can
343
- * always find it again. */
344
- link: string;
345
- step: JoinStep;
346
- /** Set once previewed (kept through errors, so retry re-renders context). */
347
- preview: JoinPreview | null;
348
- /** The engine offers an account switch. */
349
- canSwitchAccount: boolean;
350
- busy: boolean;
351
- /** Set when step === 'error': show `labelKey`, log `message`. */
352
- error: StoreError | null;
353
- }
354
- interface JoinFlow extends FlowStore<JoinSnapshot> {
355
- /** Say yes: consume the invitation. Only from 'ready'. */
356
- accept(): void;
357
- /** Re-run the account chooser, then preview again under the new account.
358
- * Available from 'ready', 'no-invite' and 'error' when the engine offers it. */
359
- switchAccount(): void;
360
- /** From 'error': preview again and resume the journey. */
361
- retry(): void;
362
- }
363
- declare function joinFlow(link: string, engine: JoinEngine, options?: {
364
- deadlineMs?: number;
365
- }): JoinFlow;
366
-
367
- export { joinFlow as A, replicaFlow as B, type ConnectTargets as C, shareFlow as D, type FlowHost as F, type JoinEngine as J, type ReplicaFlow as R, type StoreLike as S, type ConnectFlowOptions as a, type ConnectKind as b, type ConnectFlow as c, type ShareEngine as d, type ShareLevel as e, type ShareFlow as f, type JoinFlow as g, type ConnectFlowOutcome as h, type ConnectResolution as i, type ConnectSnapshot as j, type ConnectStep as k, type Connector as l, type JoinOutcome as m, type JoinPreview as n, type JoinSnapshot as o, type JoinStep as p, REPLICA_ID as q, type ReplicaFlowOptions as r, type ReplicaSnapshot as s, type ReplicaStep as t, type ResumeOffer as u, type ShareBusy as v, type ShareLinkInfo as w, type ShareMemberInfo as x, type ShareSnapshot as y, connectFlow as z };
192
+ export { type ConnectTargets as C, type FlowHost as F, type ResumeOffer as R, type StoreLike as S, type ConnectFlowOptions as a, type ConnectKind as b, type ConnectFlow as c, type ConnectFlowOutcome as d, type ConnectResolution as e, type ConnectSnapshot as f, type ConnectStep as g, type Connector as h, connectFlow as i };
@@ -1,4 +1,4 @@
1
- import { B as BackupTarget } from './store-BiQ-BuVl.js';
1
+ import { B as BackupTarget } from './store-CiLoRJmy.js';
2
2
  import { K as KV } from './cache-ilsK3Tki.js';
3
3
 
4
4
  /**
@@ -137,4 +137,4 @@ declare function renameBackup(opts: {
137
137
  fileName: string;
138
138
  }): Promise<void>;
139
139
 
140
- export { type DriveOptions as D, adopt as a, findOrCreateOwnFile as b, connect as c, createBackup as d, deleteBackup as e, fromSession as f, account as g, type DriveAccount as h, type DriveAuth as i, type DriveBackupInfo as j, listBackups as l, preview as p, renameBackup as r };
140
+ export { type DriveOptions as D, type DriveAuth as a, adopt as b, connect as c, findOrCreateOwnFile as d, createBackup as e, fromSession as f, deleteBackup as g, account as h, type DriveAccount as i, type DriveBackupInfo as j, listBackups as l, preview as p, renameBackup as r };
package/dist/flows.d.ts CHANGED
@@ -1,7 +1,186 @@
1
- export { c as ConnectFlow, a as ConnectFlowOptions, h as ConnectFlowOutcome, b as ConnectKind, i as ConnectResolution, j as ConnectSnapshot, k as ConnectStep, C as ConnectTargets, l as Connector, F as FlowHost, J as JoinEngine, g as JoinFlow, m as JoinOutcome, n as JoinPreview, o as JoinSnapshot, p as JoinStep, q as REPLICA_ID, R as ReplicaFlow, r as ReplicaFlowOptions, s as ReplicaSnapshot, t as ReplicaStep, u as ResumeOffer, v as ShareBusy, d as ShareEngine, f as ShareFlow, e as ShareLevel, w as ShareLinkInfo, x as ShareMemberInfo, y as ShareSnapshot, z as connectFlow, A as joinFlow, B as replicaFlow, D as shareFlow } from './flows-vX9zy9bI.js';
2
- export { F as FlowStore, w as withDeadline } from './machine-ALMkT3mk.js';
3
- import './store-BiQ-BuVl.js';
1
+ import { b as ConnectKind, S as StoreLike, C as ConnectTargets } from './connect-DaZFPJdt.js';
2
+ export { c as ConnectFlow, a as ConnectFlowOptions, d as ConnectFlowOutcome, e as ConnectResolution, f as ConnectSnapshot, g as ConnectStep, h as Connector, F as FlowHost, R as ResumeOffer, i as connectFlow } from './connect-DaZFPJdt.js';
3
+ import { S as StoreError, k as ReplicaState, B as BackupTarget } from './store-CiLoRJmy.js';
4
+ import { F as FlowStore } from './machine-ALMkT3mk.js';
5
+ export { w as withDeadline } from './machine-ALMkT3mk.js';
6
+ import { W as WebdavConfig, S as S3Config } from './s3-DgyTwMRc.js';
4
7
  import './cache-ilsK3Tki.js';
8
+ import './drive-B2fm71qD.js';
5
9
  import './sync.js';
6
- import './drive-CTYvGg_7.js';
7
- import './s3-DvsnA-FK.js';
10
+
11
+ /** The one replica this flow manages, as its id on the engine. */
12
+ declare const REPLICA_ID = "replica";
13
+ type ReplicaStep = 'idle' | 'choose' | 'form-webdav' | 'form-s3';
14
+ interface ReplicaSnapshot {
15
+ step: ReplicaStep;
16
+ /** The kinds the host offers, in ConnectTargets order. */
17
+ kinds: ConnectKind[];
18
+ busy: boolean;
19
+ error: StoreError | null;
20
+ /** The live engine entry (label, lastPublishAt, lastError), null when none. */
21
+ replica: ReplicaState | null;
22
+ }
23
+ interface ReplicaFlowOptions {
24
+ /** File name of the copy on its destination. Default: the primary backup
25
+ * name with `.replica` before the extension ("App.zip" -> "App.replica.zip"),
26
+ * which a same-account Drive never confuses with the primary file and the
27
+ * backups panel never lists as a backup of its own. */
28
+ fileName?: string;
29
+ /** Rebuild the target of a custom Connector kind on restore() (built-in
30
+ * kinds rebuild by themselves). Without it, custom kinds are not restored. */
31
+ restoreTarget?: (kind: ConnectKind) => Promise<BackupTarget | null>;
32
+ }
33
+ interface ReplicaFlow extends FlowStore<ReplicaSnapshot> {
34
+ /** Show the destination choice (from the user's gesture). */
35
+ open(): void;
36
+ /** Back to idle; never an error. */
37
+ cancel(): void;
38
+ /** Run a destination (inside the user's click - pickers and consent popups
39
+ * open from it). `webdav: true` / `s3: true` route to their form step. */
40
+ pick(kind: ConnectKind): void;
41
+ /** 'form-webdav' step: submit the server config. */
42
+ submitWebdav(config: WebdavConfig): void;
43
+ /** 'form-s3' step: submit the bucket config. */
44
+ submitS3(config: S3Config): void;
45
+ /** Detach the copy and forget its session. Never deletes remote data. */
46
+ remove(): Promise<void>;
47
+ /** Silent reboot: re-attach the copy recorded by a past session, if any.
48
+ * Never throws - an unreachable destination retries at the next boot. */
49
+ restore(): Promise<void>;
50
+ /** Detach from the engine (the replica itself stays attached). */
51
+ dispose(): void;
52
+ }
53
+ declare function replicaFlow(store: StoreLike, targets: ConnectTargets, options?: ReplicaFlowOptions): ReplicaFlow;
54
+
55
+ type ShareLevel = 'read' | 'write';
56
+ interface ShareLinkInfo {
57
+ /** Engine-scoped stable id (revoke keys on it). */
58
+ id: string;
59
+ /** The invitation URL to hand out. */
60
+ url: string;
61
+ level: ShareLevel;
62
+ }
63
+ interface ShareMemberInfo {
64
+ /** Engine-scoped stable id (removeMember keys on it). */
65
+ id: string;
66
+ label?: string;
67
+ /** This row is the local user. */
68
+ self?: boolean;
69
+ /** This row owns the share (the app may render a badge, the flow does not care). */
70
+ owner?: boolean;
71
+ }
72
+ /** The app-side port the flow drives. Implementations wrap whatever transport
73
+ * the app shares through; every method may throw (SelfstoreError preferred,
74
+ * anything is normalized). */
75
+ interface ShareEngine {
76
+ /** The current truth: active links and members. */
77
+ list(): Promise<{
78
+ links: ShareLinkInfo[];
79
+ members: ShareMemberInfo[];
80
+ }>;
81
+ createLink(opts: {
82
+ level: ShareLevel;
83
+ }): Promise<ShareLinkInfo>;
84
+ revokeLink(id: string): Promise<void>;
85
+ /** Optional: engines without member management simply omit it. */
86
+ removeMember?(id: string): Promise<void>;
87
+ /** Optional: END the whole share in one gesture - every link dead, every
88
+ * member out. Engines whose transport has such a move (an invitation to
89
+ * kill, a file to re-privatize) expose it; the widget renders the action
90
+ * only then. */
91
+ revokeAll?(): Promise<void>;
92
+ }
93
+ type ShareBusy = 'refresh' | 'create' | 'revoke' | 'remove' | 'revoke-all' | null;
94
+ interface ShareSnapshot {
95
+ links: readonly ShareLinkInfo[];
96
+ members: readonly ShareMemberInfo[];
97
+ /** The operation in flight, or null. */
98
+ busy: ShareBusy;
99
+ /** The last refresh failed: the lists shown are the last-known good ones. */
100
+ stale: boolean;
101
+ /** The last operation's problem (the lists are never cleared by an error). */
102
+ error: StoreError | null;
103
+ /** True once the engine offers removeMember (render the row action at all). */
104
+ canRemoveMembers: boolean;
105
+ /** True once the engine offers revokeAll (render the end-the-share action). */
106
+ canRevokeAll: boolean;
107
+ }
108
+ interface ShareFlow extends FlowStore<ShareSnapshot> {
109
+ /** Re-list from the engine. Failure keeps the last-known lists (stale). */
110
+ refresh(): Promise<void>;
111
+ /** Create an invitation link. Resolves to it, or null when refused (an
112
+ * operation was already in flight). */
113
+ createLink(opts: {
114
+ level: ShareLevel;
115
+ }): Promise<ShareLinkInfo | null>;
116
+ /** Revoke a link. The link leaves the snapshot only once the engine
117
+ * confirmed. Resolves to whether it did. */
118
+ revokeLink(id: string): Promise<boolean>;
119
+ /** Remove a member (never yourself - refused up front). Resolves to whether
120
+ * the engine confirmed. */
121
+ removeMember(id: string): Promise<boolean>;
122
+ /** End the whole share: every link and member leaves the snapshot only
123
+ * once the engine confirmed. Resolves to whether it did. */
124
+ revokeAll(): Promise<boolean>;
125
+ }
126
+ declare function shareFlow(engine: ShareEngine, options?: {
127
+ deadlineMs?: number;
128
+ }): ShareFlow;
129
+
130
+ /** What the user is about to join - enough to render an honest question. */
131
+ interface JoinPreview {
132
+ /** The share's display name, when the link carries one. */
133
+ label?: string;
134
+ /** Who shared it (a name, an email - whatever the engine knows). */
135
+ from?: string;
136
+ level?: ShareLevel;
137
+ }
138
+ /** How joining ended, other than plain failure:
139
+ * 'joined' - this device now follows the share.
140
+ * 'mismatch' - this device already follows a DIFFERENT share: joining a
141
+ * second one is refused (leave the first, or use another
142
+ * device/profile).
143
+ * 'no-invite' - the invitation is spent, expired, or aimed at another
144
+ * account (switching accounts may be the fix). */
145
+ type JoinOutcome = 'joined' | 'mismatch' | 'no-invite';
146
+ /** The app-side port: how a link is read and honoured on this transport. */
147
+ interface JoinEngine {
148
+ /** Read-only look at the invitation. Must change NOTHING. */
149
+ preview(link: string): Promise<JoinPreview>;
150
+ /** Consume the invitation and wire the share. Resolve null when the user
151
+ * backed out of a popup the engine had to open (an account chooser, a
152
+ * consent screen): the flow returns to 'ready' silently - backing out of
153
+ * a human gesture is not an error, here as everywhere in these flows. */
154
+ join(link: string): Promise<JoinOutcome | null>;
155
+ /** Optional: re-run the account chooser (the invite may belong to another
156
+ * account). The flow re-previews after it. */
157
+ switchAccount?(): Promise<void>;
158
+ }
159
+ type JoinStep = 'previewing' | 'ready' | 'joining' | 'joined' | 'mismatch' | 'no-invite' | 'error';
160
+ interface JoinSnapshot {
161
+ /** The invitation this flow was opened with - held here so the UI can
162
+ * always find it again. */
163
+ link: string;
164
+ step: JoinStep;
165
+ /** Set once previewed (kept through errors, so retry re-renders context). */
166
+ preview: JoinPreview | null;
167
+ /** The engine offers an account switch. */
168
+ canSwitchAccount: boolean;
169
+ busy: boolean;
170
+ /** Set when step === 'error': show `labelKey`, log `message`. */
171
+ error: StoreError | null;
172
+ }
173
+ interface JoinFlow extends FlowStore<JoinSnapshot> {
174
+ /** Say yes: consume the invitation. Only from 'ready'. */
175
+ accept(): void;
176
+ /** Re-run the account chooser, then preview again under the new account.
177
+ * Available from 'ready', 'no-invite' and 'error' when the engine offers it. */
178
+ switchAccount(): void;
179
+ /** From 'error': preview again and resume the journey. */
180
+ retry(): void;
181
+ }
182
+ declare function joinFlow(link: string, engine: JoinEngine, options?: {
183
+ deadlineMs?: number;
184
+ }): JoinFlow;
185
+
186
+ export { ConnectKind, ConnectTargets, FlowStore, type JoinEngine, type JoinFlow, type JoinOutcome, type JoinPreview, type JoinSnapshot, type JoinStep, REPLICA_ID, type ReplicaFlow, type ReplicaFlowOptions, type ReplicaSnapshot, type ReplicaStep, type ShareBusy, type ShareEngine, type ShareFlow, type ShareLevel, type ShareLinkInfo, type ShareMemberInfo, type ShareSnapshot, joinFlow, replicaFlow, shareFlow };
@@ -1,4 +1,4 @@
1
- import { B as BackupTarget, P as PeerSource, L as LocalStore } from './store-BiQ-BuVl.js';
1
+ import { B as BackupTarget, P as PeerSource, L as LocalStore } from './store-CiLoRJmy.js';
2
2
  import { K as KV } from './cache-ilsK3Tki.js';
3
3
  import './sync.js';
4
4
 
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
- import { n as Snapshot, E as EncodeOptions, H as Header, f as PasswordPolicy, B as BackupTarget, q as StatusDescriptor, t as StoreError, c as LocalStoreState, L as LocalStore } from './store-BiQ-BuVl.js';
2
- export { d as Mode, e as PasswordCheck, g as PasswordRequirement, S as SelfstoreError, l as SelfstoreErrorCode, m as Severity, o as SnapshotFile, p as StatusAction, s as StorageState, T as TargetKind, x as checkPasswordPolicy, C as errorLabelKey, G as isSelfstoreError } from './store-BiQ-BuVl.js';
1
+ import { o as Snapshot, E as EncodeOptions, H as Header, f as PasswordPolicy, B as BackupTarget, r as StatusDescriptor, S as StoreError, c as LocalStoreState, L as LocalStore } from './store-CiLoRJmy.js';
2
+ export { d as Mode, e as PasswordCheck, g as PasswordRequirement, l as SelfstoreError, m as SelfstoreErrorCode, n as Severity, p as SnapshotFile, q as StatusAction, t as StorageState, T as TargetKind, x as checkPasswordPolicy, C as errorLabelKey, G as isSelfstoreError } from './store-CiLoRJmy.js';
3
3
  import { L as LocalCache, K as KV } from './cache-ilsK3Tki.js';
4
4
  import { SyncConfig } from './sync.js';
5
- import { i as DriveAuth } from './drive-CTYvGg_7.js';
6
- import { W as WebdavConfig, S as S3Config } from './s3-DvsnA-FK.js';
7
- export { f as changePassword, g as gisDriveAuth, p as pickFromDisk, s as saveToDisk } from './local-VLHtF3vT.js';
5
+ import { R as ResumeOffer, C as ConnectTargets } from './connect-DaZFPJdt.js';
6
+ import { a as DriveAuth } from './drive-B2fm71qD.js';
7
+ import { W as WebdavConfig, S as S3Config } from './s3-DgyTwMRc.js';
8
+ export { f as changePassword, g as gisDriveAuth, p as pickFromDisk, s as saveToDisk } from './local-CldSa9EY.js';
9
+ import './machine-ALMkT3mk.js';
8
10
 
9
11
  declare const BACKUP_EXTENSION = ".zip";
10
12
  declare const BACKUP_MIME = "application/zip";
@@ -112,9 +114,25 @@ interface SimpleOptions {
112
114
  /** Per-collection merge tuning (id field mapping, strategies). The default -
113
115
  * records keyed by `id`, later edit wins, deletes propagate - fits most apps. */
114
116
  sync?: SyncConfig;
115
- /** Google Drive auth, when this app uses Drive: providing it here lets a
116
- * connected Drive backup restore itself on the next start. */
117
- drive?: DriveAuth;
117
+ /**
118
+ * Google Drive, when this app uses it. Two shapes for two situations:
119
+ *
120
+ * `{ clientId }` is the whole wiring - the library builds the browser
121
+ * connection, keeps ONE session for the app (several would mean several
122
+ * consent screens), reuses its token across a reload, remembers which
123
+ * account holds the backup and hands that back to Google so the chooser
124
+ * stops appearing. Nothing else to write.
125
+ *
126
+ * A `DriveAuth` instead, for an app that gets its tokens elsewhere - a
127
+ * server broker holding a refresh token, a native shell.
128
+ *
129
+ * Either way, providing it here is also what lets a connected Drive backup
130
+ * restore itself on the next start.
131
+ */
132
+ drive?: DriveAuth | {
133
+ clientId: string;
134
+ scope?: string;
135
+ };
118
136
  /** Where the working copy lives (default: IndexedDB named after the app;
119
137
  * in-memory when IndexedDB does not exist, e.g. tests or SSR). */
120
138
  cache?: LocalCache;
@@ -236,6 +254,28 @@ interface SimpleStore<S extends Record<string, SimpleRecord> = Record<string, Si
236
254
  kv: KV;
237
255
  backupName: string;
238
256
  };
257
+ /** Who holds the destination, when it is known - the Google address behind a
258
+ * Drive backup. Learned on connect and remembered across sessions, so a
259
+ * surface can NAME the destination ("Google Drive" is not an address) and a
260
+ * re-grant can skip the account chooser. Null for a file, or before a first
261
+ * connect. Reading it costs nothing: no call goes out. */
262
+ readonly account: string | null;
263
+ /** The destination this device already used, ready to hand to a connect flow
264
+ * as `options.resume` - so a returning visitor is offered "reopen my backup"
265
+ * ahead of the plain destinations, instead of re-deriving a decision the
266
+ * library already has on file. Null when there is nothing to resume. */
267
+ resumeOffer(): ResumeOffer | null;
268
+ /**
269
+ * The destinations this store can offer, ready for a connect flow or widget:
270
+ * Drive first when the app configured it, a file always.
271
+ *
272
+ * Consumers were assembling this by hand from the very session the store
273
+ * built - and the order and the "no client id means no Drive card" rule were
274
+ * rediscovered each time. An app wanting a different offer (a WebDAV server,
275
+ * its own target) still writes its own object; this is the default, not a
276
+ * ceiling.
277
+ */
278
+ destinations(): ConnectTargets;
239
279
  }
240
280
  /**
241
281
  * Open (or create) the app's local store. Awaits the initial load, so the
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { indexedDbCache, memoryCache, isLockableCache, createLocalStore } from './chunk-TXAMWBG5.js';
1
+ import { indexedDbCache, memoryCache, gisDriveAuth, isLockableCache, createLocalStore } from './chunk-TXAMWBG5.js';
2
2
  export { gisDriveAuth } from './chunk-TXAMWBG5.js';
3
3
  import './chunk-SKWHKM25.js';
4
4
  import { saveToDisk } from './chunk-DCEXWSM6.js';
5
5
  export { pickFromDisk, saveToDisk } from './chunk-DCEXWSM6.js';
6
6
  import { fromSession as fromSession$1, fromSession2, isSupported, connect3, connect2, connect } from './chunk-TKWU52LK.js';
7
7
  export { hasDesktopFiles, useDesktopFiles } from './chunk-TKWU52LK.js';
8
- import { fromSession, connect as connect$1 } from './chunk-J5MFXJU6.js';
8
+ import { fromSession, connect as connect$1, account } from './chunk-J5MFXJU6.js';
9
9
  import { restore } from './chunk-MGDXR3RB.js';
10
10
  export { BACKUP_EXTENSION, BACKUP_MIME, backup, changePassword, checkPasswordPolicy, restore } from './chunk-MGDXR3RB.js';
11
11
  import './chunk-QBCU7OOP.js';
@@ -32,6 +32,28 @@ function requireStringId(record, field, collection) {
32
32
  function hasIndexedDb() {
33
33
  return typeof indexedDB !== "undefined";
34
34
  }
35
+ function isDriveAuth(v) {
36
+ return !!v && typeof v.token === "function";
37
+ }
38
+ var driveAccountKey = (app) => `selfstore.drive.account.${app}`;
39
+ function localStore(job) {
40
+ try {
41
+ const store = globalThis.localStorage;
42
+ return store ? job(store) : void 0;
43
+ } catch {
44
+ return void 0;
45
+ }
46
+ }
47
+ function rememberedDriveAccount(app) {
48
+ return localStore((s) => s.getItem(driveAccountKey(app))) ?? void 0;
49
+ }
50
+ async function learnDriveAccount(app, auth) {
51
+ try {
52
+ const who = await account({ auth });
53
+ if (who.email) localStore((s) => s.setItem(driveAccountKey(app), who.email));
54
+ } catch {
55
+ }
56
+ }
35
57
  async function selfstore(app, options = {}) {
36
58
  if (typeof app !== "string" || app.length === 0) {
37
59
  throw new TypeError('selfstore: pass your app name, e.g. selfstore("my-app").');
@@ -42,7 +64,12 @@ async function selfstore(app, options = {}) {
42
64
  const emitData = () => dataSubs.forEach((fn) => fn());
43
65
  const cache = options.cache ?? (hasIndexedDb() ? indexedDbCache(app, options.cacheLock ? { lock: true } : void 0) : memoryCache());
44
66
  const backupName = `${app}.zip`;
45
- let driveAuth = options.drive ?? null;
67
+ let driveAuth = isDriveAuth(options.drive) ? options.drive : options.drive ? gisDriveAuth({
68
+ clientId: options.drive.clientId,
69
+ scope: options.drive.scope,
70
+ persist: "session",
71
+ hint: () => rememberedDriveAccount(app)
72
+ }) : null;
46
73
  if (options.cacheLock && isLockableCache(cache)) {
47
74
  let failed = false;
48
75
  while (!await cache.unlock(await options.cacheLock({ failed }))) failed = true;
@@ -180,7 +207,9 @@ async function selfstore(app, options = {}) {
180
207
  driveAuth = auth;
181
208
  const target = await connect$1({ auth, kv: cache.kv, fileName: backupName });
182
209
  if (!target) return "cancelled";
183
- return connectTarget(target, opts);
210
+ const outcome = await connectTarget(target, opts);
211
+ if (outcome !== "cancelled") void learnDriveAccount(app, auth);
212
+ return outcome;
184
213
  },
185
214
  async connectFile(opts) {
186
215
  if (isSupported()) {
@@ -253,7 +282,26 @@ async function selfstore(app, options = {}) {
253
282
  store.dispose();
254
283
  },
255
284
  advanced: store,
256
- flowHost: { engine: store, kv: cache.kv, backupName }
285
+ flowHost: { engine: store, kv: cache.kv, backupName },
286
+ get account() {
287
+ return rememberedDriveAccount(app) ?? null;
288
+ },
289
+ destinations() {
290
+ return driveAuth ? { drive: driveAuth, file: true } : { file: true };
291
+ },
292
+ resumeOffer() {
293
+ const detail = rememberedDriveAccount(app);
294
+ if (!driveAuth || !detail) return null;
295
+ const auth = driveAuth;
296
+ return {
297
+ kind: "drive",
298
+ detail,
299
+ // Reopening is not searching: fromSession takes the file id already
300
+ // remembered, where a fresh connect resolves by name and could land on
301
+ // another file carrying the same one.
302
+ connect: () => fromSession({ auth, kv: cache.kv, fileName: backupName })
303
+ };
304
+ }
257
305
  };
258
306
  return simple;
259
307
  }