@mikro-orm/core 7.0.0-dev.139 → 7.0.0-dev.140

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.
@@ -168,22 +168,51 @@ export class DatabaseDriver {
168
168
  };
169
169
  }
170
170
  createCursorCondition(definition, offsets, inverse, meta) {
171
- const createCondition = (prop, direction, offset, eq = false) => {
172
- if (offset === null) {
173
- throw CursorError.missingValue(meta.className, prop);
174
- }
171
+ const createCondition = (prop, direction, offset, eq = false, path = prop) => {
175
172
  if (Utils.isPlainObject(direction)) {
173
+ if (offset === undefined) {
174
+ throw CursorError.missingValue(meta.className, path);
175
+ }
176
176
  const value = Utils.keys(direction).reduce((o, key) => {
177
- if (Utils.isEmpty(offset[key])) {
178
- throw CursorError.missingValue(meta.className, `${prop}.${key}`);
179
- }
180
- Object.assign(o, createCondition(key, direction[key], offset[key], eq));
177
+ Object.assign(o, createCondition(key, direction[key], offset?.[key], eq, `${path}.${key}`));
181
178
  return o;
182
179
  }, {});
183
- return ({ [prop]: value });
180
+ return { [prop]: value };
184
181
  }
185
- const desc = direction === QueryOrderNumeric.DESC || direction.toString().toLowerCase() === 'desc';
186
- const operator = Utils.xor(desc, inverse) ? '$lt' : '$gt';
182
+ const isDesc = direction === QueryOrderNumeric.DESC || direction.toString().toLowerCase() === 'desc';
183
+ const dirStr = direction.toString().toLowerCase();
184
+ let nullsFirst;
185
+ if (dirStr.includes('nulls first')) {
186
+ nullsFirst = true;
187
+ }
188
+ else if (dirStr.includes('nulls last')) {
189
+ nullsFirst = false;
190
+ }
191
+ else {
192
+ // Default: NULLS LAST for ASC, NULLS FIRST for DESC (matches most databases)
193
+ nullsFirst = isDesc;
194
+ }
195
+ const operator = Utils.xor(isDesc, inverse) ? '$lt' : '$gt';
196
+ // For leaf-level properties, undefined means missing value
197
+ if (offset === undefined) {
198
+ throw CursorError.missingValue(meta.className, path);
199
+ }
200
+ // Handle null offset (intentional null cursor value)
201
+ if (offset === null) {
202
+ if (eq) {
203
+ // Equal to null
204
+ return { [prop]: null };
205
+ }
206
+ // Strict comparison with null cursor value
207
+ // hasItemsAfterNull: forward + nullsFirst, or backward + nullsLast
208
+ const hasItemsAfterNull = Utils.xor(nullsFirst, inverse);
209
+ if (hasItemsAfterNull) {
210
+ return { [prop]: { $ne: null } };
211
+ }
212
+ // No items after null in this direction, return impossible condition
213
+ return { [prop]: [] };
214
+ }
215
+ // Non-null offset
187
216
  return { [prop]: { [operator + (eq ? 'e' : '')]: offset } };
188
217
  };
189
218
  const [order, ...otherOrders] = definition;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
3
  "type": "module",
4
- "version": "7.0.0-dev.139",
4
+ "version": "7.0.0-dev.140",
5
5
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
6
6
  "exports": {
7
7
  "./package.json": "./package.json",
package/utils/Cursor.js CHANGED
@@ -95,15 +95,22 @@ export class Cursor {
95
95
  from(entity) {
96
96
  const processEntity = (entity, prop, direction, object = false) => {
97
97
  if (Utils.isPlainObject(direction)) {
98
+ const unwrapped = Reference.unwrapReference(entity[prop]);
99
+ // Check if the relation is loaded - for nested properties, undefined means not populated
100
+ if (Utils.isEntity(unwrapped) && !helper(unwrapped).isInitialized()) {
101
+ throw CursorError.entityNotPopulated(entity, prop);
102
+ }
98
103
  return Utils.keys(direction).reduce((o, key) => {
99
- Object.assign(o, processEntity(Reference.unwrapReference(entity[prop]), key, direction[key], true));
104
+ Object.assign(o, processEntity(unwrapped, key, direction[key], true));
100
105
  return o;
101
106
  }, {});
102
107
  }
103
- if (entity[prop] == null) {
104
- throw CursorError.entityNotPopulated(entity, prop);
105
- }
106
108
  let value = entity[prop];
109
+ // Allow null/undefined values in cursor - they will be handled in createCursorCondition
110
+ // undefined can occur with forceUndefined config option which converts null to undefined
111
+ if (value == null) {
112
+ return object ? { [prop]: null } : null;
113
+ }
107
114
  if (Utils.isEntity(value, true)) {
108
115
  value = helper(value).getPrimaryKey();
109
116
  }
@@ -131,7 +138,13 @@ export class Cursor {
131
138
  */
132
139
  static for(meta, entity, orderBy) {
133
140
  const definition = this.getDefinition(meta, orderBy);
134
- return Cursor.encode(definition.map(([key]) => entity[key]));
141
+ return Cursor.encode(definition.map(([key]) => {
142
+ const value = entity[key];
143
+ if (value === undefined) {
144
+ throw CursorError.missingValue(meta.className, key);
145
+ }
146
+ return value;
147
+ }));
135
148
  }
136
149
  static encode(value) {
137
150
  return Buffer.from(JSON.stringify(value)).toString('base64url');
package/utils/Utils.js CHANGED
@@ -123,7 +123,7 @@ export function parseJsonSafe(value) {
123
123
  }
124
124
  export class Utils {
125
125
  static PK_SEPARATOR = '~~~';
126
- static #ORM_VERSION = '7.0.0-dev.139';
126
+ static #ORM_VERSION = '7.0.0-dev.140';
127
127
  /**
128
128
  * Checks if the argument is instance of `Object`. Returns false for arrays.
129
129
  */