@vrtmrz/livesync-commonlib 0.1.43 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +57 -6
  2. package/dist/API/DirectFileManipulatorV2.d.ts +2 -0
  3. package/dist/API/DirectFileManipulatorV2.js +3 -0
  4. package/dist/common/configForDoc.d.ts +1 -0
  5. package/dist/common/configForDoc.js +11 -1
  6. package/dist/common/models/db.definition.d.ts +1 -0
  7. package/dist/common/models/setting.const.preferred.js +1 -3
  8. package/dist/diagnostics/couchdb.js +86 -13
  9. package/dist/diagnostics/index.d.ts +2 -2
  10. package/dist/diagnostics/index.js +2 -0
  11. package/dist/diagnostics/integrity.js +385 -158
  12. package/dist/diagnostics/names.d.ts +16 -0
  13. package/dist/diagnostics/names.js +50 -0
  14. package/dist/diagnostics/reports.d.ts +13 -10
  15. package/dist/diagnostics/reports.js +191 -50
  16. package/dist/diagnostics/types.d.ts +64 -4
  17. package/dist/diagnostics/usage.d.ts +4 -0
  18. package/dist/diagnostics/usage.js +158 -54
  19. package/dist/managers/EntryManager/EntryManagerImpls.js +3 -2
  20. package/dist/pouchdb/LiveSyncDBFunctions.js +4 -0
  21. package/dist/pouchdb/StreamingFetch.d.ts +5 -1
  22. package/dist/pouchdb/StreamingFetch.js +529 -65
  23. package/dist/replication/RemoteResource.d.ts +23 -2
  24. package/dist/replication/RemoteResource.js +6 -2
  25. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +8 -0
  26. package/dist/replication/couchdb/LiveSyncReplicator.js +132 -1
  27. package/dist/replication/couchdb/fastForward.d.ts +19 -0
  28. package/dist/replication/couchdb/fastForward.js +119 -0
  29. package/dist/replication/index.d.ts +3 -1
  30. package/dist/replication/joinedDevices.d.ts +25 -0
  31. package/dist/replication/joinedDevices.js +123 -0
  32. package/dist/replication/journal/JournalSyncCore.d.ts +2 -1
  33. package/dist/replication/journal/JournalSyncCore.js +11 -1
  34. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +4 -0
  35. package/dist/replication/journal/LiveSyncJournalReplicator.js +56 -1
  36. package/dist/replication/journal/objectstore/JournalStorageAdapter.d.ts +3 -0
  37. package/dist/replication/journal/objectstore/MinioStorageAdapter.d.ts +2 -1
  38. package/dist/replication/journal/objectstore/MinioStorageAdapter.js +27 -1
  39. package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +1 -1
  40. package/dist/replication/trystero/P2PRoomSessionOwner.js +27 -14
  41. package/dist/serviceModules/Rebuilder.js +41 -15
  42. package/dist/services/BrowserServices.d.ts +4 -2
  43. package/dist/services/BrowserServices.js +2 -5
  44. package/dist/services/HeadlessServices.d.ts +3 -0
  45. package/dist/services/HeadlessServices.js +2 -5
  46. package/dist/services/ServiceHub.d.ts +4 -4
  47. package/dist/services/base/IService.d.ts +10 -18
  48. package/dist/services/base/ReplicatorService.remoteResourceResolver.js +1 -1
  49. package/dist/services/base/SettingService.d.ts +8 -18
  50. package/dist/services/base/SettingService.js +28 -46
  51. package/dist/services/implements/base/SvelteDialog.d.ts +2 -2
  52. package/dist/services/implements/base/UIService.d.ts +2 -0
  53. package/dist/services/implements/base/UIService.js +5 -0
  54. package/dist/services/implements/injectable/InjectableServiceHub.d.ts +4 -4
  55. package/dist/services/implements/injectable/InjectableServices.d.ts +2 -2
  56. package/dist/services/implements/injectable/InjectableSettingService.d.ts +0 -3
  57. package/dist/services/implements/injectable/InjectableSettingService.js +0 -11
  58. package/dist/string_and_binary/chunks.js +7 -5
  59. package/dist/uiCommands.d.ts +26 -0
  60. package/dist/uiCommands.js +57 -0
  61. package/dist/worker/bgWorker.js +1 -1
  62. package/docs/database-diagnostics.md +45 -0
  63. package/docs/development.md +15 -3
  64. package/docs/proven-in-use.md +45 -5
  65. package/docs/releasing.md +20 -13
  66. package/docs/settings-lifecycle.md +37 -5
  67. package/package.json +13 -17
  68. package/dist/services/base/ConfigService.d.ts +0 -7
  69. package/dist/services/base/ConfigService.js +0 -6
  70. package/dist/services/implements/browser/ConfigServiceBrowserCompat.d.ts +0 -16
  71. package/dist/services/implements/browser/ConfigServiceBrowserCompat.js +0 -23
@@ -1,3 +1,4 @@
1
+ import { type ValueStore, type ValueDeleter } from "@vrtmrz/value-store";
1
2
  import { type ConfigPassphraseStore, type ObsidianLiveSyncSettings, type SettingsMigrationState } from "../../common/types.js";
2
3
  import type { IAPIService, ISettingService } from "./IService.js";
3
4
  import { ServiceBase, type ServiceContext } from "./ServiceBase.js";
@@ -5,6 +6,8 @@ import { createInstanceLogFunction } from "../lib/logUtils.js";
5
6
  import { type PersistedSettings } from "../../common/models/setting.policy.js";
6
7
  export interface SettingServiceDependencies {
7
8
  APIService: IAPIService;
9
+ /** Host-owned exact-key storage, created once for this application instance. */
10
+ deviceValues: ValueStore & ValueDeleter;
8
11
  /** Optional host hook for applying the loaded display language to its catalogue. */
9
12
  onDisplayLanguageChanged?: (language: ObsidianLiveSyncSettings["displayLanguage"]) => void;
10
13
  }
@@ -12,9 +15,10 @@ export declare abstract class SettingService<T extends ServiceContext = ServiceC
12
15
  deviceAndVaultName: string;
13
16
  protected APIService: IAPIService;
14
17
  private readonly onDisplayLanguageChanged?;
15
- protected abstract setItem(key: string, value: string): void;
16
- protected abstract getItem(key: string): string;
17
- protected abstract deleteItem(key: string): void;
18
+ /** Exact-key device values, including legacy keys outside the Vault namespace. */
19
+ readonly deviceValues: ValueStore & ValueDeleter;
20
+ /** Device values scoped by the current Vault name and database suffix. */
21
+ readonly config: ValueStore & ValueDeleter;
18
22
  _settings: ObsidianLiveSyncSettings;
19
23
  get settings(): ObsidianLiveSyncSettings;
20
24
  set settings(value: ObsidianLiveSyncSettings);
@@ -43,23 +47,9 @@ export declare abstract class SettingService<T extends ServiceContext = ServiceC
43
47
  /**
44
48
  * Save the current device and vault name to settings, aside from the main settings.
45
49
  */
46
- saveDeviceAndVaultName(): void;
47
- /**
48
- * Read an exact key from the host's device-local configuration store.
49
- *
50
- * Unlike the main settings document, this state is not synchronised. Callers
51
- * which need Vault namespacing should normally use {@link getSmallConfig}.
52
- */
53
- getDeviceLocalConfig(key: string): string | null;
54
- /** Store an exact key in the host's non-synchronised configuration store. */
55
- setDeviceLocalConfig(key: string, value: string): void;
56
- /** Delete an exact key from the host's non-synchronised configuration store. */
57
- deleteDeviceLocalConfig(key: string): void;
50
+ saveDeviceAndVaultName(): Promise<void>;
58
51
  private additionalSuffixOfDatabaseName;
59
52
  private getKey;
60
- setSmallConfig(key: string, value: string): void;
61
- getSmallConfig(key: string): string;
62
- deleteSmallConfig(key: string): void;
63
53
  /**
64
54
  * Save the current settings to storage.
65
55
  */
@@ -1,3 +1,4 @@
1
+ import { createDeletableValueStore } from "@vrtmrz/value-store";
1
2
  import {
2
3
  ChunkAlgorithmNames,
3
4
  LOG_LEVEL_NOTICE,
@@ -42,6 +43,10 @@ class SettingService extends ServiceBase {
42
43
  deviceAndVaultName = "";
43
44
  APIService;
44
45
  onDisplayLanguageChanged;
46
+ /** Exact-key device values, including legacy keys outside the Vault namespace. */
47
+ deviceValues;
48
+ /** Device values scoped by the current Vault name and database suffix. */
49
+ config;
45
50
  _settings;
46
51
  get settings() {
47
52
  return this._settings;
@@ -59,6 +64,12 @@ class SettingService extends ServiceBase {
59
64
  constructor(context, dependencies) {
60
65
  super(context);
61
66
  this.APIService = dependencies.APIService;
67
+ this.deviceValues = dependencies.deviceValues;
68
+ this.config = createDeletableValueStore({
69
+ get: (key) => this.deviceValues.getValue(this.getKey(key)),
70
+ set: (key, value) => this.deviceValues.setValue(this.getKey(key), value),
71
+ delete: (key) => this.deviceValues.delete(this.getKey(key))
72
+ });
62
73
  this.onDisplayLanguageChanged = dependencies.onDisplayLanguageChanged;
63
74
  this._log = createInstanceLogFunction("SettingService", this.APIService);
64
75
  }
@@ -111,26 +122,9 @@ class SettingService extends ServiceBase {
111
122
  /**
112
123
  * Save the current device and vault name to settings, aside from the main settings.
113
124
  */
114
- saveDeviceAndVaultName() {
125
+ async saveDeviceAndVaultName() {
115
126
  const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.APIService.getSystemVaultName() + this.additionalSuffixOfDatabaseName();
116
- this.setDeviceLocalConfig(lsKey, this.deviceAndVaultName);
117
- }
118
- /**
119
- * Read an exact key from the host's device-local configuration store.
120
- *
121
- * Unlike the main settings document, this state is not synchronised. Callers
122
- * which need Vault namespacing should normally use {@link getSmallConfig}.
123
- */
124
- getDeviceLocalConfig(key) {
125
- return this.getItem(key);
126
- }
127
- /** Store an exact key in the host's non-synchronised configuration store. */
128
- setDeviceLocalConfig(key, value) {
129
- this.setItem(key, value);
130
- }
131
- /** Delete an exact key from the host's non-synchronised configuration store. */
132
- deleteDeviceLocalConfig(key) {
133
- this.deleteItem(key);
127
+ await this.deviceValues.setValue(lsKey, this.deviceAndVaultName);
134
128
  }
135
129
  additionalSuffixOfDatabaseName() {
136
130
  const suffix = this.settings?.additionalSuffixOfDatabaseName;
@@ -145,18 +139,6 @@ class SettingService extends ServiceBase {
145
139
  const addSuffix = this.additionalSuffixOfDatabaseName();
146
140
  return `${keyMain}${addSuffix}-${key}`;
147
141
  }
148
- setSmallConfig(key, value) {
149
- const dbKey = this.getKey(key);
150
- this.setDeviceLocalConfig(dbKey, value);
151
- }
152
- getSmallConfig(key) {
153
- const dbKey = this.getKey(key);
154
- return this.getDeviceLocalConfig(dbKey) ?? "";
155
- }
156
- deleteSmallConfig(key) {
157
- const dbKey = this.getKey(key);
158
- this.deleteDeviceLocalConfig(dbKey);
159
- }
160
142
  /**
161
143
  * Save the current settings to storage.
162
144
  */
@@ -178,7 +160,7 @@ class SettingService extends ServiceBase {
178
160
  if (store !== "" && store !== "LOCALSTORAGE" && store !== "ASK_AT_LAUNCH") {
179
161
  throw new Error("The configuration passphrase storage mode is unsupported.");
180
162
  }
181
- const oldLocalKey = this.getDeviceLocalConfig("ls-setting-passphrase");
163
+ const oldLocalKey = await this.deviceValues.getValue("ls-setting-passphrase");
182
164
  const settings = this.cloneSettings(this.settings);
183
165
  let targetKey;
184
166
  if (store === "LOCALSTORAGE") {
@@ -196,10 +178,10 @@ class SettingService extends ServiceBase {
196
178
  const prepared = await this.prepareSettingSave(settings, targetKey);
197
179
  const changesLocalKey = store === "LOCALSTORAGE" && targetKey !== oldLocalKey;
198
180
  try {
199
- if (changesLocalKey) this.setDeviceLocalConfig("ls-setting-passphrase", targetKey);
181
+ if (changesLocalKey) await this.deviceValues.setValue("ls-setting-passphrase", targetKey);
200
182
  await this.writePreparedSettings(prepared);
201
183
  } catch (error) {
202
- if (changesLocalKey) this.restoreConfigurationPassphrase(oldLocalKey, error);
184
+ if (changesLocalKey) await this.restoreConfigurationPassphrase(oldLocalKey, error);
203
185
  throw error;
204
186
  }
205
187
  this.settings.configPassphraseStore = store;
@@ -210,10 +192,10 @@ class SettingService extends ServiceBase {
210
192
  throw error;
211
193
  });
212
194
  }
213
- restoreConfigurationPassphrase(previous, cause) {
195
+ async restoreConfigurationPassphrase(previous, cause) {
214
196
  try {
215
- if (previous) this.setDeviceLocalConfig("ls-setting-passphrase", previous);
216
- else this.deleteDeviceLocalConfig("ls-setting-passphrase");
197
+ if (previous) await this.deviceValues.setValue("ls-setting-passphrase", previous);
198
+ else await this.deviceValues.delete("ls-setting-passphrase");
217
199
  } catch {
218
200
  const message = this.context.translate("SettingService.ConfigurationPassphraseRollbackFailed");
219
201
  this._log(message, LOG_LEVEL_URGENT);
@@ -344,9 +326,9 @@ class SettingService extends ServiceBase {
344
326
  return { persisted, runtime, patch: savedPatch, passphrase: configurationPassphrase };
345
327
  }
346
328
  async writePreparedSettings(prepared) {
347
- this.saveDeviceAndVaultName();
329
+ await this.saveDeviceAndVaultName();
348
330
  if (prepared.runtime.P2P_DevicePeerName?.trim()) {
349
- this.setSmallConfig(SETTING_KEY_P2P_DEVICE_NAME, prepared.runtime.P2P_DevicePeerName.trim());
331
+ await this.config.setValue(SETTING_KEY_P2P_DEVICE_NAME, prepared.runtime.P2P_DevicePeerName.trim());
350
332
  }
351
333
  await this.saveData(prepared.persisted);
352
334
  }
@@ -528,7 +510,7 @@ class SettingService extends ServiceBase {
528
510
  getPassphrase(settings) {
529
511
  const methods = {
530
512
  "": () => Promise.resolve("*"),
531
- LOCALSTORAGE: () => Promise.resolve(this.getDeviceLocalConfig("ls-setting-passphrase") ?? false),
513
+ LOCALSTORAGE: () => this.deviceValues.getValue("ls-setting-passphrase", ""),
532
514
  ASK_AT_LAUNCH: () => this.APIService.confirm.askString(
533
515
  this.context.translate("SettingService.ConfigurationPassphraseTitle"),
534
516
  "passphrase",
@@ -700,7 +682,7 @@ class SettingService extends ServiceBase {
700
682
  } else {
701
683
  configuredIdKey(settings);
702
684
  }
703
- const previous = this.getDeviceLocalConfig("ls-setting-passphrase");
685
+ const previous = await this.deviceValues.getValue("ls-setting-passphrase");
704
686
  let candidate = previous || false;
705
687
  let retry = false;
706
688
  while (true) {
@@ -710,9 +692,9 @@ class SettingService extends ServiceBase {
710
692
  if (result.verified) {
711
693
  if (candidate !== previous) {
712
694
  try {
713
- this.setDeviceLocalConfig("ls-setting-passphrase", candidate);
695
+ await this.deviceValues.setValue("ls-setting-passphrase", candidate);
714
696
  } catch (error) {
715
- this.restoreConfigurationPassphrase(previous, error);
697
+ await this.restoreConfigurationPassphrase(previous, error);
716
698
  throw error;
717
699
  }
718
700
  }
@@ -788,9 +770,9 @@ class SettingService extends ServiceBase {
788
770
  }
789
771
  const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.APIService.getSystemVaultName() + this.additionalSuffixOfDatabaseName();
790
772
  if (this.settings.deviceAndVaultName != "") {
791
- if (!this.getDeviceLocalConfig(lsKey)) {
773
+ if (!await this.deviceValues.getValue(lsKey)) {
792
774
  this.setDeviceAndVaultName(this.settings.deviceAndVaultName);
793
- this.saveDeviceAndVaultName();
775
+ await this.saveDeviceAndVaultName();
794
776
  this.settings.deviceAndVaultName = "";
795
777
  }
796
778
  }
@@ -801,7 +783,7 @@ class SettingService extends ServiceBase {
801
783
  );
802
784
  this.settings.customChunkSize = 0;
803
785
  }
804
- this.setDeviceAndVaultName(this.getDeviceLocalConfig(lsKey) || "");
786
+ this.setDeviceAndVaultName(await this.deviceValues.getValue(lsKey) || "");
805
787
  if (this.getDeviceAndVaultName() == "") {
806
788
  if (this.settings.usePluginSync) {
807
789
  this._log("Device name missing. Disabling plug-in sync.", LOG_LEVEL_NOTICE);
@@ -1,4 +1,4 @@
1
- import type { ConfigService } from "../../base/ConfigService.js";
1
+ import type { IConfigService } from "../../base/IService.js";
2
2
  import type { AppLifecycleService } from "../../base/AppLifecycleService.js";
3
3
  import type { ReplicatorService } from "../../base/ReplicatorService.js";
4
4
  import type { Confirm } from "../../../interfaces/Confirm.js";
@@ -9,7 +9,7 @@ import type { Constructor } from "../../../common/utils.type.js";
9
9
  export type SvelteDialogManagerDependencies<T extends ServiceContext = ServiceContext> = {
10
10
  appLifecycle: AppLifecycleService<T>;
11
11
  replicator: ReplicatorService<T>;
12
- config: ConfigService<T>;
12
+ config: IConfigService;
13
13
  confirm: Confirm;
14
14
  control: IControlService;
15
15
  };
@@ -1,4 +1,5 @@
1
1
  import type { Confirm } from "../../../interfaces/Confirm.js";
2
+ import { UICommandRegistry } from "../../../uiCommands.js";
2
3
  import type { ComponentHasResult, SvelteDialogManager } from "./SvelteDialog.js";
3
4
  import type { IAPIService, IUIService } from "../../base/IService.js";
4
5
  import { ServiceBase, type ServiceContext } from "../../base/ServiceBase.js";
@@ -12,6 +13,7 @@ type DialogParams = {
12
13
  dataToCopy: string;
13
14
  };
14
15
  export declare abstract class UIService<T extends ServiceContext = ServiceContext> extends ServiceBase<T> implements IUIService {
16
+ readonly commands: UICommandRegistry;
15
17
  private _dialogManager;
16
18
  protected _APIService: IAPIService;
17
19
  abstract get dialogToCopy(): ComponentHasResult<DialogResult, DialogParams>;
@@ -1,9 +1,14 @@
1
+ import { EVENT_PLUGIN_UNLOADED } from "../../../events/coreEvents.js";
2
+ import { UICommandRegistry } from "../../../uiCommands.js";
1
3
  import { ServiceBase } from "../../base/ServiceBase.js";
2
4
  class UIService extends ServiceBase {
5
+ commands;
3
6
  _dialogManager;
4
7
  _APIService;
5
8
  constructor(context, dependents) {
6
9
  super(context);
10
+ this.commands = new UICommandRegistry();
11
+ this.context.events.once(EVENT_PLUGIN_UNLOADED, () => this.commands.dispose());
7
12
  this._dialogManager = dependents.dialogManager;
8
13
  this._APIService = dependents.APIService;
9
14
  }
@@ -1,4 +1,4 @@
1
- import type { ConfigService } from "../../base/ConfigService.js";
1
+ import type { IConfigService } from "../../base/IService.js";
2
2
  import { ControlService } from "../../base/ControlService.js";
3
3
  import type { KeyValueDBService } from "../../base/KeyValueDBService.js";
4
4
  import { PathService } from "../../base/PathService.js";
@@ -35,7 +35,7 @@ export declare class InjectableServiceHub<T extends ServiceContext = ServiceCont
35
35
  protected readonly _vault: InjectableVaultService<T>;
36
36
  protected readonly _test: InjectableTestService<T>;
37
37
  protected readonly _ui: UIService<T>;
38
- protected readonly _config: ConfigService<T>;
38
+ protected readonly _config: IConfigService;
39
39
  protected readonly _keyValueDB: KeyValueDBService<T>;
40
40
  protected readonly _control: ControlService<T>;
41
41
  get API(): InjectableAPIService<T>;
@@ -55,14 +55,14 @@ export declare class InjectableServiceHub<T extends ServiceContext = ServiceCont
55
55
  get control(): ControlService<T>;
56
56
  get keyValueDB(): KeyValueDBService<T>;
57
57
  get UI(): UIService<T>;
58
- get config(): ConfigService<T>;
58
+ get config(): IConfigService;
59
59
  constructor(context: T, services: InjectableServiceInstances<T> & {
60
60
  setting: SettingService<T>;
61
61
  appLifecycle: AppLifecycleServiceBase<T>;
62
62
  path: PathService<T>;
63
63
  API: InjectableAPIService<T>;
64
64
  ui: UIService<T>;
65
- config: ConfigService<T>;
65
+ config: IConfigService;
66
66
  database: DatabaseService<T>;
67
67
  remote: InjectableRemoteService<T>;
68
68
  vault: InjectableVaultService<T>;
@@ -1,6 +1,6 @@
1
+ import type { IConfigService } from "../../base/IService.js";
1
2
  import { type ServiceInstances } from "../../ServiceHub.js";
2
3
  import type { UIService } from "../base/UIService.js";
3
- import type { ConfigService } from "../../base/ConfigService.js";
4
4
  import type { ServiceContext } from "../../base/ServiceBase.js";
5
5
  import type { InjectableAPIService } from "./InjectableAPIService.js";
6
6
  import type { InjectableDatabaseEventService } from "./InjectableDatabaseEventService.js";
@@ -32,5 +32,5 @@ export type InjectableServiceInstances<T extends ServiceContext> = ServiceInstan
32
32
  vault?: InjectableVaultService<T>;
33
33
  test?: InjectableTestService<T>;
34
34
  ui?: UIService<T>;
35
- config?: ConfigService<T>;
35
+ config?: IConfigService;
36
36
  };
@@ -4,9 +4,6 @@ import type { ObsidianLiveSyncSettings } from "../../../common/types.js";
4
4
  import type { PersistedSettings } from "../../../common/models/setting.policy.js";
5
5
  export declare class InjectableSettingService<T extends ServiceContext> extends SettingService<T> {
6
6
  constructor(context: T, dependencies: SettingServiceDependencies);
7
- protected setItem(key: string, value: string): void;
8
- protected getItem(key: string): string;
9
- protected deleteItem(key: string): void;
10
7
  saveData: import("../../lib/HandlerUtils.js").HandlerFunction<(data: PersistedSettings) => Promise<void>, unknown>;
11
8
  loadData: import("../../lib/HandlerUtils.js").HandlerFunction<() => Promise<ObsidianLiveSyncSettings | undefined>, unknown>;
12
9
  }
@@ -2,7 +2,6 @@ import { EVENT_SETTING_SAVED } from "../../../events/coreEvents.js";
2
2
  import { SettingService } from "../../base/SettingService.js";
3
3
  import { EVENT_REQUEST_RELOAD_SETTING_TAB } from "../../../events/coreEvents.js";
4
4
  import { handlers } from "../../lib/HandlerUtils.js";
5
- import { compatGlobal } from "../../../common/coreEnvFunctions.js";
6
5
  class InjectableSettingService extends SettingService {
7
6
  constructor(context, dependencies) {
8
7
  super(context, dependencies);
@@ -15,16 +14,6 @@ class InjectableSettingService extends SettingService {
15
14
  return Promise.resolve(true);
16
15
  });
17
16
  }
18
- setItem(key, value) {
19
- return compatGlobal.localStorage.setItem(key, value);
20
- }
21
- getItem(key) {
22
- return compatGlobal.localStorage.getItem(key) ?? "";
23
- }
24
- deleteItem(key) {
25
- compatGlobal.localStorage.removeItem(key);
26
- }
27
- // override currentSettings = handlers<SettingService<T>>().binder("currentSettings");
28
17
  saveData = handlers().binder("saveData");
29
18
  loadData = handlers().binder("loadData");
30
19
  }
@@ -404,14 +404,16 @@ async function splitPiecesRabinKarp(dataSrc, absoluteMaxPieceSize, doPlainSplit,
404
404
  }
405
405
  }
406
406
  const chunkUnitBinary = 256 * 1024;
407
- const fixedAvgChunkSize = plainSplit ? chunkUnitPlain * 4 : chunkUnitBinary * 4;
408
407
  const fixedMaxChunkSize = plainSplit ? chunkUnitPlain * 16 : chunkUnitBinary * 16;
409
- const fixedMinChunkSize = plainSplit ? chunkUnitPlain * 2 : chunkUnitBinary;
410
408
  const rkAbsoluteMaxPieceSizeFloor = 30 * 1024;
411
409
  const effectiveAbsoluteMaxPieceSize = Math.max(absoluteMaxPieceSize, rkAbsoluteMaxPieceSizeFloor);
412
410
  const maxChunkSize = Math.min(fixedMaxChunkSize, effectiveAbsoluteMaxPieceSize);
413
- const minChunkSize = Math.min(Math.max(fixedMinChunkSize, minimumChunkSize), maxChunkSize);
414
- const avgChunkSize = Math.min(Math.max(fixedAvgChunkSize, minChunkSize), maxChunkSize);
411
+ const proportionalGap = Math.max(1, Math.floor(maxChunkSize / 4));
412
+ const minChunkSize = Math.min(
413
+ Math.max(plainSplit ? chunkUnitPlain * 2 : proportionalGap, minimumChunkSize),
414
+ maxChunkSize
415
+ );
416
+ const avgChunkSize = plainSplit ? Math.min(Math.max(chunkUnitPlain * 4, minChunkSize), maxChunkSize) : proportionalGap;
415
417
  const windowSize = 48;
416
418
  const hashModulus = avgChunkSize;
417
419
  const boundaryPattern = 1;
@@ -429,7 +431,7 @@ async function splitPiecesRabinKarp(dataSrc, absoluteMaxPieceSize, doPlainSplit,
429
431
  return async function* piecesBlob() {
430
432
  while (pos < length) {
431
433
  const byte = buffer[pos];
432
- if (pos >= start + windowSize) {
434
+ if (pos >= windowSize) {
433
435
  const oldByte = buffer[pos - windowSize];
434
436
  const oldByteTerm = Math.imul(oldByte, P_pow_w);
435
437
  hash = hash - oldByteTerm | 0;
@@ -0,0 +1,26 @@
1
+ declare const commandTypes: unique symbol;
2
+ /** An immutable declaration which associates a stable name with request and result types. */
3
+ export interface UICommand<Request, Result> {
4
+ readonly id: string;
5
+ readonly [commandTypes]?: {
6
+ readonly request: (value: Request) => Request;
7
+ readonly result: (value: Result) => Result;
8
+ };
9
+ }
10
+ /** Declare a command without registering it in any global state. */
11
+ export declare function declareUICommand<Request, Result>(id: string): UICommand<Request, Result>;
12
+ /** Instance-owned command handlers for one host or service composition. */
13
+ export declare class UICommandRegistry {
14
+ private readonly definitions;
15
+ private readonly handlers;
16
+ private disposed;
17
+ register<Request, Result>(command: UICommand<Request, Result>, handler: (request: NoInfer<Request>) => NoInfer<Result> | Promise<NoInfer<Result>>): () => void;
18
+ invoke<Request, Result>(command: UICommand<Request, Result>, input: NoInfer<Request>): Promise<{
19
+ status: "handled";
20
+ value: Result;
21
+ } | {
22
+ status: "unavailable";
23
+ }>;
24
+ dispose(): void;
25
+ }
26
+ export {};
@@ -0,0 +1,57 @@
1
+ function declareUICommand(id) {
2
+ return Object.freeze({ id });
3
+ }
4
+ class UICommandRegistry {
5
+ definitions = /* @__PURE__ */ new Map();
6
+ handlers = /* @__PURE__ */ new Map();
7
+ disposed = false;
8
+ register(command, handler) {
9
+ if (this.disposed) {
10
+ throw new Error("Cannot register a UI command on a disposed registry");
11
+ }
12
+ const knownDefinition = this.definitions.get(command.id);
13
+ if (knownDefinition !== void 0 && knownDefinition !== command) {
14
+ throw new Error(`A conflicting command definition is already known for '${command.id}'`);
15
+ }
16
+ if (this.handlers.has(command.id)) {
17
+ throw new Error(`A handler is already registered for '${command.id}'`);
18
+ }
19
+ this.definitions.set(command.id, command);
20
+ const entry = {
21
+ command,
22
+ handler: (input) => handler(input)
23
+ };
24
+ this.handlers.set(command.id, entry);
25
+ let active = true;
26
+ return () => {
27
+ if (!active) return;
28
+ active = false;
29
+ if (this.handlers.get(command.id) === entry) {
30
+ this.handlers.delete(command.id);
31
+ }
32
+ };
33
+ }
34
+ async invoke(command, input) {
35
+ if (this.disposed) {
36
+ return { status: "unavailable" };
37
+ }
38
+ const entry = this.handlers.get(command.id);
39
+ if (entry === void 0 || entry.command !== command) {
40
+ return { status: "unavailable" };
41
+ }
42
+ return {
43
+ status: "handled",
44
+ value: await entry.handler(input)
45
+ };
46
+ }
47
+ dispose() {
48
+ if (this.disposed) return;
49
+ this.disposed = true;
50
+ this.handlers.clear();
51
+ this.definitions.clear();
52
+ }
53
+ }
54
+ export {
55
+ UICommandRegistry,
56
+ declareUICommand
57
+ };