@classytic/mongokit 3.2.0 → 3.2.2
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 +470 -193
- package/dist/actions/index.d.mts +9 -0
- package/dist/actions/index.mjs +15 -0
- package/dist/aggregate-BAi4Do-X.mjs +767 -0
- package/dist/aggregate-CCHI7F51.d.mts +269 -0
- package/dist/ai/index.d.mts +125 -0
- package/dist/ai/index.mjs +203 -0
- package/dist/cache-keys-C8Z9B5sw.mjs +204 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/create-BuO6xt0v.mjs +55 -0
- package/dist/custom-id.plugin-B_zIs6gE.mjs +1818 -0
- package/dist/custom-id.plugin-BzZI4gnE.d.mts +893 -0
- package/dist/index.d.mts +1012 -0
- package/dist/index.mjs +1906 -0
- package/dist/limits-DsNeCx4D.mjs +299 -0
- package/dist/logger-D8ily-PP.mjs +51 -0
- package/dist/mongooseToJsonSchema-COdDEkIJ.mjs +317 -0
- package/dist/{mongooseToJsonSchema-CaRF_bCN.d.ts → mongooseToJsonSchema-Wbvjfwkn.d.mts} +16 -89
- package/dist/pagination/PaginationEngine.d.mts +93 -0
- package/dist/pagination/PaginationEngine.mjs +196 -0
- package/dist/plugins/index.d.mts +3 -0
- package/dist/plugins/index.mjs +3 -0
- package/dist/types-D-gploPr.d.mts +1241 -0
- package/dist/utils/{index.d.ts → index.d.mts} +14 -21
- package/dist/utils/index.mjs +5 -0
- package/package.json +21 -21
- package/dist/actions/index.d.ts +0 -3
- package/dist/actions/index.js +0 -5
- package/dist/ai/index.d.ts +0 -175
- package/dist/ai/index.js +0 -206
- package/dist/chunks/chunk-2ZN65ZOP.js +0 -93
- package/dist/chunks/chunk-44KXLGPO.js +0 -388
- package/dist/chunks/chunk-DEVXDBRL.js +0 -1226
- package/dist/chunks/chunk-I7CWNAJB.js +0 -46
- package/dist/chunks/chunk-JWUAVZ3L.js +0 -8
- package/dist/chunks/chunk-UE2IEXZJ.js +0 -306
- package/dist/chunks/chunk-URLJFIR7.js +0 -22
- package/dist/chunks/chunk-VWKIKZYF.js +0 -737
- package/dist/chunks/chunk-WSFCRVEQ.js +0 -7
- package/dist/index-BDn5fSTE.d.ts +0 -516
- package/dist/index.d.ts +0 -1422
- package/dist/index.js +0 -1893
- package/dist/pagination/PaginationEngine.d.ts +0 -117
- package/dist/pagination/PaginationEngine.js +0 -3
- package/dist/plugins/index.d.ts +0 -922
- package/dist/plugins/index.js +0 -6
- package/dist/types-Jni1KgkP.d.ts +0 -780
- package/dist/utils/index.js +0 -5
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import 'mongoose';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Cache Key Utilities
|
|
7
|
-
*
|
|
8
|
-
* Generates deterministic, collision-free cache keys for MongoDB queries.
|
|
9
|
-
* Key design inspired by Next.js cache tags and best practices from Stripe/Meta.
|
|
10
|
-
*/
|
|
1
|
+
import { G as PopulateSpec, at as SortSpec, et as SelectSpec } from "../types-D-gploPr.mjs";
|
|
2
|
+
import { a as isFieldUpdateAllowed, c as configureLogger, d as filterResponseData, f as getFieldsForUser, i as getSystemManagedFields, l as createError, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, p as getMongooseProjection, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel, u as createFieldPreset } from "../mongooseToJsonSchema-Wbvjfwkn.mjs";
|
|
11
3
|
|
|
4
|
+
//#region src/utils/cache-keys.d.ts
|
|
12
5
|
/**
|
|
13
6
|
* Generate cache key for getById operations
|
|
14
7
|
*
|
|
@@ -29,8 +22,8 @@ declare function byIdKey(prefix: string, model: string, id: string): string;
|
|
|
29
22
|
* // => 'mk:one:User:a1b2c3d4'
|
|
30
23
|
*/
|
|
31
24
|
declare function byQueryKey(prefix: string, model: string, query: Record<string, unknown>, options?: {
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
select?: SelectSpec;
|
|
26
|
+
populate?: PopulateSpec;
|
|
34
27
|
}): string;
|
|
35
28
|
/**
|
|
36
29
|
* Generate cache key for paginated list queries
|
|
@@ -46,13 +39,13 @@ declare function byQueryKey(prefix: string, model: string, query: Record<string,
|
|
|
46
39
|
* // => 'mk:list:User:1:e5f6g7h8'
|
|
47
40
|
*/
|
|
48
41
|
declare function listQueryKey(prefix: string, model: string, version: number, params: {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
filters?: Record<string, unknown>;
|
|
43
|
+
sort?: SortSpec;
|
|
44
|
+
page?: number;
|
|
45
|
+
limit?: number;
|
|
46
|
+
after?: string;
|
|
47
|
+
select?: SelectSpec;
|
|
48
|
+
populate?: PopulateSpec;
|
|
56
49
|
}): string;
|
|
57
50
|
/**
|
|
58
51
|
* Generate cache key for collection version tag
|
|
@@ -78,5 +71,5 @@ declare function modelPattern(prefix: string, model: string): string;
|
|
|
78
71
|
* Format: {prefix}:list:{model}:*
|
|
79
72
|
*/
|
|
80
73
|
declare function listPattern(prefix: string, model: string): string;
|
|
81
|
-
|
|
82
|
-
export { byIdKey, byQueryKey, listPattern, listQueryKey, modelPattern, versionKey };
|
|
74
|
+
//#endregion
|
|
75
|
+
export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getSystemManagedFields, isFieldUpdateAllowed, listPattern, listQueryKey, modelPattern, validateUpdateBody, versionKey };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { i as createError, t as configureLogger } from "../logger-D8ily-PP.mjs";
|
|
2
|
+
import { a as modelPattern, c as filterResponseData, i as listQueryKey, l as getFieldsForUser, n as byQueryKey, o as versionKey, r as listPattern, s as createFieldPreset, t as byIdKey, u as getMongooseProjection } from "../cache-keys-C8Z9B5sw.mjs";
|
|
3
|
+
import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-COdDEkIJ.mjs";
|
|
4
|
+
|
|
5
|
+
export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getSystemManagedFields, isFieldUpdateAllowed, listPattern, listQueryKey, modelPattern, validateUpdateBody, versionKey };
|
package/package.json
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/mongokit",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.2",
|
|
4
4
|
"description": "Production-grade MongoDB repositories with zero dependencies - smart pagination, events, and plugins",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
|
-
"main": "./dist/index.
|
|
8
|
-
"module": "./dist/index.
|
|
9
|
-
"types": "./dist/index.d.
|
|
7
|
+
"main": "./dist/index.mjs",
|
|
8
|
+
"module": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.mts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
-
"types": "./dist/index.d.
|
|
13
|
-
"default": "./dist/index.
|
|
12
|
+
"types": "./dist/index.d.mts",
|
|
13
|
+
"default": "./dist/index.mjs"
|
|
14
14
|
},
|
|
15
15
|
"./pagination": {
|
|
16
|
-
"types": "./dist/pagination/PaginationEngine.d.
|
|
17
|
-
"default": "./dist/pagination/PaginationEngine.
|
|
16
|
+
"types": "./dist/pagination/PaginationEngine.d.mts",
|
|
17
|
+
"default": "./dist/pagination/PaginationEngine.mjs"
|
|
18
18
|
},
|
|
19
19
|
"./plugins": {
|
|
20
|
-
"types": "./dist/plugins/index.d.
|
|
21
|
-
"default": "./dist/plugins/index.
|
|
20
|
+
"types": "./dist/plugins/index.d.mts",
|
|
21
|
+
"default": "./dist/plugins/index.mjs"
|
|
22
22
|
},
|
|
23
23
|
"./utils": {
|
|
24
|
-
"types": "./dist/utils/index.d.
|
|
25
|
-
"default": "./dist/utils/index.
|
|
24
|
+
"types": "./dist/utils/index.d.mts",
|
|
25
|
+
"default": "./dist/utils/index.mjs"
|
|
26
26
|
},
|
|
27
27
|
"./actions": {
|
|
28
|
-
"types": "./dist/actions/index.d.
|
|
29
|
-
"default": "./dist/actions/index.
|
|
28
|
+
"types": "./dist/actions/index.d.mts",
|
|
29
|
+
"default": "./dist/actions/index.mjs"
|
|
30
30
|
},
|
|
31
31
|
"./ai": {
|
|
32
|
-
"types": "./dist/ai/index.d.
|
|
33
|
-
"default": "./dist/ai/index.
|
|
32
|
+
"types": "./dist/ai/index.d.mts",
|
|
33
|
+
"default": "./dist/ai/index.mjs"
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
@@ -87,16 +87,16 @@
|
|
|
87
87
|
"node": ">=18"
|
|
88
88
|
},
|
|
89
89
|
"scripts": {
|
|
90
|
-
"build": "
|
|
91
|
-
"dev": "
|
|
90
|
+
"build": "tsdown",
|
|
91
|
+
"dev": "tsdown --watch",
|
|
92
92
|
"test": "vitest run",
|
|
93
93
|
"test:watch": "vitest",
|
|
94
94
|
"test:coverage": "vitest run --coverage",
|
|
95
95
|
"typecheck": "tsc --noEmit",
|
|
96
|
-
"prepublishOnly": "npm run build && npm run typecheck",
|
|
96
|
+
"prepublishOnly": "npm run build && npm run typecheck && npm test",
|
|
97
97
|
"publish:dry": "npm publish --dry-run --access public",
|
|
98
98
|
"publish:npm": "npm publish --access public",
|
|
99
|
-
"release": "npm run build && npm run typecheck && npm publish --access public",
|
|
99
|
+
"release": "npm run build && npm run typecheck && npm test && npm publish --access public",
|
|
100
100
|
"release:patch": "npm version patch && npm run release",
|
|
101
101
|
"release:minor": "npm version minor && npm run release",
|
|
102
102
|
"release:major": "npm version major && npm run release"
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"@vitest/coverage-v8": "^3.2.4",
|
|
107
107
|
"mongodb-memory-server": "^10.2.3",
|
|
108
108
|
"mongoose": "^9.1.3",
|
|
109
|
-
"
|
|
109
|
+
"tsdown": "^0.20.3",
|
|
110
110
|
"typescript": "^5.7.0",
|
|
111
111
|
"vitest": "^3.0.0"
|
|
112
112
|
}
|
package/dist/actions/index.d.ts
DELETED
package/dist/actions/index.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { aggregate_exports as aggregate, delete_exports as deleteActions, read_exports as read, update_exports as update } from '../chunks/chunk-VWKIKZYF.js';
|
|
2
|
-
export { create_exports as create } from '../chunks/chunk-I7CWNAJB.js';
|
|
3
|
-
import '../chunks/chunk-URLJFIR7.js';
|
|
4
|
-
import '../chunks/chunk-JWUAVZ3L.js';
|
|
5
|
-
import '../chunks/chunk-WSFCRVEQ.js';
|
package/dist/ai/index.d.ts
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { ClientSession, PipelineStage } from 'mongoose';
|
|
2
|
-
import { g as Plugin } from '../types-Jni1KgkP.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* AI/Vector Search Type Definitions
|
|
6
|
-
*
|
|
7
|
-
* Types for vector embedding storage, search, and similarity operations.
|
|
8
|
-
* Requires MongoDB Atlas for `$vectorSearch` aggregation.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/** Supported similarity metrics for vector search */
|
|
12
|
-
type SimilarityMetric = 'cosine' | 'euclidean' | 'dotProduct';
|
|
13
|
-
/** A single piece of content to embed — text, image, or any media */
|
|
14
|
-
interface EmbeddingInput {
|
|
15
|
-
/** Text content to embed */
|
|
16
|
-
text?: string;
|
|
17
|
-
/** Image URL or base64 data (for multimodal models like CLIP, Jina v3) */
|
|
18
|
-
image?: string;
|
|
19
|
-
/** Audio URL or base64 data */
|
|
20
|
-
audio?: string;
|
|
21
|
-
/** Arbitrary media — for custom model inputs (video frames, PDFs, etc.) */
|
|
22
|
-
media?: Record<string, unknown>;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Unified embedding function — receives structured input, returns vector.
|
|
26
|
-
* Works for text-only, multimodal, or any custom model.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```typescript
|
|
30
|
-
* // Text-only (OpenAI)
|
|
31
|
-
* const embed: EmbedFn = async ({ text }) =>
|
|
32
|
-
* openai.embeddings.create({ input: text!, model: 'text-embedding-3-small' })
|
|
33
|
-
* .then(r => r.data[0].embedding);
|
|
34
|
-
*
|
|
35
|
-
* // Multimodal (Jina CLIP v3)
|
|
36
|
-
* const embed: EmbedFn = async ({ text, image }) =>
|
|
37
|
-
* jina.embed({ input: [{ text, image }] }).then(r => r.data[0].embedding);
|
|
38
|
-
*
|
|
39
|
-
* // Local model
|
|
40
|
-
* const embed: EmbedFn = async ({ text }) =>
|
|
41
|
-
* fetch('http://localhost:11434/api/embeddings', {
|
|
42
|
-
* method: 'POST', body: JSON.stringify({ model: 'nomic-embed-text', prompt: text })
|
|
43
|
-
* }).then(r => r.json()).then(j => j.embedding);
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
type EmbedFn = (input: EmbeddingInput) => Promise<number[]>;
|
|
47
|
-
/**
|
|
48
|
-
* Batch embedding function — same contract, multiple inputs at once.
|
|
49
|
-
* Falls back to sequential EmbedFn calls if not provided.
|
|
50
|
-
*/
|
|
51
|
-
type BatchEmbedFn = (inputs: EmbeddingInput[]) => Promise<number[][]>;
|
|
52
|
-
/** Vector field configuration for a model */
|
|
53
|
-
interface VectorFieldConfig {
|
|
54
|
-
/** Field path where the vector is stored (e.g., 'embedding') */
|
|
55
|
-
path: string;
|
|
56
|
-
/** Atlas Search index name for this field */
|
|
57
|
-
index: string;
|
|
58
|
-
/** Number of dimensions in the embedding */
|
|
59
|
-
dimensions: number;
|
|
60
|
-
/** Similarity metric used by the index (informational — the index defines this) */
|
|
61
|
-
similarity?: SimilarityMetric;
|
|
62
|
-
/** Text source fields to embed from (e.g., ['title', 'description']) */
|
|
63
|
-
sourceFields?: string[];
|
|
64
|
-
/** Image/media source fields (e.g., ['imageUrl', 'thumbnailUrl']) */
|
|
65
|
-
mediaFields?: string[];
|
|
66
|
-
}
|
|
67
|
-
/** Options for vector search operations */
|
|
68
|
-
interface VectorSearchParams {
|
|
69
|
-
/** Query — vector, text string, or structured multimodal input */
|
|
70
|
-
query: number[] | string | EmbeddingInput;
|
|
71
|
-
/** Maximum number of results */
|
|
72
|
-
limit?: number;
|
|
73
|
-
/** Candidates to consider (higher = more accurate, slower). Default: limit * 10 */
|
|
74
|
-
numCandidates?: number;
|
|
75
|
-
/** Pre-filter documents before vector search */
|
|
76
|
-
filter?: Record<string, unknown>;
|
|
77
|
-
/** Use exact KNN instead of approximate (slower but precise) */
|
|
78
|
-
exact?: boolean;
|
|
79
|
-
/** Which vector field config to use (default: first configured) */
|
|
80
|
-
field?: string;
|
|
81
|
-
/** MongoDB session for transactions */
|
|
82
|
-
session?: ClientSession;
|
|
83
|
-
/** Fields to include/exclude in results */
|
|
84
|
-
project?: Record<string, 0 | 1>;
|
|
85
|
-
/** Include similarity score in results */
|
|
86
|
-
includeScore?: boolean;
|
|
87
|
-
/** Minimum score threshold (0-1 for cosine) */
|
|
88
|
-
minScore?: number;
|
|
89
|
-
/** Additional pipeline stages to append after search */
|
|
90
|
-
postPipeline?: PipelineStage[];
|
|
91
|
-
}
|
|
92
|
-
/** Vector search result with score */
|
|
93
|
-
interface ScoredResult<T = Record<string, unknown>> {
|
|
94
|
-
/** The matched document */
|
|
95
|
-
doc: T;
|
|
96
|
-
/** Similarity score from vector search */
|
|
97
|
-
score: number;
|
|
98
|
-
}
|
|
99
|
-
/** Options for the vector search plugin */
|
|
100
|
-
interface VectorPluginOptions {
|
|
101
|
-
/** Vector field configurations */
|
|
102
|
-
fields: VectorFieldConfig[];
|
|
103
|
-
/** Unified embedding function (text, image, multimodal) */
|
|
104
|
-
embedFn?: EmbedFn;
|
|
105
|
-
/** Batch embedding function for bulk operations */
|
|
106
|
-
batchEmbedFn?: BatchEmbedFn;
|
|
107
|
-
/** Auto-generate embeddings on create/update (requires embedFn) */
|
|
108
|
-
autoEmbed?: boolean;
|
|
109
|
-
/**
|
|
110
|
-
* Called when auto-embed fails (e.g., embedding service down).
|
|
111
|
-
* If provided, the write operation continues without an embedding.
|
|
112
|
-
* If not provided, the error propagates and blocks the write.
|
|
113
|
-
*/
|
|
114
|
-
onEmbedError?: (error: Error, doc: unknown) => void;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Vector Search Plugin
|
|
119
|
-
*
|
|
120
|
-
* Adds semantic similarity search to any repository using MongoDB Atlas Vector Search.
|
|
121
|
-
* Supports auto-embedding on write, text-to-vector search, and scored results.
|
|
122
|
-
*
|
|
123
|
-
* **Requires MongoDB Atlas** — `$vectorSearch` is an Atlas-only aggregation stage.
|
|
124
|
-
* Running on standalone or self-hosted MongoDB will throw an
|
|
125
|
-
* `Unrecognized pipeline stage name: '$vectorSearch'` error.
|
|
126
|
-
*
|
|
127
|
-
* @example
|
|
128
|
-
* ```typescript
|
|
129
|
-
* import { vectorPlugin } from '@classytic/mongokit/ai';
|
|
130
|
-
*
|
|
131
|
-
* // Text-only (OpenAI)
|
|
132
|
-
* const repo = new Repository(Product, [
|
|
133
|
-
* methodRegistryPlugin(),
|
|
134
|
-
* vectorPlugin({
|
|
135
|
-
* fields: [{ path: 'embedding', index: 'vec_idx', dimensions: 1536, similarity: 'cosine', sourceFields: ['title', 'description'] }],
|
|
136
|
-
* embedFn: async ({ text }) => openai.embeddings.create({ input: text!, model: 'text-embedding-3-small' }).then(r => r.data[0].embedding),
|
|
137
|
-
* autoEmbed: true,
|
|
138
|
-
* }),
|
|
139
|
-
* ]);
|
|
140
|
-
*
|
|
141
|
-
* // Multimodal (Jina CLIP v3 — text + images in one call)
|
|
142
|
-
* const repo = new Repository(Product, [
|
|
143
|
-
* methodRegistryPlugin(),
|
|
144
|
-
* vectorPlugin({
|
|
145
|
-
* fields: [{ path: 'embedding', index: 'vec_idx', dimensions: 1024, similarity: 'cosine', sourceFields: ['title'], mediaFields: ['imageUrl'] }],
|
|
146
|
-
* embedFn: async ({ text, image }) => jina.embed({ input: [{ text, image }] }).then(r => r.data[0].embedding),
|
|
147
|
-
* autoEmbed: true,
|
|
148
|
-
* }),
|
|
149
|
-
* ]);
|
|
150
|
-
*
|
|
151
|
-
* // Search by text
|
|
152
|
-
* const results = await repo.searchSimilar({ query: 'running shoes', limit: 10 });
|
|
153
|
-
*
|
|
154
|
-
* // Search by image + text (multimodal)
|
|
155
|
-
* const results = await repo.searchSimilar({ query: { text: 'red sneakers', image: 'https://...' }, limit: 10 });
|
|
156
|
-
*
|
|
157
|
-
* // Search by vector directly
|
|
158
|
-
* const results = await repo.searchSimilar({ query: [0.1, 0.2, ...], limit: 5 });
|
|
159
|
-
* ```
|
|
160
|
-
*/
|
|
161
|
-
|
|
162
|
-
interface VectorMethods {
|
|
163
|
-
searchSimilar<T = Record<string, unknown>>(params: VectorSearchParams): Promise<ScoredResult<T>[]>;
|
|
164
|
-
embed(input: EmbeddingInput | string): Promise<number[]>;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Builds the $vectorSearch pipeline stage
|
|
168
|
-
*/
|
|
169
|
-
declare function buildVectorSearchPipeline(field: VectorFieldConfig, queryVector: number[], params: VectorSearchParams): PipelineStage[];
|
|
170
|
-
/**
|
|
171
|
-
* Creates the vector search plugin
|
|
172
|
-
*/
|
|
173
|
-
declare function vectorPlugin(options: VectorPluginOptions): Plugin;
|
|
174
|
-
|
|
175
|
-
export { type BatchEmbedFn, type EmbedFn, type EmbeddingInput, type ScoredResult, type SimilarityMetric, type VectorFieldConfig, type VectorMethods, type VectorPluginOptions, type VectorSearchParams, buildVectorSearchPipeline, vectorPlugin };
|
package/dist/ai/index.js
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import '../chunks/chunk-WSFCRVEQ.js';
|
|
2
|
-
|
|
3
|
-
// src/ai/vector.plugin.ts
|
|
4
|
-
var MAX_NUM_CANDIDATES = 1e4;
|
|
5
|
-
function resolveField(fields, fieldPath) {
|
|
6
|
-
if (fieldPath) {
|
|
7
|
-
const found = fields.find((f) => f.path === fieldPath);
|
|
8
|
-
if (!found) throw new Error(`[mongokit] Vector field '${fieldPath}' not configured`);
|
|
9
|
-
return found;
|
|
10
|
-
}
|
|
11
|
-
return fields[0];
|
|
12
|
-
}
|
|
13
|
-
function toEmbeddingInput(query) {
|
|
14
|
-
return typeof query === "string" ? { text: query } : query;
|
|
15
|
-
}
|
|
16
|
-
function getNestedValue(obj, path) {
|
|
17
|
-
if (path in obj) return obj[path];
|
|
18
|
-
return path.split(".").reduce((cur, key) => {
|
|
19
|
-
if (cur != null && typeof cur === "object") return cur[key];
|
|
20
|
-
return void 0;
|
|
21
|
-
}, obj);
|
|
22
|
-
}
|
|
23
|
-
function buildInputFromDoc(data, field) {
|
|
24
|
-
const input = {};
|
|
25
|
-
if (field.sourceFields?.length) {
|
|
26
|
-
const text = field.sourceFields.map((f) => getNestedValue(data, f)).filter(Boolean).join(" ");
|
|
27
|
-
if (text.trim()) input.text = text;
|
|
28
|
-
}
|
|
29
|
-
if (field.mediaFields?.length) {
|
|
30
|
-
const firstImageField = field.mediaFields[0];
|
|
31
|
-
const imageValue = getNestedValue(data, firstImageField);
|
|
32
|
-
if (typeof imageValue === "string") input.image = imageValue;
|
|
33
|
-
if (field.mediaFields.length > 1) {
|
|
34
|
-
input.media = {};
|
|
35
|
-
for (const mf of field.mediaFields) {
|
|
36
|
-
const val = getNestedValue(data, mf);
|
|
37
|
-
if (val != null) input.media[mf] = val;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return input;
|
|
42
|
-
}
|
|
43
|
-
function hasContent(input) {
|
|
44
|
-
return !!(input.text?.trim() || input.image || input.audio || input.media && Object.keys(input.media).length);
|
|
45
|
-
}
|
|
46
|
-
function buildVectorSearchPipeline(field, queryVector, params) {
|
|
47
|
-
const limit = params.limit ?? 10;
|
|
48
|
-
const stages = [];
|
|
49
|
-
const rawCandidates = params.numCandidates ?? Math.max(limit * 10, 100);
|
|
50
|
-
const numCandidates = Math.min(Math.max(rawCandidates, limit), MAX_NUM_CANDIDATES);
|
|
51
|
-
stages.push({
|
|
52
|
-
$vectorSearch: {
|
|
53
|
-
index: field.index,
|
|
54
|
-
path: field.path,
|
|
55
|
-
queryVector,
|
|
56
|
-
numCandidates,
|
|
57
|
-
limit,
|
|
58
|
-
...params.filter && { filter: params.filter },
|
|
59
|
-
...params.exact && { exact: true }
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
const needsScore = params.includeScore !== false || params.minScore != null;
|
|
63
|
-
if (needsScore) {
|
|
64
|
-
stages.push({ $addFields: { _score: { $meta: "vectorSearchScore" } } });
|
|
65
|
-
}
|
|
66
|
-
if (params.minScore != null) {
|
|
67
|
-
stages.push({ $match: { _score: { $gte: params.minScore } } });
|
|
68
|
-
}
|
|
69
|
-
if (params.project) {
|
|
70
|
-
stages.push({ $project: { ...params.project, _score: 1 } });
|
|
71
|
-
}
|
|
72
|
-
if (params.postPipeline?.length) {
|
|
73
|
-
stages.push(...params.postPipeline);
|
|
74
|
-
}
|
|
75
|
-
return stages;
|
|
76
|
-
}
|
|
77
|
-
function vectorPlugin(options) {
|
|
78
|
-
const { fields, autoEmbed = false } = options;
|
|
79
|
-
if (!fields?.length) {
|
|
80
|
-
throw new Error("[mongokit] vectorPlugin requires at least one field config");
|
|
81
|
-
}
|
|
82
|
-
const { embedFn, batchEmbedFn } = options;
|
|
83
|
-
return {
|
|
84
|
-
name: "vector",
|
|
85
|
-
apply(repo) {
|
|
86
|
-
if (!repo.registerMethod) {
|
|
87
|
-
throw new Error("[mongokit] vectorPlugin requires methodRegistryPlugin");
|
|
88
|
-
}
|
|
89
|
-
repo.registerMethod("searchSimilar", async function searchSimilar(params) {
|
|
90
|
-
const field = resolveField(fields, params.field);
|
|
91
|
-
let queryVector;
|
|
92
|
-
if (Array.isArray(params.query)) {
|
|
93
|
-
queryVector = params.query;
|
|
94
|
-
} else {
|
|
95
|
-
if (!embedFn) {
|
|
96
|
-
throw new Error("[mongokit] Non-vector queries require embedFn in vectorPlugin options");
|
|
97
|
-
}
|
|
98
|
-
const input = toEmbeddingInput(params.query);
|
|
99
|
-
queryVector = await embedFn(input);
|
|
100
|
-
}
|
|
101
|
-
if (queryVector.length !== field.dimensions) {
|
|
102
|
-
throw new Error(
|
|
103
|
-
`[mongokit] Query vector has ${queryVector.length} dimensions, expected ${field.dimensions}`
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
const pipeline = buildVectorSearchPipeline(field, queryVector, params);
|
|
107
|
-
const agg = repo.Model.aggregate(pipeline);
|
|
108
|
-
if (params.session) agg.session(params.session);
|
|
109
|
-
const results = await agg.exec();
|
|
110
|
-
return results.map((doc) => {
|
|
111
|
-
const score = doc._score ?? 0;
|
|
112
|
-
const { _score, ...rest } = doc;
|
|
113
|
-
return { doc: rest, score };
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
repo.registerMethod("embed", async function embed(input) {
|
|
117
|
-
if (!embedFn) {
|
|
118
|
-
throw new Error("[mongokit] embed requires embedFn in vectorPlugin options");
|
|
119
|
-
}
|
|
120
|
-
return embedFn(typeof input === "string" ? { text: input } : input);
|
|
121
|
-
});
|
|
122
|
-
if (autoEmbed && embedFn) {
|
|
123
|
-
const { onEmbedError } = options;
|
|
124
|
-
const safeEmbed = async (input, doc) => {
|
|
125
|
-
try {
|
|
126
|
-
return await embedFn(input);
|
|
127
|
-
} catch (err) {
|
|
128
|
-
if (onEmbedError) {
|
|
129
|
-
onEmbedError(err, doc);
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
throw err;
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
const embedFromSource = async (data, field) => {
|
|
136
|
-
if (data[field.path] && Array.isArray(data[field.path])) return;
|
|
137
|
-
const input = buildInputFromDoc(data, field);
|
|
138
|
-
if (!hasContent(input)) return;
|
|
139
|
-
const vector = await safeEmbed(input, data);
|
|
140
|
-
if (vector) data[field.path] = vector;
|
|
141
|
-
};
|
|
142
|
-
const embedBatchFromSource = async (dataArray, field) => {
|
|
143
|
-
const toEmbed = [];
|
|
144
|
-
for (let i = 0; i < dataArray.length; i++) {
|
|
145
|
-
const data = dataArray[i];
|
|
146
|
-
if (data[field.path] && Array.isArray(data[field.path])) continue;
|
|
147
|
-
const input = buildInputFromDoc(data, field);
|
|
148
|
-
if (hasContent(input)) toEmbed.push({ idx: i, input });
|
|
149
|
-
}
|
|
150
|
-
if (!toEmbed.length) return;
|
|
151
|
-
if (batchEmbedFn) {
|
|
152
|
-
try {
|
|
153
|
-
const vectors = await batchEmbedFn(toEmbed.map((e) => e.input));
|
|
154
|
-
for (let i = 0; i < toEmbed.length; i++) {
|
|
155
|
-
dataArray[toEmbed[i].idx][field.path] = vectors[i];
|
|
156
|
-
}
|
|
157
|
-
} catch (err) {
|
|
158
|
-
if (onEmbedError) {
|
|
159
|
-
onEmbedError(err, dataArray);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
throw err;
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
for (const entry of toEmbed) {
|
|
166
|
-
const vector = await safeEmbed(entry.input, dataArray[entry.idx]);
|
|
167
|
-
if (vector) dataArray[entry.idx][field.path] = vector;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
repo.on("before:create", async (context) => {
|
|
172
|
-
if (!context.data) return;
|
|
173
|
-
for (const field of fields) {
|
|
174
|
-
await embedFromSource(context.data, field);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
repo.on("before:createMany", async (context) => {
|
|
178
|
-
if (!context.dataArray?.length) return;
|
|
179
|
-
for (const field of fields) {
|
|
180
|
-
await embedBatchFromSource(context.dataArray, field);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
repo.on("before:update", async (context) => {
|
|
184
|
-
if (!context.data) return;
|
|
185
|
-
const fieldsToEmbed = fields.filter((field) => {
|
|
186
|
-
const allFields = [...field.sourceFields ?? [], ...field.mediaFields ?? []];
|
|
187
|
-
return allFields.length > 0 && allFields.some((f) => f in context.data);
|
|
188
|
-
});
|
|
189
|
-
if (!fieldsToEmbed.length) return;
|
|
190
|
-
const existing = await repo.Model.findById(context.id).lean().session(context.session ?? null);
|
|
191
|
-
if (!existing) return;
|
|
192
|
-
for (const field of fieldsToEmbed) {
|
|
193
|
-
const merged = { ...existing, ...context.data };
|
|
194
|
-
delete merged[field.path];
|
|
195
|
-
const input = buildInputFromDoc(merged, field);
|
|
196
|
-
if (!hasContent(input)) continue;
|
|
197
|
-
const vector = await safeEmbed(input, merged);
|
|
198
|
-
if (vector) context.data[field.path] = vector;
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export { buildVectorSearchPipeline, vectorPlugin };
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// src/utils/field-selection.ts
|
|
2
|
-
function getFieldsForUser(user, preset) {
|
|
3
|
-
if (!preset) {
|
|
4
|
-
throw new Error("Field preset is required");
|
|
5
|
-
}
|
|
6
|
-
const fields = [...preset.public || []];
|
|
7
|
-
if (user) {
|
|
8
|
-
fields.push(...preset.authenticated || []);
|
|
9
|
-
const roles = Array.isArray(user.roles) ? user.roles : user.roles ? [user.roles] : [];
|
|
10
|
-
if (roles.includes("admin") || roles.includes("superadmin")) {
|
|
11
|
-
fields.push(...preset.admin || []);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return [...new Set(fields)];
|
|
15
|
-
}
|
|
16
|
-
function getMongooseProjection(user, preset) {
|
|
17
|
-
const fields = getFieldsForUser(user, preset);
|
|
18
|
-
return fields.join(" ");
|
|
19
|
-
}
|
|
20
|
-
function filterObject(obj, allowedFields) {
|
|
21
|
-
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
22
|
-
return obj;
|
|
23
|
-
}
|
|
24
|
-
const filtered = {};
|
|
25
|
-
for (const field of allowedFields) {
|
|
26
|
-
if (field in obj) {
|
|
27
|
-
filtered[field] = obj[field];
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return filtered;
|
|
31
|
-
}
|
|
32
|
-
function filterResponseData(data, preset, user = null) {
|
|
33
|
-
const allowedFields = getFieldsForUser(user, preset);
|
|
34
|
-
if (Array.isArray(data)) {
|
|
35
|
-
return data.map((item) => filterObject(item, allowedFields));
|
|
36
|
-
}
|
|
37
|
-
return filterObject(data, allowedFields);
|
|
38
|
-
}
|
|
39
|
-
function createFieldPreset(config) {
|
|
40
|
-
return {
|
|
41
|
-
public: config.public || [],
|
|
42
|
-
authenticated: config.authenticated || [],
|
|
43
|
-
admin: config.admin || []
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// src/utils/cache-keys.ts
|
|
48
|
-
function hashString(str) {
|
|
49
|
-
let hash = 5381;
|
|
50
|
-
for (let i = 0; i < str.length; i++) {
|
|
51
|
-
hash = (hash << 5) + hash ^ str.charCodeAt(i);
|
|
52
|
-
}
|
|
53
|
-
return (hash >>> 0).toString(16);
|
|
54
|
-
}
|
|
55
|
-
function stableStringify(obj) {
|
|
56
|
-
if (obj === null || obj === void 0) return "";
|
|
57
|
-
if (typeof obj !== "object") return String(obj);
|
|
58
|
-
if (Array.isArray(obj)) {
|
|
59
|
-
return "[" + obj.map(stableStringify).join(",") + "]";
|
|
60
|
-
}
|
|
61
|
-
const sorted = Object.keys(obj).sort().map((key) => `${key}:${stableStringify(obj[key])}`);
|
|
62
|
-
return "{" + sorted.join(",") + "}";
|
|
63
|
-
}
|
|
64
|
-
function byIdKey(prefix, model, id) {
|
|
65
|
-
return `${prefix}:id:${model}:${id}`;
|
|
66
|
-
}
|
|
67
|
-
function byQueryKey(prefix, model, query, options) {
|
|
68
|
-
const hashInput = stableStringify({ q: query, s: options?.select, p: options?.populate });
|
|
69
|
-
return `${prefix}:one:${model}:${hashString(hashInput)}`;
|
|
70
|
-
}
|
|
71
|
-
function listQueryKey(prefix, model, version, params) {
|
|
72
|
-
const hashInput = stableStringify({
|
|
73
|
-
f: params.filters,
|
|
74
|
-
s: params.sort,
|
|
75
|
-
pg: params.page,
|
|
76
|
-
lm: params.limit,
|
|
77
|
-
af: params.after,
|
|
78
|
-
sl: params.select,
|
|
79
|
-
pp: params.populate
|
|
80
|
-
});
|
|
81
|
-
return `${prefix}:list:${model}:${version}:${hashString(hashInput)}`;
|
|
82
|
-
}
|
|
83
|
-
function versionKey(prefix, model) {
|
|
84
|
-
return `${prefix}:ver:${model}`;
|
|
85
|
-
}
|
|
86
|
-
function modelPattern(prefix, model) {
|
|
87
|
-
return `${prefix}:*:${model}:*`;
|
|
88
|
-
}
|
|
89
|
-
function listPattern(prefix, model) {
|
|
90
|
-
return `${prefix}:list:${model}:*`;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export { byIdKey, byQueryKey, createFieldPreset, filterResponseData, getFieldsForUser, getMongooseProjection, listPattern, listQueryKey, modelPattern, versionKey };
|