remult 0.25.8-exp.1 → 0.25.8-exp.2

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.
@@ -5,6 +5,18 @@ import { updateMigrationsFile } from './update-migrations-ts-file.js';
5
5
  import { DefaultMigrationBuilder, } from './migration-types.js';
6
6
  import { isOfType } from '../src/isOfType.js';
7
7
  import { initDataProvider } from '../server/initDataProvider.js';
8
+ /**
9
+ * Generates migration scripts based on changes in entities.
10
+ *
11
+ * @param options - Configuration options for generating migrations.
12
+ * @param options.entities - An array of entity classes to be included in the migration.
13
+ * @param options.dataProvider - The data provider instance or a function returning a promise of the data provider.
14
+ * @param options.migrationsFolder - (Optional) The path to the folder where migration scripts will be stored. Default is 'src/server/migrations'.
15
+ * @param options.snapshotFile - (Optional) The path to the file where the snapshot of the last known state will be stored. Default is 'migrations-snapshot.json' in the `migrationsFolder`.
16
+ * @param options.migrationsTSFile - (Optional) The path to the TypeScript file where the generated migrations will be written. Default is 'migrations.ts' in the `migrationsFolder`.
17
+ * @param options.endConnection - (Optional) Determines whether to close the database connection after generating migrations. Default is true.
18
+ * @see [Migrations](http://remult.dev/docs/migrations.html)
19
+ */
8
20
  export async function generateMigrations(options) {
9
21
  const migrationDir = options.migrationsFolder ||
10
22
  path.join(process.cwd(), 'src/server/migrations');
@@ -3,6 +3,16 @@ import { Entity, IdEntity, Fields, remult, Remult, } from '../index.js';
3
3
  import { initDataProvider } from '../server/initDataProvider.js';
4
4
  import { doTransaction } from '../src/context.js';
5
5
  import { cast, isOfType } from '../src/isOfType.js';
6
+ /**
7
+ * Applies migration scripts to update the database schema.
8
+ *
9
+ * @param options - Configuration options for applying migrations.
10
+ * @param options.migrations - An object containing the migration scripts, each keyed by a unique identifier.
11
+ * @param options.dataProvider - The data provider instance or a function returning a promise of the data provider.
12
+ * @param options.migrationsTable - (Optional) The name of the table that tracks applied migrations. Default is '__remult_migrations_version'.
13
+ * @param options.endConnection - (Optional) Determines whether to close the database connection after applying migrations. Default is true.
14
+ * @see [Migrations](http://remult.dev/docs/migrations.html)
15
+ */
6
16
  export async function migrate(options) {
7
17
  let migrationTableName = options.migrationsTable || '__remult_migrations_version';
8
18
  const dataProvider = await initDataProvider(options.dataProvider);
@@ -8,8 +8,8 @@ export class PostgresDataProvider {
8
8
  pool;
9
9
  options;
10
10
  supportsJsonColumnType = true;
11
- static getDb(remult) {
12
- const r = (remult || defaultRemult).dataProvider;
11
+ static getDb(dataProvider) {
12
+ const r = (dataProvider || defaultRemult.dataProvider);
13
13
  if (!r._getSourceSql)
14
14
  throw 'the data provider is not an SqlDatabase';
15
15
  const me = r._getSourceSql();
@@ -41,8 +41,8 @@ export class KnexDataProvider {
41
41
  execute(sql) {
42
42
  return this.createCommand().execute(sql);
43
43
  }
44
- static getDb(remult) {
45
- const r = (remult || remultContext).dataProvider;
44
+ static getDb(dataProvider) {
45
+ const r = (dataProvider || remultContext.dataProvider);
46
46
  if (!r.knex)
47
47
  throw 'the data provider is not an KnexDataProvider';
48
48
  return r.knex;
@@ -16,8 +16,8 @@ export class MongoDataProvider {
16
16
  }
17
17
  session;
18
18
  disableTransactions = false;
19
- static getDb(remult) {
20
- const r = (remult || remultContext).dataProvider;
19
+ static getDb(dataProvider) {
20
+ const r = (dataProvider || remultContext.dataProvider);
21
21
  if (!r.db)
22
22
  throw 'the data provider is not a MongoDataProvider';
23
23
  return { db: r.db, session: r.session };
@@ -13,6 +13,7 @@ import { serverActionField } from '../src/server-action-info.js';
13
13
  import { initDataProvider } from './initDataProvider.js';
14
14
  import { remultStatic } from '../src/remult-static.js';
15
15
  import remultAdminHtml from './remult-admin.js';
16
+ import { isOfType } from '../src/isOfType.js';
16
17
  export function createRemultServerCore(options, serverCoreOptions) {
17
18
  if (!options) {
18
19
  options = {};
@@ -285,7 +286,7 @@ export class RemultServerImplementation {
285
286
  genReq.params = req['_tempParams'];
286
287
  }
287
288
  let myReq = new ExpressRequestBridgeToDataApiRequest(genReq);
288
- let myRes = new ExpressResponseBridgeToDataApiResponse(origRes, req);
289
+ let myRes = new ExpressResponseBridgeToDataApiResponse(origRes, req, this.options.error);
289
290
  try {
290
291
  await this.runWithRemult(async (remult) => {
291
292
  if (req) {
@@ -315,7 +316,7 @@ export class RemultServerImplementation {
315
316
  });
316
317
  }
317
318
  catch (err) {
318
- myRes.error(serializeError(err));
319
+ myRes.error(err, undefined);
319
320
  }
320
321
  };
321
322
  }
@@ -673,15 +674,17 @@ class ExpressRequestBridgeToDataApiRequest {
673
674
  class ExpressResponseBridgeToDataApiResponse {
674
675
  r;
675
676
  req;
677
+ handleError;
676
678
  forbidden() {
677
- this.setStatus(403).end();
679
+ this.error({ message: 'Forbidden' }, undefined, 403);
678
680
  }
679
681
  setStatus(status) {
680
682
  return this.r.status(status);
681
683
  }
682
- constructor(r, req) {
684
+ constructor(r, req, handleError) {
683
685
  this.r = r;
684
686
  this.req = req;
687
+ this.handleError = handleError;
685
688
  }
686
689
  progress(progress) { }
687
690
  success(data) {
@@ -694,22 +697,53 @@ class ExpressResponseBridgeToDataApiResponse {
694
697
  this.setStatus(204).end();
695
698
  }
696
699
  notFound() {
697
- this.setStatus(404).end();
700
+ this.error({ message: 'Forbidden' }, undefined, 404);
698
701
  }
699
- error(data) {
700
- data = serializeError(data);
701
- console.error({
702
- message: data.message,
703
- stack: data.stack?.split('\n'),
704
- url: this.req?.url,
705
- method: this.req?.method,
702
+ async error(exception, entity, httpStatusCode) {
703
+ let data = serializeError(exception);
704
+ if (!httpStatusCode) {
705
+ if (data.httpStatusCode) {
706
+ httpStatusCode = data.httpStatusCode;
707
+ }
708
+ else if (isOfType(exception, 'isForbiddenError') &&
709
+ exception.isForbiddenError) {
710
+ httpStatusCode = 403;
711
+ }
712
+ else if (data.modelState) {
713
+ httpStatusCode = 400;
714
+ }
715
+ else {
716
+ httpStatusCode = 500;
717
+ }
718
+ }
719
+ let responseSent = false;
720
+ const sendError = (httpStatusCode, body) => {
721
+ if (responseSent) {
722
+ throw Error('Error response already sent');
723
+ }
724
+ responseSent = true;
725
+ console.error({
726
+ message: body.message,
727
+ httpStatusCode,
728
+ stack: data.stack?.split('\n'),
729
+ url: this.req?.url,
730
+ method: this.req?.method,
731
+ });
732
+ this.setStatus(httpStatusCode).json(body);
733
+ };
734
+ await this.handleError?.({
735
+ httpStatusCode,
736
+ req: this.req,
737
+ entity,
738
+ exception,
739
+ responseBody: data,
740
+ sendError,
706
741
  });
707
- this.setStatus(400).json(data);
742
+ if (!responseSent) {
743
+ sendError(httpStatusCode, data);
744
+ }
708
745
  }
709
746
  }
710
- function throwError() {
711
- throw 'Invalid';
712
- }
713
747
  class inProcessQueueHandler {
714
748
  storage;
715
749
  constructor(storage) {
@@ -61,7 +61,7 @@ export class DataApi {
61
61
  });
62
62
  }
63
63
  catch (err) {
64
- response.error(err);
64
+ response.error(err, this.repository.metadata);
65
65
  }
66
66
  }
67
67
  async deleteMany(response, request, filterBody) {
@@ -79,7 +79,7 @@ export class DataApi {
79
79
  });
80
80
  }
81
81
  catch (err) {
82
- response.error(err);
82
+ response.error(err, this.repository.metadata);
83
83
  }
84
84
  }
85
85
  async getArrayImpl(response, request, filterBody) {
@@ -159,7 +159,7 @@ export class DataApi {
159
159
  if (err.isForbiddenError)
160
160
  response.forbidden();
161
161
  else
162
- response.error(err);
162
+ response.error(err, this.repository.metadata);
163
163
  }
164
164
  }
165
165
  async liveQuery(response, request, filterBody, serializeContext, queryChannel) {
@@ -185,7 +185,7 @@ export class DataApi {
185
185
  if (err.isForbiddenError)
186
186
  response.forbidden();
187
187
  else
188
- response.error(err);
188
+ response.error(err, this.repository.metadata);
189
189
  }
190
190
  }
191
191
  async buildWhere(request, filterBody) {
@@ -232,13 +232,16 @@ export class DataApi {
232
232
  if (r.length == 0)
233
233
  response.notFound();
234
234
  else if (r.length > 1)
235
- response.error({ message: 'id is not unique' });
235
+ response.error({
236
+ message: `id "${id}" is not unique for entity ` +
237
+ this.repository.metadata.key,
238
+ }, this.repository.metadata, 500);
236
239
  else
237
240
  await what(r[0]);
238
241
  });
239
242
  }
240
243
  catch (err) {
241
- response.error(err);
244
+ response.error(err, this.repository.metadata);
242
245
  }
243
246
  }
244
247
  async updateMany(response, request, body) {
@@ -256,7 +259,7 @@ export class DataApi {
256
259
  });
257
260
  }
258
261
  catch (err) {
259
- response.error(err);
262
+ response.error(err, this.repository.metadata);
260
263
  }
261
264
  }
262
265
  async actualUpdate(row, body) {
@@ -313,7 +316,7 @@ export class DataApi {
313
316
  if (err.isForbiddenError)
314
317
  response.forbidden();
315
318
  else
316
- response.error(err);
319
+ response.error(err, this.repository.metadata);
317
320
  }
318
321
  }
319
322
  }
@@ -10,8 +10,8 @@ import { isOfType } from '../isOfType.js';
10
10
  // @dynamic
11
11
  export class SqlDatabase {
12
12
  sql;
13
- static getDb(remult) {
14
- const r = (remult || defaultRemult).dataProvider;
13
+ static getDb(dataProvider) {
14
+ const r = (dataProvider || defaultRemult.dataProvider);
15
15
  if (isOfType(r, 'createCommand'))
16
16
  return r;
17
17
  else
@@ -1,33 +1,17 @@
1
1
  export {};
2
- //p1 - migrations - in docs empesize that mysql doesn't support transactions in ddl, in docs empesize destroy for knex - // await dataProvider.knex.destroy()
3
- //https://gitkraken.dev/link/dnNjb2RlOi8vZWFtb2Rpby5naXRsZW5zL2xpbmsvci9mMDgzMWU0OWIyODJkMDlkNTA0NTcxYjYwNmUzNTMwODQ3NGIwY2M2L2YvcHJvamVjdHMvcGxheS13aXRoLW1pZ3JhdGlvbnMvc3JjL3NlcnZlci9idWlsZC1taWdyYXRpb25zLnRzP3VybD1odHRwcyUzQSUyRiUyRmdpdGh1Yi5jb20lMkZyZW11bHQlMkZyZW11bHQuZ2l0?origin=gitlens
4
- /*p1 - processError in remult express
5
- - Should we merge (notFound,error,forbidden) into one method in `DataApiResponse` type?
6
- https://gitkraken.dev/link/dnNjb2RlOi8vZWFtb2Rpby5naXRsZW5zL2xpbmsvci9mMDgzMWU0OWIyODJkMDlkNTA0NTcxYjYwNmUzNTMwODQ3NGIwY2M2L2YvcHJvamVjdHMvY29yZS9zcmMvZGF0YS1hcGkudHM%2FdXJsPWh0dHBzJTNBJTJGJTJGZ2l0aHViLmNvbSUyRnJlbXVsdCUyRnJlbXVsdC5naXQ%3D?origin=gitlens
7
- - The api of the `processError` should recieve an `ErrorInfo` object and return an `ErrorInfo` object? or should it return something more in the line,
8
- of httpStatus and errorBody. - currently the `serializeError` method is used to build the response
9
- https://gitkraken.dev/link/dnNjb2RlOi8vZWFtb2Rpby5naXRsZW5zL2xpbmsvci9mMDgzMWU0OWIyODJkMDlkNTA0NTcxYjYwNmUzNTMwODQ3NGIwY2M2L2YvcHJvamVjdHMvY29yZS9zcmMvZGF0YS1hcGkudHM%2FdXJsPWh0dHBzJTNBJTJGJTJGZ2l0aHViLmNvbSUyRnJlbXVsdCUyRnJlbXVsdC5naXQmbGluZXM9NDI1?origin=gitlens
10
- - I think there should be a way to throw a forbidden exception
11
- */
12
- //p1 - cleanup root directory of reult
13
2
  //p1 - in this video I'll use remult to turn a frontend app to a fullstack app
14
3
  /*p1 - discuss using delete & put - with url query language for deleteMany and updateMany -
15
- - put & delete, similar to get
16
- - add where to count, deleteMany,updateMany,
17
- - prevent delete all and update all - must have meaningful where.
18
- - try forcing this also in typescript
19
-
20
- - protect against deleting of all rows by mistake
21
- - https://github.com/remult/remult/issues/221#issuecomment-2016519746
22
-
23
- */
24
- /*p1 - I want to change the signature of getDb from getting optional remult, to getting optional dataProvider.
4
+ - put & delete, similar to get
5
+ - add where to count, deleteMany,updateMany,
6
+ - prevent delete all and update all - must have meaningful where.
7
+ - try forcing this also in typescript
25
8
 
26
- In most cases you will not send anything - and it'll be by default remult.dataProvider.
9
+ - protect against deleting of all rows by mistake
10
+ - https://github.com/remult/remult/issues/221#issuecomment-2016519746
27
11
 
28
- I just findmyself creating remults to extract the data provider :)
29
12
  */
30
13
  //y1 - getFields didn't work for kobi in the home component
14
+ //p1 - document offline support
31
15
  //p1 - add section to Fields doc, explaining field type in db
32
16
  //p1 - add section about union type
33
17
  //p1 - add section about value list field type
@@ -65,7 +65,7 @@ export class Action {
65
65
  // got a problem in next with instance of ForbiddenError - so replaced it with this bool
66
66
  res.forbidden();
67
67
  else
68
- res.error(err);
68
+ res.error(err, undefined);
69
69
  }
70
70
  });
71
71
  }
@@ -245,7 +245,7 @@ export function BackendMethod(options) {
245
245
  // got a problem in next with instance of ForbiddenError - so replaced it with this bool
246
246
  res.forbidden();
247
247
  else
248
- res.error(err);
248
+ res.error(err, undefined);
249
249
  }
250
250
  });
251
251
  }
@@ -1,4 +1,16 @@
1
1
  import type { DataProvider } from '../index.js';
2
+ /**
3
+ * Generates migration scripts based on changes in entities.
4
+ *
5
+ * @param options - Configuration options for generating migrations.
6
+ * @param options.entities - An array of entity classes to be included in the migration.
7
+ * @param options.dataProvider - The data provider instance or a function returning a promise of the data provider.
8
+ * @param options.migrationsFolder - (Optional) The path to the folder where migration scripts will be stored. Default is 'src/server/migrations'.
9
+ * @param options.snapshotFile - (Optional) The path to the file where the snapshot of the last known state will be stored. Default is 'migrations-snapshot.json' in the `migrationsFolder`.
10
+ * @param options.migrationsTSFile - (Optional) The path to the TypeScript file where the generated migrations will be written. Default is 'migrations.ts' in the `migrationsFolder`.
11
+ * @param options.endConnection - (Optional) Determines whether to close the database connection after generating migrations. Default is true.
12
+ * @see [Migrations](http://remult.dev/docs/migrations.html)
13
+ */
2
14
  export declare function generateMigrations(options: {
3
15
  entities: any[];
4
16
  dataProvider: DataProvider | Promise<DataProvider> | (() => Promise<DataProvider | undefined>);
@@ -9,6 +9,18 @@ var update_migrations_ts_file_js_1 = require("./update-migrations-ts-file.js");
9
9
  var migration_types_js_1 = require("./migration-types.js");
10
10
  var isOfType_js_1 = require("../src/isOfType.js");
11
11
  var initDataProvider_js_1 = require("../server/initDataProvider.js");
12
+ /**
13
+ * Generates migration scripts based on changes in entities.
14
+ *
15
+ * @param options - Configuration options for generating migrations.
16
+ * @param options.entities - An array of entity classes to be included in the migration.
17
+ * @param options.dataProvider - The data provider instance or a function returning a promise of the data provider.
18
+ * @param options.migrationsFolder - (Optional) The path to the folder where migration scripts will be stored. Default is 'src/server/migrations'.
19
+ * @param options.snapshotFile - (Optional) The path to the file where the snapshot of the last known state will be stored. Default is 'migrations-snapshot.json' in the `migrationsFolder`.
20
+ * @param options.migrationsTSFile - (Optional) The path to the TypeScript file where the generated migrations will be written. Default is 'migrations.ts' in the `migrationsFolder`.
21
+ * @param options.endConnection - (Optional) Determines whether to close the database connection after generating migrations. Default is true.
22
+ * @see [Migrations](http://remult.dev/docs/migrations.html)
23
+ */
12
24
  function generateMigrations(options) {
13
25
  return tslib_1.__awaiter(this, void 0, void 0, function () {
14
26
  var migrationDir, snapshotFileName, migrationsTSFilename, dataProvider, _a, _b, p, snapshot, hasSql, steps, code, migrationBuilder;
@@ -1,5 +1,15 @@
1
1
  import { type DataProvider } from '../index.js';
2
2
  import type { Migrations } from './migration-types.js';
3
+ /**
4
+ * Applies migration scripts to update the database schema.
5
+ *
6
+ * @param options - Configuration options for applying migrations.
7
+ * @param options.migrations - An object containing the migration scripts, each keyed by a unique identifier.
8
+ * @param options.dataProvider - The data provider instance or a function returning a promise of the data provider.
9
+ * @param options.migrationsTable - (Optional) The name of the table that tracks applied migrations. Default is '__remult_migrations_version'.
10
+ * @param options.endConnection - (Optional) Determines whether to close the database connection after applying migrations. Default is true.
11
+ * @see [Migrations](http://remult.dev/docs/migrations.html)
12
+ */
3
13
  export declare function migrate(options: {
4
14
  migrations: Migrations;
5
15
  dataProvider: DataProvider | Promise<DataProvider> | (() => Promise<DataProvider | undefined>);
@@ -6,6 +6,16 @@ var index_js_1 = require("../index.js");
6
6
  var initDataProvider_js_1 = require("../server/initDataProvider.js");
7
7
  var context_js_1 = require("../src/context.js");
8
8
  var isOfType_js_1 = require("../src/isOfType.js");
9
+ /**
10
+ * Applies migration scripts to update the database schema.
11
+ *
12
+ * @param options - Configuration options for applying migrations.
13
+ * @param options.migrations - An object containing the migration scripts, each keyed by a unique identifier.
14
+ * @param options.dataProvider - The data provider instance or a function returning a promise of the data provider.
15
+ * @param options.migrationsTable - (Optional) The name of the table that tracks applied migrations. Default is '__remult_migrations_version'.
16
+ * @param options.endConnection - (Optional) Determines whether to close the database connection after applying migrations. Default is true.
17
+ * @see [Migrations](http://remult.dev/docs/migrations.html)
18
+ */
9
19
  function migrate(options) {
10
20
  return tslib_1.__awaiter(this, void 0, void 0, function () {
11
21
  var migrationTableName, dataProvider, prev, VersionInfo, repo, v, steps, _loop_1, steps_1, steps_1_1, _a, stepText, action, e_1_1;
@@ -11,6 +11,26 @@ export interface MigrationCode {
11
11
  addComment(comment: string): void;
12
12
  addTypescriptCode(code: string): void;
13
13
  }
14
+ /**
15
+ * Represents a set of migrations, each identified by a unique number.
16
+ *
17
+ * Each migration function takes a `MigrationUtils` object as a parameter, which provides utility methods for executing SQL statements and other migration-related operations.
18
+ *
19
+ * Migrations are executed in a transaction, meaning that all changes within a single migration are either fully applied or fully rolled back in case of an error. This ensures the consistency of the database state.
20
+ *
21
+ * Note: Some databases, like MySQL, do not support rolling back structural changes (e.g., table creation or alteration) as part of a transaction. Developers should be aware of this when designing migrations for such databases.
22
+ *
23
+ * @example
24
+ * export const migrations: Migrations = {
25
+ * 1: async ({ sql }) => {
26
+ * await sql(`CREATE TABLE example (id SERIAL PRIMARY KEY, name VARCHAR(255))`);
27
+ * },
28
+ * 2: async ({ sql }) => {
29
+ * await sql(`ALTER TABLE example ADD COLUMN description TEXT`);
30
+ * },
31
+ * };
32
+ * @see [Migrations](http://remult.dev/docs/migrations.html)
33
+ */
14
34
  export type Migrations = Record<number, (utils: MigrationUtils) => Promise<unknown>>;
15
35
  export type MigrationUtils = {
16
36
  sql(sql: string): Promise<unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remult",
3
- "version": "0.25.8-exp.1",
3
+ "version": "0.25.8-exp.2",
4
4
  "description": "A CRUD framework for full-stack TypeScript",
5
5
  "homepage": "https://remult.dev",
6
6
  "repository": {
@@ -1,9 +1,9 @@
1
1
  import type { ClientBase, PoolConfig, QueryResult } from 'pg';
2
- import { Remult } from '../src/context.js';
3
2
  import { SqlDatabase } from '../src/data-providers/sql-database.js';
4
3
  import type { EntityMetadata } from '../src/remult3/remult3.js';
5
4
  import type { SqlCommand, SqlImplementation } from '../src/sql-command.js';
6
5
  import type { CanBuildMigrations, MigrationBuilder, MigrationCode } from '../migrations/migration-types.js';
6
+ import type { DataProvider } from '../index.js';
7
7
  export interface PostgresPool extends PostgresCommandSource {
8
8
  connect(): Promise<PostgresClient>;
9
9
  end(): Promise<void>;
@@ -15,7 +15,7 @@ export declare class PostgresDataProvider implements SqlImplementation, CanBuild
15
15
  private pool;
16
16
  private options?;
17
17
  supportsJsonColumnType: boolean;
18
- static getDb(remult?: Remult): ClientBase;
18
+ static getDb(dataProvider?: DataProvider): ClientBase;
19
19
  entityIsUsedForTheFirstTime(entity: EntityMetadata): Promise<void>;
20
20
  getLimitSqlSyntax(limit: number, offset: number): string;
21
21
  createCommand(): SqlCommand;
@@ -31,8 +31,8 @@ var PostgresDataProvider = /** @class */ (function () {
31
31
  this.pool = new PostgresSchemaWrapper(pool, options.schema);
32
32
  }
33
33
  }
34
- PostgresDataProvider.getDb = function (remult) {
35
- var r = (remult || remult_proxy_js_1.remult).dataProvider;
34
+ PostgresDataProvider.getDb = function (dataProvider) {
35
+ var r = (dataProvider || remult_proxy_js_1.remult.dataProvider);
36
36
  if (!r._getSourceSql)
37
37
  throw 'the data provider is not an SqlDatabase';
38
38
  var me = r._getSourceSql();
@@ -13,7 +13,7 @@ export declare class KnexDataProvider implements DataProvider, HasWrapIdentifier
13
13
  end(): Promise<void>;
14
14
  createCommand(): SqlCommand;
15
15
  execute(sql: string): Promise<SqlResult>;
16
- static getDb(remult?: Remult): Knex<any, any[]>;
16
+ static getDb(dataProvider?: DataProvider): Knex<any, any[]>;
17
17
  wrapIdentifier: (name: string) => string;
18
18
  getEntityDataProvider(entity: EntityMetadata<any>): EntityDataProvider;
19
19
  transaction(action: (dataProvider: DataProvider) => Promise<void>): Promise<void>;
@@ -68,8 +68,8 @@ var KnexDataProvider = /** @class */ (function () {
68
68
  KnexDataProvider.prototype.execute = function (sql) {
69
69
  return this.createCommand().execute(sql);
70
70
  };
71
- KnexDataProvider.getDb = function (remult) {
72
- var r = (remult || remult_proxy_js_1.remult).dataProvider;
71
+ KnexDataProvider.getDb = function (dataProvider) {
72
+ var r = (dataProvider || remult_proxy_js_1.remult.dataProvider);
73
73
  if (!r.knex)
74
74
  throw 'the data provider is not an KnexDataProvider';
75
75
  return r.knex;
package/remult-mongo.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type ClientSession, type Db, type MongoClient } from 'mongodb';
2
- import type { DataProvider, EntityDataProvider, EntityFilter, EntityMetadata, Remult } from './index.js';
2
+ import type { DataProvider, EntityDataProvider, EntityFilter, EntityMetadata } from './index.js';
3
3
  import type { RepositoryOverloads } from './src/remult3/RepositoryImplementation.js';
4
4
  export declare class MongoDataProvider implements DataProvider {
5
5
  private db;
@@ -10,7 +10,7 @@ export declare class MongoDataProvider implements DataProvider {
10
10
  });
11
11
  session?: ClientSession;
12
12
  disableTransactions: boolean;
13
- static getDb(remult?: Remult): {
13
+ static getDb(dataProvider?: DataProvider): {
14
14
  db: Db;
15
15
  session: ClientSession;
16
16
  };
package/remult-mongo.js CHANGED
@@ -17,8 +17,8 @@ var MongoDataProvider = /** @class */ (function () {
17
17
  this.session = options === null || options === void 0 ? void 0 : options.session;
18
18
  this.disableTransactions = Boolean(options === null || options === void 0 ? void 0 : options.disableTransactions);
19
19
  }
20
- MongoDataProvider.getDb = function (remult) {
21
- var r = (remult || remult_proxy_js_1.remult).dataProvider;
20
+ MongoDataProvider.getDb = function (dataProvider) {
21
+ var r = (dataProvider || remult_proxy_js_1.remult.dataProvider);
22
22
  if (!r.db)
23
23
  throw 'the data provider is not a MongoDataProvider';
24
24
  return { db: r.db, session: r.session };
@@ -55,6 +55,27 @@ export interface RemultServerOptions<RequestType> {
55
55
  admin?: Allowed;
56
56
  /** Storage to use for backend methods that use queue */
57
57
  queueStorage?: QueueStorage;
58
+ /**
59
+ * This method is called whenever there is an error in the API lifecycle.
60
+ *
61
+ * @param info - Information about the error.
62
+ * @param info.req - The request object.
63
+ * @param info.entity - (Optional) The entity metadata associated with the error, if applicable.
64
+ * @param info.exception - (Optional) The exception object or error that occurred.
65
+ * @param info.httpStatusCode - The HTTP status code.
66
+ * @param info.responseBody - The body of the response.
67
+ * @param info.sendError - A method to send a custom error response. Call this method with the desired HTTP status code and response body.
68
+ *
69
+ * @returns A promise that resolves when the error handling is complete.
70
+ * @example
71
+ * export const api = remultExpress({
72
+ * error: async (e) => {
73
+ * if (e.httpStatusCode == 500) {
74
+ * e.sendError(500, { message: "An error occurred" })
75
+ * }
76
+ * }
77
+ * })
78
+ */
58
79
  error?: (info: {
59
80
  req: RequestType;
60
81
  entity?: EntityMetadata;
@@ -16,6 +16,7 @@ var server_action_info_js_1 = require("../src/server-action-info.js");
16
16
  var initDataProvider_js_1 = require("./initDataProvider.js");
17
17
  var remult_static_js_1 = require("../src/remult-static.js");
18
18
  var remult_admin_js_1 = tslib_1.__importDefault(require("./remult-admin.js"));
19
+ var isOfType_js_1 = require("../src/isOfType.js");
19
20
  function createRemultServerCore(options, serverCoreOptions) {
20
21
  if (!options) {
21
22
  options = {};
@@ -543,7 +544,7 @@ var RemultServerImplementation = /** @class */ (function () {
543
544
  genReq.params = req['_tempParams'];
544
545
  }
545
546
  myReq = new ExpressRequestBridgeToDataApiRequest(genReq);
546
- myRes = new ExpressResponseBridgeToDataApiResponse(origRes, req);
547
+ myRes = new ExpressResponseBridgeToDataApiResponse(origRes, req, this.options.error);
547
548
  _a.label = 1;
548
549
  case 1:
549
550
  _a.trys.push([1, 3, , 4]);
@@ -592,7 +593,7 @@ var RemultServerImplementation = /** @class */ (function () {
592
593
  return [3 /*break*/, 4];
593
594
  case 3:
594
595
  err_1 = _a.sent();
595
- myRes.error((0, data_api_js_1.serializeError)(err_1));
596
+ myRes.error(err_1, undefined);
596
597
  return [3 /*break*/, 4];
597
598
  case 4: return [2 /*return*/];
598
599
  }
@@ -1001,12 +1002,13 @@ var ExpressRequestBridgeToDataApiRequest = /** @class */ (function () {
1001
1002
  return ExpressRequestBridgeToDataApiRequest;
1002
1003
  }());
1003
1004
  var ExpressResponseBridgeToDataApiResponse = /** @class */ (function () {
1004
- function ExpressResponseBridgeToDataApiResponse(r, req) {
1005
+ function ExpressResponseBridgeToDataApiResponse(r, req, handleError) {
1005
1006
  this.r = r;
1006
1007
  this.req = req;
1008
+ this.handleError = handleError;
1007
1009
  }
1008
1010
  ExpressResponseBridgeToDataApiResponse.prototype.forbidden = function () {
1009
- this.setStatus(403).end();
1011
+ this.error({ message: 'Forbidden' }, undefined, 403);
1010
1012
  };
1011
1013
  ExpressResponseBridgeToDataApiResponse.prototype.setStatus = function (status) {
1012
1014
  return this.r.status(status);
@@ -1022,24 +1024,68 @@ var ExpressResponseBridgeToDataApiResponse = /** @class */ (function () {
1022
1024
  this.setStatus(204).end();
1023
1025
  };
1024
1026
  ExpressResponseBridgeToDataApiResponse.prototype.notFound = function () {
1025
- this.setStatus(404).end();
1027
+ this.error({ message: 'Forbidden' }, undefined, 404);
1026
1028
  };
1027
- ExpressResponseBridgeToDataApiResponse.prototype.error = function (data) {
1028
- var _a, _b, _c;
1029
- data = (0, data_api_js_1.serializeError)(data);
1030
- console.error({
1031
- message: data.message,
1032
- stack: (_a = data.stack) === null || _a === void 0 ? void 0 : _a.split('\n'),
1033
- url: (_b = this.req) === null || _b === void 0 ? void 0 : _b.url,
1034
- method: (_c = this.req) === null || _c === void 0 ? void 0 : _c.method,
1029
+ ExpressResponseBridgeToDataApiResponse.prototype.error = function (exception, entity, httpStatusCode) {
1030
+ var _a;
1031
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
1032
+ var data, responseSent, sendError;
1033
+ var _this = this;
1034
+ return tslib_1.__generator(this, function (_b) {
1035
+ switch (_b.label) {
1036
+ case 0:
1037
+ data = (0, data_api_js_1.serializeError)(exception);
1038
+ if (!httpStatusCode) {
1039
+ if (data.httpStatusCode) {
1040
+ httpStatusCode = data.httpStatusCode;
1041
+ }
1042
+ else if ((0, isOfType_js_1.isOfType)(exception, 'isForbiddenError') &&
1043
+ exception.isForbiddenError) {
1044
+ httpStatusCode = 403;
1045
+ }
1046
+ else if (data.modelState) {
1047
+ httpStatusCode = 400;
1048
+ }
1049
+ else {
1050
+ httpStatusCode = 500;
1051
+ }
1052
+ }
1053
+ responseSent = false;
1054
+ sendError = function (httpStatusCode, body) {
1055
+ var _a, _b, _c;
1056
+ if (responseSent) {
1057
+ throw Error('Error response already sent');
1058
+ }
1059
+ responseSent = true;
1060
+ console.error({
1061
+ message: body.message,
1062
+ httpStatusCode: httpStatusCode,
1063
+ stack: (_a = data.stack) === null || _a === void 0 ? void 0 : _a.split('\n'),
1064
+ url: (_b = _this.req) === null || _b === void 0 ? void 0 : _b.url,
1065
+ method: (_c = _this.req) === null || _c === void 0 ? void 0 : _c.method,
1066
+ });
1067
+ _this.setStatus(httpStatusCode).json(body);
1068
+ };
1069
+ return [4 /*yield*/, ((_a = this.handleError) === null || _a === void 0 ? void 0 : _a.call(this, {
1070
+ httpStatusCode: httpStatusCode,
1071
+ req: this.req,
1072
+ entity: entity,
1073
+ exception: exception,
1074
+ responseBody: data,
1075
+ sendError: sendError,
1076
+ }))];
1077
+ case 1:
1078
+ _b.sent();
1079
+ if (!responseSent) {
1080
+ sendError(httpStatusCode, data);
1081
+ }
1082
+ return [2 /*return*/];
1083
+ }
1084
+ });
1035
1085
  });
1036
- this.setStatus(400).json(data);
1037
1086
  };
1038
1087
  return ExpressResponseBridgeToDataApiResponse;
1039
1088
  }());
1040
- function throwError() {
1041
- throw 'Invalid';
1042
- }
1043
1089
  var inProcessQueueHandler = /** @class */ (function () {
1044
1090
  function inProcessQueueHandler(storage) {
1045
1091
  this.storage = storage;
package/src/data-api.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Remult } from './context.js';
2
2
  import type { ErrorInfo } from './data-interfaces.js';
3
- import type { FindOptions, Repository } from './remult3/remult3.js';
3
+ import type { EntityMetadata, FindOptions, Repository } from './remult3/remult3.js';
4
4
  import type { rowHelperImplementation } from './remult3/RepositoryImplementation.js';
5
5
  export declare class DataApi<T = any> {
6
6
  private repository;
@@ -33,7 +33,7 @@ export interface DataApiResponse {
33
33
  deleted(): void;
34
34
  created(data: any): void;
35
35
  notFound(): void;
36
- error(data: ErrorInfo): void;
36
+ error(data: ErrorInfo, entity: EntityMetadata | undefined, statusCode?: number | undefined): void;
37
37
  forbidden(): void;
38
38
  progress(progress: number): void;
39
39
  }
package/src/data-api.js CHANGED
@@ -100,7 +100,7 @@ var DataApi = /** @class */ (function () {
100
100
  return [3 /*break*/, 5];
101
101
  case 4:
102
102
  err_1 = _f.sent();
103
- response.error(err_1);
103
+ response.error(err_1, this.repository.metadata);
104
104
  return [3 /*break*/, 5];
105
105
  case 5: return [2 /*return*/];
106
106
  }
@@ -175,7 +175,7 @@ var DataApi = /** @class */ (function () {
175
175
  case 1: return [2 /*return*/, _a.sent()];
176
176
  case 2:
177
177
  err_2 = _a.sent();
178
- response.error(err_2);
178
+ response.error(err_2, this.repository.metadata);
179
179
  return [3 /*break*/, 3];
180
180
  case 3: return [2 /*return*/];
181
181
  }
@@ -304,7 +304,7 @@ var DataApi = /** @class */ (function () {
304
304
  if (err_3.isForbiddenError)
305
305
  response.forbidden();
306
306
  else
307
- response.error(err_3);
307
+ response.error(err_3, this.repository.metadata);
308
308
  return [3 /*break*/, 4];
309
309
  case 4: return [2 /*return*/];
310
310
  }
@@ -350,7 +350,7 @@ var DataApi = /** @class */ (function () {
350
350
  if (err_4.isForbiddenError)
351
351
  response.forbidden();
352
352
  else
353
- response.error(err_4);
353
+ response.error(err_4, this.repository.metadata);
354
354
  return [3 /*break*/, 6];
355
355
  case 6: return [2 /*return*/];
356
356
  }
@@ -429,7 +429,10 @@ var DataApi = /** @class */ (function () {
429
429
  return [3 /*break*/, 4];
430
430
  case 1:
431
431
  if (!(r.length > 1)) return [3 /*break*/, 2];
432
- response.error({ message: 'id is not unique' });
432
+ response.error({
433
+ message: "id \"".concat(id, "\" is not unique for entity ") +
434
+ this.repository.metadata.key,
435
+ }, this.repository.metadata, 500);
433
436
  return [3 /*break*/, 4];
434
437
  case 2: return [4 /*yield*/, what(r[0])];
435
438
  case 3:
@@ -444,7 +447,7 @@ var DataApi = /** @class */ (function () {
444
447
  return [3 /*break*/, 6];
445
448
  case 5:
446
449
  err_5 = _c.sent();
447
- response.error(err_5);
450
+ response.error(err_5, this.repository.metadata);
448
451
  return [3 /*break*/, 6];
449
452
  case 6: return [2 /*return*/];
450
453
  }
@@ -519,7 +522,7 @@ var DataApi = /** @class */ (function () {
519
522
  case 1: return [2 /*return*/, _a.sent()];
520
523
  case 2:
521
524
  err_6 = _a.sent();
522
- response.error(err_6);
525
+ response.error(err_6, this.repository.metadata);
523
526
  return [3 /*break*/, 3];
524
527
  case 3: return [2 /*return*/];
525
528
  }
@@ -690,7 +693,7 @@ var DataApi = /** @class */ (function () {
690
693
  if (err_7.isForbiddenError)
691
694
  response.forbidden();
692
695
  else
693
- response.error(err_7);
696
+ response.error(err_7, this.repository.metadata);
694
697
  return [3 /*break*/, 6];
695
698
  case 6: return [2 /*return*/];
696
699
  }
@@ -1,13 +1,12 @@
1
1
  import type { DataProvider, EntityDataProvider } from '../data-interfaces.js';
2
2
  import type { HasWrapIdentifier, SqlCommand, SqlCommandFactory, SqlCommandWithParameters, SqlImplementation, SqlResult } from '../sql-command.js';
3
- import type { Remult } from '../context.js';
4
3
  import type { CustomSqlFilterBuilderFunction, EntityDbNamesBase } from '../filter/filter-consumer-bridge-to-sql-request.js';
5
4
  import type { EntityFilter, EntityMetadata } from '../remult3/remult3.js';
6
5
  import type { RepositoryOverloads } from '../remult3/RepositoryImplementation.js';
7
6
  import type { CanBuildMigrations } from '../../migrations/migration-types.js';
8
7
  export declare class SqlDatabase implements DataProvider, HasWrapIdentifier, CanBuildMigrations, SqlCommandFactory {
9
8
  private sql;
10
- static getDb(remult?: Remult): SqlDatabase;
9
+ static getDb(dataProvider?: DataProvider): SqlDatabase;
11
10
  createCommand(): SqlCommand;
12
11
  execute(sql: string): Promise<SqlResult>;
13
12
  wrapIdentifier: (name: string) => string;
@@ -25,8 +25,8 @@ var SqlDatabase = /** @class */ (function () {
25
25
  if ((0, isOfType_js_1.isOfType)(sql, 'end'))
26
26
  this.end = function () { return sql.end(); };
27
27
  }
28
- SqlDatabase.getDb = function (remult) {
29
- var r = (remult || remult_proxy_js_1.remult).dataProvider;
28
+ SqlDatabase.getDb = function (dataProvider) {
29
+ var r = (dataProvider || remult_proxy_js_1.remult.dataProvider);
30
30
  if ((0, isOfType_js_1.isOfType)(r, 'createCommand'))
31
31
  return r;
32
32
  else
@@ -1,34 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- //p1 - migrations - in docs empesize that mysql doesn't support transactions in ddl, in docs empesize destroy for knex - // await dataProvider.knex.destroy()
4
- //https://gitkraken.dev/link/dnNjb2RlOi8vZWFtb2Rpby5naXRsZW5zL2xpbmsvci9mMDgzMWU0OWIyODJkMDlkNTA0NTcxYjYwNmUzNTMwODQ3NGIwY2M2L2YvcHJvamVjdHMvcGxheS13aXRoLW1pZ3JhdGlvbnMvc3JjL3NlcnZlci9idWlsZC1taWdyYXRpb25zLnRzP3VybD1odHRwcyUzQSUyRiUyRmdpdGh1Yi5jb20lMkZyZW11bHQlMkZyZW11bHQuZ2l0?origin=gitlens
5
- /*p1 - processError in remult express
6
- - Should we merge (notFound,error,forbidden) into one method in `DataApiResponse` type?
7
- https://gitkraken.dev/link/dnNjb2RlOi8vZWFtb2Rpby5naXRsZW5zL2xpbmsvci9mMDgzMWU0OWIyODJkMDlkNTA0NTcxYjYwNmUzNTMwODQ3NGIwY2M2L2YvcHJvamVjdHMvY29yZS9zcmMvZGF0YS1hcGkudHM%2FdXJsPWh0dHBzJTNBJTJGJTJGZ2l0aHViLmNvbSUyRnJlbXVsdCUyRnJlbXVsdC5naXQ%3D?origin=gitlens
8
- - The api of the `processError` should recieve an `ErrorInfo` object and return an `ErrorInfo` object? or should it return something more in the line,
9
- of httpStatus and errorBody. - currently the `serializeError` method is used to build the response
10
- https://gitkraken.dev/link/dnNjb2RlOi8vZWFtb2Rpby5naXRsZW5zL2xpbmsvci9mMDgzMWU0OWIyODJkMDlkNTA0NTcxYjYwNmUzNTMwODQ3NGIwY2M2L2YvcHJvamVjdHMvY29yZS9zcmMvZGF0YS1hcGkudHM%2FdXJsPWh0dHBzJTNBJTJGJTJGZ2l0aHViLmNvbSUyRnJlbXVsdCUyRnJlbXVsdC5naXQmbGluZXM9NDI1?origin=gitlens
11
- - I think there should be a way to throw a forbidden exception
12
- */
13
- //p1 - cleanup root directory of reult
14
3
  //p1 - in this video I'll use remult to turn a frontend app to a fullstack app
15
4
  /*p1 - discuss using delete & put - with url query language for deleteMany and updateMany -
16
- - put & delete, similar to get
17
- - add where to count, deleteMany,updateMany,
18
- - prevent delete all and update all - must have meaningful where.
19
- - try forcing this also in typescript
20
-
21
- - protect against deleting of all rows by mistake
22
- - https://github.com/remult/remult/issues/221#issuecomment-2016519746
23
-
24
- */
25
- /*p1 - I want to change the signature of getDb from getting optional remult, to getting optional dataProvider.
5
+ - put & delete, similar to get
6
+ - add where to count, deleteMany,updateMany,
7
+ - prevent delete all and update all - must have meaningful where.
8
+ - try forcing this also in typescript
26
9
 
27
- In most cases you will not send anything - and it'll be by default remult.dataProvider.
10
+ - protect against deleting of all rows by mistake
11
+ - https://github.com/remult/remult/issues/221#issuecomment-2016519746
28
12
 
29
- I just findmyself creating remults to extract the data provider :)
30
13
  */
31
14
  //y1 - getFields didn't work for kobi in the home component
15
+ //p1 - document offline support
32
16
  //p1 - add section to Fields doc, explaining field type in db
33
17
  //p1 - add section about union type
34
18
  //p1 - add section about value list field type
@@ -98,7 +98,7 @@ var Action = /** @class */ (function () {
98
98
  // got a problem in next with instance of ForbiddenError - so replaced it with this bool
99
99
  res.forbidden();
100
100
  else
101
- res.error(err_1);
101
+ res.error(err_1, undefined);
102
102
  return [3 /*break*/, 3];
103
103
  case 3: return [2 /*return*/];
104
104
  }
@@ -381,7 +381,7 @@ function BackendMethod(options) {
381
381
  // got a problem in next with instance of ForbiddenError - so replaced it with this bool
382
382
  res.forbidden();
383
383
  else
384
- res.error(err_3);
384
+ res.error(err_3, undefined);
385
385
  return [3 /*break*/, 4];
386
386
  case 4: return [2 /*return*/];
387
387
  }