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.
- package/esm/live-query/data-provider-live-query-storage.js +8 -2
- package/esm/src/column-interfaces.d.ts +28 -5
- package/esm/src/column.d.ts +6 -1
- package/esm/src/column.js +12 -0
- package/esm/src/data-api.js +1 -2
- package/esm/src/entity.d.ts +1 -1
- package/esm/src/live-query/SubscriptionChannel.js +3 -4
- package/esm/src/remult-proxy.js +4 -0
- package/esm/src/remult3/RepositoryImplementation.d.ts +16 -6
- package/esm/src/remult3/RepositoryImplementation.js +92 -23
- package/esm/src/remult3/remult3.d.ts +36 -13
- package/esm/src/shared-tests/db-tests.db-tests.js +14 -1
- package/esm/src/valueConverters.d.ts +1 -1
- package/esm/src/valueConverters.js +4 -1
- package/live-query/data-provider-live-query-storage.js +23 -4
- package/package.json +1 -1
- package/src/column-interfaces.d.ts +28 -5
- package/src/column.d.ts +6 -1
- package/src/column.js +12 -0
- package/src/data-api.js +1 -2
- package/src/entity.d.ts +1 -1
- package/src/live-query/SubscriptionChannel.js +3 -4
- package/src/remult-proxy.js +12 -0
- package/src/remult3/RepositoryImplementation.d.ts +16 -6
- package/src/remult3/RepositoryImplementation.js +287 -161
- package/src/remult3/remult3.d.ts +36 -13
- package/src/shared-tests/db-tests.db-tests.js +35 -1
- package/src/valueConverters.d.ts +1 -1
- package/src/valueConverters.js +4 -1
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
120
|
-
fromInput?(val: string, inputType
|
|
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;
|
package/esm/src/column.d.ts
CHANGED
|
@@ -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
|
}
|
package/esm/src/data-api.js
CHANGED
|
@@ -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 =>
|
|
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,
|
package/esm/src/entity.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Allowed, AllowedForInstance } from "./context";
|
|
2
|
-
import { FieldMetadata
|
|
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 =>
|
|
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 =>
|
|
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 =>
|
|
85
|
+
items = items.filter(x => this.repo.metadata.idMetadata.getId(x) !== message.data.id);
|
|
87
86
|
break;
|
|
88
87
|
}
|
|
89
88
|
;
|
package/esm/src/remult-proxy.js
CHANGED
|
@@ -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<
|
|
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
|
-
|
|
216
|
+
apiUpdateAllowed(item: T): boolean;
|
|
207
217
|
get apiReadAllowed(): boolean;
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
1391
|
+
apiUpdateAllowed(item) { return this.remult.repo(this.entityType).getEntityRef(item).apiUpdateAllowed; }
|
|
1323
1392
|
get apiReadAllowed() { return this.remult.isAllowed(this.options.allowApiRead); }
|
|
1324
|
-
|
|
1325
|
-
|
|
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<
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
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,
|