@gmickel/gno 1.29.4 → 1.29.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/browser-extension/artifacts/{gno-browser-clipper-v1.29.4.zip → gno-browser-clipper-v1.29.6.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.29.6.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/src/ingestion/index.ts +1 -1
- package/src/ingestion/walker.ts +45 -23
- package/src/serve/watch-service.ts +219 -23
- package/browser-extension/artifacts/gno-browser-clipper-v1.29.4.zip.sha256 +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Search finds. GNO proves.**
|
|
4
4
|
|
|
5
|
-
A local knowledge engine for your notes, code, PDFs, and Office docs
|
|
5
|
+
A local knowledge engine for your notes, code, PDFs, and Office docs. Hybrid search, a browsable workspace with graph and editor, CLI, SDK, REST API, and MCP for ten AI clients — plus retrieval that can show its work.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@gmickel/gno)
|
|
8
8
|
[](./LICENSE)
|
|
@@ -18,6 +18,12 @@ gno setup ~/notes --name notes # returns only after retrieval actually works
|
|
|
18
18
|
gno mcp install --target cursor # or claude-code, claude-desktop, zed, ...
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## What you get
|
|
22
|
+
|
|
23
|
+
BM25 and vector retrieval fused and reranked over Markdown, code, PDFs, Office files, and exported mail, calendar and transcripts. A web workspace with cross-collection browse, a knowledge graph, a markdown editor and provenance-carrying capture. A fast CLI, a TypeScript SDK, a REST API, a headless daemon, and one-command install into ten agent clients. Optional hosted publishing when a slice needs a URL.
|
|
24
|
+
|
|
25
|
+
That is the table stakes. Here is the part that is harder to find elsewhere.
|
|
26
|
+
|
|
21
27
|
## Why not just another local RAG tool
|
|
22
28
|
|
|
23
29
|
Most retrieval tools return a ranked list and leave the rest to optimism. Four things here are different, and each one is measurable rather than adjectival:
|
|
@@ -101,7 +107,7 @@ gno daemon --detach # headless indexing + resident MCP gateway
|
|
|
101
107
|
|
|
102
108
|
<!-- public-truth:current-version -->
|
|
103
109
|
|
|
104
|
-
> Current release: **v1.29.
|
|
110
|
+
> Current release: **v1.29.6** — see [CHANGELOG.md](./CHANGELOG.md)
|
|
105
111
|
|
|
106
112
|
<!-- /public-truth -->
|
|
107
113
|
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
c1fc560d5f77022b3123d36c0f488b0e662883b0b67e6e40fe7476674d70e085 gno-browser-clipper-v1.29.6.zip
|
package/package.json
CHANGED
package/src/ingestion/index.ts
CHANGED
package/src/ingestion/walker.ts
CHANGED
|
@@ -174,6 +174,50 @@ function matchesInclude(
|
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
type PathEligibilityConfig = Pick<
|
|
178
|
+
WalkConfig,
|
|
179
|
+
"additionalDefaultExtensions" | "exclude" | "include" | "pattern"
|
|
180
|
+
>;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check collection-relative path eligibility without touching the filesystem.
|
|
184
|
+
* This intentionally remains usable for deleted paths so incremental sync can
|
|
185
|
+
* mark previously indexed documents inactive.
|
|
186
|
+
*/
|
|
187
|
+
export function matchesWalkPath(
|
|
188
|
+
relPath: string,
|
|
189
|
+
config: PathEligibilityConfig
|
|
190
|
+
): boolean {
|
|
191
|
+
const normalizedPath = relPath.replaceAll("\\", "/");
|
|
192
|
+
if (
|
|
193
|
+
isAbsolute(normalizedPath) ||
|
|
194
|
+
DANGEROUS_PATTERN_REGEX.test(normalizedPath) ||
|
|
195
|
+
isRecordVirtualPath(normalizedPath)
|
|
196
|
+
) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let matchesPattern = false;
|
|
201
|
+
try {
|
|
202
|
+
matchesPattern = new Bun.Glob(config.pattern).match(normalizedPath);
|
|
203
|
+
} catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
if (!matchesPattern) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (matchesCollectionExclusion(normalizedPath, config.exclude)) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return matchesInclude(
|
|
215
|
+
normalizedPath,
|
|
216
|
+
config.include,
|
|
217
|
+
config.additionalDefaultExtensions ?? []
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
177
221
|
/**
|
|
178
222
|
* File walker implementation using Bun.Glob.
|
|
179
223
|
*
|
|
@@ -228,29 +272,7 @@ export class FileWalker implements WalkerPort {
|
|
|
228
272
|
}
|
|
229
273
|
const { absPath, relPath } = safePath;
|
|
230
274
|
|
|
231
|
-
if (
|
|
232
|
-
skipped.push({ absPath, relPath, reason: "EXCLUDED" });
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Check exclude patterns
|
|
237
|
-
if (matchesCollectionExclusion(relPath, config.exclude)) {
|
|
238
|
-
skipped.push({
|
|
239
|
-
absPath,
|
|
240
|
-
relPath,
|
|
241
|
-
reason: "EXCLUDED",
|
|
242
|
-
});
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Check include extensions
|
|
247
|
-
if (
|
|
248
|
-
!matchesInclude(
|
|
249
|
-
relPath,
|
|
250
|
-
config.include,
|
|
251
|
-
config.additionalDefaultExtensions ?? []
|
|
252
|
-
)
|
|
253
|
-
) {
|
|
275
|
+
if (!matchesWalkPath(relPath, config)) {
|
|
254
276
|
skipped.push({
|
|
255
277
|
absPath,
|
|
256
278
|
relPath,
|
|
@@ -7,7 +7,11 @@ import type { SqliteAdapter } from "../store/sqlite/adapter";
|
|
|
7
7
|
import type { DocumentEvent, DocumentEventBus } from "./doc-events";
|
|
8
8
|
import type { EmbedScheduler } from "./embed-scheduler";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
collectionToWalkConfig,
|
|
12
|
+
defaultSyncService,
|
|
13
|
+
matchesWalkPath,
|
|
14
|
+
} from "../ingestion";
|
|
11
15
|
|
|
12
16
|
export interface CollectionWatchState {
|
|
13
17
|
expectedCollections: string[];
|
|
@@ -45,6 +49,41 @@ interface CollectionWatchServiceOptions {
|
|
|
45
49
|
watchFactory?: typeof watch;
|
|
46
50
|
}
|
|
47
51
|
|
|
52
|
+
function watcherCollectionFingerprint(
|
|
53
|
+
collection: Collection,
|
|
54
|
+
syncOptions: SyncOptions
|
|
55
|
+
): string {
|
|
56
|
+
return JSON.stringify({
|
|
57
|
+
path: normalize(collection.path),
|
|
58
|
+
pattern: collection.pattern,
|
|
59
|
+
include: collection.include,
|
|
60
|
+
exclude: collection.exclude,
|
|
61
|
+
languageHint: collection.languageHint ?? null,
|
|
62
|
+
recordAdapters: collection.recordAdapters ?? null,
|
|
63
|
+
limits: syncOptions.limits ?? null,
|
|
64
|
+
concurrency: syncOptions.concurrency ?? null,
|
|
65
|
+
contentTypeRules: syncOptions.contentTypeRules ?? null,
|
|
66
|
+
contentTypeRulesFingerprint:
|
|
67
|
+
syncOptions.contentTypeRulesFingerprint ?? null,
|
|
68
|
+
projectTypedEdges: syncOptions.projectTypedEdges ?? null,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function changedPaths(
|
|
73
|
+
result: CollectionSyncResult,
|
|
74
|
+
fallbackPaths: string[] = []
|
|
75
|
+
): string[] {
|
|
76
|
+
if (result.files) {
|
|
77
|
+
return result.files
|
|
78
|
+
.filter((file) => file.status === "added" || file.status === "updated")
|
|
79
|
+
.map((file) => file.relPath);
|
|
80
|
+
}
|
|
81
|
+
return result.filesAdded + result.filesUpdated + result.filesMarkedInactive >
|
|
82
|
+
0
|
|
83
|
+
? fallbackPaths
|
|
84
|
+
: [];
|
|
85
|
+
}
|
|
86
|
+
|
|
48
87
|
export class CollectionWatchService {
|
|
49
88
|
#collections: Collection[];
|
|
50
89
|
readonly #store: SqliteAdapter;
|
|
@@ -53,12 +92,18 @@ export class CollectionWatchService {
|
|
|
53
92
|
readonly #callbacks: CollectionWatchCallbacks | null;
|
|
54
93
|
#syncOptions: SyncOptions;
|
|
55
94
|
readonly #watchers = new Map<string, FSWatcher>();
|
|
95
|
+
readonly #watchRoots = new Map<string, string>();
|
|
96
|
+
readonly #collectionGenerations = new Map<string, number>();
|
|
97
|
+
readonly #collectionFingerprints = new Map<string, string>();
|
|
56
98
|
readonly #pendingByCollection = new Map<string, Set<string>>();
|
|
57
99
|
readonly #timers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
58
100
|
readonly #syncing = new Set<string>();
|
|
101
|
+
readonly #inFlightSyncs = new Set<Promise<void>>();
|
|
59
102
|
readonly #suppressedPaths = new Map<string, number>();
|
|
60
103
|
readonly #watchFactory: typeof watch;
|
|
61
104
|
readonly #failedCollections = new Map<string, string>();
|
|
105
|
+
#nextCollectionGeneration = 0;
|
|
106
|
+
#disposed = false;
|
|
62
107
|
#lastEventAt: string | null = null;
|
|
63
108
|
#lastSyncAt: string | null = null;
|
|
64
109
|
|
|
@@ -73,6 +118,9 @@ export class CollectionWatchService {
|
|
|
73
118
|
}
|
|
74
119
|
|
|
75
120
|
start(): void {
|
|
121
|
+
if (this.#disposed) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
76
124
|
this.updateCollections(this.#collections);
|
|
77
125
|
}
|
|
78
126
|
|
|
@@ -80,16 +128,28 @@ export class CollectionWatchService {
|
|
|
80
128
|
collections: Collection[],
|
|
81
129
|
syncOptions?: SyncOptions
|
|
82
130
|
): void {
|
|
83
|
-
this.#
|
|
131
|
+
if (this.#disposed) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
84
134
|
if (syncOptions) {
|
|
85
135
|
this.#syncOptions = syncOptions;
|
|
86
136
|
}
|
|
87
|
-
const
|
|
137
|
+
const nextByName = new Map(
|
|
138
|
+
collections.map((collection) => [collection.name, collection])
|
|
139
|
+
);
|
|
88
140
|
|
|
89
141
|
for (const [collectionName, watcher] of this.#watchers) {
|
|
90
|
-
|
|
142
|
+
const nextCollection = nextByName.get(collectionName);
|
|
143
|
+
const nextRoot = nextCollection
|
|
144
|
+
? normalize(nextCollection.path)
|
|
145
|
+
: undefined;
|
|
146
|
+
if (
|
|
147
|
+
nextRoot === undefined ||
|
|
148
|
+
nextRoot !== this.#watchRoots.get(collectionName)
|
|
149
|
+
) {
|
|
91
150
|
watcher.close();
|
|
92
151
|
this.#watchers.delete(collectionName);
|
|
152
|
+
this.#watchRoots.delete(collectionName);
|
|
93
153
|
this.#failedCollections.delete(collectionName);
|
|
94
154
|
this.#pendingByCollection.delete(collectionName);
|
|
95
155
|
const timer = this.#timers.get(collectionName);
|
|
@@ -97,7 +157,31 @@ export class CollectionWatchService {
|
|
|
97
157
|
clearTimeout(timer);
|
|
98
158
|
this.#timers.delete(collectionName);
|
|
99
159
|
}
|
|
100
|
-
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const collectionName of this.#collectionFingerprints.keys()) {
|
|
164
|
+
if (!nextByName.has(collectionName)) {
|
|
165
|
+
this.#collectionFingerprints.delete(collectionName);
|
|
166
|
+
this.#collectionGenerations.set(
|
|
167
|
+
collectionName,
|
|
168
|
+
++this.#nextCollectionGeneration
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.#collections = collections;
|
|
174
|
+
for (const collection of collections) {
|
|
175
|
+
const fingerprint = watcherCollectionFingerprint(
|
|
176
|
+
collection,
|
|
177
|
+
this.#syncOptions
|
|
178
|
+
);
|
|
179
|
+
if (this.#collectionFingerprints.get(collection.name) !== fingerprint) {
|
|
180
|
+
this.#collectionFingerprints.set(collection.name, fingerprint);
|
|
181
|
+
this.#collectionGenerations.set(
|
|
182
|
+
collection.name,
|
|
183
|
+
++this.#nextCollectionGeneration
|
|
184
|
+
);
|
|
101
185
|
}
|
|
102
186
|
}
|
|
103
187
|
|
|
@@ -106,13 +190,27 @@ export class CollectionWatchService {
|
|
|
106
190
|
continue;
|
|
107
191
|
}
|
|
108
192
|
try {
|
|
193
|
+
const watchedRoot = normalize(collection.path);
|
|
109
194
|
const watcher = this.#watchFactory(
|
|
110
195
|
collection.path,
|
|
111
196
|
{ recursive: true },
|
|
112
197
|
(_eventType, filename) => {
|
|
113
|
-
if (!filename) return;
|
|
198
|
+
if (this.#disposed || !filename) return;
|
|
114
199
|
const relPath = filename.toString().replaceAll("\\", "/");
|
|
115
|
-
const
|
|
200
|
+
const currentCollection = this.#collections.find(
|
|
201
|
+
(entry) => entry.name === collection.name
|
|
202
|
+
);
|
|
203
|
+
if (
|
|
204
|
+
!currentCollection ||
|
|
205
|
+
normalize(currentCollection.path) !== watchedRoot ||
|
|
206
|
+
!matchesWalkPath(
|
|
207
|
+
relPath,
|
|
208
|
+
collectionToWalkConfig(currentCollection, 0)
|
|
209
|
+
)
|
|
210
|
+
) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const fullPath = normalize(join(watchedRoot, relPath));
|
|
116
214
|
const suppressedUntil = this.#suppressedPaths.get(fullPath);
|
|
117
215
|
if (suppressedUntil && suppressedUntil > Date.now()) {
|
|
118
216
|
return;
|
|
@@ -122,6 +220,7 @@ export class CollectionWatchService {
|
|
|
122
220
|
}
|
|
123
221
|
);
|
|
124
222
|
this.#watchers.set(collection.name, watcher);
|
|
223
|
+
this.#watchRoots.set(collection.name, watchedRoot);
|
|
125
224
|
this.#failedCollections.delete(collection.name);
|
|
126
225
|
} catch (error) {
|
|
127
226
|
this.#failedCollections.set(
|
|
@@ -136,7 +235,11 @@ export class CollectionWatchService {
|
|
|
136
235
|
this.#suppressedPaths.set(normalize(absPath), Date.now() + ms);
|
|
137
236
|
}
|
|
138
237
|
|
|
139
|
-
dispose(): void {
|
|
238
|
+
async dispose(): Promise<void> {
|
|
239
|
+
if (this.#disposed) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
this.#disposed = true;
|
|
140
243
|
for (const timer of this.#timers.values()) {
|
|
141
244
|
clearTimeout(timer);
|
|
142
245
|
}
|
|
@@ -145,7 +248,12 @@ export class CollectionWatchService {
|
|
|
145
248
|
}
|
|
146
249
|
this.#timers.clear();
|
|
147
250
|
this.#watchers.clear();
|
|
251
|
+
this.#watchRoots.clear();
|
|
252
|
+
this.#collectionGenerations.clear();
|
|
253
|
+
this.#collectionFingerprints.clear();
|
|
254
|
+
this.#collections = [];
|
|
148
255
|
this.#pendingByCollection.clear();
|
|
256
|
+
await Promise.allSettled(this.#inFlightSyncs);
|
|
149
257
|
this.#syncing.clear();
|
|
150
258
|
}
|
|
151
259
|
|
|
@@ -168,6 +276,9 @@ export class CollectionWatchService {
|
|
|
168
276
|
}
|
|
169
277
|
|
|
170
278
|
#queueChange(collectionName: string, relPath: string): void {
|
|
279
|
+
if (this.#disposed) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
171
282
|
const pending =
|
|
172
283
|
this.#pendingByCollection.get(collectionName) ?? new Set<string>();
|
|
173
284
|
pending.add(relPath);
|
|
@@ -180,12 +291,28 @@ export class CollectionWatchService {
|
|
|
180
291
|
this.#timers.set(
|
|
181
292
|
collectionName,
|
|
182
293
|
setTimeout(() => {
|
|
183
|
-
|
|
294
|
+
this.#startFlush(collectionName);
|
|
184
295
|
}, 300)
|
|
185
296
|
);
|
|
186
297
|
}
|
|
187
298
|
|
|
299
|
+
#startFlush(collectionName: string): void {
|
|
300
|
+
if (this.#disposed) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const sync = this.#flushCollection(collectionName);
|
|
304
|
+
this.#inFlightSyncs.add(sync);
|
|
305
|
+
void sync
|
|
306
|
+
.finally(() => {
|
|
307
|
+
this.#inFlightSyncs.delete(sync);
|
|
308
|
+
})
|
|
309
|
+
.catch(() => undefined);
|
|
310
|
+
}
|
|
311
|
+
|
|
188
312
|
async #flushCollection(collectionName: string): Promise<void> {
|
|
313
|
+
if (this.#disposed) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
189
316
|
const pending = this.#pendingByCollection.get(collectionName);
|
|
190
317
|
if (!pending || pending.size === 0) {
|
|
191
318
|
return;
|
|
@@ -202,8 +329,15 @@ export class CollectionWatchService {
|
|
|
202
329
|
return;
|
|
203
330
|
}
|
|
204
331
|
|
|
205
|
-
const relPaths = [...pending]
|
|
332
|
+
const relPaths = [...pending].filter((relPath) =>
|
|
333
|
+
matchesWalkPath(relPath, collectionToWalkConfig(collection, 0))
|
|
334
|
+
);
|
|
335
|
+
let syncGeneration = this.#collectionGenerations.get(collectionName) ?? 0;
|
|
206
336
|
this.#pendingByCollection.set(collectionName, new Set<string>());
|
|
337
|
+
if (relPaths.length === 0) {
|
|
338
|
+
this.#notifySettledIfIdle();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
207
341
|
this.#syncing.add(collectionName);
|
|
208
342
|
|
|
209
343
|
try {
|
|
@@ -211,7 +345,7 @@ export class CollectionWatchService {
|
|
|
211
345
|
collection: collection.name,
|
|
212
346
|
relPaths,
|
|
213
347
|
});
|
|
214
|
-
|
|
348
|
+
let result = await defaultSyncService.syncPaths(
|
|
215
349
|
collection,
|
|
216
350
|
this.#store,
|
|
217
351
|
relPaths,
|
|
@@ -220,13 +354,67 @@ export class CollectionWatchService {
|
|
|
220
354
|
runUpdateCmd: false,
|
|
221
355
|
}
|
|
222
356
|
);
|
|
357
|
+
if (this.#disposed) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
223
360
|
this.#callbacks?.onSyncComplete?.({
|
|
224
361
|
collection: collection.name,
|
|
225
362
|
relPaths,
|
|
226
363
|
result,
|
|
227
364
|
});
|
|
228
|
-
|
|
365
|
+
|
|
366
|
+
let completionCollection = collection;
|
|
367
|
+
let completionPaths = changedPaths(result, relPaths);
|
|
368
|
+
while (true) {
|
|
369
|
+
const currentCollection = this.#collections.find(
|
|
370
|
+
(entry) => entry.name === collectionName
|
|
371
|
+
);
|
|
372
|
+
if (!currentCollection) {
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
const currentGeneration =
|
|
376
|
+
this.#collectionGenerations.get(collectionName) ?? 0;
|
|
377
|
+
if (currentGeneration === syncGeneration) {
|
|
378
|
+
const currentRelPaths =
|
|
379
|
+
normalize(currentCollection.path) ===
|
|
380
|
+
normalize(completionCollection.path)
|
|
381
|
+
? completionPaths.filter((relPath) =>
|
|
382
|
+
matchesWalkPath(
|
|
383
|
+
relPath,
|
|
384
|
+
collectionToWalkConfig(currentCollection, 0)
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
: [];
|
|
388
|
+
if (currentRelPaths.length > 0) {
|
|
389
|
+
this.#afterSync(currentCollection, currentRelPaths);
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
result = await defaultSyncService.syncCollection(
|
|
395
|
+
currentCollection,
|
|
396
|
+
this.#store,
|
|
397
|
+
{
|
|
398
|
+
...this.#syncOptions,
|
|
399
|
+
runUpdateCmd: false,
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
if (this.#disposed) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
completionCollection = currentCollection;
|
|
406
|
+
completionPaths = changedPaths(result);
|
|
407
|
+
syncGeneration = currentGeneration;
|
|
408
|
+
this.#callbacks?.onSyncComplete?.({
|
|
409
|
+
collection: currentCollection.name,
|
|
410
|
+
relPaths: completionPaths,
|
|
411
|
+
result,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
229
414
|
} catch (error) {
|
|
415
|
+
if (this.#disposed) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
230
418
|
this.#callbacks?.onSyncError?.({
|
|
231
419
|
collection: collection.name,
|
|
232
420
|
relPaths,
|
|
@@ -235,22 +423,30 @@ export class CollectionWatchService {
|
|
|
235
423
|
throw error;
|
|
236
424
|
} finally {
|
|
237
425
|
this.#syncing.delete(collectionName);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
)
|
|
246
|
-
) {
|
|
247
|
-
this.#callbacks?.onSettled?.();
|
|
426
|
+
if (!this.#disposed) {
|
|
427
|
+
const remaining = this.#pendingByCollection.get(collectionName);
|
|
428
|
+
if (remaining && remaining.size > 0) {
|
|
429
|
+
this.#startFlush(collectionName);
|
|
430
|
+
} else {
|
|
431
|
+
this.#notifySettledIfIdle();
|
|
432
|
+
}
|
|
248
433
|
}
|
|
249
434
|
}
|
|
250
435
|
}
|
|
251
436
|
|
|
437
|
+
#notifySettledIfIdle(): void {
|
|
438
|
+
if (
|
|
439
|
+
this.#syncing.size === 0 &&
|
|
440
|
+
![...this.#pendingByCollection.values()].some(
|
|
441
|
+
(relPaths) => relPaths.size > 0
|
|
442
|
+
)
|
|
443
|
+
) {
|
|
444
|
+
this.#callbacks?.onSettled?.();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
252
448
|
#afterSync(collection: Collection, relPaths: string[]): void {
|
|
253
|
-
if (relPaths.length === 0) {
|
|
449
|
+
if (this.#disposed || relPaths.length === 0) {
|
|
254
450
|
return;
|
|
255
451
|
}
|
|
256
452
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
b639ecf7ff5ebd7aeacf1bad0fe3fe2f471a60c96bdf047d873b6bf932995161 gno-browser-clipper-v1.29.4.zip
|