@mikro-orm/core 7.1.10-dev.8 → 7.1.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.
@@ -64,6 +64,13 @@ 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
+ * unless the platform preserves native date types inside JSON documents (mongo).
72
+ */
73
+ private mapCursorOffset;
67
74
  protected createCursorCondition<T extends object>(definition: (readonly [keyof T & string, QueryOrder])[], offsets: Dictionary[], inverse: boolean, meta: EntityMetadata<T>): FilterQuery<T>;
68
75
  /** @internal */
69
76
  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,67 @@ 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
+ * unless the platform preserves native date types inside JSON documents (mongo).
210
+ */
211
+ mapCursorOffset(prop, value, insideJson) {
212
+ if (Utils.isScalarReference(value)) {
213
+ value = value.unwrap();
214
+ }
215
+ // scalar direction on a relation orders by its primary key
216
+ if (Utils.isEntity(value, true)) {
217
+ value = helper(value).getPrimaryKey();
218
+ }
219
+ if (value == null) {
220
+ return value;
221
+ }
222
+ if (insideJson && !this.platform.preservesDatesInsideJson()) {
223
+ // compared against the JSON document, which holds the serialized form
224
+ if (value instanceof Date) {
225
+ return value.toISOString();
226
+ }
227
+ // restore the JS value from the serialized form, `processWhere` then converts it to
228
+ // the database form, which is what the JSON document holds for custom typed props
229
+ return prop?.customType ? prop.customType.convertToJSValue(value, this.platform) : value;
230
+ }
231
+ if (typeof value === 'string' &&
232
+ (prop?.runtimeType === 'Date' ||
233
+ (prop?.customType && this.platform.getMappedType(prop.columnTypes?.[0] ?? '') instanceof DateTimeType))) {
234
+ value = new Date(value);
235
+ }
236
+ return prop?.customType ? prop.customType.convertToJSValue(value, this.platform) : value;
237
+ }
182
238
  createCursorCondition(definition, offsets, inverse, meta) {
183
- const createCondition = (prop, direction, offset, eq = false, path = prop) => {
239
+ const createCondition = (prop, direction, offset, eq = false, path = prop, properties = meta.properties, insideJson = false) => {
240
+ const propMeta = properties[prop];
184
241
  if (Utils.isPlainObject(direction)) {
185
242
  if (offset === undefined) {
186
243
  throw CursorError.missingValue(meta.className, path);
187
244
  }
245
+ // POJO cursors can carry entity, reference or embeddable class instances, read their properties directly
246
+ offset = Reference.unwrapReference(offset);
247
+ const childProps = propMeta?.kind === ReferenceKind.EMBEDDED ? propMeta.embeddedProps : propMeta?.targetMeta?.properties;
248
+ insideJson ||=
249
+ (propMeta?.kind === ReferenceKind.EMBEDDED && !!propMeta.object) || propMeta?.customType instanceof JsonType;
188
250
  const value = Utils.keys(direction).reduce((o, key) => {
189
- Object.assign(o, createCondition(key, direction[key], offset?.[key], eq, `${path}.${key}`));
251
+ Object.assign(o, createCondition(key, direction[key], offset?.[key], eq, `${path}.${key}`, childProps ?? {}, insideJson));
190
252
  return o;
191
253
  }, {});
192
254
  return { [prop]: value };
@@ -209,6 +271,7 @@ export class DatabaseDriver {
209
271
  if (offset === undefined) {
210
272
  throw CursorError.missingValue(meta.className, path);
211
273
  }
274
+ offset = this.mapCursorOffset(propMeta, offset, insideJson);
212
275
  // Handle null offset (intentional null cursor value)
213
276
  if (offset === null) {
214
277
  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",
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",
@@ -227,6 +227,8 @@ export declare abstract class Platform {
227
227
  formatIndexHint(indexNames: string[]): string | undefined;
228
228
  /** Whether the driver automatically parses JSON columns into JS objects. */
229
229
  convertsJsonAutomatically(): boolean;
230
+ /** Whether date values inside JSON documents keep their native type (e.g. BSON dates), instead of being serialized to ISO strings. */
231
+ preservesDatesInsideJson(): boolean;
230
232
  /** Converts a JS value to its JSON database representation (typically JSON.stringify). */
231
233
  convertJsonToDatabaseValue(value: unknown, context?: TransformContext): unknown;
232
234
  /** Converts a database JSON value to its JS representation. */
@@ -465,6 +465,10 @@ export class Platform {
465
465
  convertsJsonAutomatically() {
466
466
  return true;
467
467
  }
468
+ /** Whether date values inside JSON documents keep their native type (e.g. BSON dates), instead of being serialized to ISO strings. */
469
+ preservesDatesInsideJson() {
470
+ return false;
471
+ }
468
472
  /** Converts a JS value to its JSON database representation (typically JSON.stringify). */
469
473
  convertJsonToDatabaseValue(value, context) {
470
474
  return JSON.stringify(value);
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';
157
157
  /**
158
158
  * Checks if the argument is instance of `Object`. Returns false for arrays.
159
159
  */