angular-odata
Version:
Client side OData typescript library for Angular
1,227 lines • 177 kB
JavaScript
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { CID_FIELD_NAME, COMPUTED, DEFAULT_VERSION, OPTIMISTIC_CONCURRENCY, EVENT_SPLITTER, } from '../constants';
import { ODataHelper } from '../helper';
import { ODataEntityResource, ODataEntitySetResource, ODataNavigationPropertyResource, ODataPropertyResource, } from '../resources';
import { EdmType } from '../types';
import { Objects, Types } from '../utils';
import { ODataCollection } from './collection';
import { ODataModel } from './model';
import { EventEmitter } from '@angular/core';
import { ODataEntityAnnotations, } from '../annotations';
export var ODataModelEventType;
(function (ODataModelEventType) {
ODataModelEventType["Change"] = "change";
ODataModelEventType["Reset"] = "reset";
ODataModelEventType["Update"] = "update";
ODataModelEventType["Sort"] = "sort";
ODataModelEventType["Destroy"] = "destroy";
ODataModelEventType["Add"] = "add";
ODataModelEventType["Remove"] = "remove";
ODataModelEventType["Invalid"] = "invalid";
ODataModelEventType["Request"] = "request";
ODataModelEventType["Sync"] = "sync";
ODataModelEventType["Attach"] = "attach";
})(ODataModelEventType || (ODataModelEventType = {}));
export class ODataModelEvent {
type;
value;
previous;
options;
constructor(type, { model, collection, previous, value, attr, options, bubbles, chain, } = {}) {
this.type = type;
this.model = model;
this.collection = collection;
this.previous = previous;
this.value = value;
this.options = options;
this.chain = chain ?? [
[
(this.model || this.collection),
attr || null,
],
];
this.bubbles = bubbles ?? BUBBLES.indexOf(this.type) !== -1;
}
chain;
push(model, attr) {
return new ODataModelEvent(this.type, {
model: this.model ?? (model instanceof ODataModel ? model : undefined),
collection: this.collection ??
(model instanceof ODataCollection
? model
: undefined),
previous: this.previous,
value: this.value,
options: {
...this.options,
index: attr instanceof ODataModelAttribute ? attr.name : attr,
},
bubbles: this.bubbles,
chain: [[model, attr], ...this.chain],
});
}
bubbles;
stopPropagation() {
this.bubbles = false;
}
visited(model) {
return (this.chain.some((c) => c[0] === model) &&
this.chain[this.chain.length - 1][0] !== model);
}
canContinueWith(self) {
return this.bubbles && !this.visited(self);
}
get path() {
return this.chain
.map(([, attr], index) => typeof attr === 'number'
? `[${attr}]`
: attr instanceof ODataModelAttribute
? index === 0
? attr.name
: `.${attr.name}`
: '')
.join('');
}
//Reference to the model which the event was dispatched
model;
//Identifies the current model for the event
get currentModel() {
const link = this.chain.find((c) => ODataModelOptions.isModel(c[0]));
return link !== undefined ? link[0] : undefined;
}
//Reference to the collection which the event was dispatched
collection;
//Identifies the current collection for the event
get currentCollection() {
const link = this.chain.find((c) => ODataModelOptions.isCollection(c[0]));
return link !== undefined
? link[0]
: undefined;
}
}
export class ODataModelEventEmitter extends EventEmitter {
model;
collection;
constructor({ model, collection, } = {}) {
super();
this.model = model;
this.collection = collection;
}
trigger(type, { collection, previous, value, attr, options, bubbles, } = {}) {
const _trigger = (name) => this.emit(new ODataModelEvent(name, {
model: this.model,
collection: collection ?? this.collection,
previous,
value,
attr,
options,
bubbles,
}));
if (type && EVENT_SPLITTER.test(type)) {
for (const name of type.split(EVENT_SPLITTER)) {
_trigger(name);
}
}
else {
_trigger(type);
}
}
}
export const BUBBLES = [
ODataModelEventType.Change,
ODataModelEventType.Reset,
ODataModelEventType.Update,
ODataModelEventType.Destroy,
ODataModelEventType.Add,
ODataModelEventType.Remove,
];
export const INCLUDE_SHALLOW = {
include_concurrency: true,
include_computed: true,
include_key: true,
include_id: false,
};
export const INCLUDE_DEEP = {
include_navigation: true,
include_non_field: true,
...INCLUDE_SHALLOW,
};
export var ODataModelState;
(function (ODataModelState) {
ODataModelState[ODataModelState["Added"] = 0] = "Added";
ODataModelState[ODataModelState["Removed"] = 1] = "Removed";
ODataModelState[ODataModelState["Changed"] = 2] = "Changed";
ODataModelState[ODataModelState["Unchanged"] = 3] = "Unchanged";
})(ODataModelState || (ODataModelState = {}));
export function Model({ cid = CID_FIELD_NAME } = {}) {
return (constructor) => {
const Klass = constructor;
if (!Klass.hasOwnProperty('options'))
Klass.options = {
fields: new Map(),
};
Klass.options.cid = cid;
return constructor;
};
}
export function ModelField({ name, ...options } = {}) {
return (target, key) => {
const Klass = target.constructor;
if (!Klass.hasOwnProperty('options'))
Klass.options = {
fields: new Map(),
};
options.field = name ?? key;
Klass.options.fields.set(key, options);
};
}
export class ODataModelField {
name;
field;
parser;
options;
optionsForType;
modelForType;
collectionForType;
enumForType;
structuredForType;
default;
required;
concurrency;
maxLength;
minLength;
min;
max;
pattern;
parserOptions;
constructor(options, { name, field, parser, ...opts }) {
this.options = options;
this.name = name;
this.field = field;
this.parser = parser;
this.default = opts.default || parser.default;
this.required = Boolean(opts.required || !parser.nullable);
this.concurrency = Boolean(opts.concurrency);
this.maxLength = opts.maxLength || parser.maxLength;
this.minLength = opts.minLength;
this.min = opts.min;
this.max = opts.max;
this.pattern = opts.pattern;
}
get type() {
return this.parser.type;
}
get navigation() {
return this.parser.navigation;
}
get collection() {
return this.parser.collection;
}
annotatedValue(term) {
return this.parser.annotatedValue(term);
}
configure({ optionsForType, modelForType, collectionForType, enumForType, structuredForType, concurrency, options, }) {
this.optionsForType = optionsForType;
this.modelForType = modelForType;
this.collectionForType = collectionForType;
this.enumForType = enumForType;
this.structuredForType = structuredForType;
this.parserOptions = options;
if (concurrency)
this.concurrency = concurrency;
if (this.default !== undefined)
this.default = this.deserialize(this.default, options);
}
isKey() {
return this.parser.isKey();
}
hasReferentials() {
return this.parser.hasReferentials();
}
get referentials() {
return this.parser.referentials;
}
isStructuredType() {
return this.parser.isStructuredType();
}
structuredType() {
const structuredType = this.structuredForType
? this.structuredForType(this.type)
: undefined;
//Throw error if not found
if (!structuredType)
throw new Error(`Could not find structured type for ${this.parser.type}`);
return structuredType;
}
isEnumType() {
return this.parser.isEnumType();
}
enumType() {
const enumType = this.enumForType ? this.enumForType(this.type) : undefined;
//Throw error if not found
if (!enumType)
throw new Error(`Could not find enum type for ${this.parser.type}`);
return enumType;
}
validate(value, { method, navigation = false, } = {}) {
if (ODataModelOptions.isModel(value)) {
return !value.isValid({ method, navigation }) ? value._errors : undefined;
}
else if (ODataModelOptions.isCollection(value)) {
return value
.models()
.some((m) => !m.isValid({ method, navigation }))
? value.models().map((m) => m._errors)
: undefined;
}
else {
const computed = this.annotatedValue(COMPUTED);
const errors = this.parser?.validate(value, { method, navigation }) || [];
if (this.required &&
(value === null || (value === undefined && method !== 'modify')) && // Is null or undefined without patch?
!(computed && method === 'create') // Not (Is Computed field and create) ?
) {
errors['push'](`required`);
}
if (this.maxLength !== undefined &&
typeof value === 'string' &&
value.length > this.maxLength) {
errors['push'](`maxlength`);
}
if (this.minLength !== undefined &&
typeof value === 'string' &&
value.length < this.minLength) {
errors['push'](`minlength`);
}
if (this.min !== undefined &&
typeof value === 'number' &&
value < this.min) {
errors['push'](`min`);
}
if (this.max !== undefined &&
typeof value === 'number' &&
value > this.max) {
errors['push'](`max`);
}
if (this.pattern !== undefined &&
typeof value === 'string' &&
!this.pattern.test(value)) {
errors['push'](`pattern`);
}
return !Types.isEmpty(errors) ? errors : undefined;
}
}
defaults() {
const meta = this.optionsForType
? this.optionsForType(this.type)
: undefined;
return this.isStructuredType() && meta !== undefined
? meta.defaults()
: this.default;
}
deserialize(value, options) {
const parserOptions = options ?? this.parserOptions;
return this.parser.deserialize(value, parserOptions);
}
serialize(value, options) {
const parserOptions = options ?? this.parserOptions;
return this.parser.serialize(value, parserOptions);
}
encode(value, options) {
const parserOptions = options ?? this.parserOptions;
return this.parser.encode(value, parserOptions);
}
resourceFactory(base) {
if (!(base instanceof ODataEntityResource ||
base instanceof ODataNavigationPropertyResource ||
base instanceof ODataPropertyResource))
throw new Error("Can't build resource for non compatible base type");
return this.navigation
? base.navigationProperty(this.parser.name)
: base.property(this.parser.name);
}
annotationsFactory(base) {
return this.parser.collection
? base.property(this.parser.name, 'collection')
: base.property(this.parser.name, 'single');
}
modelFactory({ parent, value, reset, }) {
// Model
const annots = this.annotationsFactory(parent.annots());
let Model = this.modelForType ? this.modelForType(this.type) : undefined;
if (Model === undefined)
throw Error(`No Model type for ${this.name}`);
if (value !== undefined) {
annots.update(value);
}
if (annots?.type !== undefined && Model.meta !== null) {
const meta = Model.meta.findChildOptions((o) => o.isTypeOf(annots.type))?.structuredType;
if (meta !== undefined && meta.model !== undefined)
// Change to child model
Model = meta.model;
}
return new Model((value || {}), {
annots,
reset,
parent: [parent, this],
});
}
collectionFactory({ parent, value, reset, }) {
// Collection Factory
const annots = this.annotationsFactory(parent.annots());
const Collection = this.collectionForType
? this.collectionForType(this.type)
: undefined;
if (Collection === undefined)
throw Error(`No Collection type for ${this.name}`);
return new Collection((value || []), {
annots: annots,
reset,
parent: [parent, this],
});
}
}
export class ODataModelAttribute {
_model;
_field;
state = ODataModelState.Unchanged;
value;
change;
subscription;
events$ = new ODataModelEventEmitter();
constructor(_model, _field) {
this._model = _model;
this._field = _field;
}
get type() {
return this._field.type;
}
get navigation() {
return this._field.navigation;
}
get computed() {
return this._field.annotatedValue(COMPUTED);
}
get concurrency() {
return Boolean(this._field.concurrency);
}
get referentials() {
return this._field.referentials;
}
get options() {
return this._field.options;
}
get name() {
return this._field.name;
}
get fieldName() {
return this._field.field;
}
get() {
return this.state === ODataModelState.Changed ? this.change : this.value;
}
set(value, reset = false, reparent = false) {
const current = this.get();
if (ODataModelOptions.isModel(current) ||
ODataModelOptions.isCollection(current))
this.unlink(current);
const changed = ODataModelOptions.isModel(current) && ODataModelOptions.isModel(value)
? !current.equals(value)
: ODataModelOptions.isCollection(current) &&
ODataModelOptions.isCollection(value)
? !current.equals(value)
: !Types.isEqual(current, value);
if (reset) {
this.value = value;
this.state = ODataModelState.Unchanged;
}
else if (Types.isEqual(value, this.value)) {
this.state = ODataModelState.Unchanged;
}
else if (changed) {
this.change = value;
this.state = ODataModelState.Changed;
}
if (ODataModelOptions.isModel(value) ||
ODataModelOptions.isCollection(value)) {
this.link(value, reparent);
}
return changed;
}
isChanged({ include_navigation = false, } = {}) {
const current = this.get();
return (this.state === ODataModelState.Changed ||
((ODataModelOptions.isModel(current) ||
ODataModelOptions.isCollection(current)) &&
current.hasChanged({ include_navigation })));
}
reset() {
if (ODataModelOptions.isModel(this.change) ||
ODataModelOptions.isCollection(this.change))
this.unlink(this.change);
this.state = ODataModelState.Unchanged;
if (ODataModelOptions.isModel(this.value) ||
ODataModelOptions.isCollection(this.value))
this.link(this.value);
}
link(value, reparent = false) {
this.subscription = value.events$.subscribe((e) => this.events$.emit(e));
if (reparent) {
value._parent = [this._model, this._field];
}
}
unlink(value, reparent = false) {
this.subscription?.unsubscribe();
this.subscription = undefined;
if (reparent) {
value._parent = null;
}
}
}
export class ODataModelOptions {
name;
cid;
base;
_fields = [];
structuredType;
entitySet;
// Hierarchy
parent;
children = [];
events$ = new ODataModelEventEmitter();
constructor({ config, structuredType, }) {
this.name = structuredType.name;
this.base = structuredType.base;
this.structuredType = structuredType;
this.cid = config?.cid ?? CID_FIELD_NAME;
config.fields.forEach((value, key) => this.addField(key, value));
}
get api() {
return this.structuredType.api;
}
type({ alias = false } = {}) {
return this.structuredType.type({ alias });
}
isOpenType() {
return this.structuredType.isOpenType();
}
isEntityType() {
return this.structuredType.isEntityType();
}
isComplexType() {
return this.structuredType.isComplexType();
}
isTypeOf(type) {
return this.structuredType.type() === type;
}
isModelFor(entity) {
// Resolve By Type
const type = this.api.options.helper.type(entity);
if (type && this.isTypeOf(type))
return true;
// Resolve By fields
const keys = Object.keys(entity);
const names = this.fields({
include_navigation: true,
include_parents: true,
}).map((f) => f.name);
return keys.every((key) => names.includes(key));
}
findChildOptions(predicate) {
if (predicate(this))
return this;
let match;
for (const ch of this.children) {
match = ch.findChildOptions(predicate);
if (match !== undefined)
break;
}
return match;
}
configure({ options }) {
if (this.base) {
const parent = this.api.optionsForType(this.base);
parent.children.push(this);
this.parent = parent;
}
this.entitySet = this.api.findEntitySetForEntityType(this.type());
let concurrencyFields = [];
if (this.entitySet !== undefined) {
concurrencyFields =
this.entitySet.annotatedValue(OPTIMISTIC_CONCURRENCY) || [];
}
this._fields.forEach((field) => {
const concurrency = concurrencyFields.indexOf(field.field) !== -1;
field.configure({
optionsForType: (t) => this.api.optionsForType(t),
modelForType: (t) => this.api.modelForType(t),
collectionForType: (t) => this.api.collectionForType(t),
enumForType: (t) => this.api.findEnumType(t),
structuredForType: (t) => this.api.findStructuredType(t),
concurrency,
options,
});
});
}
fields({ include_navigation, include_parents, }) {
return [
...(include_parents && this.parent !== undefined
? this.parent.fields({ include_navigation, include_parents })
: []),
...this._fields.filter((field) => include_navigation || !field.navigation),
];
}
field(name) {
const field = this.findField(name);
//Throw error if not found
if (!field)
throw new Error(`No field with name ${name}`);
return field;
}
findField(name, { reset } = {}) {
return this.fields({
include_parents: true,
include_navigation: true,
}).find((modelField) => (reset && modelField.field === name) || modelField.name === name);
}
addField(name, options) {
const { field, parser, ...opts } = options;
if (field === undefined || name === undefined)
throw new Error('Model Properties need name and field');
const fieldParser = parser ?? this.structuredType.field(field);
if (fieldParser === undefined)
throw new Error(`No parser for ${field} with name = ${name}`);
const modelField = new ODataModelField(this, {
name,
field,
parser: fieldParser,
...opts,
});
this._fields.push(modelField);
return modelField;
}
tsToEdm = {
string: EdmType.String,
number: EdmType.Int32,
bigint: EdmType.Int64,
boolean: EdmType.Boolean,
};
modelFieldFactory(self, name, type) {
const structuredFieldParser = this.structuredType.addField(name, {
type,
});
structuredFieldParser.configure({
parserForType: (type) => this.api.parserForType(type),
options: this.api.options,
});
const modelField = this.addField(name, {
field: name,
parser: structuredFieldParser,
});
modelField.configure({
optionsForType: (t) => this.api.optionsForType(t),
modelForType: (t) => this.api.modelForType(t),
collectionForType: (t) => this.api.collectionForType(t),
enumForType: (t) => this.api.findEnumType(t),
structuredForType: (t) => this.api.findStructuredType(t),
options: this.api.options,
concurrency: false,
});
Object.defineProperty(self, modelField.name, {
configurable: true,
get: () => this.get(self, modelField),
set: (value) => this.set(self, modelField, value),
});
return modelField;
}
attach(self, resource) {
if (self._resource !== null &&
resource.outgoingType() !== self._resource.outgoingType() &&
!self._resource.isSubtypeOf(resource))
throw new Error(`Can't attach ${resource.outgoingType()} to ${self._resource.outgoingType()}`);
const current = self._resource;
if (current === null || !current.isEqualTo(resource)) {
self._resource = resource;
self.events$.trigger(ODataModelEventType.Attach, {
previous: current,
value: resource,
});
}
}
//# region Resource
static chain(child) {
const chain = [];
let tuple = [child, null];
while (tuple !== null) {
const parent = tuple;
if (chain.some((p) => p[0] === parent[0]))
break;
chain.splice(0, 0, parent);
tuple = tuple[0]._parent;
}
return chain;
}
static resource(child) {
let resource = null;
let prevField = null;
for (const [model, field] of ODataModelOptions.chain(child)) {
resource = resource || model._resource;
if (resource === null)
break;
if (ODataModelOptions.isModel(model) &&
(prevField === null || prevField.collection)) {
const m = model;
// Resolve subtype if collection not is from field
// FIXME
/*
if (field === null) {
const r = m._meta.modelResourceFactory(resource.cloneQuery<T>());
if (r !== null && !r.isTypeOf(resource) && r.isSubtypeOf(resource)) {
resource = r;
}
}
*/
// Resolve key
const mKey = m.key({ field_mapping: true });
if (mKey !== undefined) {
resource =
resource instanceof ODataEntitySetResource
? resource.entity(mKey)
: resource.key(mKey);
}
}
prevField = field;
if (field === null && model._resource !== null) {
// Apply the query from model to new resource
model._resource.query((qs) => resource?.query((qd) => qd.restore(qs.store())));
}
else if (field !== null) {
resource = field.resourceFactory(resource);
}
}
return resource;
}
collectionResourceFactory(query) {
if (this.entitySet === undefined)
return null;
return ODataEntitySetResource.factory(this.api, {
path: this.entitySet.name,
type: this.entitySet.entityType,
query,
});
}
modelResourceFactory(query) {
const resource = this.collectionResourceFactory(query);
if (resource instanceof ODataEntitySetResource)
return resource.entity();
return resource;
}
//#endregion
bind(self, { parent, resource, annots, } = {}) {
// Events
self.events$.subscribe((e) => this.events$.emit(e));
// Parent
if (parent !== undefined) {
self._parent = parent;
}
// Resource
if (self._parent === null && resource === undefined)
resource = this.modelResourceFactory();
if (resource) {
this.attach(self, resource);
}
// Annotations
self._annotations =
annots ?? new ODataEntityAnnotations(ODataHelper[DEFAULT_VERSION]);
// Fields
this.fields({
include_navigation: true,
include_parents: true,
}).forEach((field) => {
Object.defineProperty(self, field.name, {
configurable: true,
get: () => this.get(self, field),
set: (value) => this.set(self, field, value),
});
});
}
query(self, resource, func) {
resource.query(func);
this.attach(self, resource);
return self;
}
resolveKey(value, { field_mapping = false, resolve = true, single = true, } = {}) {
const keyTypes = this.structuredType.keys({ include_parents: true });
const key = new Map();
for (const kt of keyTypes) {
let v = value;
let options = this;
let field;
for (const name of kt.name.split('/')) {
if (options === undefined)
break;
field = options
.fields({ include_navigation: false, include_parents: true })
.find((field) => field.field === name);
if (field !== undefined) {
v =
Types.isPlainObject(v) || ODataModelOptions.isModel(v)
? v[field.name]
: v;
options = this.api.optionsForType(field.type);
}
}
if (field === undefined)
return undefined;
let name = field_mapping ? field.field : field.name;
if (kt.alias !== undefined)
name = kt.alias;
key.set(name, v);
}
if (key.size === 0)
return undefined;
return resolve
? Objects.resolveKey(key, { single })
: Object.fromEntries(key);
}
resolveReferential(value, attr, { field_mapping = false, resolve = true, single = false, } = {}) {
const referential = new Map();
for (const ref of attr.referentials) {
const from = this.fields({
include_navigation: false,
include_parents: true,
}).find((p) => p.field === ref.referencedProperty);
const to = attr.options
.fields({ include_navigation: false, include_parents: true })
.find((field) => field.field === ref.property);
if (from !== undefined && to !== undefined) {
const name = field_mapping ? to.field : to.name;
referential.set(name, value && value[from.name]);
}
}
if (referential.size === 0)
return undefined;
if (referential.size === 1 && Array.from(referential.values())[0] === null)
return null;
return resolve
? Objects.resolveKey(referential, { single })
: Object.fromEntries(referential);
}
resolveReferenced(value, attr, { field_mapping = false, resolve = true, single = false, } = {}) {
const referenced = new Map();
for (const ref of attr.referentials) {
const from = this.fields({
include_navigation: false,
include_parents: true,
}).find((field) => field.field === ref.property);
const meta = this.api.optionsForType(attr.type);
const to = meta
?.fields({ include_navigation: false, include_parents: true })
.find((field) => field.field === ref.referencedProperty);
if (from !== undefined && to !== undefined) {
const name = field_mapping ? to.field : to.name;
referenced.set(name, value && value[from.name]);
}
}
if (referenced.size === 0)
return undefined;
if (referenced.size === 1 && Array.from(referenced.values())[0] === null)
return null;
return resolve
? Objects.resolveKey(referenced, { single })
: Object.fromEntries(referenced);
}
validate(self, { method, navigation = false, } = {}) {
const errors = this.fields({
include_parents: true,
include_navigation: navigation,
}).reduce((acc, field) => {
const value = self[field.name];
const errs = field.validate(value, { method });
return errs !== undefined
? Object.assign(acc, { [field.name]: errs })
: acc;
}, {});
return !Types.isEmpty(errors) ? errors : undefined;
}
defaults() {
const defs = this.fields({
include_navigation: false,
include_parents: true,
}).reduce((acc, field) => {
const value = field.defaults();
return value !== undefined
? Object.assign(acc, { [field.name]: value })
: acc;
}, {});
return !Types.isEmpty(defs) ? defs : undefined;
}
hasChanged(self, { include_navigation = false } = {}) {
return [...self._attributes.values()]
.filter((attr) => !attr.navigation || include_navigation)
.some((attr) => attr.isChanged({ include_navigation }));
}
hasKey(self) {
return this.resolveKey(self) !== undefined;
}
withResource(self, resource, ctx) {
// Push
self.pushResource(resource);
// Execute function
const result = ctx(self);
if (result instanceof Observable) {
return result.pipe(finalize(() => {
// Pop
self.popResource();
}));
}
else {
// Pop
self.popResource();
return result;
}
}
asEntity(self, ctx) {
// Clone query from him or parent
let query = self._resource?.cloneQuery();
if (query === undefined &&
self._parent &&
self._parent[0] instanceof ODataCollection)
query = self._parent[0]._resource?.cloneQuery();
// Build new resource
const resource = this.modelResourceFactory(query);
return this.withResource(self, resource, ctx);
}
toEntity(self, { client_id = false, include_navigation = false, include_concurrency = false, include_computed = false, include_key = true, include_id = false, include_non_field = false, changes_only = false, field_mapping = false, chain = [], } = {}) {
let entity = [...self._attributes.values()]
.filter(
// Chain
(attr) => chain.every((c) => c !== attr.get()))
.filter(
// Changes only
(attr) => !changes_only ||
(changes_only && attr.isChanged({ include_navigation })))
.filter((attr) =>
// Navigation
(include_navigation && attr.navigation && attr.get() !== null) ||
!attr.navigation)
.reduce((acc, attr) => {
const name = field_mapping ? attr.fieldName : attr.name;
let value = attr.get();
const computed = attr.computed;
const navigation = attr.navigation;
const concurrency = attr.concurrency;
if (ODataModelOptions.isModel(value)) {
value = value.toEntity({
client_id,
include_navigation,
include_concurrency,
include_computed,
include_non_field,
field_mapping,
changes_only: changes_only && !!navigation,
include_key: include_key && !!navigation,
include_id: include_id && !!navigation,
chain: [self, ...chain],
});
}
else if (ODataModelOptions.isCollection(value)) {
value = value.toEntities({
client_id,
include_navigation,
include_concurrency,
include_computed,
include_non_field,
field_mapping,
changes_only: changes_only && !!navigation,
include_key: include_key && !!navigation,
include_id: include_id && !!navigation,
chain: [self, ...chain],
});
}
if (include_concurrency && concurrency) {
return Object.assign(acc, { [name]: value });
}
else if (include_computed && computed) {
return Object.assign(acc, { [name]: value });
}
else if (changes_only && attr.isChanged()) {
return Object.assign(acc, { [name]: value });
}
else if (!changes_only && !concurrency && !computed) {
return Object.assign(acc, { [name]: value });
}
return acc;
}, {});
if (include_non_field) {
const names = Object.keys(entity);
// Attributes from object (attributes for object)
const nonFieldAttrs = Object.entries(self)
.filter(([k]) => names.indexOf(k) === -1 && !k.startsWith('_') && !k.endsWith('$'))
.reduce((acc, [k, v]) => Object.assign(acc, { [k]: v }), {});
entity = { ...entity, ...nonFieldAttrs };
}
// Add client_id
if (client_id) {
entity[this.cid] = self[this.cid];
}
// Add key
if (include_key) {
entity = {
...entity,
...this.resolveKey(self, { field_mapping, resolve: false }),
};
}
// Add id
if (include_id) {
self.asEntity((e) => {
const resource = e.resource();
if (resource)
entity[this.api.options.helper.ODATA_ID] = `${resource.clearQuery()}`;
});
}
// Add type
if (self._parent !== null &&
((ODataModelOptions.isModel(self._parent[0]) &&
self._parent[1] !== null &&
this.api.optionsForType(self._parent[1].type) !== self._meta) ||
(ODataModelOptions.isCollection(self._parent[0]) &&
self._parent[0]._model
.meta !== self._meta))) {
entity[this.api.options.helper.ODATA_TYPE] =
`#${this.structuredType.type()}`;
}
return entity;
}
reset(self, { name, silent = false } = {}) {
let changes = [];
if (name !== undefined) {
// Reset value
const attribute = self._attributes.get(name);
if (attribute !== undefined &&
attribute.isChanged({ include_navigation: true })) {
attribute.reset();
changes = [name];
}
else if (attribute?.isChanged()) {
attribute.reset();
changes = [name];
}
}
else {
// reset all
changes = [...self._attributes.keys()];
//self._changes.clear();
self._attributes.forEach((attr, key) => {
if (attr.isChanged({ include_navigation: true })) {
attr.reset();
changes.push(key);
}
});
}
if (!silent && changes.length > 0) {
self.events$.trigger(ODataModelEventType.Reset, { options: { changes } });
}
}
assign(self, entity, { add = true, merge = true, remove = true, reset = false, reparent = false, silent = false, } = {}) {
const changes = [];
// Update annotations
self.annots().update(entity);
// Update attributes
const attrs = self.annots().attributes(entity, 'full');
Object.entries(attrs)
.filter(([, value]) => value !== undefined) // Filter undefined
.forEach(([key, value]) => {
const field = this.findField(key, { reset });
if (field !== undefined || this.isOpenType()) {
// Delegated to private setter
if (this.set(self, field ?? key, value, {
add,
merge,
remove,
reset,
reparent,
silent,
})) {
changes.push(field?.name ?? key);
}
}
else {
// Basic assignment
const current = self[key];
self[key] = value;
if (current !== value)
changes.push(key);
}
});
if (!silent && changes.length > 0) {
self.events$.trigger(reset ? ODataModelEventType.Reset : ODataModelEventType.Update, { options: { changes } });
}
return self;
}
static isModel(obj) {
return Types.rawType(obj) === 'Model';
}
static isCollection(obj) {
return Types.rawType(obj) === 'Collection';
}
get(self, field) {
const attr = self._attributes.get(field instanceof ODataModelField ? field.name : field);
if (attr !== undefined) {
const value = attr.get();
if ((attr.navigation && value === null) ||
ODataModelOptions.isModel(value)) {
// Check for reference
const referenced = this.resolveReferenced(self, attr, {
resolve: false,
});
if (value !== null && referenced !== null && referenced !== undefined) {
value.assign(referenced, {
silent: true,
});
}
else if (value !== null && referenced === null) {
// New value is null
attr.set(null);
}
else if (value === null && referenced !== null) {
// New value is undefined
attr.set(undefined);
}
}
return value;
}
else if (typeof field === 'string' &&
!field.startsWith('_') &&
!field.endsWith('$')) {
return self[field];
}
return undefined;
}
set(self, field, value, { add, merge, remove, reset, reparent, silent, type, } = {}) {
let modelField = field instanceof ODataModelField ? field : this.findField(field);
if (modelField === undefined &&
this.isOpenType() &&
typeof field === 'string') {
type = type ?? this.tsToEdm[typeof value] ?? EdmType.String;
modelField = this.modelFieldFactory(self, field, type);
}
if (modelField === undefined)
throw new Error(`No field with name ${field}`);
let changed = false;
let attr = self._attributes.get(modelField.name);
// Ensures that the attribute exists
if (attr === undefined) {
attr = new ODataModelAttribute(self, modelField);
this._link(self, attr);
self._attributes.set(modelField.name, attr);
}
const current = attr.get();
if (modelField.isStructuredType()) {
if (value === null) {
// New value is null
changed = attr.set(value, reset, reparent);
}
else if (ODataModelOptions.isCollection(current)) {
// Current is collection
const currentCollection = current;
if (ODataModelOptions.isCollection(value)) {
// New value is collection
changed = attr.set(value, reset, reparent);
}
else if (Types.isArray(value)) {
// New value is array
currentCollection._annotations = modelField.annotationsFactory(self.annots());
currentCollection.assign(value, {
add,
merge,
remove,
reset,
reparent,
silent,
});
changed = currentCollection.hasChanged();
}
}
else if (ODataModelOptions.isModel(current)) {
// Current is model
const currentModel = current;
if (ODataModelOptions.isModel(value)) {
// New value is model
changed = attr.set(value, reset, reparent);
}
else if (Types.isPlainObject(value)) {
currentModel._annotations = modelField.annotationsFactory(self.annots());
currentModel.assign(value, {
add,
merge,
remove,
reset,
reparent,
silent,
});
changed = currentModel.hasChanged();
}
}
else {
// Current is null or undefined
// create new model/collection for given value
changed = attr.set(ODataModelOptions.isCollection(value) ||
ODataModelOptions.isModel(value)
? value
: modelField.collection
? modelField.collectionFactory({
parent: self,
value: value,
reset: reset,
})
: modelField.modelFactory({
parent: self,
value: value,
reset: reset,
}), reset, reparent);
}
// Resolve referentials
if (!ODataModelOptions.isCollection(attr.get())) {
const meta = this.api.optionsForType(modelField.type);
const ref = meta?.resolveReferential(attr.get(), attr, {
resolve: false,
});
if (ref !== null && ref !== undefined) {
Object.assign(self, ref);
}
}
}
else {
changed = attr.set(value, reset, reparent);
}
if (!silent && changed) {
self.events$.trigger(ODataModelEventType.Change, {
attr,
value,
previous: current,
options: { key: modelField.isKey() },
});
}
return changed;
}
_link(self, attr) {
attr.events$.subscribe((event) => {
if (event.canContinueWith(self)) {
if (event.model === attr.get()) {
if (event.type === ODataModelEventType.Change &&
attr.navigation &&
event.options?.key) {
const ref = attr.get().referential(attr);
if (ref !== null && ref !== undefined) {
Object.assign(self, ref);
}
}
}
self.events$.emit(event.push(self, attr));
}
});
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3B0aW9ucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL2FuZ3VsYXItb2RhdGEvc3JjL2xpYi9tb2RlbHMvb3B0aW9ucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFnQixNQUFNLE1BQU0sQ0FBQztBQUNoRCxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDMUMsT0FBTyxFQUNMLGNBQWMsRUFDZCxRQUFRLEVBQ1IsZUFBZSxFQUNmLHNCQUFzQixFQUN0QixjQUFjLEdBQ2YsTUFBTSxjQUFjLENBQUM7QUFDdEIsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLFdBQVcsQ0FBQztBQVF4QyxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLHNCQUFzQixFQUN0QiwrQkFBK0IsRUFDL0IscUJBQXFCLEdBQ3RCLE1BQU0sY0FBYyxDQUFDO0FBT3RCLE9BQU8sRUFBRSxPQUFPLEVBQWlCLE1BQU0sVUFBVSxDQUFDO0FBQ2xELE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sVUFBVSxDQUFDO0FBQzFDLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDL0MsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNyQyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQzdDLE9BQU8sRUFFTCxzQkFBc0IsR0FDdkIsTUFBTSxnQkFBZ0IsQ0FBQztBQUV4QixNQUFNLENBQU4sSUFBWSxtQkFZWDtBQVpELFdBQVksbUJBQW1CO0lBQzdCLHdDQUFpQixDQUFBO0lBQ2pCLHNDQUFlLENBQUE7SUFDZix3Q0FBaUIsQ0FBQTtJQUNqQixvQ0FBYSxDQUFBO0lBQ2IsMENBQW1CLENBQUE7SUFDbkIsa0NBQVcsQ0FBQTtJQUNYLHdDQUFpQixDQUFBO0lBQ2pCLDBDQUFtQixDQUFBO0lBQ25CLDBDQUFtQixDQUFBO0lBQ25CLG9DQUFhLENBQUE7SUFDYix3Q0FBaUIsQ0FBQTtBQUNuQixDQUFDLEVBWlcsbUJBQW1CLEtBQW5CLG1CQUFtQixRQVk5QjtBQUVELE1BQU0sT0FBTyxlQUFlO0lBQzFCLElBQUksQ0FBK0I7SUFDbkMsS0FBSyxDQUFPO0lBQ1osUUFBUSxDQUFPO0lBQ2YsT0FBTyxDQUFPO0lBRWQsWUFDRSxJQUFrQyxFQUNsQyxFQUNFLEtBQUssRUFDTCxVQUFVLEVBQ1YsUUFBUSxFQUNSLEtBQUssRUFDTCxJQUFJLEVBQ0osT0FBTyxFQUNQLE9BQU8sRUFDUCxLQUFLLE1BYUgsRUFBRTtRQUVOLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO1FBQ2pCLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBQ25CLElBQUksQ0FBQyxVQUFVLEdBQUcsVUFBVSxDQUFDO1FBQzdCLElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBQ25CLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxJQUFJO1lBQ3BCO2dCQUNFLENBQUMsSUFBSSxDQUFDLEtBQUssSUFBSSxJQUFJLENBQUMsVUFBVSxDQUVXO2dCQUN6QyxJQUFJLElBQUksSUFBSTthQUNiO1NBQ0YsQ0FBQztRQUNGLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxJQUFJLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO0lBQzlELENBQUM7SUFFRCxLQUFLLENBR0Q7SUFFSixJQUFJLENBQ0YsS0FBOEQsRUFDOUQsSUFBdUM7UUFFdkMsT0FBTyxJQUFJLGVBQWUsQ0FBTSxJQUFJLENBQUMsSUFBSSxFQUFFO1lBQ3pDLEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSyxJQUFJLENBQUMsS0FBSyxZQUFZLFVBQVUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7WUFDdEUsVUFBVSxFQUNSLElBQUksQ0FBQyxVQUFVO2dCQUNmLENBQUMsS0FBSyxZQUFZLGVBQWU7b0JBQy9CLENBQUMsQ0FBRSxLQUErQztvQkFDbEQsQ0FBQyxDQUFDLFNBQVMsQ0FBQztZQUNoQixRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVE7WUFDdkIsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLO1lBQ2pCLE9BQU8sRUFBRTtnQkFDUCxHQUFHLElBQUksQ0FBQyxPQUFPO2dCQUNmLEtBQUssRUFBRSxJQUFJLFlBQVksbUJBQW1CLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUk7YUFDOUQ7WUFDRCxPQUFPLEVBQUUsSUFBSSxDQUFDLE9BQU87WUFDckIsS0FBSyxFQUFFLENBQUMsQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLEVBQUUsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDO1NBQ3RDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRCxPQUFPLENBQVU7SUFDakIsZUFBZTtRQUNiLElBQUksQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDO0lBQ3ZCLENBQUM7SUFFRCxPQUFPLENBQUMsS0FBOEQ7UUFDcEUsT0FBTyxDQUNMLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssS0FBSyxDQUFDO1lBQ3RDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssS0FBSyxDQUMvQyxDQUFDO0lBQ0osQ0FBQztJQUVELGVBQWUsQ0FBQyxJQUF1RDtRQUNyRSxPQUFPLElBQUksQ0FBQyxPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzdDLENBQUM7SUFFRCxJQUFJLElBQUk7UUFDTixPQUFPLElBQUksQ0FBQyxLQUFLO2FBQ2QsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQyxFQUFFLEtBQUssRUFBRSxFQUFFLENBQ3ZCLE9BQU8sSUFBSSxLQUFLLFFBQVE7WUFDdEIsQ0FBQyxDQUFDLElBQUksSUFBSSxHQUFHO1lBQ2IsQ0FBQyxDQUFDLElBQUksWUFBWSxtQkFBbUI7Z0JBQ25DLENBQUMsQ0FBQyxLQUFLLEtBQUssQ0FBQztvQkFDWCxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUk7b0JBQ1gsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksRUFBRTtnQkFDbkIsQ0FBQyxDQUFDLEVBQUUsQ0FDVDthQUNBLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUNkLENBQUM7SUFFRCx1REFBdUQ7SUFDdkQsS0FBSyxDQUFpQjtJQUN0Qiw0Q0FBNEM7SUFDNUMsSUFBSSxZQUFZO1FBQ2QsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3JFLE9BQU8sSUFBSSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUUsSUFBSSxDQUFDLENBQUMsQ0FBcUIsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0lBQ3ZFLENBQUM7SUFFRCw0REFBNEQ7SUFDNUQsVUFBVSxDQUFxQztJQUMvQyxpREFBaUQ7SUFDakQsSUFBSSxpQkFBaUI7UUFDbkIsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLGlCQUFpQixDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQzFFLE9BQU8sSUFBSSxLQUFLLFNBQVM7WUFDdkIsQ0FBQyxDQUFFLElBQUksQ0FBQyxDQUFDLENBQTJDO1lBQ3BELENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDaEIsQ0FBQztDQUNGO0F