@nxgt/mongo-meilisearch 0.3.2 → 0.4.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.
package/README.md CHANGED
@@ -161,15 +161,16 @@ await running.close(); // flushes, then stops; or `await using running = …`
161
161
  `start` takes a **lease** on the sync's name before anything else, the first
162
162
  reindex included, and a running sync renews it every third of `leaseMs`
163
163
  (30 s). A second process that starts the same name is refused with
164
- `RUNNING`, naming who holds it and until when. Run it as a standby that
165
- tries again — on `RUNNING`, and on `LEASE_LOST`, which a `start()` whose
166
- first reindex lost the name to another process rejects with:
164
+ `RUNNING`, naming who holds it and until when — in the message, and as the
165
+ error's `holder` and `expiresAt`. Run it as a standby that waits for the
166
+ holder and tries again — on `RUNNING`, and on `LEASE_LOST`, which a `start()`
167
+ whose first reindex lost the name to another process rejects with:
167
168
 
168
169
  ```ts
169
170
  import { type RunningSearchSync, SearchSyncError } from '@nxgt/mongo-meilisearch';
170
171
 
171
172
  // The name is held elsewhere, or was taken over while this start reindexed.
172
- const heldElsewhere = (error: unknown) =>
173
+ const heldElsewhere = (error: unknown): error is SearchSyncError =>
173
174
  error instanceof SearchSyncError &&
174
175
  (error.code === 'RUNNING' || error.code === 'LEASE_LOST');
175
176
 
@@ -179,12 +180,22 @@ async function follow(): Promise<RunningSearchSync> {
179
180
  return await articleSearch.start();
180
181
  } catch (error) {
181
182
  if (!heldElsewhere(error)) throw error;
182
- await new Promise((resolve) => setTimeout(resolve, 10_000));
183
+ // Until the holder's lease lapses when it is known, 10 s when not;
184
+ // the margin covers a host clock ahead of MongoDB's.
185
+ const until = error.expiresAt?.getTime() ?? Date.now() + 10_000;
186
+ const wait = Math.max(until - Date.now(), 0) + 250;
187
+ await new Promise((resolve) => setTimeout(resolve, wait));
183
188
  }
184
189
  }
185
190
  }
186
191
  ```
187
192
 
193
+ A holder that is alive renews its lease every third of `leaseMs`, so the next
194
+ `start()` is refused again, with a later `expiresAt`: the loop waits about one
195
+ lease per try. A holder that died is taken over as soon as its lease lapses.
196
+ The 250 ms margin covers a host clock ahead of MongoDB's, which would wake the
197
+ loop before the lease has lapsed on the server; a host further ahead is
198
+ refused again, and retries every 250 ms until the lease lapses.
188
199
  A restart inside a crashed follower's `leaseMs` gets `RUNNING` too: run this
189
200
  loop rather than exit.
190
201
 
@@ -258,7 +269,7 @@ This package throws `SearchSyncError`; what caused it is its `cause`.
258
269
  | `HISTORY_LOST` | `start` with `onHistoryLost: 'fail'`, and the recorded point is older than the server's history. `cause` is `@nxgt/mongo`'s `DataError`, `serverCode` 286 or 280 |
259
270
  | `ID_MISMATCH` | the transform gave a document whose primary key is not its index id |
260
271
  | `NOT_A_DOCUMENT` | the transform gave back something that is neither a document nor `null` — a string, a number, an array. The message says its shape, never its value |
261
- | `RUNNING` | the name is taken. On this sync object: `reindex()` or a second `start()` while it is already following. Through the lease: `start()` or `reindex()` while another process holds the name — or another `createSearchSync` with the same name in this process, whose holder then starts with this process's own `host:pid`. The message names the holder and when its lease ends |
272
+ | `RUNNING` | the name is taken. On this sync object: `reindex()` or a second `start()` while it is already following. Through the lease: `start()` or `reindex()` while another process holds the name — or another `createSearchSync` with the same name in this process, whose holder then starts with this process's own `host:pid`. The message names the holder and when its lease ends, and so do `holder` and `expiresAt` — read from the lease document, `expiresAt` on MongoDB's clock. Both are `undefined` on the sync object's own refusal, which only its `close()` ends, and when the holder let go before it could be read |
262
273
  | `LEASE_LOST` | the name's lease is no longer this sync's: it was not renewed within `leaseMs` and another process took it over, or it was removed. `closed` rejects with it, and nothing more is sent; `reindex()` and `start()` reject with it while reindexing, having recorded nothing, and removed nothing unless the lease went while the removal itself ran |
263
274
  | `FAILED` | anything else: the transform threw, MongoDB or Meilisearch refused. The message says what the sync was doing |
264
275
 
@@ -341,8 +352,12 @@ function createSearchSync<C extends AnyCollectionDefinition, I extends AnyIndexD
341
352
  `class SearchSyncError extends Error`: `code: SearchSyncErrorCode`
342
353
  (`'HISTORY_LOST' | 'ID_MISMATCH' | 'NOT_A_DOCUMENT' | 'RUNNING' | 'LEASE_LOST' | 'FAILED'`),
343
354
  `sync: string`,
355
+ `holder: string | undefined` and `expiresAt: Date | undefined` (set on a
356
+ `RUNNING` the lease refused; `undefined` on every other error, on a sync
357
+ object's own `RUNNING`, and on a lease-refused `RUNNING` whose holder let go
358
+ before it could be read),
344
359
  `cause`. Its constructor takes `(message, options: SearchSyncErrorOptions)`,
345
- that is `{ code, sync, cause? }`; both types are exported.
360
+ that is `{ code, sync, cause?, holder?, expiresAt? }`; both types are exported.
346
361
 
347
362
  ## What does not compile
348
363
 
@@ -358,6 +373,8 @@ Each is a `@ts-expect-error` case in this package's type tests.
358
373
  - A driver `Collection` for `collection`, or the SDK's `Index` for `index`.
359
374
  - No `transform`, an option this package does not have, or an
360
375
  `onHistoryLost` other than `'reindex'` or `'fail'`.
376
+ - A `SearchSyncError`'s `expiresAt` read as a `Date` without checking it is
377
+ there, or assigned; an `expiresAt` given to its constructor as a string.
361
378
 
362
379
  ## Traps
363
380
 
package/dist/errors.d.ts CHANGED
@@ -22,6 +22,10 @@ export interface SearchSyncErrorOptions {
22
22
  code: SearchSyncErrorCode;
23
23
  sync: string;
24
24
  cause?: unknown;
25
+ /** Who holds the lease, on a `RUNNING` the lease refused. */
26
+ holder?: string | undefined;
27
+ /** When that lease lapses unrenewed, on a `RUNNING` the lease refused. */
28
+ expiresAt?: Date | undefined;
25
29
  }
26
30
  /** An error of a sync, with the sync's name and what caused it. */
27
31
  export declare class SearchSyncError extends Error {
@@ -29,6 +33,22 @@ export declare class SearchSyncError extends Error {
29
33
  readonly code: SearchSyncErrorCode;
30
34
  /** The sync's name. */
31
35
  readonly sync: string;
36
+ /**
37
+ * On a `RUNNING` the lease refused: who holds the name, as that process
38
+ * described itself (`host:pid:<ObjectId>`). A label for a log, not a value
39
+ * to parse. `undefined` on every other error, on the `RUNNING` of a sync
40
+ * object that is already following in this process, and when the holder
41
+ * let go before it could be read.
42
+ */
43
+ readonly holder: string | undefined;
44
+ /**
45
+ * On a `RUNNING` the lease refused: when the holder's lease lapses if it
46
+ * stops renewing it, read from the lease document and so on the MongoDB
47
+ * server's clock. A live holder renews it every third of `leaseMs`, so a
48
+ * start at this time can still be refused, with a later one. `undefined`
49
+ * whenever `holder` is.
50
+ */
51
+ readonly expiresAt: Date | undefined;
32
52
  constructor(message: string, options: SearchSyncErrorOptions);
33
53
  }
34
54
  /** `error` as a `SearchSyncError`, kept as it is when it already is one. */
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,MAAM,MAAM,mBAAmB;AAC9B,kEAAkE;AAChE,cAAc;AAChB,2EAA2E;GACzE,aAAa;AACf,0EAA0E;GACxE,gBAAgB;AAClB;;;GAGG;GACD,SAAS;AACX;;;GAGG;GACD,YAAY;AACd,0CAA0C;GACxC,QAAQ,CAAC;AAEZ,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,mEAAmE;AACnE,qBAAa,eAAgB,SAAQ,KAAK;IAChC,IAAI,SAAqB;IAClC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,uBAAuB;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB;CAQ5D;AAED,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,mBAWjE"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,MAAM,MAAM,mBAAmB;AAC9B,kEAAkE;AAChE,cAAc;AAChB,2EAA2E;GACzE,aAAa;AACf,0EAA0E;GACxE,gBAAgB;AAClB;;;GAGG;GACD,SAAS;AACX;;;GAGG;GACD,YAAY;AACd,0CAA0C;GACxC,QAAQ,CAAC;AAEZ,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,0EAA0E;IAC1E,SAAS,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CAC7B;AAED,mEAAmE;AACnE,qBAAa,eAAgB,SAAQ,KAAK;IAChC,IAAI,SAAqB;IAClC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,uBAAuB;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,IAAI,GAAG,SAAS,CAAC;gBAEzB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB;CAU5D;AAED,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,mBAWjE"}
package/dist/index.js CHANGED
@@ -82,6 +82,8 @@ class SearchSyncError extends Error {
82
82
  this.name = "SearchSyncError";
83
83
  this.code = options.code;
84
84
  this.sync = options.sync;
85
+ this.holder = options.holder;
86
+ this.expiresAt = options.expiresAt;
85
87
  }
86
88
  }
87
89
  function failed(sync, doing, error) {
@@ -168,8 +170,15 @@ async function acquire(ctx, doing) {
168
170
  } catch (error) {
169
171
  throw failed(ctx.name, "taking its lease", error);
170
172
  }
171
- const until = current?.expiresAt instanceof Date ? current.expiresAt.toISOString() : "it lets go";
172
- throw new SearchSyncError(`Search sync "${ctx.name}" is held by ${current?.holder ?? "another process"} ` + `until ${until}: ` + `wait for it to close, or for its lease to lapse, before you ${doing}.`, { code: "RUNNING", sync: ctx.name });
173
+ const until = current?.expiresAt instanceof Date ? current.expiresAt : undefined;
174
+ const theirs = typeof current?.holder === "string" ? current.holder : undefined;
175
+ const expiresAt = theirs === undefined ? undefined : until;
176
+ throw new SearchSyncError(`Search sync "${ctx.name}" is held by ${theirs ?? "another process"} ` + `until ${until?.toISOString() ?? "it lets go"}: ` + `wait for it to close, or for its lease to lapse, before you ${doing}.`, {
177
+ code: "RUNNING",
178
+ sync: ctx.name,
179
+ holder: theirs,
180
+ expiresAt
181
+ });
173
182
  }
174
183
  async function renew(ctx, lease) {
175
184
  try {
@@ -543,5 +552,5 @@ export {
543
552
  createSearchSync
544
553
  };
545
554
 
546
- //# debugId=663F5145BB58E86F64756E2164756E21
555
+ //# debugId=FCC6FD234D55BAD364756E2164756E21
547
556
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -5,16 +5,16 @@
5
5
  "import type { AnyIndexDefinition, TypedIndex } from '@nxgt/meilisearch';\nimport type { AnyCollectionDefinition, TypedCollection } from '@nxgt/mongo';\nimport type { Collection } from 'mongodb';\nimport type { LeaseDocument } from './lease';\nimport type { SearchSyncOptions, SearchSyncState } from './types';\n\n/** Loosely typed: this layer works on any documents; the public types are what callers see. */\nexport type Doc = Record<string, unknown>;\n\n/**\n * What a sync works from, resolved once. Data, and the caller's own two\n * functions as they were given.\n */\nexport interface SyncContext {\n\treadonly name: string;\n\treadonly collection: TypedCollection<AnyCollectionDefinition>;\n\treadonly index: TypedIndex<AnyIndexDefinition>;\n\treadonly primaryKey: string;\n\treadonly state: Collection<SearchSyncState>;\n\t/** The same collection, for the lease documents it also holds. */\n\treadonly leases: Collection<LeaseDocument>;\n\t/** How long a lease lasts unrenewed, in ms. */\n\treadonly leaseMs: number;\n\treadonly batchSize: number;\n\treadonly flushIntervalMs: number;\n\treadonly positionIntervalMs: number;\n\treadonly pageSize: number;\n\treadonly onHistoryLost: 'reindex' | 'fail';\n\treadonly transform: (document: Doc) => unknown;\n\treadonly toIndexId: (id: unknown) => unknown;\n\t/**\n\t * Whether this sync is following changes in this process. A reindex\n\t * beside its own follower would remove what the follower has just\n\t * indexed, and the follower would never send it again.\n\t */\n\treadonly follower: { running: boolean };\n}\n\nconst DEFAULT_STATE_COLLECTION = 'nxgt_search_sync';\n\nfunction positive(option: string, value: number | undefined, fallback: number) {\n\tconst resolved = value ?? fallback;\n\tif (!Number.isInteger(resolved) || resolved < 1) {\n\t\tthrow new TypeError(\n\t\t\t`createSearchSync: ${option} must be a whole number above 0, not ${String(value)}`,\n\t\t);\n\t}\n\treturn resolved;\n}\n\nexport function createContext(\n\toptions: SearchSyncOptions<AnyCollectionDefinition, AnyIndexDefinition>,\n): SyncContext {\n\tconst { collection, index } = options;\n\tif (typeof options.transform !== 'function') {\n\t\tthrow new TypeError('createSearchSync: transform must be a function');\n\t}\n\tconst name = options.name ?? `${collection.collectionName}:${index.uid}`;\n\tif (name === '') {\n\t\tthrow new TypeError('createSearchSync: name must not be empty');\n\t}\n\tconst flushIntervalMs = options.flushIntervalMs ?? 1000;\n\tif (!Number.isInteger(flushIntervalMs) || flushIntervalMs < 0) {\n\t\tthrow new TypeError(\n\t\t\t`createSearchSync: flushIntervalMs must be a whole number of milliseconds, not ${String(options.flushIntervalMs)}`,\n\t\t);\n\t}\n\tconst stateCollection = options.stateCollection ?? DEFAULT_STATE_COLLECTION;\n\treturn {\n\t\tname,\n\t\tcollection,\n\t\tindex,\n\t\tprimaryKey: index.definition.primaryKey as string,\n\t\tstate: collection.db.collection<SearchSyncState>(stateCollection),\n\t\tleases: collection.db.collection<LeaseDocument>(stateCollection),\n\t\tleaseMs: positive('leaseMs', options.leaseMs, 30_000),\n\t\tbatchSize: positive('batchSize', options.batchSize, 500),\n\t\tpositionIntervalMs: positive(\n\t\t\t'positionIntervalMs',\n\t\t\toptions.positionIntervalMs,\n\t\t\t60_000,\n\t\t),\n\t\tflushIntervalMs,\n\t\tpageSize: positive('pageSize', options.pageSize, 100),\n\t\tonHistoryLost: options.onHistoryLost ?? 'reindex',\n\t\ttransform: options.transform as (document: Doc) => unknown,\n\t\ttoIndexId: (options.toIndexId ?? String) as (id: unknown) => unknown,\n\t\tfollower: { running: false },\n\t};\n}\n",
6
6
  "import type { ResumeToken } from '@nxgt/mongo';\nimport { MeilisearchApiError } from 'meilisearch';\nimport { deleteIds, sendDocuments } from './batch';\nimport type { Doc, SyncContext } from './context';\nimport { entryOf, keyOf } from './documents';\nimport { failed } from './errors';\nimport { confirmLease, type HeldLease, leaseLost, withLease } from './lease';\nimport { checkIdle } from './running';\nimport { saveState } from './state';\nimport type { ReindexReport } from './types';\n\n/**\n * Where the collection's changes are now, without waiting for one: a new\n * stream's first read answers with its position. The server holds that read\n * for up to `maxAwaitTimeMS` — a second by default, measured.\n */\nasync function currentToken(ctx: SyncContext): Promise<ResumeToken> {\n\tconst stream = ctx.collection.raw.watch([], { maxAwaitTimeMS: 1 });\n\ttry {\n\t\tawait stream.tryNext();\n\t\treturn stream.resumeToken as unknown as ResumeToken;\n\t} finally {\n\t\tawait stream.close();\n\t}\n}\n\n/** Every live document through the transform, a page at a time. */\nasync function sendAll(\n\tctx: SyncContext,\n\twanted: Set<string>,\n\tlease: HeldLease,\n) {\n\tlet indexed = 0;\n\tlet skipped = 0;\n\tlet after: string | null | undefined;\n\tdo {\n\t\tconst page = await ctx.collection.paginateByCursor({\n\t\t\tafter,\n\t\t\tlimit: ctx.pageSize,\n\t\t});\n\t\tconst documents: Doc[] = [];\n\t\tfor (const document of page.items as Doc[]) {\n\t\t\tconst entry = await entryOf(ctx, document._id, document);\n\t\t\tif (entry.kind === 'delete') {\n\t\t\t\tskipped += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\twanted.add(entry.key);\n\t\t\tdocuments.push(entry.document);\n\t\t}\n\t\tawait sendDocuments(ctx, documents);\n\t\tindexed += documents.length;\n\t\t// Found lost by a renewal: stop sending beside whoever holds it now.\n\t\tif (lease.lost) throw leaseLost(ctx);\n\t\tafter = page.nextCursor;\n\t} while (after);\n\treturn { indexed, skipped };\n}\n\nconst LIST_LIMIT = 1000;\n\n/** Nothing was ever written to it: an index that does not exist holds nothing. */\nconst missingIndex = (error: unknown) =>\n\terror instanceof MeilisearchApiError &&\n\terror.cause?.code === 'index_not_found';\n\n/** Takes out what the index holds and the collection no longer gives it. */\nasync function removeUnwanted(\n\tctx: SyncContext,\n\twanted: Set<string>,\n): Promise<number> {\n\tconst unwanted: unknown[] = [];\n\tfor (let offset = 0; ; offset += LIST_LIMIT) {\n\t\tconst page = await ctx.index.raw\n\t\t\t.getDocuments({ fields: [ctx.primaryKey], limit: LIST_LIMIT, offset })\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tif (missingIndex(error)) return undefined;\n\t\t\t\tthrow error;\n\t\t\t});\n\t\tif (!page) return 0;\n\t\tfor (const document of page.results as Doc[]) {\n\t\t\tconst id = document[ctx.primaryKey];\n\t\t\tif (!wanted.has(keyOf(id))) unwanted.push(id);\n\t\t}\n\t\tif (offset + LIST_LIMIT >= page.total) break;\n\t}\n\tawait deleteIds(ctx, unwanted);\n\treturn unwanted.length;\n}\n\n/**\n * A reindex, holding the sync's lease for as long as it runs: a follower in\n * another process would otherwise have what it just indexed removed, and\n * never send it again.\n */\nexport async function reindex(ctx: SyncContext): Promise<ReindexReport> {\n\tcheckIdle(ctx, 'reindex');\n\treturn withLease(ctx, 'reindex', (lease) => reindexHeld(ctx, lease));\n}\n\n/**\n * A reindex by a caller that holds the lease. What removes documents and what\n * records a point each go only once the server confirms the lease is still\n * this one's: after another process took it over, either would undo what\n * that process just did.\n */\nexport async function reindexHeld(\n\tctx: SyncContext,\n\tlease: HeldLease,\n): Promise<ReindexReport> {\n\ttry {\n\t\t// Taken first: a change made while the documents are read is followed\n\t\t// again from here, so it cannot fall between the two.\n\t\tconst token = await currentToken(ctx);\n\t\tconst wanted = new Set<string>();\n\t\tconst { indexed, skipped } = await sendAll(ctx, wanted, lease);\n\t\tawait confirmLease(ctx, lease);\n\t\tconst removed = await removeUnwanted(ctx, wanted);\n\t\tawait confirmLease(ctx, lease);\n\t\tawait saveState(ctx, token, true);\n\t\treturn { indexed, skipped, removed };\n\t} catch (error) {\n\t\tthrow failed(ctx.name, 'reindexing', error);\n\t}\n}\n",
7
7
  "import type { Doc, SyncContext } from './context';\nimport type { Entry } from './documents';\n\n/**\n * How long a write may take to be applied. Meilisearch's own default is\n * five seconds, which a batch on a large index outlasts.\n */\nconst WAIT = { wait: { timeout: 120_000 } } as const;\n\nfunction chunks<T>(items: readonly T[], size: number): T[][] {\n\tconst out: T[][] = [];\n\tfor (let i = 0; i < items.length; i += size) {\n\t\tout.push(items.slice(i, i + size));\n\t}\n\treturn out;\n}\n\n/** Adds documents, `batchSize` at a time, each batch applied before the next. */\nexport async function sendDocuments(\n\tctx: SyncContext,\n\tdocuments: readonly Doc[],\n): Promise<void> {\n\tfor (const batch of chunks(documents, ctx.batchSize)) {\n\t\tawait ctx.index.add(batch, WAIT);\n\t}\n}\n\n/** Deletes documents by id, `batchSize` at a time. */\nexport async function deleteIds(\n\tctx: SyncContext,\n\tids: readonly unknown[],\n): Promise<void> {\n\tfor (const batch of chunks(ids, ctx.batchSize)) {\n\t\tawait ctx.index.delete(batch, WAIT);\n\t}\n}\n\n/**\n * Applies entries: one per id, so adds and deletes touch different\n * documents and their order does not matter.\n */\nexport async function send(\n\tctx: SyncContext,\n\tentries: readonly Entry[],\n): Promise<void> {\n\tconst documents: Doc[] = [];\n\tconst ids: unknown[] = [];\n\tfor (const entry of entries) {\n\t\tif (entry.kind === 'upsert') documents.push(entry.document);\n\t\telse ids.push(entry.id);\n\t}\n\tawait sendDocuments(ctx, documents);\n\tawait deleteIds(ctx, ids);\n}\n",
8
- "/** What stopped a sync, as a string to switch on. */\nexport type SearchSyncErrorCode =\n\t/** The resume point is older than the server's change history. */\n\t| 'HISTORY_LOST'\n\t/** The transform gave a document whose primary key is not its index id. */\n\t| 'ID_MISMATCH'\n\t/** The transform gave something that is neither a document nor `null`. */\n\t| 'NOT_A_DOCUMENT'\n\t/**\n\t * This sync's name is taken: it is following changes in this process, or\n\t * another process holds its lease.\n\t */\n\t| 'RUNNING'\n\t/**\n\t * A running sync's lease was not renewed in time and is no longer its\n\t * own: it stopped rather than follow beside whoever took the name over.\n\t */\n\t| 'LEASE_LOST'\n\t/** Anything else: the cause says what. */\n\t| 'FAILED';\n\nexport interface SearchSyncErrorOptions {\n\tcode: SearchSyncErrorCode;\n\tsync: string;\n\tcause?: unknown;\n}\n\n/** An error of a sync, with the sync's name and what caused it. */\nexport class SearchSyncError extends Error {\n\toverride name = 'SearchSyncError';\n\treadonly code: SearchSyncErrorCode;\n\t/** The sync's name. */\n\treadonly sync: string;\n\n\tconstructor(message: string, options: SearchSyncErrorOptions) {\n\t\tsuper(\n\t\t\tmessage,\n\t\t\toptions.cause === undefined ? undefined : { cause: options.cause },\n\t\t);\n\t\tthis.code = options.code;\n\t\tthis.sync = options.sync;\n\t}\n}\n\n/** `error` as a `SearchSyncError`, kept as it is when it already is one. */\nexport function failed(sync: string, doing: string, error: unknown) {\n\tif (error instanceof SearchSyncError) return error;\n\tconst reason = error instanceof Error ? error.message : String(error);\n\treturn new SearchSyncError(\n\t\t`Search sync \"${sync}\" failed ${doing}: ${reason}`,\n\t\t{\n\t\t\tcode: 'FAILED',\n\t\t\tsync,\n\t\t\tcause: error,\n\t\t},\n\t);\n}\n",
8
+ "/** What stopped a sync, as a string to switch on. */\nexport type SearchSyncErrorCode =\n\t/** The resume point is older than the server's change history. */\n\t| 'HISTORY_LOST'\n\t/** The transform gave a document whose primary key is not its index id. */\n\t| 'ID_MISMATCH'\n\t/** The transform gave something that is neither a document nor `null`. */\n\t| 'NOT_A_DOCUMENT'\n\t/**\n\t * This sync's name is taken: it is following changes in this process, or\n\t * another process holds its lease.\n\t */\n\t| 'RUNNING'\n\t/**\n\t * A running sync's lease was not renewed in time and is no longer its\n\t * own: it stopped rather than follow beside whoever took the name over.\n\t */\n\t| 'LEASE_LOST'\n\t/** Anything else: the cause says what. */\n\t| 'FAILED';\n\nexport interface SearchSyncErrorOptions {\n\tcode: SearchSyncErrorCode;\n\tsync: string;\n\tcause?: unknown;\n\t/** Who holds the lease, on a `RUNNING` the lease refused. */\n\tholder?: string | undefined;\n\t/** When that lease lapses unrenewed, on a `RUNNING` the lease refused. */\n\texpiresAt?: Date | undefined;\n}\n\n/** An error of a sync, with the sync's name and what caused it. */\nexport class SearchSyncError extends Error {\n\toverride name = 'SearchSyncError';\n\treadonly code: SearchSyncErrorCode;\n\t/** The sync's name. */\n\treadonly sync: string;\n\t/**\n\t * On a `RUNNING` the lease refused: who holds the name, as that process\n\t * described itself (`host:pid:<ObjectId>`). A label for a log, not a value\n\t * to parse. `undefined` on every other error, on the `RUNNING` of a sync\n\t * object that is already following in this process, and when the holder\n\t * let go before it could be read.\n\t */\n\treadonly holder: string | undefined;\n\t/**\n\t * On a `RUNNING` the lease refused: when the holder's lease lapses if it\n\t * stops renewing it, read from the lease document and so on the MongoDB\n\t * server's clock. A live holder renews it every third of `leaseMs`, so a\n\t * start at this time can still be refused, with a later one. `undefined`\n\t * whenever `holder` is.\n\t */\n\treadonly expiresAt: Date | undefined;\n\n\tconstructor(message: string, options: SearchSyncErrorOptions) {\n\t\tsuper(\n\t\t\tmessage,\n\t\t\toptions.cause === undefined ? undefined : { cause: options.cause },\n\t\t);\n\t\tthis.code = options.code;\n\t\tthis.sync = options.sync;\n\t\tthis.holder = options.holder;\n\t\tthis.expiresAt = options.expiresAt;\n\t}\n}\n\n/** `error` as a `SearchSyncError`, kept as it is when it already is one. */\nexport function failed(sync: string, doing: string, error: unknown) {\n\tif (error instanceof SearchSyncError) return error;\n\tconst reason = error instanceof Error ? error.message : String(error);\n\treturn new SearchSyncError(\n\t\t`Search sync \"${sync}\" failed ${doing}: ${reason}`,\n\t\t{\n\t\t\tcode: 'FAILED',\n\t\t\tsync,\n\t\t\tcause: error,\n\t\t},\n\t);\n}\n",
9
9
  "import type { Doc, SyncContext } from './context';\nimport { SearchSyncError } from './errors';\n\n/** One document's fate in the index: sent, or taken out. */\nexport type Entry =\n\t| { kind: 'upsert'; key: string; document: Doc }\n\t| { kind: 'delete'; key: string; id: unknown };\n\n/** An id as a key: `1` and `'1'` are different ids to Meilisearch. */\nexport const keyOf = (id: unknown) => `${typeof id}:${String(id)}`;\n\nconst isRecord = (value: unknown): value is Doc =>\n\ttypeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** What a transform gave back, for a message: its shape, never its value. */\nfunction describe(value: unknown): string {\n\tif (Array.isArray(value)) return 'an array';\n\tif (value === undefined) return 'undefined';\n\treturn `a ${typeof value}`;\n}\n\n/**\n * What the index should hold for a document of the collection: what the\n * transform makes of it, or nothing when it is gone or kept out.\n */\nexport async function entryOf(\n\tctx: SyncContext,\n\tmongoId: unknown,\n\tdocument: Doc | undefined,\n): Promise<Entry> {\n\tconst id = ctx.toIndexId(mongoId);\n\tconst key = keyOf(id);\n\tif (!document) return { kind: 'delete', key, id };\n\tconst indexed = await ctx.transform(document);\n\tif (indexed === null) return { kind: 'delete', key, id };\n\tif (!isRecord(indexed)) {\n\t\t// A `SearchSyncError` with a code of its own, like the id mismatch\n\t\t// below it: a bare `TypeError` came back through `failed()` as\n\t\t// `FAILED`, the code that means \"anything else\", and named neither the\n\t\t// sync nor the document whose transform did it.\n\t\tthrow new SearchSyncError(\n\t\t\t`Search sync \"${ctx.name}\": transform gave ${describe(indexed)} for ` +\n\t\t\t\t`the document ${String(mongoId)}. It must give a document to ` +\n\t\t\t\t'index, or null to keep it out.',\n\t\t\t{ code: 'NOT_A_DOCUMENT', sync: ctx.name },\n\t\t);\n\t}\n\tconst given = indexed[ctx.primaryKey];\n\tif (keyOf(given) !== key) {\n\t\tthrow new SearchSyncError(\n\t\t\t`Search sync \"${ctx.name}\": transform gave \"${ctx.primaryKey}\" ` +\n\t\t\t\t`${JSON.stringify(given)} for the document ${String(mongoId)}, whose ` +\n\t\t\t\t`index id is ${JSON.stringify(id)}. A document under another id ` +\n\t\t\t\t'could never be taken out of the index again.',\n\t\t\t{ code: 'ID_MISMATCH', sync: ctx.name },\n\t\t);\n\t}\n\treturn { kind: 'upsert', key, document: indexed };\n}\n",
10
- "import { hostname } from 'node:os';\nimport { ObjectId } from 'mongodb';\nimport type { SyncContext } from './context';\nimport { failed, SearchSyncError } from './errors';\n\n/**\n * The lease on a sync's name: one document in the state collection, whose\n * `_id` is `{ lease: <name> }` — an object, so it can never be a sync's own\n * state, whose `_id` is the name itself.\n *\n * Taking it is one atomic write whose update decides — the server, not a\n * read before a write. Every time is the server's (`$$NOW`), so hosts whose\n * clocks disagree still agree on when a lease lapses.\n *\n * Renewal, release and the holder's name follow `@nxgt/mongo`'s migration\n * lock (`migrations/lock.ts`); taking it does not — see `take`.\n */\nexport interface LeaseDocument {\n\t_id: { lease: string };\n\tholder: string;\n\tacquiredAt: Date;\n\texpiresAt: Date;\n}\n\n/** A held lease: data, and whether a renewal found it gone. */\nexport interface HeldLease {\n\treadonly holder: string;\n\tlost: boolean;\n}\n\nconst idOf = (ctx: SyncContext) => ({ lease: ctx.name });\n\n/**\n * Inserts the lease, or takes over one that lapsed, in one atomic upsert.\n *\n * The filter is `_id` alone — the server allows no `$expr` in an upsert's\n * filter — and the decision is made by the update instead: a pipeline that\n * keeps the current holder's fields while its lease runs, and writes this\n * holder's when there is none or it lapsed. What comes back says who won.\n * It is a `findAndModify`, not an `aggregate`: `@nxgt/mongo`'s migration lock\n * takes its lock with `$documents`/`$merge`, which a failpoint or a monitor\n * aimed at the change stream's `aggregate` would also catch.\n */\nasync function take(ctx: SyncContext, holder: string): Promise<boolean> {\n\tconst free = {\n\t\t$or: [\n\t\t\t{ $eq: [{ $type: '$expiresAt' }, 'missing'] },\n\t\t\t{ $lte: ['$expiresAt', '$$NOW'] },\n\t\t],\n\t};\n\tconst pick = (mine: unknown, theirs: string) => ({\n\t\t$cond: [free, mine, theirs],\n\t});\n\ttry {\n\t\tconst after = await ctx.leases.findOneAndUpdate(\n\t\t\t{ _id: idOf(ctx) },\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\t$set: {\n\t\t\t\t\t\tholder: pick({ $literal: holder }, '$holder'),\n\t\t\t\t\t\tacquiredAt: pick('$$NOW', '$acquiredAt'),\n\t\t\t\t\t\texpiresAt: pick({ $add: ['$$NOW', ctx.leaseMs] }, '$expiresAt'),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\t{ upsert: true, returnDocument: 'after' },\n\t\t);\n\t\treturn after?.holder === holder;\n\t} catch (error) {\n\t\t// Two upserts racing to insert the same `_id`: the other one won.\n\t\tif ((error as { code?: number }).code === 11000) return false;\n\t\tthrow error;\n\t}\n}\n\n/**\n * Takes the lease, or throws `RUNNING` naming who holds it and until when —\n * the same code a second `start` in this process gets, since it means the\n * same thing: this name is being followed.\n */\nexport async function acquire(\n\tctx: SyncContext,\n\tdoing: string,\n): Promise<HeldLease> {\n\tconst holder = `${hostname()}:${process.pid}:${new ObjectId().toHexString()}`;\n\tlet current: LeaseDocument | null = null;\n\ttry {\n\t\t// Twice at most: a holder that let go between the take and the read\n\t\t// left the name free, and refusing it then would be wrong.\n\t\tfor (let attempt = 0; attempt < 2; attempt += 1) {\n\t\t\tif (await take(ctx, holder)) return { holder, lost: false };\n\t\t\tcurrent = await ctx.leases.findOne({ _id: idOf(ctx) });\n\t\t\tif (current) break;\n\t\t}\n\t} catch (error) {\n\t\tthrow failed(ctx.name, 'taking its lease', error);\n\t}\n\tconst until =\n\t\tcurrent?.expiresAt instanceof Date\n\t\t\t? current.expiresAt.toISOString()\n\t\t\t: 'it lets go';\n\tthrow new SearchSyncError(\n\t\t`Search sync \"${ctx.name}\" is held by ${current?.holder ?? 'another process'} ` +\n\t\t\t`until ${until}: ` +\n\t\t\t`wait for it to close, or for its lease to lapse, before you ${doing}.`,\n\t\t{ code: 'RUNNING', sync: ctx.name },\n\t);\n}\n\n/**\n * Pushes the lease's end back by `leaseMs`. A renewal that finds another\n * holder marks the lease lost; one that does not reach the server is tried\n * again at the next beat, and the lease lapses only if none reaches it for a\n * whole `leaseMs`.\n */\nexport async function renew(ctx: SyncContext, lease: HeldLease): Promise<void> {\n\ttry {\n\t\tconst result = await ctx.leases.updateOne(\n\t\t\t{ _id: idOf(ctx), holder: lease.holder },\n\t\t\t[{ $set: { expiresAt: { $add: ['$$NOW', ctx.leaseMs] } } }],\n\t\t);\n\t\tif (result.matchedCount === 0) lease.lost = true;\n\t} catch {\n\t\t// Tried again at the next beat.\n\t}\n}\n\n/**\n * Renews the lease now, and throws `LEASE_LOST` if it is someone else's: the\n * check before a step that must not run beside another holder.\n */\nexport async function confirmLease(\n\tctx: SyncContext,\n\tlease: HeldLease,\n): Promise<void> {\n\tlet result: { matchedCount: number };\n\ttry {\n\t\tresult = await ctx.leases.updateOne(\n\t\t\t{ _id: idOf(ctx), holder: lease.holder },\n\t\t\t[{ $set: { expiresAt: { $add: ['$$NOW', ctx.leaseMs] } } }],\n\t\t);\n\t} catch (error) {\n\t\t// Unlike a timed renewal, not tried again later: what comes next must\n\t\t// not run on a lease nobody could confirm.\n\t\tthrow failed(ctx.name, 'checking its lease', error);\n\t}\n\tif (result.matchedCount === 0) lease.lost = true;\n\tif (lease.lost) throw leaseLost(ctx);\n}\n\n/** Lets go of it, if it is still this holder's. One that fails lapses. */\nexport async function release(\n\tctx: SyncContext,\n\tlease: HeldLease,\n): Promise<void> {\n\tawait ctx.leases\n\t\t.deleteOne({ _id: idOf(ctx), holder: lease.holder })\n\t\t.catch(() => undefined);\n}\n\n/** The error a sync stops with once its lease is someone else's. */\nexport function leaseLost(ctx: SyncContext): SearchSyncError {\n\treturn new SearchSyncError(\n\t\t`Search sync \"${ctx.name}\" lost its lease: another process holds the ` +\n\t\t\t'name now, or the lease was removed (it lapses when not renewed ' +\n\t\t\t`within ${ctx.leaseMs} ms). It stopped rather than run beside it.`,\n\t\t{ code: 'LEASE_LOST', sync: ctx.name },\n\t);\n}\n\n/**\n * Renews the lease every third of `leaseMs`, and calls `onLost` once when a\n * renewal finds it someone else's. Gives back what stops the renewals.\n */\nexport function keepLease(\n\tctx: SyncContext,\n\tlease: HeldLease,\n\tonLost: () => void,\n): () => void {\n\tlet stopped = false;\n\tconst beat = setInterval(\n\t\t() => {\n\t\t\tvoid renew(ctx, lease).then(() => {\n\t\t\t\t// A renewal still in flight when it was stopped reports nothing.\n\t\t\t\tif (lease.lost && !stopped) {\n\t\t\t\t\tclearInterval(beat);\n\t\t\t\t\tonLost();\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tMath.max(1, Math.floor(ctx.leaseMs / 3)),\n\t);\n\treturn () => {\n\t\tstopped = true;\n\t\tclearInterval(beat);\n\t};\n}\n\n/**\n * Runs `fn` holding the lease, renewed every third of `leaseMs`. `fn` checks\n * it is still held where that matters (`confirmLease`).\n */\nexport async function withLease<T>(\n\tctx: SyncContext,\n\tdoing: string,\n\tfn: (lease: HeldLease) => Promise<T>,\n): Promise<T> {\n\tconst lease = await acquire(ctx, doing);\n\tconst stop = keepLease(ctx, lease, () => undefined);\n\ttry {\n\t\treturn await fn(lease);\n\t} finally {\n\t\tstop();\n\t\tawait release(ctx, lease);\n\t}\n}\n",
11
- "import type { SyncContext } from './context';\nimport { SearchSyncError } from './errors';\n\n/**\n * Refuses to do beside a running follower what would undo its work: a\n * reindex removes what the follower has just indexed, and the follower,\n * having handled that change, never sends it again.\n */\nexport function checkIdle(ctx: SyncContext, doing: string): void {\n\tif (!ctx.follower.running) return;\n\tthrow new SearchSyncError(\n\t\t`Search sync \"${ctx.name}\" is already following changes in this ` +\n\t\t\t`process: close it before you ${doing}.`,\n\t\t{ code: 'RUNNING', sync: ctx.name },\n\t);\n}\n",
10
+ "import { hostname } from 'node:os';\nimport { ObjectId } from 'mongodb';\nimport type { SyncContext } from './context';\nimport { failed, SearchSyncError } from './errors';\n\n/**\n * The lease on a sync's name: one document in the state collection, whose\n * `_id` is `{ lease: <name> }` — an object, so it can never be a sync's own\n * state, whose `_id` is the name itself.\n *\n * Taking it is one atomic write whose update decides — the server, not a\n * read before a write. Every time is the server's (`$$NOW`), so hosts whose\n * clocks disagree still agree on when a lease lapses.\n *\n * Renewal, release and the holder's name follow `@nxgt/mongo`'s migration\n * lock (`migrations/lock.ts`); taking it does not — see `take`.\n */\nexport interface LeaseDocument {\n\t_id: { lease: string };\n\tholder: string;\n\tacquiredAt: Date;\n\texpiresAt: Date;\n}\n\n/** A held lease: data, and whether a renewal found it gone. */\nexport interface HeldLease {\n\treadonly holder: string;\n\tlost: boolean;\n}\n\nconst idOf = (ctx: SyncContext) => ({ lease: ctx.name });\n\n/**\n * Inserts the lease, or takes over one that lapsed, in one atomic upsert.\n *\n * The filter is `_id` alone — the server allows no `$expr` in an upsert's\n * filter — and the decision is made by the update instead: a pipeline that\n * keeps the current holder's fields while its lease runs, and writes this\n * holder's when there is none or it lapsed. What comes back says who won.\n * It is a `findAndModify`, not an `aggregate`: `@nxgt/mongo`'s migration lock\n * takes its lock with `$documents`/`$merge`, which a failpoint or a monitor\n * aimed at the change stream's `aggregate` would also catch.\n */\nasync function take(ctx: SyncContext, holder: string): Promise<boolean> {\n\tconst free = {\n\t\t$or: [\n\t\t\t{ $eq: [{ $type: '$expiresAt' }, 'missing'] },\n\t\t\t{ $lte: ['$expiresAt', '$$NOW'] },\n\t\t],\n\t};\n\tconst pick = (mine: unknown, theirs: string) => ({\n\t\t$cond: [free, mine, theirs],\n\t});\n\ttry {\n\t\tconst after = await ctx.leases.findOneAndUpdate(\n\t\t\t{ _id: idOf(ctx) },\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\t$set: {\n\t\t\t\t\t\tholder: pick({ $literal: holder }, '$holder'),\n\t\t\t\t\t\tacquiredAt: pick('$$NOW', '$acquiredAt'),\n\t\t\t\t\t\texpiresAt: pick({ $add: ['$$NOW', ctx.leaseMs] }, '$expiresAt'),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\t{ upsert: true, returnDocument: 'after' },\n\t\t);\n\t\treturn after?.holder === holder;\n\t} catch (error) {\n\t\t// Two upserts racing to insert the same `_id`: the other one won.\n\t\tif ((error as { code?: number }).code === 11000) return false;\n\t\tthrow error;\n\t}\n}\n\n/**\n * Takes the lease, or throws `RUNNING` naming who holds it and until when —\n * in the message and as `holder` and `expiresAt`, so a standby can wait for\n * that moment rather than a fixed time. The same code a second `start` in\n * this process gets, since it means the same thing: this name is being\n * followed.\n */\nexport async function acquire(\n\tctx: SyncContext,\n\tdoing: string,\n): Promise<HeldLease> {\n\tconst holder = `${hostname()}:${process.pid}:${new ObjectId().toHexString()}`;\n\tlet current: LeaseDocument | null = null;\n\ttry {\n\t\t// Twice at most: a holder that let go between the take and the read\n\t\t// left the name free, and refusing it then would be wrong.\n\t\tfor (let attempt = 0; attempt < 2; attempt += 1) {\n\t\t\tif (await take(ctx, holder)) return { holder, lost: false };\n\t\t\tcurrent = await ctx.leases.findOne({ _id: idOf(ctx) });\n\t\t\tif (current) break;\n\t\t}\n\t} catch (error) {\n\t\tthrow failed(ctx.name, 'taking its lease', error);\n\t}\n\t// Each only when the document holds what it should; `expiresAt` only\n\t// beside a holder, as the error's own documentation promises.\n\tconst until =\n\t\tcurrent?.expiresAt instanceof Date ? current.expiresAt : undefined;\n\tconst theirs =\n\t\ttypeof current?.holder === 'string' ? current.holder : undefined;\n\tconst expiresAt = theirs === undefined ? undefined : until;\n\tthrow new SearchSyncError(\n\t\t`Search sync \"${ctx.name}\" is held by ${theirs ?? 'another process'} ` +\n\t\t\t`until ${until?.toISOString() ?? 'it lets go'}: ` +\n\t\t\t`wait for it to close, or for its lease to lapse, before you ${doing}.`,\n\t\t{\n\t\t\tcode: 'RUNNING',\n\t\t\tsync: ctx.name,\n\t\t\tholder: theirs,\n\t\t\texpiresAt,\n\t\t},\n\t);\n}\n\n/**\n * Pushes the lease's end back by `leaseMs`. A renewal that finds another\n * holder marks the lease lost; one that does not reach the server is tried\n * again at the next beat, and the lease lapses only if none reaches it for a\n * whole `leaseMs`.\n */\nexport async function renew(ctx: SyncContext, lease: HeldLease): Promise<void> {\n\ttry {\n\t\tconst result = await ctx.leases.updateOne(\n\t\t\t{ _id: idOf(ctx), holder: lease.holder },\n\t\t\t[{ $set: { expiresAt: { $add: ['$$NOW', ctx.leaseMs] } } }],\n\t\t);\n\t\tif (result.matchedCount === 0) lease.lost = true;\n\t} catch {\n\t\t// Tried again at the next beat.\n\t}\n}\n\n/**\n * Renews the lease now, and throws `LEASE_LOST` if it is someone else's: the\n * check before a step that must not run beside another holder.\n */\nexport async function confirmLease(\n\tctx: SyncContext,\n\tlease: HeldLease,\n): Promise<void> {\n\tlet result: { matchedCount: number };\n\ttry {\n\t\tresult = await ctx.leases.updateOne(\n\t\t\t{ _id: idOf(ctx), holder: lease.holder },\n\t\t\t[{ $set: { expiresAt: { $add: ['$$NOW', ctx.leaseMs] } } }],\n\t\t);\n\t} catch (error) {\n\t\t// Unlike a timed renewal, not tried again later: what comes next must\n\t\t// not run on a lease nobody could confirm.\n\t\tthrow failed(ctx.name, 'checking its lease', error);\n\t}\n\tif (result.matchedCount === 0) lease.lost = true;\n\tif (lease.lost) throw leaseLost(ctx);\n}\n\n/** Lets go of it, if it is still this holder's. One that fails lapses. */\nexport async function release(\n\tctx: SyncContext,\n\tlease: HeldLease,\n): Promise<void> {\n\tawait ctx.leases\n\t\t.deleteOne({ _id: idOf(ctx), holder: lease.holder })\n\t\t.catch(() => undefined);\n}\n\n/** The error a sync stops with once its lease is someone else's. */\nexport function leaseLost(ctx: SyncContext): SearchSyncError {\n\treturn new SearchSyncError(\n\t\t`Search sync \"${ctx.name}\" lost its lease: another process holds the ` +\n\t\t\t'name now, or the lease was removed (it lapses when not renewed ' +\n\t\t\t`within ${ctx.leaseMs} ms). It stopped rather than run beside it.`,\n\t\t{ code: 'LEASE_LOST', sync: ctx.name },\n\t);\n}\n\n/**\n * Renews the lease every third of `leaseMs`, and calls `onLost` once when a\n * renewal finds it someone else's. Gives back what stops the renewals.\n */\nexport function keepLease(\n\tctx: SyncContext,\n\tlease: HeldLease,\n\tonLost: () => void,\n): () => void {\n\tlet stopped = false;\n\tconst beat = setInterval(\n\t\t() => {\n\t\t\tvoid renew(ctx, lease).then(() => {\n\t\t\t\t// A renewal still in flight when it was stopped reports nothing.\n\t\t\t\tif (lease.lost && !stopped) {\n\t\t\t\t\tclearInterval(beat);\n\t\t\t\t\tonLost();\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tMath.max(1, Math.floor(ctx.leaseMs / 3)),\n\t);\n\treturn () => {\n\t\tstopped = true;\n\t\tclearInterval(beat);\n\t};\n}\n\n/**\n * Runs `fn` holding the lease, renewed every third of `leaseMs`. `fn` checks\n * it is still held where that matters (`confirmLease`).\n */\nexport async function withLease<T>(\n\tctx: SyncContext,\n\tdoing: string,\n\tfn: (lease: HeldLease) => Promise<T>,\n): Promise<T> {\n\tconst lease = await acquire(ctx, doing);\n\tconst stop = keepLease(ctx, lease, () => undefined);\n\ttry {\n\t\treturn await fn(lease);\n\t} finally {\n\t\tstop();\n\t\tawait release(ctx, lease);\n\t}\n}\n",
11
+ "import type { SyncContext } from './context';\nimport { SearchSyncError } from './errors';\n\n/**\n * Refuses to do beside a running follower what would undo its work: a\n * reindex removes what the follower has just indexed, and the follower,\n * having handled that change, never sends it again.\n *\n * Its `RUNNING` carries no `holder` and no `expiresAt`: the lease is this\n * process's own and renewed for as long as it follows, so there is no time to\n * wait for — only a `close()` frees the name.\n */\nexport function checkIdle(ctx: SyncContext, doing: string): void {\n\tif (!ctx.follower.running) return;\n\tthrow new SearchSyncError(\n\t\t`Search sync \"${ctx.name}\" is already following changes in this ` +\n\t\t\t`process: close it before you ${doing}.`,\n\t\t{ code: 'RUNNING', sync: ctx.name },\n\t);\n}\n",
12
12
  "import type { ResumeToken } from '@nxgt/mongo';\nimport type { SyncContext } from './context';\nimport { failed } from './errors';\nimport type { SearchSyncState } from './types';\n\n/** What the sync has recorded, or `undefined` before its first reindex. */\nexport async function readState(\n\tctx: SyncContext,\n): Promise<SearchSyncState | undefined> {\n\ttry {\n\t\treturn (await ctx.state.findOne({ _id: ctx.name })) ?? undefined;\n\t} catch (error) {\n\t\tthrow failed(ctx.name, 'reading its resume point', error);\n\t}\n}\n\n/** Forgets everything recorded: the next `start` reindexes. */\nexport async function clearState(ctx: SyncContext): Promise<void> {\n\ttry {\n\t\tawait ctx.state.deleteOne({ _id: ctx.name });\n\t} catch (error) {\n\t\tthrow failed(ctx.name, 'forgetting its resume point', error);\n\t}\n}\n\n/** Moves the resume point, and says a reindex finished when one did. */\nexport async function saveState(\n\tctx: SyncContext,\n\tresumeToken: ResumeToken,\n\treindexed: boolean,\n): Promise<void> {\n\tconst now = new Date();\n\ttry {\n\t\tawait ctx.state.updateOne(\n\t\t\t{ _id: ctx.name },\n\t\t\t{\n\t\t\t\t$set: {\n\t\t\t\t\tresumeToken,\n\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\t...(reindexed ? { reindexedAt: now } : {}),\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ upsert: true },\n\t\t);\n\t} catch (error) {\n\t\tthrow failed(ctx.name, 'recording its resume point', error);\n\t}\n}\n",
13
13
  "import type {\n\tAnyCollectionDefinition,\n\tChangeOf,\n\tChangeSubscription,\n\tCloseReason,\n\tResumeToken,\n} from '@nxgt/mongo';\nimport { send } from './batch';\nimport type { Doc, SyncContext } from './context';\nimport { type Entry, entryOf } from './documents';\nimport { failed, type SearchSyncError } from './errors';\nimport { type HeldLease, keepLease, leaseLost, release } from './lease';\nimport { clearState, saveState } from './state';\nimport type { RunningSearchSync } from './types';\n\nconst noop = () => undefined;\n\n/** What a running sync holds between changes. */\ninterface Following {\n\treadonly pending: Map<string, Entry>;\n\t/** The token of the last change in `pending`. */\n\tlast: ResumeToken | undefined;\n\tsaved: ResumeToken;\n\t/** Changes whose handler has not queued them yet. */\n\tinFlight: number;\n\ttimer: ReturnType<typeof setTimeout> | undefined;\n\t/** Records where the stream is while nothing is being sent. */\n\tbeat: ReturnType<typeof setInterval> | undefined;\n\t/** Stops renewing the lease on the sync's name. */\n\tstopLease: () => void;\n\t/**\n\t * The stream was heard. Only then does `closed` let go of the lease: a\n\t * stream that never got ready leaves it to `start`, which may reindex and\n\t * open again on it after a lost history.\n\t */\n\theard: boolean;\n\t/** Flushes run one after the other. */\n\tchain: Promise<void>;\n\t/** The stream has stopped: what is queued no longer records anything. */\n\tdone: boolean;\n\tfailure: SearchSyncError | undefined;\n}\n\n/**\n * Sends what is pending, then records the token that covers it — or, with\n * nothing to send and no change being handled, where the stream is: a quiet\n * collection would otherwise keep a resume point until the server's history\n * no longer reaches it.\n */\nfunction flush(\n\tctx: SyncContext,\n\tf: Following,\n\tsubscription: ChangeSubscription,\n): Promise<void> {\n\tf.chain = f.chain.then(async () => {\n\t\tif (f.failure) throw f.failure;\n\t\tif (f.done) return;\n\t\tclearTimeout(f.timer);\n\t\tf.timer = undefined;\n\t\tconst entries = [...f.pending.values()];\n\t\tconst upTo = f.last;\n\t\tf.pending.clear();\n\t\ttry {\n\t\t\tif (entries.length > 0) await send(ctx, entries);\n\t\t\tconst idle = entries.length === 0 && f.inFlight === 0;\n\t\t\tconst at = upTo ?? (idle ? subscription.position : undefined);\n\t\t\tif (at && at !== f.saved) {\n\t\t\t\tawait saveState(ctx, at, false);\n\t\t\t\tf.saved = at;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tf.failure ??= failed(ctx.name, 'sending changes', error);\n\t\t\tthrow f.failure;\n\t\t}\n\t});\n\treturn f.chain;\n}\n\n/** A sync that has not heard anything yet, from `token`. */\nfunction following(token: ResumeToken): Following {\n\treturn {\n\t\tpending: new Map(),\n\t\tlast: undefined,\n\t\tsaved: token,\n\t\tinFlight: 0,\n\t\ttimer: undefined,\n\t\tbeat: undefined,\n\t\tstopLease: noop,\n\t\theard: false,\n\t\tchain: Promise.resolve(),\n\t\tdone: false,\n\t\tfailure: undefined,\n\t};\n}\n\n/** Stops everything a running sync does on its own. */\nfunction stopFollowing(ctx: SyncContext, f: Following): void {\n\tf.done = true;\n\tclearTimeout(f.timer);\n\tclearInterval(f.beat);\n\tf.stopLease();\n\tctx.follower.running = false;\n}\n\n/**\n * What `closed` settles to once the stream stops: the reason, or the failure\n * that stopped it — after the last queued flush, and with the lease let go.\n */\nfunction settle(\n\tctx: SyncContext,\n\tf: Following,\n\tsubscription: ChangeSubscription,\n\tlease: HeldLease,\n): Promise<CloseReason> {\n\tconst letGo = () => (f.heard ? release(ctx, lease) : Promise.resolve());\n\treturn subscription.closed.then(\n\t\tasync (reason) => {\n\t\t\tstopFollowing(ctx, f);\n\t\t\ttry {\n\t\t\t\t// A flush the timer or the beat had queued would otherwise record\n\t\t\t\t// a point after this.\n\t\t\t\tawait f.chain.catch(noop);\n\t\t\t\tif (f.failure) throw f.failure;\n\t\t\t\t// The collection is gone, and so is the point the stream stopped\n\t\t\t\t// at: what is recorded is older, and every later start would stop\n\t\t\t\t// here again. Forgetting it makes the next one reindex.\n\t\t\t\tif (reason === 'invalidated') await clearState(ctx);\n\t\t\t\treturn reason;\n\t\t\t} finally {\n\t\t\t\tawait letGo();\n\t\t\t}\n\t\t},\n\t\tasync (error: unknown) => {\n\t\t\tstopFollowing(ctx, f);\n\t\t\tawait letGo();\n\t\t\tthrow f.failure ?? failed(ctx.name, 'following changes', error);\n\t\t},\n\t);\n}\n\nexport async function open(\n\tctx: SyncContext,\n\ttoken: ResumeToken,\n\tlease: HeldLease,\n): Promise<RunningSearchSync> {\n\tconst f = following(token);\n\tconst handle = async (change: ChangeOf<AnyCollectionDefinition>) => {\n\t\tif (f.failure) throw f.failure;\n\t\tf.inFlight += 1;\n\t\tlet entry: Entry;\n\t\ttry {\n\t\t\tconst document =\n\t\t\t\tchange.type === 'delete' ? undefined : (change.document as Doc);\n\t\t\tentry = await entryOf(ctx, change.id, document);\n\t\t} catch (error) {\n\t\t\t// Kept, so `flush` and `close` report it as `closed` does.\n\t\t\tf.failure = failed(ctx.name, 'following changes', error);\n\t\t\tthrow f.failure;\n\t\t} finally {\n\t\t\tf.inFlight -= 1;\n\t\t}\n\t\tf.pending.set(entry.key, entry);\n\t\tf.last = change.resumeToken;\n\t\tif (f.pending.size >= ctx.batchSize) {\n\t\t\tawait flush(ctx, f, subscription);\n\t\t} else if (!f.timer) {\n\t\t\tf.timer = setTimeout(() => {\n\t\t\t\tflush(ctx, f, subscription).catch(() => subscription.close());\n\t\t\t}, ctx.flushIntervalMs);\n\t\t}\n\t};\n\tconst subscription = ctx.collection.onChange(handle, { startAfter: token });\n\tf.beat = setInterval(() => {\n\t\tflush(ctx, f, subscription).catch(() => subscription.close());\n\t}, ctx.positionIntervalMs);\n\t// A lease found gone means another process may be following this name:\n\t// stop at once, before anything else is sent or recorded beside it.\n\tf.stopLease = keepLease(ctx, lease, () => {\n\t\tif (f.failure) return;\n\t\tf.failure = leaseLost(ctx);\n\t\tvoid subscription.close();\n\t});\n\tconst closed = settle(ctx, f, subscription, lease);\n\ttry {\n\t\tawait subscription.ready;\n\t} catch (error) {\n\t\tstopFollowing(ctx, f);\n\t\tclosed.catch(noop);\n\t\tthrow error;\n\t}\n\tf.heard = true;\n\tctx.follower.running = true;\n\tconst close = async () => {\n\t\ttry {\n\t\t\tawait flush(ctx, f, subscription);\n\t\t} finally {\n\t\t\tawait subscription.close();\n\t\t\t// The lease is let go in `closed`'s handler: waited for, so a\n\t\t\t// `reindex` or a `start` right after this finds the name free. Not\n\t\t\t// from inside a change being handled, which `closed` waits for.\n\t\t\tif (f.inFlight === 0) await closed.then(noop, noop);\n\t\t}\n\t};\n\treturn {\n\t\tready: subscription.ready,\n\t\tclosed,\n\t\tflush: () => flush(ctx, f, subscription),\n\t\tclose,\n\t\t[Symbol.asyncDispose]: close,\n\t};\n}\n",
14
14
  "import type { ResumeToken } from '@nxgt/mongo';\nimport type { SyncContext } from './context';\nimport { failed, SearchSyncError } from './errors';\nimport { open } from './follow';\nimport {\n\tacquire,\n\tconfirmLease,\n\ttype HeldLease,\n\tkeepLease,\n\trelease,\n} from './lease';\nimport { reindexHeld } from './reindex';\nimport { checkIdle } from './running';\nimport { readState } from './state';\nimport type { RunningSearchSync } from './types';\n\n/** The server no longer has the history a token points into. */\nfunction historyLost(error: unknown): boolean {\n\tconst code = (error as { serverCode?: number } | undefined)?.serverCode;\n\treturn code === 286 || code === 280;\n}\n\n/**\n * Follows the collection from its recorded point — after a reindex when\n * nothing is recorded, or when the point is older than the server's history\n * and `onHistoryLost` allows it.\n */\nexport async function start(ctx: SyncContext): Promise<RunningSearchSync> {\n\tcheckIdle(ctx, 'start it twice');\n\t// Before anything else, the first reindex included: two processes\n\t// starting at once would otherwise both reindex, and both follow.\n\tconst lease = await acquire(ctx, 'start it');\n\t// Renewed while it reindexes, which can outlast `leaseMs`; the follower\n\t// renews it on its own once it is open.\n\tconst stop = keepLease(ctx, lease, () => undefined);\n\ttry {\n\t\tconst running = await startHeld(ctx, lease);\n\t\tstop();\n\t\treturn running;\n\t} catch (error) {\n\t\tstop();\n\t\tawait release(ctx, lease);\n\t\tthrow error;\n\t}\n}\n\n/** Opens the follower, once the server confirms the lease is still held. */\nasync function openHeld(\n\tctx: SyncContext,\n\ttoken: ResumeToken,\n\tlease: HeldLease,\n): Promise<RunningSearchSync> {\n\tawait confirmLease(ctx, lease);\n\treturn open(ctx, token, lease);\n}\n\nasync function startHeld(\n\tctx: SyncContext,\n\tlease: HeldLease,\n): Promise<RunningSearchSync> {\n\tlet state = await readState(ctx);\n\tif (!state) {\n\t\tawait reindexHeld(ctx, lease);\n\t\tstate = await readState(ctx);\n\t}\n\ttry {\n\t\treturn await openHeld(\n\t\t\tctx,\n\t\t\t(state as { resumeToken: ResumeToken }).resumeToken,\n\t\t\tlease,\n\t\t);\n\t} catch (error) {\n\t\tif (!historyLost(error)) throw failed(ctx.name, 'starting', error);\n\t\tif (ctx.onHistoryLost === 'fail') {\n\t\t\tthrow new SearchSyncError(\n\t\t\t\t`Search sync \"${ctx.name}\" was last at a point the server's change ` +\n\t\t\t\t\t'history no longer reaches. Reindex it, or start it with ' +\n\t\t\t\t\t\"onHistoryLost: 'reindex'.\",\n\t\t\t\t{ code: 'HISTORY_LOST', sync: ctx.name, cause: error },\n\t\t\t);\n\t\t}\n\t\tawait reindexHeld(ctx, lease);\n\t\tconst fresh = (await readState(ctx)) as { resumeToken: ResumeToken };\n\t\ttry {\n\t\t\treturn await openHeld(ctx, fresh.resumeToken, lease);\n\t\t} catch (again) {\n\t\t\tthrow failed(ctx.name, 'starting', again);\n\t\t}\n\t}\n}\n",
15
15
  "import type { AnyIndexDefinition } from '@nxgt/meilisearch';\nimport type { AnyCollectionDefinition } from '@nxgt/mongo';\nimport { createContext } from './context';\nimport { reindex } from './reindex';\nimport { start } from './start';\nimport { readState } from './state';\nimport type { SearchSync, SearchSyncOptions } from './types';\n\n/**\n * Keeps an index in step with a collection.\n *\n * ```ts\n * const usersSearch = createSearchSync({\n * \tcollection: getCollection(db, users),\n * \tindex: bindIndex(meili, userIndex),\n * \ttransform: (user) => (user.active ? { id: user.id, name: user.name } : null),\n * });\n * const running = await usersSearch.start(); // reindexes the first time\n * // …\n * await running.close();\n * ```\n *\n * Nothing is sent before `reindex` or `start`.\n */\nexport function createSearchSync<\n\tC extends AnyCollectionDefinition,\n\tI extends AnyIndexDefinition,\n>(options: SearchSyncOptions<C, I>): SearchSync {\n\t// The one place the caller's types are let go: a method taking `IdOf<C>`\n\t// is not one taking any id, so the widening cannot be implicit. Below\n\t// here, documents are records and ids are values.\n\tconst ctx = createContext(\n\t\toptions as unknown as SearchSyncOptions<\n\t\t\tAnyCollectionDefinition,\n\t\t\tAnyIndexDefinition\n\t\t>,\n\t);\n\treturn {\n\t\tname: ctx.name,\n\t\treindex: () => reindex(ctx),\n\t\tstart: () => start(ctx),\n\t\tstate: () => readState(ctx),\n\t};\n}\n"
16
16
  ],
17
- "mappings": "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",
18
- "debugId": "663F5145BB58E86F64756E2164756E21",
17
+ "mappings": "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",
18
+ "debugId": "FCC6FD234D55BAD364756E2164756E21",
19
19
  "names": []
20
20
  }
package/dist/lease.d.ts CHANGED
@@ -27,8 +27,10 @@ export interface HeldLease {
27
27
  }
28
28
  /**
29
29
  * Takes the lease, or throws `RUNNING` naming who holds it and until when —
30
- * the same code a second `start` in this process gets, since it means the
31
- * same thing: this name is being followed.
30
+ * in the message and as `holder` and `expiresAt`, so a standby can wait for
31
+ * that moment rather than a fixed time. The same code a second `start` in
32
+ * this process gets, since it means the same thing: this name is being
33
+ * followed.
32
34
  */
33
35
  export declare function acquire(ctx: SyncContext, doing: string): Promise<HeldLease>;
34
36
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"lease.d.ts","sourceRoot":"","sources":["../src/lease.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAU,eAAe,EAAE,MAAM,UAAU,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa;IAC7B,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,IAAI,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;CAChB;AAED,+DAA+D;AAC/D,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;CACd;AA+CD;;;;GAIG;AACH,wBAAsB,OAAO,CAC5B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,SAAS,CAAC,CAwBpB;AAED;;;;;GAKG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAU7E;AAED;;;GAGG;AACH,wBAAsB,YAAY,CACjC,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,SAAS,GACd,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,0EAA0E;AAC1E,wBAAsB,OAAO,CAC5B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,SAAS,GACd,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,GAAG,eAAe,CAO3D;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACxB,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,IAAI,GAChB,MAAM,IAAI,CAkBZ;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAChC,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CASZ"}
1
+ {"version":3,"file":"lease.d.ts","sourceRoot":"","sources":["../src/lease.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAU,eAAe,EAAE,MAAM,UAAU,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa;IAC7B,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,IAAI,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;CAChB;AAED,+DAA+D;AAC/D,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;CACd;AA+CD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC5B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,SAAS,CAAC,CAgCpB;AAED;;;;;GAKG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAU7E;AAED;;;GAGG;AACH,wBAAsB,YAAY,CACjC,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,SAAS,GACd,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,0EAA0E;AAC1E,wBAAsB,OAAO,CAC5B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,SAAS,GACd,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,GAAG,eAAe,CAO3D;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACxB,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,IAAI,GAChB,MAAM,IAAI,CAkBZ;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAChC,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CASZ"}
package/dist/running.d.ts CHANGED
@@ -3,6 +3,10 @@ import type { SyncContext } from './context';
3
3
  * Refuses to do beside a running follower what would undo its work: a
4
4
  * reindex removes what the follower has just indexed, and the follower,
5
5
  * having handled that change, never sends it again.
6
+ *
7
+ * Its `RUNNING` carries no `holder` and no `expiresAt`: the lease is this
8
+ * process's own and renewed for as long as it follows, so there is no time to
9
+ * wait for — only a `close()` frees the name.
6
10
  */
7
11
  export declare function checkIdle(ctx: SyncContext, doing: string): void;
8
12
  //# sourceMappingURL=running.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"running.d.ts","sourceRoot":"","sources":["../src/running.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAO/D"}
1
+ {"version":3,"file":"running.d.ts","sourceRoot":"","sources":["../src/running.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAO/D"}
@@ -96,6 +96,31 @@ is checked first. The holder is `host:pid:<id>`, and the date is when its
96
96
  lease ends unless it is renewed. For `reindex()` the message ends
97
97
  `before you reindex.`
98
98
 
99
+ Both are on the error too, read from the lease document, so a caller waits
100
+ for that moment instead of guessing:
101
+
102
+ ```ts
103
+ import { SearchSyncError } from '@nxgt/mongo-meilisearch';
104
+
105
+ try {
106
+ await articleSearch.start();
107
+ } catch (error) {
108
+ if (!(error instanceof SearchSyncError) || error.code !== 'RUNNING') throw error;
109
+ error.holder; // 'worker-1:4127:66f0c2e5a1b2c3d4e5f60718'
110
+ error.expiresAt; // 2026-09-22T09:14:07.512Z, a Date on MongoDB's clock
111
+ }
112
+ ```
113
+
114
+ | Field | Type | Set when |
115
+ | --- | --- | --- |
116
+ | `holder` | `string \| undefined` | the lease refused the call: who holds the name, as that process described itself. A label for a log — do not parse it |
117
+ | `expiresAt` | `Date \| undefined` | with `holder`: when that lease lapses if its holder stops renewing it. A live holder renews it every `leaseMs / 3`, so trying again at that time can be refused again, with a later one |
118
+
119
+ Both are `undefined` on the sync object's own refusal — the name is this
120
+ process's, renewed for as long as it follows, so there is no time to wait for,
121
+ only a `close()` — on every other code, and when the holder let go between
122
+ the refusal and the read of its lease.
123
+
99
124
  | Option | Type | Default | Effect |
100
125
  | --- | --- | --- | --- |
101
126
  | `leaseMs` | `number` | `30000` | How long the lease lasts without being renewed. A running sync renews it every `leaseMs / 3`. A whole number above 0 |
@@ -125,12 +150,20 @@ Several processes can run the same code; one follows, the others wait for
125
150
  the name. The loop is `follow()`, in the README's
126
151
  [One process per sync name](../../README.md#one-process-per-sync-name): it
127
152
  retries `start()` on `RUNNING`, and on `LEASE_LOST`, which a `start()` whose
128
- first reindex lost the name rejects with.
153
+ first reindex lost the name rejects with. Each time it waits until the
154
+ error's `expiresAt`, plus a margin for a host clock ahead of MongoDB's, or a
155
+ fixed pause when there is none — a `LEASE_LOST` has none. A host further
156
+ ahead is refused again, and retries every 250 ms until the lease lapses.
129
157
 
130
158
  ```ts
131
159
  const running = await follow(); // resolves once this process holds the name
132
160
  ```
133
161
 
162
+ A holder that died is taken over as soon as its lease lapses, not a fixed
163
+ pause after. One that is alive has renewed its lease by then, so the standby
164
+ is refused again with a later `expiresAt` and waits once more: about one lease
165
+ per try, one `start()` against MongoDB each time.
166
+
134
167
  ### When the lease is lost
135
168
 
136
169
  A process stalled for longer than `leaseMs` — a long garbage collection, a
package/docs/roadmap.md CHANGED
@@ -32,6 +32,11 @@ _Nothing queued._
32
32
 
33
33
  ## Shipped
34
34
 
35
+ - **A standby waits exactly for the holder** — a `RUNNING` the lease refused
36
+ carries `holder` and `expiresAt`, read from the lease document, so a process
37
+ waiting for the name sleeps until the holder's lease lapses instead of a
38
+ fixed time; both are `undefined` on a sync object's own refusal, where only
39
+ its `close()` frees the name — 0.4.0.
35
40
  - **A lease on a sync name, renewed while it runs** — `start()` and
36
41
  `reindex()` take a lease on the sync's name, kept in the state collection
37
42
  and timed by the server, renewed every third of the new `leaseMs` option
@@ -223,7 +223,8 @@ Code `RUNNING`. `reindex()` on a sync that is following ends
223
223
  `… before you reindex.`
224
224
 
225
225
  **When:** a second `start()`, or a `reindex()`, on a sync object that is
226
- already following.
226
+ already following. Its `holder` and `expiresAt` are `undefined`: the name is
227
+ this object's own, so there is no lease to wait out, only a `close()`.
227
228
 
228
229
  **Why:** a reindex removes what the index holds and the collection no longer
229
230
  gives it — including the documents the running follower has just indexed, which
@@ -259,13 +260,16 @@ MongoDB server's clock, so hosts whose clocks disagree still agree on it.
259
260
 
260
261
  **Fix:** run one follower per name. A second replica can stand by, retrying
261
262
  `start()` until the name is free — on `RUNNING`, and on `LEASE_LOST`, which a
262
- `start()` whose first reindex lost the name to another process rejects with:
263
+ `start()` whose first reindex lost the name to another process rejects with.
264
+ The error carries the holder as `holder` and the end of its lease as
265
+ `expiresAt` (a `Date`, from the lease document), so the standby waits until
266
+ then rather than a fixed time:
263
267
 
264
268
  ```ts
265
269
  import { type RunningSearchSync, SearchSyncError } from '@nxgt/mongo-meilisearch';
266
270
 
267
271
  // The name is held elsewhere, or was taken over while this start reindexed.
268
- const heldElsewhere = (error: unknown) =>
272
+ const heldElsewhere = (error: unknown): error is SearchSyncError =>
269
273
  error instanceof SearchSyncError &&
270
274
  (error.code === 'RUNNING' || error.code === 'LEASE_LOST');
271
275
 
@@ -275,12 +279,24 @@ async function follow(): Promise<RunningSearchSync> {
275
279
  return await articleSearch.start();
276
280
  } catch (error) {
277
281
  if (!heldElsewhere(error)) throw error;
278
- await new Promise((resolve) => setTimeout(resolve, 10_000));
282
+ // Until the holder's lease lapses when it is known, 10 s when not;
283
+ // the margin covers a host clock ahead of MongoDB's.
284
+ const until = error.expiresAt?.getTime() ?? Date.now() + 10_000;
285
+ const wait = Math.max(until - Date.now(), 0) + 250;
286
+ await new Promise((resolve) => setTimeout(resolve, wait));
279
287
  }
280
288
  }
281
289
  }
282
290
  ```
283
291
 
292
+ A live holder renews its lease every third of `leaseMs`, so the next try is
293
+ refused again with a later `expiresAt`; a dead one is taken over as soon as
294
+ its lease lapses. `expiresAt` is `undefined` on a `LEASE_LOST`, and on a
295
+ `RUNNING` whose holder let go before its lease could be read; the loop then
296
+ falls back to its fixed pause. The 250 ms margin covers a host clock ahead of
297
+ MongoDB's; a host further ahead is refused again, and retries every 250 ms
298
+ until the lease lapses.
299
+
284
300
  Close the sync on shutdown (`await running.close()` on `SIGTERM`): that lets go
285
301
  of the name at once, where a killed process leaves it held until its lease
286
302
  lapses. To see who holds it, read `{ _id: { lease: 'articles:articles' } }` in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/mongo-meilisearch",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Keeps a Meilisearch index in step with a MongoDB collection: a typed transform, a full reindex, and a change stream that resumes where it stopped",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -49,7 +49,7 @@
49
49
  ]
50
50
  },
51
51
  "devDependencies": {
52
- "@nxgt/meilisearch": "^0.4.1",
52
+ "@nxgt/meilisearch": "^0.5.0",
53
53
  "@nxgt/mongo": "^0.17.1",
54
54
  "@types/bun": "^1.4.0",
55
55
  "meilisearch": "0.62.0",
@@ -58,7 +58,7 @@
58
58
  "zod": "4.6.5"
59
59
  },
60
60
  "peerDependencies": {
61
- "@nxgt/meilisearch": "^0.4.1",
61
+ "@nxgt/meilisearch": "^0.5.0",
62
62
  "@nxgt/mongo": "^0.17.1",
63
63
  "meilisearch": ">=0.62.0 <1",
64
64
  "mongodb": ">=7.0.0 <8",