@nxgt/mongo-meilisearch 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/documents.d.ts.map +1 -1
- package/dist/errors.d.ts +2 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +4 -4
- package/docs/guide/following-changes.md +3 -1
- package/docs/guide/reindex.md +13 -3
- package/docs/guide/sync-lifecycle.md +3 -1
- package/docs/roadmap.md +5 -0
- package/docs/troubleshooting.md +15 -8
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -192,7 +192,8 @@ createSearchSync({
|
|
|
192
192
|
|
|
193
193
|
The transform must give each document its index id as primary key: a
|
|
194
194
|
document under another id could never be taken out again, and throws
|
|
195
|
-
`ID_MISMATCH`.
|
|
195
|
+
`ID_MISMATCH`. It must also give back a document, or `null` to keep the
|
|
196
|
+
document out of the index; anything else is `NOT_A_DOCUMENT`.
|
|
196
197
|
|
|
197
198
|
## Errors
|
|
198
199
|
|
|
@@ -202,6 +203,7 @@ This package throws `SearchSyncError`; what caused it is its `cause`.
|
|
|
202
203
|
| --- | --- |
|
|
203
204
|
| `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 |
|
|
204
205
|
| `ID_MISMATCH` | the transform gave a document whose primary key is not its index id |
|
|
206
|
+
| `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 |
|
|
205
207
|
| `RUNNING` | `reindex()` or a second `start()` while this sync is already following changes in this process. A reindex beside its own follower would remove what the follower has just indexed |
|
|
206
208
|
| `FAILED` | anything else: the transform threw, MongoDB or Meilisearch refused. The message says what the sync was doing |
|
|
207
209
|
|
|
@@ -281,7 +283,8 @@ function createSearchSync<C extends AnyCollectionDefinition, I extends AnyIndexD
|
|
|
281
283
|
| `close(): Promise<void>` | flushes, then stops |
|
|
282
284
|
|
|
283
285
|
`class SearchSyncError extends Error`: `code: SearchSyncErrorCode`
|
|
284
|
-
(`'HISTORY_LOST' | 'ID_MISMATCH' | 'RUNNING' | 'FAILED'`),
|
|
286
|
+
(`'HISTORY_LOST' | 'ID_MISMATCH' | 'NOT_A_DOCUMENT' | 'RUNNING' | 'FAILED'`),
|
|
287
|
+
`sync: string`,
|
|
285
288
|
`cause`. Its constructor takes `(message, options: SearchSyncErrorOptions)`,
|
|
286
289
|
that is `{ code, sync, cause? }`; both types are exported.
|
|
287
290
|
|
package/dist/documents.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGlD,4DAA4D;AAC5D,MAAM,MAAM,KAAK,GACd;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAA;CAAE,CAAC;AAEhD,sEAAsE;AACtE,eAAO,MAAM,KAAK,GAAI,IAAI,OAAO,WAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGlD,4DAA4D;AAC5D,MAAM,MAAM,KAAK,GACd;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAA;CAAE,CAAC;AAEhD,sEAAsE;AACtE,eAAO,MAAM,KAAK,GAAI,IAAI,OAAO,WAAiC,CAAC;AAYnE;;;GAGG;AACH,wBAAsB,OAAO,CAC5B,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,GAAG,GAAG,SAAS,GACvB,OAAO,CAAC,KAAK,CAAC,CA6BhB"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type SearchSyncErrorCode =
|
|
|
4
4
|
'HISTORY_LOST'
|
|
5
5
|
/** The transform gave a document whose primary key is not its index id. */
|
|
6
6
|
| 'ID_MISMATCH'
|
|
7
|
+
/** The transform gave something that is neither a document nor `null`. */
|
|
8
|
+
| 'NOT_A_DOCUMENT'
|
|
7
9
|
/** This sync is already following changes in this process. */
|
|
8
10
|
| 'RUNNING'
|
|
9
11
|
/** Anything else: the cause says what. */
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -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,8DAA8D;GAC5D,SAAS;AACX,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,8DAA8D;GAC5D,SAAS;AACX,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"}
|
package/dist/index.js
CHANGED
|
@@ -92,6 +92,13 @@ function failed(sync, doing, error) {
|
|
|
92
92
|
// src/documents.ts
|
|
93
93
|
var keyOf = (id) => `${typeof id}:${String(id)}`;
|
|
94
94
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
95
|
+
function describe(value) {
|
|
96
|
+
if (Array.isArray(value))
|
|
97
|
+
return "an array";
|
|
98
|
+
if (value === undefined)
|
|
99
|
+
return "undefined";
|
|
100
|
+
return `a ${typeof value}`;
|
|
101
|
+
}
|
|
95
102
|
async function entryOf(ctx, mongoId, document) {
|
|
96
103
|
const id = ctx.toIndexId(mongoId);
|
|
97
104
|
const key = keyOf(id);
|
|
@@ -101,7 +108,7 @@ async function entryOf(ctx, mongoId, document) {
|
|
|
101
108
|
if (indexed === null)
|
|
102
109
|
return { kind: "delete", key, id };
|
|
103
110
|
if (!isRecord(indexed)) {
|
|
104
|
-
throw new
|
|
111
|
+
throw new SearchSyncError(`Search sync "${ctx.name}": transform gave ${describe(indexed)} for ` + `the document ${String(mongoId)}. It must give a document to ` + "index, or null to keep it out.", { code: "NOT_A_DOCUMENT", sync: ctx.name });
|
|
105
112
|
}
|
|
106
113
|
const given = indexed[ctx.primaryKey];
|
|
107
114
|
if (keyOf(given) !== key) {
|
|
@@ -379,5 +386,5 @@ export {
|
|
|
379
386
|
createSearchSync
|
|
380
387
|
};
|
|
381
388
|
|
|
382
|
-
//# debugId=
|
|
389
|
+
//# debugId=1220BC8109B997D064756E2164756E21
|
|
383
390
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import type { AnyIndexDefinition, TypedIndex } from '@nxgt/meilisearch';\nimport type { AnyCollectionDefinition, TypedCollection } from '@nxgt/mongo';\nimport type { Collection } from 'mongodb';\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\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\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>(\n\t\t\toptions.stateCollection ?? DEFAULT_STATE_COLLECTION,\n\t\t),\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 { 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",
|
|
7
|
-
"/** 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/** This sync is already following changes in this process. */\n\t| 'RUNNING'\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
|
-
"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/**\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\tthrow new
|
|
7
|
+
"/** 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/** This sync is already following changes in this process. */\n\t| 'RUNNING'\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
|
+
"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",
|
|
9
9
|
"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 { 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(ctx: SyncContext, wanted: Set<string>) {\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\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\nexport async function reindex(ctx: SyncContext): Promise<ReindexReport> {\n\tcheckIdle(ctx, 'reindex');\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);\n\t\tconst removed = await removeUnwanted(ctx, wanted);\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",
|
|
10
10
|
"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",
|
|
11
11
|
"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",
|
|
12
12
|
"import type {\n\tAnyCollectionDefinition,\n\tChangeOf,\n\tChangeSubscription,\n\tResumeToken,\n} from '@nxgt/mongo';\nimport { send } from './batch';\nimport type { Doc, SyncContext } from './context';\nimport { type Entry, entryOf } from './documents';\nimport { failed, SearchSyncError } from './errors';\nimport { reindex } from './reindex';\nimport { checkIdle } from './running';\nimport { clearState, readState, saveState } 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/** 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/** 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\nasync function open(\n\tctx: SyncContext,\n\ttoken: ResumeToken,\n): Promise<RunningSearchSync> {\n\tconst f: Following = {\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\tchain: Promise.resolve(),\n\t\tdone: false,\n\t\tfailure: undefined,\n\t};\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\tconst stop = () => {\n\t\tf.done = true;\n\t\tclearTimeout(f.timer);\n\t\tclearInterval(f.beat);\n\t\tctx.follower.running = false;\n\t};\n\tconst closed = subscription.closed.then(\n\t\tasync (reason) => {\n\t\t\tstop();\n\t\t\t// A flush the timer or the beat had queued would otherwise record a\n\t\t\t// point after this.\n\t\t\tawait f.chain.catch(() => undefined);\n\t\t\tif (f.failure) throw f.failure;\n\t\t\t// The collection is gone, and so is the point the stream stopped at:\n\t\t\t// what is recorded is older, and every later start would stop here\n\t\t\t// again. Forgetting it makes the next one reindex.\n\t\t\tif (reason === 'invalidated') await clearState(ctx);\n\t\t\treturn reason;\n\t\t},\n\t\t(error: unknown) => {\n\t\t\tstop();\n\t\t\tthrow f.failure ?? failed(ctx.name, 'following changes', error);\n\t\t},\n\t);\n\ttry {\n\t\tawait subscription.ready;\n\t} catch (error) {\n\t\tstop();\n\t\tclosed.catch(() => undefined);\n\t\tthrow error;\n\t}\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}\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\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\tlet state = await readState(ctx);\n\tif (!state) {\n\t\tawait reindex(ctx);\n\t\tstate = await readState(ctx);\n\t}\n\ttry {\n\t\treturn await open(ctx, (state as { resumeToken: ResumeToken }).resumeToken);\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 reindex(ctx);\n\t\tconst fresh = (await readState(ctx)) as { resumeToken: ResumeToken };\n\t\ttry {\n\t\t\treturn await open(ctx, fresh.resumeToken);\n\t\t} catch (again) {\n\t\t\tthrow failed(ctx.name, 'starting', again);\n\t\t}\n\t}\n}\n",
|
|
13
13
|
"import type { AnyIndexDefinition } from '@nxgt/meilisearch';\nimport type { AnyCollectionDefinition } from '@nxgt/mongo';\nimport { createContext } from './context';\nimport { start } from './follow';\nimport { reindex } from './reindex';\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"
|
|
14
14
|
],
|
|
15
|
-
"mappings": ";AAiCA,IAAM,2BAA2B;AAEjC,SAAS,QAAQ,CAAC,QAAgB,OAA2B,UAAkB;AAAA,EAC9E,MAAM,WAAW,SAAS;AAAA,EAC1B,IAAI,CAAC,OAAO,UAAU,QAAQ,KAAK,WAAW,GAAG;AAAA,IAChD,MAAM,IAAI,UACT,qBAAqB,8CAA8C,OAAO,KAAK,GAChF;AAAA,EACD;AAAA,EACA,OAAO;AAAA;AAGD,SAAS,aAAa,CAC5B,SACc;AAAA,EACd,QAAQ,YAAY,UAAU;AAAA,EAC9B,IAAI,OAAO,QAAQ,cAAc,YAAY;AAAA,IAC5C,MAAM,IAAI,UAAU,gDAAgD;AAAA,EACrE;AAAA,EACA,MAAM,OAAO,QAAQ,QAAQ,GAAG,WAAW,kBAAkB,MAAM;AAAA,EACnE,IAAI,SAAS,IAAI;AAAA,IAChB,MAAM,IAAI,UAAU,0CAA0C;AAAA,EAC/D;AAAA,EACA,MAAM,kBAAkB,QAAQ,mBAAmB;AAAA,EACnD,IAAI,CAAC,OAAO,UAAU,eAAe,KAAK,kBAAkB,GAAG;AAAA,IAC9D,MAAM,IAAI,UACT,iFAAiF,OAAO,QAAQ,eAAe,GAChH;AAAA,EACD;AAAA,EACA,OAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,MAAM,WAAW;AAAA,IAC7B,OAAO,WAAW,GAAG,WACpB,QAAQ,mBAAmB,wBAC5B;AAAA,IACA,WAAW,SAAS,aAAa,QAAQ,WAAW,GAAG;AAAA,IACvD,oBAAoB,SACnB,sBACA,QAAQ,oBACR,KACD;AAAA,IACA;AAAA,IACA,UAAU,SAAS,YAAY,QAAQ,UAAU,GAAG;AAAA,IACpD,eAAe,QAAQ,iBAAiB;AAAA,IACxC,WAAW,QAAQ;AAAA,IACnB,WAAY,QAAQ,aAAa;AAAA,IACjC,UAAU,EAAE,SAAS,MAAM;AAAA,EAC5B;AAAA;;;AC3ED,IAAM,OAAO,EAAE,MAAM,EAAE,SAAS,OAAQ,EAAE;AAE1C,SAAS,MAAS,CAAC,OAAqB,MAAqB;AAAA,EAC5D,MAAM,MAAa,CAAC;AAAA,EACpB,SAAS,IAAI,EAAG,IAAI,MAAM,QAAQ,KAAK,MAAM;AAAA,IAC5C,IAAI,KAAK,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC;AAAA,EAClC;AAAA,EACA,OAAO;AAAA;AAIR,eAAsB,aAAa,CAClC,KACA,WACgB;AAAA,EAChB,WAAW,SAAS,OAAO,WAAW,IAAI,SAAS,GAAG;AAAA,IACrD,MAAM,IAAI,MAAM,IAAI,OAAO,IAAI;AAAA,EAChC;AAAA;AAID,eAAsB,SAAS,CAC9B,KACA,KACgB;AAAA,EAChB,WAAW,SAAS,OAAO,KAAK,IAAI,SAAS,GAAG;AAAA,IAC/C,MAAM,IAAI,MAAM,OAAO,OAAO,IAAI;AAAA,EACnC;AAAA;AAOD,eAAsB,IAAI,CACzB,KACA,SACgB;AAAA,EAChB,MAAM,YAAmB,CAAC;AAAA,EAC1B,MAAM,MAAiB,CAAC;AAAA,EACxB,WAAW,SAAS,SAAS;AAAA,IAC5B,IAAI,MAAM,SAAS;AAAA,MAAU,UAAU,KAAK,MAAM,QAAQ;AAAA,IACrD;AAAA,UAAI,KAAK,MAAM,EAAE;AAAA,EACvB;AAAA,EACA,MAAM,cAAc,KAAK,SAAS;AAAA,EAClC,MAAM,UAAU,KAAK,GAAG;AAAA;;;
|
|
16
|
-
"debugId": "
|
|
15
|
+
"mappings": "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",
|
|
16
|
+
"debugId": "1220BC8109B997D064756E2164756E21",
|
|
17
17
|
"names": []
|
|
18
18
|
}
|
|
@@ -135,6 +135,7 @@ running.closed.then(
|
|
|
135
135
|
| --- | --- |
|
|
136
136
|
| `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 |
|
|
137
137
|
| `ID_MISMATCH` | the transform gave a document whose primary key is not its index id |
|
|
138
|
+
| `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 |
|
|
138
139
|
| `RUNNING` | a second `start()`, or a `reindex()`, while this sync is already following in this process |
|
|
139
140
|
| `FAILED` | anything else: the transform threw, MongoDB or Meilisearch refused. The message says what the sync was doing, and `cause` carries the original error |
|
|
140
141
|
|
|
@@ -174,7 +175,8 @@ interface RunningSearchSync extends AsyncDisposable {
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
class SearchSyncError extends Error {
|
|
177
|
-
|
|
178
|
+
// 'HISTORY_LOST' | 'ID_MISMATCH' | 'NOT_A_DOCUMENT' | 'RUNNING' | 'FAILED'
|
|
179
|
+
readonly code: SearchSyncErrorCode;
|
|
178
180
|
readonly sync: string;
|
|
179
181
|
constructor(message: string, options: SearchSyncErrorOptions);
|
|
180
182
|
}
|
package/docs/guide/reindex.md
CHANGED
|
@@ -107,9 +107,19 @@ try {
|
|
|
107
107
|
}
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
transform gave a document whose primary key is not
|
|
112
|
-
|
|
110
|
+
Two codes mean something more precise than `FAILED`, and both are the
|
|
111
|
+
transform's doing. `ID_MISMATCH`: it gave a document whose primary key is not
|
|
112
|
+
that document's index id. `NOT_A_DOCUMENT`: it gave back something that is
|
|
113
|
+
neither a document nor `null`.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const transform = (article: Article) =>
|
|
117
|
+
article.draft ? null : { id: String(article._id), title: article.title };
|
|
118
|
+
// Anything else — a string, a number, an array — is NOT_A_DOCUMENT, and the
|
|
119
|
+
// message reports its shape rather than its value.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
[Troubleshooting](../troubleshooting.md) has each message.
|
|
113
123
|
|
|
114
124
|
## Signatures
|
|
115
125
|
|
|
@@ -93,7 +93,9 @@ throws stops the sync.
|
|
|
93
93
|
|
|
94
94
|
The document it returns must carry its **index id** under the index's primary
|
|
95
95
|
key. One under another id could never be taken out again, so it is refused
|
|
96
|
-
with `ID_MISMATCH`.
|
|
96
|
+
with `ID_MISMATCH`. It must return a document or `null`; anything else —
|
|
97
|
+
a string, a number, an array — is refused with `NOT_A_DOCUMENT`, and the
|
|
98
|
+
message reports its shape rather than its value.
|
|
97
99
|
|
|
98
100
|
## Options
|
|
99
101
|
|
package/docs/roadmap.md
CHANGED
|
@@ -35,6 +35,11 @@ _Nothing queued._
|
|
|
35
35
|
|
|
36
36
|
## Shipped
|
|
37
37
|
|
|
38
|
+
- **A transform that gives back something that is not a document has its own
|
|
39
|
+
code** — `SearchSyncErrorCode` gains `NOT_A_DOCUMENT`, naming the sync and
|
|
40
|
+
the document and reporting the shape of what came back, never its value, so
|
|
41
|
+
a transform written wrong is no longer the same `FAILED` a Meilisearch
|
|
42
|
+
outage is — 0.2.0.
|
|
38
43
|
- **Documentation that travels with the package** — a guide page for the
|
|
39
44
|
sync's lifecycle, following a collection's changes, `reindex` and where the
|
|
40
45
|
package's boundaries are, a troubleshooting page whose headings are the
|
package/docs/troubleshooting.md
CHANGED
|
@@ -6,16 +6,16 @@ name is written as it comes out by default — `<collection>:<index uid>`, here
|
|
|
6
6
|
`articles:articles`.
|
|
7
7
|
|
|
8
8
|
Everything this package throws is a `SearchSyncError` carrying a `code`
|
|
9
|
-
(`HISTORY_LOST`, `ID_MISMATCH`, `RUNNING`, `FAILED`), the
|
|
10
|
-
the original error as `cause` — except the options, which
|
|
11
|
-
`TypeError` before anything is opened.
|
|
9
|
+
(`HISTORY_LOST`, `ID_MISMATCH`, `NOT_A_DOCUMENT`, `RUNNING`, `FAILED`), the
|
|
10
|
+
sync's `name`, and the original error as `cause` — except the options, which
|
|
11
|
+
are refused with a `TypeError` before anything is opened.
|
|
12
12
|
|
|
13
13
|
| Area | Entries |
|
|
14
14
|
| --- | --- |
|
|
15
15
|
| [Install](#install) | [ERESOLVE](#npm-error-eresolve-unable-to-resolve-dependency-tree) · [incorrect peer dependency](#warn-incorrect-peer-dependency-nxgtmongo0140) · [TS2307](#error-ts2307-cannot-find-module-nxgtmongo-or-its-corresponding-type-declarations) |
|
|
16
16
|
| [Options](#options) | [transform](#createsearchsync-transform-must-be-a-function) · [batchSize](#createsearchsync-batchsize-must-be-a-whole-number-above-0-not-0) · [flushIntervalMs](#createsearchsync-flushintervalms-must-be-a-whole-number-of-milliseconds-not--1) · [name](#createsearchsync-name-must-not-be-empty) |
|
|
17
17
|
| [Starting](#starting) | [no replica set](#search-sync-articlesarticles-failed-starting-the-changestream-stage-is-only-supported-on-replica-sets) · [privileges](#search-sync-articlesarticles-failed-reindexing-not-authorized-on-app-to-execute-command--aggregate-articles-pipeline---changestream-----) · [history lost](#search-sync-articlesarticles-was-last-at-a-point-the-servers-change-history-no-longer-reaches-reindex-it-or-start-it-with-onhistorylost-reindex) · [already running](#search-sync-articlesarticles-is-already-following-changes-in-this-process-close-it-before-you-start-it-twice) |
|
|
18
|
-
| [The transform](#the-transform) | [an id that is not the index's](#search-sync-articlesarticles-transform-gave-id-other-for-the-document--whose-index-id-is-) · [not a document](#transform-
|
|
18
|
+
| [The transform](#the-transform) | [an id that is not the index's](#search-sync-articlesarticles-transform-gave-id-other-for-the-document--whose-index-id-is-) · [not a document](#search-sync-articlesarticles-transform-gave-a-string-for-the-document-) · [it threw](#search-sync-articlesarticles-failed-following-changes-boom) |
|
|
19
19
|
| [Sending](#sending) | [an id Meilisearch refuses](#search-sync-articlesarticles-failed-sending-changes-task-3-documentadditionorupdate-on-index-articles-failed-document-identifier--is-invalid) |
|
|
20
20
|
| [Stopping](#stopping) | [a dropped collection](#the-sync-stops-and-closed-resolves-with-invalidated) |
|
|
21
21
|
|
|
@@ -269,16 +269,23 @@ createSearchSync({
|
|
|
269
269
|
});
|
|
270
270
|
```
|
|
271
271
|
|
|
272
|
-
### `transform
|
|
272
|
+
### `Search sync "articles:articles": transform gave a string for the document …`
|
|
273
273
|
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
The document's `_id` closes the first sentence, and the message goes on: *It
|
|
275
|
+
must give a document to index, or null to keep it out.* Code
|
|
276
|
+
`NOT_A_DOCUMENT`, with the sync's `name` on it. An array is reported as
|
|
277
|
+
`an array`, and a branch that returns nothing as `undefined`.
|
|
276
278
|
|
|
277
279
|
**When:** `reindex()`, or a change the follower handles.
|
|
278
280
|
|
|
279
281
|
**Why:** the transform gave something that is not a plain object and is not
|
|
280
282
|
`null` — a string, a number, an array, or an implicit `undefined` from a branch
|
|
281
|
-
that returns nothing.
|
|
283
|
+
that returns nothing. It reports the **shape** of what came back and never its
|
|
284
|
+
value: a transform is handed whole documents, and what it gives back can hold
|
|
285
|
+
anything they held. The code is its own since 0.2.0: it used to be a bare
|
|
286
|
+
`TypeError` that came back wrapped as `FAILED` — the code that means "anything
|
|
287
|
+
else" — so it could not be told from a Meilisearch outage without reading the
|
|
288
|
+
sentence.
|
|
282
289
|
|
|
283
290
|
**Fix:** return `null` for a document that should stay out of the index, and
|
|
284
291
|
make every branch return:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/mongo-meilisearch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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,8 +49,8 @@
|
|
|
49
49
|
]
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@nxgt/meilisearch": "^0.
|
|
53
|
-
"@nxgt/mongo": "^0.
|
|
52
|
+
"@nxgt/meilisearch": "^0.2.0",
|
|
53
|
+
"@nxgt/mongo": "^0.17.0",
|
|
54
54
|
"@types/bun": "^1.4.0",
|
|
55
55
|
"meilisearch": "0.62.0",
|
|
56
56
|
"mongodb": "7.6.0",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"zod": "4.6.5"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@nxgt/meilisearch": "^0.
|
|
62
|
-
"@nxgt/mongo": "^0.
|
|
61
|
+
"@nxgt/meilisearch": "^0.2.0",
|
|
62
|
+
"@nxgt/mongo": "^0.17.0",
|
|
63
63
|
"meilisearch": ">=0.62.0 <1",
|
|
64
64
|
"mongodb": ">=7.0.0 <8",
|
|
65
65
|
"typescript": "^6.0.3"
|