diff --git a/docs/en/api-index.md b/docs/en/api-index.md index d00ccd0..467d780 100644 --- a/docs/en/api-index.md +++ b/docs/en/api-index.md @@ -96,7 +96,7 @@ This index documents the current stable MongoDB adapter APIs and shared runtime |----------|--------|-------------| | [Aggregation pipeline](aggregate.md) | `aggregate()` | Aggregation pipeline query | | [Vector search](vector-search.md) | Collection / Model `vectorSearch()` | MongoDB Vector Search query with Model soft-delete visibility when applicable | -| [Search indexes](search-indexes.md) | Collection / Model `createSearchIndex()` and related methods | Create, inspect, update, drop, and wait for Search / Vector Search indexes | +| [Search indexes](search-indexes.md) | Collection / Model `createSearchIndex()` and related methods | Create, inspect, update, drop, and wait for Search indexes (`search` and `vectorSearch` types) | | [GridFS](gridfs.md) | `msq.gridfs()` / Model `gridfs()` | Upload, download, and resume GridFS files | --- @@ -174,7 +174,7 @@ This index documents the current stable MongoDB adapter APIs and shared runtime - [Aggregation pipeline](aggregate.md) - Aggregation pipelines - [Vector search](vector-search.md) - MongoDB Vector Search queries -- [Search indexes](search-indexes.md) - Search / Vector Search index lifecycle +- [Search indexes](search-indexes.md) - Search index lifecycle (`search` and `vectorSearch` types) - [GridFS](gridfs.md) - GridFS upload, download, and resumable upload - [Query plans](explain.md) - Query plans - [Chain queries](chaining-api.md) - Chain queries diff --git a/docs/en/capability-index.md b/docs/en/capability-index.md index 76625f6..dfeaeff 100644 --- a/docs/en/capability-index.md +++ b/docs/en/capability-index.md @@ -23,6 +23,7 @@ Principles: | Model | `msq.model()` / `MonSQLize.Model` | Supports model registration, relations, virtuals, populate, and common query entries | | write-path-policy | `writePathPolicy` | Optional runtime guard for Model-only write namespaces, including the `gridfs` category | | GridFS | `msq.gridfs()` / `model.gridfs()` | Upload, download, and phase-1 resumable GridFS upload. See [GridFS](./gridfs.md) | +| Search index and vector search | `createSearchIndex()` / `vectorSearch()` | Search index lifecycle for types `search` and `vectorSearch`, then vector queries. See [Search indexes](./search-indexes.md) and [vectorSearch()](./vector-search.md) | | cache | Query `cache` options / `msq.cache` | Database query cache, multi-layer cache access, invalidation, and statistics | | transaction | `startSession()` / `withTransaction()` | Supports MongoDB transaction wrappers and transaction statistics | | pool | `pools` / `pool()` / `ConnectionPoolManager` | Declare `pools: PoolConfig[]` in the constructor, route with `pool()`, and use the low-level manager for advanced inspection or manual management | diff --git a/docs/en/capability-traceability.md b/docs/en/capability-traceability.md index 0d6cdbc..95512fe 100644 --- a/docs/en/capability-traceability.md +++ b/docs/en/capability-traceability.md @@ -10,7 +10,8 @@ | Query (find / findOne / count / distinct) | `find.md` / `findOne.md` / `count.md` / `distinct.md` | `examples/docs/find.ts` / `find-one.ts` / `count.ts` / `distinct.ts` | `test/integration/mongodb/queries.test.ts` | | Paging / Chain query | `findPage.md` / `chaining-api.md` | `examples/docs/find-page.ts` / `chaining-api.ts` | `test/integration/model/model-features.test.ts` + `npm run test:examples` | | Aggregation / Expression | `aggregate.md` / `expression-functions.md` | `examples/docs/aggregate.ts` / `aggregate-advanced.ts` / `expression-functions.ts` | `test/unit/expression/*.test.ts` | -| Collection / Model vector search | `vector-search.md` | Documented API example; requires an external Vector Search deployment and index | `test/unit/runtime/vector-search.test.ts` / `test/unit/runtime/model-relations-p0.test.ts` | +| Collection / Model vector search | `vector-search.md` | `examples/docs/vector-search.ts` (Atlas, atlas-local, or Community 8.2+ with mongot; skips when no Search URI is set) | `test/unit/runtime/vector-search.test.ts` / `test/unit/runtime/model-relations-p0.test.ts` | +| Search index lifecycle (`search` and `vectorSearch`) | `search-indexes.md` | `examples/docs/vector-search.ts` | `test/unit/search-indexes/` / `test/integration/search-indexes/` / `test/types/search-index-usage.test-d.ts` | | Write extension / Batch | `write-operations.md` / `updateBatch.md` / `deleteBatch.md` | `examples/docs/update.ts` / `batch-operations.ts` | `test/unit/writes/batch.test.ts` + `test/integration/mongodb/writes-batch.test.ts` | | Collection management capabilities | `collection-management.md` / `create-index.md` / `database-ops.md` | `examples/docs/collection-management.ts` | `test/integration/mongodb/management.test.ts` | | Production data tasks / index rollout | `production-data-migration.md` / `data-tasks.md` / `production-rollout.md` | `examples/docs/data-tasks.ts` | `test/unit/data-tasks/data-task-job-*.test.ts` / `test/integration/data-tasks/data-task-job-facade.test.ts` / `test/smoke/data-task-cli.test.ts` | diff --git a/docs/en/error-codes.md b/docs/en/error-codes.md index 1830de4..be14fa9 100644 --- a/docs/en/error-codes.md +++ b/docs/en/error-codes.md @@ -65,7 +65,7 @@ See [Data Tasks](./data-tasks.md) for the preview, approval, apply, backup, and | `POOL_NOT_FOUND` | The specified connection pool does not exist or the health checker cannot find the pool | Whether the pool name is registered, whether the case is consistent, and whether `pools` is passed in | | `INVALID_OPERATION` | The query is executed repeatedly, all connection pools are unavailable, and the transaction status is not allowed | Whether to repeat `toArray()`, pool health status, transaction life cycle | | `INVALID_ARGUMENT` | Query chain parameters, collection management parameters, function cache parameters | Parameter type, value range, non-empty string/object/array | -| `INVALID_VECTOR_SEARCH` | Local Vector Search option validation | Vector index/path, finite query vector, ANN/ENN combination, filter/projection shape | +| `INVALID_VECTOR_SEARCH` | Local Vector Search option validation | index name and path, finite query vector, ANN/ENN combination, filter/projection shape | | `INVALID_SEARCH_INDEX_DEFINITION` | Local Search index validation | Index name/type/definition, vectorSearch fields, option values | | `SEARCH_INDEX_EXISTS` | Search index name already exists | `ifExists` option, duplicate names in `createSearchIndexes` | | `SEARCH_INDEX_NOT_FOUND` | Search index target is missing | Index name, collection, `ignoreMissing` | diff --git a/docs/en/examples.md b/docs/en/examples.md index 58a4de6..2281d16 100644 --- a/docs/en/examples.md +++ b/docs/en/examples.md @@ -2,7 +2,7 @@ Every core API in the documentation site should map, as much as possible, to an example that can run directly inside the current repository. -> See the [official examples index](https://github.com/devcodex-labs/monSQLize/blob/main/examples/README.md) for the full source list. `npm run test:examples` compiles and executes the current **58** TypeScript examples. [`examples/helpers/bootstrap.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/helpers/bootstrap.ts) is a helper module and is not executed on its own. +> See the [official examples index](https://github.com/devcodex-labs/monSQLize/blob/main/examples/README.md) for the full source list. `npm run test:examples` compiles and executes the current **59** TypeScript examples. [`examples/helpers/bootstrap.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/helpers/bootstrap.ts) is a helper module and is not executed on its own. [`examples/docs/vector-search.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/vector-search.ts) exits without calling Search when `MONSQLIZE_VECTOR_SEARCH_URI` and `MONSQLIZE_ATLAS_LOCAL_URI` are unset. ## How to Run @@ -34,12 +34,16 @@ node .generated/examples-dist/examples/docs/find.js |----------|---------| | [`find.md`](./find.md) | [`examples/docs/find.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find.ts) | | [`findOne.md`](./findOne.md) | [`examples/docs/find-one.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find-one.ts) | +| [`find-one-by-id.md`](./find-one-by-id.md) | [`examples/docs/find-one-by-id.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find-one-by-id.ts) | +| [`find-by-ids.md`](./find-by-ids.md) | [`examples/docs/find-by-ids.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find-by-ids.ts) | | [`findPage.md`](./findPage.md) | [`examples/docs/find-page.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find-page.ts) | | [`find-and-count.md`](./find-and-count.md) | [`examples/docs/find-and-count.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find-and-count.ts) | | [`count.md`](./count.md) | [`examples/docs/count.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/count.ts) | | [`count-queue.md`](./count-queue.md) | [`examples/docs/count-queue.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/count-queue.ts) | | [`distinct.md`](./distinct.md) | [`examples/docs/distinct.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/distinct.ts) | | [`aggregate.md`](./aggregate.md) | [`examples/docs/aggregate.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/aggregate.ts) | +| [`vector-search.md`](./vector-search.md) | [`examples/docs/vector-search.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/vector-search.ts) | +| [`search-indexes.md`](./search-indexes.md) | [`examples/docs/vector-search.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/vector-search.ts) | | [`explain.md`](./explain.md) | [`examples/docs/explain.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/explain.ts) | | [`chaining-api.md`](./chaining-api.md) | [`examples/docs/chaining-api.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/chaining-api.ts) | diff --git a/docs/en/recipes.md b/docs/en/recipes.md index 4dcc766..da3004b 100644 --- a/docs/en/recipes.md +++ b/docs/en/recipes.md @@ -197,6 +197,12 @@ Model schema callbacks use the isolated `schema-dsl/runtime` owned by the `MonSQ Example source: [`examples/docs/model.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/model.ts) +## Query with vectorSearch + +`vectorSearch()` queries a Search index of type `vectorSearch`. Create and wait for that index with the [Search index lifecycle API](./search-indexes.md), then run the query described in [`vectorSearch()`](./vector-search.md). + +The runnable example is [`examples/docs/vector-search.ts`](../../examples/docs/vector-search.ts). It needs MongoDB Atlas, `mongodb/mongodb-atlas-local`, or Community/Enterprise 8.2+ with `mongot`. Set `MONSQLIZE_VECTOR_SEARCH_URI` or `MONSQLIZE_ATLAS_LOCAL_URI`. `mongodb-memory-server` cannot run the Search calls. When those variables are unset, the example exits without calling Search. + ## Troubleshoot by Error Code ```ts diff --git a/docs/en/search-indexes.md b/docs/en/search-indexes.md index 1f70794..13a04e1 100644 --- a/docs/en/search-indexes.md +++ b/docs/en/search-indexes.md @@ -1,6 +1,6 @@ # Search Index Lifecycle -Since v3.4.0, collection accessors and Models can create, inspect, update, drop, and wait for MongoDB Search indexes. One API covers full-text `search` indexes and `vectorSearch` indexes; use [`vectorSearch()`](vector-search.md) to query a Vector Search index. +Since v3.4.0, collection accessors and Models can create, inspect, update, drop, and wait for MongoDB Search indexes. A **Search index** has type `search` (full text) or `vectorSearch`. Use [`vectorSearch()`](vector-search.md) to query a Search index of type `vectorSearch`. ## Requirements @@ -53,41 +53,82 @@ interface SearchIndexInfo { | Method | Behavior | |--------|----------| -| `createSearchIndex` | Validates locally, then checks for an index with the same name. With the default `ifExists: 'error'` an existing index rejects with `SEARCH_INDEX_EXISTS`. `'skip'` leaves it unchanged (`action: 'skipped'`). `'update'` replaces the definition (`action: 'updated'`), unless the type differs, which rejects with `INVALID_SEARCH_INDEX_DEFINITION` (drop the index first). | -| `createSearchIndexes` | Validates every spec before sending one command. It has no `ifExists` option; duplicate names reject with `SEARCH_INDEX_EXISTS`. | -| `listSearchIndexes` | Returns normalized entries. `type` filters by the resolved type. | -| `getSearchIndex` | A missing index resolves to `status: 'DOES_NOT_EXIST'`; it does not throw. | -| `updateSearchIndex` | Replaces the definition and the server rebuilds the index. While it rebuilds, the previous version can keep serving queries. The reported `queryable` flag varies: Community 8.2.12 kept it `true`, while atlas-local 8.3.13 reported `false` during a `search` rebuild even though `$search` still returned results (both measured). A missing index rejects with `SEARCH_INDEX_NOT_FOUND`. | -| `dropSearchIndex` | A missing index rejects with `SEARCH_INDEX_NOT_FOUND` unless `ignoreMissing: true`. | -| `waitForSearchIndex` | Polls once per `intervalMs`. See the table below. | - -> **Updating a `vectorSearch` index depends on the deployment.** -> -> | Deployment | In-place `updateSearchIndex` on `vectorSearch` | -> |---|---| -> | MongoDB Atlas | Supported according to the MongoDB documentation; the old index keeps serving queries while the new one builds. Not tested by monSQLize. | -> | `mongodb/mongodb-atlas-local` (mongod 8.3.13) | Rejected by the server: `BadValue` (2) `"mappings" is required`. Measured. | -> | Community 8.2.12 + `mongodb/mongodb-community-search` | Accepted. During the rebuild (about 30 s for 20,000 documents × 64 dimensions) `$vectorSearch` kept returning results. Measured. | -> -> On every measured deployment the server rejects a `type` field in the `updateSearchIndex` command (`IDLUnknownField`), so sending one does not help. monSQLize passes a rejection through as `MONGODB_ERROR` with `serverCode` and never falls back on its own. -> -> Where an in-place update is not available, change the definition without downtime like this: create an index under a new name, `waitForSearchIndex(newName, { until: 'queryable' })`, switch your queries to the new name, then drop the old index. Both indexes exist for a while, which uses extra resources and counts against index limits (Atlas Free tier: 3 Search indexes, Flex: 10). If a short outage is acceptable, you can instead drop the index, wait for `dropped`, and create it again. Searches fail until it is queryable again, and that gap grows with data size (measured on atlas-local: about 1–2 s for 50,000 documents × 64 dimensions). +| `createSearchIndex` | Validates the spec, then applies `ifExists`. | +| `createSearchIndexes` | Validates every spec, then sends one command. | +| `listSearchIndexes` | Returns normalized entries. Optional `type` filter. | +| `getSearchIndex` | Missing name returns `DOES_NOT_EXIST`. | +| `updateSearchIndex` | Replaces the definition. The server rebuilds it. | +| `dropSearchIndex` | Drops the index, or returns when `ignoreMissing` is set. | +| `waitForSearchIndex` | Polls until the requested condition, timeout, or abort. | + +### createSearchIndex + +The call validates locally, then looks for an index with the same name. + +- Default `ifExists: 'error'` rejects an existing index with `SEARCH_INDEX_EXISTS`. +- `'skip'` leaves the existing index unchanged and returns `action: 'skipped'`. +- `'update'` replaces the definition and returns `action: 'updated'`. +- A type change rejects with `INVALID_SEARCH_INDEX_DEFINITION`. Drop the index first. + +### createSearchIndexes + +Every spec is validated before one command is sent. There is no `ifExists` option. Duplicate names reject with `SEARCH_INDEX_EXISTS`. + +### listSearchIndexes and getSearchIndex + +`listSearchIndexes` returns normalized entries. `type` filters by the resolved type. Entries with `typeSource: 'unknown'` never match that filter. + +`getSearchIndex` resolves a missing index to `status: 'DOES_NOT_EXIST'` and does not throw. + +### updateSearchIndex + +The call replaces the definition. The server rebuilds the index asynchronously. While it rebuilds, the previous version can keep serving queries. + +The reported `queryable` flag is not the same on every deployment. Community 8.2.12 kept it `true`. atlas-local 8.3.13 reported `false` during a `search` rebuild even though `$search` still returned results. Both results were measured. A missing index rejects with `SEARCH_INDEX_NOT_FOUND`. + +In-place `updateSearchIndex` on a Search index of type `vectorSearch` depends on the deployment: + +| Deployment | In-place update | +|------------|-----------------| +| MongoDB Atlas | Documented as supported. Not measured by monSQLize. | +| atlas-local 8.3.13 | Rejected. Server `BadValue` (2). Measured. | +| Community 8.2.12 + community-search | Accepted. Queries continued during rebuild. Measured. | + +MongoDB Atlas documentation says the previous index keeps serving queries while the new definition builds. monSQLize has not measured that path. + +`mongodb/mongodb-atlas-local` (mongod 8.3.13) rejects the update with `BadValue` (2) and the message `"mappings" is required`. + +Community 8.2.12 with `mongodb/mongodb-community-search` accepts the update. During a measured rebuild of about 30 seconds for 20,000 documents with 64 dimensions, `$vectorSearch` kept returning results. + +On every measured deployment the server rejects a `type` field on the `updateSearchIndex` command (`IDLUnknownField`). Sending one does not help. monSQLize passes that rejection through as `MONGODB_ERROR` with `serverCode` and does not fall back on its own. + +When an in-place update is not available, change the definition without downtime: create an index under a new name, call `waitForSearchIndex(newName, { until: 'queryable' })`, switch queries to the new name, then drop the old index. Both indexes exist for a while. That uses extra resources and counts against index limits (Atlas Free tier: 3 Search indexes, Flex: 10). + +If a short outage is acceptable, drop the index, wait for `dropped`, and create it again. Searches fail until it is queryable again. The gap grows with data size. On atlas-local, 50,000 documents with 64 dimensions took about 1–2 seconds. `status` passes the server value through unchanged. Use `queryable` to decide whether searches can run and `status` to show progress. +### dropSearchIndex + +A missing index rejects with `SEARCH_INDEX_NOT_FOUND` unless `ignoreMissing: true`. + ### Index type -The type comes from the server's top-level `type` field (`typeSource: 'server'`). Self-managed `mongot` does not return that field ([MongoDB limitations](https://www.mongodb.com/docs/search/self-managed/current/limitations/)). In that case the type is inferred from `latestDefinition` (`typeSource: 'inferred'`): a `fields` array with a `type: 'vector'` entry means `vectorSearch`, and a `mappings` key means `search`. Otherwise `type` is omitted and `typeSource` is `'unknown'`; such entries never match a `listSearchIndexes({ type })` filter. +The type comes from the server's top-level `type` field (`typeSource: 'server'`). Self-managed `mongot` does not return that field ([MongoDB limitations](https://www.mongodb.com/docs/search/self-managed/current/limitations/)). In that case the type is inferred from `latestDefinition` (`typeSource: 'inferred'`): a `fields` array with a `type: 'vector'` entry means `vectorSearch`, and a `mappings` key means `search`. Otherwise `type` is omitted and `typeSource` is `'unknown'`. ### waitForSearchIndex | `until` | Resolves when | `FAILED` | Missing index | |---------|---------------|----------|---------------| -| `queryable` | `queryable === true` | Rejects with `SEARCH_INDEX_BUILD_FAILED` only when not queryable | Keeps waiting for up to 30 seconds, then rejects with `SEARCH_INDEX_NOT_FOUND` | -| `ready` | `status === 'READY'` | Rejects with `SEARCH_INDEX_BUILD_FAILED` | Same as above | -| `dropped` | `status === 'DOES_NOT_EXIST'` | Keeps waiting | Resolves | +| `queryable` | `queryable` is true | Rejects if not queryable | 30s grace, then not found | +| `ready` | `status` is `READY` | Rejects | Same 30s grace | +| `dropped` | `status` is `DOES_NOT_EXIST` | Keeps waiting | Resolves | + +For `until: 'queryable'`, `FAILED` rejects with `SEARCH_INDEX_BUILD_FAILED` only when the index is not queryable. For `until: 'ready'`, `FAILED` rejects with `SEARCH_INDEX_BUILD_FAILED`. For `until: 'dropped'`, `FAILED` keeps waiting. -Timeouts reject with `SEARCH_INDEX_WAIT_TIMEOUT`, and `details[0].last` holds the last observed status. Aborting `signal` rejects with `signal.reason` unchanged. +A missing index on `queryable` or `ready` keeps waiting for up to 30 seconds, then rejects with `SEARCH_INDEX_NOT_FOUND`. A missing index on `dropped` resolves immediately. + +The method polls once per `intervalMs`. Timeouts reject with `SEARCH_INDEX_WAIT_TIMEOUT`, and `details[0].last` holds the last observed status. Aborting `signal` rejects with `signal.reason` unchanged. ## writePathPolicy @@ -97,13 +138,15 @@ Timeouts reject with `SEARCH_INDEX_WAIT_TIMEOUT`, and `details[0].last` holds th | Code | When | |------|------| -| `INVALID_SEARCH_INDEX_DEFINITION` | Local validation failed (name, type, definition, options), or `ifExists: 'update'` targets an index of another type | -| `SEARCH_INDEX_EXISTS` | Same-name index exists with `ifExists: 'error'`, or duplicate names in `createSearchIndexes` | -| `SEARCH_INDEX_NOT_FOUND` | `updateSearchIndex` / `dropSearchIndex` target is missing; `waitForSearchIndex` saw no index after the grace period | +| `INVALID_SEARCH_INDEX_DEFINITION` | Local validation failed, or `update` changes type | +| `SEARCH_INDEX_EXISTS` | Same name exists, or a batch repeats a name | +| `SEARCH_INDEX_NOT_FOUND` | Update, drop, or wait target is missing | | `SEARCH_INDEX_UNSUPPORTED` | The deployment does not support Search index commands | | `SEARCH_INDEX_BUILD_FAILED` | `waitForSearchIndex` observed `FAILED` | | `SEARCH_INDEX_WAIT_TIMEOUT` | `waitForSearchIndex` exceeded `timeoutMs` | +Local validation covers the name, type, definition, and options. `ifExists: 'update'` also rejects a type change. `SEARCH_INDEX_NOT_FOUND` includes a `waitForSearchIndex` miss after the grace period. + Every error raised from a server response keeps the driver error as `cause`. Server errors without a dedicated code reject with `MONGODB_ERROR`; `details[0]` carries `serverCode`, `serverCodeName`, and `serverMessage`, and `error.monsqlize = { operation, namespace, indexName }` is attached. See [Error codes](error-codes.md). ## Examples @@ -130,3 +173,5 @@ console.log(info.status, info.queryable); await docs.dropSearchIndex('text_idx', { ignoreMissing: true }); await docs.waitForSearchIndex('text_idx', { until: 'dropped' }); ``` + +Query the `vectorSearch` index with [`vectorSearch()`](vector-search.md). A runnable create, wait, and query walkthrough is [`examples/docs/vector-search.ts`](../../examples/docs/vector-search.ts). It needs MongoDB Atlas, `mongodb/mongodb-atlas-local`, or Community/Enterprise 8.2+ with `mongot`, via `MONSQLIZE_VECTOR_SEARCH_URI` or `MONSQLIZE_ATLAS_LOCAL_URI`. diff --git a/docs/en/upgrade-3.3.2.md b/docs/en/upgrade-3.3.2.md index bd823c2..404329c 100644 --- a/docs/en/upgrade-3.3.2.md +++ b/docs/en/upgrade-3.3.2.md @@ -1,5 +1,7 @@ # Upgrade from 3.3.1 to 3.3.2 +> **Later releases.** 3.4.0 adds the [Search index lifecycle API](./search-indexes.md) and 3.5.0 adds [GridFS](./gridfs.md). Those releases add capabilities and do not have a separate migration guide. This page covers only the 3.3.1 to 3.3.2 upgrade. + Use this guide to upgrade an application from **3.3.1 to 3.3.2**. Treat an untagged checkout as a release candidate. Check `npm view monsqlize@3.3.2 version --registry=https://registry.npmjs.org/` and the immutable tag before installing that exact version; until it exists, qualify the candidate's packed artifact in staging. Preparing a candidate does not publish npm or update the stable site. The patch corrects BSON query/cache identity, Model cache invalidation, pagination, transactions, sync and resource cleanup. It retains public package entry points, Node.js `>=18`, direct runtime dependencies and build formats. Automatic cache invalidation remains disabled by default. diff --git a/docs/en/vector-search.md b/docs/en/vector-search.md index 246e704..9923e17 100644 --- a/docs/en/vector-search.md +++ b/docs/en/vector-search.md @@ -4,9 +4,9 @@ ## Prerequisites -This API requires a MongoDB deployment that supports Vector Search and a Vector Search index that already exists. Since v3.4.0 you can manage the index with the [Search index lifecycle API](search-indexes.md). Configure the index and make its vector dimensions, numeric representation, and indexed path match the embeddings you send to the API. See the [MongoDB `$vectorSearch` reference](https://www.mongodb.com/docs/vector-search/query/aggregation-stages/vector-search-stage/) for deployment and index requirements. +This API requires a MongoDB deployment that supports Vector Search and a Search index of type `vectorSearch` that already exists. A **Search index** is a MongoDB Search index whose type is full-text `search` or `vectorSearch`. Since v3.4.0 you can manage that index with the [Search index lifecycle API](search-indexes.md). Configure the index and make its vector dimensions, numeric representation, and indexed path match the embeddings you send to the API. See the [MongoDB `$vectorSearch` reference](https://www.mongodb.com/docs/vector-search/query/aggregation-stages/vector-search-stage/) for deployment and index requirements. -`$vectorSearch` must be the first aggregation stage. It is also not supported inside a `$lookup` sub-pipeline or `$facet`, so use this collection-level API as the search entry point and enrich the resulting documents afterwards when needed. +`$vectorSearch` must be the first stage of any pipeline it appears in. Current MongoDB documentation still does not allow it inside a `$lookup` sub-pipeline or a `$facet` stage ([manual](https://www.mongodb.com/docs/manual/reference/operator/aggregation/vectorsearch/), [Vector Search stage](https://www.mongodb.com/docs/vector-search/query/aggregation-stages/vector-search-stage/)). Use `vectorSearch()` as the search entry point and enrich the resulting documents afterwards when needed. ## API @@ -78,7 +78,7 @@ for (const { document, score } of hits) { } ``` -`filter` is a Vector Search pre-filter, not a later `$match` stage. Create the relevant filter fields in the Vector Search index before relying on them. +`filter` is a Vector Search pre-filter, not a later `$match` stage. Create the relevant filter fields on the Search index (`vectorSearch` type) before relying on them. ## Exact nearest-neighbor search (ENN) @@ -129,10 +129,10 @@ Invalid vector options throw `INVALID_VECTOR_SEARCH`, with field/reason details. Deployment, index, and other MongoDB execution failures are rethrown with their original driver code and message. When the error object is writable, monSQLize adds `error.monsqlize.operation === 'vectorSearch'` and the target namespace for diagnosis. -This release exposes vector search on both `Collection` and `Model`; it does not add a vector-index lifecycle API. +Index management for a Search index (including type `vectorSearch`) is on the [Search index lifecycle](search-indexes.md) page: `createSearchIndex`, `updateSearchIndex`, `dropSearchIndex`, `listSearchIndexes`, and `waitForSearchIndex`. ## Operational notes -- A default local `mongodb-memory-server` instance does not provide Vector Search. Unit coverage verifies pipeline construction and validation; end-to-end verification needs a compatible externally provisioned deployment and index. +- A default local `mongodb-memory-server` instance does not provide Vector Search. Unit coverage verifies pipeline construction and validation. End-to-end verification needs Atlas, `mongodb/mongodb-atlas-local`, or Community/Enterprise 8.2+ with `mongot`. The runnable walkthrough is [`examples/docs/vector-search.ts`](../../examples/docs/vector-search.ts). Set `MONSQLIZE_VECTOR_SEARCH_URI` or `MONSQLIZE_ATLAS_LOCAL_URI`. When neither is set, that example exits without calling Search so `npm run test:examples` can still invoke it. - Vector Search ranking is produced by MongoDB. monSQLize returns that score and does not re-rank results locally. - Keep the embedding model, vector dimensions, and index configuration synchronized across writers and readers. diff --git a/docs/zh/api-index.md b/docs/zh/api-index.md index b70c111..6880e67 100644 --- a/docs/zh/api-index.md +++ b/docs/zh/api-index.md @@ -96,7 +96,7 @@ |------|------|------| | [聚合管道查询](aggregate.md) | `aggregate()` | 聚合管道查询 | | [向量查询](vector-search.md) | Collection / Model `vectorSearch()` | MongoDB Vector Search 向量查询;Model 会应用软删除可见性 | -| [Search 索引](search-indexes.md) | Collection / Model `createSearchIndex()` 等 | 创建、查看、更新、删除并等待 Search / Vector Search 索引 | +| [Search 索引](search-indexes.md) | Collection / Model `createSearchIndex()` 等 | 创建、查看、更新、删除并等待 Search 索引(含 `vectorSearch` 类型) | | [GridFS](gridfs.md) | `msq.gridfs()` / Model `gridfs()` | 上传、下载和恢复 GridFS 文件 | --- @@ -172,7 +172,7 @@ - [聚合管道查询](aggregate.md) - 聚合管道 - [向量查询](vector-search.md) - MongoDB Vector Search 向量查询 -- [Search 索引](search-indexes.md) - Search / Vector Search 索引生命周期 +- [Search 索引](search-indexes.md) - Search 索引生命周期(含 `vectorSearch` 类型) - [GridFS](gridfs.md) - GridFS 上传、下载与可恢复上传 - [查询计划分析](explain.md) - 查询计划 - [链式调用](chaining-api.md) - 链式调用 diff --git a/docs/zh/capability-index.md b/docs/zh/capability-index.md index e219fd8..7e0a7a1 100644 --- a/docs/zh/capability-index.md +++ b/docs/zh/capability-index.md @@ -22,6 +22,7 @@ monSQLize 是数据库原生的生产数据运行时增强层。当前稳定适 | Model | `msq.model()` / `MonSQLize.Model` | 支持 Model 注册、关系、虚拟字段、populate 与常用查询入口 | | write-path-policy | `writePathPolicy` | 可选的 Model-only 写入命名空间运行时护栏,包含 `gridfs` 类别 | | GridFS | `msq.gridfs()` / `model.gridfs()` | 上传、下载和第一阶段可恢复 GridFS 上传。见 [GridFS](./gridfs.md) | +| Search 索引与向量查询 | `createSearchIndex()` / `vectorSearch()` | Search 索引生命周期(类型 `search` 与 `vectorSearch`),以及向量查询。见 [Search 索引](./search-indexes.md) 与 [vectorSearch()](./vector-search.md) | | cache | 查询 `cache` 选项 / `msq.cache` | 数据库查询缓存、多层缓存接入、缓存失效与统计 | | transaction | `startSession()` / `withTransaction()` | 支持 MongoDB 事务封装与事务统计 | | pool | `pools` / `pool()` / `ConnectionPoolManager` | 在构造函数中声明 `pools: PoolConfig[]`,通过 `pool()` 路由,底层管理器用于进阶状态查看与手动管理 | diff --git a/docs/zh/capability-traceability.md b/docs/zh/capability-traceability.md index 1d69190..f20f446 100644 --- a/docs/zh/capability-traceability.md +++ b/docs/zh/capability-traceability.md @@ -10,7 +10,8 @@ | Query(find / findOne / count / distinct) | `find.md` / `findOne.md` / `count.md` / `distinct.md` | `examples/docs/find.ts` / `find-one.ts` / `count.ts` / `distinct.ts` | `test/integration/mongodb/queries.test.ts` | | 分页 / 链式查询 | `findPage.md` / `chaining-api.md` | `examples/docs/find-page.ts` / `chaining-api.ts` | `test/integration/model/model-features.test.ts` + `npm run test:examples` | | 聚合 / 表达式 | `aggregate.md` / `expression-functions.md` | `examples/docs/aggregate.ts` / `aggregate-advanced.ts` / `expression-functions.ts` | `test/unit/expression/*.test.ts` | -| Collection / Model 向量查询 | `vector-search.md` | 文档 API 示例;需要外部 Vector Search 部署和索引 | `test/unit/runtime/vector-search.test.ts` / `test/unit/runtime/model-relations-p0.test.ts` | +| Collection / Model 向量查询 | `vector-search.md` | `examples/docs/vector-search.ts`(Atlas、atlas-local,或带 mongot 的 Community 8.2+;未设置 Search URI 时跳过) | `test/unit/runtime/vector-search.test.ts` / `test/unit/runtime/model-relations-p0.test.ts` | +| Search 索引生命周期(`search` 与 `vectorSearch`) | `search-indexes.md` | `examples/docs/vector-search.ts` | `test/unit/search-indexes/` / `test/integration/search-indexes/` / `test/types/search-index-usage.test-d.ts` | | 写入扩展 / Batch | `write-operations.md` / `updateBatch.md` / `deleteBatch.md` | `examples/docs/update.ts` / `batch-operations.ts` | `test/unit/writes/batch.test.ts` + `test/integration/mongodb/writes-batch.test.ts` | | Collection 管理能力 | `collection-management.md` / `create-index.md` / `database-ops.md` | `examples/docs/collection-management.ts` | `test/integration/mongodb/management.test.ts` | | 生产数据任务 / 索引发布 | `production-data-migration.md` / `data-tasks.md` / `production-rollout.md` | `examples/docs/data-tasks.ts` | `test/unit/data-tasks/data-task-job-*.test.ts` / `test/integration/data-tasks/data-task-job-facade.test.ts` / `test/smoke/data-task-cli.test.ts` | diff --git a/docs/zh/error-codes.md b/docs/zh/error-codes.md index 830c80c..61507e8 100644 --- a/docs/zh/error-codes.md +++ b/docs/zh/error-codes.md @@ -62,7 +62,7 @@ try { | `POOL_NOT_FOUND` | 指定连接池不存在或 health checker 找不到池 | 池名称是否注册、大小写是否一致、`pools` 是否传入 | | `INVALID_OPERATION` | 查询重复执行、连接池全部不可用、事务状态不允许 | 是否重复 `toArray()`,池健康状态,事务生命周期 | | `INVALID_ARGUMENT` | 查询链参数、集合管理参数、函数缓存参数 | 参数类型、取值范围、非空字符串/对象/数组 | -| `INVALID_VECTOR_SEARCH` | 本地 Vector Search 参数校验 | 向量索引/路径、有限 query vector、ANN/ENN 组合、filter/projection 形态 | +| `INVALID_VECTOR_SEARCH` | 本地 Vector Search 参数校验 | index 名称与 path、有限 query vector、ANN/ENN 组合、filter/projection 形态 | | `INVALID_SEARCH_INDEX_DEFINITION` | Search 索引本地校验失败 | 索引名/类型/定义、vectorSearch fields、选项取值 | | `SEARCH_INDEX_EXISTS` | Search 索引同名已存在 | `ifExists` 选项、`createSearchIndexes` 中的重复名称 | | `SEARCH_INDEX_NOT_FOUND` | Search 索引不存在 | 索引名、集合、`ignoreMissing` | diff --git a/docs/zh/examples.md b/docs/zh/examples.md index aa4ac00..c97f258 100644 --- a/docs/zh/examples.md +++ b/docs/zh/examples.md @@ -2,7 +2,7 @@ 文档站中的每个核心 API,都应尽量对应到**当前仓库内可直接运行的示例**。 -> 当前完整示例源列表见 [官方示例总览](https://github.com/devcodex-labs/monSQLize/blob/main/examples/README.md),其中 `npm run test:examples` 会统一编译并执行当前 **58 个** TypeScript 示例;[`examples/helpers/bootstrap.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/helpers/bootstrap.ts) 是辅助模块,不单独执行。 +> 当前完整示例源列表见 [官方示例总览](https://github.com/devcodex-labs/monSQLize/blob/main/examples/README.md),其中 `npm run test:examples` 会统一编译并执行当前 **59 个** TypeScript 示例;[`examples/helpers/bootstrap.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/helpers/bootstrap.ts) 是辅助模块,不单独执行。[`examples/docs/vector-search.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/vector-search.ts) 在未设置 `MONSQLIZE_VECTOR_SEARCH_URI` 和 `MONSQLIZE_ATLAS_LOCAL_URI` 时不会调用 Search 并直接退出。 ## 运行方式 @@ -34,12 +34,16 @@ node .generated/examples-dist/examples/docs/find.js |------|------| | [`find.md`](./find.md) | [`examples/docs/find.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find.ts) | | [`findOne.md`](./findOne.md) | [`examples/docs/find-one.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find-one.ts) | +| [`find-one-by-id.md`](./find-one-by-id.md) | [`examples/docs/find-one-by-id.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find-one-by-id.ts) | +| [`find-by-ids.md`](./find-by-ids.md) | [`examples/docs/find-by-ids.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find-by-ids.ts) | | [`findPage.md`](./findPage.md) | [`examples/docs/find-page.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find-page.ts) | | [`find-and-count.md`](./find-and-count.md) | [`examples/docs/find-and-count.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/find-and-count.ts) | | [`count.md`](./count.md) | [`examples/docs/count.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/count.ts) | | [`count-queue.md`](./count-queue.md) | [`examples/docs/count-queue.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/count-queue.ts) | | [`distinct.md`](./distinct.md) | [`examples/docs/distinct.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/distinct.ts) | | [`aggregate.md`](./aggregate.md) | [`examples/docs/aggregate.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/aggregate.ts) | +| [`vector-search.md`](./vector-search.md) | [`examples/docs/vector-search.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/vector-search.ts) | +| [`search-indexes.md`](./search-indexes.md) | [`examples/docs/vector-search.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/vector-search.ts) | | [`explain.md`](./explain.md) | [`examples/docs/explain.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/explain.ts) | | [`chaining-api.md`](./chaining-api.md) | [`examples/docs/chaining-api.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/chaining-api.ts) | diff --git a/docs/zh/recipes.md b/docs/zh/recipes.md index 326fc26..ff73e56 100644 --- a/docs/zh/recipes.md +++ b/docs/zh/recipes.md @@ -197,6 +197,12 @@ Model 的 schema 回调使用当前 `MonSQLize` 实例隔离的 `schema-dsl/runt 示例源码:[`examples/docs/model.ts`](https://github.com/devcodex-labs/monSQLize/blob/main/examples/docs/model.ts) +## 用 vectorSearch 查询 + +`vectorSearch()` 查询的是 `vectorSearch` 类型的 Search 索引。先用 [Search 索引生命周期 API](./search-indexes.md) 创建并等待该索引,再按 [`vectorSearch()`](./vector-search.md) 的说明发起查询。 + +可运行示例是 [`examples/docs/vector-search.ts`](../../examples/docs/vector-search.ts)。它需要 MongoDB Atlas、`mongodb/mongodb-atlas-local`,或带 `mongot` 的 Community / Enterprise 8.2+。请设置 `MONSQLIZE_VECTOR_SEARCH_URI` 或 `MONSQLIZE_ATLAS_LOCAL_URI`。`mongodb-memory-server` 不能执行 Search 调用。这两个变量都未设置时,示例不会调用 Search 并直接退出。 + ## 按错误码排障 ```ts diff --git a/docs/zh/search-indexes.md b/docs/zh/search-indexes.md index c33536a..04ce214 100644 --- a/docs/zh/search-indexes.md +++ b/docs/zh/search-indexes.md @@ -1,6 +1,6 @@ # Search 索引生命周期 -从 v3.4.0 起,集合访问器和 Model 可以创建、查看、更新、删除并等待 MongoDB Search 索引。同一套接口同时支持全文 `search` 索引和 `vectorSearch` 索引;查询向量索引请使用 [`vectorSearch()`](vector-search.md)。 +从 v3.4.0 起,集合访问器和 Model 可以创建、查看、更新、删除并等待 MongoDB Search 索引。**Search 索引**的类型为全文 `search` 或 `vectorSearch`。查询 `vectorSearch` 类型的 Search 索引请使用 [`vectorSearch()`](vector-search.md)。 ## 环境要求 @@ -53,41 +53,82 @@ interface SearchIndexInfo { | 方法 | 行为 | |------|------| -| `createSearchIndex` | 先做本地校验,再检查是否已有同名索引。默认 `ifExists: 'error'` 时以 `SEARCH_INDEX_EXISTS` 拒绝;`'skip'` 不做改动(`action: 'skipped'`);`'update'` 替换定义(`action: 'updated'`),但类型不同时以 `INVALID_SEARCH_INDEX_DEFINITION` 拒绝,需要先删除再创建。 | -| `createSearchIndexes` | 全部校验通过后一次提交;不支持 `ifExists`,名称重复时以 `SEARCH_INDEX_EXISTS` 拒绝。 | -| `listSearchIndexes` | 返回归一化后的条目;`type` 按解析出的类型过滤。 | -| `getSearchIndex` | 索引不存在时返回 `status: 'DOES_NOT_EXIST'`,不抛错。 | -| `updateSearchIndex` | 替换定义,服务端随后重建;重建期间旧版本可以继续提供查询。上报的 `queryable` 不一致:Community 8.2.12 保持 `true`;atlas-local 8.3.13 在 `search` 索引重建期间上报 `false`,但 `$search` 仍返回结果(均为实测)。索引不存在时以 `SEARCH_INDEX_NOT_FOUND` 拒绝。 | -| `dropSearchIndex` | 索引不存在时以 `SEARCH_INDEX_NOT_FOUND` 拒绝;传 `ignoreMissing: true` 则直接返回。 | -| `waitForSearchIndex` | 每隔 `intervalMs` 查询一次,规则见下表。 | - -> **`vectorSearch` 索引能否原地更新取决于部署环境。** -> -> | 部署 | 对 `vectorSearch` 执行 `updateSearchIndex` | -> |---|---| -> | MongoDB Atlas | 按 MongoDB 官方文档支持原地更新,重建期间旧索引继续提供查询。monSQLize 未实测。 | -> | `mongodb/mongodb-atlas-local`(mongod 8.3.13) | 服务端拒绝:`BadValue`(2)`"mappings" is required`。已实测。 | -> | Community 8.2.12 + `mongodb/mongodb-community-search` | 接受。重建期间(20,000 文档 × 64 维约 30 秒)`$vectorSearch` 持续返回结果。已实测。 | -> -> 在所有实测环境中,服务端都不接受 `updateSearchIndex` 命令带 `type` 字段(`IDLUnknownField`),带上也没有用。服务端拒绝时,monSQLize 原样透出为 `MONGODB_ERROR`(带 `serverCode`),不会自动降级。 -> -> 不能原地更新时,推荐用换名切换做到不中断:用新名字创建索引,`waitForSearchIndex(newName, { until: 'queryable' })`,把查询切到新名字,再删除旧索引。期间两个索引同时存在,会额外占用资源,并计入索引数量上限(Atlas Free 档 3 个 Search 索引,Flex 档 10 个)。如果能接受短暂中断,也可以先删除索引、等待 `dropped`、再重新创建;在它重新可查询之前检索会失败,中断时长随数据量增长(atlas-local 实测:50,000 文档 × 64 维约 1–2 秒)。 +| `createSearchIndex` | 校验定义,再按 `ifExists` 处理。 | +| `createSearchIndexes` | 全部校验后一次提交。 | +| `listSearchIndexes` | 返回归一化条目。可用 `type` 过滤。 | +| `getSearchIndex` | 名称不存在时返回 `DOES_NOT_EXIST`。 | +| `updateSearchIndex` | 替换定义,由服务端重建。 | +| `dropSearchIndex` | 删除索引;`ignoreMissing` 时直接返回。 | +| `waitForSearchIndex` | 轮询到目标条件、超时或中止。 | + +### createSearchIndex + +调用先做本地校验,再查找同名索引。 + +- 默认 `ifExists: 'error'`:已存在时以 `SEARCH_INDEX_EXISTS` 拒绝。 +- `'skip'`:不改动已有索引,返回 `action: 'skipped'`。 +- `'update'`:替换定义,返回 `action: 'updated'`。 +- 类型变化以 `INVALID_SEARCH_INDEX_DEFINITION` 拒绝。需要先删除再创建。 + +### createSearchIndexes + +全部 spec 校验通过后一次提交。不支持 `ifExists`。名称重复时以 `SEARCH_INDEX_EXISTS` 拒绝。 + +### listSearchIndexes 与 getSearchIndex + +`listSearchIndexes` 返回归一化后的条目。`type` 按解析出的类型过滤。`typeSource: 'unknown'` 的条目不会命中该过滤。 + +`getSearchIndex` 在索引不存在时返回 `status: 'DOES_NOT_EXIST'`,不抛错。 + +### updateSearchIndex + +调用替换定义。服务端异步重建索引。重建期间,上一版可以继续提供查询。 + +各部署上报的 `queryable` 并不相同。Community 8.2.12 保持 `true`。atlas-local 8.3.13 在 `search` 类型重建期间上报 `false`,但 `$search` 仍返回结果。两者均为实测。索引不存在时以 `SEARCH_INDEX_NOT_FOUND` 拒绝。 + +对 `vectorSearch` 类型的 Search 索引执行原地 `updateSearchIndex`,取决于部署环境: + +| 部署 | 原地更新 | +|------|----------| +| MongoDB Atlas | 文档写明支持。monSQLize 未实测。 | +| atlas-local 8.3.13 | 拒绝。服务端 `BadValue`(2)。已实测。 | +| Community 8.2.12 + community-search | 接受。重建期间查询继续返回。已实测。 | + +MongoDB Atlas 文档说明重建期间旧索引继续提供查询。monSQLize 没有实测这条路径。 + +`mongodb/mongodb-atlas-local`(mongod 8.3.13)以 `BadValue`(2)拒绝更新,消息为 `"mappings" is required`。 + +Community 8.2.12 配合 `mongodb/mongodb-community-search` 会接受更新。20,000 篇文档、64 维的一次实测重建约 30 秒,期间 `$vectorSearch` 持续返回结果。 + +在所有实测环境中,服务端都不接受 `updateSearchIndex` 命令带 `type` 字段(`IDLUnknownField`)。带上该字段也没有用。服务端拒绝时,monSQLize 原样透出为 `MONGODB_ERROR`(带 `serverCode`),不会自动降级。 + +不能原地更新时,用换名切换避免中断:用新名字创建索引,调用 `waitForSearchIndex(newName, { until: 'queryable' })`,把查询切到新名字,再删除旧索引。期间两个索引同时存在,会额外占用资源,并计入索引数量上限(Atlas Free 档 3 个 Search 索引,Flex 档 10 个)。 + +如果能接受短暂中断,可以先删除索引、等待 `dropped`、再重新创建。在它重新可查询之前,检索会失败。中断时长随数据量增长。atlas-local 上 50,000 篇文档、64 维约 1–2 秒。 `status` 原样透传服务端的值。判断能否检索看 `queryable`,展示进度看 `status`。 +### dropSearchIndex + +索引不存在时以 `SEARCH_INDEX_NOT_FOUND` 拒绝,除非 `ignoreMissing: true`。 + ### 索引类型 -类型优先取服务端返回的顶层 `type` 字段(`typeSource: 'server'`)。自建 `mongot` 不返回该字段(见 [MongoDB 限制说明](https://www.mongodb.com/docs/search/self-managed/current/limitations/)),此时根据 `latestDefinition` 推断(`typeSource: 'inferred'`):`fields` 数组中有 `type: 'vector'` 的项即为 `vectorSearch`,有 `mappings` 键即为 `search`。两者都没有时不返回 `type`,`typeSource` 为 `'unknown'`,这类条目不会被 `listSearchIndexes({ type })` 匹配。 +类型优先取服务端返回的顶层 `type` 字段(`typeSource: 'server'`)。自建 `mongot` 不返回该字段(见 [MongoDB 限制说明](https://www.mongodb.com/docs/search/self-managed/current/limitations/))。此时根据 `latestDefinition` 推断(`typeSource: 'inferred'`):`fields` 数组中有 `type: 'vector'` 的项即为 `vectorSearch`,有 `mappings` 键即为 `search`。两者都没有时不返回 `type`,`typeSource` 为 `'unknown'`。 ### waitForSearchIndex | `until` | 何时返回 | 遇到 `FAILED` | 索引不存在 | |---------|----------|---------------|------------| -| `queryable` | `queryable === true` | 仅在不可查询时以 `SEARCH_INDEX_BUILD_FAILED` 拒绝 | 最多再等 30 秒,之后以 `SEARCH_INDEX_NOT_FOUND` 拒绝 | -| `ready` | `status === 'READY'` | 以 `SEARCH_INDEX_BUILD_FAILED` 拒绝 | 同上 | -| `dropped` | `status === 'DOES_NOT_EXIST'` | 继续等待 | 直接返回 | +| `queryable` | `queryable` 为 true | 不可查询时拒绝 | 宽限 30 秒后未找到 | +| `ready` | `status` 为 `READY` | 拒绝 | 同样宽限 30 秒 | +| `dropped` | `status` 为 `DOES_NOT_EXIST` | 继续等待 | 直接返回 | + +`until: 'queryable'` 时,仅在索引不可查询的情况下,`FAILED` 以 `SEARCH_INDEX_BUILD_FAILED` 拒绝。`until: 'ready'` 时,`FAILED` 以 `SEARCH_INDEX_BUILD_FAILED` 拒绝。`until: 'dropped'` 时,`FAILED` 继续等待。 -超时以 `SEARCH_INDEX_WAIT_TIMEOUT` 拒绝,`details[0].last` 是最后一次观察到的状态。`signal` 中止时原样以 `signal.reason` 拒绝。 +`queryable` 或 `ready` 下索引不存在时,最多再等 30 秒,之后以 `SEARCH_INDEX_NOT_FOUND` 拒绝。`dropped` 下索引不存在时直接返回。 + +方法每隔 `intervalMs` 查询一次。超时以 `SEARCH_INDEX_WAIT_TIMEOUT` 拒绝,`details[0].last` 是最后一次观察到的状态。`signal` 中止时原样以 `signal.reason` 拒绝。 ## writePathPolicy @@ -97,13 +138,15 @@ interface SearchIndexInfo { | 码 | 何时出现 | |----|----------| -| `INVALID_SEARCH_INDEX_DEFINITION` | 本地校验失败(名称、类型、定义、选项),或 `ifExists: 'update'` 的目标是另一种类型的索引 | -| `SEARCH_INDEX_EXISTS` | 已有同名索引且 `ifExists: 'error'`,或 `createSearchIndexes` 中名称重复 | -| `SEARCH_INDEX_NOT_FOUND` | `updateSearchIndex` / `dropSearchIndex` 的目标不存在;`waitForSearchIndex` 在宽限期后仍未看到索引 | +| `INVALID_SEARCH_INDEX_DEFINITION` | 本地校验失败,或 `update` 改变了类型 | +| `SEARCH_INDEX_EXISTS` | 同名已存在,或批量创建里名称重复 | +| `SEARCH_INDEX_NOT_FOUND` | 更新、删除或等待的目标不存在 | | `SEARCH_INDEX_UNSUPPORTED` | 部署不支持 Search 索引命令 | | `SEARCH_INDEX_BUILD_FAILED` | `waitForSearchIndex` 观察到 `FAILED` | | `SEARCH_INDEX_WAIT_TIMEOUT` | `waitForSearchIndex` 超过 `timeoutMs` | +本地校验覆盖名称、类型、定义和选项。`ifExists: 'update'` 也会拒绝类型变化。`SEARCH_INDEX_NOT_FOUND` 包括 `waitForSearchIndex` 在宽限期后仍未看到索引。 + 由服务端返回的错误都把驱动原错误放在 `cause`。没有专用码的服务端错误以 `MONGODB_ERROR` 拒绝,`details[0]` 带 `serverCode`、`serverCodeName`、`serverMessage`,并附加 `error.monsqlize = { operation, namespace, indexName }`。详见[错误码参考](error-codes.md)。 ## 示例 @@ -130,3 +173,5 @@ console.log(info.status, info.queryable); await docs.dropSearchIndex('text_idx', { ignoreMissing: true }); await docs.waitForSearchIndex('text_idx', { until: 'dropped' }); ``` + +用 [`vectorSearch()`](vector-search.md) 查询 `vectorSearch` 类型的 Search 索引。可运行的创建、等待和查询示例是 [`examples/docs/vector-search.ts`](../../examples/docs/vector-search.ts)。它需要 MongoDB Atlas、`mongodb/mongodb-atlas-local`,或带 `mongot` 的 Community / Enterprise 8.2+,并通过 `MONSQLIZE_VECTOR_SEARCH_URI` 或 `MONSQLIZE_ATLAS_LOCAL_URI` 连接。 diff --git a/docs/zh/upgrade-3.3.2.md b/docs/zh/upgrade-3.3.2.md index 4dfd7c7..b63b8b0 100644 --- a/docs/zh/upgrade-3.3.2.md +++ b/docs/zh/upgrade-3.3.2.md @@ -1,5 +1,7 @@ # 从 3.3.1 升级到 3.3.2 +> **后续版本。** 3.4.0 增加 [Search 索引生命周期 API](./search-indexes.md),3.5.0 增加 [GridFS](./gridfs.md)。这两次发布是新增能力,没有单独的迁移指南。本页只覆盖从 3.3.1 升级到 3.3.2。 + 本页帮助应用从 **3.3.1 升级到 3.3.2**。未打 tag 的检出仍按发布候选处理。安装准确版本前,先执行 `npm view monsqlize@3.3.2 version --registry=https://registry.npmjs.org/` 并核实不可变 tag;尚未发布时,在预发布环境验证候选的打包产物。准备候选不会发布 npm,也不会更新稳定站。 本补丁修复 BSON 查询与缓存身份、Model 缓存失效、分页、事务、同步及资源清理。公开包入口、Node.js `>=18`、直接运行时依赖和构建格式保持不变。自动缓存失效仍默认关闭。 diff --git a/docs/zh/vector-search.md b/docs/zh/vector-search.md index 085a047..da6ca4b 100644 --- a/docs/zh/vector-search.md +++ b/docs/zh/vector-search.md @@ -4,9 +4,9 @@ ## 前置条件 -该 API 需要支持 Vector Search 的 MongoDB 部署,以及一个已经创建好的 Vector Search 索引。从 v3.4.0 起可以使用 [Search 索引生命周期 API](search-indexes.md) 管理该索引。请保证索引配置中的向量维度、数值表示和索引字段路径与传入 API 的 embedding 一致。部署和索引要求请参考 [MongoDB `$vectorSearch` 文档](https://www.mongodb.com/docs/vector-search/query/aggregation-stages/vector-search-stage/)。 +该 API 需要支持 Vector Search 的 MongoDB 部署,以及一个已经创建好的 Search 索引(`vectorSearch` 类型)。**Search 索引**指 MongoDB Search 索引,类型为全文 `search` 或 `vectorSearch`。从 v3.4.0 起可以使用 [Search 索引生命周期 API](search-indexes.md) 管理该索引。请保证索引配置中的向量维度、数值表示和索引字段路径与传入 API 的 embedding 一致。部署和索引要求请参考 [MongoDB `$vectorSearch` 文档](https://www.mongodb.com/docs/vector-search/query/aggregation-stages/vector-search-stage/)。 -`$vectorSearch` 必须是聚合管道的第一个阶段;它也不能放在 `$lookup` 子管道或 `$facet` 中。因此应以集合级 API 作为检索入口,如需补充关联信息,再对命中文档做后续处理。 +`$vectorSearch` 必须是它所在管道的第一个阶段。当前 MongoDB 文档仍然不允许把它放进 `$lookup` 子管道或 `$facet` 阶段([手册](https://www.mongodb.com/docs/manual/reference/operator/aggregation/vectorsearch/)、[Vector Search 阶段](https://www.mongodb.com/docs/vector-search/query/aggregation-stages/vector-search-stage/))。请用 `vectorSearch()` 作为检索入口,需要补充关联信息时再处理命中文档。 ## API @@ -78,7 +78,7 @@ for (const { document, score } of hits) { } ``` -`filter` 是 Vector Search 的预过滤条件,不是后置 `$match`。依赖该条件前,需要先在 Vector Search 索引中配置相应的过滤字段。 +`filter` 是 Vector Search 的预过滤条件,不是后置 `$match`。依赖该条件前,需要先在 Search 索引(`vectorSearch` 类型)中配置相应的过滤字段。 ## 精确最近邻查询(ENN) @@ -129,10 +129,10 @@ await hits[0]?.document.populate('author'); 部署、索引或其他 MongoDB 执行失败会保留原始 driver 的 code 和 message 后重新抛出。错误对象可写时,monSQLize 会附加 `error.monsqlize.operation === 'vectorSearch'` 和目标 namespace,便于诊断。 -本版本同时在 `Collection` 和 `Model` 上提供向量查询;不会提供向量索引生命周期 API。 +Search 索引(含 `vectorSearch` 类型)的管理见 [Search 索引生命周期](search-indexes.md):`createSearchIndex`、`updateSearchIndex`、`dropSearchIndex`、`listSearchIndexes`、`waitForSearchIndex`。 ## 运行注意事项 -- 默认本地 `mongodb-memory-server` 不支持 Vector Search。单元测试覆盖管道构造和参数校验;端到端验证需要外部准备的兼容部署和索引。 +- 默认本地 `mongodb-memory-server` 不支持 Vector Search。单元测试覆盖管道构造和参数校验。端到端验证需要 Atlas、`mongodb/mongodb-atlas-local`,或带 `mongot` 的 Community / Enterprise 8.2+。可运行示例是 [`examples/docs/vector-search.ts`](../../examples/docs/vector-search.ts)。请设置 `MONSQLIZE_VECTOR_SEARCH_URI` 或 `MONSQLIZE_ATLAS_LOCAL_URI`。两者都未设置时,该示例不会调用 Search 并直接退出,因此 `npm run test:examples` 仍可以执行它。 - 向量排序分数由 MongoDB 生成,monSQLize 只返回该分数,不会在本地二次排序。 - 请让写入端和查询端使用一致的 embedding 模型、向量维度和索引配置。 diff --git a/examples/README.md b/examples/README.md index bac1ce4..e07e286 100644 --- a/examples/README.md +++ b/examples/README.md @@ -3,7 +3,7 @@ Runnable TypeScript examples for every major monSQLize API. Each file uses an in-memory MongoDB server (`mongodb-memory-server`) — no real database needed. -> This repository currently includes **58** runnable TypeScript examples. `examples/helpers/bootstrap.ts` is a shared helper module, and `npm run test:examples` compiles and runs the executable examples. +> This repository currently includes **59** runnable TypeScript examples. `examples/helpers/bootstrap.ts` is a shared helper module, and `npm run test:examples` compiles and runs the executable examples. `examples/docs/vector-search.ts` exits without calling Search when `MONSQLIZE_VECTOR_SEARCH_URI` and `MONSQLIZE_ATLAS_LOCAL_URI` are unset. ## Prerequisites @@ -115,6 +115,14 @@ npm run test:examples | [`docs/sync.ts`](docs/sync.ts) | ChangeStreamSyncManager + ResumeTokenStore — resume-token-based change stream sync | | [`docs/sync-target-failure.ts`](docs/sync-target-failure.ts) | Sync target failure accounting and recovery path | +### Docs — Search and vectors + +`examples/docs/vector-search.ts` needs MongoDB Atlas, `mongodb/mongodb-atlas-local`, or Community/Enterprise 8.2+ with `mongot`. Set `MONSQLIZE_VECTOR_SEARCH_URI` or `MONSQLIZE_ATLAS_LOCAL_URI`. `npm run test:examples` still invokes the file; without those variables it prints a skip line and exits 0, because `mongodb-memory-server` cannot serve Search indexes. + +| File | Description | +|------|-------------| +| [`docs/vector-search.ts`](docs/vector-search.ts) | Create a `vectorSearch` Search index, wait until queryable, query with `vectorSearch()`, then drop it | + ### Docs — Legacy Compatibility These examples remain runnable for existing users, but they are not primary monSQLize capabilities. New non-database function caching, business locks, and Saga orchestration should live in the application or framework runtime. @@ -186,6 +194,7 @@ node .generated/examples-dist/examples/docs/pool-fallback.js node .generated/examples-dist/examples/docs/multi-pool-health-check.js node .generated/examples-dist/examples/docs/sync.js node .generated/examples-dist/examples/docs/sync-target-failure.js +node .generated/examples-dist/examples/docs/vector-search.js # Legacy compatibility examples node .generated/examples-dist/examples/docs/function-cache.js diff --git a/examples/docs/vector-search.ts b/examples/docs/vector-search.ts new file mode 100644 index 0000000..973ee5c --- /dev/null +++ b/examples/docs/vector-search.ts @@ -0,0 +1,133 @@ +/** + * Create a Search index of type vectorSearch, wait until it is queryable, + * and query it with collection.vectorSearch(). + * + * See: docs/vector-search.md, docs/search-indexes.md + * + * Required environment (either variable): + * MONSQLIZE_VECTOR_SEARCH_URI + * MONSQLIZE_ATLAS_LOCAL_URI + * + * Point the URI at MongoDB Atlas, the mongodb/mongodb-atlas-local image, or + * MongoDB Community/Enterprise 8.2+ with mongot. Plain mongod and the default + * mongodb-memory-server cannot serve Search indexes. + * + * When neither variable is set, this process prints a skip line and exits 0 + * so `npm run test:examples` can compile and invoke it without a Search server. + * + * Run after building the project: + * npm run build && tsc -p tsconfig.examples.json + * MONSQLIZE_VECTOR_SEARCH_URI='mongodb://127.0.0.1:27017/?directConnection=true' \ + * node .generated/examples-dist/examples/docs/vector-search.js + */ +import MonSQLize from 'monsqlize'; + +const DIMENSIONS = 8; +const INDEX_NAME = 'articles_embedding_index'; + +interface ArticleDoc { + title: string; + status: 'draft' | 'published'; + embedding: number[]; +} + +function searchUri(): string | undefined { + const raw = process.env.MONSQLIZE_VECTOR_SEARCH_URI || process.env.MONSQLIZE_ATLAS_LOCAL_URI; + const uri = raw?.trim(); + return uri ? uri : undefined; +} + +function embedding(seed: number): number[] { + return Array.from({ length: DIMENSIONS }, (_, index) => Math.sin(seed + index) / 2); +} + +async function main(): Promise { + const uri = searchUri(); + if (!uri) { + console.log('[vector-search] SKIPPED: set MONSQLIZE_VECTOR_SEARCH_URI or MONSQLIZE_ATLAS_LOCAL_URI (Atlas, mongodb/mongodb-atlas-local, or Community/Enterprise 8.2+ with mongot).'); + return; + } + + const databaseName = process.env.MONSQLIZE_VECTOR_SEARCH_DB?.trim() || 'monsqlize_vector_example'; + const collectionName = `articles_${Date.now().toString(36)}`; + const msq = new MonSQLize({ + type: 'mongodb', + databaseName, + config: { uri }, + }); + + await msq.connect(); + const articles = msq.collection(collectionName); + + try { + await articles.insertMany([ + { title: 'published-near', status: 'published', embedding: embedding(0.2) }, + { title: 'published-far', status: 'published', embedding: embedding(2.4) }, + { title: 'draft-near', status: 'draft', embedding: embedding(0.25) }, + ]); + + const created = await articles.createSearchIndex({ + name: INDEX_NAME, + type: 'vectorSearch', + definition: { + fields: [ + { + type: 'vector', + path: 'embedding', + numDimensions: DIMENSIONS, + similarity: 'cosine', + }, + { type: 'filter', path: 'status' }, + ], + }, + }); + console.log('createSearchIndex:', created.name, created.action); + + const ready = await articles.waitForSearchIndex(INDEX_NAME, { + until: 'queryable', + timeoutMs: 600_000, + }); + console.log('waitForSearchIndex:', ready.status, ready.queryable); + + const hits = await articles.vectorSearch({ + index: INDEX_NAME, + path: 'embedding', + queryVector: embedding(0.2), + limit: 2, + numCandidates: 10, + filter: { status: 'published' }, + }); + + if (hits.length === 0) { + throw new Error('vectorSearch returned no hits'); + } + + for (const hit of hits) { + if (!Number.isFinite(hit.score)) { + throw new Error('vectorSearch hit is missing a finite score'); + } + if (hit.document.status !== 'published') { + throw new Error(`vectorSearch filter leaked status ${String(hit.document.status)}`); + } + console.log(hit.score, hit.document.title); + } + + await articles.dropSearchIndex(INDEX_NAME); + const dropped = await articles.waitForSearchIndex(INDEX_NAME, { + until: 'dropped', + timeoutMs: 120_000, + }); + console.log('dropSearchIndex:', dropped.status); + console.log('Vector search example complete'); + } finally { + await articles.dropCollection().catch((error: unknown) => { + console.error('Collection cleanup failed:', error); + }); + await msq.close(); + } +} + +main().catch((err: unknown) => { + console.error('Example failed:', err); + process.exit(1); +}); diff --git a/scripts/run-examples.cjs b/scripts/run-examples.cjs index 3c71918..28dc552 100644 --- a/scripts/run-examples.cjs +++ b/scripts/run-examples.cjs @@ -74,6 +74,7 @@ const exampleScripts = [ '.generated/examples-dist/examples/docs/multi-pool-health-check.js', '.generated/examples-dist/examples/docs/sync.js', '.generated/examples-dist/examples/docs/sync-target-failure.js', + '.generated/examples-dist/examples/docs/vector-search.js', ]; function runExample(script, env) { diff --git a/scripts/validation/doc-example-matrix.json b/scripts/validation/doc-example-matrix.json index 527f813..92ed8cb 100644 --- a/scripts/validation/doc-example-matrix.json +++ b/scripts/validation/doc-example-matrix.json @@ -25,16 +25,16 @@ }, { "doc": "search-indexes", - "kind": "doc-check", - "target": "docs/en/search-indexes.md", - "runner": "check:docs-examples", + "kind": "shared-example", + "target": "examples/docs/vector-search.ts", + "runner": "test:examples", "status": "covered" }, { "doc": "vector-search", - "kind": "doc-check", - "target": "docs/en/vector-search.md", - "runner": "check:docs-examples", + "kind": "example", + "target": "examples/docs/vector-search.ts", + "runner": "test:examples", "status": "covered" }, { diff --git a/website/rspress.config.ts b/website/rspress.config.ts index 889483f..e3dde97 100644 --- a/website/rspress.config.ts +++ b/website/rspress.config.ts @@ -228,12 +228,13 @@ const sidebarSource: SidebarGroupSource[] = [ items: [ { en: 'find()', zh: 'find()', link: '/find' }, { en: 'findOne()', zh: 'findOne()', link: '/findOne' }, + { en: 'findOneById()', zh: 'findOneById()', link: '/find-one-by-id' }, + { en: 'findByIds()', zh: 'findByIds()', link: '/find-by-ids' }, { en: 'findPage()', zh: 'findPage()', link: '/findPage' }, { en: 'findAndCount()', zh: 'findAndCount()', link: '/find-and-count' }, { en: 'count()', zh: 'count()', link: '/count' }, { en: 'distinct()', zh: 'distinct()', link: '/distinct' }, { en: 'aggregate()', zh: 'aggregate()', link: '/aggregate' }, - { en: 'vectorSearch()', zh: 'vectorSearch()', link: '/vector-search' }, { en: 'explain()', zh: 'explain()', link: '/explain' } ] }, @@ -287,6 +288,14 @@ const sidebarSource: SidebarGroupSource[] = [ { en: 'Expression Function Reference', zh: '表达式函数参考', link: '/expression-functions' } ] }, + { + en: 'Search and vectors', + zh: 'Search 与向量', + items: [ + { en: 'vectorSearch()', zh: 'vectorSearch()', link: '/vector-search' }, + { en: 'Search indexes', zh: 'Search 索引', link: '/search-indexes' } + ] + }, { en: 'Model and Schema', zh: 'Model 与 Schema', @@ -304,9 +313,11 @@ const sidebarSource: SidebarGroupSource[] = [ en: 'Cache', zh: '缓存', items: [ + { en: 'Cache API', zh: '缓存 API', link: '/cache' }, { en: 'Cache Configuration', zh: '缓存配置', link: '/cache-configuration' }, { en: 'Cache Creation', zh: '缓存创建', link: '/cache-creation' }, - { en: 'Cache Invalidation', zh: '缓存失效', link: '/cache-invalidation' } + { en: 'Cache Invalidation', zh: '缓存失效', link: '/cache-invalidation' }, + { en: 'Pagination bookmarks', zh: '分页书签', link: '/bookmarks' } ] }, { @@ -352,7 +363,8 @@ const sidebarSource: SidebarGroupSource[] = [ { en: 'Performance Evidence', zh: '性能证据', link: '/performance-evidence' }, { en: 'Upsert Guide', zh: 'Upsert 指南', link: '/upsert-guide' }, { en: 'Quick Upsert API', zh: 'Quick Upsert API', link: '/quick-upsert' }, - { en: 'Failure Recovery Examples', zh: '失败恢复示例', link: '/failure-recovery-examples' } + { en: 'Failure Recovery Examples', zh: '失败恢复示例', link: '/failure-recovery-examples' }, + { en: 'Runtime architecture', zh: '运行时架构', link: '/runtime-architecture' } ] }, { @@ -365,7 +377,6 @@ const sidebarSource: SidebarGroupSource[] = [ { en: 'Create Indexes in Bulk', zh: '批量创建索引', link: '/create-indexes' }, { en: 'Drop Index', zh: '删除索引', link: '/drop-index' }, { en: 'List Indexes', zh: '列出索引', link: '/list-indexes' }, - { en: 'Search Indexes', zh: 'Search 索引', link: '/search-indexes' }, { en: 'GridFS', zh: 'GridFS', link: '/gridfs' }, { en: 'Database Administration', zh: '数据库管理', link: '/admin' }, { en: 'Error Code Reference', zh: '错误码参考', link: '/error-codes' }, @@ -377,7 +388,8 @@ const sidebarSource: SidebarGroupSource[] = [ en: 'Compatibility and Migration', zh: '兼容与迁移', items: [ - { en: 'Upgrade to 3.3.2', zh: '升级到 3.3.2', link: '/upgrade-3.3.2' }, + { en: 'Upgrade guide', zh: '升级指南', link: '/upgrade-3.3.2' }, + { en: 'Roadmap boundaries', zh: '路线图边界', link: '/roadmap-boundaries' }, { en: 'MongoDB Driver Compatibility', zh: 'MongoDB Driver 兼容性', link: '/mongodb-driver-compatibility' }, { en: 'MongoDB Native vs Extensions', zh: 'MongoDB 原生 vs 扩展', link: '/mongodb-native-vs-extensions' }, { en: 'findOneAnd Return Value', zh: 'findOneAnd 返回值统一', link: '/findOneAnd-return-value-unified' }, @@ -601,12 +613,12 @@ const config: UserConfig = { }, }, route: { + // Kept off the public site: legacy compatibility pages and maintainer runbooks. + // find-one-by-id and find-by-ids are public query references and stay published. exclude: [ '**/function-cache.md', '**/cache-and-function-cache.md', '**/cache-hub-migration.md', - '**/find-one-by-id.md', - '**/find-by-ids.md', '**/file-dependency-governance.md', '**/capability-traceability.md', '**/release-preflight.md', diff --git a/website/site-budget.json b/website/site-budget.json index 977b419..fee3213 100644 --- a/website/site-budget.json +++ b/website/site-budget.json @@ -2,7 +2,7 @@ "minimumHtmlFiles": 197, "maximumFiles": 450, "maximumTotalBytes": 68000000, - "maximumSingleJavaScriptBytes": 720000, + "maximumSingleJavaScriptBytes": 740000, "maximumSingleHtmlBytes": 550000, "maximumSearchIndexBytesPerLocale": 1800000, "maximumCombinedSearchIndexBytes": 3500000,