selfstore 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +4 -0
  2. package/dist/advanced.js +7 -7
  3. package/dist/backups.js +1 -1
  4. package/dist/backups.js.map +1 -1
  5. package/dist/{cache-lock-LZILZNLP.js → cache-lock-NXIR6JRB.js} +3 -3
  6. package/dist/{cache-lock-LZILZNLP.js.map → cache-lock-NXIR6JRB.js.map} +1 -1
  7. package/dist/{chunk-SGISYMT5.js → chunk-43BJDRPT.js} +2 -2
  8. package/dist/{chunk-SGISYMT5.js.map → chunk-43BJDRPT.js.map} +1 -1
  9. package/dist/{chunk-N4RPMX3Y.js → chunk-5AAWNPCP.js} +119 -30
  10. package/dist/chunk-5AAWNPCP.js.map +1 -0
  11. package/dist/{chunk-FDPAKHQS.js → chunk-6746AL4I.js} +9 -4
  12. package/dist/chunk-6746AL4I.js.map +1 -0
  13. package/dist/{chunk-OWLX6PNT.js → chunk-KHER6MWR.js} +26 -8
  14. package/dist/chunk-KHER6MWR.js.map +1 -0
  15. package/dist/{chunk-CUWB5EWF.js → chunk-P34WACTD.js} +12 -7
  16. package/dist/chunk-P34WACTD.js.map +1 -0
  17. package/dist/{chunk-IBIEKK36.js → chunk-SKWHKM25.js} +24 -5
  18. package/dist/chunk-SKWHKM25.js.map +1 -0
  19. package/dist/{chunk-JB4JHM7V.js → chunk-SRKUHFRG.js} +17 -8
  20. package/dist/chunk-SRKUHFRG.js.map +1 -0
  21. package/dist/{chunk-BJ3RUMGI.js → chunk-UWVAKBU4.js} +3 -3
  22. package/dist/{chunk-BJ3RUMGI.js.map → chunk-UWVAKBU4.js.map} +1 -1
  23. package/dist/{chunk-ATUPHVLG.js → chunk-Z2MRZSEP.js} +5 -5
  24. package/dist/chunk-Z2MRZSEP.js.map +1 -0
  25. package/dist/flows.js +7 -7
  26. package/dist/flows.js.map +1 -1
  27. package/dist/groups.js +3 -3
  28. package/dist/groups.js.map +1 -1
  29. package/dist/households.js +10 -4
  30. package/dist/households.js.map +1 -1
  31. package/dist/index.js +14 -11
  32. package/dist/index.js.map +1 -1
  33. package/dist/local-KIP25T7I.js +7 -0
  34. package/dist/{local-AM22RWRR.js.map → local-KIP25T7I.js.map} +1 -1
  35. package/dist/sync.js +1 -1
  36. package/dist/widgets.js +445 -86
  37. package/dist/widgets.js.map +1 -1
  38. package/package.json +129 -97
  39. package/dist/chunk-ATUPHVLG.js.map +0 -1
  40. package/dist/chunk-CUWB5EWF.js.map +0 -1
  41. package/dist/chunk-FDPAKHQS.js.map +0 -1
  42. package/dist/chunk-IBIEKK36.js.map +0 -1
  43. package/dist/chunk-JB4JHM7V.js.map +0 -1
  44. package/dist/chunk-N4RPMX3Y.js.map +0 -1
  45. package/dist/chunk-OWLX6PNT.js.map +0 -1
  46. package/dist/local-AM22RWRR.js +0 -7
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # selfstore
2
2
 
3
+ [![ci](https://github.com/selfstoredev/selfstore/actions/workflows/ci.yml/badge.svg)](https://github.com/selfstoredev/selfstore/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/selfstore)](https://www.npmjs.com/package/selfstore)
5
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
3
7
  **Local-first storage for browser apps, batteries included.** Your data lives
4
8
  on the user's device, syncs across their devices without a server, and leaves
5
9
  as a portable encrypted backup they actually own.
package/dist/advanced.js CHANGED
@@ -1,11 +1,11 @@
1
- export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-N4RPMX3Y.js';
2
- export { pickFromDisk, saveToDisk } from './chunk-BJ3RUMGI.js';
3
- import './chunk-IBIEKK36.js';
4
- import { FILE_ID_KEY, renameBackup, deleteBackup, createBackup, listBackups, findOrCreateOwnFile, adopt, preview, fromSession, connect, isSupported, fromSession2, connect2, fromSession4, connect4, peer, fromSession3, connect3 } from './chunk-CUWB5EWF.js';
5
- export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-JB4JHM7V.js';
6
- import './chunk-OWLX6PNT.js';
1
+ export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-5AAWNPCP.js';
2
+ export { pickFromDisk, saveToDisk } from './chunk-UWVAKBU4.js';
3
+ import './chunk-SKWHKM25.js';
4
+ import { FILE_ID_KEY, renameBackup, deleteBackup, createBackup, listBackups, findOrCreateOwnFile, adopt, preview, fromSession, connect, isSupported, fromSession2, connect2, fromSession4, connect4, peer, fromSession3, connect3 } from './chunk-P34WACTD.js';
5
+ export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-SRKUHFRG.js';
6
+ import './chunk-KHER6MWR.js';
7
7
  import './chunk-QBCU7OOP.js';
8
- import './chunk-FDPAKHQS.js';
8
+ import './chunk-6746AL4I.js';
9
9
  export { AuthExpiredError, SelfstoreError, errorLabelKey, isAuthExpired, isSelfstoreError } from './chunk-VBFMMR7Q.js';
10
10
 
11
11
  // src/entries/advanced.ts
package/dist/backups.js CHANGED
@@ -1,4 +1,4 @@
1
- import { makeMachine } from './chunk-SGISYMT5.js';
1
+ import { makeMachine } from './chunk-43BJDRPT.js';
2
2
  import './chunk-VBFMMR7Q.js';
3
3
 
4
4
  // src/backups/manager.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/backups/manager.ts"],"names":[],"mappings":"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","file":"backups.js","sourcesContent":["// Multi-backup manager: several backup files on one destination account, one\n// attached at a time, each an isolated snapshot - opening one replaces the\n// local data, never merges, never touches the other files.\n//\n// It owns the bookkeeping a backup panel needs: the self-healing registry of\n// files this device has connected to; the per-file memories a listing cannot\n// provide (encrypted? shared? - those bits live inside the files, learned as\n// they are opened or probed and remembered in the kv); and the open / create\n// / rename / delete gestures with their isolation invariants (fixed-id\n// targets, active-id committed only after a successful attach, wipe on every\n// switch so no sync-meta crosses files).\n//\n// Injectable and headless: a LocalStore + a kv + a BackupsHost port for the\n// destination I/O. Errors surface as stable codes, never prose; hosts wrap\n// subscribe in their own reactivity.\n\nimport type { KV } from '../persistence/cache';\nimport type { LocalStore } from '../persistence/store';\nimport type { BackupTarget } from '../persistence/target';\nimport { makeMachine, type FlowStore } from '../flows/machine';\n\n// --- Ports --------------------------------------------------------------------\n\n/** What the destination reports about a listed file (one cheap metadata call). */\nexport interface BackupFileInfo {\n\tid: string;\n\tname: string;\n\t/** RFC 3339 last-write timestamp, when the destination reports one. */\n\tmodifiedTime: string | null;\n}\n\n/** The destination I/O the manager drives - the host wires its own adapter\n * (e.g. over the Drive target ops). Every method may throw; the manager turns\n * failures into stable error codes. */\nexport interface BackupsHost {\n\t/** The target kind the managed files live on (e.g. 'drive'): the manager\n\t * only stamps an active file while the store is attached to this kind. */\n\tkind: string;\n\t/** The kv key the attached file id is persisted under (the same one the\n\t * target's connect path reads first). */\n\tactiveIdKey: string;\n\t/** List candidate backup files on the account. The manager applies the\n\t * naming rules on what comes back; internal files simply do not match. */\n\tlist(): Promise<BackupFileInfo[]>;\n\t/** A target over a FIXED file id - never re-resolved by name. The fixed id\n\t * is what keeps two backups from silently merging mid-switch. */\n\topen(fileId: string): BackupTarget;\n\t/** Create an empty file under this name and return its id. Refuse a name\n\t * the account already carries (a duplicate poisons find-by-name). */\n\tcreate(fileName: string): Promise<{ fileId: string }>;\n\t/** Delete a file for good. */\n\tremove(fileId: string): Promise<void>;\n\t/** Rename a file in place (metadata only: the id must not change). */\n\trename?(fileId: string, fileName: string): Promise<void>;\n\t/** Find (or create) the PERSONAL file by its canonical name, ignoring any\n\t * currently adopted id. Null when the user backs out of the session. */\n\tfindOrCreatePersonal(): Promise<{ fileId: string; created: boolean } | null>;\n\t/** Make sure a usable session exists before a remote call (re-consent when\n\t * it lapsed). False = the user refused; the gesture aborts as 'cancelled'.\n\t * Absent = sessions are the target's own business. */\n\tensureSession?(): Promise<boolean>;\n\t/** Resolve the owner label of a file this account does not own, for the\n\t * shared registry (\"shared by X\"). Best-effort. */\n\tfileOwner?(fileId: string): Promise<{ email: string | null; name: string | null } | null>;\n\t/** File ids that must never enter the registry even though the store may\n\t * attach them (e.g. a sharing engine's published copy file). */\n\texcludedFileIds?(): Promise<(string | null | undefined)[]>;\n}\n\n/** How backup files are named on the destination. */\nexport interface BackupsNaming {\n\t/** The personal, unnamed file (\"App.zip\"). Its label reads as null. */\n\tcanonicalName: string;\n\t/** label -> file name. Default: \"Stem (label).ext\" derived from\n\t * canonicalName (\"App.zip\" + \"family\" -> \"App (family).zip\"). */\n\tnamedFileFor?(label: string): string;\n\t/** file name -> parsed label; null = the personal file; undefined = not one\n\t * of ours (dropped from listings). Default matches the default namedFileFor. */\n\tparseLabel?(name: string): string | null | undefined;\n}\n\n/** The kv keys the manager persists under - override to keep a pre-existing\n * install's records. */\nexport interface BackupsKeys {\n\t/** The known-backups registry. */\n\tregistry?: string;\n\t/** fileId -> encrypted? map. */\n\tencrypted?: string;\n\t/** fileId -> shared? map. */\n\tshared?: string;\n\t/** Set while the ACTIVE file is not the personal one (another account's). */\n\tjoined?: string;\n\t/** Cached owner label of that file. */\n\towner?: string;\n}\n\n// --- Shapes -------------------------------------------------------------------\n\n/** Every backup this device has connected to: its own personal file and each\n * OTHER account's file it was let into. Isolated snapshots, all of them. */\nexport interface KnownBackups {\n\tpersonalFileId: string | null;\n\tshared: { fileId: string; ownerEmail: string | null; ownerName: string | null }[];\n}\n\n/** One row of the \"my backups\" list: a file this account owns. */\nexport interface BackupRow {\n\tfileId: string;\n\tname: string;\n\t/** Parsed display label; null for the personal, unnamed file. */\n\tlabel: string | null;\n\t/** Last-write time, epoch ms (null when the destination did not say). */\n\tmodifiedAt: number | null;\n\t/** true/false once known (opened, created or probed); null until then. */\n\tencrypted: boolean | null;\n\t/** true/false once learned (while this file was the active one); null\n\t * until then. A share link existing is the shared state. */\n\tshared: boolean | null;\n}\n\n/** The owner label of a joined (another account's) file. */\nexport interface BackupOwner {\n\temail: string | null;\n\tname: string | null;\n}\n\n/** Stable failure codes; the host maps them to its own copy. */\nexport type BackupsErrorCode = 'cancelled' | 'gone' | 'failed';\n\nexport interface BackupsSnapshot {\n\tregistry: KnownBackups;\n\t/** The file currently attached, when the store is on the managed kind. */\n\tactiveFileId: string | null;\n\t/** The active file is not the personal one (someone else's). */\n\tjoined: boolean;\n\t/** Who owns the joined file, when known. */\n\towner: BackupOwner | null;\n\t/** The last gesture's failure, or null. Reset at each gesture start. */\n\tlastError: BackupsErrorCode | null;\n}\n\nexport interface BackupsManager extends FlowStore<BackupsSnapshot> {\n\t/** Boot: load registry + joined/owner from kv and stamp the active file.\n\t * Kv-only, no destination call. */\n\thydrate(): Promise<void>;\n\t/** Re-read the registry and restamp the active file. Kv-only, cheap. */\n\trefresh(): Promise<void>;\n\t/** After an attach the manager did not make (a connect widget, a join):\n\t * record the now-active file in the registry and restamp joined/owner. */\n\tmarkActive(fileId: string): Promise<void>;\n\t/** Live list of this account's own files, per the naming rules, decorated\n\t * with the learned memories. One destination listing call. */\n\tlist(): Promise<BackupRow[]>;\n\t/** Learn a listed file's encrypted bit without opening it for real: one\n\t * download + header read, remembered for good. Null when unreachable. */\n\tprobeEncryption(fileId: string): Promise<boolean | null>;\n\t/** Record the ACTIVE file's live share state, keyed by file id, so its row\n\t * stays truthful after a switch away. */\n\tnoteShared(fileId: string, shared: boolean): Promise<void>;\n\t/** Open a known backup: an isolated snapshot replaces the local data.\n\t * 'encrypted' = collect its passphrase and call again with it. A file the\n\t * destination reports GONE is pruned from the registry (lastError 'gone'). */\n\topenBackup(fileId: string, passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Open (or create blank) the personal file. */\n\topenPersonal(passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Create a brand-new empty named backup and open it (starts blank - never\n\t * seeded from the currently loaded file). */\n\tcreateNamed(label: string): Promise<'ok' | 'failed'>;\n\t/** Record a file in the SHARED registry under the sharer's label, without\n\t * opening it. For a joined portfolio's dedicated wallet: the file may live\n\t * on this very account, yet it presents as \"shared by X\", never as mine. */\n\tregisterShared(fileId: string, owner: BackupOwner): Promise<void>;\n\t/** Create a joined portfolio's dedicated wallet: a file under the given\n\t * name (outside the own-files naming on purpose), attached BLANK - never\n\t * seeded from the previously loaded silo - and recorded in the shared\n\t * registry under the sharer's label. The host stages the shared content\n\t * afterwards (restore the projection, then save). */\n\tcreateShared(fileName: string, owner: BackupOwner): Promise<'ok' | 'failed'>;\n\t/** Rename a file in place (id unchanged, so renaming the ACTIVE file never\n\t * detaches it). The host target refuses a name already carried. */\n\trenameBackup(fileId: string, label: string): Promise<'ok' | 'failed'>;\n\t/** Delete a file for good. Deleting the ACTIVE one detaches first (the app\n\t * goes device-only, local data stays), so nothing writes mid-delete. */\n\tdeleteBackup(fileId: string): Promise<boolean>;\n\t/** Drop a KNOWN shared (another account's) file from this device's\n\t * registry. Forgetting only - the owner's file is never touched. */\n\tforgetShared(fileId: string): Promise<void>;\n\t/** The file name a label produces (the create form's preview line). */\n\tfileNameFor(label: string): string;\n}\n\n// --- Defaults -----------------------------------------------------------------\n\nconst DEFAULT_KEYS: Required<BackupsKeys> = {\n\tregistry: 'selfstore:backups:registry:v1',\n\tencrypted: 'selfstore:backups:encrypted:v1',\n\tshared: 'selfstore:backups:shared:v1',\n\tjoined: 'selfstore:backups:joined:v1',\n\towner: 'selfstore:backups:owner:v1'\n};\n\nconst escapeRe = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** The default naming scheme: \"App.zip\" -> personal; \"App (label).zip\" ->\n * named. Derived from the canonical name's stem and extension. */\nfunction defaultNaming(naming: BackupsNaming): Required<Pick<BackupsNaming, 'namedFileFor' | 'parseLabel'>> {\n\tconst dot = naming.canonicalName.lastIndexOf('.');\n\tconst stem = dot > 0 ? naming.canonicalName.slice(0, dot) : naming.canonicalName;\n\tconst ext = dot > 0 ? naming.canonicalName.slice(dot) : '';\n\tconst named = new RegExp(`^${escapeRe(stem)} \\\\((.+)\\\\)${escapeRe(ext)}$`);\n\treturn {\n\t\tnamedFileFor: (label: string) => `${stem} (${label.trim()})${ext}`,\n\t\tparseLabel: (name: string) => {\n\t\t\tif (name === naming.canonicalName) return null;\n\t\t\tconst m = named.exec(name);\n\t\t\treturn m ? m[1] : undefined;\n\t\t}\n\t};\n}\n\n// --- Factory ------------------------------------------------------------------\n\nexport function createBackupsManager(deps: {\n\tstore: LocalStore;\n\tkv: KV;\n\thost: BackupsHost;\n\tnaming: BackupsNaming;\n\tkeys?: BackupsKeys;\n}): BackupsManager {\n\tconst { store, kv, host } = deps;\n\tconst KEYS = { ...DEFAULT_KEYS, ...deps.keys };\n\tconst namingDefaults = defaultNaming(deps.naming);\n\tconst naming = {\n\t\tnamedFileFor: deps.naming.namedFileFor ?? namingDefaults.namedFileFor,\n\t\tparseLabel: deps.naming.parseLabel ?? namingDefaults.parseLabel\n\t};\n\n\tconst m = makeMachine<BackupsSnapshot>({\n\t\tregistry: { personalFileId: null, shared: [] },\n\t\tactiveFileId: null,\n\t\tjoined: false,\n\t\towner: null,\n\t\tlastError: null\n\t});\n\n\tconst fail = (code: BackupsErrorCode): void => m.set({ lastError: code });\n\n\t// Which file ids the destination lists as OURS, and which one carries the\n\t// canonical (personal) name. Filled by list(), refreshed lazily where a\n\t// truthful own/foreign call is needed. Null until first learned.\n\tlet ownIds: Set<string> | null = null;\n\tlet ownCanonicalId: string | null = null;\n\n\tfunction learnOwn(files: BackupFileInfo[]): void {\n\t\tconst ids = new Set<string>();\n\t\tlet canonical: string | null = null;\n\t\tfor (const f of files) {\n\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\tif (label === undefined) continue;\n\t\t\tids.add(f.id);\n\t\t\tif (label === null) canonical = f.id;\n\t\t}\n\t\townIds = ids;\n\t\townCanonicalId = canonical;\n\t}\n\n\tasync function ensureOwnKnown(): Promise<void> {\n\t\tif (ownIds !== null) return;\n\t\ttry {\n\t\t\tlearnOwn(await host.list());\n\t\t} catch {\n\t\t\t// Offline: leave unknown - markActive falls back on the registry alone.\n\t\t}\n\t}\n\n\t/** Load the registry, self-healing: excluded ids (a sharing engine's copy\n\t * file) must never sit in it - recorded as \"personal\" they would mislabel\n\t * the chooser and let a later open treat a shared file as one's own. */\n\tasync function loadRegistry(): Promise<KnownBackups> {\n\t\tconst reg = (await kv.get<KnownBackups>(KEYS.registry)) ?? { personalFileId: null, shared: [] };\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tconst bad = new Set(excluded.filter((x): x is string => typeof x === 'string'));\n\t\tif (bad.size === 0) return reg;\n\t\tconst healed: KnownBackups = {\n\t\t\tpersonalFileId: reg.personalFileId && bad.has(reg.personalFileId) ? null : reg.personalFileId,\n\t\t\tshared: reg.shared.filter((s) => !bad.has(s.fileId))\n\t\t};\n\t\tif (healed.personalFileId !== reg.personalFileId || healed.shared.length !== reg.shared.length) {\n\t\t\tawait kv.set(KEYS.registry, healed);\n\t\t}\n\t\treturn healed;\n\t}\n\n\tasync function saveRegistry(next: KnownBackups): Promise<void> {\n\t\tm.set({ registry: next });\n\t\tawait kv.set(KEYS.registry, next);\n\t}\n\n\tasync function noteEncrypted(fileId: string, encrypted: boolean): Promise<void> {\n\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\tif (map[fileId] === encrypted) return;\n\t\tmap[fileId] = encrypted;\n\t\tawait kv.set(KEYS.encrypted, map);\n\t}\n\n\t/** Stamp the currently attached file (from the target's own persisted id)\n\t * into the snapshot and the registry. Kv-only. */\n\tasync function registerActive(): Promise<void> {\n\t\tif (store.state.targetKind !== host.kind) {\n\t\t\tm.set({ activeFileId: null });\n\t\t\treturn;\n\t\t}\n\t\tconst fileId = (await kv.get<string>(host.activeIdKey)) ?? null;\n\t\tm.set({ activeFileId: fileId });\n\t\tif (!fileId) return;\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tif (excluded.includes(fileId)) return;\n\t\tconst reg = await loadRegistry();\n\t\tif (m.snapshot.joined) {\n\t\t\tconst owner = m.snapshot.owner;\n\t\t\tconst entry = { fileId, ownerEmail: owner?.email ?? null, ownerName: owner?.name ?? null };\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [entry, ...reg.shared.filter((s) => s.fileId !== fileId)]\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\t// Not joined. The personal slot belongs to the CANONICAL file alone: an\n\t\t// own NAMED backup lists live and must not clobber it (the slot is the\n\t\t// escape back to the personal file).\n\t\tconst canonical =\n\t\t\tfileId === ownCanonicalId || reg.personalFileId === fileId || (ownIds === null && reg.personalFileId === null);\n\t\tif (canonical) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: fileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t} else if (reg.shared.some((s) => s.fileId === fileId)) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t}\n\n\t/** Session guard shared by the remote gestures: absent hook = no guard. */\n\tasync function ensureSession(): Promise<boolean> {\n\t\tif (!host.ensureSession) return true;\n\t\treturn host.ensureSession();\n\t}\n\n\tasync function markActive(fileId: string): Promise<void> {\n\t\tconst reg = await loadRegistry();\n\t\tconst knownShared = reg.shared.find((s) => s.fileId === fileId);\n\t\tlet mine = reg.personalFileId === fileId;\n\t\tif (!mine && !knownShared) {\n\t\t\t// Unknown file: ask the destination whose it is (one listing call,\n\t\t\t// cached). An own named backup must never read as joined - labeled\n\t\t\t// \"shared by me\", it would sit in the wrong section for good.\n\t\t\tawait ensureOwnKnown();\n\t\t\tmine = ownIds === null ? reg.personalFileId === null : ownIds.has(fileId);\n\t\t}\n\t\tif (mine || (!knownShared && ownIds?.has(fileId))) {\n\t\t\tawait kv.del(KEYS.joined);\n\t\t\tawait kv.del(KEYS.owner);\n\t\t\tm.set({ joined: false, owner: null });\n\t\t} else {\n\t\t\tconst owner: BackupOwner | null = knownShared\n\t\t\t\t? { email: knownShared.ownerEmail, name: knownShared.ownerName }\n\t\t\t\t: ((await host.fileOwner?.(fileId).catch(() => null)) ?? null);\n\t\t\tm.set({ joined: true, owner });\n\t\t\tawait kv.set(KEYS.joined, true);\n\t\t\tif (owner) await kv.set(KEYS.owner, owner);\n\t\t\telse await kv.del(KEYS.owner);\n\t\t}\n\t\tawait registerActive();\n\t}\n\n\t/** The one attach path every open shares: FIXED-id target, and the\n\t * remembered id committed only after the attach succeeds - committed up\n\t * front, the OUTGOING target (which resolves its file lazily from that\n\t * same key) would flush the old file's data into the one being opened:\n\t * two \"isolated\" backups silently merged. `wipe` keeps sync-meta from\n\t * crossing files (cross-file tombstones erase the other side). */\n\tasync function attachFixed(\n\t\tfileId: string,\n\t\tpassphrase: string | null,\n\t\tstrategy: 'replace-local' | 'replace-remote'\n\t): Promise<void> {\n\t\tconst target = host.open(fileId);\n\t\tawait store.attachTarget(target, {\n\t\t\tpassword: passphrase,\n\t\t\tstrategy,\n\t\t\tkeepSession: store.state.targetKind === host.kind,\n\t\t\twipe: true\n\t\t});\n\t\tawait kv.set(host.activeIdKey, fileId);\n\t}\n\n\treturn {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\tfileNameFor(label: string): string {\n\t\t\treturn naming.namedFileFor(label.trim());\n\t\t},\n\n\t\tasync hydrate(): Promise<void> {\n\t\t\tconst joined = (await kv.get(KEYS.joined)) === true;\n\t\t\tconst owner = (await kv.get<BackupOwner>(KEYS.owner)) ?? null;\n\t\t\tm.set({ joined, owner, registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tasync refresh(): Promise<void> {\n\t\t\tm.set({ registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tmarkActive,\n\n\t\tasync list(): Promise<BackupRow[]> {\n\t\t\tconst files = await host.list();\n\t\t\tlearnOwn(files);\n\t\t\tconst enc = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\t\tconst shr = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tconst rows: BackupRow[] = [];\n\t\t\tfor (const f of files) {\n\t\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\t\tif (label === undefined) continue; // not one of ours (a copy, a bulletin, ...)\n\t\t\t\trows.push({\n\t\t\t\t\tfileId: f.id,\n\t\t\t\t\tname: f.name,\n\t\t\t\t\tlabel,\n\t\t\t\t\tmodifiedAt: f.modifiedTime ? Date.parse(f.modifiedTime) || null : null,\n\t\t\t\t\tencrypted: enc[f.id] ?? null,\n\t\t\t\t\tshared: shr[f.id] ?? null\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn rows;\n\t\t},\n\n\t\tasync probeEncryption(fileId: string): Promise<boolean | null> {\n\t\t\ttry {\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) return null;\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted);\n\t\t\t\treturn info.encrypted;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync noteShared(fileId: string, shared: boolean): Promise<void> {\n\t\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tif (map[fileId] === shared) return;\n\t\t\tmap[fileId] = shared;\n\t\t\tawait kv.set(KEYS.shared, map);\n\t\t},\n\n\t\tasync openBackup(fileId, passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) {\n\t\t\t\t\t// Dead entry (deleted, or unshared from this account): prune it so\n\t\t\t\t\t// its row stops re-failing forever. A transient failure throws\n\t\t\t\t\t// instead and never prunes - dropping a good record on a blip\n\t\t\t\t\t// would be far worse than one more failed tap.\n\t\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\t\tawait saveRegistry({\n\t\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t\t});\n\t\t\t\t\tfail('gone');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted); // the listing cannot tell\n\t\t\t\tif (info.encrypted && !passphrase) return 'encrypted';\n\t\t\t\tawait attachFixed(fileId, passphrase, 'replace-local');\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn store.state.locked ? 'encrypted' : 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync openPersonal(passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tconst own = await host.findOrCreatePersonal();\n\t\t\t\tif (!own) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\townCanonicalId = own.fileId;\n\t\t\t\townIds?.add(own.fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: own.fileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== own.fileId)\n\t\t\t\t});\n\t\t\t\tif (!own.created) return await this.openBackup(own.fileId, passphrase);\n\t\t\t\tawait attachFixed(own.fileId, null, 'replace-remote');\n\t\t\t\tawait markActive(own.fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync createNamed(label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(naming.namedFileFor(clean));\n\t\t\t\townIds?.add(fileId);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a fresh backup starts unprotected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync registerShared(fileId, owner): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [\n\t\t\t\t\t{ fileId, ownerEmail: owner.email, ownerName: owner.name },\n\t\t\t\t\t...reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t]\n\t\t\t});\n\t\t},\n\n\t\tasync createShared(fileName, owner): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(fileName);\n\t\t\t\t// Registered as shared BEFORE the attach: markActive must find it in\n\t\t\t\t// the shared registry, or it would resolve this account as the owner\n\t\t\t\t// and file the joined wallet under \"mine\".\n\t\t\t\tawait this.registerShared(fileId, owner);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a member wallet is never protected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync renameBackup(fileId, label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean || !host.rename) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait host.rename(fileId, naming.namedFileFor(clean));\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync deleteBackup(fileId): Promise<boolean> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (fileId === m.snapshot.activeFileId) {\n\t\t\t\t\t// Detach first: the app goes device-only (local data stays) so\n\t\t\t\t\t// nothing writes to the file as it is removed. keepSession: a\n\t\t\t\t\t// default detach runs the target's disconnect, which forgets the\n\t\t\t\t\t// destination credentials - the remove below still needs them,\n\t\t\t\t\t// and the user deleted a FILE, not their account connection.\n\t\t\t\t\tm.set({ activeFileId: null });\n\t\t\t\t\tawait store.detachTarget({ keepSession: true });\n\t\t\t\t\t// The remembered file id would normally be cleared by that\n\t\t\t\t\t// disconnect; do it here so nothing dangles on a deleted file.\n\t\t\t\t\tawait kv.del(host.activeIdKey);\n\t\t\t\t}\n\t\t\t\tawait host.remove(fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync forgetShared(fileId): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t};\n}\n"]}
1
+ {"version":3,"sources":["../src/backups/manager.ts"],"names":[],"mappings":";;;;AAiMA,IAAM,YAAA,GAAsC;AAAA,EAC3C,QAAA,EAAU,+BAAA;AAAA,EACV,SAAA,EAAW,gCAAA;AAAA,EACX,MAAA,EAAQ,6BAAA;AAAA,EACR,MAAA,EAAQ,6BAAA;AAAA,EACR,KAAA,EAAO;AACR,CAAA;AAEA,IAAM,WAAW,CAAC,CAAA,KAAsB,CAAA,CAAE,OAAA,CAAQ,uBAAuB,MAAM,CAAA;AAI/E,SAAS,cACR,MAAA,EAC+D;AAC/D,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,aAAA,CAAc,WAAA,CAAY,GAAG,CAAA;AAChD,EAAA,MAAM,IAAA,GAAO,MAAM,CAAA,GAAI,MAAA,CAAO,cAAc,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,GAAI,MAAA,CAAO,aAAA;AACnE,EAAA,MAAM,MAAM,GAAA,GAAM,CAAA,GAAI,OAAO,aAAA,CAAc,KAAA,CAAM,GAAG,CAAA,GAAI,EAAA;AACxD,EAAA,MAAM,KAAA,GAAQ,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,QAAA,CAAS,IAAI,CAAC,CAAA,WAAA,EAAc,QAAA,CAAS,GAAG,CAAC,CAAA,CAAA,CAAG,CAAA;AACzE,EAAA,OAAO;AAAA,IACN,YAAA,EAAc,CAAC,KAAA,KAAkB,CAAA,EAAG,IAAI,KAAK,KAAA,CAAM,IAAA,EAAM,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA;AAAA,IAChE,UAAA,EAAY,CAAC,IAAA,KAAiB;AAC7B,MAAA,IAAI,IAAA,KAAS,MAAA,CAAO,aAAA,EAAe,OAAO,IAAA;AAC1C,MAAA,MAAM,CAAA,GAAI,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA;AACzB,MAAA,OAAO,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA,GAAI,MAAA;AAAA,IACnB;AAAA,GACD;AACD;AAIO,SAAS,qBAAqB,IAAA,EAMlB;AAClB,EAAA,MAAM,EAAE,KAAA,EAAO,EAAA,EAAI,IAAA,EAAK,GAAI,IAAA;AAC5B,EAAA,MAAM,OAAO,EAAE,GAAG,YAAA,EAAc,GAAG,KAAK,IAAA,EAAK;AAC7C,EAAA,MAAM,cAAA,GAAiB,aAAA,CAAc,IAAA,CAAK,MAAM,CAAA;AAChD,EAAA,MAAM,MAAA,GAAS;AAAA,IACd,YAAA,EAAc,IAAA,CAAK,MAAA,CAAO,YAAA,IAAgB,cAAA,CAAe,YAAA;AAAA,IACzD,UAAA,EAAY,IAAA,CAAK,MAAA,CAAO,UAAA,IAAc,cAAA,CAAe;AAAA,GACtD;AAEA,EAAA,MAAM,IAAI,WAAA,CAA6B;AAAA,IACtC,UAAU,EAAE,cAAA,EAAgB,IAAA,EAAM,MAAA,EAAQ,EAAC,EAAE;AAAA,IAC7C,YAAA,EAAc,IAAA;AAAA,IACd,MAAA,EAAQ,KAAA;AAAA,IACR,KAAA,EAAO,IAAA;AAAA,IACP,SAAA,EAAW;AAAA,GACX,CAAA;AAED,EAAA,MAAM,IAAA,GAAO,CAAC,IAAA,KAAiC,CAAA,CAAE,IAAI,EAAE,SAAA,EAAW,MAAM,CAAA;AAKxE,EAAA,IAAI,MAAA,GAA6B,IAAA;AACjC,EAAA,IAAI,cAAA,GAAgC,IAAA;AAEpC,EAAA,SAAS,SAAS,KAAA,EAA+B;AAChD,IAAA,MAAM,GAAA,uBAAU,GAAA,EAAY;AAC5B,IAAA,IAAI,SAAA,GAA2B,IAAA;AAC/B,IAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACtB,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,UAAA,CAAW,CAAA,CAAE,IAAI,CAAA;AACtC,MAAA,IAAI,UAAU,MAAA,EAAW;AACzB,MAAA,GAAA,CAAI,GAAA,CAAI,EAAE,EAAE,CAAA;AACZ,MAAA,IAAI,KAAA,KAAU,IAAA,EAAM,SAAA,GAAY,CAAA,CAAE,EAAA;AAAA,IACnC;AACA,IAAA,MAAA,GAAS,GAAA;AACT,IAAA,cAAA,GAAiB,SAAA;AAAA,EAClB;AAEA,EAAA,eAAe,cAAA,GAAgC;AAC9C,IAAA,IAAI,WAAW,IAAA,EAAM;AACrB,IAAA,IAAI;AACH,MAAA,QAAA,CAAS,MAAM,IAAA,CAAK,IAAA,EAAM,CAAA;AAAA,IAC3B,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACD;AAKA,EAAA,eAAe,YAAA,GAAsC;AACpD,IAAA,MAAM,GAAA,GAAO,MAAM,EAAA,CAAG,GAAA,CAAkB,IAAA,CAAK,QAAQ,CAAA,IAAM,EAAE,cAAA,EAAgB,IAAA,EAAM,MAAA,EAAQ,EAAC,EAAE;AAC9F,IAAA,MAAM,QAAA,GAAY,MAAM,IAAA,CAAK,eAAA,QAAwB,EAAC;AACtD,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAmB,OAAO,CAAA,KAAM,QAAQ,CAAC,CAAA;AAC9E,IAAA,IAAI,GAAA,CAAI,IAAA,KAAS,CAAA,EAAG,OAAO,GAAA;AAC3B,IAAA,MAAM,MAAA,GAAuB;AAAA,MAC5B,cAAA,EAAgB,IAAI,cAAA,IAAkB,GAAA,CAAI,IAAI,GAAA,CAAI,cAAc,CAAA,GAAI,IAAA,GAAO,GAAA,CAAI,cAAA;AAAA,MAC/E,MAAA,EAAQ,GAAA,CAAI,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,GAAA,CAAI,GAAA,CAAI,CAAA,CAAE,MAAM,CAAC;AAAA,KACpD;AACA,IAAA,IACC,MAAA,CAAO,mBAAmB,GAAA,CAAI,cAAA,IAC9B,OAAO,MAAA,CAAO,MAAA,KAAW,GAAA,CAAI,MAAA,CAAO,MAAA,EACnC;AACD,MAAA,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,QAAA,EAAU,MAAM,CAAA;AAAA,IACnC;AACA,IAAA,OAAO,MAAA;AAAA,EACR;AAEA,EAAA,eAAe,aAAa,IAAA,EAAmC;AAC9D,IAAA,CAAA,CAAE,GAAA,CAAI,EAAE,QAAA,EAAU,IAAA,EAAM,CAAA;AACxB,IAAA,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,QAAA,EAAU,IAAI,CAAA;AAAA,EACjC;AAEA,EAAA,eAAe,aAAA,CAAc,QAAgB,SAAA,EAAmC;AAC/E,IAAA,MAAM,MAAO,MAAM,EAAA,CAAG,IAA6B,IAAA,CAAK,SAAS,KAAM,EAAC;AACxE,IAAA,IAAI,GAAA,CAAI,MAAM,CAAA,KAAM,SAAA,EAAW;AAC/B,IAAA,GAAA,CAAI,MAAM,CAAA,GAAI,SAAA;AACd,IAAA,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,SAAA,EAAW,GAAG,CAAA;AAAA,EACjC;AAIA,EAAA,eAAe,cAAA,GAAgC;AAC9C,IAAA,IAAI,KAAA,CAAM,KAAA,CAAM,UAAA,KAAe,IAAA,CAAK,IAAA,EAAM;AACzC,MAAA,CAAA,CAAE,GAAA,CAAI,EAAE,YAAA,EAAc,IAAA,EAAM,CAAA;AAC5B,MAAA;AAAA,IACD;AACA,IAAA,MAAM,SAAU,MAAM,EAAA,CAAG,GAAA,CAAY,IAAA,CAAK,WAAW,CAAA,IAAM,IAAA;AAC3D,IAAA,CAAA,CAAE,GAAA,CAAI,EAAE,YAAA,EAAc,MAAA,EAAQ,CAAA;AAC9B,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,MAAM,QAAA,GAAY,MAAM,IAAA,CAAK,eAAA,QAAwB,EAAC;AACtD,IAAA,IAAI,QAAA,CAAS,QAAA,CAAS,MAAM,CAAA,EAAG;AAC/B,IAAA,MAAM,GAAA,GAAM,MAAM,YAAA,EAAa;AAC/B,IAAA,IAAI,CAAA,CAAE,SAAS,MAAA,EAAQ;AACtB,MAAA,MAAM,KAAA,GAAQ,EAAE,QAAA,CAAS,KAAA;AACzB,MAAA,MAAM,KAAA,GAAQ,EAAE,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,SAAS,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,IAAA,IAAQ,IAAA,EAAK;AACzF,MAAA,MAAM,YAAA,CAAa;AAAA,QAClB,cAAA,EAAgB,GAAA,CAAI,cAAA,KAAmB,MAAA,GAAS,OAAO,GAAA,CAAI,cAAA;AAAA,QAC3D,MAAA,EAAQ,CAAC,KAAA,EAAO,GAAG,GAAA,CAAI,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,MAAM,CAAC;AAAA,OAChE,CAAA;AACD,MAAA;AAAA,IACD;AAIA,IAAA,MAAM,SAAA,GACL,WAAW,cAAA,IACX,GAAA,CAAI,mBAAmB,MAAA,IACtB,MAAA,KAAW,IAAA,IAAQ,GAAA,CAAI,cAAA,KAAmB,IAAA;AAC5C,IAAA,IAAI,SAAA,EAAW;AACd,MAAA,MAAM,YAAA,CAAa;AAAA,QAClB,cAAA,EAAgB,MAAA;AAAA,QAChB,MAAA,EAAQ,IAAI,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,MAAM;AAAA,OACpD,CAAA;AAAA,IACF,CAAA,MAAA,IAAW,IAAI,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,MAAM,CAAA,EAAG;AACvD,MAAA,MAAM,YAAA,CAAa;AAAA,QAClB,gBAAgB,GAAA,CAAI,cAAA;AAAA,QACpB,MAAA,EAAQ,IAAI,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,MAAM;AAAA,OACpD,CAAA;AAAA,IACF;AAAA,EACD;AAGA,EAAA,eAAe,aAAA,GAAkC;AAChD,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,EAAe,OAAO,IAAA;AAChC,IAAA,OAAO,KAAK,aAAA,EAAc;AAAA,EAC3B;AAEA,EAAA,eAAe,WAAW,MAAA,EAA+B;AACxD,IAAA,MAAM,GAAA,GAAM,MAAM,YAAA,EAAa;AAC/B,IAAA,MAAM,WAAA,GAAc,IAAI,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,MAAM,CAAA;AAC9D,IAAA,IAAI,IAAA,GAAO,IAAI,cAAA,KAAmB,MAAA;AAClC,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,WAAA,EAAa;AAI1B,MAAA,MAAM,cAAA,EAAe;AACrB,MAAA,IAAA,GAAO,WAAW,IAAA,GAAO,GAAA,CAAI,mBAAmB,IAAA,GAAO,MAAA,CAAO,IAAI,MAAM,CAAA;AAAA,IACzE;AACA,IAAA,IAAI,QAAS,CAAC,WAAA,IAAe,MAAA,EAAQ,GAAA,CAAI,MAAM,CAAA,EAAI;AAClD,MAAA,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,MAAM,CAAA;AACxB,MAAA,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,KAAK,CAAA;AACvB,MAAA,CAAA,CAAE,IAAI,EAAE,MAAA,EAAQ,KAAA,EAAO,KAAA,EAAO,MAAM,CAAA;AAAA,IACrC,CAAA,MAAO;AACN,MAAA,MAAM,QAA4B,WAAA,GAC/B,EAAE,OAAO,WAAA,CAAY,UAAA,EAAY,MAAM,WAAA,CAAY,SAAA,EAAU,GAC3D,MAAM,KAAK,SAAA,GAAY,MAAM,EAAE,KAAA,CAAM,MAAM,IAAI,CAAA,IAAM,IAAA;AAC1D,MAAA,CAAA,CAAE,GAAA,CAAI,EAAE,MAAA,EAAQ,IAAA,EAAM,OAAO,CAAA;AAC7B,MAAA,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,MAAA,EAAQ,IAAI,CAAA;AAC9B,MAAA,IAAI,OAAO,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,OAAO,KAAK,CAAA;AAAA,WACpC,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,KAAK,CAAA;AAAA,IAC7B;AACA,IAAA,MAAM,cAAA,EAAe;AAAA,EACtB;AAQA,EAAA,eAAe,WAAA,CACd,MAAA,EACA,UAAA,EACA,QAAA,EACgB;AAChB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,MAAM,CAAA;AAC/B,IAAA,MAAM,KAAA,CAAM,aAAa,MAAA,EAAQ;AAAA,MAChC,QAAA,EAAU,UAAA;AAAA,MACV,QAAA;AAAA,MACA,WAAA,EAAa,KAAA,CAAM,KAAA,CAAM,UAAA,KAAe,IAAA,CAAK,IAAA;AAAA,MAC7C,IAAA,EAAM;AAAA,KACN,CAAA;AACD,IAAA,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,WAAA,EAAa,MAAM,CAAA;AAAA,EACtC;AAEA,EAAA,OAAO;AAAA,IACN,IAAI,QAAA,GAAW;AACd,MAAA,OAAO,CAAA,CAAE,QAAA;AAAA,IACV,CAAA;AAAA,IACA,WAAW,CAAA,CAAE,SAAA;AAAA,IAEb,YAAY,KAAA,EAAuB;AAClC,MAAA,OAAO,MAAA,CAAO,YAAA,CAAa,KAAA,CAAM,IAAA,EAAM,CAAA;AAAA,IACxC,CAAA;AAAA,IAEA,MAAM,OAAA,GAAyB;AAC9B,MAAA,MAAM,SAAU,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,MAAM,CAAA,KAAO,IAAA;AAC/C,MAAA,MAAM,QAAS,MAAM,EAAA,CAAG,GAAA,CAAiB,IAAA,CAAK,KAAK,CAAA,IAAM,IAAA;AACzD,MAAA,CAAA,CAAE,GAAA,CAAI,EAAE,MAAA,EAAQ,KAAA,EAAO,UAAU,MAAM,YAAA,IAAgB,CAAA;AACvD,MAAA,MAAM,cAAA,EAAe;AAAA,IACtB,CAAA;AAAA,IAEA,MAAM,OAAA,GAAyB;AAC9B,MAAA,CAAA,CAAE,IAAI,EAAE,QAAA,EAAU,MAAM,YAAA,IAAgB,CAAA;AACxC,MAAA,MAAM,cAAA,EAAe;AAAA,IACtB,CAAA;AAAA,IAEA,UAAA;AAAA,IAEA,MAAM,IAAA,GAA6B;AAClC,MAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,IAAA,EAAK;AAC9B,MAAA,QAAA,CAAS,KAAK,CAAA;AACd,MAAA,MAAM,MAAO,MAAM,EAAA,CAAG,IAA6B,IAAA,CAAK,SAAS,KAAM,EAAC;AACxE,MAAA,MAAM,MAAO,MAAM,EAAA,CAAG,IAA6B,IAAA,CAAK,MAAM,KAAM,EAAC;AACrE,MAAA,MAAM,OAAoB,EAAC;AAC3B,MAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACtB,QAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,UAAA,CAAW,CAAA,CAAE,IAAI,CAAA;AACtC,QAAA,IAAI,UAAU,MAAA,EAAW;AACzB,QAAA,IAAA,CAAK,IAAA,CAAK;AAAA,UACT,QAAQ,CAAA,CAAE,EAAA;AAAA,UACV,MAAM,CAAA,CAAE,IAAA;AAAA,UACR,KAAA;AAAA,UACA,UAAA,EAAY,EAAE,YAAA,GAAe,IAAA,CAAK,MAAM,CAAA,CAAE,YAAY,KAAK,IAAA,GAAO,IAAA;AAAA,UAClE,SAAA,EAAW,GAAA,CAAI,CAAA,CAAE,EAAE,CAAA,IAAK,IAAA;AAAA,UACxB,MAAA,EAAQ,GAAA,CAAI,CAAA,CAAE,EAAE,CAAA,IAAK;AAAA,SACrB,CAAA;AAAA,MACF;AACA,MAAA,OAAO,IAAA;AAAA,IACR,CAAA;AAAA,IAEA,MAAM,gBAAgB,MAAA,EAAyC;AAC9D,MAAA,IAAI;AACH,QAAA,MAAM,OAAO,MAAM,KAAA,CAAM,cAAc,IAAA,CAAK,IAAA,CAAK,MAAM,CAAC,CAAA;AACxD,QAAA,IAAI,CAAC,IAAA,CAAK,SAAA,EAAW,OAAO,IAAA;AAC5B,QAAA,MAAM,aAAA,CAAc,MAAA,EAAQ,IAAA,CAAK,SAAS,CAAA;AAC1C,QAAA,OAAO,IAAA,CAAK,SAAA;AAAA,MACb,CAAA,CAAA,MAAQ;AACP,QAAA,OAAO,IAAA;AAAA,MACR;AAAA,IACD,CAAA;AAAA,IAEA,MAAM,UAAA,CAAW,MAAA,EAAgB,MAAA,EAAgC;AAChE,MAAA,MAAM,MAAO,MAAM,EAAA,CAAG,IAA6B,IAAA,CAAK,MAAM,KAAM,EAAC;AACrE,MAAA,IAAI,GAAA,CAAI,MAAM,CAAA,KAAM,MAAA,EAAQ;AAC5B,MAAA,GAAA,CAAI,MAAM,CAAA,GAAI,MAAA;AACd,MAAA,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,MAAA,EAAQ,GAAG,CAAA;AAAA,IAC9B,CAAA;AAAA,IAEA,MAAM,UAAA,CAAW,MAAA,EAAQ,UAAA,GAAa,IAAA,EAA8C;AACnF,MAAA,CAAA,CAAE,GAAA,CAAI,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AACzB,MAAA,IAAI;AACH,QAAA,IAAI,CAAE,MAAM,aAAA,EAAc,EAAI;AAC7B,UAAA,IAAA,CAAK,WAAW,CAAA;AAChB,UAAA,OAAO,QAAA;AAAA,QACR;AACA,QAAA,MAAM,OAAO,MAAM,KAAA,CAAM,cAAc,IAAA,CAAK,IAAA,CAAK,MAAM,CAAC,CAAA;AACxD,QAAA,IAAI,CAAC,KAAK,SAAA,EAAW;AAKpB,UAAA,MAAM,GAAA,GAAM,MAAM,YAAA,EAAa;AAC/B,UAAA,MAAM,YAAA,CAAa;AAAA,YAClB,cAAA,EAAgB,GAAA,CAAI,cAAA,KAAmB,MAAA,GAAS,OAAO,GAAA,CAAI,cAAA;AAAA,YAC3D,MAAA,EAAQ,IAAI,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,MAAM;AAAA,WACpD,CAAA;AACD,UAAA,IAAA,CAAK,MAAM,CAAA;AACX,UAAA,OAAO,QAAA;AAAA,QACR;AACA,QAAA,MAAM,aAAA,CAAc,MAAA,EAAQ,IAAA,CAAK,SAAS,CAAA;AAC1C,QAAA,IAAI,IAAA,CAAK,SAAA,IAAa,CAAC,UAAA,EAAY,OAAO,WAAA;AAC1C,QAAA,MAAM,WAAA,CAAY,MAAA,EAAQ,UAAA,EAAY,eAAe,CAAA;AACrD,QAAA,MAAM,WAAW,MAAM,CAAA;AACvB,QAAA,OAAO,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,WAAA,GAAc,IAAA;AAAA,MAC3C,CAAA,CAAA,MAAQ;AACP,QAAA,IAAA,CAAK,QAAQ,CAAA;AACb,QAAA,OAAO,QAAA;AAAA,MACR;AAAA,IACD,CAAA;AAAA,IAEA,MAAM,YAAA,CAAa,UAAA,GAAa,IAAA,EAA8C;AAC7E,MAAA,CAAA,CAAE,GAAA,CAAI,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AACzB,MAAA,IAAI;AACH,QAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,oBAAA,EAAqB;AAC5C,QAAA,IAAI,CAAC,GAAA,EAAK;AACT,UAAA,IAAA,CAAK,WAAW,CAAA;AAChB,UAAA,OAAO,QAAA;AAAA,QACR;AACA,QAAA,cAAA,GAAiB,GAAA,CAAI,MAAA;AACrB,QAAA,MAAA,EAAQ,GAAA,CAAI,IAAI,MAAM,CAAA;AACtB,QAAA,MAAM,GAAA,GAAM,MAAM,YAAA,EAAa;AAC/B,QAAA,MAAM,YAAA,CAAa;AAAA,UAClB,gBAAgB,GAAA,CAAI,MAAA;AAAA,UACpB,MAAA,EAAQ,IAAI,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,GAAA,CAAI,MAAM;AAAA,SACxD,CAAA;AACD,QAAA,IAAI,CAAC,IAAI,OAAA,EAAS,OAAO,MAAM,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,MAAA,EAAQ,UAAU,CAAA;AACrE,QAAA,MAAM,WAAA,CAAY,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,gBAAgB,CAAA;AACpD,QAAA,MAAM,UAAA,CAAW,IAAI,MAAM,CAAA;AAC3B,QAAA,OAAO,IAAA;AAAA,MACR,CAAA,CAAA,MAAQ;AACP,QAAA,IAAA,CAAK,QAAQ,CAAA;AACb,QAAA,OAAO,QAAA;AAAA,MACR;AAAA,IACD,CAAA;AAAA,IAEA,MAAM,YAAY,KAAA,EAAiC;AAClD,MAAA,CAAA,CAAE,GAAA,CAAI,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AACzB,MAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,EAAK;AACzB,MAAA,IAAI,CAAC,KAAA,EAAO;AACX,QAAA,IAAA,CAAK,QAAQ,CAAA;AACb,QAAA,OAAO,QAAA;AAAA,MACR;AACA,MAAA,IAAI;AACH,QAAA,IAAI,CAAE,MAAM,aAAA,EAAc,EAAI;AAC7B,UAAA,IAAA,CAAK,WAAW,CAAA;AAChB,UAAA,OAAO,QAAA;AAAA,QACR;AACA,QAAA,MAAM,EAAE,QAAO,GAAI,MAAM,KAAK,MAAA,CAAO,MAAA,CAAO,YAAA,CAAa,KAAK,CAAC,CAAA;AAC/D,QAAA,MAAA,EAAQ,IAAI,MAAM,CAAA;AAClB,QAAA,MAAM,WAAA,CAAY,MAAA,EAAQ,IAAA,EAAM,gBAAgB,CAAA;AAChD,QAAA,MAAM,aAAA,CAAc,QAAQ,KAAK,CAAA;AACjC,QAAA,MAAM,WAAW,MAAM,CAAA;AACvB,QAAA,OAAO,IAAA;AAAA,MACR,CAAA,CAAA,MAAQ;AACP,QAAA,IAAA,CAAK,QAAQ,CAAA;AACb,QAAA,OAAO,QAAA;AAAA,MACR;AAAA,IACD,CAAA;AAAA,IAEA,MAAM,cAAA,CAAe,MAAA,EAAQ,KAAA,EAAsB;AAClD,MAAA,MAAM,GAAA,GAAM,MAAM,YAAA,EAAa;AAC/B,MAAA,MAAM,YAAA,CAAa;AAAA,QAClB,cAAA,EAAgB,GAAA,CAAI,cAAA,KAAmB,MAAA,GAAS,OAAO,GAAA,CAAI,cAAA;AAAA,QAC3D,MAAA,EAAQ;AAAA,UACP,EAAE,MAAA,EAAQ,UAAA,EAAY,MAAM,KAAA,EAAO,SAAA,EAAW,MAAM,IAAA,EAAK;AAAA,UACzD,GAAG,IAAI,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,MAAM;AAAA;AAChD,OACA,CAAA;AAAA,IACF,CAAA;AAAA,IAEA,MAAM,YAAA,CAAa,QAAA,EAAU,KAAA,EAAiC;AAC7D,MAAA,CAAA,CAAE,GAAA,CAAI,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AACzB,MAAA,IAAI;AACH,QAAA,IAAI,CAAE,MAAM,aAAA,EAAc,EAAI;AAC7B,UAAA,IAAA,CAAK,WAAW,CAAA;AAChB,UAAA,OAAO,QAAA;AAAA,QACR;AACA,QAAA,MAAM,EAAE,MAAA,EAAO,GAAI,MAAM,IAAA,CAAK,OAAO,QAAQ,CAAA;AAI7C,QAAA,MAAM,IAAA,CAAK,cAAA,CAAe,MAAA,EAAQ,KAAK,CAAA;AACvC,QAAA,MAAM,WAAA,CAAY,MAAA,EAAQ,IAAA,EAAM,gBAAgB,CAAA;AAChD,QAAA,MAAM,aAAA,CAAc,QAAQ,KAAK,CAAA;AACjC,QAAA,MAAM,WAAW,MAAM,CAAA;AACvB,QAAA,OAAO,IAAA;AAAA,MACR,CAAA,CAAA,MAAQ;AACP,QAAA,IAAA,CAAK,QAAQ,CAAA;AACb,QAAA,OAAO,QAAA;AAAA,MACR;AAAA,IACD,CAAA;AAAA,IAEA,MAAM,YAAA,CAAa,MAAA,EAAQ,KAAA,EAAiC;AAC3D,MAAA,CAAA,CAAE,GAAA,CAAI,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AACzB,MAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,EAAK;AACzB,MAAA,IAAI,CAAC,KAAA,IAAS,CAAC,IAAA,CAAK,MAAA,EAAQ;AAC3B,QAAA,IAAA,CAAK,QAAQ,CAAA;AACb,QAAA,OAAO,QAAA;AAAA,MACR;AACA,MAAA,IAAI;AACH,QAAA,MAAM,KAAK,MAAA,CAAO,MAAA,EAAQ,MAAA,CAAO,YAAA,CAAa,KAAK,CAAC,CAAA;AACpD,QAAA,OAAO,IAAA;AAAA,MACR,CAAA,CAAA,MAAQ;AACP,QAAA,IAAA,CAAK,QAAQ,CAAA;AACb,QAAA,OAAO,QAAA;AAAA,MACR;AAAA,IACD,CAAA;AAAA,IAEA,MAAM,aAAa,MAAA,EAA0B;AAC5C,MAAA,CAAA,CAAE,GAAA,CAAI,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AACzB,MAAA,IAAI;AACH,QAAA,IAAI,MAAA,KAAW,CAAA,CAAE,QAAA,CAAS,YAAA,EAAc;AAMvC,UAAA,CAAA,CAAE,GAAA,CAAI,EAAE,YAAA,EAAc,IAAA,EAAM,CAAA;AAC5B,UAAA,MAAM,KAAA,CAAM,YAAA,CAAa,EAAE,WAAA,EAAa,MAAM,CAAA;AAG9C,UAAA,MAAM,EAAA,CAAG,GAAA,CAAI,IAAA,CAAK,WAAW,CAAA;AAAA,QAC9B;AACA,QAAA,MAAM,IAAA,CAAK,OAAO,MAAM,CAAA;AACxB,QAAA,MAAM,GAAA,GAAM,MAAM,YAAA,EAAa;AAC/B,QAAA,MAAM,YAAA,CAAa;AAAA,UAClB,cAAA,EAAgB,GAAA,CAAI,cAAA,KAAmB,MAAA,GAAS,OAAO,GAAA,CAAI,cAAA;AAAA,UAC3D,MAAA,EAAQ,IAAI,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,MAAM;AAAA,SACpD,CAAA;AACD,QAAA,OAAO,IAAA;AAAA,MACR,CAAA,CAAA,MAAQ;AACP,QAAA,IAAA,CAAK,QAAQ,CAAA;AACb,QAAA,OAAO,KAAA;AAAA,MACR;AAAA,IACD,CAAA;AAAA,IAEA,MAAM,aAAa,MAAA,EAAuB;AACzC,MAAA,MAAM,GAAA,GAAM,MAAM,YAAA,EAAa;AAC/B,MAAA,MAAM,YAAA,CAAa;AAAA,QAClB,gBAAgB,GAAA,CAAI,cAAA;AAAA,QACpB,MAAA,EAAQ,IAAI,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,MAAM;AAAA,OACpD,CAAA;AAAA,IACF;AAAA,GACD;AACD","file":"backups.js","sourcesContent":["// Multi-backup manager: several backup files on one destination account, one\n// attached at a time, each an isolated snapshot - opening one replaces the\n// local data, never merges, never touches the other files.\n//\n// It owns the bookkeeping a backup panel needs: the self-healing registry of\n// files this device has connected to; the per-file memories a listing cannot\n// provide (encrypted? shared? - those bits live inside the files, learned as\n// they are opened or probed and remembered in the kv); and the open / create\n// / rename / delete gestures with their isolation invariants (fixed-id\n// targets, active-id committed only after a successful attach, wipe on every\n// switch so no sync-meta crosses files).\n//\n// Injectable and headless: a LocalStore + a kv + a BackupsHost port for the\n// destination I/O. Errors surface as stable codes, never prose; hosts wrap\n// subscribe in their own reactivity.\n\nimport type { KV } from '../persistence/cache';\nimport type { LocalStore } from '../persistence/store';\nimport type { BackupTarget } from '../persistence/target';\nimport { makeMachine, type FlowStore } from '../flows/machine';\n\n// --- Ports --------------------------------------------------------------------\n\n/** What the destination reports about a listed file (one cheap metadata call). */\nexport interface BackupFileInfo {\n\tid: string;\n\tname: string;\n\t/** RFC 3339 last-write timestamp, when the destination reports one. */\n\tmodifiedTime: string | null;\n}\n\n/** The destination I/O the manager drives - the host wires its own adapter\n * (e.g. over the Drive target ops). Every method may throw; the manager turns\n * failures into stable error codes. */\nexport interface BackupsHost {\n\t/** The target kind the managed files live on (e.g. 'drive'): the manager\n\t * only stamps an active file while the store is attached to this kind. */\n\tkind: string;\n\t/** The kv key the attached file id is persisted under (the same one the\n\t * target's connect path reads first). */\n\tactiveIdKey: string;\n\t/** List candidate backup files on the account. The manager applies the\n\t * naming rules on what comes back; internal files simply do not match. */\n\tlist(): Promise<BackupFileInfo[]>;\n\t/** A target over a FIXED file id - never re-resolved by name. The fixed id\n\t * is what keeps two backups from silently merging mid-switch. */\n\topen(fileId: string): BackupTarget;\n\t/** Create an empty file under this name and return its id. Refuse a name\n\t * the account already carries (a duplicate poisons find-by-name). */\n\tcreate(fileName: string): Promise<{ fileId: string }>;\n\t/** Delete a file for good. */\n\tremove(fileId: string): Promise<void>;\n\t/** Rename a file in place (metadata only: the id must not change). */\n\trename?(fileId: string, fileName: string): Promise<void>;\n\t/** Find (or create) the PERSONAL file by its canonical name, ignoring any\n\t * currently adopted id. Null when the user backs out of the session. */\n\tfindOrCreatePersonal(): Promise<{ fileId: string; created: boolean } | null>;\n\t/** Make sure a usable session exists before a remote call (re-consent when\n\t * it lapsed). False = the user refused; the gesture aborts as 'cancelled'.\n\t * Absent = sessions are the target's own business. */\n\tensureSession?(): Promise<boolean>;\n\t/** Resolve the owner label of a file this account does not own, for the\n\t * shared registry (\"shared by X\"). Best-effort. */\n\tfileOwner?(fileId: string): Promise<{ email: string | null; name: string | null } | null>;\n\t/** File ids that must never enter the registry even though the store may\n\t * attach them (e.g. a sharing engine's published copy file). */\n\texcludedFileIds?(): Promise<(string | null | undefined)[]>;\n}\n\n/** How backup files are named on the destination. */\nexport interface BackupsNaming {\n\t/** The personal, unnamed file (\"App.zip\"). Its label reads as null. */\n\tcanonicalName: string;\n\t/** label -> file name. Default: \"Stem (label).ext\" derived from\n\t * canonicalName (\"App.zip\" + \"family\" -> \"App (family).zip\"). */\n\tnamedFileFor?(label: string): string;\n\t/** file name -> parsed label; null = the personal file; undefined = not one\n\t * of ours (dropped from listings). Default matches the default namedFileFor. */\n\tparseLabel?(name: string): string | null | undefined;\n}\n\n/** The kv keys the manager persists under - override to keep a pre-existing\n * install's records. */\nexport interface BackupsKeys {\n\t/** The known-backups registry. */\n\tregistry?: string;\n\t/** fileId -> encrypted? map. */\n\tencrypted?: string;\n\t/** fileId -> shared? map. */\n\tshared?: string;\n\t/** Set while the ACTIVE file is not the personal one (another account's). */\n\tjoined?: string;\n\t/** Cached owner label of that file. */\n\towner?: string;\n}\n\n// --- Shapes -------------------------------------------------------------------\n\n/** Every backup this device has connected to: its own personal file and each\n * OTHER account's file it was let into. Isolated snapshots, all of them. */\nexport interface KnownBackups {\n\tpersonalFileId: string | null;\n\tshared: { fileId: string; ownerEmail: string | null; ownerName: string | null }[];\n}\n\n/** One row of the \"my backups\" list: a file this account owns. */\nexport interface BackupRow {\n\tfileId: string;\n\tname: string;\n\t/** Parsed display label; null for the personal, unnamed file. */\n\tlabel: string | null;\n\t/** Last-write time, epoch ms (null when the destination did not say). */\n\tmodifiedAt: number | null;\n\t/** true/false once known (opened, created or probed); null until then. */\n\tencrypted: boolean | null;\n\t/** true/false once learned (while this file was the active one); null\n\t * until then. A share link existing is the shared state. */\n\tshared: boolean | null;\n}\n\n/** The owner label of a joined (another account's) file. */\nexport interface BackupOwner {\n\temail: string | null;\n\tname: string | null;\n}\n\n/** Stable failure codes; the host maps them to its own copy. */\nexport type BackupsErrorCode = 'cancelled' | 'gone' | 'failed';\n\nexport interface BackupsSnapshot {\n\tregistry: KnownBackups;\n\t/** The file currently attached, when the store is on the managed kind. */\n\tactiveFileId: string | null;\n\t/** The active file is not the personal one (someone else's). */\n\tjoined: boolean;\n\t/** Who owns the joined file, when known. */\n\towner: BackupOwner | null;\n\t/** The last gesture's failure, or null. Reset at each gesture start. */\n\tlastError: BackupsErrorCode | null;\n}\n\nexport interface BackupsManager extends FlowStore<BackupsSnapshot> {\n\t/** Boot: load registry + joined/owner from kv and stamp the active file.\n\t * Kv-only, no destination call. */\n\thydrate(): Promise<void>;\n\t/** Re-read the registry and restamp the active file. Kv-only, cheap. */\n\trefresh(): Promise<void>;\n\t/** After an attach the manager did not make (a connect widget, a join):\n\t * record the now-active file in the registry and restamp joined/owner. */\n\tmarkActive(fileId: string): Promise<void>;\n\t/** Live list of this account's own files, per the naming rules, decorated\n\t * with the learned memories. One destination listing call. */\n\tlist(): Promise<BackupRow[]>;\n\t/** Learn a listed file's encrypted bit without opening it for real: one\n\t * download + header read, remembered for good. Null when unreachable. */\n\tprobeEncryption(fileId: string): Promise<boolean | null>;\n\t/** Record the ACTIVE file's live share state, keyed by file id, so its row\n\t * stays truthful after a switch away. */\n\tnoteShared(fileId: string, shared: boolean): Promise<void>;\n\t/** Open a known backup: an isolated snapshot replaces the local data.\n\t * 'encrypted' = collect its passphrase and call again with it. A file the\n\t * destination reports GONE is pruned from the registry (lastError 'gone'). */\n\topenBackup(fileId: string, passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Open (or create blank) the personal file. */\n\topenPersonal(passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Create a brand-new empty named backup and open it (starts blank - never\n\t * seeded from the currently loaded file). */\n\tcreateNamed(label: string): Promise<'ok' | 'failed'>;\n\t/** Record a file in the SHARED registry under the sharer's label, without\n\t * opening it. For a joined portfolio's dedicated wallet: the file may live\n\t * on this very account, yet it presents as \"shared by X\", never as mine. */\n\tregisterShared(fileId: string, owner: BackupOwner): Promise<void>;\n\t/** Create a joined portfolio's dedicated wallet: a file under the given\n\t * name (outside the own-files naming on purpose), attached BLANK - never\n\t * seeded from the previously loaded silo - and recorded in the shared\n\t * registry under the sharer's label. The host stages the shared content\n\t * afterwards (restore the projection, then save). */\n\tcreateShared(fileName: string, owner: BackupOwner): Promise<'ok' | 'failed'>;\n\t/** Rename a file in place (id unchanged, so renaming the ACTIVE file never\n\t * detaches it). The host target refuses a name already carried. */\n\trenameBackup(fileId: string, label: string): Promise<'ok' | 'failed'>;\n\t/** Delete a file for good. Deleting the ACTIVE one detaches first (the app\n\t * goes device-only, local data stays), so nothing writes mid-delete. */\n\tdeleteBackup(fileId: string): Promise<boolean>;\n\t/** Drop a KNOWN shared (another account's) file from this device's\n\t * registry. Forgetting only - the owner's file is never touched. */\n\tforgetShared(fileId: string): Promise<void>;\n\t/** The file name a label produces (the create form's preview line). */\n\tfileNameFor(label: string): string;\n}\n\n// --- Defaults -----------------------------------------------------------------\n\nconst DEFAULT_KEYS: Required<BackupsKeys> = {\n\tregistry: 'selfstore:backups:registry:v1',\n\tencrypted: 'selfstore:backups:encrypted:v1',\n\tshared: 'selfstore:backups:shared:v1',\n\tjoined: 'selfstore:backups:joined:v1',\n\towner: 'selfstore:backups:owner:v1'\n};\n\nconst escapeRe = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** The default naming scheme: \"App.zip\" -> personal; \"App (label).zip\" ->\n * named. Derived from the canonical name's stem and extension. */\nfunction defaultNaming(\n\tnaming: BackupsNaming\n): Required<Pick<BackupsNaming, 'namedFileFor' | 'parseLabel'>> {\n\tconst dot = naming.canonicalName.lastIndexOf('.');\n\tconst stem = dot > 0 ? naming.canonicalName.slice(0, dot) : naming.canonicalName;\n\tconst ext = dot > 0 ? naming.canonicalName.slice(dot) : '';\n\tconst named = new RegExp(`^${escapeRe(stem)} \\\\((.+)\\\\)${escapeRe(ext)}$`);\n\treturn {\n\t\tnamedFileFor: (label: string) => `${stem} (${label.trim()})${ext}`,\n\t\tparseLabel: (name: string) => {\n\t\t\tif (name === naming.canonicalName) return null;\n\t\t\tconst m = named.exec(name);\n\t\t\treturn m ? m[1] : undefined;\n\t\t}\n\t};\n}\n\n// --- Factory ------------------------------------------------------------------\n\nexport function createBackupsManager(deps: {\n\tstore: LocalStore;\n\tkv: KV;\n\thost: BackupsHost;\n\tnaming: BackupsNaming;\n\tkeys?: BackupsKeys;\n}): BackupsManager {\n\tconst { store, kv, host } = deps;\n\tconst KEYS = { ...DEFAULT_KEYS, ...deps.keys };\n\tconst namingDefaults = defaultNaming(deps.naming);\n\tconst naming = {\n\t\tnamedFileFor: deps.naming.namedFileFor ?? namingDefaults.namedFileFor,\n\t\tparseLabel: deps.naming.parseLabel ?? namingDefaults.parseLabel\n\t};\n\n\tconst m = makeMachine<BackupsSnapshot>({\n\t\tregistry: { personalFileId: null, shared: [] },\n\t\tactiveFileId: null,\n\t\tjoined: false,\n\t\towner: null,\n\t\tlastError: null\n\t});\n\n\tconst fail = (code: BackupsErrorCode): void => m.set({ lastError: code });\n\n\t// Which file ids the destination lists as OURS, and which one carries the\n\t// canonical (personal) name. Filled by list(), refreshed lazily where a\n\t// truthful own/foreign call is needed. Null until first learned.\n\tlet ownIds: Set<string> | null = null;\n\tlet ownCanonicalId: string | null = null;\n\n\tfunction learnOwn(files: BackupFileInfo[]): void {\n\t\tconst ids = new Set<string>();\n\t\tlet canonical: string | null = null;\n\t\tfor (const f of files) {\n\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\tif (label === undefined) continue;\n\t\t\tids.add(f.id);\n\t\t\tif (label === null) canonical = f.id;\n\t\t}\n\t\townIds = ids;\n\t\townCanonicalId = canonical;\n\t}\n\n\tasync function ensureOwnKnown(): Promise<void> {\n\t\tif (ownIds !== null) return;\n\t\ttry {\n\t\t\tlearnOwn(await host.list());\n\t\t} catch {\n\t\t\t// Offline: leave unknown - markActive falls back on the registry alone.\n\t\t}\n\t}\n\n\t/** Load the registry, self-healing: excluded ids (a sharing engine's copy\n\t * file) must never sit in it - recorded as \"personal\" they would mislabel\n\t * the chooser and let a later open treat a shared file as one's own. */\n\tasync function loadRegistry(): Promise<KnownBackups> {\n\t\tconst reg = (await kv.get<KnownBackups>(KEYS.registry)) ?? { personalFileId: null, shared: [] };\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tconst bad = new Set(excluded.filter((x): x is string => typeof x === 'string'));\n\t\tif (bad.size === 0) return reg;\n\t\tconst healed: KnownBackups = {\n\t\t\tpersonalFileId: reg.personalFileId && bad.has(reg.personalFileId) ? null : reg.personalFileId,\n\t\t\tshared: reg.shared.filter((s) => !bad.has(s.fileId))\n\t\t};\n\t\tif (\n\t\t\thealed.personalFileId !== reg.personalFileId ||\n\t\t\thealed.shared.length !== reg.shared.length\n\t\t) {\n\t\t\tawait kv.set(KEYS.registry, healed);\n\t\t}\n\t\treturn healed;\n\t}\n\n\tasync function saveRegistry(next: KnownBackups): Promise<void> {\n\t\tm.set({ registry: next });\n\t\tawait kv.set(KEYS.registry, next);\n\t}\n\n\tasync function noteEncrypted(fileId: string, encrypted: boolean): Promise<void> {\n\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\tif (map[fileId] === encrypted) return;\n\t\tmap[fileId] = encrypted;\n\t\tawait kv.set(KEYS.encrypted, map);\n\t}\n\n\t/** Stamp the currently attached file (from the target's own persisted id)\n\t * into the snapshot and the registry. Kv-only. */\n\tasync function registerActive(): Promise<void> {\n\t\tif (store.state.targetKind !== host.kind) {\n\t\t\tm.set({ activeFileId: null });\n\t\t\treturn;\n\t\t}\n\t\tconst fileId = (await kv.get<string>(host.activeIdKey)) ?? null;\n\t\tm.set({ activeFileId: fileId });\n\t\tif (!fileId) return;\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tif (excluded.includes(fileId)) return;\n\t\tconst reg = await loadRegistry();\n\t\tif (m.snapshot.joined) {\n\t\t\tconst owner = m.snapshot.owner;\n\t\t\tconst entry = { fileId, ownerEmail: owner?.email ?? null, ownerName: owner?.name ?? null };\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [entry, ...reg.shared.filter((s) => s.fileId !== fileId)]\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\t// Not joined. The personal slot belongs to the CANONICAL file alone: an\n\t\t// own NAMED backup lists live and must not clobber it (the slot is the\n\t\t// escape back to the personal file).\n\t\tconst canonical =\n\t\t\tfileId === ownCanonicalId ||\n\t\t\treg.personalFileId === fileId ||\n\t\t\t(ownIds === null && reg.personalFileId === null);\n\t\tif (canonical) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: fileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t} else if (reg.shared.some((s) => s.fileId === fileId)) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t}\n\n\t/** Session guard shared by the remote gestures: absent hook = no guard. */\n\tasync function ensureSession(): Promise<boolean> {\n\t\tif (!host.ensureSession) return true;\n\t\treturn host.ensureSession();\n\t}\n\n\tasync function markActive(fileId: string): Promise<void> {\n\t\tconst reg = await loadRegistry();\n\t\tconst knownShared = reg.shared.find((s) => s.fileId === fileId);\n\t\tlet mine = reg.personalFileId === fileId;\n\t\tif (!mine && !knownShared) {\n\t\t\t// Unknown file: ask the destination whose it is (one listing call,\n\t\t\t// cached). An own named backup must never read as joined - labeled\n\t\t\t// \"shared by me\", it would sit in the wrong section for good.\n\t\t\tawait ensureOwnKnown();\n\t\t\tmine = ownIds === null ? reg.personalFileId === null : ownIds.has(fileId);\n\t\t}\n\t\tif (mine || (!knownShared && ownIds?.has(fileId))) {\n\t\t\tawait kv.del(KEYS.joined);\n\t\t\tawait kv.del(KEYS.owner);\n\t\t\tm.set({ joined: false, owner: null });\n\t\t} else {\n\t\t\tconst owner: BackupOwner | null = knownShared\n\t\t\t\t? { email: knownShared.ownerEmail, name: knownShared.ownerName }\n\t\t\t\t: ((await host.fileOwner?.(fileId).catch(() => null)) ?? null);\n\t\t\tm.set({ joined: true, owner });\n\t\t\tawait kv.set(KEYS.joined, true);\n\t\t\tif (owner) await kv.set(KEYS.owner, owner);\n\t\t\telse await kv.del(KEYS.owner);\n\t\t}\n\t\tawait registerActive();\n\t}\n\n\t/** The one attach path every open shares: FIXED-id target, and the\n\t * remembered id committed only after the attach succeeds - committed up\n\t * front, the OUTGOING target (which resolves its file lazily from that\n\t * same key) would flush the old file's data into the one being opened:\n\t * two \"isolated\" backups silently merged. `wipe` keeps sync-meta from\n\t * crossing files (cross-file tombstones erase the other side). */\n\tasync function attachFixed(\n\t\tfileId: string,\n\t\tpassphrase: string | null,\n\t\tstrategy: 'replace-local' | 'replace-remote'\n\t): Promise<void> {\n\t\tconst target = host.open(fileId);\n\t\tawait store.attachTarget(target, {\n\t\t\tpassword: passphrase,\n\t\t\tstrategy,\n\t\t\tkeepSession: store.state.targetKind === host.kind,\n\t\t\twipe: true\n\t\t});\n\t\tawait kv.set(host.activeIdKey, fileId);\n\t}\n\n\treturn {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\tfileNameFor(label: string): string {\n\t\t\treturn naming.namedFileFor(label.trim());\n\t\t},\n\n\t\tasync hydrate(): Promise<void> {\n\t\t\tconst joined = (await kv.get(KEYS.joined)) === true;\n\t\t\tconst owner = (await kv.get<BackupOwner>(KEYS.owner)) ?? null;\n\t\t\tm.set({ joined, owner, registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tasync refresh(): Promise<void> {\n\t\t\tm.set({ registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tmarkActive,\n\n\t\tasync list(): Promise<BackupRow[]> {\n\t\t\tconst files = await host.list();\n\t\t\tlearnOwn(files);\n\t\t\tconst enc = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\t\tconst shr = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tconst rows: BackupRow[] = [];\n\t\t\tfor (const f of files) {\n\t\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\t\tif (label === undefined) continue; // not one of ours (a copy, a bulletin, ...)\n\t\t\t\trows.push({\n\t\t\t\t\tfileId: f.id,\n\t\t\t\t\tname: f.name,\n\t\t\t\t\tlabel,\n\t\t\t\t\tmodifiedAt: f.modifiedTime ? Date.parse(f.modifiedTime) || null : null,\n\t\t\t\t\tencrypted: enc[f.id] ?? null,\n\t\t\t\t\tshared: shr[f.id] ?? null\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn rows;\n\t\t},\n\n\t\tasync probeEncryption(fileId: string): Promise<boolean | null> {\n\t\t\ttry {\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) return null;\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted);\n\t\t\t\treturn info.encrypted;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync noteShared(fileId: string, shared: boolean): Promise<void> {\n\t\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tif (map[fileId] === shared) return;\n\t\t\tmap[fileId] = shared;\n\t\t\tawait kv.set(KEYS.shared, map);\n\t\t},\n\n\t\tasync openBackup(fileId, passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) {\n\t\t\t\t\t// Dead entry (deleted, or unshared from this account): prune it so\n\t\t\t\t\t// its row stops re-failing forever. A transient failure throws\n\t\t\t\t\t// instead and never prunes - dropping a good record on a blip\n\t\t\t\t\t// would be far worse than one more failed tap.\n\t\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\t\tawait saveRegistry({\n\t\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t\t});\n\t\t\t\t\tfail('gone');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted); // the listing cannot tell\n\t\t\t\tif (info.encrypted && !passphrase) return 'encrypted';\n\t\t\t\tawait attachFixed(fileId, passphrase, 'replace-local');\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn store.state.locked ? 'encrypted' : 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync openPersonal(passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tconst own = await host.findOrCreatePersonal();\n\t\t\t\tif (!own) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\townCanonicalId = own.fileId;\n\t\t\t\townIds?.add(own.fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: own.fileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== own.fileId)\n\t\t\t\t});\n\t\t\t\tif (!own.created) return await this.openBackup(own.fileId, passphrase);\n\t\t\t\tawait attachFixed(own.fileId, null, 'replace-remote');\n\t\t\t\tawait markActive(own.fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync createNamed(label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(naming.namedFileFor(clean));\n\t\t\t\townIds?.add(fileId);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a fresh backup starts unprotected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync registerShared(fileId, owner): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [\n\t\t\t\t\t{ fileId, ownerEmail: owner.email, ownerName: owner.name },\n\t\t\t\t\t...reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t]\n\t\t\t});\n\t\t},\n\n\t\tasync createShared(fileName, owner): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(fileName);\n\t\t\t\t// Registered as shared BEFORE the attach: markActive must find it in\n\t\t\t\t// the shared registry, or it would resolve this account as the owner\n\t\t\t\t// and file the joined wallet under \"mine\".\n\t\t\t\tawait this.registerShared(fileId, owner);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a member wallet is never protected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync renameBackup(fileId, label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean || !host.rename) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait host.rename(fileId, naming.namedFileFor(clean));\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync deleteBackup(fileId): Promise<boolean> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (fileId === m.snapshot.activeFileId) {\n\t\t\t\t\t// Detach first: the app goes device-only (local data stays) so\n\t\t\t\t\t// nothing writes to the file as it is removed. keepSession: a\n\t\t\t\t\t// default detach runs the target's disconnect, which forgets the\n\t\t\t\t\t// destination credentials - the remove below still needs them,\n\t\t\t\t\t// and the user deleted a FILE, not their account connection.\n\t\t\t\t\tm.set({ activeFileId: null });\n\t\t\t\t\tawait store.detachTarget({ keepSession: true });\n\t\t\t\t\t// The remembered file id would normally be cleared by that\n\t\t\t\t\t// disconnect; do it here so nothing dangles on a deleted file.\n\t\t\t\t\tawait kv.del(host.activeIdKey);\n\t\t\t\t}\n\t\t\t\tawait host.remove(fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync forgetShared(fileId): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t};\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { unseal } from './chunk-QBCU7OOP.js';
2
- import { freshKdfParams, deriveKey } from './chunk-FDPAKHQS.js';
2
+ import { freshKdfParams, deriveKey } from './chunk-6746AL4I.js';
3
3
  import './chunk-VBFMMR7Q.js';
4
4
 
5
5
  // src/persistence/cache-lock.ts
@@ -19,5 +19,5 @@ async function opens(key, probe) {
19
19
  }
20
20
 
21
21
  export { freshCacheKdf, keyFromPassword, opens };
22
- //# sourceMappingURL=cache-lock-LZILZNLP.js.map
23
- //# sourceMappingURL=cache-lock-LZILZNLP.js.map
22
+ //# sourceMappingURL=cache-lock-NXIR6JRB.js.map
23
+ //# sourceMappingURL=cache-lock-NXIR6JRB.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/persistence/cache-lock.ts"],"names":[],"mappings":";;;;;AAeO,SAAS,aAAA,GAA0B;AACzC,EAAA,OAAO,cAAA,EAAe;AACvB;AAGO,SAAS,eAAA,CAAgB,UAAkB,GAAA,EAAmC;AACpF,EAAA,OAAO,SAAA,CAAU,UAAU,GAAG,CAAA;AAC/B;AAIA,eAAsB,KAAA,CAAM,KAAgB,KAAA,EAAsC;AACjF,EAAA,IAAI;AACH,IAAA,MAAM,MAAA,CAAO,KAAK,KAAK,CAAA;AACvB,IAAA,OAAO,IAAA;AAAA,EACR,CAAA,CAAA,MAAQ;AACP,IAAA,OAAO,KAAA;AAAA,EACR;AACD","file":"cache-lock-LZILZNLP.js","sourcesContent":["// Seal-key material for a lock-mode cache. The key is derived from a password\n// (the backup's Argon2id KDF) or supplied by the app (e.g. a passkey PRF key),\n// and is held in memory only - never written next to the data. That is what a\n// device-key seal (cache-crypto) cannot give: a copy of the whole browser\n// profile carries the device key, but not a secret kept in RAM. Only the KDF\n// salt is persisted, and a salt is not secret.\n\nimport { unseal, type EncEnvelope } from './cache-crypto';\nimport { deriveKey, freshKdfParams } from '../selfstore/crypto';\nimport type { KdfParams } from '../selfstore/types';\n\n/** Argon2id parameters for a password-locked cache; persisted in the clear. */\nexport type CacheKdf = KdfParams;\n\n/** Fresh Argon2id parameters (random salt, current default cost). */\nexport function freshCacheKdf(): CacheKdf {\n\treturn freshKdfParams();\n}\n\n/** Derive the AES-GCM seal key from a password and its stored parameters. */\nexport function keyFromPassword(password: string, kdf: CacheKdf): Promise<CryptoKey> {\n\treturn deriveKey(password, kdf);\n}\n\n/** True if `key` decrypts `probe`. A wrong key fails AES-GCM authentication;\n * that is a wrong-secret answer, never an error to surface. */\nexport async function opens(key: CryptoKey, probe: EncEnvelope): Promise<boolean> {\n\ttry {\n\t\tawait unseal(key, probe);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n"]}
1
+ {"version":3,"sources":["../src/persistence/cache-lock.ts"],"names":[],"mappings":";;;;;AAeO,SAAS,aAAA,GAA0B;AACzC,EAAA,OAAO,cAAA,EAAe;AACvB;AAGO,SAAS,eAAA,CAAgB,UAAkB,GAAA,EAAmC;AACpF,EAAA,OAAO,SAAA,CAAU,UAAU,GAAG,CAAA;AAC/B;AAIA,eAAsB,KAAA,CAAM,KAAgB,KAAA,EAAsC;AACjF,EAAA,IAAI;AACH,IAAA,MAAM,MAAA,CAAO,KAAK,KAAK,CAAA;AACvB,IAAA,OAAO,IAAA;AAAA,EACR,CAAA,CAAA,MAAQ;AACP,IAAA,OAAO,KAAA;AAAA,EACR;AACD","file":"cache-lock-NXIR6JRB.js","sourcesContent":["// Seal-key material for a lock-mode cache. The key is derived from a password\n// (the backup's Argon2id KDF) or supplied by the app (e.g. a passkey PRF key),\n// and is held in memory only - never written next to the data. That is what a\n// device-key seal (cache-crypto) cannot give: a copy of the whole browser\n// profile carries the device key, but not a secret kept in RAM. Only the KDF\n// salt is persisted, and a salt is not secret.\n\nimport { unseal, type EncEnvelope } from './cache-crypto';\nimport { deriveKey, freshKdfParams } from '../selfstore/crypto';\nimport type { KdfParams } from '../selfstore/types';\n\n/** Argon2id parameters for a password-locked cache; persisted in the clear. */\nexport type CacheKdf = KdfParams;\n\n/** Fresh Argon2id parameters (random salt, current default cost). */\nexport function freshCacheKdf(): CacheKdf {\n\treturn freshKdfParams();\n}\n\n/** Derive the AES-GCM seal key from a password and its stored parameters. */\nexport function keyFromPassword(password: string, kdf: CacheKdf): Promise<CryptoKey> {\n\treturn deriveKey(password, kdf);\n}\n\n/** True if `key` decrypts `probe`. A wrong key fails AES-GCM authentication;\n * that is a wrong-secret answer, never an error to surface. */\nexport async function opens(key: CryptoKey, probe: EncEnvelope): Promise<boolean> {\n\ttry {\n\t\tawait unseal(key, probe);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n"]}
@@ -42,5 +42,5 @@ function toStoreError(e) {
42
42
  }
43
43
 
44
44
  export { makeMachine, toStoreError, withDeadline };
45
- //# sourceMappingURL=chunk-SGISYMT5.js.map
46
- //# sourceMappingURL=chunk-SGISYMT5.js.map
45
+ //# sourceMappingURL=chunk-43BJDRPT.js.map
46
+ //# sourceMappingURL=chunk-43BJDRPT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/flows/machine.ts"],"names":[],"mappings":";;;AAuBO,SAAS,YAA8B,OAAA,EAAwB;AACrE,EAAA,IAAI,QAAA,GAAW,OAAA;AACf,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAoB;AAC1C,EAAA,OAAO;AAAA,IACN,IAAI,QAAA,GAAc;AACjB,MAAA,OAAO,QAAA;AAAA,IACR,CAAA;AAAA,IACA,IAAI,KAAA,EAAyB;AAC5B,MAAA,QAAA,GAAW,EAAE,GAAG,QAAA,EAAU,GAAG,KAAA,EAAM;AACnC,MAAA,SAAA,CAAU,OAAA,CAAQ,CAAC,EAAA,KAAO,EAAA,CAAG,QAAQ,CAAC,CAAA;AAAA,IACvC,CAAA;AAAA,IACA,UAAU,QAAA,EAAsC;AAC/C,MAAA,SAAA,CAAU,IAAI,QAAQ,CAAA;AACtB,MAAA,QAAA,CAAS,QAAQ,CAAA;AACjB,MAAA,OAAO,MAAM,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,IACvC;AAAA,GACD;AACD;AAOA,eAAsB,YAAA,CAAgB,IAAA,EAAkB,EAAA,EAAY,IAAA,EAA0B;AAC7F,EAAA,IAAI,KAAA;AACJ,EAAA,MAAM,IAAA,GAAO,IAAI,OAAA,CAAe,CAAC,UAAU,MAAA,KAAW;AACrD,IAAA,KAAA,GAAQ,UAAA;AAAA,MACP,MAAM,MAAA,CAAO,IAAI,cAAA,CAAe,oBAAA,EAAsB,GAAG,IAAI,CAAA,uBAAA,EAA0B,EAAE,CAAA,GAAA,CAAK,CAAC,CAAA;AAAA,MAC/F;AAAA,KACD;AAAA,EACD,CAAC,CAAA;AACD,EAAA,IAAI;AACH,IAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,IAAA,EAAM,IAAI,CAAC,CAAA;AAAA,EACvC,CAAA,SAAE;AACD,IAAA,YAAA,CAAa,KAAK,CAAA;AAAA,EACnB;AACD;AAMO,SAAS,aAAa,CAAA,EAAwB;AACpD,EAAA,IAAI,gBAAA,CAAiB,CAAC,CAAA,EAAG;AACxB,IAAA,OAAO,EAAE,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,QAAA,EAAU,aAAA,CAAc,CAAA,CAAE,IAAI,CAAA,EAAG,OAAA,EAAS,CAAA,CAAE,OAAA,EAAQ;AAAA,EAC5E;AACA,EAAA,MAAM,UAAU,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,OAAO,CAAC,CAAA;AACzD,EAAA,OAAO,EAAE,IAAA,EAAM,oBAAA,EAAsB,UAAU,aAAA,CAAc,oBAAoB,GAAG,OAAA,EAAQ;AAC7F","file":"chunk-SGISYMT5.js","sourcesContent":["// Shared plumbing for the flows: a tiny observable snapshot (fits the\n// Svelte store contract and React's useSyncExternalStore), a deadline\n// wrapper for network legs, and the normalizer that turns anything thrown\n// into the stable { code, labelKey, message } shape.\n\nimport type { StoreError } from '../persistence/store';\nimport { SelfstoreError, errorLabelKey, isSelfstoreError } from '../selfstore/errors';\n\n/** What every flow exposes: the current snapshot plus a subscription.\n * `subscribe` calls the listener IMMEDIATELY with the current snapshot and on\n * every change after - that is the Svelte store contract, so a flow works as\n * `$flow` in Svelte; React reads `useSyncExternalStore(flow.subscribe, () =>\n * flow.snapshot)`; everyone else just re-renders in the callback. */\nexport interface FlowStore<T> {\n\treadonly snapshot: T;\n\tsubscribe(listener: (snapshot: T) => void): () => void;\n}\n\nexport interface Machine<T> extends FlowStore<T> {\n\t/** Merge a partial into the snapshot and notify. */\n\tset(patch: Partial<T>): void;\n}\n\nexport function makeMachine<T extends object>(initial: T): Machine<T> {\n\tlet snapshot = initial;\n\tconst listeners = new Set<(s: T) => void>();\n\treturn {\n\t\tget snapshot(): T {\n\t\t\treturn snapshot;\n\t\t},\n\t\tset(patch: Partial<T>): void {\n\t\t\tsnapshot = { ...snapshot, ...patch };\n\t\t\tlisteners.forEach((fn) => fn(snapshot));\n\t\t},\n\t\tsubscribe(listener: (s: T) => void): () => void {\n\t\t\tlisteners.add(listener);\n\t\t\tlistener(snapshot);\n\t\t\treturn () => listeners.delete(listener);\n\t\t}\n\t};\n}\n\n/** Bound a network leg so a stuck destination surfaces as a retryable error\n * instead of a flow frozen on a spinner. The rejection is TARGET_UNAVAILABLE -\n * transient by contract - because \"did not answer in time\" says nothing about\n * access being lost. Never wrap a human step (a consent popup, a picker):\n * people are allowed to be slower than a deadline. */\nexport async function withDeadline<T>(work: Promise<T>, ms: number, what: string): Promise<T> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tconst gate = new Promise<never>((_resolve, reject) => {\n\t\ttimer = setTimeout(\n\t\t\t() => reject(new SelfstoreError('TARGET_UNAVAILABLE', `${what} did not answer within ${ms}ms.`)),\n\t\t\tms\n\t\t);\n\t});\n\ttry {\n\t\treturn await Promise.race([work, gate]);\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n}\n\n/** Normalize anything thrown into the stable StoreError shape the whole\n * library reports with: show `labelKey`, log `message`. Unknown errors map to\n * TARGET_UNAVAILABLE (transient, retryable) - the flow never invents new\n * codes for the app to branch on. */\nexport function toStoreError(e: unknown): StoreError {\n\tif (isSelfstoreError(e)) {\n\t\treturn { code: e.code, labelKey: errorLabelKey(e.code), message: e.message };\n\t}\n\tconst message = e instanceof Error ? e.message : String(e);\n\treturn { code: 'TARGET_UNAVAILABLE', labelKey: errorLabelKey('TARGET_UNAVAILABLE'), message };\n}\n"]}
1
+ {"version":3,"sources":["../src/flows/machine.ts"],"names":[],"mappings":";;;AAuBO,SAAS,YAA8B,OAAA,EAAwB;AACrE,EAAA,IAAI,QAAA,GAAW,OAAA;AACf,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAoB;AAC1C,EAAA,OAAO;AAAA,IACN,IAAI,QAAA,GAAc;AACjB,MAAA,OAAO,QAAA;AAAA,IACR,CAAA;AAAA,IACA,IAAI,KAAA,EAAyB;AAC5B,MAAA,QAAA,GAAW,EAAE,GAAG,QAAA,EAAU,GAAG,KAAA,EAAM;AACnC,MAAA,SAAA,CAAU,OAAA,CAAQ,CAAC,EAAA,KAAO,EAAA,CAAG,QAAQ,CAAC,CAAA;AAAA,IACvC,CAAA;AAAA,IACA,UAAU,QAAA,EAAsC;AAC/C,MAAA,SAAA,CAAU,IAAI,QAAQ,CAAA;AACtB,MAAA,QAAA,CAAS,QAAQ,CAAA;AACjB,MAAA,OAAO,MAAM,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,IACvC;AAAA,GACD;AACD;AAOA,eAAsB,YAAA,CAAgB,IAAA,EAAkB,EAAA,EAAY,IAAA,EAA0B;AAC7F,EAAA,IAAI,KAAA;AACJ,EAAA,MAAM,IAAA,GAAO,IAAI,OAAA,CAAe,CAAC,UAAU,MAAA,KAAW;AACrD,IAAA,KAAA,GAAQ,UAAA;AAAA,MACP,MACC,MAAA,CAAO,IAAI,cAAA,CAAe,oBAAA,EAAsB,GAAG,IAAI,CAAA,uBAAA,EAA0B,EAAE,CAAA,GAAA,CAAK,CAAC,CAAA;AAAA,MAC1F;AAAA,KACD;AAAA,EACD,CAAC,CAAA;AACD,EAAA,IAAI;AACH,IAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,IAAA,EAAM,IAAI,CAAC,CAAA;AAAA,EACvC,CAAA,SAAE;AACD,IAAA,YAAA,CAAa,KAAK,CAAA;AAAA,EACnB;AACD;AAMO,SAAS,aAAa,CAAA,EAAwB;AACpD,EAAA,IAAI,gBAAA,CAAiB,CAAC,CAAA,EAAG;AACxB,IAAA,OAAO,EAAE,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,QAAA,EAAU,aAAA,CAAc,CAAA,CAAE,IAAI,CAAA,EAAG,OAAA,EAAS,CAAA,CAAE,OAAA,EAAQ;AAAA,EAC5E;AACA,EAAA,MAAM,UAAU,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,OAAO,CAAC,CAAA;AACzD,EAAA,OAAO,EAAE,IAAA,EAAM,oBAAA,EAAsB,UAAU,aAAA,CAAc,oBAAoB,GAAG,OAAA,EAAQ;AAC7F","file":"chunk-43BJDRPT.js","sourcesContent":["// Shared plumbing for the flows: a tiny observable snapshot (fits the\n// Svelte store contract and React's useSyncExternalStore), a deadline\n// wrapper for network legs, and the normalizer that turns anything thrown\n// into the stable { code, labelKey, message } shape.\n\nimport type { StoreError } from '../persistence/store';\nimport { SelfstoreError, errorLabelKey, isSelfstoreError } from '../selfstore/errors';\n\n/** What every flow exposes: the current snapshot plus a subscription.\n * `subscribe` calls the listener IMMEDIATELY with the current snapshot and on\n * every change after - that is the Svelte store contract, so a flow works as\n * `$flow` in Svelte; React reads `useSyncExternalStore(flow.subscribe, () =>\n * flow.snapshot)`; everyone else just re-renders in the callback. */\nexport interface FlowStore<T> {\n\treadonly snapshot: T;\n\tsubscribe(listener: (snapshot: T) => void): () => void;\n}\n\nexport interface Machine<T> extends FlowStore<T> {\n\t/** Merge a partial into the snapshot and notify. */\n\tset(patch: Partial<T>): void;\n}\n\nexport function makeMachine<T extends object>(initial: T): Machine<T> {\n\tlet snapshot = initial;\n\tconst listeners = new Set<(s: T) => void>();\n\treturn {\n\t\tget snapshot(): T {\n\t\t\treturn snapshot;\n\t\t},\n\t\tset(patch: Partial<T>): void {\n\t\t\tsnapshot = { ...snapshot, ...patch };\n\t\t\tlisteners.forEach((fn) => fn(snapshot));\n\t\t},\n\t\tsubscribe(listener: (s: T) => void): () => void {\n\t\t\tlisteners.add(listener);\n\t\t\tlistener(snapshot);\n\t\t\treturn () => listeners.delete(listener);\n\t\t}\n\t};\n}\n\n/** Bound a network leg so a stuck destination surfaces as a retryable error\n * instead of a flow frozen on a spinner. The rejection is TARGET_UNAVAILABLE -\n * transient by contract - because \"did not answer in time\" says nothing about\n * access being lost. Never wrap a human step (a consent popup, a picker):\n * people are allowed to be slower than a deadline. */\nexport async function withDeadline<T>(work: Promise<T>, ms: number, what: string): Promise<T> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tconst gate = new Promise<never>((_resolve, reject) => {\n\t\ttimer = setTimeout(\n\t\t\t() =>\n\t\t\t\treject(new SelfstoreError('TARGET_UNAVAILABLE', `${what} did not answer within ${ms}ms.`)),\n\t\t\tms\n\t\t);\n\t});\n\ttry {\n\t\treturn await Promise.race([work, gate]);\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n}\n\n/** Normalize anything thrown into the stable StoreError shape the whole\n * library reports with: show `labelKey`, log `message`. Unknown errors map to\n * TARGET_UNAVAILABLE (transient, retryable) - the flow never invents new\n * codes for the app to branch on. */\nexport function toStoreError(e: unknown): StoreError {\n\tif (isSelfstoreError(e)) {\n\t\treturn { code: e.code, labelKey: errorLabelKey(e.code), message: e.message };\n\t}\n\tconst message = e instanceof Error ? e.message : String(e);\n\treturn { code: 'TARGET_UNAVAILABLE', labelKey: errorLabelKey('TARGET_UNAVAILABLE'), message };\n}\n"]}
@@ -1,8 +1,8 @@
1
- import { createNode, createMeta, gcTombstones, stamp, merge, unsyncableCounts, idPathFor, detectConflicts, changes } from './chunk-IBIEKK36.js';
2
- import { inspect, checkPasswordPolicy, writeBox, BACKUP_MIME, readBoxWithSync, asBytes } from './chunk-JB4JHM7V.js';
3
- import { openManifest, GROUP_KEYING } from './chunk-OWLX6PNT.js';
1
+ import { createNode, createMeta, gcTombstones, stamp, merge, unsyncableCounts, idPathFor, detectConflicts, changes } from './chunk-SKWHKM25.js';
2
+ import { inspect, checkPasswordPolicy, writeBox, BACKUP_MIME, readBoxWithSync, asBytes } from './chunk-SRKUHFRG.js';
3
+ import { openManifest, GROUP_KEYING } from './chunk-KHER6MWR.js';
4
4
  import { seal, isEnvelope, unseal, newDeviceKey } from './chunk-QBCU7OOP.js';
5
- import { mintSlot, MAX_KEY_SLOTS, mintExternalSlot, openSlot } from './chunk-FDPAKHQS.js';
5
+ import { mintSlot, MAX_KEY_SLOTS, mintExternalSlot, openSlot } from './chunk-6746AL4I.js';
6
6
  import { SelfstoreError, isAuthExpired, errorLabelKey, isSelfstoreError } from './chunk-VBFMMR7Q.js';
7
7
  import { openDB } from 'idb';
8
8
 
@@ -86,9 +86,40 @@ var KEY = {
86
86
  groupSeqIds: "groupSeqIds"
87
87
  };
88
88
  var JOURNAL_MAX = 20;
89
+ var BOOT_NET_MS = 25e3;
90
+ var BootTimeout = class extends Error {
91
+ constructor(what) {
92
+ super(`${what} timed out at boot`);
93
+ this.name = "BootTimeout";
94
+ }
95
+ };
96
+ function bootDeadline(work, what, ms = BOOT_NET_MS) {
97
+ return new Promise((resolve, reject) => {
98
+ const timer = setTimeout(() => reject(new BootTimeout(what)), ms);
99
+ work.then(
100
+ (v) => {
101
+ clearTimeout(timer);
102
+ resolve(v);
103
+ },
104
+ (e) => {
105
+ clearTimeout(timer);
106
+ reject(e instanceof Error ? e : new Error(String(e)));
107
+ }
108
+ );
109
+ });
110
+ }
89
111
  var STALE_CHECK_MS = 2e4;
90
112
  function createLocalStore(opts) {
91
- const { app, schemaVersion: version, appVersion, gather, apply, cache, readme, debounceMs = 800 } = opts;
113
+ const {
114
+ app,
115
+ schemaVersion: version,
116
+ appVersion,
117
+ gather,
118
+ apply,
119
+ cache,
120
+ readme,
121
+ debounceMs = 800
122
+ } = opts;
92
123
  const logger = opts.logger ?? console;
93
124
  const requireEncryption = opts.requireEncryption ?? false;
94
125
  const passwordPolicy = opts.passwordPolicy;
@@ -96,7 +127,10 @@ function createLocalStore(opts) {
96
127
  if (!passwordPolicy) return;
97
128
  const { ok, unmet } = checkPasswordPolicy(pw, passwordPolicy);
98
129
  if (!ok) {
99
- throw new SelfstoreError("WEAK_PASSWORD", `Password does not meet the policy: ${unmet.join(", ")}.`);
130
+ throw new SelfstoreError(
131
+ "WEAK_PASSWORD",
132
+ `Password does not meet the policy: ${unmet.join(", ")}.`
133
+ );
100
134
  }
101
135
  }
102
136
  const journalSilent = new Set(opts.journalSilent ?? []);
@@ -446,7 +480,7 @@ function createLocalStore(opts) {
446
480
  }
447
481
  }
448
482
  async function requireReady(where) {
449
- let ok = false;
483
+ let ok;
450
484
  try {
451
485
  ok = await durable.isReady();
452
486
  } catch (e) {
@@ -469,7 +503,10 @@ function createLocalStore(opts) {
469
503
  }
470
504
  function redactForStorage(entries) {
471
505
  return entries.map(
472
- (e) => e.conflicts ? { ...e, conflicts: e.conflicts.map(({ collection, id, kept }) => ({ collection, id, kept })) } : e
506
+ (e) => e.conflicts ? {
507
+ ...e,
508
+ conflicts: e.conflicts.map(({ collection, id, kept }) => ({ collection, id, kept }))
509
+ } : e
473
510
  );
474
511
  }
475
512
  function recordJournal(delta, source, conflicts = []) {
@@ -716,7 +753,12 @@ function createLocalStore(opts) {
716
753
  );
717
754
  continue;
718
755
  }
719
- const { snapshot, sidecar, author, envelope: peerEnv } = await readBoxWithSync(
756
+ const {
757
+ snapshot,
758
+ sidecar,
759
+ author,
760
+ envelope: peerEnv
761
+ } = await readBoxWithSync(
720
762
  await asBytes(remote),
721
763
  p.password ?? pass ?? void 0,
722
764
  peerGroupRead(),
@@ -824,7 +866,9 @@ function createLocalStore(opts) {
824
866
  }
825
867
  await cache.requestPersistent?.();
826
868
  }
827
- async function dropDurable({ keepSession = false } = {}) {
869
+ async function dropDurable({
870
+ keepSession = false
871
+ } = {}) {
828
872
  if (durable && !keepSession) await durable.disconnect();
829
873
  durable = null;
830
874
  label = null;
@@ -947,7 +991,10 @@ function createLocalStore(opts) {
947
991
  let t = null;
948
992
  let restoreThrew = false;
949
993
  try {
950
- t = opts.restoreTarget ? await opts.restoreTarget(savedKind) : null;
994
+ t = opts.restoreTarget ? await bootDeadline(
995
+ Promise.resolve(opts.restoreTarget(savedKind)),
996
+ "destination restore"
997
+ ) : null;
951
998
  } catch (e) {
952
999
  restoreThrew = true;
953
1000
  lastError = transient("Could not restore the destination; will retry.");
@@ -959,14 +1006,19 @@ function createLocalStore(opts) {
959
1006
  label = t.label;
960
1007
  let ok = false;
961
1008
  try {
962
- ok = await t.isReady();
963
- } catch {
964
- raiseGate("AUTH_EXPIRED", "Access to the destination expired or was revoked.");
1009
+ ok = await bootDeadline(t.isReady(), "destination check");
1010
+ } catch (e) {
1011
+ if (e instanceof BootTimeout) {
1012
+ lastError = transient("Destination is slow to answer; will retry.");
1013
+ logger.error("[selfstore] boot destination check stalled", e);
1014
+ } else {
1015
+ raiseGate("AUTH_EXPIRED", "Access to the destination expired or was revoked.");
1016
+ }
965
1017
  }
966
1018
  needsAttention = needsAttention || locked();
967
1019
  if (ok && !locked()) {
968
1020
  try {
969
- await pull("boot");
1021
+ await bootDeadline(pull("boot"), "boot sync");
970
1022
  } catch (e) {
971
1023
  lastError = transient("Boot sync failed; will retry.");
972
1024
  logger.error("[selfstore] boot sync failed", e);
@@ -982,7 +1034,7 @@ function createLocalStore(opts) {
982
1034
  }
983
1035
  if (!durable && peerList.length > 0) {
984
1036
  try {
985
- await pull("boot");
1037
+ await bootDeadline(pull("boot"), "boot sync");
986
1038
  } catch (e) {
987
1039
  lastError = transient("Boot sync failed; will retry.");
988
1040
  logger.error("[selfstore] boot sync failed", e);
@@ -1115,7 +1167,7 @@ function createLocalStore(opts) {
1115
1167
  async inspectTarget(target) {
1116
1168
  const blob = await target.load();
1117
1169
  if (!blob || blob.size === 0) return { hasBackup: false, date: null, encrypted: false };
1118
- let marker = null;
1170
+ let marker;
1119
1171
  try {
1120
1172
  marker = target.stat ? await target.stat() : null;
1121
1173
  } catch {
@@ -1252,7 +1304,13 @@ function createLocalStore(opts) {
1252
1304
  if (replicaList.some((r) => r.id === id)) {
1253
1305
  throw new TypeError(`attachReplica(): a replica with id "${id}" is already attached`);
1254
1306
  }
1255
- replicaList.push({ id, target, label: target.label ?? id, lastPublishAt: null, lastError: null });
1307
+ replicaList.push({
1308
+ id,
1309
+ target,
1310
+ label: target.label ?? id,
1311
+ lastPublishAt: null,
1312
+ lastError: null
1313
+ });
1256
1314
  notify();
1257
1315
  if (ready) {
1258
1316
  void serialize(publishReplicas).catch(
@@ -1359,7 +1417,10 @@ function createLocalStore(opts) {
1359
1417
  }
1360
1418
  assertPasswordPolicy(password);
1361
1419
  if (!durable) {
1362
- throw new SelfstoreError("NOT_CONNECTED", "addEncryptionKey(): no destination is connected.");
1420
+ throw new SelfstoreError(
1421
+ "NOT_CONNECTED",
1422
+ "addEncryptionKey(): no destination is connected."
1423
+ );
1363
1424
  }
1364
1425
  if (locked() || needsAttention) {
1365
1426
  throw new SelfstoreError(
@@ -1389,7 +1450,9 @@ function createLocalStore(opts) {
1389
1450
  envelope = { dataKey, slots: [await mintSlot(pass, dataKey)] };
1390
1451
  }
1391
1452
  if (envelope.slots.length >= MAX_KEY_SLOTS) {
1392
- throw new TypeError(`addEncryptionKey(): this backup already has ${MAX_KEY_SLOTS} key slots.`);
1453
+ throw new TypeError(
1454
+ `addEncryptionKey(): this backup already has ${MAX_KEY_SLOTS} key slots.`
1455
+ );
1393
1456
  }
1394
1457
  if (id && envelope.slots.some((s) => s.id === id)) {
1395
1458
  throw new TypeError(`addEncryptionKey(): a key slot named "${id}" already exists.`);
@@ -1422,7 +1485,10 @@ function createLocalStore(opts) {
1422
1485
  );
1423
1486
  }
1424
1487
  if (!durable) {
1425
- throw new SelfstoreError("NOT_CONNECTED", "removeEncryptionKey(): no destination is connected.");
1488
+ throw new SelfstoreError(
1489
+ "NOT_CONNECTED",
1490
+ "removeEncryptionKey(): no destination is connected."
1491
+ );
1426
1492
  }
1427
1493
  if (locked() || needsAttention) {
1428
1494
  throw new SelfstoreError(
@@ -1499,7 +1565,10 @@ function createLocalStore(opts) {
1499
1565
  );
1500
1566
  }
1501
1567
  if (!durable) {
1502
- throw new SelfstoreError("NOT_CONNECTED", "setExternalEncryption(): no destination is connected.");
1568
+ throw new SelfstoreError(
1569
+ "NOT_CONNECTED",
1570
+ "setExternalEncryption(): no destination is connected."
1571
+ );
1503
1572
  }
1504
1573
  if (locked() || needsAttention) {
1505
1574
  throw new SelfstoreError(
@@ -1532,7 +1601,13 @@ function createLocalStore(opts) {
1532
1601
  await persistLocal();
1533
1602
  pendingEdit = false;
1534
1603
  const blob = await buildBlob();
1535
- await readBoxWithSync(await asBytes(blob), void 0, void 0, void 0, async () => secret);
1604
+ await readBoxWithSync(
1605
+ await asBytes(blob),
1606
+ void 0,
1607
+ void 0,
1608
+ void 0,
1609
+ async () => secret
1610
+ );
1536
1611
  const written = await durable.save(blob);
1537
1612
  await setRemoteVersion(typeof written === "string" ? written : await statRemote());
1538
1613
  lastError = null;
@@ -1592,7 +1667,9 @@ function createLocalStore(opts) {
1592
1667
  throw new TypeError("addExternalKey(): no data key in hand - unlock the backup first.");
1593
1668
  }
1594
1669
  if (envelope.slots.length >= MAX_KEY_SLOTS) {
1595
- throw new TypeError(`addExternalKey(): this backup already has ${MAX_KEY_SLOTS} key slots.`);
1670
+ throw new TypeError(
1671
+ `addExternalKey(): this backup already has ${MAX_KEY_SLOTS} key slots.`
1672
+ );
1596
1673
  }
1597
1674
  if (id && envelope.slots.some((s) => s.id === id)) {
1598
1675
  throw new TypeError(`addExternalKey(): a key slot named "${id}" already exists.`);
@@ -1602,7 +1679,13 @@ function createLocalStore(opts) {
1602
1679
  envelope = { dataKey: envelope.dataKey, slots: [...envelope.slots, slot] };
1603
1680
  try {
1604
1681
  const blob = await buildBlob();
1605
- await readBoxWithSync(await asBytes(blob), void 0, void 0, void 0, async () => secret);
1682
+ await readBoxWithSync(
1683
+ await asBytes(blob),
1684
+ void 0,
1685
+ void 0,
1686
+ void 0,
1687
+ async () => secret
1688
+ );
1606
1689
  const written = await durable.save(blob);
1607
1690
  await setRemoteVersion(typeof written === "string" ? written : await statRemote());
1608
1691
  lastError = null;
@@ -1697,7 +1780,10 @@ function indexedDbCache(name, opts) {
1697
1780
  function key() {
1698
1781
  if (lockMode) {
1699
1782
  return sealKey ? Promise.resolve(sealKey) : Promise.reject(
1700
- new SelfstoreError("PASSWORD_REQUIRED", "The local cache is locked - unlock() it first.")
1783
+ new SelfstoreError(
1784
+ "PASSWORD_REQUIRED",
1785
+ "The local cache is locked - unlock() it first."
1786
+ )
1701
1787
  );
1702
1788
  }
1703
1789
  return _key ??= loadOrCreateKey();
@@ -1737,7 +1823,10 @@ function indexedDbCache(name, opts) {
1737
1823
  const rawCollections = await d.get("kv", COLLECTIONS_KEY);
1738
1824
  if (rawCollections === void 0) return null;
1739
1825
  if (!isEnvelope(rawCollections)) {
1740
- throw new SelfstoreError("BAD_FORMAT", "Unrecognized cache record (not a sealed envelope).");
1826
+ throw new SelfstoreError(
1827
+ "BAD_FORMAT",
1828
+ "Unrecognized cache record (not a sealed envelope)."
1829
+ );
1741
1830
  }
1742
1831
  const collections = JSON.parse(
1743
1832
  new TextDecoder().decode(await unseal(await key(), rawCollections))
@@ -1797,7 +1886,7 @@ function indexedDbCache(name, opts) {
1797
1886
  sealKey = null;
1798
1887
  },
1799
1888
  async unlock(secret) {
1800
- const { keyFromPassword, freshCacheKdf, opens } = await import('./cache-lock-LZILZNLP.js');
1889
+ const { keyFromPassword, freshCacheKdf, opens } = await import('./cache-lock-NXIR6JRB.js');
1801
1890
  const d = await db();
1802
1891
  let candidate;
1803
1892
  if (typeof secret === "string") {
@@ -1904,5 +1993,5 @@ function gisDriveAuth(opts) {
1904
1993
  }
1905
1994
 
1906
1995
  export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache };
1907
- //# sourceMappingURL=chunk-N4RPMX3Y.js.map
1908
- //# sourceMappingURL=chunk-N4RPMX3Y.js.map
1996
+ //# sourceMappingURL=chunk-5AAWNPCP.js.map
1997
+ //# sourceMappingURL=chunk-5AAWNPCP.js.map