@zenith-open/zenithcms-db-mongodb 0.1.0 → 1.0.0-beta.10

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.
@@ -1,643 +0,0 @@
1
- import mongoose, { Model } from 'mongoose'
2
- import { CollectionConfig, DatabaseAdapter, FindOptions, BaseOptions, AuditLogData, VersionData, WebhookDeliveryData, WebhookDeliveryRecord } from '@zenith-open/zenithcms-types'
3
- import { getModelForCollection } from './model-factory'
4
- import NodeCache from 'node-cache'
5
- import Redis from 'ioredis'
6
- import pino from 'pino'
7
-
8
- const logger = pino()
9
-
10
- // Hard ceiling on query result size to prevent memory exhaustion / DoS
11
- const MAX_QUERY_LIMIT = 500
12
- const DEFAULT_QUERY_LIMIT = 100
13
-
14
- export interface CacheLayer {
15
- get<T>(key: string): Promise<T | undefined>
16
- set<T>(key: string, value: T, collection: string): Promise<void>
17
- invalidate(collection: string): Promise<void>
18
- }
19
-
20
- export class LocalCacheLayer implements CacheLayer {
21
- private cache: NodeCache
22
- constructor() {
23
- this.cache = new NodeCache({ stdTTL: 60, checkperiod: 120 })
24
- }
25
- async get<T>(key: string): Promise<T | undefined> {
26
- return this.cache.get<T>(key)
27
- }
28
- async set<T>(key: string, value: T, collection: string): Promise<void> {
29
- this.cache.set(key, value)
30
- }
31
- async invalidate(collection: string): Promise<void> {
32
- const keys = this.cache.keys()
33
- const targets = keys.filter((k) => k.startsWith(`${collection}:`))
34
- this.cache.del(targets)
35
- }
36
- }
37
-
38
- export class RedisCacheLayer implements CacheLayer {
39
- private redis: Redis
40
- constructor(redisUrl: string) {
41
- this.redis = new Redis(redisUrl, {
42
- maxRetriesPerRequest: 3,
43
- })
44
- logger.info('MongooseAdapter: Redis_Cache_Layer Initialized')
45
- }
46
- async get<T>(key: string): Promise<T | undefined> {
47
- try {
48
- const data = await this.redis.get(key)
49
- return data ? JSON.parse(data) : undefined
50
- } catch (error: any) {
51
- logger.warn({ error: error.message }, 'RedisCacheLayer: Get failed')
52
- return undefined
53
- }
54
- }
55
- async set<T>(key: string, value: T, collection: string): Promise<void> {
56
- try {
57
- const setKey = `zenith:cache:collection:${collection}`
58
- await this.redis.setex(key, 60, JSON.stringify(value))
59
- await this.redis.sadd(setKey, key)
60
- await this.redis.expire(setKey, 120)
61
- } catch (error: any) {
62
- logger.warn({ error: error.message }, 'RedisCacheLayer: Set failed')
63
- }
64
- }
65
- async invalidate(collection: string): Promise<void> {
66
- try {
67
- const setKey = `zenith:cache:collection:${collection}`
68
- const keys = await this.redis.smembers(setKey)
69
- if (keys.length > 0) {
70
- await this.redis.del(...keys)
71
- }
72
- await this.redis.del(setKey)
73
- } catch (error: any) {
74
- logger.warn({ error: error.message }, 'RedisCacheLayer: Invalidate failed')
75
- }
76
- }
77
- }
78
-
79
- /**
80
- * Mongoose Database Adapter — Hardened Edition
81
- * ──────────────────────────────────────────
82
- * High-performance implementation for MongoDB.
83
- * Features: Neural Cache Layer, automatic session management, and health monitoring.
84
- */
85
- export class MongooseAdapter implements DatabaseAdapter {
86
- name = 'mongoose'
87
- private models: Record<string, Model<unknown>> = {}
88
- private cache: CacheLayer
89
-
90
- constructor(private uri: string) {
91
- const redisUrl = process.env.REDIS_URL
92
- if (redisUrl) {
93
- this.cache = new RedisCacheLayer(redisUrl)
94
- } else {
95
- this.cache = new LocalCacheLayer()
96
- logger.warn('MongooseAdapter: Local_Cache_Layer Initialized (Warning: Cache desync risk under horizontal scaling)')
97
- }
98
- logger.info('MongooseAdapter: Neural_Cache_Layer Initialized')
99
- }
100
-
101
- async connect(): Promise<void> {
102
- try {
103
- const poolMax = parseInt(process.env.DB_POOL_SIZE || '10', 10)
104
- await mongoose.connect(this.uri, {
105
- serverSelectionTimeoutMS: 5000,
106
- socketTimeoutMS: 45000,
107
- maxPoolSize: poolMax,
108
- })
109
- logger.info('MongooseAdapter: Connected to MongoDB')
110
- this._initSystemModels()
111
- } catch (error: any) {
112
- logger.error({ error: error.message }, 'MongooseAdapter: Connection failed')
113
- throw error
114
- }
115
- }
116
-
117
- async disconnect(): Promise<void> {
118
- await mongoose.disconnect()
119
- logger.info('MongooseAdapter: Disconnected')
120
- }
121
-
122
- getHealth(): 'ok' | 'connecting' | 'disconnected' | 'error' {
123
- const state = mongoose.connection.readyState
124
- switch (state) {
125
- case 0:
126
- return 'disconnected'
127
- case 1:
128
- return 'ok'
129
- case 2:
130
- return 'connecting'
131
- case 3:
132
- return 'disconnected'
133
- default:
134
- return 'error'
135
- }
136
- }
137
-
138
- private _initSystemModels() {
139
- // Ensure system models are indexed for performance
140
- if (!mongoose.models['AuditLog']) {
141
- const schema = new mongoose.Schema(
142
- {
143
- timestamp: { type: Date, default: Date.now, index: true },
144
- collectionName: { type: String, index: true },
145
- documentId: { type: String, index: true },
146
- userId: { type: String, index: true },
147
- userEmail: { type: String },
148
- userName: { type: String },
149
- action: { type: String, index: true },
150
- changes: { type: mongoose.Schema.Types.Mixed },
151
- ip: { type: String },
152
- userAgent: { type: String },
153
- status: { type: String, index: true },
154
- resource: { type: String },
155
- siteId: { type: String, index: true },
156
- hash: { type: String },
157
- previousHash: { type: String },
158
- },
159
- { strict: false }
160
- )
161
- schema.index({ siteId: 1, timestamp: -1 })
162
- schema.index({ action: 1, timestamp: -1 })
163
- mongoose.model('AuditLog', schema)
164
- }
165
- if (!mongoose.models['Version']) {
166
- const schema = new mongoose.Schema(
167
- {
168
- timestamp: { type: Date, default: Date.now, index: true },
169
- collectionSlug: { type: String, index: true },
170
- documentId: { type: String, index: true },
171
- },
172
- { strict: false }
173
- )
174
- mongoose.model('Version', schema)
175
- }
176
- if (!mongoose.models['flows']) {
177
- const schema = new mongoose.Schema(
178
- {
179
- name: { type: String, required: true },
180
- description: { type: String },
181
- active: { type: Boolean, default: false },
182
- trigger: { type: mongoose.Schema.Types.Mixed, default: {} },
183
- steps: { type: mongoose.Schema.Types.Mixed, default: [] },
184
- },
185
- { timestamps: true, strict: false }
186
- )
187
- mongoose.model('flows', schema)
188
- }
189
- if (!mongoose.models['z_migrations']) {
190
- const schema = new mongoose.Schema(
191
- {
192
- name: { type: String, required: true, unique: true, index: true },
193
- batch: { type: Number, required: true },
194
- executedAt: { type: Date, default: Date.now },
195
- },
196
- { strict: true }
197
- )
198
- mongoose.model('z_migrations', schema)
199
- }
200
- if (!mongoose.models['z_collections']) {
201
- const schema = new mongoose.Schema(
202
- {
203
- name: { type: String, required: true },
204
- slug: { type: String, required: true, unique: true, index: true },
205
- labels: { singular: { type: String }, plural: { type: String } },
206
- drafts: { type: Boolean, default: false },
207
- timestamps: { type: Boolean, default: true },
208
- fields: { type: mongoose.Schema.Types.Mixed, default: [] },
209
- },
210
- { timestamps: true, strict: false }
211
- )
212
- mongoose.model('z_collections', schema)
213
- }
214
- if (!mongoose.models['z_components']) {
215
- const schema = new mongoose.Schema(
216
- {
217
- slug: { type: String, required: true, unique: true, index: true },
218
- displayName: { type: String, required: true },
219
- category: { type: String, default: 'General' },
220
- icon: { type: String, default: 'Box' },
221
- description: { type: String },
222
- fields: { type: mongoose.Schema.Types.Mixed, default: [] },
223
- },
224
- { timestamps: true, strict: false }
225
- )
226
- mongoose.model('z_components', schema)
227
- }
228
- if (!mongoose.models['z_presence']) {
229
- const schema = new mongoose.Schema(
230
- {
231
- userId: { type: String, required: true },
232
- email: { type: String, required: true },
233
- collectionName: { type: String, required: true },
234
- documentId: { type: String, required: true },
235
- lastActive: { type: Number, required: true },
236
- },
237
- { timestamps: false, strict: true }
238
- )
239
- mongoose.model('z_presence', schema)
240
- }
241
- if (!mongoose.models['Lock']) {
242
- const schema = new mongoose.Schema(
243
- {
244
- collectionName: { type: String, required: true, index: true },
245
- documentId: { type: String, required: true, index: true },
246
- siteId: { type: String, index: true },
247
- lockedBy: { type: String, required: true },
248
- lockedByEmail: { type: String, required: true },
249
- lockedAt: { type: Date, default: Date.now },
250
- lockExpiresAt: { type: Date, required: true },
251
- },
252
- { collection: 'z_locks', timestamps: false }
253
- )
254
- schema.index({ collectionName: 1, documentId: 1, siteId: 1 }, { unique: true })
255
- mongoose.model('Lock', schema)
256
- }
257
- }
258
-
259
- async registerCollection(config: CollectionConfig): Promise<void> {
260
- console.log(`[MongooseAdapter] Registering collection: ${config.slug}`)
261
- const model = getModelForCollection(config)
262
- console.log(`[MongooseAdapter] Successfully registered model: ${model.modelName}`)
263
- this.models[config.slug] = model
264
- }
265
-
266
- async getExistingCollections(): Promise<string[]> {
267
- const db = mongoose.connection.db
268
- if (!db) return []
269
- const collections = await db.listCollections().toArray()
270
- return collections.map((c) => c.name)
271
- }
272
-
273
- private getModel(collection: string): Model<unknown> {
274
- if (collection === 'flows') return mongoose.models['flows']
275
-
276
- let resolvedCollection = collection
277
- if (collection === 'users') resolvedCollection = 'User'
278
- if (collection === 'z_sites' || collection === 'sites') resolvedCollection = 'Site'
279
- if (collection === 'z_workspaces' || collection === 'workspaces') resolvedCollection = 'Workspace'
280
- if (collection === 'z_password_resets') resolvedCollection = 'z_password_resets'
281
- if (collection === 'z_api_keys') resolvedCollection = 'z_api_keys'
282
- if (collection === 'z_migrations') resolvedCollection = 'z_migrations'
283
- if (collection === 'z_collections') resolvedCollection = 'z_collections'
284
- if (collection === 'z_components') resolvedCollection = 'z_components'
285
- if (collection === 'z_presence') resolvedCollection = 'z_presence'
286
- if (collection === 'z_locks' || collection === 'locks') resolvedCollection = 'Lock'
287
- if (collection === 'z_webhook_configs') resolvedCollection = 'WebhookConfig'
288
- if (collection === 'z_plugins') resolvedCollection = 'Plugin'
289
- if (collection === 'audit_logs' || collection === 'z_audit_logs') resolvedCollection = 'AuditLog'
290
- if (collection === 'versions' || collection === 'z_versions') resolvedCollection = 'Version'
291
-
292
- const model = this.models[resolvedCollection] || mongoose.models[resolvedCollection]
293
- if (!model) throw new Error(`Collection "${collection}" not registered`)
294
- return model
295
- }
296
-
297
- private _getCacheKey(collection: string, query: unknown, options: unknown): string {
298
- const sortObject = (obj: any): any => {
299
- if (obj === null || typeof obj !== 'object') return obj
300
- if (Array.isArray(obj)) return obj.map(sortObject)
301
- return Object.keys(obj).sort().reduce((acc: any, key: string) => {
302
- acc[key] = sortObject(obj[key])
303
- return acc
304
- }, {})
305
- }
306
- const siteId = (options as any)?.siteId || (options as any)?.tenantId || (globalThis as any).zenithAls?.getStore()?.siteId
307
- const enrichedQuery = siteId ? { ...(query as Record<string, unknown>), siteId } : query
308
- return `${collection}:${JSON.stringify(sortObject(enrichedQuery))}:${JSON.stringify(sortObject(options))}`
309
- }
310
-
311
- async find<T = unknown>(
312
- collection: string,
313
- query: Record<string, unknown>,
314
- options: FindOptions = {}
315
- ): Promise<T[]> {
316
- const cacheKey = this._getCacheKey(collection, query, options)
317
- const cached = await this.cache.get<T[]>(cacheKey)
318
- if (cached) return cached
319
-
320
- const globalAot = (globalThis as any).zenithAotBridge
321
- if (globalAot && globalAot.hasQuery(collection, 'find')) {
322
- const model = this.getModel(collection)
323
- const docs = await globalAot.executeQuery(collection, 'find', mongoose.connection.db, model, this._normalizeQuery(query, options), options)
324
- await this.cache.set(cacheKey, docs, collection)
325
- return docs
326
- }
327
-
328
- const model = this.getModel(collection)
329
- const normalizedQuery = this._normalizeQuery(query, options);
330
- const q = model.find(normalizedQuery)
331
-
332
- if (options.select) q.select(options.select)
333
- if (options.populate) {
334
- const populateArr = Array.isArray(options.populate) ? options.populate : [options.populate]
335
- populateArr.forEach((p: any) => q.populate(p))
336
- }
337
-
338
- const requestedLimit = options.limit ?? DEFAULT_QUERY_LIMIT
339
- const limit = Math.min(requestedLimit, MAX_QUERY_LIMIT)
340
- const docs = (await q
341
- .sort((options.sort as any) || { createdAt: -1 })
342
- .skip(options.skip || 0)
343
- .limit(limit)
344
- .session(options.session as any)
345
- .lean()
346
- .exec()) as T[]
347
-
348
- await this.cache.set(cacheKey, docs, collection)
349
- return docs
350
- }
351
-
352
- async findOne<T = unknown>(
353
- collection: string,
354
- query: Record<string, unknown>,
355
- options: FindOptions = {}
356
- ): Promise<T | null> {
357
- const cacheKey = this._getCacheKey(collection, query, options)
358
- const cached = await this.cache.get<T>(cacheKey)
359
- if (cached) return cached
360
-
361
- const model = this.getModel(collection)
362
- const q = model.findOne(this._normalizeQuery(query, options))
363
-
364
- if (options.select) q.select(options.select)
365
- if (options.populate) {
366
- const populateArr = Array.isArray(options.populate) ? options.populate : [options.populate]
367
- populateArr.forEach((p: any) => q.populate(p))
368
- }
369
-
370
- const doc = (await q
371
- .session(options.session as any)
372
- .lean()
373
- .exec()) as T | null
374
- if (doc) await this.cache.set(cacheKey, doc, collection)
375
- return doc
376
- }
377
-
378
- private async _invalidateCache(collection: string) {
379
- await this.cache.invalidate(collection)
380
- }
381
-
382
- async create<T = unknown>(
383
- collection: string,
384
- data: Partial<T>,
385
- options: BaseOptions = {}
386
- ): Promise<T> {
387
- // Inject tenant scoping into created documents
388
- const siteId = options?.siteId || options?.tenantId || (globalThis as any).zenithAls?.getStore()?.siteId
389
- const enrichedData = siteId && !(data as any).siteId
390
- ? { ...data, siteId }
391
- : data
392
-
393
- const globalAot = (globalThis as any).zenithAotBridge
394
- if (globalAot && globalAot.hasQuery(collection, 'create')) {
395
- const model = this.getModel(collection)
396
- const doc = await globalAot.executeQuery(collection, 'create', mongoose.connection.db, model, enrichedData, options)
397
- await this._invalidateCache(collection)
398
- return doc as T
399
- }
400
-
401
- const model = this.getModel(collection)
402
- const [doc] = await model.create([enrichedData] as any, { session: options.session as any })
403
- await this._invalidateCache(collection)
404
- return doc.toObject() as T
405
- }
406
-
407
- async update<T = unknown>(
408
- collection: string,
409
- id: string,
410
- data: Partial<T>,
411
- options: BaseOptions = {}
412
- ): Promise<T | null> {
413
- const model = this.getModel(collection)
414
- const siteId = options?.siteId || options?.tenantId || (globalThis as any).zenithAls?.getStore()?.siteId
415
- const filter: Record<string, unknown> = { _id: id }
416
- if (siteId) filter.siteId = siteId
417
- // Atomic optimistic locking: include expected _version in the filter
418
- if (options.expectedVersion !== undefined) {
419
- filter._version = options.expectedVersion
420
- }
421
- const doc = await model
422
- .findOneAndUpdate(
423
- filter,
424
- { $set: data },
425
- {
426
- new: true,
427
- session: options.session as any,
428
- runValidators: true,
429
- }
430
- )
431
- .lean()
432
- .exec()
433
- await this._invalidateCache(collection)
434
- return doc as T | null
435
- }
436
-
437
- private _normalizeQuery(query: Record<string, unknown>, options?: BaseOptions): Record<string, unknown> {
438
- const normalized = { ...query }
439
- if ('id' in normalized) {
440
- normalized._id = normalized.id
441
- delete normalized.id
442
- }
443
- // Inject tenant scoping from options to prevent cross-tenant data access
444
- const siteId = options?.siteId || options?.tenantId || (globalThis as any).zenithAls?.getStore()?.siteId
445
- if (siteId && !normalized.siteId) {
446
- normalized.siteId = siteId
447
- }
448
- return normalized
449
- }
450
-
451
- async findOneAndUpdate<T = unknown>(
452
- collection: string,
453
- query: Record<string, unknown>,
454
- update: Record<string, unknown>,
455
- options?: BaseOptions & { returnDocument?: 'before' | 'after' }
456
- ): Promise<T | null> {
457
- const model = this.getModel(collection)
458
- const normalized = this._normalizeQuery(query, options)
459
- const returnDoc = options?.returnDocument === 'after' ? true : false
460
- const doc = await model
461
- .findOneAndUpdate(
462
- normalized,
463
- { $set: update },
464
- {
465
- new: returnDoc,
466
- session: options?.session as any,
467
- runValidators: true,
468
- }
469
- )
470
- .lean()
471
- .exec()
472
- return doc as T | null
473
- }
474
-
475
- async updateMany(
476
- collection: string,
477
- query: Record<string, unknown>,
478
- data: unknown,
479
- options: BaseOptions = {}
480
- ): Promise<number> {
481
- const model = this.getModel(collection)
482
- const result = await model.updateMany(this._normalizeQuery(query, options), { $set: data } as any, {
483
- session: options.session as any,
484
- })
485
- await this._invalidateCache(collection)
486
- return result.modifiedCount
487
- }
488
-
489
- async delete(collection: string, id: string, options: BaseOptions = {}): Promise<boolean> {
490
- const model = this.getModel(collection)
491
- const siteId = options?.siteId || options?.tenantId || (globalThis as any).zenithAls?.getStore()?.siteId
492
- const filter: Record<string, unknown> = { _id: id }
493
- if (siteId) filter.siteId = siteId
494
- const result = await model.findOneAndDelete(filter, { session: options.session as any })
495
- await this._invalidateCache(collection)
496
- return !!result
497
- }
498
-
499
- async deleteMany(
500
- collection: string,
501
- query: Record<string, unknown>,
502
- options: BaseOptions = {}
503
- ): Promise<number> {
504
- const model = this.getModel(collection)
505
- const result = await model.deleteMany(this._normalizeQuery(query, options), { session: options.session as any })
506
- await this._invalidateCache(collection)
507
- return result.deletedCount
508
- }
509
-
510
- async count(collection: string, query: Record<string, unknown>, options?: BaseOptions): Promise<number> {
511
- const model = this.getModel(collection)
512
- return model.countDocuments(this._normalizeQuery(query, options))
513
- }
514
-
515
- async aggregate<T = unknown>(collection: string, pipeline: unknown[], options?: BaseOptions): Promise<T[]> {
516
- const model = this.getModel(collection)
517
- const enrichedPipeline = [...pipeline] as any[]
518
- // Inject tenant scoping — prepend $match stage to prevent cross-tenant data leaks
519
- const siteId = options?.siteId || options?.tenantId || (globalThis as any).zenithAls?.getStore()?.siteId
520
- if (siteId) {
521
- enrichedPipeline.unshift({ $match: { siteId } })
522
- }
523
- return model.aggregate(enrichedPipeline).exec() as Promise<T[]>
524
- }
525
-
526
- async transaction<T>(fn: (session: any) => Promise<T>): Promise<T> {
527
- try {
528
- const session = await mongoose.startSession()
529
- try {
530
- let result: T
531
- await session.withTransaction(async () => {
532
- result = await fn(session)
533
- })
534
- return result! as T
535
- } catch (error: any) {
536
- // Fallback for standalone MongoDB (no replica set)
537
- if (error.message?.includes('replica set') || error.codeName === 'NotAReplicaSet') {
538
- if (process.env.NODE_ENV === 'production') {
539
- throw new Error('FATAL: MongoDB must be running as a Replica Set in production to guarantee ACID transactions. Standalone MongoDB instances are strictly forbidden because they silently drop transactions and risk data corruption.')
540
- }
541
- logger.warn(
542
- 'Transactions not supported on this MongoDB instance. Running without transaction.'
543
- )
544
- return await fn(undefined)
545
- }
546
- throw error
547
- } finally {
548
- session.endSession()
549
- }
550
- } catch (sessionError: any) {
551
- // If we can't even start a session
552
- if (process.env.NODE_ENV === 'production') {
553
- throw new Error(`FATAL: Failed to start MongoDB session in production: ${sessionError.message}. Replica Set is required.`)
554
- }
555
- logger.warn(
556
- { err: sessionError.message },
557
- 'Failed to start MongoDB session. Running without transaction.'
558
- )
559
- return await fn(undefined)
560
- }
561
- }
562
-
563
- async createAuditLog(data: AuditLogData, options?: BaseOptions): Promise<void> {
564
- const AuditModel = mongoose.models['AuditLog']
565
- if (AuditModel) {
566
- if (options?.session) {
567
- await AuditModel.create([data], { session: options.session as any })
568
- } else {
569
- await AuditModel.create(data)
570
- }
571
- }
572
- }
573
-
574
- async createVersion(data: VersionData, options?: BaseOptions): Promise<void> {
575
- const VersionModel = mongoose.models['Version']
576
- if (VersionModel) {
577
- if (options?.session) {
578
- await VersionModel.create([data], { session: options.session as any })
579
- } else {
580
- await VersionModel.create(data)
581
- }
582
- }
583
- }
584
-
585
- async getVersions(collection: string, documentId: string): Promise<VersionData[]> {
586
- const VersionModel = mongoose.models['Version']
587
- if (!VersionModel) return []
588
- return VersionModel.find({ collectionName: collection, documentId })
589
- .sort({ timestamp: -1 })
590
- .lean()
591
- .exec() as any as Promise<VersionData[]>
592
- }
593
-
594
- async createWebhookDelivery(data: WebhookDeliveryData): Promise<void> {
595
- const WebhookModel = mongoose.models['WebhookDelivery']
596
- if (WebhookModel) await WebhookModel.create(data)
597
- }
598
-
599
- async getWebhookDeliveries(webhookId: string, limit = 50): Promise<WebhookDeliveryRecord[]> {
600
- const WebhookModel = mongoose.models['WebhookDelivery']
601
- if (!WebhookModel) return []
602
- const docs = await WebhookModel.find({ webhookId })
603
- .sort({ timestamp: -1 })
604
- .limit(limit)
605
- .lean()
606
- return docs.map((d: any) => ({
607
- id: d._id?.toString() || d.id,
608
- webhookId: d.webhookId,
609
- collectionSlug: d.collectionSlug,
610
- event: d.event,
611
- url: d.url,
612
- payload: d.payload,
613
- success: d.success,
614
- responseStatus: d.responseStatus,
615
- timestamp: d.timestamp,
616
- }))
617
- }
618
-
619
- async search<T = unknown>(
620
- collection: string,
621
- query: string,
622
- fields: string[],
623
- limit = 10,
624
- options?: BaseOptions
625
- ): Promise<T[]> {
626
- const model = this.getModel(collection)
627
- const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
628
- const regex = { $regex: escaped, $options: 'i' }
629
- const orQuery = fields.map((f) => ({ [f]: regex }))
630
-
631
- const findQuery: Record<string, any> = { $or: orQuery }
632
- const siteId = options?.siteId || options?.tenantId || (globalThis as any).zenithAls?.getStore()?.siteId
633
- if (siteId) {
634
- findQuery.siteId = siteId
635
- }
636
-
637
- return model
638
- .find(findQuery)
639
- .limit(Math.min(limit, 50))
640
- .lean()
641
- .exec() as Promise<T[]>
642
- }
643
- }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from './MongooseAdapter'
2
- export * from './model-factory'