remult 0.18.1 → 0.19.0-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.
@@ -8,7 +8,9 @@ export class DataProviderLiveQueryStorage {
8
8
  this.repo = this.dataProvider.then(dp => new Remult(dp).repo(LiveQueryStorageEntity));
9
9
  }
10
10
  async ensureSchema() {
11
- await (await this.dataProvider).ensureSchema([(await this.repo).metadata]);
11
+ const dp = await this.dataProvider;
12
+ if (dp.ensureSchema)
13
+ await dp.ensureSchema([(await this.repo).metadata]);
12
14
  }
13
15
  async add({ id, entityKey, data }) {
14
16
  await this.repo.then(async (repo) => {
@@ -17,7 +19,11 @@ export class DataProviderLiveQueryStorage {
17
19
  });
18
20
  }
19
21
  async remove(queryId) {
20
- await this.repo.then(repo => repo.delete(queryId)).catch(() => { });
22
+ await this.repo.then(async (repo) => {
23
+ const r = await repo.findId(queryId);
24
+ if (r)
25
+ await repo.delete(r);
26
+ }).catch(() => { });
21
27
  }
22
28
  async forEach(entityKey, callback) {
23
29
  const repo = await this.repo;
@@ -1,6 +1,6 @@
1
1
  import { ClassType } from '../classType';
2
2
  import { Allowed, AllowedForInstance } from './context';
3
- import { EntityMetadata, FieldRef } from './remult3';
3
+ import { EntityMetadata, FieldRef, OmitEB } from './remult3';
4
4
  export interface FieldOptions<entityType = any, valueType = any> {
5
5
  /** A human readable name for the field. Can be used to achieve a consistent caption for a field throughout the app
6
6
  * @example
@@ -75,7 +75,7 @@ export interface FieldOptions<entityType = any, valueType = any> {
75
75
  key?: string;
76
76
  }
77
77
  /**Metadata for a `Field`, this metadata can be used in the user interface to provide a richer UI experience */
78
- export interface FieldMetadata<valueType = any> {
78
+ export interface FieldMetadata<valueType = any, entityType = any> {
79
79
  /** The field's member name in an object.
80
80
  * @example
81
81
  * const taskRepo = remult.repo(Task);
@@ -109,15 +109,38 @@ export interface FieldMetadata<valueType = any> {
109
109
  /** indicates that this field should only be included in select statement, and excluded from update or insert. useful for db generated ids etc... */
110
110
  readonly dbReadOnly: boolean;
111
111
  /** the Value converter for this field */
112
- readonly valueConverter: ValueConverter<valueType>;
112
+ readonly valueConverter: Required<ValueConverter<valueType>>;
113
+ /** Get the display value for a specific item
114
+ * @example
115
+ * repo.fields.createDate.displayValue(task) //will display the date as defined in the `displayValue` option defined for it.
116
+ */
117
+ displayValue(item: Partial<OmitEB<entityType>>): string;
118
+ apiUpdateAllowed(item: Partial<OmitEB<entityType>>): boolean;
119
+ readonly includedInApi: boolean;
120
+ /** Adapts the value for usage with html input
121
+ * @example
122
+ * @Fields.dateOnly()
123
+ * birthDate = new Date(1976,5,16)
124
+ * //...
125
+ * input.value = repo.fields.birthDate.toInput(person) // will return '1976-06-16'
126
+ */
127
+ toInput(value: valueType, inputType?: string): string;
128
+ /** Adapts the value for usage with html input
129
+ * @example
130
+ * @Fields.dateOnly()
131
+ * birthDate = new Date(1976,5,16)
132
+ * //...
133
+ * person.birthDate = repo.fields.birthDate.fromInput(personFormState) // will return Date
134
+ */
135
+ fromInput(inputValue: string, inputType?: string): valueType;
113
136
  }
114
137
  export interface ValueConverter<valueType> {
115
138
  fromJson?(val: any): valueType;
116
139
  toJson?(val: valueType): any;
117
140
  fromDb?(val: any): valueType;
118
141
  toDb?(val: valueType): any;
119
- toInput?(val: valueType, inputType: string): string;
120
- fromInput?(val: string, inputType: string): valueType;
142
+ toInput?(val: valueType, inputType?: string): string;
143
+ fromInput?(val: string, inputType?: string): valueType;
121
144
  displayValue?(val: valueType): string;
122
145
  readonly fieldTypeInDb?: string;
123
146
  readonly inputType?: string;
@@ -6,10 +6,15 @@ export declare function makeTitle(name: string): string;
6
6
  export declare class CompoundIdField implements FieldMetadata<string> {
7
7
  fields: FieldMetadata[];
8
8
  constructor(...columns: FieldMetadata[]);
9
+ apiUpdateAllowed(item: any): boolean;
10
+ displayValue(item: any): string;
11
+ includedInApi: boolean;
12
+ toInput(value: string, inputType?: string): string;
13
+ fromInput(inputValue: string, inputType?: string): string;
9
14
  getDbName(): Promise<string>;
10
15
  getId(instance: any): string;
11
16
  options: FieldOptions<any, any>;
12
- get valueConverter(): ValueConverter<string>;
17
+ get valueConverter(): Required<ValueConverter<string>>;
13
18
  target: ClassType<any>;
14
19
  readonly: true;
15
20
  allowNull: boolean;
package/esm/src/column.js CHANGED
@@ -11,6 +11,18 @@ export class CompoundIdField {
11
11
  constructor(...columns) {
12
12
  this.fields = columns;
13
13
  }
14
+ apiUpdateAllowed(item) {
15
+ throw new Error('Method not implemented.');
16
+ }
17
+ displayValue(item) {
18
+ throw new Error('Method not implemented.');
19
+ }
20
+ toInput(value, inputType) {
21
+ throw new Error('Method not implemented.');
22
+ }
23
+ fromInput(inputValue, inputType) {
24
+ throw new Error('Method not implemented.');
25
+ }
14
26
  getDbName() {
15
27
  return Promise.resolve("");
16
28
  }
@@ -2,7 +2,6 @@ import { customUrlToken, buildFilterFromRequestParameters } from './filter/filte
2
2
  import { doTransaction } from './context';
3
3
  import { Filter } from './filter/filter-interfaces';
4
4
  import { ForbiddenError } from './server-action';
5
- import { getId } from './remult3/getId';
6
5
  import { findOptionsToJson, liveQueryAction } from './data-providers/rest-data-provider';
7
6
  export class DataApi {
8
7
  constructor(repository, remult) {
@@ -131,7 +130,7 @@ export class DataApi {
131
130
  const data = {
132
131
  requestJson: serializeRequest(),
133
132
  findOptionsJson: findOptionsToJson(r.findOptions, this.repository.metadata),
134
- lastIds: r.r.map(y => getId(this.repository.metadata, y))
133
+ lastIds: r.r.map(y => this.repository.metadata.idMetadata.getId(y))
135
134
  };
136
135
  await this.remult.liveQueryStorage.add({
137
136
  entityKey: this.repository.metadata.key,
@@ -1,5 +1,5 @@
1
1
  import { Allowed, AllowedForInstance } from "./context";
2
- import { FieldMetadata as FieldMetadata } from './column-interfaces';
2
+ import { FieldMetadata } from './column-interfaces';
3
3
  import { EntityOrderBy, FieldsMetadata, EntityFilter, EntityRef, EntityMetadata } from "./remult3";
4
4
  export interface EntityOptions<entityType = any> {
5
5
  /**A human readable name for the entity */
@@ -1,5 +1,4 @@
1
1
  import { remult as defaultRemult, Sort } from '../../index';
2
- import { getId } from '../remult3/getId';
3
2
  import { v4 as uuid } from 'uuid';
4
3
  import { getLiveQueryChannel } from '../data-api';
5
4
  export const streamUrl = 'stream';
@@ -75,15 +74,15 @@ export class LiveQuerySubscriber {
75
74
  this.setAllItems(message.data);
76
75
  break;
77
76
  case "replace": {
78
- items = items.map(x => getId(this.repo.metadata, x) === message.data.oldId ? message.data.item : x);
77
+ items = items.map(x => this.repo.metadata.idMetadata.getId(x) === message.data.oldId ? message.data.item : x);
79
78
  break;
80
79
  }
81
80
  case "add":
82
- items = items.filter(x => getId(this.repo.metadata, x) !== getId(this.repo.metadata, message.data.item));
81
+ items = items.filter(x => this.repo.metadata.idMetadata.getId(x) !== this.repo.metadata.idMetadata.getId(message.data.item));
83
82
  items.push(message.data.item);
84
83
  break;
85
84
  case "remove":
86
- items = items.filter(x => getId(this.repo.metadata, x) !== message.data.id);
85
+ items = items.filter(x => this.repo.metadata.idMetadata.getId(x) !== message.data.id);
87
86
  break;
88
87
  }
89
88
  ;
@@ -7,6 +7,10 @@ export class RemultProxy {
7
7
  this.repo = (...args) => {
8
8
  let self = this;
9
9
  return {
10
+ get fields() {
11
+ return self.remultFactory().repo(...args).metadata.fields;
12
+ },
13
+ validate: (...args2) => self.remultFactory().repo(...args).validate(...args2),
10
14
  addEventListener: (...args2) => self.remultFactory().repo(...args).addEventListener(...args2),
11
15
  count: (...args2) => self.remultFactory().repo(...args).count(...args2),
12
16
  create: (...args2) => self.remultFactory().repo(...args).create(...args2),
@@ -5,7 +5,7 @@ import { EntityMetadata, FieldRef, FieldsRef, EntityFilter, FindOptions, Reposit
5
5
  import { ClassType } from "../../classType";
6
6
  import { Remult } from "../context";
7
7
  import { entityEventListener } from "../__EntityValueProvider";
8
- import { DataProvider, EntityDataProvider, EntityDataProviderFindOptions } from "../data-interfaces";
8
+ import { DataProvider, EntityDataProvider, EntityDataProviderFindOptions, ErrorInfo } from "../data-interfaces";
9
9
  import { RefSubscriber } from ".";
10
10
  import { Unsubscribe } from "../live-query/SubscriptionChannel";
11
11
  export declare class RepositoryImplementation<entityType> implements Repository<entityType> {
@@ -34,6 +34,8 @@ export declare class RepositoryImplementation<entityType> implements Repository<
34
34
  delete(item: entityType): Promise<void>;
35
35
  insert(item: Partial<OmitEB<entityType>>[]): Promise<entityType[]>;
36
36
  insert(item: Partial<OmitEB<entityType>>): Promise<entityType>;
37
+ get fields(): FieldsMetadata<entityType>;
38
+ validate(entity: Partial<OmitEB<entityType>>, ...fields: (Extract<keyof OmitEB<entityType>, string>)[]): Promise<ErrorInfo<entityType> | undefined>;
37
39
  update(id: (entityType extends {
38
40
  id?: number;
39
41
  } ? number : entityType extends {
@@ -54,6 +56,7 @@ export declare class RepositoryImplementation<entityType> implements Repository<
54
56
  findFirst(where?: EntityFilter<entityType>, options?: FindFirstOptions<entityType>, skipOrderByAndLimit?: boolean): Promise<entityType>;
55
57
  private fieldsOf;
56
58
  create(item?: Partial<OmitEB<entityType>>): entityType;
59
+ fixTypes(item: any): Promise<any>;
57
60
  findId(id: any, options?: FindFirstOptionsBase<entityType>): Promise<entityType>;
58
61
  }
59
62
  export declare function __updateEntityBasedOnWhere<T>(entityDefs: EntityMetadata<T>, where: EntityFilter<T>, r: T): void;
@@ -84,6 +87,7 @@ declare abstract class rowHelperBase<T> {
84
87
  [key: string]: string;
85
88
  };
86
89
  protected __assertValidity(): void;
90
+ buildErrorInfoObject(): ErrorInfo<any>;
87
91
  abstract get fields(): FieldsRef<T>;
88
92
  catchSaveErrors(err: any): any;
89
93
  __clearErrorsAndReportChanged(): void;
@@ -93,7 +97,7 @@ declare abstract class rowHelperBase<T> {
93
97
  originalValues: any;
94
98
  saveOriginalData(): void;
95
99
  saveMoreOriginalData(): void;
96
- validate(): Promise<boolean>;
100
+ validate(): Promise<ErrorInfo<any>>;
97
101
  __validateEntity(): Promise<void>;
98
102
  __performColumnAndEntityValidations(): Promise<void>;
99
103
  toApiJson(): any;
@@ -180,12 +184,18 @@ export declare function buildCaption(caption: string | ((remult: Remult) => stri
180
184
  export declare class columnDefsImpl implements FieldMetadata {
181
185
  private settings;
182
186
  private entityDefs;
187
+ private remult;
183
188
  constructor(settings: FieldOptions, entityDefs: EntityFullInfo<any>, remult: Remult);
189
+ apiUpdateAllowed(item: any): boolean;
190
+ displayValue(item: any): string;
191
+ get includedInApi(): boolean;
192
+ toInput(value: any, inputType?: string): string;
193
+ fromInput(inputValue: string, inputType?: string): any;
184
194
  getDbName(): Promise<string>;
185
195
  options: FieldOptions<any, any>;
186
196
  target: ClassType<any>;
187
197
  readonly: boolean;
188
- valueConverter: ValueConverter<any>;
198
+ valueConverter: Required<ValueConverter<any>>;
189
199
  allowNull: boolean;
190
200
  caption: string;
191
201
  get dbName(): any;
@@ -203,10 +213,10 @@ declare class EntityFullInfo<T> implements EntityMetadata<T> {
203
213
  readonly key: string;
204
214
  options: EntityOptions<any>;
205
215
  constructor(columnsInfo: FieldOptions[], entityInfo: EntityOptions, remult: Remult, entityType: ClassType<T>, key: string);
206
- get apiUpdateAllowed(): boolean;
216
+ apiUpdateAllowed(item: T): boolean;
207
217
  get apiReadAllowed(): boolean;
208
- get apiDeleteAllowed(): boolean;
209
- get apiInsertAllowed(): boolean;
218
+ apiDeleteAllowed(item: T): boolean;
219
+ apiInsertAllowed(item: T): boolean;
210
220
  dbNamePromise: Promise<string>;
211
221
  getDbName(): Promise<string>;
212
222
  idMetadata: IdMetadata<T>;
@@ -8,7 +8,6 @@ import { ValueConverters } from "../valueConverters";
8
8
  import { filterHelper } from "../filter/filter-interfaces";
9
9
  import { assign } from "../../assign";
10
10
  import { remult as defaultRemult, RemultProxy } from "../remult-proxy";
11
- import { getId } from "./getId";
12
11
  //import { remult } from "../remult-proxy";
13
12
  let classValidatorValidate = undefined;
14
13
  // import("class-validator".toString())
@@ -101,7 +100,7 @@ export class RepositoryImplementation {
101
100
  this.idCache.set(id, row);
102
101
  return await row;
103
102
  }
104
- //TODO - Used to prevent unnecessary many to one relations
103
+ //TODO2 - Used to prevent unnecessary many to one relations
105
104
  addToCache(item) {
106
105
  if (item)
107
106
  this.idCache.set(this.getEntityRef(item).getId() + '', item);
@@ -122,6 +121,7 @@ export class RepositoryImplementation {
122
121
  getEntityRef(entity) {
123
122
  let x = entity[entityMember];
124
123
  if (!x) {
124
+ this.fixTypes(entity);
125
125
  x = new rowHelperImplementation(this._info, entity, this, this.edp, this.remult, true);
126
126
  Object.defineProperty(entity, entityMember, {
127
127
  get: () => x
@@ -157,6 +157,28 @@ export class RepositoryImplementation {
157
157
  }
158
158
  }
159
159
  }
160
+ get fields() {
161
+ return this.metadata.fields;
162
+ }
163
+ async validate(entity, ...fields) {
164
+ {
165
+ let ref = this.getEntityRef(entity);
166
+ if (!fields || fields.length === 0) {
167
+ return await ref.validate();
168
+ }
169
+ else {
170
+ ref.__clearErrorsAndReportChanged();
171
+ let hasError = false;
172
+ for (const f of fields) {
173
+ if (!await ref.fields.find(f).validate())
174
+ hasError = true;
175
+ }
176
+ if (!hasError)
177
+ return undefined;
178
+ return ref.buildErrorInfoObject();
179
+ }
180
+ }
181
+ }
160
182
  async update(id, entity) {
161
183
  let ref = this.getRefForExistingRow(entity, id);
162
184
  return (await ref.save());
@@ -168,6 +190,7 @@ export class RepositoryImplementation {
168
190
  for (const field of this.fieldsOf(entity)) {
169
191
  instance[field.key] = entity[field.key];
170
192
  }
193
+ this.fixTypes(instance);
171
194
  let row = new rowHelperImplementation(this._info, instance, this, this.edp, this.remult, false);
172
195
  if (id) {
173
196
  row.id = id;
@@ -300,7 +323,7 @@ export class RepositoryImplementation {
300
323
  helper.saveOriginalData();
301
324
  return x;
302
325
  }
303
- // TODO - consider replacing with fromJsonArray - also consider having a TOJSON for similar cases
326
+ // TODO2 - consider replacing with fromJsonArray - also consider having a TOJSON for similar cases
304
327
  async fromJson(json, newRow) {
305
328
  let obj = {};
306
329
  for (const col of this.fieldsOf(json)) {
@@ -384,13 +407,30 @@ export class RepositoryImplementation {
384
407
  }
385
408
  create(item) {
386
409
  let r = new this.entity(this.remult);
387
- if (item)
410
+ if (item) {
388
411
  for (const field of this.fieldsOf(item)) {
389
412
  r[field.key] = item[field.key];
390
413
  }
414
+ this.fixTypes(r);
415
+ }
391
416
  let z = this.getEntityRef(r);
392
417
  return r;
393
418
  }
419
+ async fixTypes(item) {
420
+ for (const field of this.fieldsOf(item)) {
421
+ const val = item[field.key];
422
+ if (val !== null && val !== undefined) {
423
+ if (field.valueType === Date && !(val instanceof Date))
424
+ item[field.key] = field.valueConverter.fromJson(field.valueConverter.toJson(val));
425
+ else
426
+ for (const [type, typeName] of [[String, "string"], [Number, "number"], [Boolean, "boolean"]]) {
427
+ if (field.valueType === type && typeof val !== typeName)
428
+ item[field.key] = field.valueConverter.fromJson(field.valueConverter.toJson(val));
429
+ }
430
+ }
431
+ }
432
+ return item;
433
+ }
394
434
  findId(id, options) {
395
435
  if (id === null || id === undefined)
396
436
  return null;
@@ -599,22 +639,24 @@ class rowHelperBase {
599
639
  await Promise.all([...this.lookups.values()].map(x => x.waitLoad()));
600
640
  }
601
641
  __assertValidity() {
602
- if (!this.hasErrors()) {
603
- let error = {
604
- modelState: Object.assign({}, this.errors),
605
- message: this.error
606
- };
607
- if (!error.message) {
608
- for (const col of this.columnsInfo) {
609
- if (this.errors[col.key]) {
610
- error.message = this.fields[col.key].metadata.caption + ": " + this.errors[col.key];
611
- this.error = error.message;
612
- break;
613
- }
642
+ if (!this.hasErrors())
643
+ throw this.buildErrorInfoObject();
644
+ }
645
+ buildErrorInfoObject() {
646
+ let error = {
647
+ modelState: Object.assign({}, this.errors),
648
+ message: this.error
649
+ };
650
+ if (!error.message) {
651
+ for (const col of this.columnsInfo) {
652
+ if (this.errors[col.key]) {
653
+ error.message = this.fields[col.key].metadata.caption + ": " + this.errors[col.key];
654
+ this.error = error.message;
655
+ break;
614
656
  }
615
657
  }
616
- throw error;
617
658
  }
659
+ return error;
618
660
  }
619
661
  catchSaveErrors(err) {
620
662
  let e = err;
@@ -686,7 +728,10 @@ class rowHelperBase {
686
728
  await classValidatorValidate(this.instance, this);
687
729
  await this.__performColumnAndEntityValidations();
688
730
  let r = this.hasErrors();
689
- return r;
731
+ if (!this.hasErrors())
732
+ return this.buildErrorInfoObject();
733
+ else
734
+ return undefined;
690
735
  }
691
736
  async __validateEntity() {
692
737
  this.__clearErrorsAndReportChanged();
@@ -768,7 +813,7 @@ export class rowHelperImplementation extends rowHelperBase {
768
813
  get apiDeleteAllowed() { return this.remult.isAllowedForInstance(this.instance, this.metadata.options.allowApiDelete); }
769
814
  get apiInsertAllowed() { return this.remult.isAllowedForInstance(this.instance, this.metadata.options.allowApiInsert); }
770
815
  getId() {
771
- return getId(this.info, this.instance);
816
+ return this.info.idMetadata.getId(this.instance);
772
817
  }
773
818
  saveMoreOriginalData() {
774
819
  this.originalId = this.getId();
@@ -925,7 +970,7 @@ export class rowHelperImplementation extends rowHelperBase {
925
970
  this.instance[col.key] = data[col.key];
926
971
  }
927
972
  await this.calcServerExpression();
928
- this.id = getId(this.info, this.instance);
973
+ this.id = this.getId();
929
974
  }
930
975
  getOriginalId() {
931
976
  return this.originalId;
@@ -1187,6 +1232,7 @@ export class columnDefsImpl {
1187
1232
  constructor(settings, entityDefs, remult) {
1188
1233
  this.settings = settings;
1189
1234
  this.entityDefs = entityDefs;
1235
+ this.remult = remult;
1190
1236
  this.options = this.settings;
1191
1237
  this.target = this.settings.target;
1192
1238
  this.valueConverter = this.settings.valueConverter;
@@ -1202,6 +1248,23 @@ export class columnDefsImpl {
1202
1248
  this.inputType = this.valueConverter.inputType;
1203
1249
  this.caption = buildCaption(settings.caption, settings.key, remult);
1204
1250
  }
1251
+ apiUpdateAllowed(item) {
1252
+ return this.remult.isAllowedForInstance(item, this.options.allowApiUpdate);
1253
+ }
1254
+ displayValue(item) {
1255
+ return this.remult.repo(this.entityDefs.entityType).getEntityRef(item).fields.find(this.key).displayValue;
1256
+ }
1257
+ get includedInApi() {
1258
+ if (this.options.includeInApi === undefined)
1259
+ return true;
1260
+ return this.remult.isAllowed(this.options.includeInApi); //TODO - consolidate other code paths to go through here
1261
+ }
1262
+ toInput(value, inputType) {
1263
+ return this.valueConverter.toInput(value, inputType);
1264
+ }
1265
+ fromInput(inputValue, inputType) {
1266
+ return this.valueConverter.fromInput(inputValue, inputType);
1267
+ }
1205
1268
  async getDbName() {
1206
1269
  try {
1207
1270
  if (this.settings.sqlExpression) {
@@ -1247,6 +1310,12 @@ class EntityFullInfo {
1247
1310
  this.key = key;
1248
1311
  this.options = this.entityInfo;
1249
1312
  this.idMetadata = {
1313
+ getId: item => {
1314
+ if (this.idMetadata.field instanceof CompoundIdField)
1315
+ return this.idMetadata.field.getId(item);
1316
+ else
1317
+ return item[this.idMetadata.field.key];
1318
+ },
1250
1319
  field: undefined,
1251
1320
  createIdInFilter: (items) => {
1252
1321
  if (items.length > 0)
@@ -1319,10 +1388,10 @@ class EntityFullInfo {
1319
1388
  this.idMetadata.field = [...this.fields][0];
1320
1389
  }
1321
1390
  }
1322
- get apiUpdateAllowed() { return this.remult.isAllowedForInstance(undefined, this.options.allowApiUpdate); }
1391
+ apiUpdateAllowed(item) { return this.remult.repo(this.entityType).getEntityRef(item).apiUpdateAllowed; }
1323
1392
  get apiReadAllowed() { return this.remult.isAllowed(this.options.allowApiRead); }
1324
- get apiDeleteAllowed() { return this.remult.isAllowedForInstance(undefined, this.options.allowApiDelete); }
1325
- get apiInsertAllowed() { return this.remult.isAllowedForInstance(undefined, this.options.allowApiInsert); }
1393
+ apiDeleteAllowed(item) { return this.remult.repo(this.entityType).getEntityRef(item).apiDeleteAllowed; }
1394
+ apiInsertAllowed(item) { return this.remult.repo(this.entityType).getEntityRef(item).apiInsertAllowed; }
1326
1395
  getDbName() {
1327
1396
  if (this.dbNamePromise)
1328
1397
  return this.dbNamePromise;
@@ -4,6 +4,7 @@ import { LiveQueryChange, SubscriptionListener, Unsubscribe } from "../live-quer
4
4
  import { EntityOptions } from "../entity";
5
5
  import { SortSegment } from "../sort";
6
6
  import { entityEventListener } from "../__EntityValueProvider";
7
+ import { ErrorInfo } from "../..";
7
8
  export interface EntityRef<entityType> extends Subscribable {
8
9
  hasErrors(): boolean;
9
10
  undoChanges(): any;
@@ -20,7 +21,7 @@ export interface EntityRef<entityType> extends Subscribable {
20
21
  repository: Repository<entityType>;
21
22
  metadata: EntityMetadata<entityType>;
22
23
  toApiJson(): any;
23
- validate(): Promise<boolean>;
24
+ validate(): Promise<ErrorInfo<entityType> | undefined>;
24
25
  readonly apiUpdateAllowed: boolean;
25
26
  readonly apiDeleteAllowed: boolean;
26
27
  readonly apiInsertAllowed: boolean;
@@ -30,7 +31,7 @@ export interface ControllerRef<entityType> extends Subscribable {
30
31
  hasErrors(): boolean;
31
32
  fields: FieldsRef<entityType>;
32
33
  error: string;
33
- validate(): Promise<boolean>;
34
+ validate(): Promise<ErrorInfo<entityType> | undefined>;
34
35
  readonly isLoading: boolean;
35
36
  }
36
37
  export interface RefSubscriberBase {
@@ -51,11 +52,11 @@ export declare type FieldsRef<entityType> = {
51
52
  toArray(): FieldRef<entityType, any>[];
52
53
  };
53
54
  export declare type FieldsMetadata<entityType> = {
54
- [Properties in keyof OmitEB<entityType>]: FieldMetadata<entityType[Properties]>;
55
+ [Properties in keyof OmitEB<entityType>]: FieldMetadata<entityType[Properties], entityType>;
55
56
  } & {
56
- find(fieldMetadataOrKey: FieldMetadata | string): FieldMetadata;
57
- [Symbol.iterator]: () => IterableIterator<FieldMetadata>;
58
- toArray(): FieldMetadata<any>[];
57
+ find(fieldMetadataOrKey: FieldMetadata | string): FieldMetadata<any, entityType>;
58
+ [Symbol.iterator]: () => IterableIterator<FieldMetadata<any, entityType>>;
59
+ toArray(): FieldMetadata<any, entityType>[];
59
60
  };
60
61
  export declare type SortSegments<entityType> = {
61
62
  [Properties in keyof entityType]: SortSegment & {
@@ -90,6 +91,11 @@ export interface FieldRef<entityType = any, valueType = any> extends Subscribabl
90
91
  validate(): Promise<boolean>;
91
92
  }
92
93
  export interface IdMetadata<entityType = any> {
94
+ /** Extracts the id value of an entity item. Useful in cases where the id column is not called id
95
+ * @example
96
+ * repo.metadata.idMetadata.getId(task)
97
+ */
98
+ getId(item: entityType): any;
93
99
  field: FieldMetadata<any>;
94
100
  getIdFilter(...ids: any[]): EntityFilter<entityType>;
95
101
  isIdField(col: FieldMetadata): boolean;
@@ -113,11 +119,11 @@ export interface EntityMetadata<entityType = any> {
113
119
  /** true if the current user is allowed to update an entity instance
114
120
  * @example
115
121
  * const taskRepo = remult.repo(Task);
116
- * if (taskRepo.metadata.apiUpdateAllowed){
122
+ * if (taskRepo.metadata.apiUpdateAllowed(task)){
117
123
  * // Allow user to edit the entity
118
124
  * }
119
125
  */
120
- readonly apiUpdateAllowed: boolean;
126
+ apiUpdateAllowed(item: entityType): boolean;
121
127
  /** true if the current user is allowed to read from entity
122
128
  * @example
123
129
  * const taskRepo = remult.repo(Task);
@@ -129,19 +135,19 @@ export interface EntityMetadata<entityType = any> {
129
135
  /** true if the current user is allowed to delete an entity instance
130
136
  * @example
131
137
  * const taskRepo = remult.repo(Task);
132
- * if (taskRepo.metadata.apiDeleteAllowed){
138
+ * if (taskRepo.metadata.apiDeleteAllowed(task)){
133
139
  * // display delete button
134
140
  * }
135
141
  */
136
- readonly apiDeleteAllowed: boolean;
142
+ apiDeleteAllowed(item: entityType): boolean;
137
143
  /** true if the current user is allowed to create an entity instance
138
144
  * @example
139
145
  * const taskRepo = remult.repo(Task);
140
- * if (taskRepo.metadata.apiInsertAllowed){
146
+ * if (taskRepo.metadata.apiInsertAllowed(task)){
141
147
  * // display insert button
142
148
  * }
143
149
  */
144
- readonly apiInsertAllowed: boolean;
150
+ apiInsertAllowed(item: entityType): boolean;
145
151
  /** Returns the dbName - based on it's `dbName` option and it's `sqlExpression` option */
146
152
  getDbName(): Promise<string>;
147
153
  /** Metadata for the Entity's id */
@@ -184,8 +190,19 @@ export interface Repository<entityType> {
184
190
  * @see [EntityFilter](http://remult.dev/docs/entityFilter.html)
185
191
  * @example
186
192
  * await taskRepo.count({ completed:false })
187
- */
193
+ */
188
194
  count(where?: EntityFilter<entityType>): Promise<number>;
195
+ /**Validates an item
196
+ * @example
197
+ * const error = repo.validate(task);
198
+ * if (error){
199
+ * alert(error.message);
200
+ * alert(error.modelState.title);//shows the specific error for the title field
201
+ * }
202
+ * // Can also be used to validate specific fields
203
+ * const error = repo.validate(task,"title")
204
+ */
205
+ validate(item: Partial<entityType>, ...fields: (Extract<keyof OmitEB<entityType>, string>)[]): Promise<ErrorInfo<entityType> | undefined>;
189
206
  /** saves an item or item[] to the data source. It assumes that if an `id` value exists, it's an existing row - otherwise it's a new row
190
207
  * @example
191
208
  * await taskRepo.save({...task, completed:true })
@@ -222,6 +239,12 @@ export interface Repository<entityType> {
222
239
  fromJson(x: any, isNew?: boolean): Promise<entityType>;
223
240
  /** returns an `entityRef` for an item returned by `create`, `find` etc... */
224
241
  getEntityRef(item: entityType): EntityRef<entityType>;
242
+ /** Provides information about the fields of the Repository's entity
243
+ * @example
244
+ * console.log(repo.fields.title.caption) // displays the caption of a specific field
245
+ * console.log(repo.fields.title.options)// writes the options that were defined for this field
246
+ */
247
+ fields: FieldsMetadata<entityType>;
225
248
  /**The metadata for the `entity`
226
249
  * @See [EntityMetadata](https://remult.dev/docs/ref_entitymetadata.html)
227
250
  */
@@ -797,6 +797,19 @@ testAll("test contains with names with casing", async ({ createEntity }) => {
797
797
  await r.insert({ a: 1, firstName: "noam" });
798
798
  let item = await r.findFirst({ firstName: { $contains: "oa" } });
799
799
  expect(item.firstName).toBe("noam");
800
- }, true, {
800
+ }, false, {
801
801
  exclude: [TestDbs.mongo]
802
802
  });
803
+ testAll("test live query storage", async (x) => {
804
+ const s = new DataProviderLiveQueryStorage(x.db);
805
+ await s.ensureSchema();
806
+ const entityKey = "ek";
807
+ const id = 'id';
808
+ await s.add({ id, entityKey, data: {} });
809
+ await s.add({ id, entityKey, data: {} });
810
+ x.remult.clearAllCache();
811
+ await s.add({ id, entityKey, data: {} });
812
+ await s.keepAliveAndReturnUnknownQueryIds([id]);
813
+ await s.remove(id);
814
+ await s.remove(id);
815
+ }, false, { exclude: [TestDbs.restDataProvider] });
@@ -6,7 +6,7 @@ export declare class ValueConverters {
6
6
  static readonly Boolean: ValueConverter<Boolean>;
7
7
  static readonly Number: ValueConverter<number>;
8
8
  static readonly Integer: ValueConverter<number>;
9
- static readonly Default: ValueConverter<any>;
9
+ static readonly Default: Required<ValueConverter<any>>;
10
10
  static readonly JsonString: ValueConverter<any>;
11
11
  static readonly JsonValue: ValueConverter<any>;
12
12
  }
@@ -158,7 +158,10 @@ ValueConverters.Default = {
158
158
  fromDb: x => ValueConverters.JsonString.fromDb(x),
159
159
  toDb: x => ValueConverters.JsonString.toDb(x),
160
160
  fromInput: x => ValueConverters.Default.fromJson(x),
161
- toInput: x => ValueConverters.Default.toJson(x)
161
+ toInput: x => ValueConverters.Default.toJson(x),
162
+ displayValue: x => x + '',
163
+ fieldTypeInDb: '',
164
+ inputType: 'text'
162
165
  };
163
166
  ValueConverters.JsonString = {
164
167
  fromJson: x => x,