@airoom/nextmin-node 1.4.5 → 2.0.1

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.
Files changed (41) hide show
  1. package/README.md +48 -5
  2. package/dist/api/apiRouter.d.ts +2 -0
  3. package/dist/api/apiRouter.js +68 -19
  4. package/dist/api/router/mountCrudRoutes.js +209 -221
  5. package/dist/api/router/mountFindRoutes.js +2 -49
  6. package/dist/api/router/mountSearchRoutes.js +10 -52
  7. package/dist/api/router/mountSearchRoutes_extended.js +7 -48
  8. package/dist/api/router/setupAuthRoutes.js +6 -2
  9. package/dist/api/router/utils.js +20 -7
  10. package/dist/cli.d.ts +1 -0
  11. package/dist/cli.js +83 -0
  12. package/dist/database/DatabaseAdapter.d.ts +7 -0
  13. package/dist/database/NMAdapter.d.ts +41 -0
  14. package/dist/database/NMAdapter.js +979 -0
  15. package/dist/database/QueryEngine.d.ts +14 -0
  16. package/dist/database/QueryEngine.js +215 -0
  17. package/dist/database/utils.d.ts +2 -0
  18. package/dist/database/utils.js +21 -0
  19. package/dist/index.d.ts +4 -1
  20. package/dist/index.js +11 -5
  21. package/dist/models/BaseModel.d.ts +16 -0
  22. package/dist/models/BaseModel.js +32 -4
  23. package/dist/policy/authorize.js +118 -43
  24. package/dist/schemas/Users.json +66 -30
  25. package/dist/services/RealtimeService.d.ts +20 -0
  26. package/dist/services/RealtimeService.js +93 -0
  27. package/dist/services/SchemaService.d.ts +3 -0
  28. package/dist/services/SchemaService.js +9 -5
  29. package/dist/utils/DefaultDataInitializer.js +10 -2
  30. package/dist/utils/Events.d.ts +34 -0
  31. package/dist/utils/Events.js +55 -0
  32. package/dist/utils/Logger.js +12 -10
  33. package/dist/utils/QueryCache.d.ts +16 -0
  34. package/dist/utils/QueryCache.js +106 -0
  35. package/dist/utils/SchemaLoader.d.ts +7 -2
  36. package/dist/utils/SchemaLoader.js +58 -18
  37. package/package.json +19 -4
  38. package/dist/database/InMemoryAdapter.d.ts +0 -15
  39. package/dist/database/InMemoryAdapter.js +0 -71
  40. package/dist/database/MongoAdapter.d.ts +0 -52
  41. package/dist/database/MongoAdapter.js +0 -410
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Events = exports.events = void 0;
7
+ exports.getModelEvent = getModelEvent;
8
+ const events_1 = require("events");
9
+ const Logger_1 = __importDefault(require("./Logger"));
10
+ /**
11
+ * NextMinEvents is a central event emitter for the NextMin ecosystem.
12
+ * It allows components to hook into CRUD and system events.
13
+ */
14
+ class NextMinEvents extends events_1.EventEmitter {
15
+ constructor() {
16
+ super();
17
+ // Increase max listeners to avoid warnings in complex apps
18
+ this.setMaxListeners(100);
19
+ }
20
+ /**
21
+ * Typed emit to provide better DX (Developer Experience)
22
+ */
23
+ emitEvent(event, payload) {
24
+ Logger_1.default.info('NextMinEvents', `Emitting event: ${event}`);
25
+ this.emit(event, payload);
26
+ }
27
+ }
28
+ // Singleton instance
29
+ exports.events = new NextMinEvents();
30
+ // Standard event constants
31
+ exports.Events = {
32
+ // Document events
33
+ BEFORE_CREATE: 'before:doc:create',
34
+ AFTER_CREATE: 'after:doc:create',
35
+ BEFORE_UPDATE: 'before:doc:update',
36
+ AFTER_UPDATE: 'after:doc:update',
37
+ BEFORE_DELETE: 'before:doc:delete',
38
+ AFTER_DELETE: 'after:doc:delete',
39
+ BEFORE_READ: 'before:doc:read',
40
+ AFTER_READ: 'after:doc:read',
41
+ // Auth events
42
+ AUTH_LOGIN: 'auth:login',
43
+ AUTH_LOGOUT: 'auth:logout',
44
+ AUTH_SIGNUP: 'auth:signup',
45
+ // System events
46
+ SCHEMA_UPDATE: 'schema:update',
47
+ SERVER_START: 'server:start',
48
+ SERVER_STOP: 'server:stop',
49
+ };
50
+ /**
51
+ * Helper to generate model-specific event names
52
+ */
53
+ function getModelEvent(modelName, action, phase = 'after') {
54
+ return `${modelName.toLowerCase()}:${phase}:${action}`;
55
+ }
@@ -9,8 +9,7 @@ const util_1 = require("util");
9
9
  const path_1 = __importDefault(require("path"));
10
10
  class Logger {
11
11
  constructor() {
12
- const env = process.env.NODE_ENV || 'development';
13
- this.isDevelopment = env === 'development';
12
+ this.isDevelopment = process.env.APP_MODE !== 'production' && process.env.NODE_ENV !== 'production';
14
13
  }
15
14
  getCallerInfo() {
16
15
  const originalPrepareStackTrace = Error.prepareStackTrace;
@@ -24,12 +23,11 @@ class Logger {
24
23
  !frame.getFileName()?.includes('Logger.ts') &&
25
24
  !frame.getFunctionName()?.includes('getCallerInfo'));
26
25
  if (!callerFrame) {
27
- return 'unknown:0:0';
26
+ return 'unknown:0';
28
27
  }
29
- const fileName = path_1.default.relative(process.cwd(), callerFrame.getFileName() || 'unknown');
28
+ const fileName = path_1.default.basename(callerFrame.getFileName() || 'unknown');
30
29
  const lineNumber = callerFrame.getLineNumber() || 0;
31
- const columnNumber = callerFrame.getColumnNumber() || 0;
32
- return `${fileName}:${lineNumber}:${columnNumber}`;
30
+ return `${fileName}:${lineNumber}`;
33
31
  }
34
32
  formatMessage(level, label, messages) {
35
33
  const callerInfo = this.getCallerInfo();
@@ -66,12 +64,16 @@ class Logger {
66
64
  }
67
65
  }
68
66
  warn(label, ...messages) {
69
- const formatted = this.formatMessage('WARN', label, messages);
70
- console.warn(formatted);
67
+ if (this.isDevelopment) {
68
+ const formatted = this.formatMessage('WARN', label, messages);
69
+ console.warn(formatted);
70
+ }
71
71
  }
72
72
  error(label, ...messages) {
73
- const formatted = this.formatMessage('ERROR', label, messages);
74
- console.error(formatted);
73
+ if (this.isDevelopment) {
74
+ const formatted = this.formatMessage('ERROR', label, messages);
75
+ console.error(formatted);
76
+ }
75
77
  }
76
78
  }
77
79
  exports.Logger = Logger;
@@ -0,0 +1,16 @@
1
+ export interface CacheConfig {
2
+ ttl?: number;
3
+ enabled?: boolean;
4
+ }
5
+ export declare class QueryCache {
6
+ private cache;
7
+ private enabled;
8
+ private defaultTtl;
9
+ constructor(config?: CacheConfig);
10
+ generateKey(collection: string, operation: string, args: any): Promise<string>;
11
+ private sortObjectKeys;
12
+ get<T>(key: string): Promise<T | null>;
13
+ set(key: string, value: any, ttl?: number): Promise<void>;
14
+ invalidateCollection(collection: string): Promise<void>;
15
+ private getCollectionVersion;
16
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.QueryCache = void 0;
7
+ const keyv_1 = __importDefault(require("keyv"));
8
+ const redis_1 = __importDefault(require("@keyv/redis"));
9
+ const Logger_1 = __importDefault(require("./Logger"));
10
+ class QueryCache {
11
+ constructor(config = {}) {
12
+ this.enabled = config.enabled !== false; // Enabled by default
13
+ this.defaultTtl = config.ttl || 60000; // 60 seconds default
14
+ const redisUrl = process.env.REDIS_URL;
15
+ if (redisUrl) {
16
+ try {
17
+ const store = new redis_1.default(redisUrl);
18
+ this.cache = new keyv_1.default({ store });
19
+ Logger_1.default.info('QueryCache', `Initialized with Redis store at ${redisUrl}`);
20
+ }
21
+ catch (err) {
22
+ Logger_1.default.error('QueryCache', 'Failed to initialize Redis store, falling back to basic map', err);
23
+ this.cache = new keyv_1.default();
24
+ }
25
+ }
26
+ else {
27
+ // Falls back to in-memory map
28
+ this.cache = new keyv_1.default();
29
+ Logger_1.default.info('QueryCache', 'Initialized with in-memory store (no REDIS_URL provided)');
30
+ }
31
+ this.cache.on('error', (err) => Logger_1.default.warn('QueryCache', 'Connection Error', err));
32
+ }
33
+ async generateKey(collection, operation, args) {
34
+ try {
35
+ const version = await this.getCollectionVersion(collection);
36
+ const sortedArgs = this.sortObjectKeys(args);
37
+ const strArgs = JSON.stringify(sortedArgs);
38
+ return `nm:cache:${collection}:v${version}:${operation}:${strArgs}`;
39
+ }
40
+ catch (e) {
41
+ // Fallback if JSON.stringify fails (e.g. circular refs, though unlikely in simple queries)
42
+ const v = await this.getCollectionVersion(collection);
43
+ return `nm:cache:${collection}:v${v}:${operation}:${Date.now()}`;
44
+ }
45
+ }
46
+ sortObjectKeys(obj) {
47
+ if (obj === null || typeof obj !== 'object') {
48
+ return obj;
49
+ }
50
+ if (Array.isArray(obj)) {
51
+ return obj.map(item => this.sortObjectKeys(item));
52
+ }
53
+ const sortedObj = {};
54
+ Object.keys(obj).sort().forEach(key => {
55
+ sortedObj[key] = this.sortObjectKeys(obj[key]);
56
+ });
57
+ return sortedObj;
58
+ }
59
+ async get(key) {
60
+ if (!this.enabled)
61
+ return null;
62
+ try {
63
+ const data = await this.cache.get(key);
64
+ if (data) {
65
+ // Return a deep clone to prevent external mutation of cached objects
66
+ return JSON.parse(JSON.stringify(data));
67
+ }
68
+ return null;
69
+ }
70
+ catch (err) {
71
+ Logger_1.default.warn('QueryCache', `Failed to get key ${key}`, err);
72
+ return null;
73
+ }
74
+ }
75
+ async set(key, value, ttl) {
76
+ if (!this.enabled || value === undefined)
77
+ return;
78
+ try {
79
+ // Store a clean copy
80
+ const cleanValue = JSON.parse(JSON.stringify(value));
81
+ await this.cache.set(key, cleanValue, ttl || this.defaultTtl);
82
+ }
83
+ catch (err) {
84
+ Logger_1.default.warn('QueryCache', `Failed to set key ${key}`, err);
85
+ }
86
+ }
87
+ async invalidateCollection(collection) {
88
+ if (!this.enabled)
89
+ return;
90
+ try {
91
+ // Increment collection version:
92
+ const vKey = `nm:collection:version:${collection}`;
93
+ let currentVersion = await this.cache.get(vKey) || 0;
94
+ await this.cache.set(vKey, currentVersion + 1, 0); // never expire the version
95
+ Logger_1.default.info('QueryCache', `Invalidated cache for collection: ${collection} (version bumped)`);
96
+ }
97
+ catch (err) {
98
+ Logger_1.default.warn('QueryCache', `Failed to invalidate collection ${collection}`, err);
99
+ }
100
+ }
101
+ async getCollectionVersion(collection) {
102
+ const vKey = `nm:collection:version:${collection}`;
103
+ return (await this.cache.get(vKey)) || 0;
104
+ }
105
+ }
106
+ exports.QueryCache = QueryCache;
@@ -6,6 +6,11 @@ type PublicSchema = Omit<Schema, 'attributes'> & {
6
6
  showCount: boolean;
7
7
  attributes: PublicAttributes;
8
8
  allowedMethods: Schema['allowedMethods'];
9
+ extends?: string;
10
+ group?: string;
11
+ schemaType?: string;
12
+ columnsSelector?: string[];
13
+ actions?: Schema['actions'];
9
14
  };
10
15
  export declare class SchemaLoader {
11
16
  private emitter;
@@ -33,9 +38,9 @@ export declare class SchemaLoader {
33
38
  [key: string]: Schema;
34
39
  };
35
40
  /** CLIENT/API: sanitized map with private attributes removed */
36
- getPublicSchemas(): Record<string, PublicSchema>;
41
+ getPublicSchemas(showPrivate?: boolean): Record<string, PublicSchema>;
37
42
  /** CLIENT/API convenience: array of { modelName, attributes, allowedMethods } */
38
- getPublicSchemaList(): Array<PublicSchema>;
43
+ getPublicSchemaList(showPrivate?: boolean): Array<PublicSchema>;
39
44
  /** Strip any attr marked private; also remove the `private` flag from others */
40
45
  /** Keep private/sensitive flags so the UI and policy layer can decide.
41
46
  * We only shallow-clone values to avoid leaking references.
@@ -138,13 +138,14 @@ class SchemaLoader {
138
138
  continue;
139
139
  if (Object.prototype.hasOwnProperty.call(mergedAttrs, key)) {
140
140
  const val = mergedAttrs[key];
141
+ const isInherited = !Object.prototype.hasOwnProperty.call(schema.attributes, key);
141
142
  if (Array.isArray(val) && val[0] && typeof val[0] === 'object') {
142
143
  const { unique: _u, index: _i, ...rest } = val[0];
143
- mergedAttrs[key] = [{ ...rest }];
144
+ mergedAttrs[key] = [{ ...rest, inherited: isInherited }];
144
145
  }
145
146
  else if (val && typeof val === 'object') {
146
147
  const { unique: _u, index: _i, ...rest } = val;
147
- mergedAttrs[key] = { ...rest };
148
+ mergedAttrs[key] = { ...rest, inherited: isInherited };
148
149
  }
149
150
  }
150
151
  }
@@ -153,14 +154,50 @@ class SchemaLoader {
153
154
  ...baseSchema.allowedMethods,
154
155
  ...schema.allowedMethods,
155
156
  };
157
+ // Merge access (policies/masks/reachability)
158
+ if (baseSchema.access) {
159
+ const baseAccess = baseSchema.access;
160
+ if (!schema.access)
161
+ schema.access = {};
162
+ // 1. Selective Merge for bypassPrivacy (combine roles)
163
+ if (baseAccess.bypassPrivacy?.roles) {
164
+ if (!schema.access.bypassPrivacy)
165
+ schema.access.bypassPrivacy = {};
166
+ const baseRoles = baseAccess.bypassPrivacy.roles;
167
+ const currentRoles = schema.access.bypassPrivacy.roles || [];
168
+ schema.access.bypassPrivacy.roles = Array.from(new Set([...baseRoles, ...currentRoles]));
169
+ }
170
+ // 2. Inherit masks/filters/restrictions ONLY if not defined in child
171
+ // Note: We DO NOT inherit public/authenticated/roles reachability here.
172
+ // Child schemas should manage their own reachability.
173
+ if (!schema.access.readMask)
174
+ schema.access.readMask = baseAccess.readMask;
175
+ if (!schema.access.writeDeny)
176
+ schema.access.writeDeny = baseAccess.writeDeny;
177
+ if (!schema.access.queryFilter)
178
+ schema.access.queryFilter = baseAccess.queryFilter;
179
+ if (!schema.access.createDefaults)
180
+ schema.access.createDefaults = baseAccess.createDefaults;
181
+ if (!schema.access.restrictions)
182
+ schema.access.restrictions = baseAccess.restrictions;
183
+ if (!schema.access.conditions)
184
+ schema.access.conditions = baseAccess.conditions;
185
+ }
156
186
  // Inject hidden link field to base (used for storage join)
157
187
  // Ensure hidden link field
158
188
  const linkField = 'baseId';
159
189
  if (!schema.attributes[linkField]) {
190
+ const isUserExt = baseName.toLowerCase() === 'users';
160
191
  schema.attributes[linkField] = {
161
192
  type: 'ObjectId',
162
193
  ref: baseName,
163
- private: true,
194
+ private: !isUserExt,
195
+ required: false,
196
+ label: isUserExt ? 'Associate User Account (Optional)' : 'Base Record',
197
+ show: isUserExt ? 'username' : 'id',
198
+ index: true,
199
+ unique: isUserExt,
200
+ sparse: isUserExt,
164
201
  };
165
202
  }
166
203
  }
@@ -288,13 +325,13 @@ class SchemaLoader {
288
325
  return this.schemas;
289
326
  }
290
327
  /** CLIENT/API: sanitized map with private attributes removed */
291
- getPublicSchemas() {
328
+ getPublicSchemas(showPrivate = false) {
292
329
  const out = {};
293
330
  for (const [name, s] of Object.entries(this.schemas)) {
294
331
  // if (this.nonOverridableSchemas.has(name)) continue;
295
332
  // Clone sanitized attributes and add timestamps
296
333
  const attributesWithTimestamps = {
297
- ...this.sanitizeAttributes(s.attributes),
334
+ ...this.sanitizeAttributes(s.attributes, showPrivate),
298
335
  createdAt: { type: 'datetime' },
299
336
  updatedAt: { type: 'datetime' },
300
337
  };
@@ -303,20 +340,25 @@ class SchemaLoader {
303
340
  showCount: s.showCount,
304
341
  allowedMethods: s.allowedMethods,
305
342
  attributes: attributesWithTimestamps,
343
+ extends: s.extends,
344
+ group: s.group,
345
+ schemaType: s.schemaType,
346
+ columnsSelector: s.columnsSelector,
347
+ actions: s.actions,
306
348
  };
307
349
  }
308
350
  return out;
309
351
  }
310
352
  /** CLIENT/API convenience: array of { modelName, attributes, allowedMethods } */
311
- getPublicSchemaList() {
312
- const pub = this.getPublicSchemas();
353
+ getPublicSchemaList(showPrivate = false) {
354
+ const pub = this.getPublicSchemas(showPrivate);
313
355
  return Object.values(pub);
314
356
  }
315
357
  /** Strip any attr marked private; also remove the `private` flag from others */
316
358
  /** Keep private/sensitive flags so the UI and policy layer can decide.
317
359
  * We only shallow-clone values to avoid leaking references.
318
360
  */
319
- sanitizeAttributes(attrs) {
361
+ sanitizeAttributes(attrs, showPrivate = false) {
320
362
  const out = {};
321
363
  if (!attrs || typeof attrs !== 'object' || Array.isArray(attrs))
322
364
  return out;
@@ -325,13 +367,12 @@ class SchemaLoader {
325
367
  if (Array.isArray(attr)) {
326
368
  const elem = attr[0];
327
369
  if (elem && typeof elem === 'object') {
328
- // If the inner descriptor is private, omit this field entirely from public schema
329
- if (elem.private) {
370
+ // If the inner descriptor is private and we're not showing private, omit this field entirely
371
+ if (elem.private && !showPrivate) {
330
372
  continue;
331
373
  }
332
- // Keep as an array with a single shallow-cloned descriptor (without leaking private flag)
333
- const { private: _omit, ...rest } = elem;
334
- out[key] = [{ ...rest }];
374
+ // Preserve the descriptor, including the 'private' flag if showPrivate is true
375
+ out[key] = [{ ...elem }];
335
376
  }
336
377
  else {
337
378
  // Fallback: keep as-is (no private flag to check)
@@ -341,13 +382,12 @@ class SchemaLoader {
341
382
  }
342
383
  // Single attribute object
343
384
  if (attr && typeof attr === 'object') {
344
- // If marked private, omit from public schema entirely
345
- if (attr.private) {
385
+ // If marked private and we're not showing private, omit from public schema entirely
386
+ if (attr.private && !showPrivate) {
346
387
  continue;
347
388
  }
348
- // Shallow clone and drop the private flag if present
349
- const { private: _omit, ...rest } = attr;
350
- out[key] = { ...rest };
389
+ // Preserve the descriptor, including the 'private' flag if showPrivate is true
390
+ out[key] = { ...attr };
351
391
  continue;
352
392
  }
353
393
  // Unexpected primitives — pass through
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@airoom/nextmin-node",
3
- "version": "1.4.5",
3
+ "version": "2.0.1",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "rm -rf dist && tsc --project tsconfig.json && copyfiles -u 2 \"src/schemas/**/*\" dist/schemas",
9
9
  "watch": "tsc --project tsconfig.json --watch",
10
+ "test": "vitest run",
11
+ "test:watch": "vitest",
12
+ "test:coverage": "vitest run --coverage",
10
13
  "prepublishOnly": "npm run build && npm pack --dry-run | (! grep -E '\\bsrc/|\\.map$')"
11
14
  },
12
15
  "dependencies": {
@@ -17,19 +20,31 @@
17
20
  "chokidar": "^4.0.3",
18
21
  "fast-glob": "^3.3.1",
19
22
  "jsonwebtoken": "^9.0.2",
23
+ "keyv": "^5.2.3",
20
24
  "kleur": "^4.1.5",
21
- "mongoose": "^8.17.0",
25
+ "@keyv/redis": "^4.0.1",
26
+ "mongodb": "^6.21.0",
27
+ "mongoose": "^8.18.0",
22
28
  "mongoose-autopopulate": "^1.1.0",
29
+ "mssql": "^12.2.0",
23
30
  "multer": "^2.0.2",
24
- "socket.io": "^4.7.5"
31
+ "mysql2": "^3.16.1",
32
+ "pg": "^8.17.2",
33
+ "reflect-metadata": "^0.2.2",
34
+ "socket.io": "^4.7.5",
35
+ "sqlite3": "^5.1.7",
36
+ "typeorm": "^0.3.28"
25
37
  },
26
38
  "devDependencies": {
27
39
  "@types/bcrypt": "^6.0.0",
28
40
  "@types/chokidar": "^2.1.7",
29
41
  "@types/jsonwebtoken": "^9.0.10",
30
42
  "@types/multer": "^2.0.0",
43
+ "@vitest/coverage-v8": "^4.0.17",
31
44
  "copyfiles": "^2.4.1",
32
- "typescript": "^5.3.3"
45
+ "ts-node": "^10.9.2",
46
+ "typescript": "^5.3.3",
47
+ "vitest": "^4.0.17"
33
48
  },
34
49
  "files": [
35
50
  "dist",
@@ -1,15 +0,0 @@
1
- import { DatabaseAdapter, FieldIndexSpec } from './DatabaseAdapter';
2
- import { ReadOptions, Schema } from '../models/BaseModel';
3
- export declare class InMemoryAdapter implements DatabaseAdapter {
4
- private data;
5
- connect(): Promise<void>;
6
- disconnect(): Promise<void>;
7
- registerSchemas(schemas: Record<string, Schema>): void;
8
- create(collection: string, data: any, schemaDefinition: Schema): Promise<any>;
9
- read(collection: string, query: any, limit?: number, skip?: number, schemaDefinition?: Schema, _includePrivateFields?: boolean, _options?: ReadOptions): Promise<any[]>;
10
- update(collection: string, id: string, data: any, schemaDefinition: Schema): Promise<any>;
11
- delete(collection: string, id: string, schemaDefinition: Schema): Promise<any>;
12
- count(collection: string, query: any, schemaDefinition: Schema): Promise<number>;
13
- syncIndexes(_modelName: string, _desired: FieldIndexSpec): Promise<void>;
14
- private generateId;
15
- }
@@ -1,71 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InMemoryAdapter = void 0;
4
- class InMemoryAdapter {
5
- constructor() {
6
- this.data = {};
7
- }
8
- async connect() {
9
- // No-op for in-memory
10
- }
11
- async disconnect() {
12
- // No-op for in-memory
13
- }
14
- registerSchemas(schemas) {
15
- // No-op for in-memory, but you could validate schemas here if needed
16
- }
17
- async create(collection, data, schemaDefinition) {
18
- if (!this.data[collection]) {
19
- this.data[collection] = [];
20
- }
21
- // Generate a simple id if not present
22
- if (!data.id) {
23
- data.id = `${Date.now()}_${Math.random()}`;
24
- }
25
- this.data[collection].push(data);
26
- return data;
27
- }
28
- async read(collection, query, limit, skip, schemaDefinition, _includePrivateFields, _options) {
29
- const allData = this.data[collection] || [];
30
- let results = allData.filter((item) => {
31
- return Object.entries(query).every(([key, value]) => item[key] === value);
32
- });
33
- if (limit !== undefined && skip !== undefined) {
34
- results = results.slice(skip, skip + limit);
35
- }
36
- return results;
37
- }
38
- async update(collection, id, data, schemaDefinition) {
39
- const allData = this.data[collection] || [];
40
- const index = allData.findIndex((item) => item.id === id);
41
- if (index === -1) {
42
- throw new Error(`Document with id ${id} not found in collection '${collection}'`);
43
- }
44
- this.data[collection][index] = { ...allData[index], ...data };
45
- return this.data[collection][index];
46
- }
47
- async delete(collection, id, schemaDefinition) {
48
- const allData = this.data[collection] || [];
49
- const index = allData.findIndex((item) => item.id === id);
50
- if (index === -1) {
51
- throw new Error(`Document with id ${id} not found in collection '${collection}'`);
52
- }
53
- const deleted = this.data[collection].splice(index, 1)[0];
54
- return deleted;
55
- }
56
- async count(collection, query, schemaDefinition) {
57
- const allData = this.data[collection] || [];
58
- // Count documents matching the query
59
- const results = allData.filter((item) => {
60
- return Object.entries(query).every(([key, value]) => item[key] === value);
61
- });
62
- return results.length;
63
- }
64
- async syncIndexes(_modelName, _desired) {
65
- // no-op for memory adapter
66
- }
67
- generateId() {
68
- return Math.random().toString(36).substr(2, 9);
69
- }
70
- }
71
- exports.InMemoryAdapter = InMemoryAdapter;
@@ -1,52 +0,0 @@
1
- import { DatabaseAdapter, FieldIndexSpec } from './DatabaseAdapter';
2
- import { ReadOptions, Schema as NextMinSchema } from '../models/BaseModel';
3
- export declare class MongoAdapter implements DatabaseAdapter {
4
- private url;
5
- private dbName;
6
- private connection;
7
- /** Map keyed by lowercased modelName (e.g., "users", "roles") → Mongoose Model */
8
- private models;
9
- constructor(url: string, dbName: string);
10
- connect(): Promise<void>;
11
- disconnect(): Promise<void>;
12
- /**
13
- * Register (and re-register) all schemas.
14
- * - Drops compiled models that no longer exist.
15
- * - Recompiles every current model so validators reflect latest schema.
16
- */
17
- registerSchemas(schemas: Record<string, NextMinSchema>): Promise<void>;
18
- /** Optional hook used by the router when schemas are removed. */
19
- unregisterSchemas(names: string[]): Promise<void>;
20
- /** Optional single-model drop hook. */
21
- dropModel(nameRaw: string): Promise<void>;
22
- /**
23
- * Delete a compiled model by case-insensitive match.
24
- * Works whether we compiled it as "Users" or "users".
25
- */
26
- private safeDeleteModel;
27
- private safeDropCollection;
28
- /** Build a fresh Mongoose schema from our NextMin schema definition. */
29
- private buildMongooseSchema;
30
- private mapAttribute;
31
- private mapScalar;
32
- /**
33
- * Resolve (or lazily create) the compiled model for a schema.
34
- * Uses schema.modelName as the Mongoose model name and
35
- * (schema.collection ?? modelName).toLowerCase() as the collection.
36
- */
37
- private getModel;
38
- private getSchema;
39
- private convertQueryFieldsToObjectId;
40
- private toAppObject;
41
- create(collection: string, data: any, schemaDefinition: NextMinSchema, includePrivateFields?: boolean): Promise<any>;
42
- read(collection: string, query: any, limit: number | undefined, skip: number | undefined, schemaDefinition: NextMinSchema, includePrivateFields?: boolean, options?: ReadOptions): Promise<any[]>;
43
- count(collection: string, query: any, schemaDefinition: NextMinSchema): Promise<number>;
44
- update(collection: string, id: string, data: any, schemaDefinition: NextMinSchema, includePrivateFields?: boolean): Promise<any>;
45
- delete(collection: string, id: string, schemaDefinition: NextMinSchema, includePrivateFields?: boolean): Promise<any>;
46
- private getNativeCollectionByModelName;
47
- /** Create/drop single-field indexes based on schema plan.
48
- * Only touches indexes named with the prefix below (won't touch user indexes).
49
- */
50
- private managedIndexName;
51
- syncIndexes(modelName: string, spec: FieldIndexSpec): Promise<void>;
52
- }