remult 3.3.0 → 3.3.1

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.
@@ -256,7 +256,8 @@ class KnexEntityDataProvider {
256
256
  }
257
257
  }
258
258
  let insert = this.getEntityFrom(e).insert(insertObject);
259
- if (isAutoIncrement(this.entity.idMetadata.field)) {
259
+ if (isAutoIncrement(this.entity.idMetadata.field) &&
260
+ !(options?.select === 'none')) {
260
261
  let newId;
261
262
  if (this.knex.client.config.client === 'mysql2' ||
262
263
  this.knex.client.config.client === 'mysql') {
@@ -446,13 +446,15 @@ export class DataApi {
446
446
  response.error(err, this.repository.metadata);
447
447
  }
448
448
  }
449
- async actualUpdate(row, body, req) {
449
+ async actualUpdate(row, body, req, doNotSelect) {
450
450
  let ref = this.repository.getEntityRef(row);
451
451
  await ref._updateEntityBasedOnApi(body);
452
452
  if (!ref.apiUpdateAllowed) {
453
453
  throw new ForbiddenError();
454
454
  }
455
- let options = req.get('_select') === 'none' ? { select: 'none' } : undefined;
455
+ let options = req.get('_select') === 'none' || doNotSelect
456
+ ? { select: 'none' }
457
+ : undefined;
456
458
  await ref.save(options);
457
459
  if (options?.select === 'none')
458
460
  return undefined;
@@ -106,18 +106,66 @@ export const liveQueryKeepAliveRoute = '_liveQueryKeepAlive';
106
106
  *
107
107
  * @template messageType The type of the message that the channel will handle.
108
108
  * @example
109
+ * ```ts
109
110
  * // Defined in code that is shared between the frontend and the backend
110
- * const statusChange = new SubscriptionChannel<{ oldStatus: number, newStatus: number }>("statusChange");
111
+ * export const notif = new SubscriptionChannel<{ coolInfo: string }>("notif");
111
112
  *
112
113
  * // Backend: Publishing a message
113
- * statusChange.publish({ oldStatus: 1, newStatus: 2 });
114
+ * notif.publish({ coolInfo: "Message coming from the backend" });
114
115
  *
115
116
  * // Frontend: Subscribing to messages
116
- * statusChange.subscribe((message) => {
117
- * console.log(`Status changed from ${message.oldStatus} to ${message.newStatus}`);
117
+ * notif.subscribe((message) => {
118
+ * console.log(`Something exciting happened: ${message.coolInfo}`);
118
119
  * });
119
120
  *
120
121
  * // Note: If you want to publish from the frontend, use a BackendMethod for that.
122
+ * ```
123
+ * ---
124
+ *
125
+ * ### Use cases:
126
+ *
127
+ * #### A user _(Paul)_ wants to notify everyone _(Eric & Simona)_
128
+ * ```ts
129
+ * // shared code between the frontend and the backend
130
+ * export const notif = new SubscriptionChannel<{ from: UserInfo, msg: string }>("notif");
131
+ *
132
+ * // Frontend, Paul can call a BackendMethod to send his message
133
+ * NotificationContoller.send("Hello everyone!")
134
+ *
135
+ * // Backend
136
+ * export class NotificationContoller {
137
+ * @BackendMethod({ allowed: true })
138
+ * static async send(message: string) {
139
+ * notif.publish({ from: remult.user, msg: message });
140
+ * }
141
+ * }
142
+ *
143
+ * // Frontend: Eric & Simona can subscribe to messages
144
+ * notif.subscribe((message) => {
145
+ * if (message.from === remult.user) return;
146
+ * console.log(`Message from ${message.from.name}: ${message.msg}`);
147
+ * });
148
+ * ```
149
+ *
150
+ * #### A complexe chart that needs to be updated on multiple changes
151
+ * ```ts
152
+ * // shared code between the frontend and the backend
153
+ * export const chart = new SubscriptionChannel<{ kind: "pie" | "bar" | "line", data: number[] }>("chart");
154
+ *
155
+ * // Entity
156
+ * @Entity<Task>("tasks", {
157
+ * saved: async (e) => {
158
+ * if (e.completed) chart.publish({ kind: "pie", data: [1, 2, 3] });
159
+ * }
160
+ * })
161
+ * export class Task { ... }
162
+ *
163
+ * // Frontend: in the chart component, we can subscribe to messages
164
+ * chart.subscribe((message) => {
165
+ * chartData = message.data;
166
+ * });
167
+ * ```
168
+ *
121
169
  */
122
170
  export class SubscriptionChannel {
123
171
  channelKey;
@@ -412,9 +412,11 @@ export class RepositoryImplementation {
412
412
  }
413
413
  else {
414
414
  let updated = 0;
415
+ if (where === 'all')
416
+ where = undefined;
415
417
  for await (const item of this.query({ where, aggregate: undefined })) {
416
418
  assign(item, set);
417
- await getEntityRef(item).save();
419
+ await getEntityRef(item).save({ select: 'none' });
418
420
  updated++;
419
421
  }
420
422
  return updated;
@@ -532,22 +534,22 @@ export class RepositoryImplementation {
532
534
  }
533
535
  return ref;
534
536
  }
535
- async save(entity) {
537
+ async save(entity, options) {
536
538
  if (Array.isArray(entity)) {
537
539
  return promiseAll(entity, (x) => this.save(x));
538
540
  }
539
541
  else {
540
542
  let ref = getEntityRef(entity, false);
541
543
  if (ref)
542
- return await ref.save();
544
+ return await ref.save(options);
543
545
  else if (entity instanceof EntityBase) {
544
- return await this.getEntityRef(entity).save();
546
+ return await this.getEntityRef(entity).save(options);
545
547
  }
546
548
  else {
547
549
  let id = this.metadata.idMetadata.getId(entity);
548
550
  if (id === undefined)
549
- return this.insert(entity);
550
- return this.update(id, entity);
551
+ return this.insert(entity, options);
552
+ return this.update(id, entity, options);
551
553
  }
552
554
  }
553
555
  }
@@ -873,6 +875,8 @@ export class RepositoryImplementation {
873
875
  }
874
876
  else {
875
877
  let deleted = 0;
878
+ if (where === 'all')
879
+ where = undefined;
876
880
  for await (const item of this.query({ where, aggregate: undefined })) {
877
881
  await getEntityRef(item).delete();
878
882
  deleted++;
@@ -2524,8 +2528,8 @@ export class EntityBase {
2524
2528
  get _() {
2525
2529
  return getEntityRef(this);
2526
2530
  }
2527
- save() {
2528
- return getEntityRef(this).save();
2531
+ save(options) {
2532
+ return getEntityRef(this).save(options);
2529
2533
  }
2530
2534
  assign(values) {
2531
2535
  assign(this, values);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remult",
3
- "version": "3.3.0",
3
+ "version": "3.3.1",
4
4
  "description": "A CRUD framework for full-stack TypeScript",
5
5
  "homepage": "https://remult.dev",
6
6
  "repository": {
@@ -482,7 +482,8 @@ var KnexEntityDataProvider = /** @class */ (function () {
482
482
  return [7 /*endfinally*/];
483
483
  case 10:
484
484
  insert = this.getEntityFrom(e).insert(insertObject);
485
- if (!(0, RepositoryImplementation_js_1.isAutoIncrement)(this.entity.idMetadata.field)) return [3 /*break*/, 15];
485
+ if (!((0, RepositoryImplementation_js_1.isAutoIncrement)(this.entity.idMetadata.field) &&
486
+ !((options === null || options === void 0 ? void 0 : options.select) === 'none'))) return [3 /*break*/, 15];
486
487
  if (!(this.knex.client.config.client === 'mysql2' ||
487
488
  this.knex.client.config.client === 'mysql')) return [3 /*break*/, 12];
488
489
  return [4 /*yield*/, insert];
package/src/data-api.d.ts CHANGED
@@ -33,7 +33,7 @@ export declare class DataApi<T = unknown> {
33
33
  where?: any;
34
34
  set?: any;
35
35
  }): Promise<void>;
36
- actualUpdate(row: any, body: any, req: DataApiRequest): Promise<any>;
36
+ actualUpdate(row: any, body: any, req: DataApiRequest, doNotSelect?: boolean): Promise<any>;
37
37
  put(response: DataApiResponse, req: DataApiRequest, id: any, body: any): Promise<void>;
38
38
  actualDelete(row: any): Promise<void>;
39
39
  delete(response: DataApiResponse, id: any): Promise<void>;
package/src/data-api.js CHANGED
@@ -862,7 +862,7 @@ var DataApi = /** @class */ (function () {
862
862
  });
863
863
  });
864
864
  };
865
- DataApi.prototype.actualUpdate = function (row, body, req) {
865
+ DataApi.prototype.actualUpdate = function (row, body, req, doNotSelect) {
866
866
  return tslib_1.__awaiter(this, void 0, void 0, function () {
867
867
  var ref, options;
868
868
  return tslib_1.__generator(this, function (_a) {
@@ -875,7 +875,9 @@ var DataApi = /** @class */ (function () {
875
875
  if (!ref.apiUpdateAllowed) {
876
876
  throw new server_action_js_1.ForbiddenError();
877
877
  }
878
- options = req.get('_select') === 'none' ? { select: 'none' } : undefined;
878
+ options = req.get('_select') === 'none' || doNotSelect
879
+ ? { select: 'none' }
880
+ : undefined;
879
881
  return [4 /*yield*/, ref.save(options)];
880
882
  case 2:
881
883
  _a.sent();
@@ -38,18 +38,66 @@ export declare type LiveQueryChange = {
38
38
  *
39
39
  * @template messageType The type of the message that the channel will handle.
40
40
  * @example
41
+ * ```ts
41
42
  * // Defined in code that is shared between the frontend and the backend
42
- * const statusChange = new SubscriptionChannel<{ oldStatus: number, newStatus: number }>("statusChange");
43
+ * export const notif = new SubscriptionChannel<{ coolInfo: string }>("notif");
43
44
  *
44
45
  * // Backend: Publishing a message
45
- * statusChange.publish({ oldStatus: 1, newStatus: 2 });
46
+ * notif.publish({ coolInfo: "Message coming from the backend" });
46
47
  *
47
48
  * // Frontend: Subscribing to messages
48
- * statusChange.subscribe((message) => {
49
- * console.log(`Status changed from ${message.oldStatus} to ${message.newStatus}`);
49
+ * notif.subscribe((message) => {
50
+ * console.log(`Something exciting happened: ${message.coolInfo}`);
50
51
  * });
51
52
  *
52
53
  * // Note: If you want to publish from the frontend, use a BackendMethod for that.
54
+ * ```
55
+ * ---
56
+ *
57
+ * ### Use cases:
58
+ *
59
+ * #### A user _(Paul)_ wants to notify everyone _(Eric & Simona)_
60
+ * ```ts
61
+ * // shared code between the frontend and the backend
62
+ * export const notif = new SubscriptionChannel<{ from: UserInfo, msg: string }>("notif");
63
+ *
64
+ * // Frontend, Paul can call a BackendMethod to send his message
65
+ * NotificationContoller.send("Hello everyone!")
66
+ *
67
+ * // Backend
68
+ * export class NotificationContoller {
69
+ * @BackendMethod({ allowed: true })
70
+ * static async send(message: string) {
71
+ * notif.publish({ from: remult.user, msg: message });
72
+ * }
73
+ * }
74
+ *
75
+ * // Frontend: Eric & Simona can subscribe to messages
76
+ * notif.subscribe((message) => {
77
+ * if (message.from === remult.user) return;
78
+ * console.log(`Message from ${message.from.name}: ${message.msg}`);
79
+ * });
80
+ * ```
81
+ *
82
+ * #### A complexe chart that needs to be updated on multiple changes
83
+ * ```ts
84
+ * // shared code between the frontend and the backend
85
+ * export const chart = new SubscriptionChannel<{ kind: "pie" | "bar" | "line", data: number[] }>("chart");
86
+ *
87
+ * // Entity
88
+ * @Entity<Task>("tasks", {
89
+ * saved: async (e) => {
90
+ * if (e.completed) chart.publish({ kind: "pie", data: [1, 2, 3] });
91
+ * }
92
+ * })
93
+ * export class Task { ... }
94
+ *
95
+ * // Frontend: in the chart component, we can subscribe to messages
96
+ * chart.subscribe((message) => {
97
+ * chartData = message.data;
98
+ * });
99
+ * ```
100
+ *
53
101
  */
54
102
  export declare class SubscriptionChannel<messageType> {
55
103
  channelKey: string;
@@ -167,18 +167,66 @@ exports.liveQueryKeepAliveRoute = '_liveQueryKeepAlive';
167
167
  *
168
168
  * @template messageType The type of the message that the channel will handle.
169
169
  * @example
170
+ * ```ts
170
171
  * // Defined in code that is shared between the frontend and the backend
171
- * const statusChange = new SubscriptionChannel<{ oldStatus: number, newStatus: number }>("statusChange");
172
+ * export const notif = new SubscriptionChannel<{ coolInfo: string }>("notif");
172
173
  *
173
174
  * // Backend: Publishing a message
174
- * statusChange.publish({ oldStatus: 1, newStatus: 2 });
175
+ * notif.publish({ coolInfo: "Message coming from the backend" });
175
176
  *
176
177
  * // Frontend: Subscribing to messages
177
- * statusChange.subscribe((message) => {
178
- * console.log(`Status changed from ${message.oldStatus} to ${message.newStatus}`);
178
+ * notif.subscribe((message) => {
179
+ * console.log(`Something exciting happened: ${message.coolInfo}`);
179
180
  * });
180
181
  *
181
182
  * // Note: If you want to publish from the frontend, use a BackendMethod for that.
183
+ * ```
184
+ * ---
185
+ *
186
+ * ### Use cases:
187
+ *
188
+ * #### A user _(Paul)_ wants to notify everyone _(Eric & Simona)_
189
+ * ```ts
190
+ * // shared code between the frontend and the backend
191
+ * export const notif = new SubscriptionChannel<{ from: UserInfo, msg: string }>("notif");
192
+ *
193
+ * // Frontend, Paul can call a BackendMethod to send his message
194
+ * NotificationContoller.send("Hello everyone!")
195
+ *
196
+ * // Backend
197
+ * export class NotificationContoller {
198
+ * @BackendMethod({ allowed: true })
199
+ * static async send(message: string) {
200
+ * notif.publish({ from: remult.user, msg: message });
201
+ * }
202
+ * }
203
+ *
204
+ * // Frontend: Eric & Simona can subscribe to messages
205
+ * notif.subscribe((message) => {
206
+ * if (message.from === remult.user) return;
207
+ * console.log(`Message from ${message.from.name}: ${message.msg}`);
208
+ * });
209
+ * ```
210
+ *
211
+ * #### A complexe chart that needs to be updated on multiple changes
212
+ * ```ts
213
+ * // shared code between the frontend and the backend
214
+ * export const chart = new SubscriptionChannel<{ kind: "pie" | "bar" | "line", data: number[] }>("chart");
215
+ *
216
+ * // Entity
217
+ * @Entity<Task>("tasks", {
218
+ * saved: async (e) => {
219
+ * if (e.completed) chart.publish({ kind: "pie", data: [1, 2, 3] });
220
+ * }
221
+ * })
222
+ * export class Task { ... }
223
+ *
224
+ * // Frontend: in the chart component, we can subscribe to messages
225
+ * chart.subscribe((message) => {
226
+ * chartData = message.data;
227
+ * });
228
+ * ```
229
+ *
182
230
  */
183
231
  var SubscriptionChannel = /** @class */ (function () {
184
232
  /**
@@ -64,8 +64,8 @@ export declare class RepositoryImplementation<entityType> implements Repository<
64
64
  upsert(options: UpsertOptions<entityType>[]): Promise<entityType[]>;
65
65
  upsert(options: UpsertOptions<entityType>): Promise<entityType>;
66
66
  private _getRefForExistingRow;
67
- save(item: Partial<MembersOnly<entityType>>[]): Promise<entityType[]>;
68
- save(item: Partial<MembersOnly<entityType>>): Promise<entityType>;
67
+ save(item: Partial<MembersOnly<entityType>>[], options?: InsertOrUpdateOptions): Promise<entityType[]>;
68
+ save(item: Partial<MembersOnly<entityType>>, options?: InsertOrUpdateOptions): Promise<entityType>;
69
69
  liveQuery(options?: FindOptions<entityType>): LiveQuery<entityType>;
70
70
  _rawFind(options: FindOptions<entityType> | undefined, skipOrderByAndLimit: boolean | undefined, loader: RelationLoader, actualFind: (ops: EntityDataProviderFindOptions) => Promise<any[]>): Promise<entityType[]>;
71
71
  find(options?: FindOptions<entityType>): Promise<entityType[]>;
@@ -328,7 +328,7 @@ export declare function buildOptions<entityType = unknown, valueType = unknown>(
328
328
  export declare function decorateColumnSettings<valueType>(settings: FieldOptions<unknown, valueType>, remult: Remult): FieldOptions<unknown, valueType>;
329
329
  export declare class EntityBase {
330
330
  get _(): EntityRefForEntityBase<this>;
331
- save(): Promise<this>;
331
+ save(options?: InsertOrUpdateOptions): Promise<this>;
332
332
  assign(values: Partial<Omit<this, keyof EntityBase>>): this;
333
333
  delete(): Promise<void>;
334
334
  isNew(): boolean;
@@ -676,6 +676,8 @@ var RepositoryImplementation = /** @class */ (function () {
676
676
  case 3: return [2 /*return*/, _d.apply(_c, [_e, this.__createDto(tslib_1.__assign({}, set))])];
677
677
  case 4:
678
678
  updated = 0;
679
+ if (where === 'all')
680
+ where = undefined;
679
681
  _m.label = 5;
680
682
  case 5:
681
683
  _m.trys.push([5, 11, 12, 17]);
@@ -688,7 +690,7 @@ var RepositoryImplementation = /** @class */ (function () {
688
690
  _f = false;
689
691
  item = _l;
690
692
  (0, assign_js_1.assign)(item, set);
691
- return [4 /*yield*/, (0, getEntityRef_js_1.getEntityRef)(item).save()];
693
+ return [4 /*yield*/, (0, getEntityRef_js_1.getEntityRef)(item).save({ select: 'none' })];
692
694
  case 8:
693
695
  _m.sent();
694
696
  updated++;
@@ -872,7 +874,7 @@ var RepositoryImplementation = /** @class */ (function () {
872
874
  }
873
875
  return ref;
874
876
  };
875
- RepositoryImplementation.prototype.save = function (entity) {
877
+ RepositoryImplementation.prototype.save = function (entity, options) {
876
878
  return tslib_1.__awaiter(this, void 0, void 0, function () {
877
879
  var ref, id;
878
880
  var _this = this;
@@ -884,17 +886,17 @@ var RepositoryImplementation = /** @class */ (function () {
884
886
  case 1:
885
887
  ref = (0, getEntityRef_js_1.getEntityRef)(entity, false);
886
888
  if (!ref) return [3 /*break*/, 3];
887
- return [4 /*yield*/, ref.save()];
889
+ return [4 /*yield*/, ref.save(options)];
888
890
  case 2: return [2 /*return*/, _a.sent()];
889
891
  case 3:
890
892
  if (!(entity instanceof EntityBase)) return [3 /*break*/, 5];
891
- return [4 /*yield*/, this.getEntityRef(entity).save()];
893
+ return [4 /*yield*/, this.getEntityRef(entity).save(options)];
892
894
  case 4: return [2 /*return*/, _a.sent()];
893
895
  case 5:
894
896
  id = this.metadata.idMetadata.getId(entity);
895
897
  if (id === undefined)
896
- return [2 /*return*/, this.insert(entity)];
897
- return [2 /*return*/, this.update(id, entity)];
898
+ return [2 /*return*/, this.insert(entity, options)];
899
+ return [2 /*return*/, this.update(id, entity, options)];
898
900
  }
899
901
  });
900
902
  });
@@ -1468,6 +1470,8 @@ var RepositoryImplementation = /** @class */ (function () {
1468
1470
  case 3: return [2 /*return*/, _d.apply(_c, [_e])];
1469
1471
  case 4:
1470
1472
  deleted = 0;
1473
+ if (where === 'all')
1474
+ where = undefined;
1471
1475
  _m.label = 5;
1472
1476
  case 5:
1473
1477
  _m.trys.push([5, 11, 12, 17]);
@@ -4025,8 +4029,8 @@ var EntityBase = /** @class */ (function () {
4025
4029
  enumerable: false,
4026
4030
  configurable: true
4027
4031
  });
4028
- EntityBase.prototype.save = function () {
4029
- return (0, getEntityRef_js_1.getEntityRef)(this).save();
4032
+ EntityBase.prototype.save = function (options) {
4033
+ return (0, getEntityRef_js_1.getEntityRef)(this).save(options);
4030
4034
  };
4031
4035
  EntityBase.prototype.assign = function (values) {
4032
4036
  (0, assign_js_1.assign)(this, values);
@@ -395,9 +395,14 @@ export type GroupByResult<entityType, groupByFields extends (keyof entityType)[]
395
395
  };
396
396
  /** used to perform CRUD operations on an `entityType` */
397
397
  export interface Repository<entityType> {
398
- /** returns a result array based on the provided options */
398
+ /** returns an array based on the provided options */
399
399
  find(options?: FindOptions<entityType>): Promise<entityType[]>;
400
- /** returns a result array based on the provided options */
400
+ /** Using the same options as the {@link find} method, but subscribing to entity changes.
401
+ *
402
+ * _Note that today it subscribes to entity changes, but not included entities [Feature Request](https://github.com/remult/remult/issues/712)!_
403
+ *
404
+ * @see {@link LiveQuery}
405
+ */
401
406
  liveQuery(options?: FindOptions<entityType>): LiveQuery<entityType>;
402
407
  /** returns the first item that matchers the `where` condition
403
408
  * @example
@@ -547,8 +552,8 @@ export interface Repository<entityType> {
547
552
  * @example
548
553
  * await taskRepo.save({...task, completed:true })
549
554
  */
550
- save(item: Partial<MembersOnly<entityType>>[]): Promise<entityType[]>;
551
- save(item: Partial<MembersOnly<entityType>>): Promise<entityType>;
555
+ save(item: Partial<MembersOnly<entityType>>[], options?: InsertOrUpdateOptions): Promise<entityType[]>;
556
+ save(item: Partial<MembersOnly<entityType>>, options?: InsertOrUpdateOptions): Promise<entityType>;
552
557
  /**Insert an item or item[] to the data source
553
558
  * @example
554
559
  * await taskRepo.insert({title:"task a"})
@@ -674,7 +679,7 @@ export interface LiveQuery<entityType> {
674
679
  *
675
680
  * @example
676
681
  * // Subscribing to changes in a live query
677
- * const unsubscribe = taskRepo
682
+ * const unsubscribe = repo(Task)
678
683
  * .liveQuery({
679
684
  * limit: 20,
680
685
  * orderBy: { createdAt: 'asc' }
@@ -734,20 +739,14 @@ export interface LiveQueryChangeInfo<entityType> {
734
739
  applyChanges(prevState: entityType[] | undefined): entityType[];
735
740
  }
736
741
  export interface FindOptions<entityType> extends FindOptionsBase<entityType> {
737
- /** Determines the number of rows returned by the request, on the browser the default is 100 rows
742
+ /** Number of rows returned. _(Defaults to 100 in the browser)_
738
743
  * @example
739
- * await repo(Products).find({
740
- * limit: 10,
741
- * page: 2
742
- * })
744
+ * await repo(Products).find({ limit: 10 })
743
745
  */
744
746
  limit?: number;
745
- /** Determines the page number that will be used to extract the data
747
+ /** Determines the page number to retrieve. Works in tandem with the `limit` option.
746
748
  * @example
747
- * await repo(Products).find({
748
- * limit: 10,
749
- * page: 2
750
- * })
749
+ * await repo(Products).find({ page: 2 })
751
750
  */
752
751
  page?: number;
753
752
  }
@@ -1058,7 +1057,7 @@ export interface FindOptionsBase<entityType> extends LoadOptions<entityType> {
1058
1057
  select?: EntitySelectFields<entityType>;
1059
1058
  /** filters the data
1060
1059
  * @example
1061
- * await taskRepo.find({where: { completed:false }})
1060
+ * await taskRepo.find({ where: { completed:false } })
1062
1061
  * @see For more usage examples see [EntityFilter](https://remult.dev/docs/entityFilter.html)
1063
1062
  */
1064
1063
  where?: EntityFilter<entityType>;