remult 0.25.8 → 0.26.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 (102) hide show
  1. package/README.md +194 -129
  2. package/esm/graphql.js +131 -59
  3. package/esm/index.js +1 -1
  4. package/esm/migrations/compare-migration-snapshots.js +61 -0
  5. package/esm/migrations/generate-migrations.js +68 -0
  6. package/esm/migrations/index.js +2 -0
  7. package/esm/migrations/migrate.js +90 -0
  8. package/esm/migrations/migration-types.js +36 -0
  9. package/esm/migrations/update-migrations-ts-file.js +115 -0
  10. package/esm/postgres/postgres-data-provider.js +24 -3
  11. package/esm/postgres/schema-builder.js +35 -26
  12. package/esm/remult-better-sqlite3.js +47 -0
  13. package/esm/remult-bun-sqlite.js +41 -0
  14. package/esm/remult-knex/index.js +180 -51
  15. package/esm/remult-mongo.js +2 -2
  16. package/esm/remult-sql-js.js +3 -78
  17. package/esm/remult-sqlite-core.js +104 -0
  18. package/esm/server/expressBridge.js +130 -29
  19. package/esm/server/initAsyncHooks.js +75 -1
  20. package/esm/src/CompoundIdField.js +8 -2
  21. package/esm/src/context.js +20 -24
  22. package/esm/src/data-api.js +71 -30
  23. package/esm/src/data-providers/rest-data-provider.js +17 -15
  24. package/esm/src/data-providers/sql-database.js +32 -7
  25. package/esm/src/data-providers/web-sql-data-provider.js +1 -9
  26. package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +37 -11
  27. package/esm/src/filter/filter-interfaces.js +229 -17
  28. package/esm/src/isOfType.js +9 -0
  29. package/esm/src/remult3/Fields.js +70 -18
  30. package/esm/src/remult3/RepositoryImplementation.js +47 -21
  31. package/esm/src/remult3/addValidator.js +15 -0
  32. package/esm/src/remult3/remult3.js +28 -57
  33. package/esm/src/server-action.js +4 -4
  34. package/esm/src/validators.js +6 -3
  35. package/esm/src/valueConverters.js +1 -1
  36. package/graphql.js +156 -71
  37. package/index.d.ts +4 -4
  38. package/index.js +2 -1
  39. package/migrations/compare-migration-snapshots.d.ts +27 -0
  40. package/migrations/compare-migration-snapshots.js +165 -0
  41. package/migrations/generate-migrations.d.ts +21 -0
  42. package/migrations/generate-migrations.js +104 -0
  43. package/migrations/index.d.ts +3 -0
  44. package/migrations/index.js +7 -0
  45. package/migrations/migrate.d.ts +27 -0
  46. package/migrations/migrate.js +175 -0
  47. package/migrations/migration-types.d.ts +48 -0
  48. package/migrations/migration-types.js +88 -0
  49. package/migrations/update-migrations-ts-file.d.ts +1 -0
  50. package/migrations/update-migrations-ts-file.js +141 -0
  51. package/package.json +21 -1
  52. package/postgres/postgres-data-provider.d.ts +8 -3
  53. package/postgres/postgres-data-provider.js +46 -3
  54. package/postgres/schema-builder.js +81 -48
  55. package/remult-better-sqlite3.d.ts +12 -0
  56. package/remult-better-sqlite3.js +64 -0
  57. package/remult-bun-sqlite.d.ts +11 -0
  58. package/remult-bun-sqlite.js +56 -0
  59. package/remult-knex/index.d.ts +12 -5
  60. package/remult-knex/index.js +257 -90
  61. package/remult-mongo.d.ts +2 -2
  62. package/remult-mongo.js +2 -2
  63. package/remult-sql-js.d.ts +2 -15
  64. package/remult-sql-js.js +11 -152
  65. package/remult-sqlite-core.d.ts +21 -0
  66. package/remult-sqlite-core.js +217 -0
  67. package/server/expressBridge.d.ts +30 -1
  68. package/server/expressBridge.js +140 -32
  69. package/server/initAsyncHooks.d.ts +16 -0
  70. package/server/initAsyncHooks.js +128 -2
  71. package/src/CompoundIdField.js +8 -2
  72. package/src/column-interfaces.d.ts +9 -3
  73. package/src/context.d.ts +8 -6
  74. package/src/context.js +27 -36
  75. package/src/data-api.d.ts +13 -9
  76. package/src/data-api.js +184 -135
  77. package/src/data-providers/rest-data-provider.js +26 -15
  78. package/src/data-providers/sql-database.d.ts +17 -5
  79. package/src/data-providers/sql-database.js +31 -7
  80. package/src/data-providers/web-sql-data-provider.d.ts +1 -2
  81. package/src/data-providers/web-sql-data-provider.js +4 -8
  82. package/src/entity.d.ts +64 -6
  83. package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +31 -7
  84. package/src/filter/filter-consumer-bridge-to-sql-request.js +46 -21
  85. package/src/filter/filter-interfaces.d.ts +106 -39
  86. package/src/filter/filter-interfaces.js +312 -32
  87. package/src/isOfType.d.ts +2 -0
  88. package/src/isOfType.js +14 -0
  89. package/src/remult3/Fields.d.ts +36 -0
  90. package/src/remult3/Fields.js +81 -19
  91. package/src/remult3/RepositoryImplementation.d.ts +7 -3
  92. package/src/remult3/RepositoryImplementation.js +106 -71
  93. package/src/remult3/addValidator.d.ts +2 -0
  94. package/src/remult3/addValidator.js +20 -0
  95. package/src/remult3/remult3.d.ts +6 -2
  96. package/src/remult3/remult3.js +28 -57
  97. package/src/server-action.d.ts +1 -1
  98. package/src/server-action.js +5 -4
  99. package/src/sql-command.d.ts +6 -1
  100. package/src/validators.d.ts +1 -0
  101. package/src/validators.js +10 -4
  102. package/src/valueConverters.js +1 -1
@@ -10,6 +10,7 @@ var RepositoryImplementation_js_1 = require("../remult3/RepositoryImplementation
10
10
  var sort_js_1 = require("../sort.js");
11
11
  var valueConverters_js_1 = require("../valueConverters.js");
12
12
  var repository_internals_js_1 = require("../remult3/repository-internals.js");
13
+ var isOfType_js_1 = require("../isOfType.js");
13
14
  // @dynamic
14
15
  var SqlDatabase = /** @class */ (function () {
15
16
  function SqlDatabase(sql) {
@@ -18,12 +19,18 @@ var SqlDatabase = /** @class */ (function () {
18
19
  this.createdEntities = [];
19
20
  if (sql.wrapIdentifier)
20
21
  this.wrapIdentifier = function (x) { return sql.wrapIdentifier(x); };
22
+ if ((0, isOfType_js_1.isOfType)(sql, 'provideMigrationBuilder')) {
23
+ this.provideMigrationBuilder = function (x) { return sql.provideMigrationBuilder(x); };
24
+ }
25
+ if ((0, isOfType_js_1.isOfType)(sql, 'end'))
26
+ this.end = function () { return sql.end(); };
21
27
  }
22
- SqlDatabase.getDb = function (remult) {
23
- var r = (remult || remult_proxy_js_1.remult).dataProvider;
24
- if (!r.createCommand)
25
- throw 'the data provider is not an SqlDatabase';
26
- return r;
28
+ SqlDatabase.getDb = function (dataProvider) {
29
+ var r = (dataProvider || remult_proxy_js_1.remult.dataProvider);
30
+ if ((0, isOfType_js_1.isOfType)(r, 'createCommand'))
31
+ return r;
32
+ else
33
+ throw 'the data provider is not an SqlCommandFactory';
27
34
  };
28
35
  SqlDatabase.prototype.createCommand = function () {
29
36
  return new LogSQLCommand(this.sql.createCommand(), SqlDatabase.LogToConsole);
@@ -129,6 +136,7 @@ var SqlDatabase = /** @class */ (function () {
129
136
  transaction: function (z) { return x.transaction(z); },
130
137
  supportsJsonColumnType: this.sql.supportsJsonColumnType,
131
138
  wrapIdentifier: this.wrapIdentifier,
139
+ end: this.end,
132
140
  }))];
133
141
  case 2:
134
142
  _a.sent();
@@ -141,6 +149,16 @@ var SqlDatabase = /** @class */ (function () {
141
149
  });
142
150
  }); });
143
151
  };
152
+ /**
153
+ * Creates a raw filter for entity filtering.
154
+ * @param {CustomSqlFilterBuilderFunction} build - The custom SQL filter builder function.
155
+ * @returns {EntityFilter<any>} - The entity filter with a custom SQL filter.
156
+ * @example
157
+ * SqlDatabase.rawFilter(({param}) =>
158
+ `"customerId" in (select id from customers where city = ${param(customerCity)})`
159
+ )
160
+ * @see [Leveraging Database Capabilities with Raw SQL in Custom Filters](https://remult.dev/docs/custom-filter.html#leveraging-database-capabilities-with-raw-sql-in-custom-filters)
161
+ */
144
162
  SqlDatabase.rawFilter = function (build) {
145
163
  var _a;
146
164
  return _a = {},
@@ -149,7 +167,8 @@ var SqlDatabase = /** @class */ (function () {
149
167
  },
150
168
  _a;
151
169
  };
152
- SqlDatabase.filterToRaw = function (repo, condition, sqlCommand, dbNames) {
170
+ SqlDatabase.filterToRaw = function (repo, condition, sqlCommand, dbNames, wrapIdentifier) {
171
+ if (wrapIdentifier === void 0) { wrapIdentifier = function (x) { return x; }; }
153
172
  return tslib_1.__awaiter(this, void 0, void 0, function () {
154
173
  var r, b, _a, _b, _c;
155
174
  return tslib_1.__generator(this, function (_d) {
@@ -163,7 +182,7 @@ var SqlDatabase = /** @class */ (function () {
163
182
  _b = [void 0, sqlCommand];
164
183
  _c = dbNames;
165
184
  if (_c) return [3 /*break*/, 2];
166
- return [4 /*yield*/, (0, filter_consumer_bridge_to_sql_request_js_1.dbNamesOf)(r.metadata)];
185
+ return [4 /*yield*/, (0, filter_consumer_bridge_to_sql_request_js_1.dbNamesOf)(r.metadata, wrapIdentifier)];
167
186
  case 1:
168
187
  _c = (_d.sent());
169
188
  _d.label = 2;
@@ -515,6 +534,11 @@ var ActualSQLServerDataProvider = /** @class */ (function () {
515
534
  return [2 /*return*/, r.execute(statement).then(function (sqlResult) {
516
535
  var _a, _b;
517
536
  (_b = (_a = _this.sql._getSourceSql()).afterMutation) === null || _b === void 0 ? void 0 : _b.call(_a);
537
+ if (sqlResult.rows.length != 1)
538
+ throw new Error('Failed to update row with id ' +
539
+ id +
540
+ ', rows updated: ' +
541
+ sqlResult.rows.length);
518
542
  return _this.buildResultRow(colKeys, sqlResult.rows[0], sqlResult);
519
543
  })];
520
544
  }
@@ -1,6 +1,5 @@
1
1
  import type { __RowsOfDataForTesting } from '../__RowsOfDataForTesting.js';
2
2
  import type { SqlCommand, SqlImplementation } from '../sql-command.js';
3
- import type { Remult } from '../context.js';
4
3
  import type { EntityMetadata } from '../remult3/remult3.js';
5
4
  export declare class WebSqlDataProvider implements SqlImplementation, __RowsOfDataForTesting {
6
5
  private databaseName;
@@ -8,7 +7,7 @@ export declare class WebSqlDataProvider implements SqlImplementation, __RowsOfDa
8
7
  [tableName: string]: any;
9
8
  };
10
9
  constructor(databaseName: string, databaseSize?: number);
11
- static getDb(remult?: Remult): any;
10
+ end(): Promise<void>;
12
11
  getLimitSqlSyntax(limit: number, offset: number): string;
13
12
  entityIsUsedForTheFirstTime(entity: EntityMetadata): Promise<void>;
14
13
  ensureSchema(entities: EntityMetadata<any>[]): Promise<void>;
@@ -13,14 +13,10 @@ var WebSqlDataProvider = /** @class */ (function () {
13
13
  //@ts-ignore
14
14
  this.db = window.openDatabase(databaseName, '1.0', databaseName, databaseSize);
15
15
  }
16
- // return instance of websql.Database class - see @types/websql
17
- WebSqlDataProvider.getDb = function (remult) {
18
- var sql = sql_database_js_1.SqlDatabase.getDb(remult);
19
- var me = sql._getSourceSql();
20
- if (!me.db) {
21
- throw 'the data provider is not a WebSqlDataProvider';
22
- }
23
- return me.db;
16
+ WebSqlDataProvider.prototype.end = function () {
17
+ return tslib_1.__awaiter(this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
18
+ return [2 /*return*/];
19
+ }); });
24
20
  };
25
21
  WebSqlDataProvider.prototype.getLimitSqlSyntax = function (limit, offset) {
26
22
  return ' limit ' + limit + ' offset ' + offset;
package/src/entity.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Allowed, AllowedForInstance } from './context.js';
2
2
  import type { FieldMetadata } from './column-interfaces.js';
3
3
  import type { EntityFilter, EntityIdFields, EntityMetadata, EntityOrderBy, EntityRef, FieldsMetadata, LifecycleEvent } from './remult3/remult3.js';
4
+ import type { FilterPreciseValues } from './filter/filter-interfaces.js';
4
5
  export interface EntityOptions<entityType = any> {
5
6
  /**A human readable name for the entity */
6
7
  caption?: string;
@@ -24,18 +25,57 @@ export interface EntityOptions<entityType = any> {
24
25
  allowApiInsert?: AllowedForInstance<entityType>;
25
26
  /** sets the `allowApiUpdate`, `allowApiDelete` and `allowApiInsert` properties in a single set */
26
27
  allowApiCrud?: Allowed;
27
- /** A filter that determines which rows can be queries using the api.
28
- * @description
29
- * Use apiPrefilter in cases where you to restrict data based on user profile
28
+ /**
29
+ * An optional filter that determines which rows can be queried using the API.
30
+ * This filter is applied to all CRUD operations to ensure that only authorized data is accessible.
31
+ *
32
+ * Use `apiPrefilter` to restrict data based on user profile or other conditions.
33
+ *
30
34
  * @example
31
- * apiPrefilter: { archive:false }
35
+ * // Only include non-archived items in API responses
36
+ * apiPrefilter: { archive: false }
32
37
  *
33
38
  * @example
34
- * apiPrefilter: ()=> remult.isAllowed("admin")?{}:{ archive:false }
35
- * @see [EntityFilter](http://remult.dev/docs/entityFilter.html)
39
+ * // Allow admins to access all rows, but restrict non-admins to non-archived items
40
+ * apiPrefilter: () => remult.isAllowed("admin") ? {} : { archive: false }
36
41
  *
42
+ * @see [EntityFilter](https://remult.dev/docs/access-control.html#filtering-accessible-rows)
37
43
  */
38
44
  apiPrefilter?: EntityFilter<entityType> | (() => EntityFilter<entityType> | Promise<EntityFilter<entityType>>);
45
+ /**
46
+ * An optional function that allows for preprocessing or modifying the EntityFilter for a specific entity type
47
+ * before it is used in API CRUD operations. This function can be used to enforce additional access control
48
+ * rules or adjust the filter based on the current context or specific request.
49
+ *
50
+ * @template entityType The type of the entity being filtered.
51
+ * @param filter The initial EntityFilter for the entity type.
52
+ * @param event Additional information and utilities for preprocessing the filter.
53
+ * @returns The modified EntityFilter or a Promise that resolves to the modified EntityFilter.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * @Entity<Task>("tasks", {
58
+ * apiPreprocessFilter: async (filter, { getPreciseValues }) => {
59
+ * // Ensure that users can only query tasks for specific customers
60
+ * const preciseValues = await getPreciseValues();
61
+ * if (!preciseValues.customerId) {
62
+ * throw new ForbiddenError("You must specify a valid customerId filter");
63
+ * }
64
+ * return filter;
65
+ * }
66
+ * })
67
+ * ```
68
+ */
69
+ apiPreprocessFilter?: (filter: EntityFilter<entityType>, event: PreprocessFilterEvent<entityType>) => EntityFilter<entityType> | Promise<EntityFilter<entityType>>;
70
+ /**
71
+ * Similar to apiPreprocessFilter, but for backend operations.
72
+ *
73
+ * @template entityType The type of the entity being filtered.
74
+ * @param filter The initial EntityFilter for the entity type.
75
+ * @param event Additional information and utilities for preprocessing the filter.
76
+ * @returns The modified EntityFilter or a Promise that resolves to the modified EntityFilter.
77
+ */
78
+ backendPreprocessFilter?: (filter: EntityFilter<entityType>, event: PreprocessFilterEvent<entityType>) => EntityFilter<entityType> | Promise<EntityFilter<entityType>>;
39
79
  /** A filter that will be used for all queries from this entity both from the API and from within the backend.
40
80
  * @example
41
81
  * backendPrefilter: { archive:false }
@@ -124,3 +164,21 @@ export interface EntityOptions<entityType = any> {
124
164
  entityRefInit?: (ref: EntityRef<entityType>, row: entityType) => void;
125
165
  apiRequireId?: Allowed;
126
166
  }
167
+ /**
168
+ * Provides additional information and utilities for preprocessing filters in API and backend operations.
169
+ * @template entityType The type of the entity being filtered.
170
+ */
171
+ export interface PreprocessFilterEvent<entityType> {
172
+ /**
173
+ * Metadata of the entity being filtered.
174
+ */
175
+ metadata: EntityMetadata<entityType>;
176
+ /**
177
+ * Retrieves precise values for each property in a filter for an entity.
178
+ * @param filter Optional filter to analyze. If not provided, the current filter being preprocessed is used.
179
+ * @returns A promise that resolves to a FilterPreciseValues object containing the precise values for each property.
180
+
181
+ * {@Link FilterPreciseValues }
182
+ */
183
+ getFilterPreciseValues(filter?: EntityFilter<entityType>): Promise<FilterPreciseValues<entityType>>;
184
+ }
@@ -1,7 +1,7 @@
1
1
  import type { FieldMetadata } from '../column-interfaces.js';
2
2
  import type { EntityMetadataOverloads, RepositoryOverloads } from '../remult3/RepositoryImplementation.js';
3
3
  import type { EntityFilter, EntityMetadata, MembersOnly } from '../remult3/remult3.js';
4
- import type { SqlCommandWithParameters } from '../sql-command.js';
4
+ import type { HasWrapIdentifier, SqlCommandWithParameters } from '../sql-command.js';
5
5
  import type { Filter, FilterConsumer } from './filter-interfaces.js';
6
6
  export declare class FilterConsumerBridgeToSqlRequest implements FilterConsumer {
7
7
  private r;
@@ -28,25 +28,49 @@ export declare class FilterConsumerBridgeToSqlRequest implements FilterConsumer
28
28
  private addToWhere;
29
29
  databaseCustom(databaseCustom: CustomSqlFilterObject): void;
30
30
  }
31
- export type CustomSqlFilterBuilderFunction = (builder: CustomSqlFilterBuilder) => void | Promise<any>;
32
31
  export interface CustomSqlFilterObject {
33
32
  buildSql: CustomSqlFilterBuilderFunction;
34
33
  }
35
- export declare class CustomSqlFilterBuilder {
34
+ /**
35
+ * Represents a custom SQL filter builder function.
36
+ * @callback CustomSqlFilterBuilderFunction
37
+ * @param {CustomSqlFilterBuilder} builder - The custom SQL filter builder instance.
38
+ * @returns {void | string | Promise<string | void> } - The result of the custom SQL filter builder function.
39
+ */
40
+ export type CustomSqlFilterBuilderFunction = (builder: CustomSqlFilterBuilder) => void | string | Promise<string | void>;
41
+ /**
42
+ * Represents a custom SQL filter builder.
43
+ */
44
+ export declare class CustomSqlFilterBuilder implements SqlCommandWithParameters, HasWrapIdentifier {
36
45
  private r;
37
- constructor(r: SqlCommandWithParameters);
46
+ wrapIdentifier: (name: string) => string;
47
+ constructor(r: SqlCommandWithParameters, wrapIdentifier: (name: string) => string);
38
48
  sql: string;
39
- /** @deprecated @deprecated use `param` instead*/
49
+ /** @deprecated use `param` instead*/
40
50
  addParameterAndReturnSqlToken(val: any): string;
41
- param<valueType>(val: valueType, field?: FieldMetadata<valueType>): string;
42
- filterToRaw<entityType>(repo: RepositoryOverloads<entityType>, condition: EntityFilter<entityType>): Promise<string>;
51
+ /**
52
+ * Adds a parameter value.
53
+ * @param {valueType} val - The value to add as a parameter.
54
+ * @param {FieldMetadata<valueType>} [field] - The field metadata.
55
+ * @returns {string} - The SQL token.
56
+ */
57
+ param: <valueType>(val: valueType, field?: FieldMetadata<valueType, any>) => string;
58
+ /**
59
+ * Converts an entity filter into a raw SQL condition - and appends to it any `backendPrefilter` and `backendPreprocessFilter`
60
+ * @param {RepositoryOverloads<entityType>} repo - The repository.
61
+ * @param {EntityFilter<entityType>} condition - The entity filter.
62
+ * @returns {Promise<string>} - The raw SQL.
63
+ */
64
+ filterToRaw: <entityType>(repo: RepositoryOverloads<entityType>, condition: EntityFilter<entityType>) => Promise<string>;
43
65
  }
44
66
  export declare function isDbReadonly<entityType>(field: FieldMetadata, dbNames: EntityDbNames<entityType>): boolean;
45
67
  export declare function shouldNotCreateField<entityType>(field: FieldMetadata, dbNames: EntityDbNames<entityType>): boolean;
68
+ export declare function shouldCreateEntity(entity: EntityMetadata<any>, e: EntityDbNamesBase): boolean;
46
69
  export declare type EntityDbNamesBase = {
47
70
  $entityName: string;
48
71
  $dbNameOf(field: FieldMetadata<any> | string): string;
49
72
  toString(): string;
73
+ wrapIdentifier: (name: string) => string;
50
74
  };
51
75
  export declare type EntityDbNames<entityType> = {
52
76
  [Properties in keyof Required<MembersOnly<entityType>>]: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fieldDbName = exports.entityDbName = exports.dbNamesOf = exports.shouldNotCreateField = exports.isDbReadonly = exports.CustomSqlFilterBuilder = exports.FilterConsumerBridgeToSqlRequest = void 0;
3
+ exports.fieldDbName = exports.entityDbName = exports.dbNamesOf = exports.shouldCreateEntity = exports.shouldNotCreateField = exports.isDbReadonly = exports.CustomSqlFilterBuilder = exports.FilterConsumerBridgeToSqlRequest = void 0;
4
4
  var tslib_1 = require("tslib");
5
5
  var sql_database_js_1 = require("../data-providers/sql-database.js");
6
6
  var remult_proxy_js_1 = require("../remult-proxy.js");
@@ -215,17 +215,19 @@ var FilterConsumerBridgeToSqlRequest = /** @class */ (function () {
215
215
  FilterConsumerBridgeToSqlRequest.prototype.databaseCustom = function (databaseCustom) {
216
216
  var _this = this;
217
217
  this.promises.push((function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
218
- var item;
218
+ var item, sql;
219
219
  return tslib_1.__generator(this, function (_a) {
220
220
  switch (_a.label) {
221
221
  case 0:
222
222
  if (!(databaseCustom === null || databaseCustom === void 0 ? void 0 : databaseCustom.buildSql)) return [3 /*break*/, 2];
223
- item = new CustomSqlFilterBuilder(this.r);
223
+ item = new CustomSqlFilterBuilder(this.r, this.nameProvider.wrapIdentifier);
224
224
  return [4 /*yield*/, databaseCustom.buildSql(item)];
225
225
  case 1:
226
- _a.sent();
227
- if (item.sql) {
228
- this.addToWhere('(' + item.sql + ')');
226
+ sql = _a.sent();
227
+ if (typeof sql !== 'string')
228
+ sql = item.sql;
229
+ if (sql) {
230
+ this.addToWhere('(' + sql + ')');
229
231
  }
230
232
  _a.label = 2;
231
233
  case 2: return [2 /*return*/];
@@ -236,28 +238,45 @@ var FilterConsumerBridgeToSqlRequest = /** @class */ (function () {
236
238
  return FilterConsumerBridgeToSqlRequest;
237
239
  }());
238
240
  exports.FilterConsumerBridgeToSqlRequest = FilterConsumerBridgeToSqlRequest;
241
+ /**
242
+ * Represents a custom SQL filter builder.
243
+ */
239
244
  var CustomSqlFilterBuilder = /** @class */ (function () {
240
- function CustomSqlFilterBuilder(r) {
245
+ function CustomSqlFilterBuilder(r, wrapIdentifier) {
246
+ var _this = this;
241
247
  this.r = r;
248
+ this.wrapIdentifier = wrapIdentifier;
242
249
  this.sql = '';
250
+ /**
251
+ * Adds a parameter value.
252
+ * @param {valueType} val - The value to add as a parameter.
253
+ * @param {FieldMetadata<valueType>} [field] - The field metadata.
254
+ * @returns {string} - The SQL token.
255
+ */
256
+ this.param = function (val, field) {
257
+ if (typeof field === 'object' && field.valueConverter.toDb) {
258
+ val = field.valueConverter.toDb(val);
259
+ }
260
+ return _this.r.param(val);
261
+ };
262
+ /**
263
+ * Converts an entity filter into a raw SQL condition - and appends to it any `backendPrefilter` and `backendPreprocessFilter`
264
+ * @param {RepositoryOverloads<entityType>} repo - The repository.
265
+ * @param {EntityFilter<entityType>} condition - The entity filter.
266
+ * @returns {Promise<string>} - The raw SQL.
267
+ */
268
+ this.filterToRaw = function (repo, condition) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
269
+ return tslib_1.__generator(this, function (_a) {
270
+ return [2 /*return*/, sql_database_js_1.SqlDatabase.filterToRaw(repo, condition, this, undefined, this.wrapIdentifier)];
271
+ });
272
+ }); };
273
+ this.param.bind(this);
274
+ this.filterToRaw.bind(this);
243
275
  }
244
- /** @deprecated @deprecated use `param` instead*/
276
+ /** @deprecated use `param` instead*/
245
277
  CustomSqlFilterBuilder.prototype.addParameterAndReturnSqlToken = function (val) {
246
278
  return this.param(val);
247
279
  };
248
- CustomSqlFilterBuilder.prototype.param = function (val, field) {
249
- if (typeof field === 'object' && field.valueConverter.toDb) {
250
- val = field.valueConverter.toDb(val);
251
- }
252
- return this.r.param(val);
253
- };
254
- CustomSqlFilterBuilder.prototype.filterToRaw = function (repo, condition) {
255
- return tslib_1.__awaiter(this, void 0, void 0, function () {
256
- return tslib_1.__generator(this, function (_a) {
257
- return [2 /*return*/, sql_database_js_1.SqlDatabase.filterToRaw(repo, condition, this)];
258
- });
259
- });
260
- };
261
280
  return CustomSqlFilterBuilder;
262
281
  }());
263
282
  exports.CustomSqlFilterBuilder = CustomSqlFilterBuilder;
@@ -272,6 +291,11 @@ function shouldNotCreateField(field, dbNames) {
272
291
  (field.options.sqlExpression && field.dbName != dbNames.$dbNameOf(field)));
273
292
  }
274
293
  exports.shouldNotCreateField = shouldNotCreateField;
294
+ function shouldCreateEntity(entity, e) {
295
+ return (!entity.options.sqlExpression &&
296
+ e.$entityName.toLowerCase().indexOf('from ') < 0);
297
+ }
298
+ exports.shouldCreateEntity = shouldCreateEntity;
275
299
  function dbNamesOf(repo, wrapIdentifierOrOptions) {
276
300
  return tslib_1.__awaiter(this, void 0, void 0, function () {
277
301
  var options, meta, result, _a, _b, field, r, e_3_1;
@@ -301,6 +325,7 @@ function dbNamesOf(repo, wrapIdentifierOrOptions) {
301
325
  key = field.key;
302
326
  return result[key];
303
327
  },
328
+ _c.wrapIdentifier = options.wrapIdentifier,
304
329
  _c);
305
330
  _e.label = 2;
306
331
  case 2:
@@ -1,78 +1,124 @@
1
1
  import type { FieldMetadata } from '../column-interfaces.js';
2
2
  import type { Remult } from '../context.js';
3
- import type { EntityFilter, EntityMetadata } from '../remult3/remult3.js';
3
+ import type { EntityFilter, EntityMetadata, MembersOnly } from '../remult3/remult3.js';
4
4
  /**
5
5
  * The `Filter` class is a helper class that focuses on filter-related concerns. It provides methods
6
6
  * for creating and applying filters in queries.
7
7
  */
8
8
  export declare class Filter {
9
9
  private apply;
10
+ /**
11
+ * Retrieves precise values for each property in a filter for an entity.
12
+ * @template entityType The type of the entity being filtered.
13
+ * @param metadata The metadata of the entity being filtered.
14
+ * @param filter The filter to analyze.
15
+ * @returns A promise that resolves to a FilterPreciseValues object containing the precise values for each property.
16
+ * @example
17
+ * const preciseValues = await Filter.getPreciseValues(meta, {
18
+ * status: { $ne: 'active' },
19
+ * $or: [
20
+ * { customerId: ["1", "2"] },
21
+ * { customerId: "3" }
22
+ * ]
23
+ * });
24
+ * console.log(preciseValues);
25
+ * // Output:
26
+ * // {
27
+ * // "customerId": ["1", "2", "3"], // Precise values inferred from the filter
28
+ * // "status": undefined, // Cannot infer precise values for 'status'
29
+ * // }
30
+
31
+ */
32
+ static getPreciseValues<entityType>(metadata: EntityMetadata<entityType>, filter: EntityFilter<entityType>): Promise<FilterPreciseValues<entityType>>;
33
+ /**
34
+ * Retrieves precise values for each property in a filter for an entity.
35
+ * @template entityType The type of the entity being filtered.
36
+ * @param metadata The metadata of the entity being filtered.
37
+ * @param filter The filter to analyze.
38
+ * @returns A promise that resolves to a FilterPreciseValues object containing the precise values for each property.
39
+ * @example
40
+ * const preciseValues = await where.getPreciseValues();
41
+ * console.log(preciseValues);
42
+ * // Output:
43
+ * // {
44
+ * // "customerId": ["1", "2", "3"], // Precise values inferred from the filter
45
+ * // "status": undefined, // Cannot infer precise values for 'status'
46
+ * // }
47
+
48
+ */
49
+ getPreciseValues<entityType>(): Promise<FilterPreciseValues<entityType>>;
10
50
  /**
11
51
  * Creates a custom filter. Custom filters are evaluated on the backend, ensuring security and efficiency.
12
52
  * When the filter is used in the frontend, only its name is sent to the backend via the API,
13
53
  * where the filter gets translated and applied in a safe manner.
14
54
  *
15
55
  * @template entityType The entity type for the filter.
16
- * @param {function(): EntityFilter<entityType>} rawFilterTranslator A function that returns an `EntityFilter`.
56
+ * @param {function(): EntityFilter<entityType>} translator A function that returns an `EntityFilter`.
17
57
  * @param {string} [key] An optional unique identifier for the custom filter.
18
58
  * @returns {function(): EntityFilter<entityType>} A function that returns an `EntityFilter` of type `entityType`.
19
59
  *
20
60
  * @example
21
- * // In an entity class, add a static method for the custom filter
22
- * static titleLengthFilter = Filter.createCustom<Task>(() => {
23
- * return SqlDatabase.rawFilter((whereFragment) => {
24
- * whereFragment.sql = 'length(title) > 10';
25
- * });
26
- * });
27
- *
61
+ * class Order {
62
+ * //...
63
+ * static activeOrdersFor = Filter.createCustom<Order, { year: number }>(
64
+ * async ({ year }) => {
65
+ * return {
66
+ * status: ['created', 'confirmed', 'pending', 'blocked', 'delayed'],
67
+ * createdAt: {
68
+ * $gte: new Date(year, 0, 1),
69
+ * $lt: new Date(year + 1, 0, 1),
70
+ * },
71
+ * }
72
+ * },
73
+ * )
74
+ *}
28
75
  * // Usage
29
- * console.table(
30
- * await remult.repo(Task).find({
31
- * where: Task.titleLengthFilter()
32
- * })
33
- * );
76
+ * await repo(Order).find({
77
+ * where: Order.activeOrders({ year }),
78
+ *})
79
+
80
+
34
81
  * @see
35
82
  * [Sql filter and Custom filter](/docs/custom-filter.html)
36
83
  * [Filtering and Relations](/docs/filtering-and-relations.html)
37
84
  */
38
- static createCustom<entityType>(rawFilterTranslator: (unused: never, r: Remult) => EntityFilter<entityType> | Promise<EntityFilter<entityType>>, key?: string): (() => EntityFilter<entityType>) & customFilterInfo<entityType>;
85
+ static createCustom<entityType>(translator: (unused: never, r: Remult) => EntityFilter<entityType> | Promise<EntityFilter<entityType>>, key?: string): (() => EntityFilter<entityType>) & customFilterInfo<entityType>;
39
86
  /**
40
87
  * Creates a custom filter. Custom filters are evaluated on the backend, ensuring security and efficiency.
41
- * When the filter is used in the frontend, only its name and value arguments are sent to the backend via the API,
88
+ * When the filter is used in the frontend, only its name is sent to the backend via the API,
42
89
  * where the filter gets translated and applied in a safe manner.
43
90
  *
44
91
  * @template entityType The entity type for the filter.
45
- * @template argsType The type of the argument for the filter.
46
- * @param {function(argsType, Remult): EntityFilter<entityType>} rawFilterTranslator A function that takes an argument of type `argsType` and an instance of `Remult`, and returns an `EntityFilter`.
92
+ * @param {function(): EntityFilter<entityType>} translator A function that returns an `EntityFilter`.
47
93
  * @param {string} [key] An optional unique identifier for the custom filter.
48
- * @returns {function(argsType): EntityFilter<entityType>} A function that takes an argument of type `argsType` and returns an `EntityFilter` of type `entityType`.
94
+ * @returns {function(): EntityFilter<entityType>} A function that returns an `EntityFilter` of type `entityType`.
49
95
  *
50
96
  * @example
51
- * // In an entity class, add a static method for the custom filter with parameters
52
- * static filterCity = Filter.createCustom<Order, { city: string }>(
53
- * async ({ city }) => {
54
- * const orders = await dbNamesOf(Order);
55
- * const customers = await dbNamesOf(Customer);
56
- * return SqlDatabase.rawFilter(async (whereFragment) => {
57
- * whereFragment.sql = `${orders.customer} in
58
- * (select ${customers.id}
59
- * from ${customers}
60
- * where ${await whereFragment.filterToRaw(Customer, { city })})`;
61
- * });
62
- * }
63
- * );
64
- *
97
+ * class Order {
98
+ * //...
99
+ * static activeOrdersFor = Filter.createCustom<Order, { year: number }>(
100
+ * async ({ year }) => {
101
+ * return {
102
+ * status: ['created', 'confirmed', 'pending', 'blocked', 'delayed'],
103
+ * createdAt: {
104
+ * $gte: new Date(year, 0, 1),
105
+ * $lt: new Date(year + 1, 0, 1),
106
+ * },
107
+ * }
108
+ * },
109
+ * )
110
+ *}
65
111
  * // Usage
66
- * const cityFilter = Order.filterCity({ city: 'New York' });
67
- * const ordersInNewYork = await remult.repo(Order).find({ where: cityFilter });
68
- *
69
- * // The filter is sent to the backend as:
70
- * // http://127.0.0.1:3002/api/orders?%24custom%24filterCity=%7B%22city%22%3A%22New%20York%22%7D
112
+ * await repo(Order).find({
113
+ * where: Order.activeOrders({ year }),
114
+ *})
115
+
116
+
71
117
  * @see
72
118
  * [Sql filter and Custom filter](/docs/custom-filter.html)
73
119
  * [Filtering and Relations](/docs/filtering-and-relations.html)
74
120
  */
75
- static createCustom<entityType, argsType>(rawFilterTranslator: (args: argsType, r: Remult) => EntityFilter<entityType> | Promise<EntityFilter<entityType>>, key?: string): ((y: argsType) => EntityFilter<entityType>) & customFilterInfo<entityType>;
121
+ static createCustom<entityType, argsType>(translator: (args: argsType, r: Remult) => EntityFilter<entityType> | Promise<EntityFilter<entityType>>, key?: string): ((y: argsType) => EntityFilter<entityType>) & customFilterInfo<entityType>;
76
122
  /**
77
123
  * Translates an `EntityFilter` to a plain JSON object that can be stored or transported.
78
124
  *
@@ -135,6 +181,7 @@ export declare class Filter {
135
181
  * @returns {Promise<EntityFilter<entityType>>} The resolved entity filter.
136
182
  */
137
183
  static resolve<entityType>(filter: EntityFilter<entityType> | (() => EntityFilter<entityType> | Promise<EntityFilter<entityType>>)): Promise<EntityFilter<entityType>>;
184
+ toJson(): any;
138
185
  }
139
186
  export interface FilterConsumer {
140
187
  or(orElements: Filter[]): any;
@@ -194,3 +241,23 @@ export interface customFilterInfo<entityType> {
194
241
  };
195
242
  }
196
243
  export declare function __updateEntityBasedOnWhere<T>(entityDefs: EntityMetadata<T>, where: EntityFilter<T>, r: T): void;
244
+ /**
245
+ * A mapping of property names to arrays of precise values for those properties.
246
+ * @example
247
+ * const preciseValues = await getPreciseValues(meta, {
248
+ * status: { $ne: 'active' },
249
+ * $or: [
250
+ * { customerId: ["1", "2"] },
251
+ * { customerId: "3" }
252
+ * ]
253
+ * });
254
+ * console.log(preciseValues);
255
+ * // Output:
256
+ * // {
257
+ * // "customerId": ["1", "2", "3"], // Precise values inferred from the filter
258
+ * // "status": undefined, // Cannot infer precise values for 'status'
259
+ * // }
260
+ */
261
+ export type FilterPreciseValues<entityType> = {
262
+ [Properties in keyof MembersOnly<entityType>]?: Partial<entityType[Properties]>[];
263
+ };