angular-odata 0.160.0 → 0.162.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -7
- package/fesm2022/angular-odata.mjs +53 -68
- package/fesm2022/angular-odata.mjs.map +1 -1
- package/package.json +1 -1
- package/schematics/apigen/angular/base.js +7 -7
- package/schematics/apigen/angular/base.js.map +1 -1
- package/schematics/apigen/angular/entity.d.ts +2 -1
- package/schematics/apigen/angular/entity.js +6 -2
- package/schematics/apigen/angular/entity.js.map +1 -1
- package/schematics/apigen/angular/model.d.ts +2 -1
- package/schematics/apigen/angular/model.js +7 -6
- package/schematics/apigen/angular/model.js.map +1 -1
- package/schematics/apigen/angular/package.js.map +1 -1
- package/schematics/apigen/files/entity/__fileName__.ts +1 -1
- package/schematics/apigen/files/model/__fileName__.ts +2 -2
- package/schematics/metadata/index.js +4 -2
- package/schematics/metadata/index.js.map +1 -1
- package/schematics/metadata/parser/csdl/csdl-structural-property.d.ts +1 -0
- package/schematics/metadata/parser/csdl/csdl-structural-property.js +5 -0
- package/schematics/metadata/parser/csdl/csdl-structural-property.js.map +1 -1
- package/types/angular-odata.d.ts +37 -34
package/README.md
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
<h1 align="center">Angular OData</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<em>A fluent
|
|
4
|
+
<em>A typed, fluent client for OData services in Angular.</em>
|
|
5
5
|
<br>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
|
-
<a href="
|
|
9
|
+
<a href="https://diegomvh.github.io/angular-odata/docs/api/additional-documentation/introduction.html">Handbook</a>
|
|
10
10
|
·
|
|
11
|
-
<a href="
|
|
11
|
+
<a href="https://diegomvh.github.io/angular-odata/docs/api/index.html">API reference</a>
|
|
12
12
|
·
|
|
13
13
|
<a href="https://github.com/diegomvh/AngularODataEntity">Demo</a>
|
|
14
|
+
·
|
|
15
|
+
<a href="https://github.com/diegomvh/angular-odata/blob/main/CONTRIBUTING.md">Contributing</a>
|
|
14
16
|
<br>
|
|
15
17
|
<br>
|
|
16
18
|
</p>
|
|
@@ -26,14 +28,170 @@
|
|
|
26
28
|
|
|
27
29
|
<hr>
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
[OData](https://www.odata.org/) is a standard for REST APIs: every service describes its
|
|
32
|
+
data in a `$metadata` document and accepts the same query options (`$filter`, `$select`,
|
|
33
|
+
`$expand`, `$orderby`, …). **angular-odata** lets your Angular application talk to any OData
|
|
34
|
+
service without writing URLs by hand: you build requests with typed TypeScript code and get
|
|
35
|
+
back typed results.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// GET People?$select=UserName,FirstName&$filter=contains(UserName, 'russell')&$top=10
|
|
39
|
+
client
|
|
40
|
+
.entitySet<Person>('People')
|
|
41
|
+
.query((q) => {
|
|
42
|
+
q.filter(({ e, t }) => e().contains(t.UserName, 'russell'));
|
|
43
|
+
q.select(['UserName', 'FirstName']);
|
|
44
|
+
q.top(10);
|
|
45
|
+
})
|
|
46
|
+
.fetch()
|
|
47
|
+
.subscribe(({ entities }) => console.log(entities));
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- **Typed query builder**: `$filter`, `$select`, `$expand`, `$orderby`, `$search`,
|
|
53
|
+
`$compute`, `$apply` and paging, with field names checked by the compiler.
|
|
54
|
+
- **Complete OData API**: entity sets, entities, navigation properties, functions,
|
|
55
|
+
actions, singletons, media and `$batch` requests.
|
|
56
|
+
- **Code generation**: one command creates interfaces, services and models from your
|
|
57
|
+
service's `$metadata`.
|
|
58
|
+
- **Models and collections**: change tracking, validation and `save()` / `destroy()`.
|
|
59
|
+
- **Caching**: in-memory, Web Storage or IndexedDB, with fetch policies such as
|
|
60
|
+
`cache-first` and `cache-and-network`.
|
|
61
|
+
- **Several APIs** in the same application, each with its own configuration.
|
|
30
62
|
|
|
31
|
-
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
### 1. Install
|
|
32
66
|
|
|
33
67
|
```bash
|
|
34
68
|
npm i angular-odata
|
|
35
69
|
```
|
|
36
70
|
|
|
37
|
-
|
|
71
|
+
### 2. Register the client
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// app.config.ts
|
|
75
|
+
import { ApplicationConfig } from '@angular/core';
|
|
76
|
+
import { provideODataClient } from 'angular-odata';
|
|
77
|
+
|
|
78
|
+
export const appConfig: ApplicationConfig = {
|
|
79
|
+
providers: [
|
|
80
|
+
provideODataClient({
|
|
81
|
+
config: { serviceRootUrl: 'https://services.odata.org/V4/TripPinServiceRW/' },
|
|
82
|
+
}),
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Applications that use NgModules can import `ODataModule.forRoot({ config })` instead.
|
|
88
|
+
|
|
89
|
+
### 3. Query your service
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Component, inject, signal } from '@angular/core';
|
|
93
|
+
import { ODataClient } from 'angular-odata';
|
|
94
|
+
|
|
95
|
+
interface Person {
|
|
96
|
+
UserName: string;
|
|
97
|
+
FirstName: string;
|
|
98
|
+
LastName: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Component({
|
|
102
|
+
selector: 'app-people',
|
|
103
|
+
template: `
|
|
104
|
+
@for (person of people(); track person.UserName) {
|
|
105
|
+
<p>{{ person.FirstName }} {{ person.LastName }}</p>
|
|
106
|
+
}
|
|
107
|
+
`,
|
|
108
|
+
})
|
|
109
|
+
export class PeopleComponent {
|
|
110
|
+
private client = inject(ODataClient);
|
|
111
|
+
people = signal<Person[]>([]);
|
|
112
|
+
|
|
113
|
+
ngOnInit() {
|
|
114
|
+
this.client
|
|
115
|
+
.entitySet<Person>('People')
|
|
116
|
+
.query((q) => {
|
|
117
|
+
q.orderBy('LastName');
|
|
118
|
+
q.top(10);
|
|
119
|
+
})
|
|
120
|
+
.fetch()
|
|
121
|
+
.subscribe(({ entities }) => this.people.set(entities ?? []));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 4. Optional: generate a typed API
|
|
127
|
+
|
|
128
|
+
Instead of writing interfaces and services by hand, generate them from the `$metadata` of
|
|
129
|
+
your service:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
ng generate angular-odata:apigen --name=TripPin --metadata='https://services.odata.org/V4/TripPinServiceRW/$metadata'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { PeopleService, TripPinConfig } from './trip-pin';
|
|
137
|
+
|
|
138
|
+
// app.config.ts
|
|
139
|
+
provideODataClient({ config: TripPinConfig });
|
|
140
|
+
|
|
141
|
+
// Any component or service
|
|
142
|
+
const people = inject(PeopleService);
|
|
143
|
+
people
|
|
144
|
+
.entity('russellwhyte')
|
|
145
|
+
.fetch()
|
|
146
|
+
.subscribe(({ entity }) => console.log(entity));
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## A few more examples
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const people = client.entitySet<Person>('People');
|
|
153
|
+
|
|
154
|
+
// Create, update and delete
|
|
155
|
+
people.create({ UserName: 'jdoe', FirstName: 'John', LastName: 'Doe' }).subscribe();
|
|
156
|
+
people.entity('jdoe').modify({ FirstName: 'Johnny' }).subscribe();
|
|
157
|
+
people.entity('jdoe').destroy().subscribe();
|
|
158
|
+
|
|
159
|
+
// Expand related entities: $expand=Trips($select=Name,Budget)
|
|
160
|
+
people
|
|
161
|
+
.entity('russellwhyte')
|
|
162
|
+
.query((q) =>
|
|
163
|
+
q.expand(({ e, t }) =>
|
|
164
|
+
e().field(t.Trips, (f) => f.select(({ e, t }) => e().field(t.Name).field(t.Budget))),
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
.fetch()
|
|
168
|
+
.subscribe(({ entity }) => console.log(entity?.Trips));
|
|
169
|
+
|
|
170
|
+
// Follow the server's next links and get every entity
|
|
171
|
+
people.fetchAll().subscribe(({ entities }) => console.log(entities.length));
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Documentation
|
|
175
|
+
|
|
176
|
+
- **[Handbook](https://diegomvh.github.io/angular-odata/docs/api/additional-documentation/introduction.html)**:
|
|
177
|
+
guides for configuration, resources, the query builder, services, models, caching and code
|
|
178
|
+
generation.
|
|
179
|
+
- **[API reference](https://diegomvh.github.io/angular-odata/docs/api/index.html)**: every
|
|
180
|
+
class and method, generated from the source code.
|
|
181
|
+
- **[Demo application](https://github.com/diegomvh/AngularODataEntity)**: a complete Angular
|
|
182
|
+
application that uses the TripPin and Northwind sample services.
|
|
183
|
+
|
|
184
|
+
## Compatibility
|
|
185
|
+
|
|
186
|
+
- **Angular**: 20 or later.
|
|
187
|
+
- **OData**: version 4 is fully supported; versions 3 and 2 have basic support.
|
|
188
|
+
|
|
189
|
+
## Contributing
|
|
190
|
+
|
|
191
|
+
Bug reports and pull requests are welcome. Read the
|
|
192
|
+
[contributing guide](https://github.com/diegomvh/angular-odata/blob/main/CONTRIBUTING.md)
|
|
193
|
+
to get started.
|
|
194
|
+
|
|
195
|
+
## License
|
|
38
196
|
|
|
39
|
-
|
|
197
|
+
[MIT](https://github.com/diegomvh/angular-odata/blob/main/LICENSE)
|
|
@@ -1593,7 +1593,7 @@ const Objects = {
|
|
|
1593
1593
|
},
|
|
1594
1594
|
resolveKey(key, { single = true } = {}) {
|
|
1595
1595
|
const type = Types.rawType(key);
|
|
1596
|
-
if (['
|
|
1596
|
+
if (['Number', 'String'].indexOf(type) !== -1)
|
|
1597
1597
|
return key;
|
|
1598
1598
|
if (type !== 'Map' && type !== 'Object') {
|
|
1599
1599
|
return undefined;
|
|
@@ -2153,9 +2153,9 @@ class ODataEnumTypeParser extends ODataAnnotatable {
|
|
|
2153
2153
|
}
|
|
2154
2154
|
return schema;
|
|
2155
2155
|
}
|
|
2156
|
-
validate(value, { method, navigation = false, }
|
|
2156
|
+
validate(value, { method, navigation = false, }) {
|
|
2157
2157
|
if (this.flags) {
|
|
2158
|
-
|
|
2158
|
+
const fields = this.fields(value);
|
|
2159
2159
|
return value && fields.length === 0 ? ['mismatch'] : undefined;
|
|
2160
2160
|
}
|
|
2161
2161
|
else {
|
|
@@ -2195,6 +2195,7 @@ class ODataStructuredTypeFieldParser extends ODataAnnotatable {
|
|
|
2195
2195
|
navigation;
|
|
2196
2196
|
nullable;
|
|
2197
2197
|
default;
|
|
2198
|
+
optimisticConcurrency = false;
|
|
2198
2199
|
maxLength;
|
|
2199
2200
|
precision;
|
|
2200
2201
|
scale;
|
|
@@ -2215,7 +2216,7 @@ class ODataStructuredTypeFieldParser extends ODataAnnotatable {
|
|
|
2215
2216
|
this.precision = field.precision;
|
|
2216
2217
|
this.scale = field.scale;
|
|
2217
2218
|
}
|
|
2218
|
-
validate(value, { method, navigation = false, }
|
|
2219
|
+
validate(value, { method, navigation = false, }) {
|
|
2219
2220
|
let errors;
|
|
2220
2221
|
if (this.collection && Array.isArray(value)) {
|
|
2221
2222
|
errors = value.map((v) => this.validate(v, { method, navigation }));
|
|
@@ -2231,12 +2232,8 @@ class ODataStructuredTypeFieldParser extends ODataAnnotatable {
|
|
|
2231
2232
|
}
|
|
2232
2233
|
else {
|
|
2233
2234
|
// IsEdmType
|
|
2234
|
-
const computed = this.annotatedValue(COMPUTED);
|
|
2235
2235
|
errors = [];
|
|
2236
|
-
if (!this.nullable
|
|
2237
|
-
(value === null || (value === undefined && method !== 'modify')) && // Is null or undefined without patch?
|
|
2238
|
-
!(computed && method === 'create') // Not (Is Computed field and create) ?
|
|
2239
|
-
) {
|
|
2236
|
+
if (method !== 'modify' && value == null && !(this.nullable || this.isComputed())) {
|
|
2240
2237
|
errors.push(`required`);
|
|
2241
2238
|
}
|
|
2242
2239
|
if (this.maxLength !== undefined &&
|
|
@@ -2304,12 +2301,13 @@ class ODataStructuredTypeFieldParser extends ODataAnnotatable {
|
|
|
2304
2301
|
});
|
|
2305
2302
|
}
|
|
2306
2303
|
//#endregion
|
|
2307
|
-
configure({ options, parserForType, }) {
|
|
2304
|
+
configure({ options, optimisticConcurrency, parserForType, }) {
|
|
2308
2305
|
this.parserOptions = options;
|
|
2309
2306
|
this.parser = parserForType(this.type);
|
|
2310
|
-
if (
|
|
2307
|
+
if (optimisticConcurrency !== undefined)
|
|
2308
|
+
this.optimisticConcurrency = optimisticConcurrency;
|
|
2309
|
+
if (this.default !== undefined)
|
|
2311
2310
|
this.default = this.deserialize(this.default, options);
|
|
2312
|
-
}
|
|
2313
2311
|
}
|
|
2314
2312
|
//#region Json Schema
|
|
2315
2313
|
// https://json-schema.org/
|
|
@@ -2386,6 +2384,12 @@ class ODataStructuredTypeFieldParser extends ODataAnnotatable {
|
|
|
2386
2384
|
isKey() {
|
|
2387
2385
|
return this.structured.keys({ include_parents: true }).some((k) => k.name === this.name);
|
|
2388
2386
|
}
|
|
2387
|
+
isComputed() {
|
|
2388
|
+
return this.annotatedValue(COMPUTED);
|
|
2389
|
+
}
|
|
2390
|
+
isOptimisticConcurrency() {
|
|
2391
|
+
return this.optimisticConcurrency;
|
|
2392
|
+
}
|
|
2389
2393
|
hasReferentials() {
|
|
2390
2394
|
return this.referentials.length !== 0;
|
|
2391
2395
|
}
|
|
@@ -2518,14 +2522,18 @@ class ODataStructuredTypeParser extends ODataAnnotatable {
|
|
|
2518
2522
|
const parserOptions = { ...this.parserOptions, ...options };
|
|
2519
2523
|
return raw(JSON.stringify(this.serialize(value, parserOptions)));
|
|
2520
2524
|
}
|
|
2521
|
-
configure({ options, parserForType, }) {
|
|
2525
|
+
configure({ options, entitySet, parserForType, }) {
|
|
2522
2526
|
this.parserOptions = options;
|
|
2523
2527
|
if (this.base !== undefined) {
|
|
2524
2528
|
this.parent = parserForType(this.base);
|
|
2525
2529
|
if (this.parent !== undefined)
|
|
2526
2530
|
this.parent.children.push(this);
|
|
2527
2531
|
}
|
|
2528
|
-
|
|
2532
|
+
const optimisticConcurrencyFields = entitySet?.annotatedValue(OPTIMISTIC_CONCURRENCY) ?? [];
|
|
2533
|
+
this._fields.forEach((field) => {
|
|
2534
|
+
const optimisticConcurrency = optimisticConcurrencyFields.indexOf(field.name) !== -1;
|
|
2535
|
+
field.configure({ options, optimisticConcurrency, parserForType });
|
|
2536
|
+
});
|
|
2529
2537
|
}
|
|
2530
2538
|
/**
|
|
2531
2539
|
* Returns all fields of the structured type.
|
|
@@ -2677,7 +2685,7 @@ class ODataStructuredTypeParser extends ODataAnnotatable {
|
|
|
2677
2685
|
}
|
|
2678
2686
|
return schema;
|
|
2679
2687
|
}
|
|
2680
|
-
validate(attrs, { method, navigation = false, }
|
|
2688
|
+
validate(attrs, { method, navigation = false, }) {
|
|
2681
2689
|
const errors = {};
|
|
2682
2690
|
const fields = this.fields({
|
|
2683
2691
|
include_navigation: true,
|
|
@@ -2995,8 +3003,10 @@ class ODataStructuredType extends ODataParserSchemaElement {
|
|
|
2995
3003
|
if (this.parent !== undefined)
|
|
2996
3004
|
this.parent.children.push(this);
|
|
2997
3005
|
}
|
|
3006
|
+
const entitySet = this.api.findEntitySetForEntityType(this.type()) ?? this.api.findEntitySetForEntityType(this.type({ alias: true }));
|
|
2998
3007
|
this.parser.configure({
|
|
2999
3008
|
options,
|
|
3009
|
+
entitySet,
|
|
3000
3010
|
parserForType: (t) => this.api.parserForType(t),
|
|
3001
3011
|
});
|
|
3002
3012
|
if (this.model !== undefined) {
|
|
@@ -3180,7 +3190,7 @@ class ODataStructuredType extends ODataParserSchemaElement {
|
|
|
3180
3190
|
* @param method Method to use for the process validation
|
|
3181
3191
|
* @returns Object with the errors
|
|
3182
3192
|
*/
|
|
3183
|
-
validate(attrs, { method, navigation = false, }
|
|
3193
|
+
validate(attrs, { method, navigation = false, }) {
|
|
3184
3194
|
return this.parser.validate(attrs, { method, navigation });
|
|
3185
3195
|
}
|
|
3186
3196
|
}
|
|
@@ -3204,7 +3214,7 @@ class ODataSchema extends ODataAnnotatable {
|
|
|
3204
3214
|
this.containers = (config.containers ?? []).map((config) => new ODataEntityContainer(config, this));
|
|
3205
3215
|
}
|
|
3206
3216
|
isNamespaceOf(type) {
|
|
3207
|
-
return type.startsWith(this.namespace)
|
|
3217
|
+
return type.startsWith(this.namespace) || (this.alias != null && type.startsWith(this.alias));
|
|
3208
3218
|
}
|
|
3209
3219
|
get entitySets() {
|
|
3210
3220
|
return this.containers.reduce((acc, container) => [...acc, ...container.entitySets], []);
|
|
@@ -5988,8 +5998,7 @@ class ODataRequest {
|
|
|
5988
5998
|
if (apiOptions.prefer?.maxPageSize !== undefined && ['GET'].indexOf(this._method) !== -1)
|
|
5989
5999
|
prefer.push(`odata.maxpagesize=${apiOptions.prefer?.maxPageSize}`);
|
|
5990
6000
|
// Annotations
|
|
5991
|
-
if (apiOptions.prefer?.includeAnnotations !== undefined &&
|
|
5992
|
-
['GET'].indexOf(this._method) !== -1)
|
|
6001
|
+
if (apiOptions.prefer?.includeAnnotations !== undefined && ['GET'].indexOf(this._method) !== -1)
|
|
5993
6002
|
prefer.push(`odata.include-annotations=${apiOptions.prefer?.includeAnnotations}`);
|
|
5994
6003
|
// Omit Null Values
|
|
5995
6004
|
if (apiOptions.prefer?.omitNullValues === true && ['GET'].indexOf(this._method) !== -1)
|
|
@@ -10750,7 +10759,7 @@ class ODataCollection {
|
|
|
10750
10759
|
}
|
|
10751
10760
|
create(attrs = {}, { silent = false, server = true } = {}) {
|
|
10752
10761
|
const model = this.modelFactory(attrs);
|
|
10753
|
-
return (model.isValid() && server ? model.save() : of(model)).pipe(switchMap((model) => this.add(model, { silent, server })), map(() => model));
|
|
10762
|
+
return (model.isValid({ method: 'create' }) && server ? model.save() : of(model)).pipe(switchMap((model) => this.add(model, { silent, server })), map(() => model));
|
|
10754
10763
|
}
|
|
10755
10764
|
set(path, value, {}) {
|
|
10756
10765
|
const pathArray = (Types.isArray(path) ? path : path.match(/([^[.\]])+/g));
|
|
@@ -11204,10 +11213,10 @@ class ODataModel {
|
|
|
11204
11213
|
}
|
|
11205
11214
|
// Validation
|
|
11206
11215
|
_errors;
|
|
11207
|
-
validate({ method, navigation = false, }
|
|
11216
|
+
validate({ method, navigation = false, }) {
|
|
11208
11217
|
return this._meta.validate(this, { method, navigation });
|
|
11209
11218
|
}
|
|
11210
|
-
isValid({ method, navigation = false, }
|
|
11219
|
+
isValid({ method, navigation = false, }) {
|
|
11211
11220
|
this._errors = this.validate({ method, navigation });
|
|
11212
11221
|
if (this._errors !== undefined)
|
|
11213
11222
|
this.events$.trigger(ODataModelEventType.Invalid, {
|
|
@@ -11338,7 +11347,7 @@ class ODataModel {
|
|
|
11338
11347
|
resource instanceof ODataNavigationPropertyResource))
|
|
11339
11348
|
return throwError(() => new Error('save: Resource type ODataEntityResource/ODataNavigationPropertyResource needed'));
|
|
11340
11349
|
// Resolve method and resource key
|
|
11341
|
-
if (method === undefined && this.
|
|
11350
|
+
if (method === undefined && this.isCompoundKey())
|
|
11342
11351
|
return throwError(() => new Error('save: Composite key require a specific method, use create/update/modify'));
|
|
11343
11352
|
method = method || (!resource.hasKey() ? 'create' : 'update');
|
|
11344
11353
|
if (resource instanceof ODataEntityResource &&
|
|
@@ -11415,6 +11424,9 @@ class ODataModel {
|
|
|
11415
11424
|
isNew() {
|
|
11416
11425
|
return !this._meta.hasKey(this);
|
|
11417
11426
|
}
|
|
11427
|
+
isCompoundKey() {
|
|
11428
|
+
return this.schema().isCompoundKey();
|
|
11429
|
+
}
|
|
11418
11430
|
withResource(resource, ctx) {
|
|
11419
11431
|
return this._meta.withResource(this, resource, ctx);
|
|
11420
11432
|
}
|
|
@@ -11809,9 +11821,6 @@ class ODataModelField {
|
|
|
11809
11821
|
parser;
|
|
11810
11822
|
options;
|
|
11811
11823
|
default;
|
|
11812
|
-
required;
|
|
11813
|
-
concurrency;
|
|
11814
|
-
maxLength;
|
|
11815
11824
|
minLength;
|
|
11816
11825
|
min;
|
|
11817
11826
|
max;
|
|
@@ -11823,9 +11832,6 @@ class ODataModelField {
|
|
|
11823
11832
|
this.field = field;
|
|
11824
11833
|
this.parser = parser;
|
|
11825
11834
|
this.default = opts.default || parser.default;
|
|
11826
|
-
this.required = Boolean(opts.required || !parser.nullable);
|
|
11827
|
-
this.concurrency = Boolean(opts.concurrency);
|
|
11828
|
-
this.maxLength = opts.maxLength || parser.maxLength;
|
|
11829
11835
|
this.minLength = opts.minLength;
|
|
11830
11836
|
this.min = opts.min;
|
|
11831
11837
|
this.max = opts.max;
|
|
@@ -11843,16 +11849,20 @@ class ODataModelField {
|
|
|
11843
11849
|
annotatedValue(term) {
|
|
11844
11850
|
return this.parser.annotatedValue(term);
|
|
11845
11851
|
}
|
|
11846
|
-
configure({
|
|
11852
|
+
configure({ options }) {
|
|
11847
11853
|
this.parserOptions = options;
|
|
11848
|
-
if (concurrency)
|
|
11849
|
-
this.concurrency = concurrency;
|
|
11850
11854
|
if (this.default !== undefined)
|
|
11851
11855
|
this.default = this.deserialize(this.default, options);
|
|
11852
11856
|
}
|
|
11853
11857
|
isKey() {
|
|
11854
11858
|
return this.parser.isKey();
|
|
11855
11859
|
}
|
|
11860
|
+
isComputed() {
|
|
11861
|
+
return this.parser.isComputed();
|
|
11862
|
+
}
|
|
11863
|
+
isOptimisticConcurrency() {
|
|
11864
|
+
return this.parser.isOptimisticConcurrency();
|
|
11865
|
+
}
|
|
11856
11866
|
hasReferentials() {
|
|
11857
11867
|
return this.parser.hasReferentials();
|
|
11858
11868
|
}
|
|
@@ -11882,7 +11892,7 @@ class ODataModelField {
|
|
|
11882
11892
|
throw new Error(`Could not find enum type for ${this.parser.type}`);
|
|
11883
11893
|
return enumType;
|
|
11884
11894
|
}
|
|
11885
|
-
validate(value, { method, navigation = false, }
|
|
11895
|
+
validate(value, { method, navigation = false, }) {
|
|
11886
11896
|
if (ODataModelOptions.isModel(value)) {
|
|
11887
11897
|
return !value.isValid({ method, navigation }) ? value._errors : undefined;
|
|
11888
11898
|
}
|
|
@@ -11892,21 +11902,7 @@ class ODataModelField {
|
|
|
11892
11902
|
: undefined;
|
|
11893
11903
|
}
|
|
11894
11904
|
else {
|
|
11895
|
-
const computed = this.annotatedValue(COMPUTED);
|
|
11896
11905
|
const errors = this.parser?.validate(value, { method, navigation }) ?? [];
|
|
11897
|
-
if (this.required &&
|
|
11898
|
-
(value === null || (value === undefined && method !== 'modify')) && // Is null or undefined without patch?
|
|
11899
|
-
!(computed && method === 'create') // Not (Is Computed field and create) ?
|
|
11900
|
-
) {
|
|
11901
|
-
if (errors.indexOf('required') === -1)
|
|
11902
|
-
errors.push(`required`);
|
|
11903
|
-
}
|
|
11904
|
-
if (this.maxLength !== undefined &&
|
|
11905
|
-
typeof value === 'string' &&
|
|
11906
|
-
value.length > this.maxLength) {
|
|
11907
|
-
if (errors.indexOf('maxlength') === -1)
|
|
11908
|
-
errors.push(`maxlength`);
|
|
11909
|
-
}
|
|
11910
11906
|
if (this.minLength !== undefined &&
|
|
11911
11907
|
typeof value === 'string' &&
|
|
11912
11908
|
value.length < this.minLength) {
|
|
@@ -12010,11 +12006,11 @@ class ODataModelAttribute {
|
|
|
12010
12006
|
get navigation() {
|
|
12011
12007
|
return this._field.navigation;
|
|
12012
12008
|
}
|
|
12013
|
-
|
|
12014
|
-
return this._field.
|
|
12009
|
+
isComputed() {
|
|
12010
|
+
return this._field.isComputed();
|
|
12015
12011
|
}
|
|
12016
|
-
|
|
12017
|
-
return
|
|
12012
|
+
isOptimisticConcurrency() {
|
|
12013
|
+
return this._field.isOptimisticConcurrency();
|
|
12018
12014
|
}
|
|
12019
12015
|
get referentials() {
|
|
12020
12016
|
return this._field.referentials;
|
|
@@ -12184,17 +12180,7 @@ class ODataModelOptions {
|
|
|
12184
12180
|
this.parent.children.push(this);
|
|
12185
12181
|
}
|
|
12186
12182
|
this.entitySet = this.api.findEntitySetForEntityType(this.type());
|
|
12187
|
-
|
|
12188
|
-
if (this.entitySet !== undefined) {
|
|
12189
|
-
concurrencyFields = this.entitySet.annotatedValue(OPTIMISTIC_CONCURRENCY) ?? [];
|
|
12190
|
-
}
|
|
12191
|
-
this._fields.forEach((field) => {
|
|
12192
|
-
const concurrency = concurrencyFields.indexOf(field.field) !== -1;
|
|
12193
|
-
field.configure({
|
|
12194
|
-
concurrency,
|
|
12195
|
-
options,
|
|
12196
|
-
});
|
|
12197
|
-
});
|
|
12183
|
+
this._fields.forEach((field) => field.configure({ options, }));
|
|
12198
12184
|
}
|
|
12199
12185
|
fields({ include_navigation, include_parents, }) {
|
|
12200
12186
|
return [
|
|
@@ -12252,8 +12238,7 @@ class ODataModelOptions {
|
|
|
12252
12238
|
parser: structuredFieldParser,
|
|
12253
12239
|
});
|
|
12254
12240
|
modelField.configure({
|
|
12255
|
-
options: this.api.options
|
|
12256
|
-
concurrency: false,
|
|
12241
|
+
options: this.api.options
|
|
12257
12242
|
});
|
|
12258
12243
|
Object.defineProperty(self, modelField.name, {
|
|
12259
12244
|
configurable: true,
|
|
@@ -12450,7 +12435,7 @@ class ODataModelOptions {
|
|
|
12450
12435
|
return null;
|
|
12451
12436
|
return resolve ? Objects.resolveKey(referenced, { single }) : Object.fromEntries(referenced);
|
|
12452
12437
|
}
|
|
12453
|
-
validate(self, { method, navigation = false, }
|
|
12438
|
+
validate(self, { method, navigation = false, }) {
|
|
12454
12439
|
const errors = this.fields({
|
|
12455
12440
|
include_parents: true,
|
|
12456
12441
|
include_navigation: navigation,
|
|
@@ -12517,9 +12502,9 @@ class ODataModelOptions {
|
|
|
12517
12502
|
.reduce((acc, attr) => {
|
|
12518
12503
|
const name = field_mapping ? attr.fieldName : attr.name;
|
|
12519
12504
|
let value = attr.get();
|
|
12520
|
-
const computed = attr.
|
|
12505
|
+
const computed = attr.isComputed();
|
|
12521
12506
|
const navigation = attr.navigation;
|
|
12522
|
-
const concurrency = attr.
|
|
12507
|
+
const concurrency = attr.isOptimisticConcurrency();
|
|
12523
12508
|
if (ODataModelOptions.isModel(value)) {
|
|
12524
12509
|
value = value.toEntity({
|
|
12525
12510
|
client_id,
|
|
@@ -13036,7 +13021,7 @@ class ODataApi {
|
|
|
13036
13021
|
});
|
|
13037
13022
|
return true;
|
|
13038
13023
|
}
|
|
13039
|
-
populate({ schemas, references, models, collections }) {
|
|
13024
|
+
populate({ schemas, references, models, collections, }) {
|
|
13040
13025
|
const odataSchemas = (schemas ?? []).map((schema) => new ODataSchema(schema, this));
|
|
13041
13026
|
//TODO: Merge duplicates
|
|
13042
13027
|
this.schemas = [...this.schemas, ...odataSchemas];
|