@classytic/mongokit 3.11.1 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +75 -5
  2. package/dist/{PaginationEngine-CvDFfVXT.mjs → PaginationEngine-CfQRM1yz.mjs} +16 -16
  3. package/dist/Repository-BY40uhcv.mjs +3603 -0
  4. package/dist/actions/index.d.mts +1 -1
  5. package/dist/actions/index.mjs +1 -1
  6. package/dist/adapter/index.d.mts +190 -0
  7. package/dist/adapter/index.mjs +2 -0
  8. package/dist/adapter-DTDMnUOH.mjs +158 -0
  9. package/dist/ai/index.d.mts +1 -1
  10. package/dist/ai/index.mjs +3 -3
  11. package/dist/better-auth/index.d.mts +149 -0
  12. package/dist/better-auth/index.mjs +81 -0
  13. package/dist/field-selection-Br3WPOEb.mjs +99 -0
  14. package/dist/{index-dNTmpgK8.d.mts → index-BbHaKwi5.d.mts} +33 -13
  15. package/dist/index.d.mts +678 -36
  16. package/dist/index.mjs +240 -17
  17. package/dist/{logger-DJ7egMnd.mjs → logger-DprijP5h.mjs} +1 -5
  18. package/dist/{mongooseToJsonSchema-D1sdCOmj.mjs → mongooseToJsonSchema-BYbG894y.mjs} +68 -20
  19. package/dist/{mongooseToJsonSchema-DXxjFkLB.d.mts → mongooseToJsonSchema-BgRAaXX-.d.mts} +10 -2
  20. package/dist/pagination/PaginationEngine.d.mts +3 -2
  21. package/dist/pagination/PaginationEngine.mjs +1 -1
  22. package/dist/plugins/index.d.mts +2 -2
  23. package/dist/plugins/index.mjs +2 -2
  24. package/dist/query/primitives/coercion.d.mts +1 -1
  25. package/dist/query/primitives/coercion.mjs +1 -1
  26. package/dist/query/primitives/indexes.d.mts +1 -1
  27. package/dist/{types-Dc2aDVUi.d.mts → types-BbaPZleN.d.mts} +419 -205
  28. package/dist/{update-DEJYcgfV.mjs → update-DLcSSa5b.mjs} +184 -45
  29. package/dist/utils/index.d.mts +2 -91
  30. package/dist/utils/index.mjs +5 -5
  31. package/dist/{validation-chain.plugin-B0VSdcwa.d.mts → validation-chain.plugin-CAPYCj_T.d.mts} +228 -98
  32. package/dist/validation-chain.plugin-_-WuNml2.mjs +2399 -0
  33. package/package.json +25 -5
  34. package/dist/field-selection-dI0m4RBs.mjs +0 -221
  35. package/dist/validation-chain.plugin-Dllv-cml.mjs +0 -5122
  36. /package/dist/{coercion-oM8G4b6m.d.mts → coercion-TRMmR49h.d.mts} +0 -0
  37. /package/dist/{id-resolution-e4BT9zAV.mjs → id-resolution-B0Odvvou.mjs} +0 -0
  38. /package/dist/{indexes-C26GNZRU.d.mts → indexes-CgOF7Wjd.d.mts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/mongokit",
3
- "version": "3.11.1",
3
+ "version": "3.13.0",
4
4
  "description": "Production-grade MongoDB repositories - smart pagination, events, and plugins. Built on @classytic/repo-core.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -32,6 +32,14 @@
32
32
  "types": "./dist/ai/index.d.mts",
33
33
  "default": "./dist/ai/index.mjs"
34
34
  },
35
+ "./adapter": {
36
+ "types": "./dist/adapter/index.d.mts",
37
+ "default": "./dist/adapter/index.mjs"
38
+ },
39
+ "./better-auth": {
40
+ "types": "./dist/better-auth/index.d.mts",
41
+ "default": "./dist/better-auth/index.mjs"
42
+ },
35
43
  "./query/primitives/geo": {
36
44
  "types": "./dist/query/primitives/geo.d.mts",
37
45
  "default": "./dist/query/primitives/geo.mjs"
@@ -93,8 +101,14 @@
93
101
  },
94
102
  "homepage": "https://github.com/classytic/mongokit#readme",
95
103
  "peerDependencies": {
96
- "mongoose": ">=9.4.1",
97
- "@classytic/repo-core": ">=0.2.0"
104
+ "@classytic/repo-core": ">=0.4.0",
105
+ "better-auth": ">=1.6.0",
106
+ "mongoose": ">=9.4.1"
107
+ },
108
+ "peerDependenciesMeta": {
109
+ "better-auth": {
110
+ "optional": true
111
+ }
98
112
  },
99
113
  "engines": {
100
114
  "node": ">=22"
@@ -115,22 +129,28 @@
115
129
  "lint:fix": "biome check src/ --write",
116
130
  "format": "biome format src/ --write",
117
131
  "check": "biome ci src/ --diagnostic-level=error",
132
+ "push": "classytic-push",
118
133
  "prepublishOnly": "npm run check && npm run build && npm run typecheck && npm run typecheck:tests && npm test",
119
134
  "publish:dry": "npm publish --dry-run --access public",
120
135
  "publish:npm": "npm publish --access public",
121
- "release": "npm run check && npm run build && npm run typecheck && npm run typecheck:tests && npm test && npm publish --access public",
136
+ "release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
137
+ "release": "npm run push -- main && npm run release:tag && npm publish",
122
138
  "release:patch": "npm version patch && npm run release",
123
139
  "release:minor": "npm version minor && npm run release",
124
140
  "release:major": "npm version major && npm run release"
125
141
  },
126
142
  "devDependencies": {
127
143
  "@arethetypeswrong/cli": "^0.18.2",
144
+ "@better-auth/api-key": "^1.6.9",
145
+ "@better-auth/mongo-adapter": "^1.6.9",
128
146
  "@biomejs/biome": "^2.4.10",
129
- "@classytic/repo-core": ">=0.2.0",
147
+ "@classytic/dev-tools": "^0.2.0",
148
+ "@classytic/repo-core": "^0.4.0",
130
149
  "@types/node": "^22.0.0",
131
150
  "@vitest/coverage-v8": "^3.2.4",
132
151
  "ajv": "^8.18.0",
133
152
  "ajv-formats": "^3.0.1",
153
+ "better-auth": "^1.6.9",
134
154
  "dotenv": "^17.4.2",
135
155
  "knip": "^6.3.0",
136
156
  "mongodb-memory-server": "^10.4.3",
@@ -1,221 +0,0 @@
1
- //#region src/utils/cache-keys.ts
2
- /**
3
- * Simple hash function for query parameters
4
- * Using djb2 algorithm - fast and good distribution
5
- */
6
- function hashString(str) {
7
- let hash = 5381;
8
- for (let i = 0; i < str.length; i++) hash = (hash << 5) + hash ^ str.charCodeAt(i);
9
- return (hash >>> 0).toString(16);
10
- }
11
- /**
12
- * Normalize and stringify an object for hashing
13
- * Ensures deterministic key generation regardless of property order
14
- */
15
- function stableStringify(obj) {
16
- if (obj === null || obj === void 0) return "";
17
- if (typeof obj !== "object") return String(obj);
18
- if (Array.isArray(obj)) return `[${obj.map(stableStringify).join(",")}]`;
19
- return `{${Object.keys(obj).sort().map((key) => `${key}:${stableStringify(obj[key])}`).join(",")}}`;
20
- }
21
- /**
22
- * Generate cache key for getById operations
23
- *
24
- * Includes select/populate/lean in the key so different query shapes
25
- * (e.g., plain vs populated, lean vs hydrated) never collide.
26
- *
27
- * Format: {prefix}:id:{model}:{documentId} (no options)
28
- * Format: {prefix}:id:{model}:{documentId}:{optionsHash} (with options)
29
- *
30
- * @example
31
- * byIdKey('mk', 'User', '507f1f77bcf86cd799439011')
32
- * // => 'mk:id:User:507f1f77bcf86cd799439011'
33
- * byIdKey('mk', 'User', '507f1f77bcf86cd799439011', { select: 'name email' })
34
- * // => 'mk:id:User:507f1f77bcf86cd799439011:a1b2c3d4'
35
- */
36
- function byIdKey(prefix, model, id, options) {
37
- const base = `${prefix}:id:${model}:${id}`;
38
- if (!options || options.select == null && options.populate == null && options.lean == null) return base;
39
- return `${base}:${hashString(stableStringify({
40
- s: options.select,
41
- p: options.populate,
42
- l: options.lean
43
- }))}`;
44
- }
45
- /**
46
- * Generate cache key for single-document queries
47
- *
48
- * Format: {prefix}:one:{model}:{queryHash}
49
- *
50
- * @example
51
- * byQueryKey('mk', 'User', { email: 'john@example.com' })
52
- * // => 'mk:one:User:a1b2c3d4'
53
- */
54
- function byQueryKey(prefix, model, version, query, options) {
55
- return `${prefix}:one:${model}:${version}:${hashString(stableStringify({
56
- q: query,
57
- s: options?.select,
58
- p: options?.populate
59
- }))}`;
60
- }
61
- /**
62
- * Generate cache key for paginated list queries
63
- *
64
- * Format: {prefix}:list:{model}:{version}:{queryHash}
65
- *
66
- * The version component enables efficient bulk invalidation:
67
- * - On any mutation, bump the version
68
- * - All list cache keys become invalid without scanning/deleting each
69
- *
70
- * @example
71
- * listQueryKey('mk', 'User', 1, { filters: { status: 'active' }, page: 1, limit: 20 })
72
- * // => 'mk:list:User:1:e5f6g7h8'
73
- */
74
- function listQueryKey(prefix, model, version, params) {
75
- return `${prefix}:list:${model}:${version}:${hashString(stableStringify({
76
- f: params.filters,
77
- s: params.sort,
78
- pg: params.page,
79
- lm: params.limit,
80
- af: params.after,
81
- sl: params.select,
82
- pp: params.populate,
83
- sr: params.search,
84
- md: params.mode,
85
- ln: params.lean,
86
- rp: params.readPreference,
87
- hn: params.hint,
88
- mt: params.maxTimeMS,
89
- cs: params.countStrategy
90
- }))}`;
91
- }
92
- /**
93
- * Generate cache key for collection version tag
94
- *
95
- * Format: {prefix}:ver:{model}
96
- *
97
- * Used to track mutation version for list invalidation
98
- */
99
- function versionKey(prefix, model) {
100
- return `${prefix}:ver:${model}`;
101
- }
102
- /**
103
- * Generate pattern for clearing all cache keys for a model
104
- *
105
- * Format: {prefix}:*:{model}:*
106
- *
107
- * @example
108
- * modelPattern('mk', 'User')
109
- * // => 'mk:*:User:*'
110
- */
111
- function modelPattern(prefix, model) {
112
- return `${prefix}:*:${model}:*`;
113
- }
114
- /**
115
- * Generate pattern for clearing all list cache keys for a model
116
- *
117
- * Format: {prefix}:list:{model}:*
118
- */
119
- function listPattern(prefix, model) {
120
- return `${prefix}:list:${model}:*`;
121
- }
122
- //#endregion
123
- //#region src/utils/field-selection.ts
124
- /**
125
- * Get allowed fields for a user based on their context
126
- *
127
- * @param user - User object from request.user (or null for public)
128
- * @param preset - Field preset configuration
129
- * @returns Array of allowed field names
130
- *
131
- * @example
132
- * const fields = getFieldsForUser(request.user, {
133
- * public: ['id', 'name', 'price'],
134
- * authenticated: ['description', 'features'],
135
- * admin: ['createdAt', 'internalNotes']
136
- * });
137
- */
138
- function getFieldsForUser(user, preset) {
139
- if (!preset) throw new Error("Field preset is required");
140
- const fields = [...preset.public || []];
141
- if (user) {
142
- fields.push(...preset.authenticated || []);
143
- const roles = Array.isArray(user.roles) ? user.roles : user.roles ? [user.roles] : [];
144
- if (roles.includes("admin") || roles.includes("superadmin")) fields.push(...preset.admin || []);
145
- }
146
- return [...new Set(fields)];
147
- }
148
- /**
149
- * Get Mongoose projection string for query .select()
150
- *
151
- * @param user - User object from request.user
152
- * @param preset - Field preset configuration
153
- * @returns Space-separated field names for Mongoose .select()
154
- *
155
- * @example
156
- * const projection = getMongooseProjection(request.user, fieldPresets.gymPlans);
157
- * const plans = await GymPlan.find({ organizationId }).select(projection).lean();
158
- */
159
- function getMongooseProjection(user, preset) {
160
- return getFieldsForUser(user, preset).join(" ");
161
- }
162
- /**
163
- * Filter a single object to include only allowed fields
164
- */
165
- function filterObject(obj, allowedFields) {
166
- if (!obj || typeof obj !== "object" || Array.isArray(obj)) return obj;
167
- const filtered = {};
168
- for (const field of allowedFields) if (field in obj) filtered[field] = obj[field];
169
- return filtered;
170
- }
171
- /**
172
- * Filter response data to include only allowed fields
173
- *
174
- * Use this for complex responses where Mongoose projections aren't applicable:
175
- * - Aggregation pipeline results
176
- * - Data from multiple sources
177
- * - Custom computed fields
178
- *
179
- * For simple DB queries, prefer getMongooseProjection() (10x faster)
180
- *
181
- * @param data - Data to filter
182
- * @param preset - Field preset configuration
183
- * @param user - User object from request.user
184
- * @returns Filtered data
185
- *
186
- * @example
187
- * const stats = await calculateComplexStats();
188
- * const filtered = filterResponseData(stats, fieldPresets.dashboard, request.user);
189
- * return reply.send(filtered);
190
- */
191
- function filterResponseData(data, preset, user = null) {
192
- const allowedFields = getFieldsForUser(user, preset);
193
- if (Array.isArray(data)) return data.map((item) => filterObject(item, allowedFields));
194
- return filterObject(data, allowedFields);
195
- }
196
- /**
197
- * Helper to create field presets (module-level)
198
- *
199
- * Each module should define its own field preset in its own directory.
200
- * This keeps modules independent and self-contained.
201
- *
202
- * @param config - Field configuration
203
- * @returns Field preset
204
- *
205
- * @example
206
- * // In modules/gym-plan/gym-plan.fields.ts
207
- * export const gymPlanFieldPreset = createFieldPreset({
208
- * public: ['id', 'name', 'price'],
209
- * authenticated: ['features', 'description'],
210
- * admin: ['createdAt', 'updatedAt', 'internalNotes']
211
- * });
212
- */
213
- function createFieldPreset(config) {
214
- return {
215
- public: config.public || [],
216
- authenticated: config.authenticated || [],
217
- admin: config.admin || []
218
- };
219
- }
220
- //#endregion
221
- export { byIdKey as a, listQueryKey as c, getMongooseProjection as i, modelPattern as l, filterResponseData as n, byQueryKey as o, getFieldsForUser as r, listPattern as s, createFieldPreset as t, versionKey as u };