@vrtmrz/livesync-commonlib 0.2.2 → 0.2.3

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 +60 -0
  2. package/dist/ContentSplitter/ContentSplitterRabinKarp.js +2 -17
  3. package/dist/ContentSplitter/ContentSplitterV1.js +9 -19
  4. package/dist/ContentSplitter/ContentSplitterV2.js +9 -19
  5. package/dist/common/utils.d.ts +33 -3
  6. package/dist/common/utils.js +12 -44
  7. package/dist/common/utils.object.js +2 -1
  8. package/dist/concurrency/keyedPendingWork.d.ts +13 -0
  9. package/dist/concurrency/keyedPendingWork.js +27 -0
  10. package/dist/conflicts/conflictCheckQueue.d.ts +45 -0
  11. package/dist/conflicts/conflictCheckQueue.js +108 -0
  12. package/dist/conflicts.d.ts +3 -0
  13. package/dist/conflicts.js +4 -0
  14. package/dist/diagnostics/integrity.js +11 -12
  15. package/dist/interfaces/ServiceModule.d.ts +42 -24
  16. package/dist/interfaces/ServiceModule.js +4 -3
  17. package/dist/managers/StorageEventManager.d.ts +3 -3
  18. package/dist/managers/StorageEventManager.js +15 -6
  19. package/dist/pouchdb/LiveSyncLocalDB.js +1 -0
  20. package/dist/pouchdb/chunks.d.ts +16 -0
  21. package/dist/pouchdb/pouchdb-browser.js +2 -84
  22. package/dist/pouchdb/pouchdb-http.js +2 -84
  23. package/dist/pouchdb/purgeMulti.d.ts +4 -0
  24. package/dist/pouchdb/purgeMulti.js +89 -0
  25. package/dist/replication/journal/JournalSyncCore.js +6 -1
  26. package/dist/replication/trystero/TrysteroReplicator.js +3 -4
  27. package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +3 -2
  28. package/dist/replication/trystero/peerPatterns.d.ts +1 -0
  29. package/dist/replication/trystero/peerPatterns.js +6 -0
  30. package/dist/rpc/RpcRoom.d.ts +5 -1
  31. package/dist/rpc/RpcRoom.js +80 -105
  32. package/dist/serviceFeatures/offlineScanner.d.ts +4 -4
  33. package/dist/serviceFeatures/offlineScanner.js +14 -15
  34. package/dist/serviceFeatures/prepareDatabaseForUse.d.ts +2 -2
  35. package/dist/serviceFeatures/remoteConfig.d.ts +10 -5
  36. package/dist/serviceModules/ServiceFileHandlerBase.js +2 -7
  37. package/dist/services/implements/injectable/InjectableConflictService.d.ts +5 -5
  38. package/dist/services/lib/HandlerUtils.d.ts +8 -6
  39. package/dist/services/lib/HandlerUtils.js +14 -0
  40. package/dist/worker/bgWorker.js +1 -1
  41. package/docs/conflict-resolution.md +30 -0
  42. package/docs/development.md +46 -0
  43. package/docs/proven-in-use.md +63 -0
  44. package/docs/releasing.md +2 -0
  45. package/docs/service-feature-composition.md +56 -9
  46. package/package.json +10 -2
@@ -20,29 +20,49 @@ export interface ServiceModules {
20
20
  */
21
21
  rebuilder: Rebuilder;
22
22
  }
23
- export type RequiredServices<T extends keyof ServiceHub> = Pick<ServiceHub, T | "context">;
23
+ /**
24
+ * Selects service dependencies from the host's service type.
25
+ * A context is retained when the selected source exposes one. The default
26
+ * source preserves the existing ServiceHub dependency contract.
27
+ */
28
+ export type RequiredServices<T extends keyof THub, THub = ServiceHub> = Pick<THub, T | Extract<"context", keyof THub>>;
24
29
  export type RequiredServiceModules<T extends keyof ServiceModules> = Pick<ServiceModules, T>;
25
- export type RequiredServicesInterfaces<T extends keyof IServiceHub> = Pick<IServiceHub, T>;
30
+ export type RequiredServicesInterfaces<T extends keyof IServiceHub> = RequiredServices<T, IServiceHub>;
26
31
  export type RequiredServiceModulesInterfaces<T extends keyof ServiceModules> = Pick<ServiceModules, T>;
27
- export type NecessaryServices<T extends keyof ServiceHub, U extends keyof ServiceModules> = {
28
- services: RequiredServices<T>;
32
+ /**
33
+ * Declares the Services and ServiceModules consumed by a feature.
34
+ * Select a more specific source to retain injectable handlers or an extended
35
+ * host context, or use IServiceHub for operation-only interfaces. Additional
36
+ * collaborators, such as a borrowed LogFunction, belong in the feature's own
37
+ * dependency type. This type neither selects runtime values nor owns them.
38
+ */
39
+ export type NecessaryServices<T extends keyof THub, U extends keyof ServiceModules, THub = ServiceHub> = {
40
+ services: RequiredServices<T, THub>;
29
41
  serviceModules: RequiredServiceModules<U>;
30
42
  };
31
- export type NecessaryServicesInterfaces<T extends keyof IServiceHub, U extends keyof ServiceModules> = {
32
- services: RequiredServicesInterfaces<T>;
33
- serviceModules: RequiredServiceModulesInterfaces<U>;
34
- };
35
- export type ServiceFeatureFunction<T extends keyof ServiceHub, U extends keyof ServiceModules, TR> = (host: NecessaryServices<T, U>) => TR;
43
+ export type NecessaryServicesInterfaces<T extends keyof IServiceHub, U extends keyof ServiceModules> = NecessaryServices<T, U, IServiceHub>;
44
+ export type ServiceFeatureFunction<T extends keyof THub, U extends keyof ServiceModules, TR, THub = ServiceHub> = (host: NecessaryServices<T, U, THub>) => TR;
36
45
  type ServiceFeatureContext<T> = T & {
37
46
  _log: LogFunction;
38
47
  };
39
- export type ServiceFeatureFunctionWithContext<T extends keyof ServiceHub, U extends keyof ServiceModules, C, TR> = (host: NecessaryServices<T, U>, context: ServiceFeatureContext<C>) => TR;
48
+ export type ServiceFeatureFunctionWithContext<T extends keyof THub, U extends keyof ServiceModules, C, TR, THub = ServiceHub> = (host: NecessaryServices<T, U, THub>, context: ServiceFeatureContext<C>) => TR;
49
+ type AnyServiceFeature = (...args: never[]) => unknown;
50
+ type ServiceFeatureWithArguments<T extends keyof THub, U extends keyof ServiceModules, THub = ServiceHub> = (host: NecessaryServices<T, U, THub>, ...args: never[]) => unknown;
40
51
  /**
41
- * Helper function to create a service feature with proper typing.
42
- * @param featureFunction The feature function to be wrapped.
43
- * @returns The same feature function with proper typing.
52
+ * Retains a service feature's complete function type and identity.
53
+ * Declare the dependency type on the feature parameter so that required
54
+ * collaborators, concrete service types, and returned views remain explicit.
55
+ * Optional arguments, overloads, generic signatures, and asynchronous results
56
+ * are preserved. Older unannotated callbacks retain their ServiceHub context;
57
+ * their dependency keys cannot be inferred from the callback body.
58
+ *
59
+ * This helper does not invoke the feature, acquire or release its dependencies,
60
+ * or wrap its return value or errors. The host owns composition and disposal.
61
+ * @param featureFunction The feature function to retain.
62
+ * @returns The supplied function without a runtime wrapper.
44
63
  * @example
45
- * const useIndexFeature = createServiceFeature((host: NecessaryServices<"appLifecycle", "storageAccess">) => {
64
+ * type IndexHost = NecessaryServices<"appLifecycle", "storageAccess">;
65
+ * const useIndexFeature = createServiceFeature((host: IndexHost) => {
46
66
  * const { appLifecycle } = host.services;
47
67
  * const { storageAccess } = host.serviceModules;
48
68
  *
@@ -51,18 +71,16 @@ export type ServiceFeatureFunctionWithContext<T extends keyof ServiceHub, U exte
51
71
  * return true;
52
72
  * });
53
73
  * });
54
- *
55
- * useIndexFeature({
56
- * services: { context: serviceHub.context, appLifecycle: serviceHub.appLifecycle },
57
- * serviceModules: { storageAccess },
58
- * });
59
74
  */
75
+ export declare function createServiceFeature<TFeature extends ServiceFeatureWithArguments<keyof ServiceHub, keyof ServiceModules>>(featureFunction: TFeature): TFeature;
76
+ export declare function createServiceFeature<TFeature extends AnyServiceFeature>(featureFunction: TFeature): TFeature;
60
77
  export declare function createServiceFeature<T extends keyof ServiceHub, U extends keyof ServiceModules, TR>(featureFunction: ServiceFeatureFunction<T, U, TR>): ServiceFeatureFunction<T, U, TR>;
61
- type ContextFactory<T extends keyof ServiceHub, U extends keyof ServiceModules, C> = (host: NecessaryServices<T, U>) => ServiceFeatureContext<C>;
62
- export declare function serviceFeature<T extends keyof ServiceHub, U extends keyof ServiceModules>(): {
63
- create<TR>(featureFunction: ServiceFeatureFunction<T, U, TR>): ServiceFeatureFunction<T, U, TR>;
64
- withContext<C extends object = object>(ContextFactory: ContextFactory<T, U, C>): {
65
- create: <TR>(featureFunction: ServiceFeatureFunctionWithContext<T, U, C, TR>) => (host: NecessaryServices<T, U>, context: ServiceFeatureContext<C>) => TR;
78
+ type ContextFactory<T extends keyof THub, U extends keyof ServiceModules, C, THub = ServiceHub> = (host: NecessaryServices<T, U, THub>) => ServiceFeatureContext<C>;
79
+ /** Selects the host type for the existing service-feature builder. */
80
+ export declare function serviceFeature<T extends keyof THub, U extends keyof ServiceModules, THub = ServiceHub>(): {
81
+ create: <TR, TFeature extends (host: NecessaryServices<T, U, THub>, ...args: never[]) => TR = ServiceFeatureFunction<T, U, TR, THub>>(featureFunction: TFeature) => TFeature;
82
+ withContext<C extends object = object>(ContextFactory: ContextFactory<T, U, C, THub>): {
83
+ create: <TR>(featureFunction: ServiceFeatureFunctionWithContext<T, U, C, TR, THub>) => (host: NecessaryServices<T, U, THub>, context: ServiceFeatureContext<C>) => TR;
66
84
  };
67
85
  };
68
86
  export {};
@@ -2,10 +2,11 @@ function createServiceFeature(featureFunction) {
2
2
  return featureFunction;
3
3
  }
4
4
  function serviceFeature() {
5
+ function create(featureFunction) {
6
+ return featureFunction;
7
+ }
5
8
  return {
6
- create(featureFunction) {
7
- return featureFunction;
8
- },
9
+ create,
9
10
  withContext(ContextFactory) {
10
11
  return {
11
12
  create: (featureFunction) => (host, context) => featureFunction(host, ContextFactory(host))
@@ -1,5 +1,6 @@
1
1
  import { type FileEventType, type FilePath, type UXFileInfoStub, type UXFolderInfo, type UXInternalFileInfoStub } from "../common/types.js";
2
2
  import { type FileEventItem } from "../common/types.js";
3
+ import { type DeferredAction } from "octagonal-wheels/concurrency/deferredAction";
3
4
  import type { IStorageAccessManager } from "../interfaces/StorageAccess.js";
4
5
  import { type PromiseWithResolvers } from "octagonal-wheels/promises";
5
6
  import { StorageEventManager, type FileEvent } from "../interfaces/StorageEventManager.js";
@@ -8,12 +9,11 @@ import type { SettingService } from "../services/base/SettingService.js";
8
9
  import type { FileProcessingService } from "../services/base/FileProcessingService.js";
9
10
  import { createInstanceLogFunction } from "../services/lib/logUtils.js";
10
11
  import type { IStorageEventManagerAdapter } from "./adapters/index.js";
11
- import { type CompatTimeoutHandle } from "../common/coreEnvFunctions.js";
12
12
  type WaitInfo = {
13
13
  since: number;
14
14
  type: FileEventType;
15
15
  canProceed: PromiseWithResolvers<boolean>;
16
- timerHandler: CompatTimeoutHandle;
16
+ deadline: DeferredAction;
17
17
  event: FileEventItem;
18
18
  };
19
19
  declare const TYPE_SENTINEL_FLUSH = "SENTINEL_FLUSH";
@@ -88,7 +88,7 @@ export declare abstract class StorageEventManagerBase<TAdapter extends IStorageE
88
88
  enqueue(newItem: FileEventItem): void;
89
89
  private _triggerTakeSnapshot;
90
90
  triggerTakeSnapshot: import("octagonal-wheels/function").ThrottledFunction<() => void>;
91
- protected concurrentProcessing: import("octagonal-wheels/concurrency/semaphore_v2").SemaphoreObject;
91
+ protected concurrentProcessing: import("octagonal-wheels/concurrency/semaphore").SemaphoreObject;
92
92
  protected _waitingMap: Map<string, WaitInfo>;
93
93
  private pendingOperations;
94
94
  private releaseOperations;
@@ -10,10 +10,17 @@ import { delay, fireAndForget, isRemediationModeActive, throttle } from "../comm
10
10
  import {} from "../common/types.js";
11
11
  import { serialized } from "octagonal-wheels/concurrency/lock";
12
12
  import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
13
+ import {
14
+ createDeferredAction
15
+ } from "octagonal-wheels/concurrency/deferredAction";
13
16
  import { promiseWithResolvers } from "octagonal-wheels/promises";
14
17
  import { StorageEventManager } from "../interfaces/StorageEventManager.js";
15
18
  import { createInstanceLogFunction } from "../services/lib/logUtils.js";
16
19
  import { compatGlobal } from "../common/coreEnvFunctions.js";
20
+ const scheduleStorageTimeout = (callback, delayMs) => {
21
+ const handle = compatGlobal.setTimeout(callback, delayMs);
22
+ return () => compatGlobal.clearTimeout(handle);
23
+ };
17
24
  const TYPE_SENTINEL_FLUSH = "SENTINEL_FLUSH";
18
25
  class StorageEventManagerBase extends StorageEventManager {
19
26
  _log;
@@ -311,7 +318,7 @@ class StorageEventManagerBase extends StorageEventManager {
311
318
  const waitInfo = this._waitingMap.get(key);
312
319
  if (waitInfo) {
313
320
  waitInfo.canProceed.resolve(true);
314
- compatGlobal.clearTimeout(waitInfo.timerHandler);
321
+ waitInfo.deadline.dispose();
315
322
  this._waitingMap.delete(key);
316
323
  }
317
324
  this.triggerTakeSnapshot();
@@ -323,7 +330,7 @@ class StorageEventManagerBase extends StorageEventManager {
323
330
  const waitInfo = this._waitingMap.get(key);
324
331
  if (waitInfo) {
325
332
  waitInfo.canProceed.resolve(false);
326
- compatGlobal.clearTimeout(waitInfo.timerHandler);
333
+ waitInfo.deadline.dispose();
327
334
  this._waitingMap.delete(key);
328
335
  }
329
336
  this.triggerTakeSnapshot();
@@ -355,16 +362,18 @@ class StorageEventManagerBase extends StorageEventManager {
355
362
  LOG_LEVEL_DEBUG
356
363
  );
357
364
  }
365
+ const deadline = createDeferredAction(() => {
366
+ this._log(`Processing ${key}: Batch save timeout reached: ${event.type}`, LOG_LEVEL_DEBUG);
367
+ this._proceedWaiting(key);
368
+ }, scheduleStorageTimeout);
358
369
  const waitInfo = {
359
370
  since,
360
371
  type: event.type,
361
372
  event,
362
373
  canProceed: resolver,
363
- timerHandler: compatGlobal.setTimeout(() => {
364
- this._log(`Processing ${key}: Batch save timeout reached: ${event.type}`, LOG_LEVEL_DEBUG);
365
- this._proceedWaiting(key);
366
- }, nextDelay)
374
+ deadline
367
375
  };
376
+ deadline.schedule(nextDelay);
368
377
  this._waitingMap.set(key, waitInfo);
369
378
  this.triggerTakeSnapshot();
370
379
  return waitInfo;
@@ -185,6 +185,7 @@ class LiveSyncLocalDB {
185
185
  this._log("Database initialisation was rejected by a required module.", LOG_LEVEL_NOTICE);
186
186
  return false;
187
187
  }
188
+ manager.changeManager.restartWatch();
188
189
  this._log("Opening Database...");
189
190
  this._log("Database info", LOG_LEVEL_VERBOSE);
190
191
  this._log(JSON.stringify(await openedDatabase.info(), null, 2), LOG_LEVEL_VERBOSE);
@@ -1,10 +1,26 @@
1
1
  import type { CouchDBConnection } from "../common/types.js";
2
2
  export declare function purgeUnreferencedChunks(db: PouchDB.Database, dryRun: boolean, connSetting?: CouchDBConnection, performCompact?: boolean): Promise<number>;
3
+ /**
4
+ * Copy chunks missing from the destination, preserving their source revisions.
5
+ *
6
+ * @deprecated Obsolete purge-based cleanup compatibility helper. Retained for
7
+ * existing package consumers; current Self-hosted LiveSync no longer calls it.
8
+ * Garbage Collection V3 propagates ordinary deletion revisions instead.
9
+ */
3
10
  export declare function transferChunks(key: string, label: string, dbFrom: PouchDB.Database, dbTo: PouchDB.Database, items: {
4
11
  id: string;
5
12
  rev: string;
6
13
  }[]): Promise<boolean>;
14
+ /**
15
+ * Complement chunks between databases which were separately purged.
16
+ * @deprecated Obsolete cleanup compatibility wrapper around transferChunks.
17
+ * Current Self-hosted LiveSync uses its standard locked-remote recovery flow.
18
+ */
7
19
  export declare function balanceChunkPurgedDBs(local: PouchDB.Database, remote: PouchDB.Database): Promise<void>;
20
+ /**
21
+ * @deprecated Obsolete transferChunks compatibility wrapper with no callers in
22
+ * the current Commonlib or Self-hosted LiveSync source trees.
23
+ */
8
24
  export declare function fetchAllUsedChunks(local: PouchDB.Database, remote: PouchDB.Database): Promise<void>;
9
25
  export declare function purgeChunksLocal(db: PouchDB.Database, docs: {
10
26
  id: string;
@@ -6,91 +6,9 @@ import mapreduce from "pouchdb-mapreduce";
6
6
  import replication from "pouchdb-replication";
7
7
  import find from "pouchdb-find";
8
8
  import transform from "transform-pouch";
9
- import { findPathToLeaf } from "pouchdb-merge";
10
- import { adapterFun } from "pouchdb-utils";
11
- import { createError, MISSING_DOC, UNKNOWN_ERROR } from "pouchdb-errors";
12
- import { mapAllTasksWithConcurrencyLimit, unwrapTaskResult } from "octagonal-wheels/concurrency/task";
9
+ import { installPurgeMulti } from "./purgeMulti.js";
13
10
  PouchDB.plugin(IDBPouch).plugin(INDEXEDDBPouch).plugin(HttpPouch).plugin(mapreduce).plugin(replication).plugin(find).plugin(transform);
14
- function appendPurgeSeqs(db, docs) {
15
- return db.get("_local/purges").then(function(doc) {
16
- for (const [docId, rev$$1] of docs) {
17
- const purgeSeq = doc.purgeSeq + 1;
18
- doc.purges.push({
19
- docId,
20
- rev: rev$$1,
21
- purgeSeq
22
- });
23
- if (doc.purges.length > db.purged_infos_limit) {
24
- doc.purges.splice(0, doc.purges.length - db.purged_infos_limit);
25
- }
26
- doc.purgeSeq = purgeSeq;
27
- }
28
- return doc;
29
- }).catch(function(err) {
30
- if (err.status !== 404) {
31
- throw err;
32
- }
33
- return {
34
- _id: "_local/purges",
35
- purges: docs.map(([docId, rev$$1], idx) => ({
36
- docId,
37
- rev: rev$$1,
38
- purgeSeq: idx
39
- })),
40
- purgeSeq: docs.length
41
- };
42
- }).then(function(doc) {
43
- return db.put(doc);
44
- });
45
- }
46
- PouchDB.prototype.purgeMulti = adapterFun(
47
- "_purgeMulti",
48
- function(docs, callback) {
49
- if (typeof this._purge === "undefined") {
50
- return callback(
51
- //@ts-ignore: this ts-ignore might be hiding a `this` bug where we don't have "this" conext.
52
- createError(UNKNOWN_ERROR, "Purge is not implemented in the " + this.adapter + " adapter.")
53
- );
54
- }
55
- const self = this;
56
- const tasks = docs.map(
57
- (param) => () => new Promise((res, rej) => {
58
- const [docId, rev$$1] = param;
59
- self._getRevisionTree(docId, (error, revs) => {
60
- if (error) {
61
- return res([param, error]);
62
- }
63
- if (!revs) {
64
- return res([param, createError(MISSING_DOC)]);
65
- }
66
- let path;
67
- try {
68
- path = findPathToLeaf(revs, rev$$1);
69
- } catch (error2) {
70
- return res([param, error2.message || error2]);
71
- }
72
- self._purge(docId, path, (error2, result) => {
73
- if (error2) {
74
- return res([param, error2]);
75
- } else {
76
- return res([param, result]);
77
- }
78
- });
79
- });
80
- })
81
- );
82
- (async () => {
83
- const ret = await mapAllTasksWithConcurrencyLimit(1, tasks);
84
- const retAll = ret.map((e) => unwrapTaskResult(e));
85
- await appendPurgeSeqs(
86
- self,
87
- retAll.filter((e) => "ok" in e[1]).map((e) => e[0])
88
- );
89
- const result = Object.fromEntries(retAll.map((e) => [e[0][0], e[1]]));
90
- return result;
91
- })().then((result) => callback(void 0, result)).catch((error) => callback(error));
92
- }
93
- );
11
+ installPurgeMulti(PouchDB);
94
12
  export {
95
13
  PouchDB
96
14
  };
@@ -4,91 +4,9 @@ import mapreduce from "pouchdb-mapreduce";
4
4
  import replication from "pouchdb-replication";
5
5
  import find from "pouchdb-find";
6
6
  import transform from "transform-pouch";
7
- import { findPathToLeaf } from "pouchdb-merge";
8
- import { adapterFun } from "pouchdb-utils";
9
- import { createError, MISSING_DOC, UNKNOWN_ERROR } from "pouchdb-errors";
10
- import { mapAllTasksWithConcurrencyLimit, unwrapTaskResult } from "octagonal-wheels/concurrency/task";
7
+ import { installPurgeMulti } from "./purgeMulti.js";
11
8
  PouchDB.plugin(HttpPouch).plugin(mapreduce).plugin(replication).plugin(find).plugin(transform);
12
- function appendPurgeSeqs(db, docs) {
13
- return db.get("_local/purges").then(function(doc) {
14
- for (const [docId, rev$$1] of docs) {
15
- const purgeSeq = doc.purgeSeq + 1;
16
- doc.purges.push({
17
- docId,
18
- rev: rev$$1,
19
- purgeSeq
20
- });
21
- if (doc.purges.length > db.purged_infos_limit) {
22
- doc.purges.splice(0, doc.purges.length - db.purged_infos_limit);
23
- }
24
- doc.purgeSeq = purgeSeq;
25
- }
26
- return doc;
27
- }).catch(function(err) {
28
- if (err.status !== 404) {
29
- throw err;
30
- }
31
- return {
32
- _id: "_local/purges",
33
- purges: docs.map(([docId, rev$$1], idx) => ({
34
- docId,
35
- rev: rev$$1,
36
- purgeSeq: idx
37
- })),
38
- purgeSeq: docs.length
39
- };
40
- }).then(function(doc) {
41
- return db.put(doc);
42
- });
43
- }
44
- PouchDB.prototype.purgeMulti = adapterFun(
45
- "_purgeMulti",
46
- function(docs, callback) {
47
- if (typeof this._purge === "undefined") {
48
- return callback(
49
- //@ts-ignore: this ts-ignore might be hiding a `this` bug where we don't have "this" conext.
50
- createError(UNKNOWN_ERROR, "Purge is not implemented in the " + this.adapter + " adapter.")
51
- );
52
- }
53
- const self = this;
54
- const tasks = docs.map(
55
- (param) => () => new Promise((res, rej) => {
56
- const [docId, rev$$1] = param;
57
- self._getRevisionTree(docId, (error, revs) => {
58
- if (error) {
59
- return res([param, error]);
60
- }
61
- if (!revs) {
62
- return res([param, createError(MISSING_DOC)]);
63
- }
64
- let path;
65
- try {
66
- path = findPathToLeaf(revs, rev$$1);
67
- } catch (error2) {
68
- return res([param, error2.message || error2]);
69
- }
70
- self._purge(docId, path, (error2, result) => {
71
- if (error2) {
72
- return res([param, error2]);
73
- } else {
74
- return res([param, result]);
75
- }
76
- });
77
- });
78
- })
79
- );
80
- (async () => {
81
- const ret = await mapAllTasksWithConcurrencyLimit(1, tasks);
82
- const retAll = ret.map((e) => unwrapTaskResult(e));
83
- await appendPurgeSeqs(
84
- self,
85
- retAll.filter((e) => "ok" in e[1]).map((e) => e[0])
86
- );
87
- const result = Object.fromEntries(retAll.map((e) => [e[0][0], e[1]]));
88
- return result;
89
- })().then((result) => callback(void 0, result)).catch((error) => callback(error));
90
- }
91
- );
9
+ installPurgeMulti(PouchDB);
92
10
  export {
93
11
  PouchDB
94
12
  };
@@ -0,0 +1,4 @@
1
+ import type PouchDB from "pouchdb-core";
2
+ type PouchDBConstructor = typeof PouchDB;
3
+ export declare function installPurgeMulti(PouchDB: PouchDBConstructor): void;
4
+ export {};
@@ -0,0 +1,89 @@
1
+ import { findPathToLeaf } from "pouchdb-merge";
2
+ import { adapterFun } from "pouchdb-utils";
3
+ import { createError, MISSING_DOC, UNKNOWN_ERROR } from "pouchdb-errors";
4
+ import { mapAllTasksWithConcurrencyLimit, unwrapTaskResult } from "octagonal-wheels/concurrency/task";
5
+ function appendPurgeSeqs(db, docs) {
6
+ return db.get("_local/purges").then(function(doc) {
7
+ for (const [docId, rev$$1] of docs) {
8
+ const purgeSeq = doc.purgeSeq + 1;
9
+ doc.purges.push({
10
+ docId,
11
+ rev: rev$$1,
12
+ purgeSeq
13
+ });
14
+ if (doc.purges.length > db.purged_infos_limit) {
15
+ doc.purges.splice(0, doc.purges.length - db.purged_infos_limit);
16
+ }
17
+ doc.purgeSeq = purgeSeq;
18
+ }
19
+ return doc;
20
+ }).catch(function(err) {
21
+ if (err.status !== 404) {
22
+ throw err;
23
+ }
24
+ return {
25
+ _id: "_local/purges",
26
+ purges: docs.map(([docId, rev$$1], idx) => ({
27
+ docId,
28
+ rev: rev$$1,
29
+ purgeSeq: idx
30
+ })),
31
+ purgeSeq: docs.length
32
+ };
33
+ }).then(function(doc) {
34
+ return db.put(doc);
35
+ });
36
+ }
37
+ function installPurgeMulti(PouchDB) {
38
+ PouchDB.prototype.purgeMulti = adapterFun(
39
+ "_purgeMulti",
40
+ function(docs, callback) {
41
+ if (typeof this._purge === "undefined") {
42
+ return callback(
43
+ //@ts-ignore: this ts-ignore might be hiding a `this` bug where we don't have "this" conext.
44
+ createError(UNKNOWN_ERROR, "Purge is not implemented in the " + this.adapter + " adapter.")
45
+ );
46
+ }
47
+ const self = this;
48
+ const tasks = docs.map(
49
+ (param) => () => new Promise((res, rej) => {
50
+ const [docId, rev$$1] = param;
51
+ self._getRevisionTree(docId, (error, revs) => {
52
+ if (error) {
53
+ return res([param, error]);
54
+ }
55
+ if (!revs) {
56
+ return res([param, createError(MISSING_DOC)]);
57
+ }
58
+ let path;
59
+ try {
60
+ path = findPathToLeaf(revs, rev$$1);
61
+ } catch (error2) {
62
+ return res([param, error2.message || error2]);
63
+ }
64
+ self._purge(docId, path, (error2, result) => {
65
+ if (error2) {
66
+ return res([param, error2]);
67
+ } else {
68
+ return res([param, result]);
69
+ }
70
+ });
71
+ });
72
+ })
73
+ );
74
+ (async () => {
75
+ const ret = await mapAllTasksWithConcurrencyLimit(1, tasks);
76
+ const retAll = ret.map((e) => unwrapTaskResult(e));
77
+ await appendPurgeSeqs(
78
+ self,
79
+ retAll.filter((e) => "ok" in e[1]).map((e) => e[0])
80
+ );
81
+ const result = Object.fromEntries(retAll.map((e) => [e[0][0], e[1]]));
82
+ return result;
83
+ })().then((result) => callback(void 0, result)).catch((error) => callback(error));
84
+ }
85
+ );
86
+ }
87
+ export {
88
+ installPurgeMulti
89
+ };
@@ -677,7 +677,12 @@ class JournalSyncCore {
677
677
  const piece = decompressed.slice(idxFrom, idxTo);
678
678
  const strPiece = d.decode(piece);
679
679
  if (strPiece.startsWith("~")) {
680
- const [idPart, dataPart] = strPiece.substring(1).split(UNIT_SPLIT);
680
+ const separatorIndex = strPiece.indexOf(UNIT_SPLIT);
681
+ if (separatorIndex === -1) {
682
+ throw new Error("Invalid journal chunk record: missing separator");
683
+ }
684
+ const idPart = strPiece.substring(1, separatorIndex);
685
+ const dataPart = strPiece.substring(separatorIndex + 1);
681
686
  result.push({
682
687
  _id: idPart,
683
688
  data: unescapeNewLineFromString(dataPart),
@@ -1,3 +1,4 @@
1
+ import { parsePeerPatterns } from "./peerPatterns.js";
1
2
  import {
2
3
  TweakValuesShouldMatchedTemplate,
3
4
  omitP2PRuntimeSettings
@@ -171,12 +172,10 @@ class TrysteroReplicator {
171
172
  }
172
173
  }
173
174
  get autoSyncPeers() {
174
- const peers = this.currentSettings.P2P_AutoSyncPeers.split(",").map((e) => e.trim()).filter((e) => e.length > 0).map((e) => e.startsWith("~") ? new RegExp(e.substring(1), "i") : new RegExp(`^${e}$`, "i"));
175
- return peers;
175
+ return parsePeerPatterns(this.currentSettings.P2P_AutoSyncPeers);
176
176
  }
177
177
  get autoWatchPeers() {
178
- const peers = this.currentSettings.P2P_AutoWatchPeers.split(",").map((e) => e.trim()).filter((e) => e.length > 0).map((e) => e.startsWith("~") ? new RegExp(e.substring(1), "i") : new RegExp(`^${e}$`, "i"));
179
- return peers;
178
+ return parsePeerPatterns(this.currentSettings.P2P_AutoWatchPeers);
180
179
  }
181
180
  async onNewPeer(peer) {
182
181
  const peerName = peer.name;
@@ -1,3 +1,4 @@
1
+ import { parsePeerPatterns } from "./peerPatterns.js";
1
2
  import { selfId, joinRoom } from "@trystero-p2p/nostr";
2
3
  import { AutoAccepting, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE } from "../../common/types.js";
3
4
  import { LOG_LEVEL_VERBOSE, Logger } from "../../common/logger.js";
@@ -281,12 +282,12 @@ You can chose as follows:
281
282
  }
282
283
  _acceptablePeers = new Computed({
283
284
  evaluation: (settings) => {
284
- return `${settings?.P2P_AutoAcceptingPeers ?? ""}`.split(",").map((e) => e.trim()).filter((e) => !!e).map((e) => e.startsWith("~") ? new RegExp(e.substring(1), "i") : new RegExp(`^${e}$`, "i"));
285
+ return parsePeerPatterns(`${settings?.P2P_AutoAcceptingPeers ?? ""}`);
285
286
  }
286
287
  });
287
288
  _shouldDenyPeers = new Computed({
288
289
  evaluation: (settings) => {
289
- return `${settings?.P2P_AutoDenyingPeers ?? ""}`.split(",").map((e) => e.trim()).filter((e) => !!e).map((e) => e.startsWith("~") ? new RegExp(e.substring(1), "i") : new RegExp(`^${e}$`, "i"));
290
+ return parsePeerPatterns(`${settings?.P2P_AutoDenyingPeers ?? ""}`);
290
291
  }
291
292
  });
292
293
  /**
@@ -0,0 +1 @@
1
+ export declare function parsePeerPatterns(value: string): RegExp[];
@@ -0,0 +1,6 @@
1
+ function parsePeerPatterns(value) {
2
+ return value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => entry.startsWith("~") ? new RegExp(entry.substring(1), "i") : new RegExp(`^${entry}$`, "i"));
3
+ }
4
+ export {
5
+ parsePeerPatterns
6
+ };
@@ -8,7 +8,7 @@ export declare class RpcRoom {
8
8
  private sessions;
9
9
  private outgoingChunkMap;
10
10
  private incomingChunkMap;
11
- private incomingChunkTimers;
11
+ private incomingChunkWaits;
12
12
  private peerVersion;
13
13
  private disposers;
14
14
  private closed;
@@ -32,7 +32,11 @@ export declare class RpcRoom {
32
32
  private notifyRemoteCancellation;
33
33
  private abortInboundCall;
34
34
  private sendEnvelope;
35
+ private sendChunk;
36
+ private sendErrorResponse;
35
37
  private scheduleMissingAck;
36
38
  private onWireMessage;
37
39
  private onEnvelopePayload;
40
+ private onRequest;
41
+ private runMethod;
38
42
  }