@verdant-web/store 3.0.0-next.0 → 3.0.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/dist/bundle/index.js +1 -1
- package/dist/bundle/index.js.map +4 -4
- package/dist/cjs/DocumentManager.d.ts +5 -6
- package/dist/cjs/DocumentManager.js +2 -2
- package/dist/cjs/DocumentManager.js.map +1 -1
- package/dist/cjs/client/Client.d.ts +3 -2
- package/dist/cjs/client/Client.js +1 -1
- package/dist/cjs/client/Client.js.map +1 -1
- package/dist/cjs/entities/Entity.d.ts +106 -171
- package/dist/cjs/entities/Entity.js +558 -383
- package/dist/cjs/entities/Entity.js.map +1 -1
- package/dist/cjs/entities/Entity.test.js.map +1 -0
- package/dist/cjs/entities/{2/EntityCache.d.ts → EntityCache.d.ts} +1 -1
- package/dist/cjs/entities/{2/EntityCache.js → EntityCache.js} +1 -1
- package/dist/cjs/entities/EntityCache.js.map +1 -0
- package/dist/{esm/entities/2 → cjs/entities}/EntityMetadata.d.ts +1 -1
- package/dist/cjs/entities/EntityMetadata.js.map +1 -0
- package/dist/cjs/entities/EntityStore.d.ts +63 -68
- package/dist/cjs/entities/EntityStore.js +296 -424
- package/dist/cjs/entities/EntityStore.js.map +1 -1
- package/dist/{esm/entities/2 → cjs/entities}/OperationBatcher.d.ts +2 -2
- package/dist/cjs/entities/OperationBatcher.js.map +1 -0
- package/dist/cjs/entities/{2/types.js.map → types.js.map} +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/queries/BaseQuery.js +1 -1
- package/dist/cjs/queries/BaseQuery.js.map +1 -1
- package/dist/cjs/queries/CollectionQueries.d.ts +4 -2
- package/dist/cjs/queries/utils.js +1 -1
- package/dist/cjs/queries/utils.js.map +1 -1
- package/dist/esm/DocumentManager.d.ts +5 -6
- package/dist/esm/DocumentManager.js +2 -2
- package/dist/esm/DocumentManager.js.map +1 -1
- package/dist/esm/client/Client.d.ts +3 -2
- package/dist/esm/client/Client.js +1 -1
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/entities/Entity.d.ts +106 -171
- package/dist/esm/entities/Entity.js +559 -383
- package/dist/esm/entities/Entity.js.map +1 -1
- package/dist/esm/entities/Entity.test.js.map +1 -0
- package/dist/esm/entities/{2/EntityCache.d.ts → EntityCache.d.ts} +1 -1
- package/dist/esm/entities/{2/EntityCache.js → EntityCache.js} +1 -1
- package/dist/esm/entities/EntityCache.js.map +1 -0
- package/dist/{cjs/entities/2 → esm/entities}/EntityMetadata.d.ts +1 -1
- package/dist/esm/entities/EntityMetadata.js.map +1 -0
- package/dist/esm/entities/EntityStore.d.ts +63 -68
- package/dist/esm/entities/EntityStore.js +297 -425
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/{cjs/entities/2 → esm/entities}/OperationBatcher.d.ts +2 -2
- package/dist/esm/entities/OperationBatcher.js.map +1 -0
- package/dist/esm/entities/{2/types.js.map → types.js.map} +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/queries/BaseQuery.js +1 -1
- package/dist/esm/queries/BaseQuery.js.map +1 -1
- package/dist/esm/queries/CollectionQueries.d.ts +4 -2
- package/dist/esm/queries/utils.js +1 -1
- package/dist/esm/queries/utils.js.map +1 -1
- package/dist/tsconfig-cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/DocumentManager.ts +7 -3
- package/src/__tests__/batching.test.ts +1 -1
- package/src/client/Client.ts +1 -1
- package/src/entities/{2/Entity.test.ts → Entity.test.ts} +2 -2
- package/src/entities/{2/Entity.ts → Entity.ts} +4 -4
- package/src/entities/{2/EntityCache.ts → EntityCache.ts} +1 -1
- package/src/entities/{2/EntityMetadata.ts → EntityMetadata.ts} +1 -1
- package/src/entities/{2/EntityStore.ts → EntityStore.ts} +12 -8
- package/src/entities/{2/OperationBatcher.ts → OperationBatcher.ts} +2 -2
- package/src/index.ts +2 -2
- package/src/queries/BaseQuery.ts +1 -1
- package/src/queries/CollectionQueries.ts +2 -2
- package/src/queries/utils.ts +1 -1
- package/dist/cjs/entities/2/Entity.d.ts +0 -148
- package/dist/cjs/entities/2/Entity.js +0 -711
- package/dist/cjs/entities/2/Entity.js.map +0 -1
- package/dist/cjs/entities/2/Entity.test.js.map +0 -1
- package/dist/cjs/entities/2/EntityCache.js.map +0 -1
- package/dist/cjs/entities/2/EntityMetadata.js.map +0 -1
- package/dist/cjs/entities/2/EntityStore.d.ts +0 -78
- package/dist/cjs/entities/2/EntityStore.js +0 -352
- package/dist/cjs/entities/2/EntityStore.js.map +0 -1
- package/dist/cjs/entities/2/OperationBatcher.js.map +0 -1
- package/dist/cjs/entities/DocumentFamiliyCache.d.ts +0 -96
- package/dist/cjs/entities/DocumentFamiliyCache.js +0 -287
- package/dist/cjs/entities/DocumentFamiliyCache.js.map +0 -1
- package/dist/cjs/entities/FakeWeakRef.d.ts +0 -11
- package/dist/cjs/entities/FakeWeakRef.js +0 -19
- package/dist/cjs/entities/FakeWeakRef.js.map +0 -1
- package/dist/cjs/indexes.d.ts +0 -3
- package/dist/cjs/indexes.js +0 -20
- package/dist/cjs/indexes.js.map +0 -1
- package/dist/esm/entities/2/Entity.d.ts +0 -148
- package/dist/esm/entities/2/Entity.js +0 -707
- package/dist/esm/entities/2/Entity.js.map +0 -1
- package/dist/esm/entities/2/Entity.test.js.map +0 -1
- package/dist/esm/entities/2/EntityCache.js.map +0 -1
- package/dist/esm/entities/2/EntityMetadata.js.map +0 -1
- package/dist/esm/entities/2/EntityStore.d.ts +0 -78
- package/dist/esm/entities/2/EntityStore.js +0 -348
- package/dist/esm/entities/2/EntityStore.js.map +0 -1
- package/dist/esm/entities/2/OperationBatcher.js.map +0 -1
- package/dist/esm/entities/DocumentFamiliyCache.d.ts +0 -96
- package/dist/esm/entities/DocumentFamiliyCache.js +0 -283
- package/dist/esm/entities/DocumentFamiliyCache.js.map +0 -1
- package/dist/esm/entities/FakeWeakRef.d.ts +0 -11
- package/dist/esm/entities/FakeWeakRef.js +0 -15
- package/dist/esm/entities/FakeWeakRef.js.map +0 -1
- package/dist/esm/indexes.d.ts +0 -3
- package/dist/esm/indexes.js +0 -15
- package/dist/esm/indexes.js.map +0 -1
- package/src/entities/2/NOTES.md +0 -22
- package/src/entities/design.tldr +0 -808
- /package/dist/cjs/entities/{2/Entity.test.d.ts → Entity.test.d.ts} +0 -0
- /package/dist/cjs/entities/{2/Entity.test.js → Entity.test.js} +0 -0
- /package/dist/cjs/entities/{2/EntityMetadata.js → EntityMetadata.js} +0 -0
- /package/dist/cjs/entities/{2/OperationBatcher.js → OperationBatcher.js} +0 -0
- /package/dist/cjs/entities/{2/types.d.ts → types.d.ts} +0 -0
- /package/dist/cjs/entities/{2/types.js → types.js} +0 -0
- /package/dist/esm/entities/{2/Entity.test.d.ts → Entity.test.d.ts} +0 -0
- /package/dist/esm/entities/{2/Entity.test.js → Entity.test.js} +0 -0
- /package/dist/esm/entities/{2/EntityMetadata.js → EntityMetadata.js} +0 -0
- /package/dist/esm/entities/{2/OperationBatcher.js → OperationBatcher.js} +0 -0
- /package/dist/esm/entities/{2/types.d.ts → types.d.ts} +0 -0
- /package/dist/esm/entities/{2/types.js → types.js} +0 -0
- /package/src/entities/{2/types.ts → types.ts} +0 -0
|
@@ -1,185 +1,256 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var _a, _b;
|
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.Entity =
|
|
3
|
+
exports.Entity = void 0;
|
|
5
4
|
const common_1 = require("@verdant-web/common");
|
|
6
5
|
const utils_js_1 = require("../files/utils.js");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
get hasSubscribers() {
|
|
21
|
-
var _c, _d;
|
|
22
|
-
if (this.events.totalSubscriberCount() > 0) {
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
// even if nobody subscribes directly to this entity, if a parent
|
|
26
|
-
// has a deep subscription that counts.
|
|
27
|
-
let parent = (_c = this.parent) === null || _c === void 0 ? void 0 : _c.deref();
|
|
28
|
-
while (parent) {
|
|
29
|
-
if (parent.hasSubscribersToDeepChanges()) {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
parent = (_d = parent.parent) === null || _d === void 0 ? void 0 : _d.deref();
|
|
33
|
-
}
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
get deleted() {
|
|
37
|
-
return this._deleted;
|
|
38
|
-
}
|
|
39
|
-
get value() {
|
|
40
|
-
return this._current;
|
|
41
|
-
}
|
|
42
|
-
get isList() {
|
|
43
|
-
return Array.isArray(this._current);
|
|
44
|
-
}
|
|
45
|
-
get updatedAt() {
|
|
46
|
-
return this._updatedAt;
|
|
47
|
-
}
|
|
48
|
-
get deepUpdatedAt() {
|
|
49
|
-
if (this.cachedDeepUpdatedAt)
|
|
50
|
-
return this.cachedDeepUpdatedAt;
|
|
51
|
-
// iterate over all children and take the latest timestamp
|
|
52
|
-
let latest = this._updatedAt;
|
|
53
|
-
if (this.isList) {
|
|
54
|
-
this.forEach((child) => {
|
|
55
|
-
if (child instanceof Entity) {
|
|
56
|
-
const childTimestamp = child.deepUpdatedAt;
|
|
57
|
-
if (childTimestamp && (!latest || childTimestamp > latest)) {
|
|
58
|
-
latest = childTimestamp;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
this.values().forEach((child) => {
|
|
65
|
-
if (child instanceof Entity) {
|
|
66
|
-
const childTimestamp = child.deepUpdatedAt;
|
|
67
|
-
if (childTimestamp && (!latest || childTimestamp > latest)) {
|
|
68
|
-
latest = childTimestamp;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
this.cachedDeepUpdatedAt = latest;
|
|
74
|
-
return latest;
|
|
75
|
-
}
|
|
76
|
-
get uid() {
|
|
77
|
-
return this.oid;
|
|
78
|
-
}
|
|
79
|
-
constructor({ oid, store, fieldSchema, cache, parent, onAllUnsubscribed, readonlyKeys = [], fieldPath = [], }) {
|
|
80
|
-
// if current is null, the entity was deleted.
|
|
81
|
-
this._current = null;
|
|
82
|
-
this._deleted = false;
|
|
83
|
-
this.cachedSnapshot = null;
|
|
84
|
-
this.cachedDestructure = null;
|
|
6
|
+
const index_js_1 = require("../index.js");
|
|
7
|
+
const EntityCache_js_1 = require("./EntityCache.js");
|
|
8
|
+
class Entity extends common_1.EventSubscriber {
|
|
9
|
+
constructor({ oid, schema, entityFamily: childCache, parent, ctx, metadataFamily, readonlyKeys, files, patchCreator, events, }) {
|
|
10
|
+
super();
|
|
11
|
+
this.fieldPath = [];
|
|
12
|
+
// an internal representation of this Entity.
|
|
13
|
+
// if present, this is the cached, known value. If null,
|
|
14
|
+
// the entity is deleted. If undefined, we need to recompute
|
|
15
|
+
// the view.
|
|
16
|
+
this._viewData = undefined;
|
|
17
|
+
this.validationError = undefined;
|
|
85
18
|
this.cachedDeepUpdatedAt = null;
|
|
86
|
-
|
|
87
|
-
this
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.cachedDestructure = null;
|
|
93
|
-
this._updatedAt = lastTimestamp ? lastTimestamp : null;
|
|
94
|
-
this.cachedDeepUpdatedAt = null;
|
|
95
|
-
if (this._deleted) {
|
|
96
|
-
this.events.emit('delete', info);
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
this.events.emit('change', info);
|
|
100
|
-
this[exports.DEEP_CHANGE](this, info);
|
|
101
|
-
}
|
|
102
|
-
if (restored) {
|
|
103
|
-
this.cachedSnapshot = null;
|
|
104
|
-
this.events.emit('restore', info);
|
|
19
|
+
// only used for root entities to track delete/restore state.
|
|
20
|
+
this.wasDeletedLastChange = false;
|
|
21
|
+
this.cachedView = undefined;
|
|
22
|
+
this.onAdd = (_store, data) => {
|
|
23
|
+
if (data.oid === this.oid) {
|
|
24
|
+
this.addConfirmedData(data);
|
|
105
25
|
}
|
|
106
26
|
};
|
|
107
|
-
this
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.cachedDeepUpdatedAt = null;
|
|
111
|
-
this.events.emit('changeDeep', source, info);
|
|
112
|
-
const parent = (_c = this.parent) === null || _c === void 0 ? void 0 : _c.deref();
|
|
113
|
-
if (parent) {
|
|
114
|
-
parent[exports.DEEP_CHANGE](source, info);
|
|
27
|
+
this.onReplace = (_store, data) => {
|
|
28
|
+
if (data.oid === this.oid) {
|
|
29
|
+
this.replaceAllData(data);
|
|
115
30
|
}
|
|
116
31
|
};
|
|
117
|
-
this.
|
|
118
|
-
|
|
119
|
-
|
|
32
|
+
this.onResetAll = () => {
|
|
33
|
+
this.resetAllData();
|
|
34
|
+
};
|
|
35
|
+
this.childIsNull = (child) => {
|
|
36
|
+
if (child instanceof Entity) {
|
|
37
|
+
const childView = child.view;
|
|
38
|
+
return childView === null || childView === undefined;
|
|
120
39
|
}
|
|
121
|
-
|
|
122
|
-
|
|
40
|
+
return child === null || child === undefined;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Pruning - when entities have invalid children, we 'prune' that
|
|
44
|
+
* data up to the nearest prunable point - a nullable field,
|
|
45
|
+
* or a list.
|
|
46
|
+
*/
|
|
47
|
+
this.validate = (0, common_1.memoByKeys)(() => {
|
|
48
|
+
var _a;
|
|
49
|
+
this.validationError =
|
|
50
|
+
(_a = (0, common_1.validateEntityField)({
|
|
51
|
+
field: this.schema,
|
|
52
|
+
value: this.rawView,
|
|
53
|
+
fieldPath: this.fieldPath,
|
|
54
|
+
depth: 1,
|
|
55
|
+
})) !== null && _a !== void 0 ? _a : undefined;
|
|
56
|
+
return this.validationError;
|
|
57
|
+
}, () => [this.viewData]);
|
|
58
|
+
this.viewWithMappedChildren = (mapper) => {
|
|
59
|
+
const view = this.view;
|
|
60
|
+
if (!view) {
|
|
61
|
+
return null;
|
|
123
62
|
}
|
|
124
|
-
|
|
125
|
-
|
|
63
|
+
if (Array.isArray(view)) {
|
|
64
|
+
const mapped = view.map((value) => {
|
|
65
|
+
if (value instanceof Entity || value instanceof index_js_1.EntityFile) {
|
|
66
|
+
return mapper(value);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
(0, common_1.assignOid)(mapped, this.oid);
|
|
73
|
+
return mapped;
|
|
126
74
|
}
|
|
127
|
-
else
|
|
128
|
-
|
|
75
|
+
else {
|
|
76
|
+
const mapped = Object.entries(view).reduce((acc, [key, value]) => {
|
|
77
|
+
if (value instanceof Entity || value instanceof index_js_1.EntityFile) {
|
|
78
|
+
acc[key] = mapper(value);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
acc[key] = value;
|
|
82
|
+
}
|
|
83
|
+
return acc;
|
|
84
|
+
}, {});
|
|
85
|
+
(0, common_1.assignOid)(mapped, this.oid);
|
|
86
|
+
return mapped;
|
|
129
87
|
}
|
|
130
|
-
throw new Error('Invalid field schema');
|
|
131
88
|
};
|
|
132
|
-
|
|
133
|
-
|
|
89
|
+
/**
|
|
90
|
+
* A current snapshot of this Entity's data, including nested
|
|
91
|
+
* Entities.
|
|
92
|
+
*/
|
|
93
|
+
this.getSnapshot = () => {
|
|
94
|
+
return this.viewWithMappedChildren((child) => child.getSnapshot());
|
|
95
|
+
};
|
|
96
|
+
// change management methods (internal use only)
|
|
97
|
+
this.addPendingOperations = (operations) => {
|
|
98
|
+
this.ctx.log('debug', 'Entity: adding pending operations', this.oid);
|
|
99
|
+
const changes = this.metadataFamily.addPendingData(operations);
|
|
100
|
+
for (const change of changes) {
|
|
101
|
+
this.change(change);
|
|
102
|
+
}
|
|
134
103
|
};
|
|
135
|
-
this.
|
|
136
|
-
|
|
137
|
-
|
|
104
|
+
this.addConfirmedData = (data) => {
|
|
105
|
+
this.ctx.log('debug', 'Entity: adding confirmed data', this.oid);
|
|
106
|
+
const changes = this.metadataFamily.addConfirmedData(data);
|
|
107
|
+
for (const change of changes) {
|
|
108
|
+
this.change(change);
|
|
109
|
+
}
|
|
138
110
|
};
|
|
139
|
-
this.
|
|
140
|
-
this.
|
|
111
|
+
this.replaceAllData = (data) => {
|
|
112
|
+
this.ctx.log('debug', 'Entity: replacing all data', this.oid);
|
|
113
|
+
const changes = this.metadataFamily.replaceAllData(data);
|
|
114
|
+
for (const change of changes) {
|
|
115
|
+
this.change(change);
|
|
116
|
+
}
|
|
141
117
|
};
|
|
142
|
-
this.
|
|
143
|
-
|
|
144
|
-
|
|
118
|
+
this.resetAllData = () => {
|
|
119
|
+
this.ctx.log('debug', 'Entity: resetting all data', this.oid);
|
|
120
|
+
this.cachedDeepUpdatedAt = null;
|
|
121
|
+
this.cachedView = undefined;
|
|
122
|
+
this._viewData = undefined;
|
|
123
|
+
const changes = this.metadataFamily.replaceAllData({});
|
|
124
|
+
for (const change of changes) {
|
|
125
|
+
this.change(change);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
this.change = (ev) => {
|
|
129
|
+
if (ev.oid === this.oid) {
|
|
130
|
+
// reset cached view
|
|
131
|
+
this._viewData = undefined;
|
|
132
|
+
this.cachedView = undefined;
|
|
133
|
+
// chain deepChanges to parents
|
|
134
|
+
this.deepChange(this, ev);
|
|
135
|
+
// emit the change, it's for us
|
|
136
|
+
this.ctx.log('Emitting change event', this.oid);
|
|
137
|
+
this.emit('change', { isLocal: ev.isLocal });
|
|
138
|
+
// for root entities, we need to go ahead and decide if we're
|
|
139
|
+
// deleted or not - so queries can exclude us if we are.
|
|
140
|
+
if (!this.parent) {
|
|
141
|
+
// newly deleted - emit event
|
|
142
|
+
if (this.deleted && !this.wasDeletedLastChange) {
|
|
143
|
+
this.ctx.log('debug', 'Entity deleted', this.oid);
|
|
144
|
+
this.emit('delete', { isLocal: ev.isLocal });
|
|
145
|
+
this.wasDeletedLastChange = true;
|
|
146
|
+
}
|
|
147
|
+
else if (!this.deleted && this.wasDeletedLastChange) {
|
|
148
|
+
this.ctx.log('debug', 'Entity restored', this.oid);
|
|
149
|
+
// newly restored - emit event
|
|
150
|
+
this.emit('restore', { isLocal: ev.isLocal });
|
|
151
|
+
this.wasDeletedLastChange = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// forward it to the correct family member. if none exists
|
|
157
|
+
// in cache, no one will hear it anyways.
|
|
158
|
+
const other = this.entityFamily.getCached(ev.oid);
|
|
159
|
+
if (other && other instanceof Entity) {
|
|
160
|
+
other.change(ev);
|
|
161
|
+
}
|
|
145
162
|
}
|
|
146
|
-
return (0, common_1.cloneDeep)(this._current);
|
|
147
163
|
};
|
|
148
|
-
this.
|
|
149
|
-
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
164
|
+
this.deepChange = (target, ev) => {
|
|
165
|
+
var _a;
|
|
166
|
+
// reset cached deep updated at timestamp; either this
|
|
167
|
+
// entity or children have changed
|
|
168
|
+
this.cachedDeepUpdatedAt = null;
|
|
169
|
+
// reset this flag to recompute snapshot data - children
|
|
170
|
+
// or self has changed. new pruning needs to happen.
|
|
171
|
+
this.cachedView = undefined;
|
|
172
|
+
this.ctx.log('debug', 'Deep change detected at', this.oid, 'reset cached view');
|
|
173
|
+
this.ctx.log('debug', 'Emitting deep change event', this.oid);
|
|
174
|
+
this.emit('changeDeep', target, ev);
|
|
175
|
+
(_a = this.parent) === null || _a === void 0 ? void 0 : _a.deepChange(target, ev);
|
|
176
|
+
};
|
|
177
|
+
this.getChild = (key, oid) => {
|
|
178
|
+
const schema = (0, common_1.getChildFieldSchema)(this.schema, key);
|
|
179
|
+
if (!schema) {
|
|
180
|
+
throw new Error(`No schema for key ${String(key)} in ${JSON.stringify(this.schema)}`);
|
|
181
|
+
}
|
|
182
|
+
return this.entityFamily.get({
|
|
154
183
|
oid,
|
|
155
|
-
|
|
184
|
+
schema,
|
|
185
|
+
entityFamily: this.entityFamily,
|
|
186
|
+
metadataFamily: this.metadataFamily,
|
|
156
187
|
parent: this,
|
|
157
|
-
|
|
188
|
+
ctx: this.ctx,
|
|
189
|
+
files: this.files,
|
|
190
|
+
fieldPath: [...this.fieldPath, key],
|
|
191
|
+
patchCreator: this.patchCreator,
|
|
192
|
+
events: this.events,
|
|
158
193
|
});
|
|
159
194
|
};
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
195
|
+
// generic entity methods
|
|
196
|
+
/**
|
|
197
|
+
* Gets a value from this Entity. If the value
|
|
198
|
+
* is an object, it will be wrapped in another
|
|
199
|
+
* Entity.
|
|
200
|
+
*/
|
|
201
|
+
this.get = (key) => {
|
|
202
|
+
assertNotSymbol(key);
|
|
203
|
+
const view = this.rawView;
|
|
204
|
+
if (!view) {
|
|
205
|
+
throw new Error(`Cannot access data at key ${key} on deleted entity ${this.oid}`);
|
|
206
|
+
}
|
|
207
|
+
const child = view[key];
|
|
208
|
+
const schema = (0, common_1.getChildFieldSchema)(this.schema, key);
|
|
209
|
+
if (!schema) {
|
|
210
|
+
throw new Error(`No schema for key ${String(key)} in ${JSON.stringify(this.schema)}`);
|
|
211
|
+
}
|
|
212
|
+
if ((0, common_1.isRef)(child)) {
|
|
213
|
+
if ((0, common_1.isFileRef)(child)) {
|
|
214
|
+
if (schema.type !== 'file') {
|
|
215
|
+
throw new Error(`Expected file schema for key ${String(key)}, got ${schema.type}`);
|
|
216
|
+
}
|
|
217
|
+
const file = this.files.get(child.id, {
|
|
218
|
+
downloadRemote: !!schema.downloadRemote,
|
|
219
|
+
});
|
|
220
|
+
// FIXME: this seems bad and inconsistent
|
|
172
221
|
file.subscribe('change', () => {
|
|
173
|
-
this
|
|
174
|
-
isLocal: false,
|
|
175
|
-
});
|
|
222
|
+
this.deepChange(this, { isLocal: false, oid: this.oid });
|
|
176
223
|
});
|
|
177
224
|
return file;
|
|
178
225
|
}
|
|
226
|
+
else {
|
|
227
|
+
return this.getChild(key, child.id);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
// prune invalid primitive fields
|
|
232
|
+
if ((0, common_1.validateEntityField)({
|
|
233
|
+
field: schema,
|
|
234
|
+
value: child,
|
|
235
|
+
fieldPath: [...this.fieldPath, key],
|
|
236
|
+
depth: 1,
|
|
237
|
+
requireDefaults: true,
|
|
238
|
+
})) {
|
|
239
|
+
if ((0, common_1.hasDefault)(schema)) {
|
|
240
|
+
return (0, common_1.getDefault)(schema);
|
|
241
|
+
}
|
|
242
|
+
if ((0, common_1.isNullable)(schema)) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
return child;
|
|
179
248
|
}
|
|
180
|
-
return value;
|
|
181
249
|
};
|
|
182
250
|
this.processInputValue = (value, key) => {
|
|
251
|
+
if (this.readonlyKeys.includes(key)) {
|
|
252
|
+
throw new Error(`Cannot set readonly key ${key.toString()}`);
|
|
253
|
+
}
|
|
183
254
|
// disassociate incoming OIDs on values and generally break object
|
|
184
255
|
// references. cloning doesn't work on files so those are
|
|
185
256
|
// filtered out.
|
|
@@ -194,331 +265,427 @@ class Entity {
|
|
|
194
265
|
// referenced in multiple entities, which could mean introduction
|
|
195
266
|
// of foreign OIDs, or one object being assigned different OIDs
|
|
196
267
|
// with unexpected results.
|
|
197
|
-
if (!(
|
|
268
|
+
if (!(0, utils_js_1.isFile)(value)) {
|
|
198
269
|
value = (0, common_1.cloneDeep)(value, false);
|
|
199
270
|
}
|
|
200
|
-
const fieldSchema =
|
|
271
|
+
const fieldSchema = (0, common_1.getChildFieldSchema)(this.schema, key);
|
|
201
272
|
if (fieldSchema) {
|
|
202
273
|
(0, common_1.traverseCollectionFieldsAndApplyDefaults)(value, fieldSchema);
|
|
274
|
+
const validationError = (0, common_1.validateEntityField)({
|
|
275
|
+
field: fieldSchema,
|
|
276
|
+
value,
|
|
277
|
+
fieldPath: [...this.fieldPath, key],
|
|
278
|
+
});
|
|
279
|
+
if (validationError) {
|
|
280
|
+
// TODO: is it a good idea to throw an error here? a runtime error won't be that helpful,
|
|
281
|
+
// but also we don't really want invalid data supplied.
|
|
282
|
+
throw new Error(validationError.message);
|
|
283
|
+
}
|
|
203
284
|
}
|
|
204
|
-
|
|
205
|
-
...this.fieldPath,
|
|
206
|
-
key,
|
|
207
|
-
]);
|
|
208
|
-
if (validationError) {
|
|
209
|
-
// TODO: is it a good idea to throw an error here? a runtime error won't be that helpful,
|
|
210
|
-
// but also we don't really want invalid data supplied.
|
|
211
|
-
throw new Error(validationError);
|
|
212
|
-
}
|
|
213
|
-
return (0, utils_js_1.processValueFiles)(value, this.store.addFile);
|
|
214
|
-
};
|
|
215
|
-
this.get = (key) => {
|
|
216
|
-
if (this.value === undefined || this.value === null) {
|
|
217
|
-
throw new Error('Cannot access deleted entity');
|
|
218
|
-
}
|
|
219
|
-
const value = this.value[key];
|
|
220
|
-
return this.wrapValue(value, key);
|
|
285
|
+
return (0, utils_js_1.processValueFiles)(value, this.files.add);
|
|
221
286
|
};
|
|
222
|
-
this.
|
|
223
|
-
if (this.
|
|
224
|
-
|
|
287
|
+
this.getDeleteMode = (key) => {
|
|
288
|
+
if (this.readonlyKeys.includes(key)) {
|
|
289
|
+
return false;
|
|
225
290
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (Array.isArray(this.value)) {
|
|
230
|
-
result = this.value.map((value, index) => this.wrapValue(value, index));
|
|
291
|
+
// any is always deletable, and map values
|
|
292
|
+
if (this.schema.type === 'any' || this.schema.type === 'map') {
|
|
293
|
+
return 'delete';
|
|
231
294
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
295
|
+
if (this.schema.type === 'object') {
|
|
296
|
+
const property = this.schema.properties[key];
|
|
297
|
+
if (!property) {
|
|
298
|
+
// huh, the property doesn't exist. it's ok to
|
|
299
|
+
// remove I suppose.
|
|
300
|
+
return 'delete';
|
|
236
301
|
}
|
|
302
|
+
if (property.type === 'any')
|
|
303
|
+
return 'delete';
|
|
304
|
+
// map can't be nullable. should it be?
|
|
305
|
+
if (property.type === 'map')
|
|
306
|
+
return false;
|
|
307
|
+
if (property.nullable)
|
|
308
|
+
return 'null';
|
|
237
309
|
}
|
|
238
|
-
|
|
239
|
-
return
|
|
310
|
+
// no other types are deletable
|
|
311
|
+
return false;
|
|
240
312
|
};
|
|
241
313
|
/**
|
|
242
|
-
* Returns
|
|
243
|
-
*
|
|
314
|
+
* Returns the referent value of an item in the list, used for
|
|
315
|
+
* operations which act on items. if the item is an object,
|
|
316
|
+
* it will attempt to create an OID reference to it. If it
|
|
317
|
+
* is a primitive, it will return the primitive.
|
|
244
318
|
*/
|
|
245
|
-
this.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
|
-
if (this.deleted) {
|
|
251
|
-
return null;
|
|
319
|
+
this.getItemRefValue = (item) => {
|
|
320
|
+
if (item instanceof Entity) {
|
|
321
|
+
return (0, common_1.createRef)(item.oid);
|
|
252
322
|
}
|
|
253
|
-
if (
|
|
254
|
-
return
|
|
323
|
+
if (item instanceof index_js_1.EntityFile) {
|
|
324
|
+
return (0, common_1.createFileRef)(item.id);
|
|
255
325
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
else if ((0, common_1.isFileRef)(item)) {
|
|
264
|
-
return this.getFileSnapshot(item);
|
|
265
|
-
}
|
|
266
|
-
return item;
|
|
267
|
-
});
|
|
326
|
+
if (typeof item === 'object') {
|
|
327
|
+
const itemOid = (0, common_1.maybeGetOid)(item);
|
|
328
|
+
if (!itemOid || !this.entityFamily.has(itemOid)) {
|
|
329
|
+
throw new Error(`Cannot move object ${JSON.stringify(item)} which does not exist in this list`);
|
|
330
|
+
}
|
|
331
|
+
return (0, common_1.createRef)(itemOid);
|
|
268
332
|
}
|
|
269
333
|
else {
|
|
270
|
-
|
|
271
|
-
for (const [key, value] of Object.entries(snapshot)) {
|
|
272
|
-
if ((0, common_1.isObjectRef)(value)) {
|
|
273
|
-
snapshot[key] = (_c = this.getSubObject(value.id, key)) === null || _c === void 0 ? void 0 : _c.getSnapshot();
|
|
274
|
-
}
|
|
275
|
-
else if ((0, common_1.isFileRef)(value)) {
|
|
276
|
-
snapshot[key] = this.getFileSnapshot(value);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
334
|
+
return item;
|
|
279
335
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
336
|
+
};
|
|
337
|
+
this.set = (key, value) => {
|
|
338
|
+
assertNotSymbol(key);
|
|
339
|
+
this.addPendingOperations(this.patchCreator.createSet(this.oid, key, this.processInputValue(value, key)));
|
|
283
340
|
};
|
|
284
341
|
/**
|
|
285
|
-
*
|
|
342
|
+
* Returns a destructured version of this Entity, where child
|
|
343
|
+
* Entities are accessible at their respective keys.
|
|
286
344
|
*/
|
|
287
|
-
this.
|
|
288
|
-
return
|
|
289
|
-
};
|
|
290
|
-
this.entries = () => {
|
|
291
|
-
return Object.entries(this.getAll());
|
|
292
|
-
};
|
|
293
|
-
this.values = () => {
|
|
294
|
-
return Object.values(this.getAll());
|
|
295
|
-
};
|
|
296
|
-
this.set = (key, value) => {
|
|
297
|
-
if (this.readonlyKeys.includes(key)) {
|
|
298
|
-
throw new Error(`Cannot set readonly key ${key.toString()}`);
|
|
299
|
-
}
|
|
300
|
-
this.addPatches(this.store.patchCreator.createSet(this.oid, key, this.processInputValue(value, key)));
|
|
345
|
+
this.getAll = () => {
|
|
346
|
+
return this.view;
|
|
301
347
|
};
|
|
302
348
|
this.delete = (key) => {
|
|
303
|
-
if (
|
|
304
|
-
|
|
349
|
+
if (this.isList) {
|
|
350
|
+
assertNumber(key);
|
|
351
|
+
this.addPendingOperations(this.patchCreator.createListDelete(this.oid, key));
|
|
305
352
|
}
|
|
306
353
|
else {
|
|
307
|
-
// the key must be deletable - i.e. optional in the schema
|
|
354
|
+
// the key must be deletable - i.e. optional in the schema.
|
|
308
355
|
const deleteMode = this.getDeleteMode(key);
|
|
309
356
|
if (!deleteMode) {
|
|
310
|
-
throw new Error(`Cannot delete key ${key} - the property is not marked as optional in the schema
|
|
357
|
+
throw new Error(`Cannot delete key ${key.toString()} - the property is not marked as optional in the schema.`);
|
|
311
358
|
}
|
|
312
359
|
if (deleteMode === 'delete') {
|
|
313
|
-
this.
|
|
360
|
+
this.addPendingOperations(this.patchCreator.createRemove(this.oid, key));
|
|
314
361
|
}
|
|
315
362
|
else {
|
|
316
|
-
this.
|
|
363
|
+
this.addPendingOperations(this.patchCreator.createSet(this.oid, key, null));
|
|
317
364
|
}
|
|
318
365
|
}
|
|
319
366
|
};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (property.type === 'any')
|
|
336
|
-
return 'delete';
|
|
337
|
-
// map can't be nullable
|
|
338
|
-
// TODO: should it be?
|
|
339
|
-
if (property.type === 'map')
|
|
340
|
-
return false;
|
|
341
|
-
// nullable properties can only be set null
|
|
342
|
-
if (property.nullable)
|
|
343
|
-
return 'null';
|
|
344
|
-
}
|
|
345
|
-
// no other parent objects support deleting
|
|
346
|
-
return false;
|
|
367
|
+
// object entity methods
|
|
368
|
+
this.keys = () => {
|
|
369
|
+
if (!this.view)
|
|
370
|
+
return [];
|
|
371
|
+
return Object.keys(this.view);
|
|
372
|
+
};
|
|
373
|
+
this.entries = () => {
|
|
374
|
+
if (!this.view)
|
|
375
|
+
return [];
|
|
376
|
+
return Object.entries(this.view);
|
|
377
|
+
};
|
|
378
|
+
this.values = () => {
|
|
379
|
+
if (!this.view)
|
|
380
|
+
return [];
|
|
381
|
+
return Object.values(this.view);
|
|
347
382
|
};
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
this.update = (value, { replaceSubObjects = false, merge = true, } = {
|
|
351
|
-
/**
|
|
352
|
-
* If true, merged sub-objects will be replaced entirely if there's
|
|
353
|
-
* ambiguity about their identity.
|
|
354
|
-
*/
|
|
355
|
-
replaceSubObjects: false,
|
|
356
|
-
/**
|
|
357
|
-
* If false, omitted keys will erase their respective fields.
|
|
358
|
-
*/
|
|
359
|
-
merge: true,
|
|
360
|
-
}) => {
|
|
361
|
-
if (!merge &&
|
|
362
|
-
this.fieldSchema.type !== 'any' &&
|
|
363
|
-
this.fieldSchema.type !== 'map') {
|
|
383
|
+
this.update = (data, { merge = true, replaceSubObjects = false, } = {}) => {
|
|
384
|
+
if (!merge && this.schema.type !== 'any' && this.schema.type !== 'map') {
|
|
364
385
|
throw new Error('Cannot use .update without merge if the field has a strict schema type. merge: false is only available on "any" or "map" types.');
|
|
365
386
|
}
|
|
366
|
-
|
|
387
|
+
const changes = {};
|
|
388
|
+
(0, common_1.assignOid)(changes, this.oid);
|
|
389
|
+
for (const [key, field] of Object.entries(data)) {
|
|
367
390
|
if (this.readonlyKeys.includes(key)) {
|
|
368
391
|
throw new Error(`Cannot set readonly key ${key.toString()}`);
|
|
369
392
|
}
|
|
370
|
-
const fieldSchema =
|
|
393
|
+
const fieldSchema = (0, common_1.getChildFieldSchema)(this.schema, key);
|
|
371
394
|
if (fieldSchema) {
|
|
372
395
|
(0, common_1.traverseCollectionFieldsAndApplyDefaults)(field, fieldSchema);
|
|
373
396
|
}
|
|
397
|
+
changes[key] = this.processInputValue(field, key);
|
|
374
398
|
}
|
|
375
|
-
|
|
376
|
-
this.addPatches(this.store.patchCreator.createDiff(this.getSnapshot(), (0, common_1.assignOid)(withoutFiles, this.oid), {
|
|
399
|
+
this.addPendingOperations(this.patchCreator.createDiff(this.getSnapshot(), changes, {
|
|
377
400
|
mergeUnknownObjects: !replaceSubObjects,
|
|
378
401
|
defaultUndefined: merge,
|
|
379
402
|
}));
|
|
380
403
|
};
|
|
381
|
-
/**
|
|
382
|
-
* List methods
|
|
383
|
-
*/
|
|
384
|
-
/**
|
|
385
|
-
* Returns the referent value of an item in the list, used for
|
|
386
|
-
* operations which act on items. if the item is an object,
|
|
387
|
-
* it will attempt to create an OID reference to it. If it
|
|
388
|
-
* is a primitive, it will return the primitive.
|
|
389
|
-
*/
|
|
390
|
-
this.getItemRefValue = (item) => {
|
|
391
|
-
if (typeof item === 'object') {
|
|
392
|
-
const itemOid = (0, common_1.maybeGetOid)(item);
|
|
393
|
-
if (!itemOid || !this.cache.hasOid(itemOid)) {
|
|
394
|
-
throw new Error(`Cannot move object ${JSON.stringify(item)} which does not exist in this list`);
|
|
395
|
-
}
|
|
396
|
-
return itemOid;
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
return item;
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
404
|
this.push = (value) => {
|
|
403
|
-
this.
|
|
405
|
+
this.addPendingOperations(this.patchCreator.createListPush(this.oid, this.processInputValue(value, this.view.length)));
|
|
404
406
|
};
|
|
405
407
|
this.insert = (index, value) => {
|
|
406
|
-
this.
|
|
408
|
+
this.addPendingOperations(this.patchCreator.createListInsert(this.oid, index, this.processInputValue(value, index)));
|
|
407
409
|
};
|
|
408
410
|
this.move = (from, to) => {
|
|
409
|
-
this.
|
|
411
|
+
this.addPendingOperations(this.patchCreator.createListMoveByIndex(this.oid, from, to));
|
|
410
412
|
};
|
|
411
413
|
this.moveItem = (item, to) => {
|
|
412
414
|
const itemRef = this.getItemRefValue(item);
|
|
413
|
-
if ((0, common_1.
|
|
414
|
-
this.
|
|
415
|
+
if ((0, common_1.isRef)(itemRef)) {
|
|
416
|
+
this.addPendingOperations(this.patchCreator.createListMoveByRef(this.oid, itemRef, to));
|
|
415
417
|
}
|
|
416
418
|
else {
|
|
417
|
-
const index = this.
|
|
418
|
-
|
|
419
|
+
const index = this.view.indexOf(item);
|
|
420
|
+
if (index === -1) {
|
|
421
|
+
throw new Error(`Cannot move item ${JSON.stringify(item)} which does not exist in this list`);
|
|
422
|
+
}
|
|
423
|
+
this.move(index, to);
|
|
419
424
|
}
|
|
420
425
|
};
|
|
426
|
+
this.add = (value) => {
|
|
427
|
+
this.addPendingOperations(this.patchCreator.createListAdd(this.oid, this.processInputValue(value, this.view.length)));
|
|
428
|
+
};
|
|
421
429
|
this.removeAll = (item) => {
|
|
422
|
-
this.
|
|
430
|
+
this.addPendingOperations(this.patchCreator.createListRemove(this.oid, this.getItemRefValue(item)));
|
|
423
431
|
};
|
|
424
432
|
this.removeFirst = (item) => {
|
|
425
|
-
this.
|
|
433
|
+
this.addPendingOperations(this.patchCreator.createListRemove(this.oid, this.getItemRefValue(item), 'first'));
|
|
426
434
|
};
|
|
427
435
|
this.removeLast = (item) => {
|
|
428
|
-
this.
|
|
429
|
-
};
|
|
430
|
-
this.add = (item) => {
|
|
431
|
-
this.addPatches(this.store.patchCreator.createListAdd(this.oid, this.processInputValue(item, this.value.length)));
|
|
432
|
-
};
|
|
433
|
-
this.has = (item) => {
|
|
434
|
-
if (typeof item === 'object') {
|
|
435
|
-
return this.value.some((val) => {
|
|
436
|
-
if ((0, common_1.isObjectRef)(val))
|
|
437
|
-
return val.id === (0, common_1.maybeGetOid)(item);
|
|
438
|
-
// Sets of files don't work right now, there's no way to compare them
|
|
439
|
-
// effectively.
|
|
440
|
-
if ((0, common_1.isFileRef)(val))
|
|
441
|
-
return false;
|
|
442
|
-
return false;
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
return this.value.includes(item);
|
|
446
|
-
};
|
|
447
|
-
// additional access methods
|
|
448
|
-
this.getAsWrapped = () => {
|
|
449
|
-
if (!this.isList)
|
|
450
|
-
throw new Error('Cannot map items of a non-list');
|
|
451
|
-
return this.value.map(this.wrapValue);
|
|
436
|
+
this.addPendingOperations(this.patchCreator.createListRemove(this.oid, this.getItemRefValue(item), 'last'));
|
|
452
437
|
};
|
|
453
438
|
this.map = (callback) => {
|
|
454
|
-
return this.
|
|
439
|
+
return this.view.map(callback);
|
|
455
440
|
};
|
|
456
441
|
this.filter = (callback) => {
|
|
457
|
-
return this.
|
|
458
|
-
|
|
459
|
-
|
|
442
|
+
return this.view.filter(callback);
|
|
443
|
+
};
|
|
444
|
+
this.has = (value) => {
|
|
445
|
+
if (!this.isList) {
|
|
446
|
+
throw new Error('has() is only available on list entities');
|
|
447
|
+
}
|
|
448
|
+
const itemRef = this.getItemRefValue(value);
|
|
449
|
+
if ((0, common_1.isRef)(itemRef)) {
|
|
450
|
+
return this.view.some((item) => {
|
|
451
|
+
if ((0, common_1.isRef)(item)) {
|
|
452
|
+
return (0, common_1.compareRefs)(item, itemRef);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
return this.view.includes(value);
|
|
458
|
+
}
|
|
460
459
|
};
|
|
461
460
|
this.forEach = (callback) => {
|
|
462
|
-
this.
|
|
461
|
+
this.view.forEach(callback);
|
|
463
462
|
};
|
|
464
463
|
this.some = (predicate) => {
|
|
465
|
-
return this.
|
|
464
|
+
return this.view.some(predicate);
|
|
466
465
|
};
|
|
467
466
|
this.every = (predicate) => {
|
|
468
|
-
return this.
|
|
467
|
+
return this.view.every(predicate);
|
|
469
468
|
};
|
|
470
469
|
this.find = (predicate) => {
|
|
471
|
-
return this.
|
|
470
|
+
return this.view.find(predicate);
|
|
472
471
|
};
|
|
473
|
-
this.includes =
|
|
474
|
-
|
|
472
|
+
this.includes = this.has;
|
|
473
|
+
// TODO: make these escape hatches unnecessary
|
|
474
|
+
this.__getViewData__ = (oid, type) => {
|
|
475
|
+
return this.metadataFamily.get(oid).computeView(type === 'confirmed');
|
|
475
476
|
};
|
|
477
|
+
this.__getFamilyOids__ = () => this.metadataFamily.getAllOids();
|
|
478
|
+
(0, common_1.assert)(!!oid, 'oid is required');
|
|
476
479
|
this.oid = oid;
|
|
477
|
-
|
|
478
|
-
this.
|
|
479
|
-
this.
|
|
480
|
-
this.
|
|
481
|
-
this.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
this.
|
|
487
|
-
this.
|
|
488
|
-
this.
|
|
489
|
-
this.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (this.oid.includes('.') && !this.parent) {
|
|
496
|
-
throw new Error('Parent must be provided for sub entities');
|
|
480
|
+
this.readonlyKeys = readonlyKeys || [];
|
|
481
|
+
this.ctx = ctx;
|
|
482
|
+
this.files = files;
|
|
483
|
+
this.schema = schema;
|
|
484
|
+
this.entityFamily =
|
|
485
|
+
childCache ||
|
|
486
|
+
new EntityCache_js_1.EntityCache({
|
|
487
|
+
initial: [this],
|
|
488
|
+
});
|
|
489
|
+
this.patchCreator = patchCreator;
|
|
490
|
+
this.metadataFamily = metadataFamily;
|
|
491
|
+
this.events = events;
|
|
492
|
+
this.parent = parent;
|
|
493
|
+
// TODO: should any but the root entity be listening to these?
|
|
494
|
+
if (!this.parent) {
|
|
495
|
+
events.add.attach(this.onAdd);
|
|
496
|
+
events.replace.attach(this.onReplace);
|
|
497
|
+
events.resetAll.attach(this.onResetAll);
|
|
497
498
|
}
|
|
498
|
-
(0, common_1.assert)(!!fieldSchema, 'Field schema must be provided');
|
|
499
499
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
500
|
+
get metadata() {
|
|
501
|
+
return this.metadataFamily.get(this.oid);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* The view of this Entity, not including nested
|
|
505
|
+
* entities (that's the snapshot - see #getSnapshot())
|
|
506
|
+
*
|
|
507
|
+
* Nested entities are represented by refs.
|
|
508
|
+
*/
|
|
509
|
+
get viewData() {
|
|
510
|
+
if (this._viewData === undefined) {
|
|
511
|
+
this._viewData = this.metadata.computeView();
|
|
512
|
+
this.validate();
|
|
504
513
|
}
|
|
505
|
-
|
|
506
|
-
|
|
514
|
+
return this._viewData;
|
|
515
|
+
}
|
|
516
|
+
/** convenience getter for viewData.view */
|
|
517
|
+
get rawView() {
|
|
518
|
+
return this.viewData.view;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* An Entity's View includes the rendering of its underlying data,
|
|
522
|
+
* connecting of children where refs were, and validation
|
|
523
|
+
* and pruning according to schema.
|
|
524
|
+
*/
|
|
525
|
+
get view() {
|
|
526
|
+
if (this.cachedView !== undefined) {
|
|
527
|
+
return this.cachedView;
|
|
528
|
+
}
|
|
529
|
+
if (this.viewData.deleted) {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
// can't use invalid data - but this should be bubbled up to
|
|
533
|
+
// a prune point
|
|
534
|
+
const rawView = this.rawView;
|
|
535
|
+
const viewIsWrongType = (!rawView && !(0, common_1.isNullable)(this.schema)) ||
|
|
536
|
+
(this.schema.type === 'array' && !Array.isArray(rawView)) ||
|
|
537
|
+
((this.schema.type === 'object' || this.schema.type === 'map') &&
|
|
538
|
+
!(0, common_1.isObject)(rawView));
|
|
539
|
+
if (viewIsWrongType) {
|
|
540
|
+
// this will cover lists and maps, too.
|
|
541
|
+
if ((0, common_1.hasDefault)(this.schema)) {
|
|
542
|
+
return (0, common_1.getDefault)(this.schema);
|
|
543
|
+
}
|
|
544
|
+
// force null - invalid - will require parent prune
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
this.cachedView = this.isList ? [] : {};
|
|
548
|
+
(0, common_1.assignOid)(this.cachedView, this.oid);
|
|
549
|
+
if (Array.isArray(rawView)) {
|
|
550
|
+
const schema = (0, common_1.getChildFieldSchema)(this.schema, 0);
|
|
551
|
+
if (!schema) {
|
|
552
|
+
/**
|
|
553
|
+
* PRUNE - this is a prune point. we can't continue
|
|
554
|
+
* to render this data, so we'll just return [].
|
|
555
|
+
* This skips the loop.
|
|
556
|
+
*/
|
|
557
|
+
this.ctx.log('error', 'No child field schema for list entity.', this.oid);
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
for (let i = 0; i < rawView.length; i++) {
|
|
561
|
+
const child = this.get(i);
|
|
562
|
+
if (this.childIsNull(child) && !(0, common_1.isNullable)(schema)) {
|
|
563
|
+
this.ctx.log('error', 'Child missing in non-nullable field', this.oid, 'index:', i);
|
|
564
|
+
// this item will be pruned.
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
this.cachedView.push(child);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else if ((0, common_1.isObject)(rawView)) {
|
|
573
|
+
// iterate over known properties in object-type entities;
|
|
574
|
+
// for maps, we just iterate over the keys.
|
|
575
|
+
const keys = this.schema.type === 'object'
|
|
576
|
+
? Object.keys(this.schema.properties)
|
|
577
|
+
: Object.keys(rawView);
|
|
578
|
+
for (const key of keys) {
|
|
579
|
+
const schema = (0, common_1.getChildFieldSchema)(this.schema, key);
|
|
580
|
+
if (!schema) {
|
|
581
|
+
/**
|
|
582
|
+
* PRUNE - this is a prune point. we can't continue
|
|
583
|
+
* to render this data. If this is a map, it will be
|
|
584
|
+
* pruned empty. Otherwise, prune moves upward.
|
|
585
|
+
*
|
|
586
|
+
* This exits the loop.
|
|
587
|
+
*/
|
|
588
|
+
this.ctx.log('error', 'No child field schema for object entity at key', key);
|
|
589
|
+
if (this.schema.type === 'map') {
|
|
590
|
+
// it's valid to prune here if it's a map
|
|
591
|
+
this.cachedView = {};
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
// otherwise prune moves upward
|
|
595
|
+
this.cachedView = null;
|
|
596
|
+
}
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
const child = this.get(key);
|
|
600
|
+
if (this.childIsNull(child) && !(0, common_1.isNullable)(schema)) {
|
|
601
|
+
this.ctx.log('error', 'Child entity is missing for non-nullable field', this.oid, 'key:', key);
|
|
602
|
+
/**
|
|
603
|
+
* PRUNE - this is a prune point. we can't continue
|
|
604
|
+
* to render this data. If this is a map, we can ignore
|
|
605
|
+
* this value. Otherwise we must prune upward.
|
|
606
|
+
* This exits the loop.
|
|
607
|
+
*/
|
|
608
|
+
if (this.schema.type !== 'map') {
|
|
609
|
+
this.cachedView = null;
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
this.cachedView[key] = child;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return this.cachedView;
|
|
619
|
+
}
|
|
620
|
+
get uid() {
|
|
621
|
+
return this.oid;
|
|
622
|
+
}
|
|
623
|
+
get deleted() {
|
|
624
|
+
return this.viewData.deleted || this.view === null;
|
|
625
|
+
}
|
|
626
|
+
get invalid() {
|
|
627
|
+
return !!this.validate();
|
|
628
|
+
}
|
|
629
|
+
get isList() {
|
|
630
|
+
// have to turn TS off here as our two interfaces both implement
|
|
631
|
+
// const values for this boolean.
|
|
632
|
+
return (this.schema.type === 'array' || Array.isArray(this.viewData.view));
|
|
633
|
+
}
|
|
634
|
+
get updatedAt() {
|
|
635
|
+
return this.viewData.updatedAt;
|
|
636
|
+
}
|
|
637
|
+
get deepUpdatedAt() {
|
|
638
|
+
if (this.cachedDeepUpdatedAt)
|
|
639
|
+
return this.cachedDeepUpdatedAt;
|
|
640
|
+
// iterate over all children and take the latest timestamp
|
|
641
|
+
let latest = this.updatedAt;
|
|
642
|
+
if (this.isList) {
|
|
643
|
+
this.forEach((child) => {
|
|
644
|
+
if (child instanceof Entity) {
|
|
645
|
+
const childTimestamp = child.deepUpdatedAt;
|
|
646
|
+
if (childTimestamp && (!latest || childTimestamp > latest)) {
|
|
647
|
+
latest = childTimestamp;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
});
|
|
507
651
|
}
|
|
508
652
|
else {
|
|
509
|
-
|
|
653
|
+
this.values().forEach((child) => {
|
|
654
|
+
if (child instanceof Entity) {
|
|
655
|
+
const childTimestamp = child.deepUpdatedAt;
|
|
656
|
+
if (childTimestamp && (!latest || childTimestamp > latest)) {
|
|
657
|
+
latest = childTimestamp;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
});
|
|
510
661
|
}
|
|
662
|
+
this.cachedDeepUpdatedAt = latest;
|
|
663
|
+
return latest;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* @internal - this is relevant to Verdant's system, not users.
|
|
667
|
+
*
|
|
668
|
+
* Indicates whether this document is from an outdated version
|
|
669
|
+
* of the schema - which means it cannot be used until it is upgraded.
|
|
670
|
+
*/
|
|
671
|
+
get isOutdatedVersion() {
|
|
672
|
+
if (this.parent)
|
|
673
|
+
return this.parent.isOutdatedVersion;
|
|
674
|
+
return this.viewData.fromOlderVersion;
|
|
511
675
|
}
|
|
676
|
+
// array entity methods
|
|
512
677
|
get length() {
|
|
513
|
-
return this.
|
|
678
|
+
return this.view.length;
|
|
514
679
|
}
|
|
515
680
|
// list implements an iterator which maps items to wrapped
|
|
516
681
|
// versions
|
|
517
|
-
[
|
|
682
|
+
[Symbol.iterator]() {
|
|
683
|
+
var _a;
|
|
518
684
|
let index = 0;
|
|
685
|
+
let length = (_a = this.view) === null || _a === void 0 ? void 0 : _a.length;
|
|
519
686
|
return {
|
|
520
687
|
next: () => {
|
|
521
|
-
if (index <
|
|
688
|
+
if (index < length) {
|
|
522
689
|
return {
|
|
523
690
|
value: this.get(index++),
|
|
524
691
|
done: false,
|
|
@@ -533,4 +700,12 @@ class Entity {
|
|
|
533
700
|
}
|
|
534
701
|
}
|
|
535
702
|
exports.Entity = Entity;
|
|
703
|
+
function assertNotSymbol(key) {
|
|
704
|
+
if (typeof key === 'symbol')
|
|
705
|
+
throw new Error("Symbol keys aren't supported");
|
|
706
|
+
}
|
|
707
|
+
function assertNumber(key) {
|
|
708
|
+
if (typeof key !== 'number')
|
|
709
|
+
throw new Error('Only number keys are supported in list entities');
|
|
710
|
+
}
|
|
536
711
|
//# sourceMappingURL=Entity.js.map
|