jotai-state-tree 0.1.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/LICENSE +21 -0
- package/README.md +168 -0
- package/dist/chunk-XXZK62DD.mjs +931 -0
- package/dist/index.d.mts +1109 -0
- package/dist/index.d.ts +1109 -0
- package/dist/index.js +3579 -0
- package/dist/index.mjs +2625 -0
- package/dist/react.d.mts +144 -0
- package/dist/react.d.ts +144 -0
- package/dist/react.js +1259 -0
- package/dist/react.mjs +372 -0
- package/package.json +77 -0
- package/src/__tests__/index.test.ts +1371 -0
- package/src/__tests__/memory.test.ts +681 -0
- package/src/__tests__/performance.test.ts +667 -0
- package/src/__tests__/react.react.test.tsx +811 -0
- package/src/__tests__/registry.test.ts +589 -0
- package/src/array.ts +335 -0
- package/src/compat.ts +294 -0
- package/src/index.ts +647 -0
- package/src/lifecycle.ts +580 -0
- package/src/map.ts +276 -0
- package/src/model.ts +832 -0
- package/src/primitives.ts +400 -0
- package/src/react.ts +626 -0
- package/src/registry.ts +741 -0
- package/src/tree.ts +1275 -0
- package/src/types.ts +520 -0
- package/src/undo.ts +566 -0
- package/src/utilities.ts +616 -0
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
types,
|
|
4
|
+
registerModel,
|
|
5
|
+
unregisterModel,
|
|
6
|
+
isModelRegistered,
|
|
7
|
+
resolveModel,
|
|
8
|
+
tryResolveModel,
|
|
9
|
+
resolveModelAsync,
|
|
10
|
+
getModelMetadata,
|
|
11
|
+
getRegisteredModelNames,
|
|
12
|
+
onModelRegistered,
|
|
13
|
+
clearModelRegistry,
|
|
14
|
+
lateModel,
|
|
15
|
+
dynamicReference,
|
|
16
|
+
safeDynamicReference,
|
|
17
|
+
getSnapshot,
|
|
18
|
+
resolveIdentifier,
|
|
19
|
+
} from "../index";
|
|
20
|
+
|
|
21
|
+
describe("Model Registry", () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
// Clear registry before each test
|
|
24
|
+
clearModelRegistry();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("registerModel / unregisterModel", () => {
|
|
28
|
+
it("should register a model", () => {
|
|
29
|
+
const User = types.model("User", {
|
|
30
|
+
id: types.identifier,
|
|
31
|
+
name: types.string,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
registerModel("User", User);
|
|
35
|
+
expect(isModelRegistered("User")).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should register a model with metadata", () => {
|
|
39
|
+
const User = types.model("User", {
|
|
40
|
+
id: types.identifier,
|
|
41
|
+
name: types.string,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
registerModel("User", User, { version: "1.0.0", author: "test" });
|
|
45
|
+
expect(isModelRegistered("User")).toBe(true);
|
|
46
|
+
expect(getModelMetadata("User")).toEqual({
|
|
47
|
+
version: "1.0.0",
|
|
48
|
+
author: "test",
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should throw when registering duplicate model name", () => {
|
|
53
|
+
const User = types.model("User", {
|
|
54
|
+
id: types.identifier,
|
|
55
|
+
name: types.string,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
registerModel("User", User);
|
|
59
|
+
expect(() => registerModel("User", User)).toThrow(
|
|
60
|
+
'Model "User" is already registered',
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should unregister a model", () => {
|
|
65
|
+
const User = types.model("User", {
|
|
66
|
+
id: types.identifier,
|
|
67
|
+
name: types.string,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
registerModel("User", User);
|
|
71
|
+
expect(isModelRegistered("User")).toBe(true);
|
|
72
|
+
|
|
73
|
+
const result = unregisterModel("User");
|
|
74
|
+
expect(result).toBe(true);
|
|
75
|
+
expect(isModelRegistered("User")).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should return false when unregistering non-existent model", () => {
|
|
79
|
+
const result = unregisterModel("NonExistent");
|
|
80
|
+
expect(result).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("resolveModel / tryResolveModel", () => {
|
|
85
|
+
it("should resolve a registered model", () => {
|
|
86
|
+
const User = types.model("User", {
|
|
87
|
+
id: types.identifier,
|
|
88
|
+
name: types.string,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
registerModel("User", User);
|
|
92
|
+
const resolved = resolveModel("User");
|
|
93
|
+
expect(resolved).toBe(User);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should throw when resolving non-existent model", () => {
|
|
97
|
+
expect(() => resolveModel("NonExistent")).toThrow(
|
|
98
|
+
'Model "NonExistent" is not registered',
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should return undefined with tryResolveModel for non-existent model", () => {
|
|
103
|
+
const result = tryResolveModel("NonExistent");
|
|
104
|
+
expect(result).toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should return model with tryResolveModel for existing model", () => {
|
|
108
|
+
const User = types.model("User", {
|
|
109
|
+
id: types.identifier,
|
|
110
|
+
name: types.string,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
registerModel("User", User);
|
|
114
|
+
const result = tryResolveModel("User");
|
|
115
|
+
expect(result).toBe(User);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("resolveModelAsync", () => {
|
|
120
|
+
it("should resolve immediately if model is registered", async () => {
|
|
121
|
+
const User = types.model("User", {
|
|
122
|
+
id: types.identifier,
|
|
123
|
+
name: types.string,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
registerModel("User", User);
|
|
127
|
+
const resolved = await resolveModelAsync("User");
|
|
128
|
+
expect(resolved).toBe(User);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should wait for model to be registered", async () => {
|
|
132
|
+
const User = types.model("User", {
|
|
133
|
+
id: types.identifier,
|
|
134
|
+
name: types.string,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Start waiting, then register after a delay
|
|
138
|
+
const promise = resolveModelAsync("User", 1000);
|
|
139
|
+
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
registerModel("User", User);
|
|
142
|
+
}, 50);
|
|
143
|
+
|
|
144
|
+
const resolved = await promise;
|
|
145
|
+
expect(resolved).toBe(User);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should timeout if model is not registered in time", async () => {
|
|
149
|
+
await expect(resolveModelAsync("NonExistent", 100)).rejects.toThrow(
|
|
150
|
+
'Timeout waiting for model "NonExistent" to be registered',
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("getRegisteredModelNames", () => {
|
|
156
|
+
it("should return empty array when no models registered", () => {
|
|
157
|
+
expect(getRegisteredModelNames()).toEqual([]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should return all registered model names", () => {
|
|
161
|
+
const User = types.model("User", { id: types.identifier });
|
|
162
|
+
const Post = types.model("Post", { id: types.identifier });
|
|
163
|
+
const Comment = types.model("Comment", { id: types.identifier });
|
|
164
|
+
|
|
165
|
+
registerModel("User", User);
|
|
166
|
+
registerModel("Post", Post);
|
|
167
|
+
registerModel("Comment", Comment);
|
|
168
|
+
|
|
169
|
+
const names = getRegisteredModelNames();
|
|
170
|
+
expect(names).toHaveLength(3);
|
|
171
|
+
expect(names).toContain("User");
|
|
172
|
+
expect(names).toContain("Post");
|
|
173
|
+
expect(names).toContain("Comment");
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("onModelRegistered", () => {
|
|
178
|
+
it("should call listener when model is registered", () => {
|
|
179
|
+
const listener = vi.fn();
|
|
180
|
+
const unsubscribe = onModelRegistered(listener);
|
|
181
|
+
|
|
182
|
+
const User = types.model("User", {
|
|
183
|
+
id: types.identifier,
|
|
184
|
+
name: types.string,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
registerModel("User", User);
|
|
188
|
+
|
|
189
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
190
|
+
expect(listener).toHaveBeenCalledWith("User", User);
|
|
191
|
+
|
|
192
|
+
unsubscribe();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should not call listener after unsubscribe", () => {
|
|
196
|
+
const listener = vi.fn();
|
|
197
|
+
const unsubscribe = onModelRegistered(listener);
|
|
198
|
+
|
|
199
|
+
unsubscribe();
|
|
200
|
+
|
|
201
|
+
const User = types.model("User", {
|
|
202
|
+
id: types.identifier,
|
|
203
|
+
name: types.string,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
registerModel("User", User);
|
|
207
|
+
|
|
208
|
+
expect(listener).not.toHaveBeenCalled();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should support multiple listeners", () => {
|
|
212
|
+
const listener1 = vi.fn();
|
|
213
|
+
const listener2 = vi.fn();
|
|
214
|
+
|
|
215
|
+
onModelRegistered(listener1);
|
|
216
|
+
onModelRegistered(listener2);
|
|
217
|
+
|
|
218
|
+
const User = types.model("User", { id: types.identifier });
|
|
219
|
+
registerModel("User", User);
|
|
220
|
+
|
|
221
|
+
expect(listener1).toHaveBeenCalledTimes(1);
|
|
222
|
+
expect(listener2).toHaveBeenCalledTimes(1);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("clearModelRegistry", () => {
|
|
227
|
+
it("should clear all registered models", () => {
|
|
228
|
+
const User = types.model("User", { id: types.identifier });
|
|
229
|
+
const Post = types.model("Post", { id: types.identifier });
|
|
230
|
+
|
|
231
|
+
registerModel("User", User);
|
|
232
|
+
registerModel("Post", Post);
|
|
233
|
+
|
|
234
|
+
expect(getRegisteredModelNames()).toHaveLength(2);
|
|
235
|
+
|
|
236
|
+
clearModelRegistry();
|
|
237
|
+
|
|
238
|
+
expect(getRegisteredModelNames()).toHaveLength(0);
|
|
239
|
+
expect(isModelRegistered("User")).toBe(false);
|
|
240
|
+
expect(isModelRegistered("Post")).toBe(false);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("types.lateModel", () => {
|
|
245
|
+
it("should resolve registered model lazily", () => {
|
|
246
|
+
const User = types.model("User", {
|
|
247
|
+
id: types.identifier,
|
|
248
|
+
name: types.string,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Create a model that uses lateModel before User is registered
|
|
252
|
+
const Post = types.model("Post", {
|
|
253
|
+
id: types.identifier,
|
|
254
|
+
title: types.string,
|
|
255
|
+
author: lateModel("User"),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Register User after Post is defined
|
|
259
|
+
registerModel("User", User);
|
|
260
|
+
|
|
261
|
+
// Create instances
|
|
262
|
+
const user = User.create({ id: "user-1", name: "Alice" });
|
|
263
|
+
const post = Post.create({
|
|
264
|
+
id: "post-1",
|
|
265
|
+
title: "Hello",
|
|
266
|
+
author: { id: "user-2", name: "Bob" },
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(post.author.name).toBe("Bob");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("should throw when model is not registered at creation time", () => {
|
|
273
|
+
const Post = types.model("Post", {
|
|
274
|
+
id: types.identifier,
|
|
275
|
+
author: lateModel("NonExistent"),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
expect(() =>
|
|
279
|
+
Post.create({
|
|
280
|
+
id: "post-1",
|
|
281
|
+
author: { id: "user-1" },
|
|
282
|
+
}),
|
|
283
|
+
).toThrow('Model "NonExistent" is not registered');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe("types.dynamicReference", () => {
|
|
288
|
+
it("should create dynamic reference type with custom resolver", () => {
|
|
289
|
+
const User = types.model("User", {
|
|
290
|
+
id: types.identifier,
|
|
291
|
+
name: types.string,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
registerModel("User", User);
|
|
295
|
+
|
|
296
|
+
// Create a simple lookup map
|
|
297
|
+
const usersById = new Map<string, unknown>();
|
|
298
|
+
const user1 = User.create({ id: "user-1", name: "Alice" });
|
|
299
|
+
const user2 = User.create({ id: "user-2", name: "Bob" });
|
|
300
|
+
usersById.set("user-1", user1);
|
|
301
|
+
usersById.set("user-2", user2);
|
|
302
|
+
|
|
303
|
+
// Test that dynamic reference type is valid
|
|
304
|
+
const refType = dynamicReference("User", {
|
|
305
|
+
get: (identifier) => usersById.get(String(identifier)),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
expect(refType).toBeDefined();
|
|
309
|
+
expect(refType.name).toBe('dynamicReference("User")');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should support custom get resolver", () => {
|
|
313
|
+
const User = types.model("User", {
|
|
314
|
+
id: types.identifier,
|
|
315
|
+
name: types.string,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
registerModel("User", User);
|
|
319
|
+
|
|
320
|
+
const usersById = new Map<string, unknown>();
|
|
321
|
+
|
|
322
|
+
const Post = types.model("Post", {
|
|
323
|
+
id: types.identifier,
|
|
324
|
+
authorId: dynamicReference("User", {
|
|
325
|
+
get: (identifier) => usersById.get(String(identifier)),
|
|
326
|
+
}),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const user = User.create({ id: "user-1", name: "Alice" });
|
|
330
|
+
usersById.set("user-1", user);
|
|
331
|
+
|
|
332
|
+
const post = Post.create({
|
|
333
|
+
id: "post-1",
|
|
334
|
+
authorId: "user-1",
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Access through the dynamic reference proxy
|
|
338
|
+
expect(post.authorId).toBeDefined();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("should call onInvalidated when reference cannot be resolved", () => {
|
|
342
|
+
const User = types.model("User", {
|
|
343
|
+
id: types.identifier,
|
|
344
|
+
name: types.string,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
registerModel("User", User);
|
|
348
|
+
|
|
349
|
+
const onInvalidated = vi
|
|
350
|
+
.fn()
|
|
351
|
+
.mockReturnValue({ id: "fallback", name: "Fallback User" });
|
|
352
|
+
|
|
353
|
+
const Post = types.model("Post", {
|
|
354
|
+
id: types.identifier,
|
|
355
|
+
authorId: dynamicReference("User", {
|
|
356
|
+
get: () => undefined, // Always return undefined
|
|
357
|
+
onInvalidated,
|
|
358
|
+
}),
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const post = Post.create({
|
|
362
|
+
id: "post-1",
|
|
363
|
+
authorId: "non-existent",
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Access the reference - should trigger onInvalidated
|
|
367
|
+
const name = post.authorId.name;
|
|
368
|
+
expect(onInvalidated).toHaveBeenCalled();
|
|
369
|
+
expect(name).toBe("Fallback User");
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
describe("types.safeDynamicReference", () => {
|
|
374
|
+
it("should return undefined for unresolved reference", () => {
|
|
375
|
+
const User = types.model("User", {
|
|
376
|
+
id: types.identifier,
|
|
377
|
+
name: types.string,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
registerModel("User", User);
|
|
381
|
+
|
|
382
|
+
const Post = types.model("Post", {
|
|
383
|
+
id: types.identifier,
|
|
384
|
+
authorId: safeDynamicReference("User", {
|
|
385
|
+
get: () => undefined, // Always return undefined
|
|
386
|
+
}),
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const post = Post.create({
|
|
390
|
+
id: "post-1",
|
|
391
|
+
authorId: "non-existent",
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(post.authorId).toBeUndefined();
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("should resolve when reference exists", () => {
|
|
398
|
+
const User = types.model("User", {
|
|
399
|
+
id: types.identifier,
|
|
400
|
+
name: types.string,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
registerModel("User", User);
|
|
404
|
+
|
|
405
|
+
const usersById = new Map<string, unknown>();
|
|
406
|
+
|
|
407
|
+
const Post = types.model("Post", {
|
|
408
|
+
id: types.identifier,
|
|
409
|
+
authorId: safeDynamicReference("User", {
|
|
410
|
+
get: (identifier) => usersById.get(String(identifier)),
|
|
411
|
+
}),
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const user = User.create({ id: "user-1", name: "Alice" });
|
|
415
|
+
usersById.set("user-1", user);
|
|
416
|
+
|
|
417
|
+
const post = Post.create({
|
|
418
|
+
id: "post-1",
|
|
419
|
+
authorId: "user-1",
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
expect(post.authorId).toBeDefined();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it("should allow undefined in create", () => {
|
|
426
|
+
const User = types.model("User", {
|
|
427
|
+
id: types.identifier,
|
|
428
|
+
name: types.string,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
registerModel("User", User);
|
|
432
|
+
|
|
433
|
+
const Post = types.model("Post", {
|
|
434
|
+
id: types.identifier,
|
|
435
|
+
authorId: safeDynamicReference("User"),
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const post = Post.create({
|
|
439
|
+
id: "post-1",
|
|
440
|
+
authorId: undefined,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
expect(post.authorId).toBeUndefined();
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
describe("Integration: Plugin Architecture", () => {
|
|
448
|
+
it("should support lazy-loading plugin models", async () => {
|
|
449
|
+
// Core model defined upfront
|
|
450
|
+
const CoreStore = types.model("CoreStore", {
|
|
451
|
+
name: types.string,
|
|
452
|
+
plugins: types.array(types.string),
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// Create store before plugins are loaded
|
|
456
|
+
const store = CoreStore.create({
|
|
457
|
+
name: "MyApp",
|
|
458
|
+
plugins: [],
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
expect(store.name).toBe("MyApp");
|
|
462
|
+
|
|
463
|
+
// Simulate plugin loading
|
|
464
|
+
const loadPlugin = async (pluginName: string) => {
|
|
465
|
+
// Simulate async module loading
|
|
466
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
467
|
+
|
|
468
|
+
if (pluginName === "UserPlugin") {
|
|
469
|
+
const UserPlugin = types.model("UserPlugin", {
|
|
470
|
+
id: types.identifier,
|
|
471
|
+
users: types.array(
|
|
472
|
+
types.model({
|
|
473
|
+
id: types.identifier,
|
|
474
|
+
name: types.string,
|
|
475
|
+
}),
|
|
476
|
+
),
|
|
477
|
+
});
|
|
478
|
+
registerModel("UserPlugin", UserPlugin);
|
|
479
|
+
return UserPlugin;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
throw new Error(`Unknown plugin: ${pluginName}`);
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
// Load plugin dynamically
|
|
486
|
+
await loadPlugin("UserPlugin");
|
|
487
|
+
|
|
488
|
+
// Verify plugin is now available
|
|
489
|
+
expect(isModelRegistered("UserPlugin")).toBe(true);
|
|
490
|
+
const UserPlugin = resolveModel("UserPlugin");
|
|
491
|
+
expect(UserPlugin).toBeDefined();
|
|
492
|
+
|
|
493
|
+
// Create plugin instance
|
|
494
|
+
const pluginInstance = UserPlugin.create({
|
|
495
|
+
id: "user-plugin-1",
|
|
496
|
+
users: [{ id: "user-1", name: "Alice" }],
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
expect(getSnapshot(pluginInstance)).toMatchObject({
|
|
500
|
+
id: "user-plugin-1",
|
|
501
|
+
users: [{ id: "user-1", name: "Alice" }],
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("should handle multiple dependent plugins", async () => {
|
|
506
|
+
// Register base model
|
|
507
|
+
const BaseEntity = types.model("BaseEntity", {
|
|
508
|
+
id: types.identifier,
|
|
509
|
+
createdAt: types.optional(types.string, () => new Date().toISOString()),
|
|
510
|
+
});
|
|
511
|
+
registerModel("BaseEntity", BaseEntity);
|
|
512
|
+
|
|
513
|
+
// Plugin A depends on BaseEntity
|
|
514
|
+
const PluginA = types.compose(
|
|
515
|
+
"PluginA",
|
|
516
|
+
resolveModel("BaseEntity"),
|
|
517
|
+
types.model({
|
|
518
|
+
pluginAData: types.string,
|
|
519
|
+
}),
|
|
520
|
+
);
|
|
521
|
+
registerModel("PluginA", PluginA);
|
|
522
|
+
|
|
523
|
+
// Plugin B also depends on BaseEntity
|
|
524
|
+
const PluginB = types.compose(
|
|
525
|
+
"PluginB",
|
|
526
|
+
resolveModel("BaseEntity"),
|
|
527
|
+
types.model({
|
|
528
|
+
pluginBData: types.number,
|
|
529
|
+
}),
|
|
530
|
+
);
|
|
531
|
+
registerModel("PluginB", PluginB);
|
|
532
|
+
|
|
533
|
+
// Create instances
|
|
534
|
+
const instanceA = resolveModel("PluginA").create({
|
|
535
|
+
id: "a-1",
|
|
536
|
+
pluginAData: "hello",
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const instanceB = resolveModel("PluginB").create({
|
|
540
|
+
id: "b-1",
|
|
541
|
+
pluginBData: 42,
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
expect(instanceA.pluginAData).toBe("hello");
|
|
545
|
+
expect(instanceB.pluginBData).toBe(42);
|
|
546
|
+
expect(instanceA.id).toBe("a-1");
|
|
547
|
+
expect(instanceB.id).toBe("b-1");
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it("should support code splitting with resolveModelAsync", async () => {
|
|
551
|
+
// Start waiting for a model that will be "loaded" later
|
|
552
|
+
const modelPromise = resolveModelAsync("LazyLoadedModel", 5000);
|
|
553
|
+
|
|
554
|
+
// Simulate code splitting / dynamic import
|
|
555
|
+
setTimeout(() => {
|
|
556
|
+
const LazyModel = types.model("LazyLoadedModel", {
|
|
557
|
+
id: types.identifier,
|
|
558
|
+
data: types.frozen(),
|
|
559
|
+
});
|
|
560
|
+
registerModel("LazyLoadedModel", LazyModel);
|
|
561
|
+
}, 50);
|
|
562
|
+
|
|
563
|
+
// Wait for the model to be available
|
|
564
|
+
const LazyModel = await modelPromise;
|
|
565
|
+
|
|
566
|
+
expect(LazyModel).toBeDefined();
|
|
567
|
+
const instance = LazyModel.create({
|
|
568
|
+
id: "lazy-1",
|
|
569
|
+
data: { foo: "bar" },
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
expect(instance.data).toEqual({ foo: "bar" });
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
describe("Registry with types namespace", () => {
|
|
577
|
+
it("should have lateModel available on types namespace", () => {
|
|
578
|
+
expect(types.lateModel).toBe(lateModel);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it("should have dynamicReference available on types namespace", () => {
|
|
582
|
+
expect(types.dynamicReference).toBe(dynamicReference);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it("should have safeDynamicReference available on types namespace", () => {
|
|
586
|
+
expect(types.safeDynamicReference).toBe(safeDynamicReference);
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
});
|