selfstore 1.6.4 → 1.6.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.
@@ -1,12 +1,53 @@
1
- import { B as BackupTarget } from './store-BQhcwSbe.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, P as PasswordCheck, e as PasswordPolicy, f as PasswordRequirement, g 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-BQhcwSbe.js';
1
+ import { B as BackupTarget } from './store-CSxHlrov.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-CSxHlrov.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 { e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, a as inspect, b as isEncrypted, p as pickFromDisk, s as saveToDisk } from './local-hr_BimjH.js';
6
- 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 connect$2, h as fromSession$2, i as connect$3, j as fromSession$3, k as peer } from './drive-CO_mNEgi.js';
7
- export { D as DriveAuth, m as DriveBackupInfo, n as DriveOptions, S as S3Config, o as S3ConnectOptions, W as WebdavConfig, q as WebdavConnectOptions, s as WebdavPeerOptions } from './drive-CO_mNEgi.js';
5
+ export { e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, a as inspect, b as isEncrypted, p as pickFromDisk, s as saveToDisk } from './local-BowlLFZc.js';
6
+ 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 connect$2, h as fromSession$2, i as connect$3, j as fromSession$3, k as peer } from './drive-Cf_ulDfn.js';
7
+ export { D as DriveAuth, m as DriveBackupInfo, n as DriveOptions, S as S3Config, o as S3ConnectOptions, W as WebdavConfig, q as WebdavConnectOptions, s as WebdavPeerOptions } from './drive-Cf_ulDfn.js';
8
8
  import './sync.js';
9
9
 
10
+ /**
11
+ * Whether this browser will keep the local store, and what the user can do
12
+ * about it.
13
+ *
14
+ * One browser does not merely inconvenience a local-first app, it DELETES its
15
+ * data: Safari erases script-writable storage after seven days of browser use
16
+ * without interaction with the site. Someone who closes for three weeks comes
17
+ * back to an empty app, and nothing warned them - which is also the moment they
18
+ * notice it last.
19
+ *
20
+ * Adding the app to the Dock or the Home Screen switches its storage to
21
+ * persistent mode, which WebKit exempts from its eviction triggers. WebKit does
22
+ * not write anywhere that this neutralises the inactivity rule by name, so this
23
+ * module reports an advisory, never a guarantee.
24
+ *
25
+ * The library ships no user-facing copy: it names the situation, the host writes
26
+ * the sentence.
27
+ */
28
+ /** What stands between this browser and keeping the data. */
29
+ type StorageRisk =
30
+ /** Nothing known against it: the store survives until the user clears it. */
31
+ 'none'
32
+ /** Data is erased when the tab closes (private browsing). Refuse to set up here. */
33
+ | 'ephemeral'
34
+ /** Erased after a stretch of not opening the app, unless it is installed. */
35
+ | 'evicted-when-idle';
36
+ interface StorageAdvice {
37
+ risk: StorageRisk;
38
+ /** The gesture that removes the risk, when there is one. Stable key; the
39
+ * host owns the wording and the platform-specific instructions. */
40
+ remedy?: 'install-to-dock' | 'install-to-home-screen';
41
+ /** Already running as an installed app, so the remedy is done. */
42
+ installed: boolean;
43
+ }
44
+ /**
45
+ * What this browser will do with the local store, and the one gesture that
46
+ * changes it. Call it before setting up, and again in whatever screen owns the
47
+ * data: the advice disappears on its own once the remedy is applied.
48
+ */
49
+ declare function storageAdvice(): StorageAdvice;
50
+
10
51
  /**
11
52
  * Disk-file backup target (Chromium only). The user picks a backup file once via
12
53
  * the File System Access API; the store then re-writes that real file on every
@@ -34,6 +75,13 @@ declare function isOpenSupported(): boolean;
34
75
  * next sessions. Returns the target, or null if cancelled - or if the browser
35
76
  * refused to open a picker at all, which `isSupported()` then reports. */
36
77
  declare function connect(opts: FileConnectOptions): Promise<BackupTarget | null>;
78
+ /** Prompt the user to pick an EXISTING backup file and adopt it as the
79
+ * destination (the connect journey then reads it like any backup: password,
80
+ * conflict, resume). Asks for readwrite up front so the first save does not
81
+ * stall on a second permission prompt. Returns null if cancelled. */
82
+ declare function openExisting(opts: {
83
+ kv: KV;
84
+ }): Promise<BackupTarget | null>;
37
85
  /** Rebuild the target from the handle persisted in a past session, or null. */
38
86
  declare function fromSession(opts: {
39
87
  kv: KV;
@@ -61,6 +109,10 @@ declare const fileTarget: {
61
109
  fromSession: typeof fromSession;
62
110
  isSupported: typeof isSupported;
63
111
  isOpenSupported: typeof isOpenSupported;
112
+ /** Let the user point at an EXISTING backup file and adopt it as the
113
+ * destination. Gate on isOpenSupported(): unlike creating, this has no
114
+ * download fallback. */
115
+ openExisting: typeof openExisting;
64
116
  };
65
117
  /** WebDAV destination (Nextcloud, ownCloud, any server the user controls), plus
66
118
  * `peer()`: a read-only source over another member's shared WebDAV link, so a
@@ -96,4 +148,4 @@ declare const driveTarget: {
96
148
  FILE_ID_KEY: string;
97
149
  };
98
150
 
99
- export { BackupTarget, type FileConnectOptions, KV, driveTarget, fileTarget, s3Target, webdavTarget };
151
+ export { BackupTarget, type FileConnectOptions, KV, type StorageAdvice, type StorageRisk, driveTarget, fileTarget, s3Target, storageAdvice, webdavTarget };
package/dist/advanced.js CHANGED
@@ -1,19 +1,49 @@
1
- export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-5L7JC2SH.js';
1
+ export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-PBWDDWWN.js';
2
2
  export { pickFromDisk, saveToDisk } from './chunk-5W2G6DWU.js';
3
3
  import './chunk-SKWHKM25.js';
4
- import { FILE_ID_KEY, renameBackup, deleteBackup, createBackup, listBackups, findOrCreateOwnFile, adopt, preview, fromSession, connect, isOpenSupported, isSupported, fromSession2, connect2, fromSession4, connect4, peer, fromSession3, connect3 } from './chunk-WF732QXN.js';
4
+ import { FILE_ID_KEY, renameBackup, deleteBackup, createBackup, listBackups, findOrCreateOwnFile, adopt, preview, fromSession, connect, openExisting, isOpenSupported, isSupported, fromSession2, connect2, fromSession4, connect4, peer, fromSession3, connect3 } from './chunk-WF732QXN.js';
5
5
  export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-FEJC2WGL.js';
6
6
  import './chunk-KHER6MWR.js';
7
7
  import './chunk-QBCU7OOP.js';
8
8
  import './chunk-6746AL4I.js';
9
9
  export { AuthExpiredError, SelfstoreError, errorLabelKey, isAuthExpired, isSelfstoreError } from './chunk-VBFMMR7Q.js';
10
10
 
11
+ // src/persistence/installation.ts
12
+ function isSafari(ua) {
13
+ return /Safari\//.test(ua) && !/Chrome|Chromium|Edg\//.test(ua) && !/OPR\/|Android/.test(ua);
14
+ }
15
+ function isStandalone() {
16
+ if (typeof window === "undefined") return false;
17
+ const displayMode = window.matchMedia?.("(display-mode: standalone)")?.matches === true;
18
+ const ios = navigator.standalone === true;
19
+ return displayMode || ios;
20
+ }
21
+ function storageAdvice() {
22
+ if (typeof navigator === "undefined" || typeof window === "undefined") {
23
+ return { risk: "none", installed: false };
24
+ }
25
+ const installed = isStandalone();
26
+ if (!isSafari(navigator.userAgent) || installed) {
27
+ return { risk: "none", installed };
28
+ }
29
+ const touch = /iPhone|iPad|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1;
30
+ return {
31
+ risk: "evicted-when-idle",
32
+ remedy: touch ? "install-to-home-screen" : "install-to-dock",
33
+ installed: false
34
+ };
35
+ }
36
+
11
37
  // src/entries/advanced.ts
12
38
  var fileTarget = {
13
39
  connect: connect2,
14
40
  fromSession: fromSession2,
15
41
  isSupported,
16
- isOpenSupported
42
+ isOpenSupported,
43
+ /** Let the user point at an EXISTING backup file and adopt it as the
44
+ * destination. Gate on isOpenSupported(): unlike creating, this has no
45
+ * download fallback. */
46
+ openExisting
17
47
  };
18
48
  var webdavTarget = {
19
49
  connect: connect3,
@@ -37,6 +67,6 @@ var driveTarget = {
37
67
  FILE_ID_KEY
38
68
  };
39
69
 
40
- export { driveTarget, fileTarget, s3Target, webdavTarget };
70
+ export { driveTarget, fileTarget, s3Target, storageAdvice, webdavTarget };
41
71
  //# sourceMappingURL=advanced.js.map
42
72
  //# sourceMappingURL=advanced.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/entries/advanced.ts"],"names":["connect","fromSession"],"mappings":";;;;;;;;;;;AA+HO,IAAM,UAAA,GAAa;AAAA,EACzB,OAAA,EAASA,QAAAA;AAAA,EACT,WAAA,EAAaC,YAAAA;AAAA,EACb,WAAA;AAAA,EACA;AACD;AAKO,IAAM,YAAA,GAAe;AAAA,EAC3B,OAAA,EAASD,QAAAA;AAAA,EACT,WAAA,EAAaC,YAAAA;AAAA,EACb;AACD;AAMO,IAAM,QAAA,GAAW;AAAA,EACvB,OAAA,EAASD,QAAAA;AAAA,EACT,WAAA,EAAaC;AACd;AAOO,IAAM,WAAA,GAAc;AAAA,EAC1B,OAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,mBAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA;AACD","file":"advanced.js","sourcesContent":["/**\n * selfstore/advanced - the pull-model store and the machinery under it.\n *\n * The simple `selfstore()` facade (package root) owns its data. This entry\n * inverts that: with createLocalStore your app owns the data and hands the\n * store a gather/apply pair - use it when state lives in your own reactive\n * model (Svelte runes, Redux, signals) and the facade's collections would be\n * a second copy. Everything here also backs `store.advanced` on the simple\n * store: same machinery, same instance, so the two styles compose.\n *\n * Also here: the durable destinations (disk file, Google Drive, WebDAV) as\n * connectable targets, the BackupTarget contract for writing your own, the\n * caches, the headless status derivation, and the functional codec for the\n * backup file format (the fluent one lives at the package root).\n */\n\n// --- The pull-model store: your app owns the data ---\nexport {\n\tcreateLocalStore,\n\ttype LocalStore,\n\ttype LocalStoreOptions,\n\ttype LocalStoreState,\n\ttype StoreError,\n\ttype Mode,\n\ttype TargetKind,\n\ttype SyncSource,\n\ttype SyncJournalEntry,\n\ttype PeerState,\n\ttype MirrorState,\n\ttype ReplicaState,\n\ttype StoreGroupConfig\n} from '../persistence/store';\n\n// --- On-device caches (the store's working copy) ---\nexport {\n\tindexedDbCache,\n\tmemoryCache,\n\tisLockableCache,\n\ttype LocalCache,\n\ttype LockableCache,\n\ttype CachedFile,\n\ttype KV\n} from '../persistence/cache';\n\n// --- Headless status: map store state to your UI copy ---\nexport {\n\tderiveStatus,\n\ttype StatusDescriptor,\n\ttype StatusInput,\n\ttype Severity,\n\ttype StorageState,\n\ttype StatusAction\n} from '../persistence/status';\n\n// --- The target contract (write your own destination) ---\nexport {\n\tRESERVED_STORE_MODES,\n\tisReservedStoreMode,\n\ttype BackupTarget,\n\ttype BuiltinTargetKind,\n\ttype PeerSource\n} from '../persistence/target';\n\n// --- The backup file format, functional style (fluent lives at the root) ---\nexport {\n\texportSnapshot,\n\timportSnapshot,\n\tinspect,\n\tisEncrypted,\n\tRESERVED_COLLECTION_PREFIX,\n\ttype Snapshot,\n\ttype SnapshotFile,\n\ttype Header,\n\ttype KdfParams,\n\ttype RecipientStanza,\n\ttype EncodeOptions\n} from '../selfstore';\n\n// --- Password strength policy (the store enforces it; the UI previews it) ---\nexport {\n\tcheckPasswordPolicy,\n\ttype PasswordPolicy,\n\ttype PasswordCheck,\n\ttype PasswordRequirement\n} from '../selfstore';\n\n// --- The full error contract (AuthExpiredError is what a custom target or\n// DriveAuth THROWS to signal genuine access loss; everything else it\n// throws is treated as transient and retried) ---\nexport {\n\tSelfstoreError,\n\tAuthExpiredError,\n\tisSelfstoreError,\n\tisAuthExpired,\n\terrorLabelKey,\n\ttype SelfstoreErrorCode\n} from '../selfstore';\n\n// --- Durable destinations (opt-in), each implementing BackupTarget ---\n// Explicit namespace objects: every member is deliberately public, so a future\n// internal helper in a target module can never leak into the contract.\nimport {\n\tconnect as fileConnect,\n\tfromSession as fileFromSession,\n\tisSupported as fileIsSupported,\n\tisOpenSupported as fileIsOpenSupported\n} from '../persistence/targets/file';\nimport {\n\tconnect as webdavConnect,\n\tfromSession as webdavFromSession,\n\tpeer as webdavPeer\n} from '../persistence/targets/webdav';\nimport { connect as s3Connect, fromSession as s3FromSession } from '../persistence/targets/s3';\nimport {\n\tconnect as driveConnect,\n\tfromSession as driveFromSession,\n\tpreview as drivePreview,\n\tadopt as driveAdopt,\n\tfindOrCreateOwnFile as driveFindOrCreateOwnFile,\n\tlistBackups as driveListBackups,\n\tcreateBackup as driveCreateBackup,\n\tdeleteBackup as driveDeleteBackup,\n\trenameBackup as driveRenameBackup,\n\tFILE_ID_KEY as DRIVE_FILE_ID_KEY\n} from '../persistence/targets/drive';\n\n/** Disk-file destination (File System Access, Chromium). */\nexport const fileTarget = {\n\tconnect: fileConnect,\n\tfromSession: fileFromSession,\n\tisSupported: fileIsSupported,\n\tisOpenSupported: fileIsOpenSupported\n};\n\n/** WebDAV destination (Nextcloud, ownCloud, any server the user controls), plus\n * `peer()`: a read-only source over another member's shared WebDAV link, so a\n * group can share read-write without Google Drive (see store.attachPeer). */\nexport const webdavTarget = {\n\tconnect: webdavConnect,\n\tfromSession: webdavFromSession,\n\tpeer: webdavPeer\n};\n\n/** S3-compatible destination (Amazon S3, Cloudflare R2, Backblaze B2, MinIO):\n * the browser signs each request with SigV4 and writes one object on a bucket\n * the user controls, no vendor broker. The bucket only ever holds the same\n * end-to-end-encrypted .selfstore bytes. */\nexport const s3Target = {\n\tconnect: s3Connect,\n\tfromSession: s3FromSession\n};\n\n/** Google Drive destination, plus the shared-file primitives (preview a specific\n * file, adopt it as this device's backup, find-or-create the user's own file)\n * and the backup-management ops (list the app's files with dates, create a\n * named empty backup, rename one, delete one for good) - the raw pieces a \"my\n * backups\" panel builds on. */\nexport const driveTarget = {\n\tconnect: driveConnect,\n\tfromSession: driveFromSession,\n\tpreview: drivePreview,\n\tadopt: driveAdopt,\n\tfindOrCreateOwnFile: driveFindOrCreateOwnFile,\n\tlistBackups: driveListBackups,\n\tcreateBackup: driveCreateBackup,\n\tdeleteBackup: driveDeleteBackup,\n\trenameBackup: driveRenameBackup,\n\tFILE_ID_KEY: DRIVE_FILE_ID_KEY\n};\n\nexport type { FileConnectOptions } from '../persistence/targets/file';\nexport type { DriveAuth, DriveOptions, DriveBackupInfo } from '../persistence/targets/drive';\nexport type {\n\tWebdavConfig,\n\tWebdavConnectOptions,\n\tWebdavPeerOptions\n} from '../persistence/targets/webdav';\nexport type { S3Config, S3ConnectOptions } from '../persistence/targets/s3';\nexport { gisDriveAuth } from '../persistence/targets/drive-auth-gis';\nexport { saveToDisk, pickFromDisk } from '../selfstore/targets/local';\n"]}
1
+ {"version":3,"sources":["../src/persistence/installation.ts","../src/entries/advanced.ts"],"names":["connect","fromSession"],"mappings":";;;;;;;;;;;AAuCA,SAAS,SAAS,EAAA,EAAqB;AACtC,EAAA,OAAO,UAAA,CAAW,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,uBAAA,CAAwB,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,eAAA,CAAgB,IAAA,CAAK,EAAE,CAAA;AAC5F;AAEA,SAAS,YAAA,GAAwB;AAChC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAC1C,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,UAAA,GAAa,4BAA4B,GAAG,OAAA,KAAY,IAAA;AAEnF,EAAA,MAAM,GAAA,GAAO,UAAmD,UAAA,KAAe,IAAA;AAC/E,EAAA,OAAO,WAAA,IAAe,GAAA;AACvB;AAOO,SAAS,aAAA,GAA+B;AAC9C,EAAA,IAAI,OAAO,SAAA,KAAc,WAAA,IAAe,OAAO,WAAW,WAAA,EAAa;AACtE,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAA,EAAW,KAAA,EAAM;AAAA,EACzC;AACA,EAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,EAAA,IAAI,CAAC,QAAA,CAAS,SAAA,CAAU,SAAS,KAAK,SAAA,EAAW;AAChD,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAA,EAAU;AAAA,EAClC;AACA,EAAA,MAAM,QAAQ,kBAAA,CAAmB,IAAA,CAAK,UAAU,SAAS,CAAA,IAAK,UAAU,cAAA,GAAiB,CAAA;AACzF,EAAA,OAAO;AAAA,IACN,IAAA,EAAM,mBAAA;AAAA,IACN,MAAA,EAAQ,QAAQ,wBAAA,GAA2B,iBAAA;AAAA,IAC3C,SAAA,EAAW;AAAA,GACZ;AACD;;;AC6DO,IAAM,UAAA,GAAa;AAAA,EACzB,OAAA,EAASA,QAAAA;AAAA,EACT,WAAA,EAAaC,YAAAA;AAAA,EACb,WAAA;AAAA,EACA,eAAA;AAAA;AAAA;AAAA;AAAA,EAIA;AACD;AAKO,IAAM,YAAA,GAAe;AAAA,EAC3B,OAAA,EAASD,QAAAA;AAAA,EACT,WAAA,EAAaC,YAAAA;AAAA,EACb;AACD;AAMO,IAAM,QAAA,GAAW;AAAA,EACvB,OAAA,EAASD,QAAAA;AAAA,EACT,WAAA,EAAaC;AACd;AAOO,IAAM,WAAA,GAAc;AAAA,EAC1B,OAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,mBAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA;AACD","file":"advanced.js","sourcesContent":["/**\n * Whether this browser will keep the local store, and what the user can do\n * about it.\n *\n * One browser does not merely inconvenience a local-first app, it DELETES its\n * data: Safari erases script-writable storage after seven days of browser use\n * without interaction with the site. Someone who closes for three weeks comes\n * back to an empty app, and nothing warned them - which is also the moment they\n * notice it last.\n *\n * Adding the app to the Dock or the Home Screen switches its storage to\n * persistent mode, which WebKit exempts from its eviction triggers. WebKit does\n * not write anywhere that this neutralises the inactivity rule by name, so this\n * module reports an advisory, never a guarantee.\n *\n * The library ships no user-facing copy: it names the situation, the host writes\n * the sentence.\n */\n\n/** What stands between this browser and keeping the data. */\nexport type StorageRisk =\n\t/** Nothing known against it: the store survives until the user clears it. */\n\t| 'none'\n\t/** Data is erased when the tab closes (private browsing). Refuse to set up here. */\n\t| 'ephemeral'\n\t/** Erased after a stretch of not opening the app, unless it is installed. */\n\t| 'evicted-when-idle';\n\nexport interface StorageAdvice {\n\trisk: StorageRisk;\n\t/** The gesture that removes the risk, when there is one. Stable key; the\n\t * host owns the wording and the platform-specific instructions. */\n\tremedy?: 'install-to-dock' | 'install-to-home-screen';\n\t/** Already running as an installed app, so the remedy is done. */\n\tinstalled: boolean;\n}\n\n/** Real Safari, not Chrome or Edge on macOS - all three carry \"Safari\" in the\n * user agent, and only Safari carries none of the others' tokens. */\nfunction isSafari(ua: string): boolean {\n\treturn /Safari\\//.test(ua) && !/Chrome|Chromium|Edg\\//.test(ua) && !/OPR\\/|Android/.test(ua);\n}\n\nfunction isStandalone(): boolean {\n\tif (typeof window === 'undefined') return false;\n\tconst displayMode = window.matchMedia?.('(display-mode: standalone)')?.matches === true;\n\t// iOS and iPadOS kept their own flag, older than display-mode.\n\tconst ios = (navigator as Navigator & { standalone?: boolean }).standalone === true;\n\treturn displayMode || ios;\n}\n\n/**\n * What this browser will do with the local store, and the one gesture that\n * changes it. Call it before setting up, and again in whatever screen owns the\n * data: the advice disappears on its own once the remedy is applied.\n */\nexport function storageAdvice(): StorageAdvice {\n\tif (typeof navigator === 'undefined' || typeof window === 'undefined') {\n\t\treturn { risk: 'none', installed: false };\n\t}\n\tconst installed = isStandalone();\n\tif (!isSafari(navigator.userAgent) || installed) {\n\t\treturn { risk: 'none', installed };\n\t}\n\tconst touch = /iPhone|iPad|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1;\n\treturn {\n\t\trisk: 'evicted-when-idle',\n\t\tremedy: touch ? 'install-to-home-screen' : 'install-to-dock',\n\t\tinstalled: false\n\t};\n}\n","/**\n * selfstore/advanced - the pull-model store and the machinery under it.\n *\n * The simple `selfstore()` facade (package root) owns its data. This entry\n * inverts that: with createLocalStore your app owns the data and hands the\n * store a gather/apply pair - use it when state lives in your own reactive\n * model (Svelte runes, Redux, signals) and the facade's collections would be\n * a second copy. Everything here also backs `store.advanced` on the simple\n * store: same machinery, same instance, so the two styles compose.\n *\n * Also here: the durable destinations (disk file, Google Drive, WebDAV) as\n * connectable targets, the BackupTarget contract for writing your own, the\n * caches, the headless status derivation, and the functional codec for the\n * backup file format (the fluent one lives at the package root).\n */\n\n// --- The pull-model store: your app owns the data ---\nexport {\n\tcreateLocalStore,\n\ttype LocalStore,\n\ttype LocalStoreOptions,\n\ttype LocalStoreState,\n\ttype StoreError,\n\ttype Mode,\n\ttype TargetKind,\n\ttype SyncSource,\n\ttype SyncJournalEntry,\n\ttype PeerState,\n\ttype MirrorState,\n\ttype ReplicaState,\n\ttype StoreGroupConfig\n} from '../persistence/store';\n\n// --- On-device caches (the store's working copy) ---\nexport {\n\tindexedDbCache,\n\tmemoryCache,\n\tisLockableCache,\n\ttype LocalCache,\n\ttype LockableCache,\n\ttype CachedFile,\n\ttype KV\n} from '../persistence/cache';\n\n// --- Headless status: map store state to your UI copy ---\nexport {\n\tderiveStatus,\n\ttype StatusDescriptor,\n\ttype StatusInput,\n\ttype Severity,\n\ttype StorageState,\n\ttype StatusAction\n} from '../persistence/status';\n\n// --- Will this browser keep the data, and what can the user do about it ---\nexport { storageAdvice, type StorageAdvice, type StorageRisk } from '../persistence/installation';\n\n// --- The target contract (write your own destination) ---\nexport {\n\tRESERVED_STORE_MODES,\n\tisReservedStoreMode,\n\ttype BackupTarget,\n\ttype BuiltinTargetKind,\n\ttype PeerSource\n} from '../persistence/target';\n\n// --- The backup file format, functional style (fluent lives at the root) ---\nexport {\n\texportSnapshot,\n\timportSnapshot,\n\tinspect,\n\tisEncrypted,\n\tRESERVED_COLLECTION_PREFIX,\n\ttype Snapshot,\n\ttype SnapshotFile,\n\ttype Header,\n\ttype KdfParams,\n\ttype RecipientStanza,\n\ttype EncodeOptions\n} from '../selfstore';\n\n// --- Password strength policy (the store enforces it; the UI previews it) ---\nexport {\n\tcheckPasswordPolicy,\n\ttype PasswordPolicy,\n\ttype PasswordCheck,\n\ttype PasswordRequirement\n} from '../selfstore';\n\n// --- The full error contract (AuthExpiredError is what a custom target or\n// DriveAuth THROWS to signal genuine access loss; everything else it\n// throws is treated as transient and retried) ---\nexport {\n\tSelfstoreError,\n\tAuthExpiredError,\n\tisSelfstoreError,\n\tisAuthExpired,\n\terrorLabelKey,\n\ttype SelfstoreErrorCode\n} from '../selfstore';\n\n// --- Durable destinations (opt-in), each implementing BackupTarget ---\n// Explicit namespace objects: every member is deliberately public, so a future\n// internal helper in a target module can never leak into the contract.\nimport {\n\tconnect as fileConnect,\n\tfromSession as fileFromSession,\n\tisSupported as fileIsSupported,\n\tisOpenSupported as fileIsOpenSupported,\n\topenExisting as fileOpenExisting\n} from '../persistence/targets/file';\nimport {\n\tconnect as webdavConnect,\n\tfromSession as webdavFromSession,\n\tpeer as webdavPeer\n} from '../persistence/targets/webdav';\nimport { connect as s3Connect, fromSession as s3FromSession } from '../persistence/targets/s3';\nimport {\n\tconnect as driveConnect,\n\tfromSession as driveFromSession,\n\tpreview as drivePreview,\n\tadopt as driveAdopt,\n\tfindOrCreateOwnFile as driveFindOrCreateOwnFile,\n\tlistBackups as driveListBackups,\n\tcreateBackup as driveCreateBackup,\n\tdeleteBackup as driveDeleteBackup,\n\trenameBackup as driveRenameBackup,\n\tFILE_ID_KEY as DRIVE_FILE_ID_KEY\n} from '../persistence/targets/drive';\n\n/** Disk-file destination (File System Access, Chromium). */\nexport const fileTarget = {\n\tconnect: fileConnect,\n\tfromSession: fileFromSession,\n\tisSupported: fileIsSupported,\n\tisOpenSupported: fileIsOpenSupported,\n\t/** Let the user point at an EXISTING backup file and adopt it as the\n\t * destination. Gate on isOpenSupported(): unlike creating, this has no\n\t * download fallback. */\n\topenExisting: fileOpenExisting\n};\n\n/** WebDAV destination (Nextcloud, ownCloud, any server the user controls), plus\n * `peer()`: a read-only source over another member's shared WebDAV link, so a\n * group can share read-write without Google Drive (see store.attachPeer). */\nexport const webdavTarget = {\n\tconnect: webdavConnect,\n\tfromSession: webdavFromSession,\n\tpeer: webdavPeer\n};\n\n/** S3-compatible destination (Amazon S3, Cloudflare R2, Backblaze B2, MinIO):\n * the browser signs each request with SigV4 and writes one object on a bucket\n * the user controls, no vendor broker. The bucket only ever holds the same\n * end-to-end-encrypted .selfstore bytes. */\nexport const s3Target = {\n\tconnect: s3Connect,\n\tfromSession: s3FromSession\n};\n\n/** Google Drive destination, plus the shared-file primitives (preview a specific\n * file, adopt it as this device's backup, find-or-create the user's own file)\n * and the backup-management ops (list the app's files with dates, create a\n * named empty backup, rename one, delete one for good) - the raw pieces a \"my\n * backups\" panel builds on. */\nexport const driveTarget = {\n\tconnect: driveConnect,\n\tfromSession: driveFromSession,\n\tpreview: drivePreview,\n\tadopt: driveAdopt,\n\tfindOrCreateOwnFile: driveFindOrCreateOwnFile,\n\tlistBackups: driveListBackups,\n\tcreateBackup: driveCreateBackup,\n\tdeleteBackup: driveDeleteBackup,\n\trenameBackup: driveRenameBackup,\n\tFILE_ID_KEY: DRIVE_FILE_ID_KEY\n};\n\nexport type { FileConnectOptions } from '../persistence/targets/file';\nexport type { DriveAuth, DriveOptions, DriveBackupInfo } from '../persistence/targets/drive';\nexport type {\n\tWebdavConfig,\n\tWebdavConnectOptions,\n\tWebdavPeerOptions\n} from '../persistence/targets/webdav';\nexport type { S3Config, S3ConnectOptions } from '../persistence/targets/s3';\nexport { gisDriveAuth } from '../persistence/targets/drive-auth-gis';\nexport { saveToDisk, pickFromDisk } from '../selfstore/targets/local';\n"]}
package/dist/backups.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { K as KV } from './cache-ilsK3Tki.js';
2
- import { B as BackupTarget, L as LocalStore } from './store-BQhcwSbe.js';
2
+ import { B as BackupTarget, L as LocalStore } from './store-CSxHlrov.js';
3
3
  import { F as FlowStore } from './machine-ALMkT3mk.js';
4
4
  import './sync.js';
5
5
 
@@ -1332,6 +1332,8 @@ function createLocalStore(opts) {
1332
1332
  },
1333
1333
  markDownloaded() {
1334
1334
  pendingDownload = false;
1335
+ lastSavedAt = Date.now();
1336
+ void kv.set(KEY.lastSavedAt, lastSavedAt).catch(() => void 0);
1335
1337
  notify();
1336
1338
  },
1337
1339
  setEphemeral() {
@@ -2028,5 +2030,5 @@ function gisDriveAuth(opts) {
2028
2030
  }
2029
2031
 
2030
2032
  export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache };
2031
- //# sourceMappingURL=chunk-5L7JC2SH.js.map
2032
- //# sourceMappingURL=chunk-5L7JC2SH.js.map
2033
+ //# sourceMappingURL=chunk-PBWDDWWN.js.map
2034
+ //# sourceMappingURL=chunk-PBWDDWWN.js.map