@primate/core 0.9.9 → 0.9.11
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/lib/private/db/test.js +26 -13
- package/lib/private/store/Store.d.ts +11 -2
- package/lib/private/store/Store.js +10 -3
- package/package.json +1 -1
package/lib/private/db/test.js
CHANGED
|
@@ -317,7 +317,9 @@ export default (db) => {
|
|
|
317
317
|
});
|
|
318
318
|
}
|
|
319
319
|
function bad_where(label, mk, expected) {
|
|
320
|
-
return security_pair("where", label, mk, base => User.find({ where: base }), w => Author.find({
|
|
320
|
+
return security_pair("where", label, mk, base => User.find({ where: base }), w => Author.find({
|
|
321
|
+
with: { articles: { store: Article, where: w } },
|
|
322
|
+
}), expected);
|
|
321
323
|
}
|
|
322
324
|
function bad_select(label, mk, expected) {
|
|
323
325
|
return security_pair("find", label, mk, base => User.find({ select: base }), w => Author.find({ with: { articles: { store: Article, select: w } } }), expected);
|
|
@@ -1084,7 +1086,9 @@ export default (db) => {
|
|
|
1084
1086
|
//const missing = `missing-${Date.now()}-${Math.random()}`;
|
|
1085
1087
|
const missing = "00000000-0000-4000-8000-000000000000";
|
|
1086
1088
|
await throws(assert, Code.record_not_found, () => Article.get(missing, { with: { author: Author } }));
|
|
1087
|
-
assert(await Article.try(missing, {
|
|
1089
|
+
assert(await Article.try(missing, {
|
|
1090
|
+
with: { author: Author },
|
|
1091
|
+
})).undefined();
|
|
1088
1092
|
});
|
|
1089
1093
|
$rel("get/try: missing id (+ parent)", async (assert) => {
|
|
1090
1094
|
const missing = "00000000-0000-4000-8000-000000000000";
|
|
@@ -1287,7 +1291,8 @@ export default (db) => {
|
|
|
1287
1291
|
assert(rows.length).equals(1);
|
|
1288
1292
|
assert(rows[0].id).equals(u.id);
|
|
1289
1293
|
});
|
|
1290
|
-
/*$store("json column: inferred type on insert", AuthorWithJSON,
|
|
1294
|
+
/*$store("json column: inferred type on insert", AuthorWithJSON,
|
|
1295
|
+
async assert => {
|
|
1291
1296
|
const author = await AuthorWithJSON.insert({
|
|
1292
1297
|
name: "John",
|
|
1293
1298
|
articles: [{ title: "First", published: new Date() }],
|
|
@@ -1322,7 +1327,9 @@ export default (db) => {
|
|
|
1322
1327
|
});
|
|
1323
1328
|
const author = await AuthorWithJSON.get(a.id);
|
|
1324
1329
|
|
|
1325
|
-
assert(author.articles).type<{
|
|
1330
|
+
assert(author.articles).type<{
|
|
1331
|
+
title: string; published: Date
|
|
1332
|
+
}[] | undefined>();
|
|
1326
1333
|
assert(author.meta).type<{ views: number; tags: string[] } | undefined>();
|
|
1327
1334
|
assert(author.notes).type<JSONValue | undefined>();
|
|
1328
1335
|
|
|
@@ -1331,7 +1338,8 @@ export default (db) => {
|
|
|
1331
1338
|
assert(author.articles?.[0].published).type<Date | undefined>();
|
|
1332
1339
|
});
|
|
1333
1340
|
|
|
1334
|
-
$store("json column: roundtrip preserves types", AuthorWithJSON,
|
|
1341
|
+
$store("json column: roundtrip preserves types", AuthorWithJSON,
|
|
1342
|
+
async assert => {
|
|
1335
1343
|
const now = new Date();
|
|
1336
1344
|
const a = await AuthorWithJSON.insert({
|
|
1337
1345
|
name: "John",
|
|
@@ -1354,7 +1362,9 @@ export default (db) => {
|
|
|
1354
1362
|
await AuthorWithJSON.find({ where: { meta: { tags: { $like: "%ts%" } } } });
|
|
1355
1363
|
|
|
1356
1364
|
// $contains always available, untyped
|
|
1357
|
-
await AuthorWithJSON.find({ where: {
|
|
1365
|
+
await AuthorWithJSON.find({ where: {
|
|
1366
|
+
articles: { $contains: { title: "First" } } },
|
|
1367
|
+
});
|
|
1358
1368
|
});*/
|
|
1359
1369
|
$user("where: null matches unset via update", async (assert) => {
|
|
1360
1370
|
const [paul] = await User.find({ where: { name: "Paul" } });
|
|
@@ -1571,7 +1581,7 @@ export default (db) => {
|
|
|
1571
1581
|
select: ["name"],
|
|
1572
1582
|
with: {
|
|
1573
1583
|
articles: {
|
|
1574
|
-
store: Article, select: ["title"], sort: { title: "asc" }
|
|
1584
|
+
store: Article, select: ["title"], sort: { title: "asc" },
|
|
1575
1585
|
},
|
|
1576
1586
|
},
|
|
1577
1587
|
sort: { name: "asc" },
|
|
@@ -1622,7 +1632,9 @@ export default (db) => {
|
|
|
1622
1632
|
id: key.primary(p.uuid),
|
|
1623
1633
|
parent_id: key.foreign(p.uuid),
|
|
1624
1634
|
url: p.url.optional(),
|
|
1625
|
-
parent: relation.one({
|
|
1635
|
+
parent: relation.one({
|
|
1636
|
+
table: "j_parent", by: "parent_id", reverse: true,
|
|
1637
|
+
}),
|
|
1626
1638
|
},
|
|
1627
1639
|
});
|
|
1628
1640
|
await Parent.create();
|
|
@@ -1636,7 +1648,7 @@ export default (db) => {
|
|
|
1636
1648
|
parent_id: p0.id,
|
|
1637
1649
|
url: new URL("https://example.com/joined"),
|
|
1638
1650
|
});
|
|
1639
|
-
//
|
|
1651
|
+
// force joined path + projection that omits pk, but still must join
|
|
1640
1652
|
const [got] = await Parent.find({
|
|
1641
1653
|
where: { id: p0.id },
|
|
1642
1654
|
select: ["url"],
|
|
@@ -1645,11 +1657,12 @@ export default (db) => {
|
|
|
1645
1657
|
assert(got).defined();
|
|
1646
1658
|
// pk must not leak
|
|
1647
1659
|
assert("id" in got).false();
|
|
1648
|
-
//
|
|
1660
|
+
// base assertion (should fail if joined base isn't unbound/decoded)
|
|
1649
1661
|
assert(got.url).type();
|
|
1650
1662
|
assert(got.url instanceof URL).true();
|
|
1651
1663
|
assert(got.url.href).equals("https://example.com/parent");
|
|
1652
|
-
//
|
|
1664
|
+
// relation assertion (should fail if joined relation isn't
|
|
1665
|
+
// unbound/decoded)
|
|
1653
1666
|
assert(Array.isArray(got.children)).true();
|
|
1654
1667
|
assert(got.children.length).equals(1);
|
|
1655
1668
|
const c0 = got.children[0];
|
|
@@ -1912,7 +1925,7 @@ export default (db) => {
|
|
|
1912
1925
|
$user("find: offset skips records", async (assert) => {
|
|
1913
1926
|
const all = await User.find({ sort: { name: "asc" } });
|
|
1914
1927
|
const offset = await User.find({
|
|
1915
|
-
sort: { name: "asc" }, offset: 2, limit: all.length - 2
|
|
1928
|
+
sort: { name: "asc" }, offset: 2, limit: all.length - 2,
|
|
1916
1929
|
});
|
|
1917
1930
|
assert(offset.length).equals(all.length - 2);
|
|
1918
1931
|
assert(offset[0].name).equals(all[2].name);
|
|
@@ -1920,7 +1933,7 @@ export default (db) => {
|
|
|
1920
1933
|
$user("find: offset 0 is same as no offset", async (assert) => {
|
|
1921
1934
|
const without = await User.find({ sort: { name: "asc" } });
|
|
1922
1935
|
const with_zero = await User.find({
|
|
1923
|
-
sort: { name: "asc" }, offset: 0, limit: without.length
|
|
1936
|
+
sort: { name: "asc" }, offset: 0, limit: without.length,
|
|
1924
1937
|
});
|
|
1925
1938
|
assert(with_zero.length).equals(without.length);
|
|
1926
1939
|
assert(with_zero.map(r => r.name)).equals(without.map(r => r.name));
|
|
@@ -16,8 +16,17 @@ type X<T> = {
|
|
|
16
16
|
[K in keyof T]: T[K];
|
|
17
17
|
} & {};
|
|
18
18
|
type OrNull<T> = {
|
|
19
|
-
[P in keyof T]?: null | T[P];
|
|
19
|
+
[P in keyof T]?: null | T[P] | undefined;
|
|
20
20
|
};
|
|
21
|
+
type Optional<T> = {
|
|
22
|
+
[P in keyof T]?: T[P] | undefined;
|
|
23
|
+
};
|
|
24
|
+
type OptionalKeys<T> = {
|
|
25
|
+
[K in keyof T]-?: object extends Pick<T, K> ? K : never;
|
|
26
|
+
}[keyof T];
|
|
27
|
+
type UndefinedOptional<T> = X<Omit<T, OptionalKeys<T>> & {
|
|
28
|
+
[K in OptionalKeys<T>]?: T[K] | undefined;
|
|
29
|
+
}>;
|
|
21
30
|
type StringOperators = {
|
|
22
31
|
$like?: string;
|
|
23
32
|
$ilike?: string;
|
|
@@ -59,7 +68,7 @@ type Projected<T, S extends Select<T> | undefined> = S extends readonly (keyof T
|
|
|
59
68
|
type DefaultFields<T extends StoreInput> = {
|
|
60
69
|
[K in keyof InferRecord<T>]: T[K] extends DefaultType<any, any> ? K : never;
|
|
61
70
|
}[keyof InferRecord<T>];
|
|
62
|
-
type Insertable<T extends StoreInput> = Omit<InferRecord<T>, PrimaryKeyField<T> | DefaultFields<T>> &
|
|
71
|
+
type Insertable<T extends StoreInput> = UndefinedOptional<Omit<InferRecord<T>, PrimaryKeyField<T> | DefaultFields<T>> & Optional<Pick<InferRecord<T>, PrimaryKeyField<T> | DefaultFields<T>>>>;
|
|
63
72
|
type PrimaryKeyField<T extends StoreInput> = {
|
|
64
73
|
[K in keyof T]: T[K] extends PrimaryKey<any> ? K : never;
|
|
65
74
|
}[keyof T] & keyof InferRecord<T>;
|
|
@@ -24,6 +24,10 @@ function guard_options(options, allowed) {
|
|
|
24
24
|
throw E.option_unknown(k);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
function strip_undefined(record) {
|
|
28
|
+
return Object.fromEntries(Object.entries(record)
|
|
29
|
+
.filter(([, value]) => value !== undefined));
|
|
30
|
+
}
|
|
27
31
|
const INT_LIMITS = {
|
|
28
32
|
u8: [0, 255],
|
|
29
33
|
u16: [0, 65535],
|
|
@@ -512,15 +516,18 @@ export default class Store {
|
|
|
512
516
|
*/
|
|
513
517
|
async insert(record) {
|
|
514
518
|
assert.dict(record);
|
|
515
|
-
const prepared = this.#prepare_insert(record);
|
|
519
|
+
const prepared = this.#prepare_insert(strip_undefined(record));
|
|
516
520
|
return this.db.create(this.#as, this.#schema.parse(prepared));
|
|
517
521
|
}
|
|
518
522
|
async update(arg0, options) {
|
|
519
523
|
const by_pk = options !== undefined;
|
|
520
|
-
const
|
|
524
|
+
const input_set = by_pk
|
|
521
525
|
? options.set
|
|
522
526
|
: arg0.set;
|
|
523
|
-
if (!is.dict(
|
|
527
|
+
if (!is.dict(input_set))
|
|
528
|
+
throw E.set_empty();
|
|
529
|
+
const set = strip_undefined(input_set);
|
|
530
|
+
if (is.empty(set))
|
|
524
531
|
throw E.set_empty();
|
|
525
532
|
const pk = this.#pk;
|
|
526
533
|
if (pk !== null && pk in set)
|