@mikro-orm/core 7.1.10-dev.8 → 7.1.10-dev.9

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.
@@ -64,6 +64,12 @@ export declare abstract class DatabaseDriver<C extends Connection> implements ID
64
64
  orderBy: OrderDefinition<T>[];
65
65
  where: FilterQuery<T>;
66
66
  };
67
+ /**
68
+ * Restores the JS value of a single cursor offset: ISO strings become `Date` instances based on the
69
+ * property type (never based on the string shape alone), and custom types are restored via
70
+ * `convertToJSValue`. Values compared against a JSON document keep their serialized form instead.
71
+ */
72
+ private mapCursorOffset;
67
73
  protected createCursorCondition<T extends object>(definition: (readonly [keyof T & string, QueryOrder])[], offsets: Dictionary[], inverse: boolean, meta: EntityMetadata<T>): FilterQuery<T>;
68
74
  /** @internal */
69
75
  mapDataToFieldNames(data: Dictionary, stringifyJsonArrays: boolean, properties?: Record<string, EntityProperty>, convertCustomTypes?: boolean, object?: boolean): Dictionary;
@@ -7,8 +7,11 @@ import { EntityManager } from '../EntityManager.js';
7
7
  import { CursorError, ValidationError } from '../errors.js';
8
8
  import { DriverException } from '../exceptions.js';
9
9
  import { helper } from '../entity/wrap.js';
10
+ import { Reference } from '../entity/Reference.js';
10
11
  import { PolymorphicRef } from '../entity/PolymorphicRef.js';
11
12
  import { JsonType } from '../types/JsonType.js';
13
+ import { DateTimeType } from '../types/DateTimeType.js';
14
+ import { QueryHelper } from '../utils/QueryHelper.js';
12
15
  import { MikroORM } from '../MikroORM.js';
13
16
  /** Abstract base class for all database drivers, implementing common driver logic. */
14
17
  export class DatabaseDriver {
@@ -141,13 +144,24 @@ export class DatabaseDriver {
141
144
  return !!val && typeof val === 'object' && key in val;
142
145
  };
143
146
  const createCursor = (val, key, inverse = false) => {
144
- let def = isCursor(val, key) ? val[key] : val;
145
- if (Utils.isPlainObject(def)) {
146
- def = Cursor.for(meta, def, orderBy);
147
+ const def = Reference.unwrapReference((isCursor(val, key) ? val[key] : val));
148
+ let offsets;
149
+ // entity (and reference) instances are supported as cursors too, their properties are read the same way
150
+ if (Utils.isPlainObject(def) || Utils.isEntity(def)) {
151
+ // POJO values are already JS values, extract them ordered per the definition,
152
+ // without the JSON round trip `Cursor.for` + `Cursor.decode` would impose
153
+ offsets = definition.map(([key]) => {
154
+ if (def[key] === undefined) {
155
+ throw CursorError.missingValue(meta.className, key);
156
+ }
157
+ return def[key];
158
+ });
147
159
  }
148
- /* v8 ignore next */
149
- const offsets = def ? Cursor.decode(def) : [];
150
- if (definition.length === offsets.length) {
160
+ else {
161
+ /* v8 ignore next */
162
+ offsets = def ? Cursor.decode(def) : [];
163
+ }
164
+ if (definition.length > 0 && definition.length === offsets.length) {
151
165
  return this.createCursorCondition(definition, offsets, inverse, meta);
152
166
  }
153
167
  /* v8 ignore next */
@@ -174,19 +188,66 @@ export class DatabaseDriver {
174
188
  const dir = Utils.xor(desc, isLast) ? 'desc' : 'asc';
175
189
  return { [prop]: dir };
176
190
  };
191
+ // the cursor condition is created at the driver level, after the EM already converted custom types
192
+ // in the user `where`, so we need to run the same conversion over it explicitly
193
+ const where = QueryHelper.processWhere({
194
+ where: ($and.length > 1 ? { $and } : { ...$and[0] }),
195
+ entityName: meta.class,
196
+ metadata: this.metadata,
197
+ platform: this.platform,
198
+ convertCustomTypes: options.convertCustomTypes,
199
+ });
177
200
  return {
178
201
  orderBy: definition.map(([prop, direction]) => createOrderBy(prop, direction)),
179
- where: ($and.length > 1 ? { $and } : { ...$and[0] }),
202
+ where,
180
203
  };
181
204
  }
205
+ /**
206
+ * Restores the JS value of a single cursor offset: ISO strings become `Date` instances based on the
207
+ * property type (never based on the string shape alone), and custom types are restored via
208
+ * `convertToJSValue`. Values compared against a JSON document keep their serialized form instead.
209
+ */
210
+ mapCursorOffset(prop, value, insideJson) {
211
+ if (Utils.isScalarReference(value)) {
212
+ value = value.unwrap();
213
+ }
214
+ // scalar direction on a relation orders by its primary key
215
+ if (Utils.isEntity(value, true)) {
216
+ value = helper(value).getPrimaryKey();
217
+ }
218
+ if (value == null) {
219
+ return value;
220
+ }
221
+ if (insideJson) {
222
+ // compared against the JSON document, which holds the serialized form
223
+ if (value instanceof Date) {
224
+ return value.toISOString();
225
+ }
226
+ // restore the JS value from the serialized form, `processWhere` then converts it to
227
+ // the database form, which is what the JSON document holds for custom typed props
228
+ return prop?.customType ? prop.customType.convertToJSValue(value, this.platform) : value;
229
+ }
230
+ if (typeof value === 'string' &&
231
+ (prop?.runtimeType === 'Date' ||
232
+ (prop?.customType && this.platform.getMappedType(prop.columnTypes?.[0] ?? '') instanceof DateTimeType))) {
233
+ value = new Date(value);
234
+ }
235
+ return prop?.customType ? prop.customType.convertToJSValue(value, this.platform) : value;
236
+ }
182
237
  createCursorCondition(definition, offsets, inverse, meta) {
183
- const createCondition = (prop, direction, offset, eq = false, path = prop) => {
238
+ const createCondition = (prop, direction, offset, eq = false, path = prop, properties = meta.properties, insideJson = false) => {
239
+ const propMeta = properties[prop];
184
240
  if (Utils.isPlainObject(direction)) {
185
241
  if (offset === undefined) {
186
242
  throw CursorError.missingValue(meta.className, path);
187
243
  }
244
+ // POJO cursors can carry entity, reference or embeddable class instances, read their properties directly
245
+ offset = Reference.unwrapReference(offset);
246
+ const childProps = propMeta?.kind === ReferenceKind.EMBEDDED ? propMeta.embeddedProps : propMeta?.targetMeta?.properties;
247
+ insideJson ||=
248
+ (propMeta?.kind === ReferenceKind.EMBEDDED && !!propMeta.object) || propMeta?.customType instanceof JsonType;
188
249
  const value = Utils.keys(direction).reduce((o, key) => {
189
- Object.assign(o, createCondition(key, direction[key], offset?.[key], eq, `${path}.${key}`));
250
+ Object.assign(o, createCondition(key, direction[key], offset?.[key], eq, `${path}.${key}`, childProps ?? {}, insideJson));
190
251
  return o;
191
252
  }, {});
192
253
  return { [prop]: value };
@@ -209,6 +270,7 @@ export class DatabaseDriver {
209
270
  if (offset === undefined) {
210
271
  throw CursorError.missingValue(meta.className, path);
211
272
  }
273
+ offset = this.mapCursorOffset(propMeta, offset, insideJson);
212
274
  // Handle null offset (intentional null cursor value)
213
275
  if (offset === null) {
214
276
  if (eq) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.1.10-dev.8",
3
+ "version": "7.1.10-dev.9",
4
4
  "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.",
5
5
  "keywords": [
6
6
  "data-mapper",
package/utils/Cursor.js CHANGED
@@ -150,12 +150,7 @@ export class Cursor {
150
150
  return Buffer.from(JSON.stringify(value)).toString('base64url');
151
151
  }
152
152
  static decode(value) {
153
- return JSON.parse(Buffer.from(value, 'base64url').toString('utf8')).map((value) => {
154
- if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}/.exec(value)) {
155
- return new Date(value);
156
- }
157
- return value;
158
- });
153
+ return JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
159
154
  }
160
155
  static getDefinition(meta, orderBy) {
161
156
  return Utils.asArray(orderBy).flatMap(order => {
package/utils/Utils.js CHANGED
@@ -153,7 +153,7 @@ export function parseJsonSafe(value) {
153
153
  /** Collection of general-purpose utility methods used throughout the ORM. */
154
154
  export class Utils {
155
155
  static PK_SEPARATOR = '~~~';
156
- static #ORM_VERSION = '7.1.10-dev.8';
156
+ static #ORM_VERSION = '7.1.10-dev.9';
157
157
  /**
158
158
  * Checks if the argument is instance of `Object`. Returns false for arrays.
159
159
  */