Skip to content

Commit 6abc818

Browse files
committed
fix(store): improve store.record() support & error messages while defining the model
1 parent 92ed729 commit 6abc818

2 files changed

Lines changed: 81 additions & 28 deletions

File tree

src/store.js

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -401,15 +401,11 @@ function resolveKey(Model, key, config) {
401401
if (refs.has(defaultValue)) defaultValue = defaultValue();
402402

403403
if (records.has(defaultValue)) {
404-
const value = records.get(defaultValue);
405-
if (typeof value === "function") {
406-
throw TypeError(
407-
`A function is not supported as the value of the record for '${key}' property`,
408-
);
409-
}
404+
let value = records.get(defaultValue);
405+
value = typeof value === "function" ? value() : value;
410406

411407
return {
412-
defaultValue: { id: true, value },
408+
defaultValue: { id: true, [key]: value },
413409
type: "record",
414410
};
415411
}
@@ -440,8 +436,16 @@ function resolveKey(Model, key, config) {
440436
return { defaultValue, type };
441437
}
442438

439+
const parentModels = new WeakMap();
440+
443441
function stringifyModel(Model, msg) {
444-
return `${msg}\n\nModel definition:\n\n${JSON.stringify(Model, null, 2)}\n`;
442+
const ResolvedModel = parentModels.get(Model) || Model;
443+
const modelMsg = `Model definition\n${JSON.stringify(ResolvedModel, null, 2)}`
444+
.split("\n")
445+
.map((s) => `| ${s}`)
446+
.join("\n");
447+
448+
return `${msg}\n\n${modelMsg}\n`;
445449
}
446450

447451
const resolvedPromise = Promise.resolve();
@@ -576,7 +580,10 @@ function setupModel(Model, nested) {
576580
case "object": {
577581
if (defaultValue === null) {
578582
throw TypeError(
579-
`The value for the '${key}' must be an object instance: ${defaultValue}`,
583+
stringifyModel(
584+
Model,
585+
`The value for the '${key}' must be an object instance: ${defaultValue}`,
586+
),
580587
);
581588
}
582589

@@ -587,7 +594,10 @@ function setupModel(Model, nested) {
587594

588595
if (nestedType === "undefined") {
589596
throw TypeError(
590-
`The first item of the '${key}' array must be defined`,
597+
stringifyModel(
598+
Model,
599+
`The first item of the '${key}' array must be defined`,
600+
),
591601
);
592602
}
593603

@@ -597,7 +607,10 @@ function setupModel(Model, nested) {
597607
![String, Number, Boolean].includes(defaultValue[0])
598608
) {
599609
throw TypeError(
600-
`The array item for the '${key}' must be one of the primitive types constructor: String, Number, or Boolean`,
610+
stringifyModel(
611+
Model,
612+
`The array item for the '${key}' must be one of the primitive types constructor: String, Number, or Boolean`,
613+
),
601614
);
602615
}
603616

@@ -639,15 +652,21 @@ function setupModel(Model, nested) {
639652
config.storage.offline.threshold
640653
) {
641654
throw Error(
642-
`External nested model for '${key}' property has lower offline threshold (${localConfig.storage.offline.threshold} ms) than the parent definition (${config.storage.offline.threshold} ms)`,
655+
stringifyModel(
656+
Model,
657+
`External nested model for '${key}' property has lower offline threshold (${localConfig.storage.offline.threshold} ms) than the parent definition (${config.storage.offline.threshold} ms)`,
658+
),
643659
);
644660
}
645661

646662
if (localConfig.enumerable && defaultValue[1]) {
647663
const nestedOptions = defaultValue[1];
648664
if (typeof nestedOptions !== "object") {
649665
throw TypeError(
650-
`Options for '${key}' array property must be an object instance: ${typeof nestedOptions}`,
666+
stringifyModel(
667+
Model,
668+
`Options for '${key}' array property must be an object instance: ${typeof nestedOptions}`,
669+
),
651670
);
652671
}
653672
if (nestedOptions.loose) {
@@ -675,12 +694,6 @@ function setupModel(Model, nested) {
675694
};
676695
}
677696

678-
if (Object.keys(defaultValue).length === 0) {
679-
throw TypeError(
680-
`The object for the '${key}' must have at least one property`,
681-
);
682-
}
683-
684697
const nestedConfig = bootstrap(defaultValue, true);
685698
if (nestedConfig.enumerable || nestedConfig.external) {
686699
if (
@@ -690,7 +703,10 @@ function setupModel(Model, nested) {
690703
config.storage.offline.threshold
691704
) {
692705
throw Error(
693-
`External nested model for '${key}' property has lower offline threshold (${nestedConfig.storage.offline.threshold} ms) than the parent definition (${config.storage.offline.threshold} ms)`,
706+
stringifyModel(
707+
Model,
708+
`External nested model for '${key}' property has lower offline threshold (${nestedConfig.storage.offline.threshold} ms) than the parent definition (${config.storage.offline.threshold} ms)`,
709+
),
694710
);
695711
}
696712
return (model, data, lastModel) => {
@@ -757,6 +773,7 @@ function setupModel(Model, nested) {
757773
};
758774
}
759775
case "record": {
776+
parentModels.set(defaultValue, Model);
760777
const localConfig = bootstrap(defaultValue, true);
761778

762779
return (model, data, lastModel) => {
@@ -772,7 +789,10 @@ function setupModel(Model, nested) {
772789

773790
if (typeof data[key] !== "object") {
774791
throw TypeError(
775-
`The value for the '${key}' must be an object instance: ${typeof data[key]}`,
792+
stringifyModel(
793+
Model,
794+
`The value for the '${key}' must be an object instance: ${typeof data[key]}`,
795+
),
776796
);
777797
}
778798

@@ -799,13 +819,13 @@ function setupModel(Model, nested) {
799819
}
800820

801821
const item = localConfig.create(
802-
{ id, value: record[id] },
803-
{ id, value: lastModel && lastModel[key][id] },
822+
{ id, [key]: record[id] },
823+
{ id, [key]: lastModel && lastModel[key][id] },
804824
);
805825

806826
Object.defineProperty(result, id, {
807827
get() {
808-
return cache.get(this, id, () => item.value);
828+
return cache.get(this, id, () => item[key]);
809829
},
810830
enumerable: true,
811831
});

test/spec/store.js

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,6 @@ describe("store:", () => {
110110
expect(() => store.get({ nested: [] })).toThrow();
111111
});
112112

113-
it("throws when nested object has no properties defined", () => {
114-
expect(() => store.get({ nested: {} })).toThrow();
115-
});
116-
117113
it("set to an error state when get method returning undefined", () => {
118114
Model = {
119115
value: "test",
@@ -1196,6 +1192,17 @@ describe("store:", () => {
11961192
expect(() => store.set(Model, { values: 123 })).toThrow();
11971193
});
11981194

1195+
it("throws when record value model definition is invalid", () => {
1196+
Model = {
1197+
id: true,
1198+
other: "",
1199+
test: 123,
1200+
myKey: store.record(store.ref(() => [])),
1201+
};
1202+
1203+
expect(() => store.set(Model, { myKey: ["test"] })).toThrow();
1204+
});
1205+
11991206
describe("for primitive value", () => {
12001207
beforeEach(() => {
12011208
Model = {
@@ -1361,6 +1368,32 @@ describe("store:", () => {
13611368
});
13621369
});
13631370
});
1371+
1372+
describe("for external model set by ref()", () => {
1373+
let OtherModel;
1374+
1375+
beforeEach(() => {
1376+
OtherModel = {
1377+
id: true,
1378+
value: "",
1379+
};
1380+
1381+
Model = {
1382+
id: true,
1383+
records: store.record(store.ref(() => OtherModel)),
1384+
};
1385+
});
1386+
1387+
it("creates a record property", async () => {
1388+
const record = await store.set(OtherModel, { value: "one" });
1389+
const model = await store.set(Model, {
1390+
records: { first: record },
1391+
});
1392+
expect(model.records).toEqual({
1393+
first: { id: record.id, value: "one" },
1394+
});
1395+
});
1396+
});
13641397
});
13651398

13661399
describe("guards", () => {

0 commit comments

Comments
 (0)