typeorm 0.3.29-dev.cc07c90 → 0.3.30

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 (179) hide show
  1. package/README.md +2 -2
  2. package/browser/cache/RedisQueryResultCache.d.ts +6 -9
  3. package/browser/cache/RedisQueryResultCache.js +21 -42
  4. package/browser/cache/RedisQueryResultCache.js.map +1 -1
  5. package/browser/cli-ts-node-commonjs.js +0 -0
  6. package/browser/cli-ts-node-esm.js +0 -0
  7. package/browser/driver/aurora-mysql/AuroraMysqlDriver.js +18 -5
  8. package/browser/driver/aurora-mysql/AuroraMysqlDriver.js.map +1 -1
  9. package/browser/driver/cockroachdb/CockroachDriver.d.ts +2 -2
  10. package/browser/driver/cockroachdb/CockroachDriver.js +13 -3
  11. package/browser/driver/cockroachdb/CockroachDriver.js.map +1 -1
  12. package/browser/driver/cockroachdb/CockroachQueryRunner.js +22 -16
  13. package/browser/driver/cockroachdb/CockroachQueryRunner.js.map +1 -1
  14. package/browser/driver/mysql/MysqlDriver.js +18 -5
  15. package/browser/driver/mysql/MysqlDriver.js.map +1 -1
  16. package/browser/driver/oracle/OracleDriver.d.ts +2 -2
  17. package/browser/driver/oracle/OracleDriver.js +8 -5
  18. package/browser/driver/oracle/OracleDriver.js.map +1 -1
  19. package/browser/driver/postgres/PostgresDriver.d.ts +2 -2
  20. package/browser/driver/postgres/PostgresDriver.js +18 -5
  21. package/browser/driver/postgres/PostgresDriver.js.map +1 -1
  22. package/browser/driver/postgres/PostgresQueryRunner.js +16 -10
  23. package/browser/driver/postgres/PostgresQueryRunner.js.map +1 -1
  24. package/browser/driver/sap/SapConnectionOptions.d.ts +8 -1
  25. package/browser/driver/sap/SapConnectionOptions.js.map +1 -1
  26. package/browser/driver/sap/SapDriver.js +4 -8
  27. package/browser/driver/sap/SapDriver.js.map +1 -1
  28. package/browser/driver/spanner/SpannerDriver.d.ts +2 -2
  29. package/browser/driver/spanner/SpannerDriver.js +1 -1
  30. package/browser/driver/spanner/SpannerDriver.js.map +1 -1
  31. package/browser/driver/sqlserver/SqlServerDriver.d.ts +2 -2
  32. package/browser/driver/sqlserver/SqlServerDriver.js +1 -1
  33. package/browser/driver/sqlserver/SqlServerDriver.js.map +1 -1
  34. package/browser/driver/sqlserver/SqlServerQueryRunner.js +4 -2
  35. package/browser/driver/sqlserver/SqlServerQueryRunner.js.map +1 -1
  36. package/browser/entity-manager/EntityManager.d.ts +3 -2
  37. package/browser/entity-manager/EntityManager.js +32 -18
  38. package/browser/entity-manager/EntityManager.js.map +1 -1
  39. package/browser/error/QueryFailedError.js +1 -2
  40. package/browser/error/QueryFailedError.js.map +1 -1
  41. package/browser/find-options/operator/JsonContains.d.ts +1 -1
  42. package/browser/find-options/operator/JsonContains.js.map +1 -1
  43. package/browser/index.d.ts +3 -0
  44. package/browser/index.js +1 -0
  45. package/browser/index.js.map +1 -1
  46. package/browser/metadata-builder/ClosureJunctionEntityMetadataBuilder.js +2 -0
  47. package/browser/metadata-builder/ClosureJunctionEntityMetadataBuilder.js.map +1 -1
  48. package/browser/metadata-builder/RelationJoinColumnBuilder.d.ts +11 -5
  49. package/browser/metadata-builder/RelationJoinColumnBuilder.js +11 -5
  50. package/browser/metadata-builder/RelationJoinColumnBuilder.js.map +1 -1
  51. package/browser/migration/MigrationExecutor.d.ts +3 -1
  52. package/browser/migration/MigrationExecutor.js +13 -3
  53. package/browser/migration/MigrationExecutor.js.map +1 -1
  54. package/browser/persistence/SubjectChangedColumnsComputer.js +6 -2
  55. package/browser/persistence/SubjectChangedColumnsComputer.js.map +1 -1
  56. package/browser/persistence/SubjectExecutor.d.ts +3 -0
  57. package/browser/persistence/SubjectExecutor.js +11 -0
  58. package/browser/persistence/SubjectExecutor.js.map +1 -1
  59. package/browser/query-builder/QueryBuilder.d.ts +8 -0
  60. package/browser/query-builder/QueryBuilder.js +42 -24
  61. package/browser/query-builder/QueryBuilder.js.map +1 -1
  62. package/browser/query-builder/ReturningOption.d.ts +4 -0
  63. package/browser/query-builder/ReturningOption.js +3 -0
  64. package/browser/query-builder/ReturningOption.js.map +1 -0
  65. package/browser/query-builder/SelectQueryBuilder.d.ts +1 -5
  66. package/browser/query-builder/SelectQueryBuilder.js +48 -54
  67. package/browser/query-builder/SelectQueryBuilder.js.map +1 -1
  68. package/browser/query-builder/SoftDeleteQueryBuilder.js +17 -12
  69. package/browser/query-builder/SoftDeleteQueryBuilder.js.map +1 -1
  70. package/browser/query-builder/UpdateQueryBuilder.js +17 -12
  71. package/browser/query-builder/UpdateQueryBuilder.js.map +1 -1
  72. package/browser/repository/BaseEntity.d.ts +2 -1
  73. package/browser/repository/BaseEntity.js +2 -2
  74. package/browser/repository/BaseEntity.js.map +1 -1
  75. package/browser/repository/Repository.d.ts +3 -2
  76. package/browser/repository/Repository.js +4 -4
  77. package/browser/repository/Repository.js.map +1 -1
  78. package/browser/repository/TreeRepository.js +2 -2
  79. package/browser/repository/TreeRepository.js.map +1 -1
  80. package/browser/repository/UpdateOptions.d.ts +11 -0
  81. package/browser/repository/UpdateOptions.js +3 -0
  82. package/browser/repository/UpdateOptions.js.map +1 -0
  83. package/browser/repository/UpsertOptions.d.ts +6 -0
  84. package/browser/repository/UpsertOptions.js.map +1 -1
  85. package/browser/schema-builder/RdbmsSchemaBuilder.js +1 -1
  86. package/browser/schema-builder/RdbmsSchemaBuilder.js.map +1 -1
  87. package/browser/util/OrmUtils.d.ts +13 -0
  88. package/browser/util/OrmUtils.js +53 -0
  89. package/browser/util/OrmUtils.js.map +1 -1
  90. package/cache/RedisQueryResultCache.d.ts +6 -9
  91. package/cache/RedisQueryResultCache.js +21 -42
  92. package/cache/RedisQueryResultCache.js.map +1 -1
  93. package/commands/InitCommand.js +1 -1
  94. package/commands/MigrationRunCommand.js +4 -4
  95. package/commands/MigrationRunCommand.js.map +1 -1
  96. package/driver/aurora-mysql/AuroraMysqlDriver.js +18 -5
  97. package/driver/aurora-mysql/AuroraMysqlDriver.js.map +1 -1
  98. package/driver/cockroachdb/CockroachDriver.d.ts +2 -2
  99. package/driver/cockroachdb/CockroachDriver.js +13 -3
  100. package/driver/cockroachdb/CockroachDriver.js.map +1 -1
  101. package/driver/cockroachdb/CockroachQueryRunner.js +22 -16
  102. package/driver/cockroachdb/CockroachQueryRunner.js.map +1 -1
  103. package/driver/mysql/MysqlDriver.js +18 -5
  104. package/driver/mysql/MysqlDriver.js.map +1 -1
  105. package/driver/oracle/OracleDriver.d.ts +2 -2
  106. package/driver/oracle/OracleDriver.js +8 -5
  107. package/driver/oracle/OracleDriver.js.map +1 -1
  108. package/driver/postgres/PostgresDriver.d.ts +2 -2
  109. package/driver/postgres/PostgresDriver.js +18 -5
  110. package/driver/postgres/PostgresDriver.js.map +1 -1
  111. package/driver/postgres/PostgresQueryRunner.js +16 -10
  112. package/driver/postgres/PostgresQueryRunner.js.map +1 -1
  113. package/driver/sap/SapConnectionOptions.d.ts +8 -1
  114. package/driver/sap/SapConnectionOptions.js.map +1 -1
  115. package/driver/sap/SapDriver.js +4 -8
  116. package/driver/sap/SapDriver.js.map +1 -1
  117. package/driver/spanner/SpannerDriver.d.ts +2 -2
  118. package/driver/spanner/SpannerDriver.js +1 -1
  119. package/driver/spanner/SpannerDriver.js.map +1 -1
  120. package/driver/sqlserver/SqlServerDriver.d.ts +2 -2
  121. package/driver/sqlserver/SqlServerDriver.js +1 -1
  122. package/driver/sqlserver/SqlServerDriver.js.map +1 -1
  123. package/driver/sqlserver/SqlServerQueryRunner.js +4 -2
  124. package/driver/sqlserver/SqlServerQueryRunner.js.map +1 -1
  125. package/entity-manager/EntityManager.d.ts +3 -2
  126. package/entity-manager/EntityManager.js +32 -18
  127. package/entity-manager/EntityManager.js.map +1 -1
  128. package/error/QueryFailedError.js +1 -2
  129. package/error/QueryFailedError.js.map +1 -1
  130. package/find-options/operator/JsonContains.d.ts +1 -1
  131. package/find-options/operator/JsonContains.js.map +1 -1
  132. package/index.d.ts +3 -0
  133. package/index.js +1 -0
  134. package/index.js.map +1 -1
  135. package/metadata-builder/ClosureJunctionEntityMetadataBuilder.js +2 -0
  136. package/metadata-builder/ClosureJunctionEntityMetadataBuilder.js.map +1 -1
  137. package/metadata-builder/RelationJoinColumnBuilder.d.ts +11 -5
  138. package/metadata-builder/RelationJoinColumnBuilder.js +11 -5
  139. package/metadata-builder/RelationJoinColumnBuilder.js.map +1 -1
  140. package/migration/MigrationExecutor.d.ts +3 -1
  141. package/migration/MigrationExecutor.js +13 -3
  142. package/migration/MigrationExecutor.js.map +1 -1
  143. package/package.json +278 -1
  144. package/persistence/SubjectChangedColumnsComputer.js +6 -2
  145. package/persistence/SubjectChangedColumnsComputer.js.map +1 -1
  146. package/persistence/SubjectExecutor.d.ts +3 -0
  147. package/persistence/SubjectExecutor.js +11 -0
  148. package/persistence/SubjectExecutor.js.map +1 -1
  149. package/query-builder/QueryBuilder.d.ts +8 -0
  150. package/query-builder/QueryBuilder.js +42 -24
  151. package/query-builder/QueryBuilder.js.map +1 -1
  152. package/query-builder/ReturningOption.d.ts +4 -0
  153. package/query-builder/ReturningOption.js +4 -0
  154. package/query-builder/ReturningOption.js.map +1 -0
  155. package/query-builder/SelectQueryBuilder.d.ts +1 -5
  156. package/query-builder/SelectQueryBuilder.js +48 -54
  157. package/query-builder/SelectQueryBuilder.js.map +1 -1
  158. package/query-builder/SoftDeleteQueryBuilder.js +17 -12
  159. package/query-builder/SoftDeleteQueryBuilder.js.map +1 -1
  160. package/query-builder/UpdateQueryBuilder.js +17 -12
  161. package/query-builder/UpdateQueryBuilder.js.map +1 -1
  162. package/repository/BaseEntity.d.ts +2 -1
  163. package/repository/BaseEntity.js +2 -2
  164. package/repository/BaseEntity.js.map +1 -1
  165. package/repository/Repository.d.ts +3 -2
  166. package/repository/Repository.js +4 -4
  167. package/repository/Repository.js.map +1 -1
  168. package/repository/TreeRepository.js +2 -2
  169. package/repository/TreeRepository.js.map +1 -1
  170. package/repository/UpdateOptions.d.ts +11 -0
  171. package/repository/UpdateOptions.js +4 -0
  172. package/repository/UpdateOptions.js.map +1 -0
  173. package/repository/UpsertOptions.d.ts +6 -0
  174. package/repository/UpsertOptions.js.map +1 -1
  175. package/schema-builder/RdbmsSchemaBuilder.js +1 -1
  176. package/schema-builder/RdbmsSchemaBuilder.js.map +1 -1
  177. package/util/OrmUtils.d.ts +13 -0
  178. package/util/OrmUtils.js +53 -0
  179. package/util/OrmUtils.js.map +1 -1
@@ -499,25 +499,17 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
499
499
  * calling this function will override previously set ORDER BY conditions.
500
500
  */
501
501
  orderBy(sort, order = "ASC", nulls) {
502
- if (order !== undefined && order !== "ASC" && order !== "DESC")
503
- throw new error_1.TypeORMError(`SelectQueryBuilder.addOrderBy "order" can accept only "ASC" and "DESC" values.`);
504
- if (nulls !== undefined &&
505
- nulls !== "NULLS FIRST" &&
506
- nulls !== "NULLS LAST")
507
- throw new error_1.TypeORMError(`SelectQueryBuilder.addOrderBy "nulls" can accept only "NULLS FIRST" and "NULLS LAST" values.`);
508
502
  if (sort) {
509
503
  if (typeof sort === "object") {
504
+ this.validateOrderByCondition(sort);
510
505
  this.expressionMap.orderBys = sort;
511
506
  }
512
507
  else {
513
- if (nulls) {
514
- this.expressionMap.orderBys = {
515
- [sort]: { order, nulls },
516
- };
517
- }
518
- else {
519
- this.expressionMap.orderBys = { [sort]: order };
520
- }
508
+ const condition = nulls
509
+ ? { [sort]: { order, nulls } }
510
+ : { [sort]: order };
511
+ this.validateOrderByCondition(condition);
512
+ this.expressionMap.orderBys = condition;
521
513
  }
522
514
  }
523
515
  else {
@@ -529,12 +521,10 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
529
521
  * Adds ORDER BY condition in the query builder.
530
522
  */
531
523
  addOrderBy(sort, order = "ASC", nulls) {
532
- if (order !== undefined && order !== "ASC" && order !== "DESC")
533
- throw new error_1.TypeORMError(`SelectQueryBuilder.addOrderBy "order" can accept only "ASC" and "DESC" values.`);
534
- if (nulls !== undefined &&
535
- nulls !== "NULLS FIRST" &&
536
- nulls !== "NULLS LAST")
537
- throw new error_1.TypeORMError(`SelectQueryBuilder.addOrderBy "nulls" can accept only "NULLS FIRST" and "NULLS LAST" values.`);
524
+ const condition = nulls
525
+ ? { [sort]: { order, nulls } }
526
+ : { [sort]: order };
527
+ this.validateOrderByCondition(condition);
538
528
  if (nulls) {
539
529
  this.expressionMap.orderBys[sort] = { order, nulls };
540
530
  }
@@ -550,10 +540,7 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
550
540
  * then use the take method instead.
551
541
  */
552
542
  limit(limit) {
553
- this.expressionMap.limit = this.normalizeNumber(limit);
554
- if (this.expressionMap.limit !== undefined &&
555
- isNaN(this.expressionMap.limit))
556
- throw new error_1.TypeORMError(`Provided "limit" value is not a number. Please provide a numeric value.`);
543
+ this.expressionMap.limit = this.validateNumericInput("limit", limit);
557
544
  return this;
558
545
  }
559
546
  /**
@@ -563,30 +550,21 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
563
550
  * then use the skip method instead.
564
551
  */
565
552
  offset(offset) {
566
- this.expressionMap.offset = this.normalizeNumber(offset);
567
- if (this.expressionMap.offset !== undefined &&
568
- isNaN(this.expressionMap.offset))
569
- throw new error_1.TypeORMError(`Provided "offset" value is not a number. Please provide a numeric value.`);
553
+ this.expressionMap.offset = this.validateNumericInput("offset", offset);
570
554
  return this;
571
555
  }
572
556
  /**
573
557
  * Sets maximal number of entities to take.
574
558
  */
575
559
  take(take) {
576
- this.expressionMap.take = this.normalizeNumber(take);
577
- if (this.expressionMap.take !== undefined &&
578
- isNaN(this.expressionMap.take))
579
- throw new error_1.TypeORMError(`Provided "take" value is not a number. Please provide a numeric value.`);
560
+ this.expressionMap.take = this.validateNumericInput("take", take);
580
561
  return this;
581
562
  }
582
563
  /**
583
564
  * Sets number of entities to skip.
584
565
  */
585
566
  skip(skip) {
586
- this.expressionMap.skip = this.normalizeNumber(skip);
587
- if (this.expressionMap.skip !== undefined &&
588
- isNaN(this.expressionMap.skip))
589
- throw new error_1.TypeORMError(`Provided "skip" value is not a number. Please provide a numeric value.`);
567
+ this.expressionMap.skip = this.validateNumericInput("skip", skip);
590
568
  return this;
591
569
  }
592
570
  /**
@@ -1475,14 +1453,13 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
1475
1453
  return "";
1476
1454
  }
1477
1455
  /**
1478
- * Creates "LOCK" part of SQL query.
1456
+ * @returns "LOCK" part of SQL query
1479
1457
  */
1480
1458
  createLockExpression() {
1481
1459
  const driver = this.connection.driver;
1482
1460
  let lockTablesClause = "";
1483
1461
  if (this.expressionMap.lockTables) {
1484
- if (!(DriverUtils_1.DriverUtils.isPostgresFamily(driver) ||
1485
- driver.options.type === "cockroachdb")) {
1462
+ if (!DriverUtils_1.DriverUtils.isPostgresFamily(driver)) {
1486
1463
  throw new error_1.TypeORMError("Lock tables not supported in selected driver");
1487
1464
  }
1488
1465
  if (this.expressionMap.lockTables.length < 1) {
@@ -1495,7 +1472,12 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
1495
1472
  onLockExpression = " NOWAIT";
1496
1473
  }
1497
1474
  else if (this.expressionMap.onLocked === "skip_locked") {
1498
- onLockExpression = " SKIP LOCKED";
1475
+ if (driver.options.type === "sap") {
1476
+ onLockExpression = " IGNORE LOCKED";
1477
+ }
1478
+ else {
1479
+ onLockExpression = " SKIP LOCKED";
1480
+ }
1499
1481
  }
1500
1482
  switch (this.expressionMap.lockMode) {
1501
1483
  case "pessimistic_read":
@@ -1514,6 +1496,9 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
1514
1496
  else if (DriverUtils_1.DriverUtils.isPostgresFamily(driver)) {
1515
1497
  return " FOR SHARE" + lockTablesClause + onLockExpression;
1516
1498
  }
1499
+ else if (driver.options.type === "sap") {
1500
+ return (" FOR SHARE LOCK" + lockTablesClause + onLockExpression);
1501
+ }
1517
1502
  else if (driver.options.type === "oracle") {
1518
1503
  return " FOR UPDATE";
1519
1504
  }
@@ -1530,7 +1515,7 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
1530
1515
  return " FOR UPDATE" + onLockExpression;
1531
1516
  }
1532
1517
  else if (DriverUtils_1.DriverUtils.isPostgresFamily(driver) ||
1533
- driver.options.type === "cockroachdb") {
1518
+ driver.options.type === "sap") {
1534
1519
  return " FOR UPDATE" + lockTablesClause + onLockExpression;
1535
1520
  }
1536
1521
  else if (driver.options.type === "mssql") {
@@ -1539,19 +1524,24 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
1539
1524
  else {
1540
1525
  throw new LockNotSupportedOnGivenDriverError_1.LockNotSupportedOnGivenDriverError();
1541
1526
  }
1527
+ // deprecated, use pessimistic_write with onLocked = "skip_locked" instead
1542
1528
  case "pessimistic_partial_write":
1543
1529
  if (DriverUtils_1.DriverUtils.isPostgresFamily(driver)) {
1544
1530
  return " FOR UPDATE" + lockTablesClause + " SKIP LOCKED";
1545
1531
  }
1532
+ else if (driver.options.type === "sap") {
1533
+ return " FOR UPDATE" + lockTablesClause + " IGNORE LOCKED";
1534
+ }
1546
1535
  else if (DriverUtils_1.DriverUtils.isMySQLFamily(driver)) {
1547
1536
  return " FOR UPDATE SKIP LOCKED";
1548
1537
  }
1549
1538
  else {
1550
1539
  throw new LockNotSupportedOnGivenDriverError_1.LockNotSupportedOnGivenDriverError();
1551
1540
  }
1541
+ // deprecated, use pessimistic_write with onLocked = "nowait" instead
1552
1542
  case "pessimistic_write_or_fail":
1553
1543
  if (DriverUtils_1.DriverUtils.isPostgresFamily(driver) ||
1554
- driver.options.type === "cockroachdb") {
1544
+ driver.options.type === "sap") {
1555
1545
  return " FOR UPDATE" + lockTablesClause + " NOWAIT";
1556
1546
  }
1557
1547
  else if (DriverUtils_1.DriverUtils.isMySQLFamily(driver)) {
@@ -1561,8 +1551,7 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
1561
1551
  throw new LockNotSupportedOnGivenDriverError_1.LockNotSupportedOnGivenDriverError();
1562
1552
  }
1563
1553
  case "for_no_key_update":
1564
- if (DriverUtils_1.DriverUtils.isPostgresFamily(driver) ||
1565
- driver.options.type === "cockroachdb") {
1554
+ if (DriverUtils_1.DriverUtils.isPostgresFamily(driver)) {
1566
1555
  return (" FOR NO KEY UPDATE" +
1567
1556
  lockTablesClause +
1568
1557
  onLockExpression);
@@ -2258,14 +2247,6 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
2258
2247
  ObjectUtils_1.ObjectUtils.assign(this.expressionMap, expressionMap);
2259
2248
  return this;
2260
2249
  }
2261
- /**
2262
- * Normalizes a give number - converts to int if possible.
2263
- */
2264
- normalizeNumber(num) {
2265
- if (typeof num === "number" || num === undefined || num === null)
2266
- return num;
2267
- return Number(num);
2268
- }
2269
2250
  /**
2270
2251
  * Creates a query builder used to execute sql queries inside this query builder.
2271
2252
  */
@@ -2586,8 +2567,21 @@ class SelectQueryBuilder extends QueryBuilder_1.QueryBuilder {
2586
2567
  // if all properties of where are undefined we don't need to join anything
2587
2568
  // this can happen when user defines map with conditional queries inside
2588
2569
  if (typeof where[key] === "object") {
2589
- const allAllUndefined = Object.keys(where[key]).every((k) => where[key][k] === undefined);
2590
- if (allAllUndefined) {
2570
+ const whereKeys = Object.keys(where[key]);
2571
+ // empty object — no predicates to apply, skip the join
2572
+ if (whereKeys.length === 0) {
2573
+ continue;
2574
+ }
2575
+ const allUndefined = whereKeys.every((k) => where[key][k] === undefined);
2576
+ if (allUndefined) {
2577
+ const undefinedBehavior = this.connection.options
2578
+ .invalidWhereValuesBehavior?.undefined ||
2579
+ "ignore";
2580
+ if (undefinedBehavior === "throw") {
2581
+ throw new error_1.TypeORMError(`Undefined value encountered in nested relation '${alias}.${key}' of a where condition. ` +
2582
+ `All properties of the nested object are undefined. ` +
2583
+ `Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values.`);
2584
+ }
2591
2585
  continue;
2592
2586
  }
2593
2587
  }