@adobe/data 0.6.3 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ecs/database/applied/applied-database.d.ts +47 -0
- package/dist/ecs/database/applied/applied-database.js +22 -0
- package/dist/ecs/database/applied/applied-database.js.map +1 -0
- package/dist/ecs/database/applied/applied-entry.d.ts +30 -0
- package/dist/ecs/database/applied/applied-entry.js +84 -0
- package/dist/ecs/database/applied/applied-entry.js.map +1 -0
- package/dist/ecs/database/applied/create-applied-database.d.ts +8 -0
- package/dist/ecs/database/applied/create-applied-database.js +266 -0
- package/dist/ecs/database/applied/create-applied-database.js.map +1 -0
- package/dist/ecs/database/applied/create-applied-database.test.d.ts +1 -0
- package/dist/ecs/database/applied/create-applied-database.test.js +94 -0
- package/dist/ecs/database/applied/create-applied-database.test.js.map +1 -0
- package/dist/ecs/database/create-applied-database.d.ts +49 -0
- package/dist/ecs/database/create-applied-database.js +275 -0
- package/dist/ecs/database/create-applied-database.js.map +1 -0
- package/dist/ecs/database/create-database.d.ts +1 -1
- package/dist/ecs/database/create-database.js +123 -198
- package/dist/ecs/database/create-database.js.map +1 -1
- package/dist/ecs/database/create-database.test.js +155 -684
- package/dist/ecs/database/create-database.test.js.map +1 -1
- package/dist/ecs/database/database.d.ts +1 -0
- package/dist/ecs/database/index.d.ts +6 -0
- package/dist/ecs/database/index.js +6 -0
- package/dist/ecs/database/index.js.map +1 -1
- package/dist/ecs/database/observe-select-entities.performance.test.js +6 -5
- package/dist/ecs/database/observe-select-entities.performance.test.js.map +1 -1
- package/dist/ecs/database/observed/create-observed-database.d.ts +7 -0
- package/dist/ecs/database/observed/create-observed-database.js +154 -0
- package/dist/ecs/database/observed/create-observed-database.js.map +1 -0
- package/dist/ecs/database/observed/create-observed-database.test.d.ts +1 -0
- package/dist/ecs/database/observed/create-observed-database.test.js +557 -0
- package/dist/ecs/database/observed/create-observed-database.test.js.map +1 -0
- package/dist/ecs/database/observed/observed-database.d.ts +36 -0
- package/dist/ecs/database/observed/observed-database.js +19 -0
- package/dist/ecs/database/observed/observed-database.js.map +1 -0
- package/dist/ecs/database/reconciling/applied-database.d.ts +47 -0
- package/dist/ecs/database/reconciling/applied-database.js +22 -0
- package/dist/ecs/database/reconciling/applied-database.js.map +1 -0
- package/dist/ecs/database/reconciling/applied-entry.d.ts +30 -0
- package/dist/ecs/database/reconciling/applied-entry.js +84 -0
- package/dist/ecs/database/reconciling/applied-entry.js.map +1 -0
- package/dist/ecs/database/reconciling/create-applied-database.d.ts +8 -0
- package/dist/ecs/database/reconciling/create-applied-database.js +266 -0
- package/dist/ecs/database/reconciling/create-applied-database.js.map +1 -0
- package/dist/ecs/database/reconciling/create-applied-database.test.d.ts +1 -0
- package/dist/ecs/database/reconciling/create-applied-database.test.js +94 -0
- package/dist/ecs/database/reconciling/create-applied-database.test.js.map +1 -0
- package/dist/ecs/database/reconciling/create-reconciling-database.d.ts +8 -0
- package/dist/ecs/database/reconciling/create-reconciling-database.js +146 -0
- package/dist/ecs/database/reconciling/create-reconciling-database.js.map +1 -0
- package/dist/ecs/database/reconciling/create-reconciling-database.test.d.ts +1 -0
- package/dist/ecs/database/reconciling/create-reconciling-database.test.js +94 -0
- package/dist/ecs/database/reconciling/create-reconciling-database.test.js.map +1 -0
- package/dist/ecs/database/reconciling/reconciling-database.d.ts +21 -0
- package/dist/ecs/database/reconciling/reconciling-database.js +22 -0
- package/dist/ecs/database/reconciling/reconciling-database.js.map +1 -0
- package/dist/ecs/database/reconciling/reconciling-entry.d.ts +30 -0
- package/dist/ecs/database/reconciling/reconciling-entry.js +88 -0
- package/dist/ecs/database/reconciling/reconciling-entry.js.map +1 -0
- package/dist/ecs/database/replicate.d.ts +9 -0
- package/dist/ecs/database/replicate.js +66 -0
- package/dist/ecs/database/replicate.js.map +1 -0
- package/dist/ecs/database/replicate.test.d.ts +1 -0
- package/dist/ecs/database/replicate.test.js +235 -0
- package/dist/ecs/database/replicate.test.js.map +1 -0
- package/dist/ecs/database/transactional-store/create-transactional-store.test.js +20 -0
- package/dist/ecs/database/transactional-store/create-transactional-store.test.js.map +1 -1
- package/dist/ecs/database/transactional-store/patch-entity-values.js +4 -1
- package/dist/ecs/database/transactional-store/patch-entity-values.js.map +1 -1
- package/dist/ecs/store/core/core.d.ts +2 -2
- package/dist/ecs/store/core/create-core.js +5 -2
- package/dist/ecs/store/core/create-core.js.map +1 -1
- package/dist/functions/serialization/index.d.ts +1 -0
- package/dist/functions/serialization/index.js +1 -0
- package/dist/functions/serialization/index.js.map +1 -1
- package/dist/functions/serialization/serialize-to-json.d.ts +9 -0
- package/dist/functions/serialization/serialize-to-json.js +161 -0
- package/dist/functions/serialization/serialize-to-json.js.map +1 -0
- package/dist/functions/serialization/serialize-to-json.test.d.ts +1 -0
- package/dist/functions/serialization/serialize-to-json.test.js +244 -0
- package/dist/functions/serialization/serialize-to-json.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -21,6 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
21
21
|
SOFTWARE.*/
|
|
22
22
|
import { describe, it, expect, vi } from "vitest";
|
|
23
23
|
import { createDatabase } from "./create-database.js";
|
|
24
|
+
import { createReconcilingDatabase } from "./reconciling/create-reconciling-database.js";
|
|
24
25
|
import { createStore } from "../store/create-store.js";
|
|
25
26
|
import { F32Schema } from "../../schema/f32.js";
|
|
26
27
|
import { toPromise } from "../../observe/to-promise.js";
|
|
@@ -48,7 +49,7 @@ const nameSchema = {
|
|
|
48
49
|
type: "string",
|
|
49
50
|
maxLength: 50,
|
|
50
51
|
};
|
|
51
|
-
|
|
52
|
+
const createStoreConfig = () => {
|
|
52
53
|
const baseStore = createStore({ position: positionSchema, health: healthSchema, name: nameSchema }, {
|
|
53
54
|
time: { default: { delta: 0.016, elapsed: 0 } },
|
|
54
55
|
generating: { type: "boolean", default: false }
|
|
@@ -59,7 +60,7 @@ function createTestDatabase() {
|
|
|
59
60
|
PositionName: ["position", "name"],
|
|
60
61
|
Full: ["position", "health", "name"],
|
|
61
62
|
});
|
|
62
|
-
|
|
63
|
+
const transactions = {
|
|
63
64
|
createPositionEntity(t, args) {
|
|
64
65
|
return t.archetypes.Position.insert(args);
|
|
65
66
|
},
|
|
@@ -84,6 +85,10 @@ function createTestDatabase() {
|
|
|
84
85
|
updateTime(t, args) {
|
|
85
86
|
t.resources.time = args;
|
|
86
87
|
},
|
|
88
|
+
createFailingPositionEntity(t, args) {
|
|
89
|
+
const entity = t.archetypes.Position.insert(args);
|
|
90
|
+
throw new Error("Simulated failure");
|
|
91
|
+
},
|
|
87
92
|
startGenerating(t, args) {
|
|
88
93
|
if (args.progress < 1.0) {
|
|
89
94
|
t.resources.generating = true;
|
|
@@ -95,260 +100,62 @@ function createTestDatabase() {
|
|
|
95
100
|
t.delete(entity);
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
|
-
}
|
|
103
|
+
};
|
|
104
|
+
return { baseStore, transactions };
|
|
105
|
+
};
|
|
106
|
+
const createSequentialClock = (sequence, startFallback = 1) => {
|
|
107
|
+
let index = 0;
|
|
108
|
+
let current = Math.max(startFallback, 1);
|
|
109
|
+
return () => {
|
|
110
|
+
if (index < sequence.length) {
|
|
111
|
+
const value = sequence[index++];
|
|
112
|
+
current = Math.max(value, current);
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
current += 1;
|
|
116
|
+
return current;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
function createTestDatabase(now = Date.now) {
|
|
120
|
+
const { baseStore, transactions } = createStoreConfig();
|
|
121
|
+
return createDatabase(baseStore, transactions, now);
|
|
99
122
|
}
|
|
100
123
|
describe("createDatabase", () => {
|
|
124
|
+
it("should replay out-of-order commits by absolute time in reconciling database", () => {
|
|
125
|
+
const { baseStore, transactions } = createStoreConfig();
|
|
126
|
+
const reconciling = createReconcilingDatabase(baseStore, transactions);
|
|
127
|
+
reconciling.apply({
|
|
128
|
+
id: 1,
|
|
129
|
+
name: "createPositionNameEntity",
|
|
130
|
+
args: { position: { x: 10, y: 20, z: 30 }, name: "LateCommit" },
|
|
131
|
+
time: 5,
|
|
132
|
+
});
|
|
133
|
+
reconciling.apply({
|
|
134
|
+
id: 2,
|
|
135
|
+
name: "createPositionNameEntity",
|
|
136
|
+
args: { position: { x: 40, y: 50, z: 60 }, name: "EarlyCommit" },
|
|
137
|
+
time: 1,
|
|
138
|
+
});
|
|
139
|
+
const entities = reconciling.select(["name"]);
|
|
140
|
+
const namesById = entities
|
|
141
|
+
.map(entity => reconciling.read(entity)?.name)
|
|
142
|
+
.filter((name) => !!name);
|
|
143
|
+
expect(namesById).toHaveLength(2);
|
|
144
|
+
expect(namesById).toEqual(["EarlyCommit", "LateCommit"]);
|
|
145
|
+
});
|
|
101
146
|
it("should support deleting entities", () => {
|
|
102
147
|
const store = createTestDatabase();
|
|
103
148
|
const entity = store.transactions.createPositionEntity({ position: { x: 1, y: 2, z: 3 } });
|
|
104
149
|
store.transactions.deletePositionEntities();
|
|
105
150
|
expect(store.locate(entity)).toBeNull();
|
|
106
151
|
});
|
|
107
|
-
it("should
|
|
108
|
-
const store = createTestDatabase();
|
|
109
|
-
const positionObserver = vi.fn();
|
|
110
|
-
const nameObserver = vi.fn();
|
|
111
|
-
// Subscribe to component changes
|
|
112
|
-
const unsubscribePosition = store.observe.components.position(positionObserver);
|
|
113
|
-
const unsubscribeName = store.observe.components.name(nameObserver);
|
|
114
|
-
// Create an entity that affects both components
|
|
115
|
-
const testEntity = store.transactions.createFullEntity({
|
|
116
|
-
position: { x: 1, y: 2, z: 3 },
|
|
117
|
-
name: "Test",
|
|
118
|
-
health: { current: 100, max: 100 }
|
|
119
|
-
});
|
|
120
|
-
// Both observers should be notified
|
|
121
|
-
expect(positionObserver).toHaveBeenCalledTimes(1);
|
|
122
|
-
expect(nameObserver).toHaveBeenCalledTimes(1);
|
|
123
|
-
// Update only position
|
|
124
|
-
store.transactions.updateEntity({
|
|
125
|
-
entity: testEntity,
|
|
126
|
-
values: { position: { x: 4, y: 5, z: 6 } }
|
|
127
|
-
});
|
|
128
|
-
// Only position observer should be notified
|
|
129
|
-
expect(positionObserver).toHaveBeenCalledTimes(2);
|
|
130
|
-
expect(nameObserver).toHaveBeenCalledTimes(1);
|
|
131
|
-
// Unsubscribe and verify no more notifications
|
|
132
|
-
unsubscribePosition();
|
|
133
|
-
unsubscribeName();
|
|
134
|
-
store.transactions.updateEntity({
|
|
135
|
-
entity: testEntity,
|
|
136
|
-
values: { position: { x: 7, y: 8, z: 9 }, name: "Updated" }
|
|
137
|
-
});
|
|
138
|
-
expect(positionObserver).toHaveBeenCalledTimes(2);
|
|
139
|
-
expect(nameObserver).toHaveBeenCalledTimes(1);
|
|
140
|
-
});
|
|
141
|
-
it("should notify entity observers with correct values", () => {
|
|
142
|
-
const store = createTestDatabase();
|
|
143
|
-
// Create initial entity
|
|
144
|
-
const testEntity = store.transactions.createFullEntity({
|
|
145
|
-
position: { x: 1, y: 2, z: 3 },
|
|
146
|
-
name: "Test",
|
|
147
|
-
health: { current: 100, max: 100 }
|
|
148
|
-
});
|
|
149
|
-
// Subscribe to entity changes
|
|
150
|
-
const observer = vi.fn();
|
|
151
|
-
const unsubscribe = store.observe.entity(testEntity)(observer);
|
|
152
|
-
// Initial notification should have current values
|
|
153
|
-
expect(observer).toHaveBeenCalledWith(expect.objectContaining({
|
|
154
|
-
position: { x: 1, y: 2, z: 3 },
|
|
155
|
-
name: "Test",
|
|
156
|
-
health: { current: 100, max: 100 }
|
|
157
|
-
}));
|
|
158
|
-
// Update entity
|
|
159
|
-
store.transactions.updateEntity({
|
|
160
|
-
entity: testEntity,
|
|
161
|
-
values: { name: "Updated", health: { current: 50, max: 100 } }
|
|
162
|
-
});
|
|
163
|
-
// Observer should be notified with new values
|
|
164
|
-
expect(observer).toHaveBeenCalledWith(expect.objectContaining({
|
|
165
|
-
position: { x: 1, y: 2, z: 3 }, // unchanged
|
|
166
|
-
name: "Updated",
|
|
167
|
-
health: { current: 50, max: 100 }
|
|
168
|
-
}));
|
|
169
|
-
// Delete entity
|
|
170
|
-
store.transactions.deleteEntity({ entity: testEntity });
|
|
171
|
-
// Observer should be notified with null
|
|
172
|
-
expect(observer).toHaveBeenCalledWith(null);
|
|
173
|
-
unsubscribe();
|
|
174
|
-
});
|
|
175
|
-
it("should notify transaction observers with full transaction results", () => {
|
|
152
|
+
it("should roll back state when a transaction throws synchronously", () => {
|
|
176
153
|
const store = createTestDatabase();
|
|
177
|
-
|
|
178
|
-
const unsubscribe = store.observe.transactions(transactionObserver);
|
|
179
|
-
// Execute a transaction with multiple operations
|
|
180
|
-
store.transactions.createFullEntity({
|
|
154
|
+
expect(() => store.transactions.createFailingPositionEntity({
|
|
181
155
|
position: { x: 1, y: 2, z: 3 },
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// Transaction observer should be called with the full result
|
|
186
|
-
expect(transactionObserver).toHaveBeenCalledWith(expect.objectContaining({
|
|
187
|
-
changedEntities: expect.any(Map),
|
|
188
|
-
changedComponents: expect.any(Set),
|
|
189
|
-
changedArchetypes: expect.any(Set),
|
|
190
|
-
redo: expect.any(Array),
|
|
191
|
-
undo: expect.any(Array)
|
|
192
|
-
}));
|
|
193
|
-
const result = transactionObserver.mock.calls[0][0];
|
|
194
|
-
expect(result.changedEntities.size).toBe(1);
|
|
195
|
-
expect(result.changedComponents.has("position")).toBe(true);
|
|
196
|
-
expect(result.changedComponents.has("name")).toBe(true);
|
|
197
|
-
unsubscribe();
|
|
198
|
-
});
|
|
199
|
-
it("should notify archetype observers when entities change archetypes", () => {
|
|
200
|
-
const store = createTestDatabase();
|
|
201
|
-
// Create initial entity
|
|
202
|
-
const entity = store.transactions.createPositionEntity({
|
|
203
|
-
position: { x: 1, y: 2, z: 3 }
|
|
204
|
-
});
|
|
205
|
-
const archetype = store.locate(entity)?.archetype;
|
|
206
|
-
expect(archetype).toBeDefined();
|
|
207
|
-
const archetypeObserver = vi.fn();
|
|
208
|
-
const unsubscribe = store.observe.archetype(archetype.id)(archetypeObserver);
|
|
209
|
-
// No initial notification for archetype observers
|
|
210
|
-
expect(archetypeObserver).toHaveBeenCalledTimes(0);
|
|
211
|
-
// Update entity to add name component, potentially changing archetype
|
|
212
|
-
store.transactions.updateEntity({
|
|
213
|
-
entity,
|
|
214
|
-
values: { name: "Test" }
|
|
215
|
-
});
|
|
216
|
-
// Archetype observer should be notified of the change
|
|
217
|
-
expect(archetypeObserver).toHaveBeenCalledTimes(1);
|
|
218
|
-
unsubscribe();
|
|
219
|
-
});
|
|
220
|
-
it("should notify resource observers with immediate and update notifications", () => {
|
|
221
|
-
const store = createTestDatabase();
|
|
222
|
-
const timeObserver = vi.fn();
|
|
223
|
-
// Subscribe to resource changes
|
|
224
|
-
const unsubscribeTime = store.observe.resources.time(timeObserver);
|
|
225
|
-
// Observer should be notified immediately with initial value
|
|
226
|
-
expect(timeObserver).toHaveBeenCalledWith({ delta: 0.016, elapsed: 0 });
|
|
227
|
-
// Update time resource
|
|
228
|
-
store.transactions.updateTime({ delta: 0.032, elapsed: 1 });
|
|
229
|
-
// Observer should be notified with new value
|
|
230
|
-
expect(timeObserver).toHaveBeenCalledWith({ delta: 0.032, elapsed: 1 });
|
|
231
|
-
});
|
|
232
|
-
it("should support multiple observers for the same target", () => {
|
|
233
|
-
const store = createTestDatabase();
|
|
234
|
-
const observer1 = vi.fn();
|
|
235
|
-
const observer2 = vi.fn();
|
|
236
|
-
const observer3 = vi.fn();
|
|
237
|
-
// Subscribe multiple observers to the same component
|
|
238
|
-
const unsubscribe1 = store.observe.components.position(observer1);
|
|
239
|
-
const unsubscribe2 = store.observe.components.position(observer2);
|
|
240
|
-
const unsubscribe3 = store.observe.components.position(observer3);
|
|
241
|
-
// Create entity with position
|
|
242
|
-
const entity = store.transactions.createPositionEntity({
|
|
243
|
-
position: { x: 1, y: 2, z: 3 }
|
|
244
|
-
});
|
|
245
|
-
// All observers should be notified
|
|
246
|
-
expect(observer1).toHaveBeenCalledTimes(1);
|
|
247
|
-
expect(observer2).toHaveBeenCalledTimes(1);
|
|
248
|
-
expect(observer3).toHaveBeenCalledTimes(1);
|
|
249
|
-
// Unsubscribe one observer
|
|
250
|
-
unsubscribe2();
|
|
251
|
-
// Update position
|
|
252
|
-
store.transactions.updateEntity({
|
|
253
|
-
entity,
|
|
254
|
-
values: { position: { x: 4, y: 5, z: 6 } }
|
|
255
|
-
});
|
|
256
|
-
// Only remaining observers should be notified
|
|
257
|
-
expect(observer1).toHaveBeenCalledTimes(2);
|
|
258
|
-
expect(observer2).toHaveBeenCalledTimes(1); // No more calls
|
|
259
|
-
expect(observer3).toHaveBeenCalledTimes(2);
|
|
260
|
-
unsubscribe1();
|
|
261
|
-
unsubscribe3();
|
|
262
|
-
});
|
|
263
|
-
it("should handle observer cleanup correctly", () => {
|
|
264
|
-
const store = createTestDatabase();
|
|
265
|
-
const observer = vi.fn();
|
|
266
|
-
const unsubscribe = store.observe.components.position(observer);
|
|
267
|
-
// Create entity
|
|
268
|
-
const entity = store.transactions.createPositionEntity({
|
|
269
|
-
position: { x: 1, y: 2, z: 3 }
|
|
270
|
-
});
|
|
271
|
-
expect(observer).toHaveBeenCalledTimes(1);
|
|
272
|
-
// Unsubscribe
|
|
273
|
-
unsubscribe();
|
|
274
|
-
// Update entity
|
|
275
|
-
store.transactions.updateEntity({
|
|
276
|
-
entity,
|
|
277
|
-
values: { position: { x: 4, y: 5, z: 6 } }
|
|
278
|
-
});
|
|
279
|
-
// Observer should not be called after unsubscribe
|
|
280
|
-
expect(observer).toHaveBeenCalledTimes(1);
|
|
281
|
-
});
|
|
282
|
-
it("should handle observing non-existent entities", () => {
|
|
283
|
-
const store = createTestDatabase();
|
|
284
|
-
const observer = vi.fn();
|
|
285
|
-
const unsubscribe = store.observe.entity(999)(observer);
|
|
286
|
-
// Should be notified with null for non-existent entity
|
|
287
|
-
expect(observer).toHaveBeenCalledWith(null);
|
|
288
|
-
unsubscribe();
|
|
289
|
-
});
|
|
290
|
-
it("should handle complex transaction scenarios with multiple observers", () => {
|
|
291
|
-
const store = createTestDatabase();
|
|
292
|
-
const positionObserver = vi.fn();
|
|
293
|
-
const healthObserver = vi.fn();
|
|
294
|
-
const transactionObserver = vi.fn();
|
|
295
|
-
const entityObserver = vi.fn();
|
|
296
|
-
// Subscribe to various observers
|
|
297
|
-
const unsubscribePosition = store.observe.components.position(positionObserver);
|
|
298
|
-
const unsubscribeHealth = store.observe.components.health(healthObserver);
|
|
299
|
-
const unsubscribeTransaction = store.observe.transactions(transactionObserver);
|
|
300
|
-
// Create entity
|
|
301
|
-
const entity = store.transactions.createPositionHealthEntity({
|
|
302
|
-
position: { x: 1, y: 2, z: 3 },
|
|
303
|
-
health: { current: 100, max: 100 }
|
|
304
|
-
});
|
|
305
|
-
const unsubscribeEntity = store.observe.entity(entity)(entityObserver);
|
|
306
|
-
// All observers should be notified
|
|
307
|
-
expect(positionObserver).toHaveBeenCalledTimes(1);
|
|
308
|
-
expect(healthObserver).toHaveBeenCalledTimes(1);
|
|
309
|
-
expect(transactionObserver).toHaveBeenCalledTimes(1);
|
|
310
|
-
expect(entityObserver).toHaveBeenCalledTimes(1);
|
|
311
|
-
// Update multiple components
|
|
312
|
-
store.transactions.updateEntity({
|
|
313
|
-
entity,
|
|
314
|
-
values: {
|
|
315
|
-
position: { x: 4, y: 5, z: 6 },
|
|
316
|
-
health: { current: 50, max: 100 }
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
// All observers should be notified again
|
|
320
|
-
expect(positionObserver).toHaveBeenCalledTimes(2);
|
|
321
|
-
expect(healthObserver).toHaveBeenCalledTimes(2);
|
|
322
|
-
expect(transactionObserver).toHaveBeenCalledTimes(2);
|
|
323
|
-
expect(entityObserver).toHaveBeenCalledTimes(2);
|
|
324
|
-
// Verify entity observer received correct values
|
|
325
|
-
expect(entityObserver).toHaveBeenCalledWith(expect.objectContaining({
|
|
326
|
-
position: { x: 4, y: 5, z: 6 },
|
|
327
|
-
health: { current: 50, max: 100 }
|
|
328
|
-
}));
|
|
329
|
-
unsubscribePosition();
|
|
330
|
-
unsubscribeHealth();
|
|
331
|
-
unsubscribeTransaction();
|
|
332
|
-
unsubscribeEntity();
|
|
333
|
-
});
|
|
334
|
-
it("should handle rapid successive changes efficiently", () => {
|
|
335
|
-
const store = createTestDatabase();
|
|
336
|
-
const observer = vi.fn();
|
|
337
|
-
const unsubscribe = store.observe.components.position(observer);
|
|
338
|
-
// Create entity
|
|
339
|
-
const entity = store.transactions.createPositionEntity({
|
|
340
|
-
position: { x: 1, y: 2, z: 3 }
|
|
341
|
-
});
|
|
342
|
-
// Make rapid successive updates
|
|
343
|
-
for (let i = 0; i < 5; i++) {
|
|
344
|
-
store.transactions.updateEntity({
|
|
345
|
-
entity,
|
|
346
|
-
values: { position: { x: i, y: i, z: i } }
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
// Observer should be called for each change
|
|
350
|
-
expect(observer).toHaveBeenCalledTimes(6); // 1 for create + 5 for updates
|
|
351
|
-
unsubscribe();
|
|
156
|
+
})).toThrow("Simulated failure");
|
|
157
|
+
const entities = store.select(["position"]);
|
|
158
|
+
expect(entities).toHaveLength(0);
|
|
352
159
|
});
|
|
353
160
|
it("should support transaction functions that return an Entity", () => {
|
|
354
161
|
const store = createTestDatabase();
|
|
@@ -542,14 +349,14 @@ describe("createDatabase", () => {
|
|
|
542
349
|
expect(error.message).toBe("Test error");
|
|
543
350
|
// Wait for processing to complete
|
|
544
351
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
545
|
-
// Verify
|
|
352
|
+
// Verify the transient entity was rolled back after the error
|
|
546
353
|
const entities = store.select(["position", "name"]);
|
|
547
|
-
const
|
|
354
|
+
const beforeErrorEntities = entities.filter(entityId => {
|
|
548
355
|
const values = store.read(entityId);
|
|
549
356
|
return values?.name === "BeforeError";
|
|
550
357
|
});
|
|
551
|
-
expect(
|
|
552
|
-
expect(observer).
|
|
358
|
+
expect(beforeErrorEntities).toHaveLength(0);
|
|
359
|
+
expect(observer).toHaveBeenCalled();
|
|
553
360
|
unsubscribe();
|
|
554
361
|
});
|
|
555
362
|
it("should handle complex AsyncGenerator with conditional yielding", async () => {
|
|
@@ -796,463 +603,127 @@ describe("createDatabase", () => {
|
|
|
796
603
|
}
|
|
797
604
|
});
|
|
798
605
|
});
|
|
799
|
-
describe("
|
|
800
|
-
it("should
|
|
606
|
+
describe("toData/fromData functionality", () => {
|
|
607
|
+
it("should serialize and deserialize database state correctly", () => {
|
|
801
608
|
const store = createTestDatabase();
|
|
802
|
-
// Create
|
|
803
|
-
const
|
|
609
|
+
// Create some entities and update resources
|
|
610
|
+
const entity1 = store.transactions.createPositionEntity({
|
|
804
611
|
position: { x: 1, y: 2, z: 3 }
|
|
805
612
|
});
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
id: entity,
|
|
811
|
-
position: { x: 1, y: 2, z: 3 }
|
|
812
|
-
}));
|
|
813
|
-
unsubscribe();
|
|
814
|
-
});
|
|
815
|
-
it("should observe entity when it has more components than minArchetype", () => {
|
|
816
|
-
const store = createTestDatabase();
|
|
817
|
-
// Create entity with position and health
|
|
818
|
-
const entity = store.transactions.createPositionHealthEntity({
|
|
819
|
-
position: { x: 1, y: 2, z: 3 },
|
|
820
|
-
health: { current: 100, max: 100 }
|
|
613
|
+
const entity2 = store.transactions.createFullEntity({
|
|
614
|
+
position: { x: 4, y: 5, z: 6 },
|
|
615
|
+
health: { current: 100, max: 100 },
|
|
616
|
+
name: "TestEntity"
|
|
821
617
|
});
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
618
|
+
store.transactions.updateTime({ delta: 0.033, elapsed: 1.5 });
|
|
619
|
+
// Serialize the database
|
|
620
|
+
const serializedData = store.toData();
|
|
621
|
+
// Create a new database and restore from serialized data
|
|
622
|
+
const newStore = createTestDatabase();
|
|
623
|
+
newStore.fromData(serializedData);
|
|
624
|
+
// Verify entities are restored
|
|
625
|
+
const restoredEntities = newStore.select(["position"]);
|
|
626
|
+
expect(restoredEntities).toHaveLength(2);
|
|
627
|
+
// Verify entity data is correct
|
|
628
|
+
const restoredData1 = newStore.read(restoredEntities[0]);
|
|
629
|
+
const restoredData2 = newStore.read(restoredEntities[1]);
|
|
630
|
+
expect(restoredData1).toEqual({
|
|
631
|
+
id: restoredEntities[0],
|
|
835
632
|
position: { x: 1, y: 2, z: 3 }
|
|
836
633
|
});
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
});
|
|
843
|
-
it("should return null when entity has different components than minArchetype", () => {
|
|
844
|
-
const store = createTestDatabase();
|
|
845
|
-
// Create entity with position and name
|
|
846
|
-
const entity = store.transactions.createPositionNameEntity({
|
|
847
|
-
position: { x: 1, y: 2, z: 3 },
|
|
848
|
-
name: "Test"
|
|
634
|
+
expect(restoredData2).toEqual({
|
|
635
|
+
id: restoredEntities[1],
|
|
636
|
+
position: { x: 4, y: 5, z: 6 },
|
|
637
|
+
health: { current: 100, max: 100 },
|
|
638
|
+
name: "TestEntity"
|
|
849
639
|
});
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
// Should return null since entity doesn't have health component
|
|
853
|
-
expect(observer).toHaveBeenCalledWith(null);
|
|
854
|
-
unsubscribe();
|
|
640
|
+
// Verify resources are restored
|
|
641
|
+
expect(newStore.resources.time).toEqual({ delta: 0.033, elapsed: 1.5 });
|
|
855
642
|
});
|
|
856
|
-
it("should
|
|
857
|
-
const store = createTestDatabase();
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
});
|
|
862
|
-
const observer = vi.fn();
|
|
863
|
-
const unsubscribe = store.observe.entity(entity, store.archetypes.PositionHealth)(observer);
|
|
864
|
-
// Initially should be null
|
|
865
|
-
expect(observer).toHaveBeenCalledWith(null);
|
|
866
|
-
// Add health component
|
|
867
|
-
store.transactions.updateEntity({
|
|
868
|
-
entity,
|
|
869
|
-
values: { health: { current: 100, max: 100 } }
|
|
870
|
-
});
|
|
871
|
-
// Should now receive the entity data
|
|
872
|
-
expect(observer).toHaveBeenCalledWith(expect.objectContaining({
|
|
873
|
-
id: entity,
|
|
874
|
-
position: { x: 1, y: 2, z: 3 },
|
|
875
|
-
health: { current: 100, max: 100 }
|
|
876
|
-
}));
|
|
877
|
-
unsubscribe();
|
|
878
|
-
});
|
|
879
|
-
it("should update observation when entity loses required components", () => {
|
|
880
|
-
const store = createTestDatabase();
|
|
881
|
-
// Create entity with position and health
|
|
882
|
-
const entity = store.transactions.createPositionHealthEntity({
|
|
883
|
-
position: { x: 1, y: 2, z: 3 },
|
|
884
|
-
health: { current: 100, max: 100 }
|
|
643
|
+
it("should restore applied entry ordering after serialization", () => {
|
|
644
|
+
const store = createTestDatabase(createSequentialClock([2, 1], 2));
|
|
645
|
+
store.transactions.createPositionNameEntity({
|
|
646
|
+
position: { x: 10, y: 20, z: 30 },
|
|
647
|
+
name: "LateCommit",
|
|
885
648
|
});
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
expect(observer).toHaveBeenCalledWith(expect.objectContaining({
|
|
890
|
-
id: entity,
|
|
891
|
-
position: { x: 1, y: 2, z: 3 },
|
|
892
|
-
health: { current: 100, max: 100 }
|
|
893
|
-
}));
|
|
894
|
-
// Remove health component
|
|
895
|
-
store.transactions.updateEntity({
|
|
896
|
-
entity,
|
|
897
|
-
values: { health: undefined }
|
|
649
|
+
store.transactions.createPositionNameEntity({
|
|
650
|
+
position: { x: 40, y: 50, z: 60 },
|
|
651
|
+
name: "EarlyCommit",
|
|
898
652
|
});
|
|
899
|
-
|
|
900
|
-
expect(
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
const entity = store.transactions.createPositionHealthEntity({
|
|
907
|
-
position: { x: 1, y: 2, z: 3 },
|
|
908
|
-
health: { current: 100, max: 100 }
|
|
653
|
+
const serializedData = store.toData();
|
|
654
|
+
expect(Array.isArray(serializedData.appliedEntries)).toBe(true);
|
|
655
|
+
const newStore = createTestDatabase(createSequentialClock([0.5], 1));
|
|
656
|
+
newStore.fromData(serializedData);
|
|
657
|
+
newStore.transactions.createPositionNameEntity({
|
|
658
|
+
position: { x: 70, y: 80, z: 90 },
|
|
659
|
+
name: "EarliestCommit",
|
|
909
660
|
});
|
|
910
|
-
const
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
position: { x: 1, y: 2, z: 3 }
|
|
916
|
-
}));
|
|
917
|
-
// Delete entity
|
|
918
|
-
store.transactions.deleteEntity({ entity });
|
|
919
|
-
// Should return null for deleted entity
|
|
920
|
-
expect(observer).toHaveBeenCalledWith(null);
|
|
921
|
-
unsubscribe();
|
|
922
|
-
});
|
|
923
|
-
it("should handle non-existent entity with minArchetype", () => {
|
|
924
|
-
const store = createTestDatabase();
|
|
925
|
-
const observer = vi.fn();
|
|
926
|
-
const unsubscribe = store.observe.entity(999, store.archetypes.Position)(observer);
|
|
927
|
-
// Should return null for non-existent entity
|
|
928
|
-
expect(observer).toHaveBeenCalledWith(null);
|
|
929
|
-
unsubscribe();
|
|
661
|
+
const entities = newStore.select(["name"]);
|
|
662
|
+
const names = entities
|
|
663
|
+
.map(entityId => newStore.read(entityId)?.name)
|
|
664
|
+
.filter((name) => Boolean(name));
|
|
665
|
+
expect(new Set(names)).toEqual(new Set(["EarliestCommit", "LateCommit", "EarlyCommit"]));
|
|
930
666
|
});
|
|
931
|
-
it("should
|
|
667
|
+
it("should remove cancelled applied entries", async () => {
|
|
932
668
|
const store = createTestDatabase();
|
|
933
|
-
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
669
|
+
let rejectGenerator = () => { };
|
|
670
|
+
const generator = async function* () {
|
|
671
|
+
yield { position: { x: 1, y: 1, z: 1 }, name: "Transient" };
|
|
672
|
+
await new Promise((_, reject) => {
|
|
673
|
+
rejectGenerator = reject;
|
|
674
|
+
});
|
|
675
|
+
};
|
|
676
|
+
const promise = store.transactions.createPositionNameEntity(generator);
|
|
677
|
+
// Allow the first yield to be processed
|
|
678
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
679
|
+
const serializedBefore = store.toData();
|
|
680
|
+
expect(serializedBefore.appliedEntries).toHaveLength(1);
|
|
681
|
+
const transientId = serializedBefore.appliedEntries[0].id;
|
|
682
|
+
store.cancelTransaction(transientId);
|
|
683
|
+
rejectGenerator(new Error("cancelled"));
|
|
684
|
+
await expect(promise).rejects.toThrow("cancelled");
|
|
685
|
+
const serializedAfter = store.toData();
|
|
686
|
+
expect(serializedAfter.appliedEntries ?? []).toHaveLength(0);
|
|
687
|
+
const entities = store.select(["position"]);
|
|
688
|
+
expect(entities).toHaveLength(0);
|
|
938
689
|
});
|
|
939
|
-
it("should
|
|
690
|
+
it("should preserve transaction functionality after restoration", () => {
|
|
940
691
|
const store = createTestDatabase();
|
|
941
|
-
// Create
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
const
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
const
|
|
950
|
-
const unsubscribeHealth = store.observe.entity(entity, store.archetypes.Health)(healthObserver);
|
|
951
|
-
const unsubscribeFull = store.observe.entity(entity, store.archetypes.PositionHealth)(fullObserver);
|
|
952
|
-
// All should receive data since entity has all components
|
|
953
|
-
expect(positionObserver).toHaveBeenCalledWith(expect.objectContaining({
|
|
954
|
-
id: entity,
|
|
692
|
+
// Create initial state
|
|
693
|
+
store.transactions.updateTime({ delta: 0.016, elapsed: 0 });
|
|
694
|
+
// Serialize the database
|
|
695
|
+
const serializedData = store.toData();
|
|
696
|
+
// Create a new database and restore
|
|
697
|
+
const newStore = createTestDatabase();
|
|
698
|
+
newStore.fromData(serializedData);
|
|
699
|
+
// Verify transactions still work
|
|
700
|
+
const entity = newStore.transactions.createPositionEntity({
|
|
955
701
|
position: { x: 1, y: 2, z: 3 }
|
|
956
|
-
}));
|
|
957
|
-
expect(healthObserver).toHaveBeenCalledWith(expect.objectContaining({
|
|
958
|
-
id: entity,
|
|
959
|
-
health: { current: 100, max: 100 }
|
|
960
|
-
}));
|
|
961
|
-
expect(fullObserver).toHaveBeenCalledWith(expect.objectContaining({
|
|
962
|
-
id: entity,
|
|
963
|
-
position: { x: 1, y: 2, z: 3 },
|
|
964
|
-
health: { current: 100, max: 100 }
|
|
965
|
-
}));
|
|
966
|
-
// Remove health component
|
|
967
|
-
store.transactions.updateEntity({
|
|
968
|
-
entity,
|
|
969
|
-
values: { health: undefined }
|
|
970
702
|
});
|
|
971
|
-
|
|
972
|
-
expect(
|
|
703
|
+
expect(entity).toBeDefined();
|
|
704
|
+
expect(typeof entity).toBe("number");
|
|
705
|
+
const entityData = newStore.read(entity);
|
|
706
|
+
expect(entityData).toEqual({
|
|
973
707
|
id: entity,
|
|
974
708
|
position: { x: 1, y: 2, z: 3 }
|
|
975
|
-
})
|
|
976
|
-
//
|
|
977
|
-
|
|
978
|
-
expect(
|
|
979
|
-
unsubscribePosition();
|
|
980
|
-
unsubscribeHealth();
|
|
981
|
-
unsubscribeFull();
|
|
709
|
+
});
|
|
710
|
+
// Verify resource transactions work
|
|
711
|
+
newStore.transactions.updateTime({ delta: 0.033, elapsed: 1.5 });
|
|
712
|
+
expect(newStore.resources.time).toEqual({ delta: 0.033, elapsed: 1.5 });
|
|
982
713
|
});
|
|
983
|
-
it("
|
|
714
|
+
it("all transient operations should be rolled back", async () => {
|
|
984
715
|
const store = createTestDatabase();
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
health: { current: 100, max: 100 }
|
|
989
|
-
});
|
|
990
|
-
const observer = vi.fn();
|
|
991
|
-
const unsubscribe = store.observe.entity(entity, store.archetypes.PositionHealth)(observer);
|
|
992
|
-
// Initially should receive data
|
|
993
|
-
expect(observer).toHaveBeenCalledWith(expect.objectContaining({
|
|
994
|
-
id: entity,
|
|
995
|
-
position: { x: 1, y: 2, z: 3 }
|
|
996
|
-
}));
|
|
997
|
-
// Update position (should trigger notification)
|
|
998
|
-
store.transactions.updateEntity({
|
|
999
|
-
entity,
|
|
1000
|
-
values: { position: { x: 10, y: 20, z: 30 } }
|
|
1001
|
-
});
|
|
1002
|
-
// Should receive updated data
|
|
1003
|
-
expect(observer).toHaveBeenCalledWith(expect.objectContaining({
|
|
1004
|
-
id: entity,
|
|
1005
|
-
position: { x: 10, y: 20, z: 30 }
|
|
1006
|
-
}));
|
|
1007
|
-
// Update health (should not affect position observation)
|
|
1008
|
-
store.transactions.updateEntity({
|
|
1009
|
-
entity,
|
|
1010
|
-
values: { health: { current: 50, max: 100 } }
|
|
716
|
+
const promise = store.transactions.startGenerating(async function* () {
|
|
717
|
+
yield { progress: 0 };
|
|
718
|
+
yield { progress: 1 };
|
|
1011
719
|
});
|
|
1012
|
-
//
|
|
1013
|
-
expect(
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
});
|
|
1019
|
-
});
|
|
1020
|
-
});
|
|
1021
|
-
describe("toData/fromData functionality", () => {
|
|
1022
|
-
it("should serialize and deserialize database state correctly", () => {
|
|
1023
|
-
const store = createTestDatabase();
|
|
1024
|
-
// Create some entities and update resources
|
|
1025
|
-
const entity1 = store.transactions.createPositionEntity({
|
|
1026
|
-
position: { x: 1, y: 2, z: 3 }
|
|
1027
|
-
});
|
|
1028
|
-
const entity2 = store.transactions.createFullEntity({
|
|
1029
|
-
position: { x: 4, y: 5, z: 6 },
|
|
1030
|
-
health: { current: 100, max: 100 },
|
|
1031
|
-
name: "TestEntity"
|
|
1032
|
-
});
|
|
1033
|
-
store.transactions.updateTime({ delta: 0.033, elapsed: 1.5 });
|
|
1034
|
-
// Serialize the database
|
|
1035
|
-
const serializedData = store.toData();
|
|
1036
|
-
// Create a new database and restore from serialized data
|
|
1037
|
-
const newStore = createTestDatabase();
|
|
1038
|
-
newStore.fromData(serializedData);
|
|
1039
|
-
// Verify entities are restored
|
|
1040
|
-
const restoredEntities = newStore.select(["position"]);
|
|
1041
|
-
expect(restoredEntities).toHaveLength(2);
|
|
1042
|
-
// Verify entity data is correct
|
|
1043
|
-
const restoredData1 = newStore.read(restoredEntities[0]);
|
|
1044
|
-
const restoredData2 = newStore.read(restoredEntities[1]);
|
|
1045
|
-
expect(restoredData1).toEqual({
|
|
1046
|
-
id: restoredEntities[0],
|
|
1047
|
-
position: { x: 1, y: 2, z: 3 }
|
|
1048
|
-
});
|
|
1049
|
-
expect(restoredData2).toEqual({
|
|
1050
|
-
id: restoredEntities[1],
|
|
1051
|
-
position: { x: 4, y: 5, z: 6 },
|
|
1052
|
-
health: { current: 100, max: 100 },
|
|
1053
|
-
name: "TestEntity"
|
|
1054
|
-
});
|
|
1055
|
-
// Verify resources are restored
|
|
1056
|
-
expect(newStore.resources.time).toEqual({ delta: 0.033, elapsed: 1.5 });
|
|
1057
|
-
});
|
|
1058
|
-
it("should notify all observers when database is restored from serialized data", () => {
|
|
1059
|
-
const store = createTestDatabase();
|
|
1060
|
-
// Create initial state
|
|
1061
|
-
const entity = store.transactions.createPositionEntity({
|
|
1062
|
-
position: { x: 1, y: 2, z: 3 }
|
|
1063
|
-
});
|
|
1064
|
-
store.transactions.updateTime({ delta: 0.016, elapsed: 0 });
|
|
1065
|
-
// Set up observers
|
|
1066
|
-
const positionObserver = vi.fn();
|
|
1067
|
-
const timeObserver = vi.fn();
|
|
1068
|
-
const entityObserver = vi.fn();
|
|
1069
|
-
const transactionObserver = vi.fn();
|
|
1070
|
-
const unsubscribePosition = store.observe.components.position(positionObserver);
|
|
1071
|
-
const unsubscribeTime = store.observe.resources.time(timeObserver);
|
|
1072
|
-
const unsubscribeEntity = store.observe.entity(entity)(entityObserver);
|
|
1073
|
-
const unsubscribeTransaction = store.observe.transactions(transactionObserver);
|
|
1074
|
-
// Clear initial notifications
|
|
1075
|
-
positionObserver.mockClear();
|
|
1076
|
-
timeObserver.mockClear();
|
|
1077
|
-
entityObserver.mockClear();
|
|
1078
|
-
transactionObserver.mockClear();
|
|
1079
|
-
// Serialize the database
|
|
1080
|
-
const serializedData = store.toData();
|
|
1081
|
-
// Create a new database with different state
|
|
1082
|
-
const newStore = createTestDatabase();
|
|
1083
|
-
const newEntity = newStore.transactions.createFullEntity({
|
|
1084
|
-
position: { x: 10, y: 20, z: 30 },
|
|
1085
|
-
health: { current: 50, max: 100 },
|
|
1086
|
-
name: "NewEntity"
|
|
1087
|
-
});
|
|
1088
|
-
newStore.transactions.updateTime({ delta: 0.025, elapsed: 2.0 });
|
|
1089
|
-
// Set up observers on the new store
|
|
1090
|
-
const newPositionObserver = vi.fn();
|
|
1091
|
-
const newTimeObserver = vi.fn();
|
|
1092
|
-
const newEntityObserver = vi.fn();
|
|
1093
|
-
const newTransactionObserver = vi.fn();
|
|
1094
|
-
const newUnsubscribePosition = newStore.observe.components.position(newPositionObserver);
|
|
1095
|
-
const newUnsubscribeTime = newStore.observe.resources.time(newTimeObserver);
|
|
1096
|
-
const newUnsubscribeEntity = newStore.observe.entity(newEntity)(newEntityObserver);
|
|
1097
|
-
const newUnsubscribeTransaction = newStore.observe.transactions(newTransactionObserver);
|
|
1098
|
-
// Clear initial notifications
|
|
1099
|
-
newPositionObserver.mockClear();
|
|
1100
|
-
newTimeObserver.mockClear();
|
|
1101
|
-
newEntityObserver.mockClear();
|
|
1102
|
-
newTransactionObserver.mockClear();
|
|
1103
|
-
// Restore from serialized data
|
|
1104
|
-
newStore.fromData(serializedData);
|
|
1105
|
-
// All observers should be notified because the entire state changed
|
|
1106
|
-
expect(newPositionObserver).toHaveBeenCalledTimes(1);
|
|
1107
|
-
expect(newTimeObserver).toHaveBeenCalledTimes(1);
|
|
1108
|
-
expect(newEntityObserver).toHaveBeenCalledTimes(1);
|
|
1109
|
-
expect(newTransactionObserver).toHaveBeenCalledTimes(1);
|
|
1110
|
-
// Verify the transaction observer received the correct notification
|
|
1111
|
-
const transactionResult = newTransactionObserver.mock.calls[0][0];
|
|
1112
|
-
expect(transactionResult.changedComponents.has("position")).toBe(true);
|
|
1113
|
-
expect(transactionResult.transient).toBe(false);
|
|
1114
|
-
// Verify the entity observer received the correct data
|
|
1115
|
-
const entityData = newEntityObserver.mock.calls[0][0];
|
|
1116
|
-
expect(entityData).toEqual({
|
|
1117
|
-
id: newEntity,
|
|
1118
|
-
position: { x: 1, y: 2, z: 3 }
|
|
1119
|
-
});
|
|
1120
|
-
// Verify the time observer received the correct data
|
|
1121
|
-
const timeData = newTimeObserver.mock.calls[0][0];
|
|
1122
|
-
expect(timeData).toEqual({ delta: 0.016, elapsed: 0 });
|
|
1123
|
-
// Clean up
|
|
1124
|
-
unsubscribePosition();
|
|
1125
|
-
unsubscribeTime();
|
|
1126
|
-
unsubscribeEntity();
|
|
1127
|
-
unsubscribeTransaction();
|
|
1128
|
-
newUnsubscribePosition();
|
|
1129
|
-
newUnsubscribeTime();
|
|
1130
|
-
newUnsubscribeEntity();
|
|
1131
|
-
newUnsubscribeTransaction();
|
|
1132
|
-
});
|
|
1133
|
-
it("should notify observers even when no entities exist in restored data", () => {
|
|
1134
|
-
const store = createTestDatabase();
|
|
1135
|
-
// Set up observers on empty store
|
|
1136
|
-
const positionObserver = vi.fn();
|
|
1137
|
-
const timeObserver = vi.fn();
|
|
1138
|
-
const transactionObserver = vi.fn();
|
|
1139
|
-
const unsubscribePosition = store.observe.components.position(positionObserver);
|
|
1140
|
-
const unsubscribeTime = store.observe.resources.time(timeObserver);
|
|
1141
|
-
const unsubscribeTransaction = store.observe.transactions(transactionObserver);
|
|
1142
|
-
// Clear initial notifications
|
|
1143
|
-
positionObserver.mockClear();
|
|
1144
|
-
timeObserver.mockClear();
|
|
1145
|
-
transactionObserver.mockClear();
|
|
1146
|
-
// Serialize empty database
|
|
1147
|
-
const serializedData = store.toData();
|
|
1148
|
-
// Create a new database with some data
|
|
1149
|
-
const newStore = createTestDatabase();
|
|
1150
|
-
newStore.transactions.createPositionEntity({
|
|
1151
|
-
position: { x: 1, y: 2, z: 3 }
|
|
1152
|
-
});
|
|
1153
|
-
newStore.transactions.updateTime({ delta: 0.033, elapsed: 1.5 });
|
|
1154
|
-
// Set up observers on the new store
|
|
1155
|
-
const newPositionObserver = vi.fn();
|
|
1156
|
-
const newTimeObserver = vi.fn();
|
|
1157
|
-
const newTransactionObserver = vi.fn();
|
|
1158
|
-
const newUnsubscribePosition = newStore.observe.components.position(newPositionObserver);
|
|
1159
|
-
const newUnsubscribeTime = newStore.observe.resources.time(newTimeObserver);
|
|
1160
|
-
const newUnsubscribeTransaction = newStore.observe.transactions(newTransactionObserver);
|
|
1161
|
-
// Clear initial notifications
|
|
1162
|
-
newPositionObserver.mockClear();
|
|
1163
|
-
newTimeObserver.mockClear();
|
|
1164
|
-
newTransactionObserver.mockClear();
|
|
1165
|
-
// Restore from empty serialized data
|
|
1166
|
-
newStore.fromData(serializedData);
|
|
1167
|
-
// All observers should still be notified
|
|
1168
|
-
expect(newPositionObserver).toHaveBeenCalledTimes(1);
|
|
1169
|
-
expect(newTimeObserver).toHaveBeenCalledTimes(1);
|
|
1170
|
-
expect(newTransactionObserver).toHaveBeenCalledTimes(1);
|
|
1171
|
-
// Verify the store is now empty
|
|
1172
|
-
const entities = newStore.select(["position"]);
|
|
1173
|
-
expect(entities).toHaveLength(0);
|
|
1174
|
-
expect(newStore.resources.time).toEqual({ delta: 0.016, elapsed: 0 });
|
|
1175
|
-
// Clean up
|
|
1176
|
-
unsubscribePosition();
|
|
1177
|
-
unsubscribeTime();
|
|
1178
|
-
unsubscribeTransaction();
|
|
1179
|
-
newUnsubscribePosition();
|
|
1180
|
-
newUnsubscribeTime();
|
|
1181
|
-
newUnsubscribeTransaction();
|
|
1182
|
-
});
|
|
1183
|
-
it("should handle entity observers correctly during restoration", () => {
|
|
1184
|
-
const store = createTestDatabase();
|
|
1185
|
-
// Create entity and set up observer
|
|
1186
|
-
const entity = store.transactions.createPositionEntity({
|
|
1187
|
-
position: { x: 1, y: 2, z: 3 }
|
|
1188
|
-
});
|
|
1189
|
-
const entityObserver = vi.fn();
|
|
1190
|
-
const unsubscribe = store.observe.entity(entity)(entityObserver);
|
|
1191
|
-
// Clear initial notification
|
|
1192
|
-
entityObserver.mockClear();
|
|
1193
|
-
// Serialize the database
|
|
1194
|
-
const serializedData = store.toData();
|
|
1195
|
-
// Create a new database
|
|
1196
|
-
const newStore = createTestDatabase();
|
|
1197
|
-
// Set up observer on the new store for a different entity
|
|
1198
|
-
const newEntity = newStore.transactions.createFullEntity({
|
|
1199
|
-
position: { x: 10, y: 20, z: 30 },
|
|
1200
|
-
health: { current: 100, max: 100 },
|
|
1201
|
-
name: "NewEntity"
|
|
1202
|
-
});
|
|
1203
|
-
const newEntityObserver = vi.fn();
|
|
1204
|
-
const newUnsubscribe = newStore.observe.entity(newEntity)(newEntityObserver);
|
|
1205
|
-
// Clear initial notification
|
|
1206
|
-
newEntityObserver.mockClear();
|
|
1207
|
-
// Restore from serialized data
|
|
1208
|
-
newStore.fromData(serializedData);
|
|
1209
|
-
// The entity observer should be notified with the restored entity data
|
|
1210
|
-
expect(newEntityObserver).toHaveBeenCalledTimes(1);
|
|
1211
|
-
const restoredEntityData = newEntityObserver.mock.calls[0][0];
|
|
1212
|
-
expect(restoredEntityData).toEqual({
|
|
1213
|
-
id: newEntity,
|
|
1214
|
-
position: { x: 1, y: 2, z: 3 }
|
|
1215
|
-
});
|
|
1216
|
-
// Clean up
|
|
1217
|
-
unsubscribe();
|
|
1218
|
-
newUnsubscribe();
|
|
1219
|
-
});
|
|
1220
|
-
it("should preserve transaction functionality after restoration", () => {
|
|
1221
|
-
const store = createTestDatabase();
|
|
1222
|
-
// Create initial state
|
|
1223
|
-
store.transactions.updateTime({ delta: 0.016, elapsed: 0 });
|
|
1224
|
-
// Serialize the database
|
|
1225
|
-
const serializedData = store.toData();
|
|
1226
|
-
// Create a new database and restore
|
|
1227
|
-
const newStore = createTestDatabase();
|
|
1228
|
-
newStore.fromData(serializedData);
|
|
1229
|
-
// Verify transactions still work
|
|
1230
|
-
const entity = newStore.transactions.createPositionEntity({
|
|
1231
|
-
position: { x: 1, y: 2, z: 3 }
|
|
1232
|
-
});
|
|
1233
|
-
expect(entity).toBeDefined();
|
|
1234
|
-
expect(typeof entity).toBe("number");
|
|
1235
|
-
const entityData = newStore.read(entity);
|
|
1236
|
-
expect(entityData).toEqual({
|
|
1237
|
-
id: entity,
|
|
1238
|
-
position: { x: 1, y: 2, z: 3 }
|
|
1239
|
-
});
|
|
1240
|
-
// Verify resource transactions work
|
|
1241
|
-
newStore.transactions.updateTime({ delta: 0.033, elapsed: 1.5 });
|
|
1242
|
-
expect(newStore.resources.time).toEqual({ delta: 0.033, elapsed: 1.5 });
|
|
1243
|
-
});
|
|
1244
|
-
it("all transient operations should be rolled back", async () => {
|
|
1245
|
-
const store = createTestDatabase();
|
|
1246
|
-
const promise = store.transactions.startGenerating(async function* () {
|
|
1247
|
-
yield { progress: 0 };
|
|
1248
|
-
yield { progress: 1 };
|
|
720
|
+
// Check that the result is a promise
|
|
721
|
+
expect(promise).toBeInstanceOf(Promise);
|
|
722
|
+
const result = await promise;
|
|
723
|
+
expect(result).toBe(-1);
|
|
724
|
+
const generating = await toPromise(store.observe.resources.generating);
|
|
725
|
+
expect(generating).toBe(false);
|
|
1249
726
|
});
|
|
1250
|
-
// Check that the result is a promise
|
|
1251
|
-
expect(promise).toBeInstanceOf(Promise);
|
|
1252
|
-
const result = await promise;
|
|
1253
|
-
expect(result).toBe(-1);
|
|
1254
|
-
const generating = await toPromise(store.observe.resources.generating);
|
|
1255
|
-
expect(generating).toBe(false);
|
|
1256
727
|
});
|
|
1257
728
|
});
|
|
1258
729
|
//# sourceMappingURL=create-database.test.js.map
|