@primate/core 0.6.2 → 0.7.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/lib/private/App.d.ts +76 -149
- package/lib/private/App.js +22 -6
- package/lib/private/Flags.d.ts +5 -3
- package/lib/private/Flags.js +4 -2
- package/lib/private/app/Facade.d.ts +77 -156
- package/lib/private/app/Facade.js +4 -1
- package/lib/private/build/App.d.ts +4 -2
- package/lib/private/build/App.js +13 -4
- package/lib/private/build/client/index.js +24 -10
- package/lib/private/build/client/plugin/routes.d.ts +4 -0
- package/lib/private/build/client/plugin/routes.js +74 -0
- package/lib/private/build/hook.js +14 -6
- package/lib/private/build/index.d.ts +3 -2
- package/lib/private/build/index.js +9 -15
- package/lib/private/build/preclient/index.d.ts +3 -0
- package/lib/private/build/preclient/index.js +69 -0
- package/lib/private/build/preclient/plugin/routes.d.ts +4 -0
- package/lib/private/build/preclient/plugin/routes.js +44 -0
- package/lib/private/build/server/index.js +8 -5
- package/lib/private/build/server/plugin/assets.js +3 -3
- package/lib/private/build/server/plugin/native-addons.js +6 -8
- package/lib/private/build/server/plugin/node-imports.js +5 -7
- package/lib/private/build/server/plugin/route-client.d.ts +4 -0
- package/lib/private/build/server/plugin/route-client.js +110 -0
- package/lib/private/build/server/plugin/route.js +3 -10
- package/lib/private/build/server/plugin/virtual-pages.js +3 -3
- package/lib/private/build/server/plugin/virtual-routes.d.ts +2 -1
- package/lib/private/build/server/plugin/virtual-routes.js +27 -8
- package/lib/private/build/server/plugin/wasm.js +3 -2
- package/lib/private/client/Data.d.ts +1 -1
- package/lib/private/client/boot.js +2 -2
- package/lib/private/client/create-form.d.ts +11 -1
- package/lib/private/client/create-form.js +21 -3
- package/lib/private/client/index.d.ts +2 -2
- package/lib/private/client/navigate.d.ts +1 -0
- package/lib/private/client/navigate.js +7 -6
- package/lib/private/client/submit.d.ts +2 -1
- package/lib/private/client/submit.js +8 -7
- package/lib/private/client/{http.d.ts → transport.d.ts} +3 -3
- package/lib/private/client/{http.js → transport.js} +7 -9
- package/lib/private/config/schema.d.ts +5 -13
- package/lib/private/config/schema.js +1 -5
- package/lib/private/db/DB.d.ts +3 -1
- package/lib/private/db/MemoryDB.d.ts +5 -2
- package/lib/private/db/MemoryDB.js +33 -19
- package/lib/private/db/SQLDB.d.ts +23 -0
- package/lib/private/db/SQLDB.js +2 -0
- package/lib/private/db/errors.d.ts +74 -7
- package/lib/private/db/errors.js +31 -7
- package/lib/private/db/migrate/apply.js +8 -9
- package/lib/private/db/migrate/bundle.js +3 -2
- package/lib/private/db/migrate/create.js +18 -20
- package/lib/private/db/migrate/status.js +9 -10
- package/lib/private/db/migrate/store.d.ts +3 -3
- package/lib/private/db/migrate/store.js +5 -5
- package/lib/private/db/test.js +256 -115
- package/lib/private/db/testSQL.d.ts +50 -0
- package/lib/private/db/testSQL.js +196 -0
- package/lib/private/errors.d.ts +66 -9
- package/lib/private/errors.js +37 -16
- package/lib/private/frontend.d.ts +4 -4
- package/lib/private/frontend.js +11 -8
- package/lib/private/i18n/errors.d.ts +7 -1
- package/lib/private/i18n/errors.js +1 -1
- package/lib/private/i18n/index/types.d.ts +1 -1
- package/lib/private/i18n/locale.d.ts +1 -1
- package/lib/private/i18n/module.js +6 -5
- package/lib/private/index.d.ts +10 -2
- package/lib/private/index.js +13 -1
- package/lib/private/logger.d.ts +24 -0
- package/lib/private/logger.js +66 -0
- package/lib/private/module/Setup.d.ts +3 -0
- package/lib/private/module/create.d.ts +2 -1
- package/lib/private/module/create.js +4 -0
- package/lib/private/paths.d.ts +2 -3
- package/lib/private/paths.js +6 -12
- package/lib/private/request/ContentType.d.ts +3 -0
- package/lib/private/request/ContentType.js +2 -0
- package/lib/private/request/RequestBag.d.ts +2 -18
- package/lib/private/request/RequestBag.js +4 -16
- package/lib/private/request/RequestBody.d.ts +20 -28
- package/lib/private/request/RequestBody.js +63 -86
- package/lib/private/request/RequestFacade.d.ts +2 -2
- package/lib/private/request/handle.js +2 -2
- package/lib/private/request/parse.js +2 -4
- package/lib/private/request/route.js +15 -8
- package/lib/private/response/binary.d.ts +2 -2
- package/lib/private/response/binary.js +6 -6
- package/lib/private/response/error.js +6 -2
- package/lib/private/response/json.js +2 -2
- package/lib/private/response/null.d.ts +3 -0
- package/lib/private/response/null.js +6 -0
- package/lib/private/response/redirect.js +4 -3
- package/lib/private/response/respond.js +4 -4
- package/lib/private/response/sse.js +2 -2
- package/lib/private/response/text.js +2 -2
- package/lib/private/route/ContentTypeMap.d.ts +10 -0
- package/lib/private/route/ContentTypeMap.js +2 -0
- package/lib/private/route/Handler.d.ts +3 -2
- package/lib/private/route/NarrowedRequest.d.ts +23 -0
- package/lib/private/route/NarrowedRequest.js +2 -0
- package/lib/private/route/Options.d.ts +6 -1
- package/lib/private/route/Path.d.ts +2 -2
- package/lib/private/route/hook.d.ts +3 -1
- package/lib/private/route/hook.js +1 -2
- package/lib/private/route/router.d.ts +7 -11
- package/lib/private/route/router.js +13 -20
- package/lib/private/route.client.d.ts +36 -0
- package/lib/private/route.client.js +8 -0
- package/lib/private/route.d.ts +21 -5
- package/lib/private/route.js +21 -5
- package/lib/private/serve/App.d.ts +1 -2
- package/lib/private/serve/App.js +64 -58
- package/lib/private/serve/Init.d.ts +2 -0
- package/lib/private/serve/dev-module.js +2 -3
- package/lib/private/serve/index.js +5 -6
- package/lib/private/server/TAG.d.ts +1 -1
- package/lib/private/server/TAG.js +1 -1
- package/lib/private/session/index.d.ts +1 -1
- package/lib/private/session/index.js +3 -2
- package/lib/private/session/module.js +3 -3
- package/lib/private/session/schema.d.ts +3 -3
- package/lib/private/session/schema.js +4 -3
- package/lib/private/store/ExtractRelation.d.ts +7 -0
- package/lib/private/store/ExtractRelation.js +2 -0
- package/lib/private/store/ExtractSchema.d.ts +10 -0
- package/lib/private/{orm → store}/ForeignKey.d.ts +1 -1
- package/lib/private/store/Init.d.ts +13 -0
- package/lib/private/store/Init.js +2 -0
- package/lib/private/{orm/store.d.ts → store/Store.d.ts} +50 -50
- package/lib/private/{orm/store.js → store/Store.js} +163 -107
- package/lib/private/store/StoreInput.d.ts +11 -0
- package/lib/private/store/key.d.ts +8 -0
- package/lib/private/store/key.js +8 -0
- package/lib/private/{orm → store}/parse.d.ts +3 -3
- package/lib/private/{orm → store}/parse.js +7 -2
- package/lib/private/store/relation.d.ts +29 -0
- package/lib/private/store/relation.js +26 -0
- package/lib/private/store.d.ts +24 -0
- package/lib/private/store.js +10 -0
- package/lib/public/db/errors.d.ts +1 -1
- package/lib/public/db/errors.js +1 -1
- package/lib/public/db/testSQL.d.ts +2 -0
- package/lib/public/db/testSQL.js +2 -0
- package/lib/public/db.d.ts +1 -0
- package/lib/public/index.d.ts +1 -0
- package/lib/public/index.js +1 -1
- package/lib/public/response.d.ts +6 -6
- package/lib/public/response.js +4 -1
- package/lib/public/route.client.d.ts +2 -0
- package/lib/public/route.client.js +2 -0
- package/lib/public/store.d.ts +2 -0
- package/lib/public/store.js +2 -0
- package/package.json +24 -17
- package/lib/private/bye.d.ts +0 -3
- package/lib/private/bye.js +0 -4
- package/lib/private/log.d.ts +0 -20
- package/lib/private/log.js +0 -47
- package/lib/private/orm/ExtractSchema.d.ts +0 -9
- package/lib/private/orm/StoreInput.d.ts +0 -10
- package/lib/private/orm/key.d.ts +0 -8
- package/lib/private/orm/key.js +0 -8
- package/lib/private/orm/relation.d.ts +0 -43
- package/lib/private/orm/relation.js +0 -26
- package/lib/private/request/Verb.d.ts +0 -4
- package/lib/private/request/Verb.js +0 -2
- package/lib/private/request/verbs.d.ts +0 -3
- package/lib/private/request/verbs.js +0 -12
- package/lib/private/route/wrap.d.ts +0 -2
- package/lib/private/route/wrap.js +0 -12
- package/lib/public/log.d.ts +0 -2
- package/lib/public/log.js +0 -2
- package/lib/public/orm/key.d.ts +0 -2
- package/lib/public/orm/key.js +0 -2
- package/lib/public/orm/relation.d.ts +0 -2
- package/lib/public/orm/relation.js +0 -2
- package/lib/public/orm/store.d.ts +0 -2
- package/lib/public/orm/store.js +0 -2
- package/lib/public/request/verbs.d.ts +0 -2
- package/lib/public/request/verbs.js +0 -2
- /package/lib/private/{orm → store}/ExtractSchema.js +0 -0
- /package/lib/private/{orm → store}/ForeignKey.js +0 -0
- /package/lib/private/{orm → store}/PrimaryKey.d.ts +0 -0
- /package/lib/private/{orm → store}/PrimaryKey.js +0 -0
- /package/lib/private/{orm → store}/StoreInput.js +0 -0
package/lib/private/db/test.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Code } from "#db/errors";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import store from "#store";
|
|
3
|
+
import key from "#store/key";
|
|
4
|
+
import relation from "#store/relation";
|
|
5
|
+
import { CodeError } from "@rcompat/error";
|
|
5
6
|
import test from "@rcompat/test";
|
|
6
7
|
import any from "@rcompat/test/any";
|
|
7
8
|
import p from "pema";
|
|
@@ -101,13 +102,15 @@ async function throws(assert, code, fn) {
|
|
|
101
102
|
assert(false).true();
|
|
102
103
|
}
|
|
103
104
|
catch (error) {
|
|
104
|
-
|
|
105
|
+
if (CodeError.is(error)) {
|
|
106
|
+
assert(error.code).equals(code);
|
|
107
|
+
}
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
export default (db) => {
|
|
108
111
|
test.ended(() => db.close());
|
|
109
112
|
const Post = store({
|
|
110
|
-
|
|
113
|
+
table: "post",
|
|
111
114
|
db,
|
|
112
115
|
schema: {
|
|
113
116
|
id: key.primary(p.uuid),
|
|
@@ -116,8 +119,11 @@ export default (db) => {
|
|
|
116
119
|
},
|
|
117
120
|
});
|
|
118
121
|
Post.update;
|
|
122
|
+
test.case("#table typed from table name", assert => {
|
|
123
|
+
assert(Post.table).type();
|
|
124
|
+
});
|
|
119
125
|
const User = store({
|
|
120
|
-
|
|
126
|
+
table: "user",
|
|
121
127
|
db,
|
|
122
128
|
schema: {
|
|
123
129
|
id: key.primary(p.uuid),
|
|
@@ -127,7 +133,7 @@ export default (db) => {
|
|
|
127
133
|
},
|
|
128
134
|
});
|
|
129
135
|
const UserN = store({
|
|
130
|
-
|
|
136
|
+
table: "user_n",
|
|
131
137
|
db,
|
|
132
138
|
schema: {
|
|
133
139
|
id: key.primary(p.u32),
|
|
@@ -137,7 +143,7 @@ export default (db) => {
|
|
|
137
143
|
},
|
|
138
144
|
});
|
|
139
145
|
const UserB = store({
|
|
140
|
-
|
|
146
|
+
table: "user_b",
|
|
141
147
|
db,
|
|
142
148
|
schema: {
|
|
143
149
|
id: key.primary(p.u128),
|
|
@@ -148,10 +154,11 @@ export default (db) => {
|
|
|
148
154
|
});
|
|
149
155
|
const USER_STORES = [User, UserN, UserB];
|
|
150
156
|
const Type = store({
|
|
151
|
-
|
|
157
|
+
table: "type",
|
|
152
158
|
db,
|
|
153
159
|
schema: {
|
|
154
160
|
id: key.primary(p.uuid),
|
|
161
|
+
blob: p.blob.optional(),
|
|
155
162
|
boolean: p.boolean.optional(),
|
|
156
163
|
date: p.date.optional(),
|
|
157
164
|
f32: p.f32.optional(),
|
|
@@ -173,7 +180,7 @@ export default (db) => {
|
|
|
173
180
|
// this stresses identifier quoting in CREATE/INSERT/SELECT/UPDATE/DELETE
|
|
174
181
|
const Reserved = store({
|
|
175
182
|
// deliberately reserved-like table name
|
|
176
|
-
|
|
183
|
+
table: "select",
|
|
177
184
|
db,
|
|
178
185
|
schema: {
|
|
179
186
|
id: key.primary(p.uuid),
|
|
@@ -182,28 +189,14 @@ export default (db) => {
|
|
|
182
189
|
name: p.string,
|
|
183
190
|
},
|
|
184
191
|
});
|
|
185
|
-
const AuthorSchema = {
|
|
186
|
-
id: key.primary(p.uuid),
|
|
187
|
-
name: p.string,
|
|
188
|
-
};
|
|
189
|
-
const ArticleSchema = {
|
|
190
|
-
id: key.primary(p.uuid),
|
|
191
|
-
title: p.string,
|
|
192
|
-
author_id: key.foreign(p.uuid),
|
|
193
|
-
};
|
|
194
|
-
const ProfileSchema = {
|
|
195
|
-
id: key.primary(p.uuid),
|
|
196
|
-
bio: p.string,
|
|
197
|
-
url: p.url.optional(),
|
|
198
|
-
author_id: key.foreign(p.uuid),
|
|
199
|
-
};
|
|
200
192
|
const Author = store({
|
|
193
|
+
table: "author",
|
|
201
194
|
db,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
articles: relation.many(
|
|
206
|
-
profile: relation.one(
|
|
195
|
+
schema: {
|
|
196
|
+
id: key.primary(p.uuid),
|
|
197
|
+
name: p.string,
|
|
198
|
+
articles: relation.many({ table: "article", by: "author_id" }),
|
|
199
|
+
profile: relation.one({ table: "profile", by: "author_id" }),
|
|
207
200
|
},
|
|
208
201
|
});
|
|
209
202
|
/*const AuthorWithJSON = store({
|
|
@@ -217,36 +210,41 @@ export default (db) => {
|
|
|
217
210
|
},
|
|
218
211
|
});*/
|
|
219
212
|
const Article = store({
|
|
213
|
+
table: "article",
|
|
220
214
|
db,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
215
|
+
schema: {
|
|
216
|
+
id: key.primary(p.uuid),
|
|
217
|
+
title: p.string,
|
|
218
|
+
author_id: key.foreign(p.uuid),
|
|
219
|
+
author: relation.one({ table: "author", by: "author_id", reverse: true }),
|
|
225
220
|
},
|
|
226
221
|
});
|
|
227
222
|
const Profile = store({
|
|
228
223
|
db,
|
|
229
|
-
|
|
230
|
-
schema:
|
|
231
|
-
|
|
232
|
-
|
|
224
|
+
table: "profile",
|
|
225
|
+
schema: {
|
|
226
|
+
id: key.primary(p.uuid),
|
|
227
|
+
bio: p.string,
|
|
228
|
+
url: p.url.optional(),
|
|
229
|
+
author_id: key.foreign(p.uuid),
|
|
230
|
+
author: relation.one({ table: "author", by: "author_id", reverse: true }),
|
|
233
231
|
},
|
|
234
232
|
});
|
|
235
233
|
function $store(label, s, body) {
|
|
236
234
|
test.case(label, async (assert) => {
|
|
237
|
-
await s.
|
|
235
|
+
await s.create();
|
|
238
236
|
try {
|
|
239
237
|
await body(assert);
|
|
240
238
|
}
|
|
241
239
|
finally {
|
|
242
|
-
await s.
|
|
240
|
+
await s.drop();
|
|
243
241
|
}
|
|
244
242
|
});
|
|
245
243
|
}
|
|
246
244
|
function $user(label, body) {
|
|
247
245
|
test.case(label, async (assert) => {
|
|
248
246
|
for (const S of USER_STORES)
|
|
249
|
-
await S.
|
|
247
|
+
await S.create();
|
|
250
248
|
try {
|
|
251
249
|
for (const u of Object.values(USERS)) {
|
|
252
250
|
for (const S of USER_STORES)
|
|
@@ -256,7 +254,7 @@ export default (db) => {
|
|
|
256
254
|
}
|
|
257
255
|
finally {
|
|
258
256
|
for (const S of USER_STORES)
|
|
259
|
-
await S.
|
|
257
|
+
await S.drop();
|
|
260
258
|
}
|
|
261
259
|
});
|
|
262
260
|
}
|
|
@@ -268,9 +266,9 @@ export default (db) => {
|
|
|
268
266
|
}
|
|
269
267
|
function $rel(label, body) {
|
|
270
268
|
test.case(`relation: ${label}`, async (assert) => {
|
|
271
|
-
await Author.
|
|
272
|
-
await Article.
|
|
273
|
-
await Profile.
|
|
269
|
+
await Author.create();
|
|
270
|
+
await Article.create();
|
|
271
|
+
await Profile.create();
|
|
274
272
|
try {
|
|
275
273
|
const john = await Author.insert({ name: "John" });
|
|
276
274
|
const bob = await Author.insert({ name: "Bob" });
|
|
@@ -296,9 +294,9 @@ export default (db) => {
|
|
|
296
294
|
await body(assert);
|
|
297
295
|
}
|
|
298
296
|
finally {
|
|
299
|
-
await Profile.
|
|
300
|
-
await Article.
|
|
301
|
-
await Author.
|
|
297
|
+
await Profile.drop();
|
|
298
|
+
await Article.drop();
|
|
299
|
+
await Author.drop();
|
|
302
300
|
}
|
|
303
301
|
});
|
|
304
302
|
}
|
|
@@ -317,13 +315,13 @@ export default (db) => {
|
|
|
317
315
|
});
|
|
318
316
|
}
|
|
319
317
|
function bad_where(label, mk, expected) {
|
|
320
|
-
return security_pair("where", label, mk, base => User.find({ where: base }), w => Author.find({ with: { articles: { where: w } } }), expected);
|
|
318
|
+
return security_pair("where", label, mk, base => User.find({ where: base }), w => Author.find({ with: { articles: { store: Article, where: w } } }), expected);
|
|
321
319
|
}
|
|
322
320
|
function bad_select(label, mk, expected) {
|
|
323
|
-
return security_pair("find", label, mk, base => User.find({ select: base }), w => Author.find({ with: { articles: { select: w } } }), expected);
|
|
321
|
+
return security_pair("find", label, mk, base => User.find({ select: base }), w => Author.find({ with: { articles: { store: Article, select: w } } }), expected);
|
|
324
322
|
}
|
|
325
323
|
function bad_sort(label, mk, expected) {
|
|
326
|
-
return security_pair("find", label, mk, base => User.find({ sort: base }), w => Author.find({ with: { articles: { sort: w } } }), expected);
|
|
324
|
+
return security_pair("find", label, mk, base => User.find({ sort: base }), w => Author.find({ with: { articles: { store: Article, sort: w } } }), expected);
|
|
327
325
|
}
|
|
328
326
|
$store("insert", User, async (assert) => {
|
|
329
327
|
const donald = await User.insert({ age: 30, name: "Donald" });
|
|
@@ -349,7 +347,7 @@ export default (db) => {
|
|
|
349
347
|
assert(await UserB.has(user.id)).true();
|
|
350
348
|
});
|
|
351
349
|
const ManualUser = store({
|
|
352
|
-
|
|
350
|
+
table: "manual_user",
|
|
353
351
|
db,
|
|
354
352
|
schema: {
|
|
355
353
|
id: key.primary(p.uuid, { generate: false }),
|
|
@@ -377,6 +375,12 @@ export default (db) => {
|
|
|
377
375
|
return User.insert({ name: "Nullman", lastname: null });
|
|
378
376
|
});
|
|
379
377
|
});
|
|
378
|
+
$store("insert: explicit undefined on optional field is allowed", User, async (assert) => {
|
|
379
|
+
const u = await User.insert({ name: "Test", lastname: undefined });
|
|
380
|
+
assert(await User.has(u.id)).true();
|
|
381
|
+
const [got] = await User.find({ where: { id: u.id } });
|
|
382
|
+
assert(got.lastname).undefined();
|
|
383
|
+
});
|
|
380
384
|
$user("find: empty object equals no options", async (assert) => {
|
|
381
385
|
const a = await User.find();
|
|
382
386
|
const b = await User.find({});
|
|
@@ -853,7 +857,7 @@ export default (db) => {
|
|
|
853
857
|
const articles = await Article.find();
|
|
854
858
|
const article = await Article.get(articles[0].id, {
|
|
855
859
|
with: {
|
|
856
|
-
author:
|
|
860
|
+
author: Author,
|
|
857
861
|
},
|
|
858
862
|
});
|
|
859
863
|
assert(article).type();
|
|
@@ -862,7 +866,7 @@ export default (db) => {
|
|
|
862
866
|
});
|
|
863
867
|
$rel("get with many", async (assert) => {
|
|
864
868
|
const [first] = await Author.find({ where: { name: "John" } });
|
|
865
|
-
const john = await Author.get(first.id, { with: { articles:
|
|
869
|
+
const john = await Author.get(first.id, { with: { articles: Article } });
|
|
866
870
|
assert(john.articles).type();
|
|
867
871
|
const titles = john.articles.map(a => a.title);
|
|
868
872
|
assert(titles.includes("John First Post")).true();
|
|
@@ -870,19 +874,19 @@ export default (db) => {
|
|
|
870
874
|
});
|
|
871
875
|
$rel("get by id", async (assert) => {
|
|
872
876
|
const [first] = await Author.find({ where: { name: "John" } });
|
|
873
|
-
const john = await Author.get(first.id, { with: { profile:
|
|
877
|
+
const john = await Author.get(first.id, { with: { profile: Profile } });
|
|
874
878
|
assert(john.profile).not.null();
|
|
875
879
|
assert(john.profile?.bio).equals("John is a writer");
|
|
876
880
|
});
|
|
877
881
|
$rel("get by id returns null when missing", async (assert) => {
|
|
878
882
|
const [first] = await Author.find({ where: { name: "Bob" } });
|
|
879
|
-
const bob = await Author.get(first.id, { with: { profile:
|
|
883
|
+
const bob = await Author.get(first.id, { with: { profile: Profile } });
|
|
880
884
|
assert(bob.profile).null();
|
|
881
885
|
});
|
|
882
886
|
$rel("find", async (assert) => {
|
|
883
887
|
const articles = await Article.find({
|
|
884
888
|
where: { title: { $like: "% Post" } },
|
|
885
|
-
with: { author:
|
|
889
|
+
with: { author: Author },
|
|
886
890
|
sort: { title: "asc" },
|
|
887
891
|
});
|
|
888
892
|
assert(articles.length).equals(3);
|
|
@@ -893,14 +897,14 @@ export default (db) => {
|
|
|
893
897
|
});
|
|
894
898
|
$rel("try", async (assert) => {
|
|
895
899
|
const [first] = await Article.find();
|
|
896
|
-
const article = await Article.try(first.id, { with: { author:
|
|
900
|
+
const article = await Article.try(first.id, { with: { author: Author } });
|
|
897
901
|
assert(article).defined();
|
|
898
902
|
assert(article?.author).not.null();
|
|
899
903
|
});
|
|
900
904
|
$rel("many returns empty array when no matches", async (assert) => {
|
|
901
905
|
// insert author with no articles
|
|
902
906
|
const lonely = await Author.insert({ name: "Lonely" });
|
|
903
|
-
const author = await Author.get(lonely.id, { with: { articles:
|
|
907
|
+
const author = await Author.get(lonely.id, { with: { articles: Article } });
|
|
904
908
|
assert(author.articles).type();
|
|
905
909
|
assert(author.articles.length).equals(0);
|
|
906
910
|
});
|
|
@@ -908,8 +912,8 @@ export default (db) => {
|
|
|
908
912
|
const authors = await Author.find({ where: { name: "John" } });
|
|
909
913
|
const john = await Author.get(authors[0].id, {
|
|
910
914
|
with: {
|
|
911
|
-
articles:
|
|
912
|
-
profile:
|
|
915
|
+
articles: Article,
|
|
916
|
+
profile: Profile,
|
|
913
917
|
},
|
|
914
918
|
});
|
|
915
919
|
const titles = john.articles.map(a => a.title);
|
|
@@ -931,12 +935,13 @@ export default (db) => {
|
|
|
931
935
|
limit: 20,
|
|
932
936
|
with: {
|
|
933
937
|
articles: {
|
|
938
|
+
store: Article,
|
|
934
939
|
where: { title: { $like: "%foo%" } },
|
|
935
940
|
select: ["id", "title"],
|
|
936
941
|
sort: { title: "asc" },
|
|
937
942
|
limit: SUBLIMIT,
|
|
938
943
|
},
|
|
939
|
-
profile:
|
|
944
|
+
profile: Profile,
|
|
940
945
|
},
|
|
941
946
|
});
|
|
942
947
|
assert(authors).type();
|
|
@@ -1008,8 +1013,8 @@ export default (db) => {
|
|
|
1008
1013
|
const rows = await Author.find({
|
|
1009
1014
|
select: ["id"],
|
|
1010
1015
|
with: {
|
|
1011
|
-
profile: { select: ["bio"] },
|
|
1012
|
-
articles: { select: ["title"] },
|
|
1016
|
+
profile: { store: Profile, select: ["bio"] },
|
|
1017
|
+
articles: { store: Article, select: ["title"] },
|
|
1013
1018
|
},
|
|
1014
1019
|
});
|
|
1015
1020
|
assert(rows).type();
|
|
@@ -1031,15 +1036,15 @@ export default (db) => {
|
|
|
1031
1036
|
$rel("get/try: missing id (with relations)", async (assert) => {
|
|
1032
1037
|
//const missing = `missing-${Date.now()}-${Math.random()}`;
|
|
1033
1038
|
const missing = "00000000-0000-4000-8000-000000000000";
|
|
1034
|
-
await throws(assert, Code.record_not_found, () => Article.get(missing, { with: { author:
|
|
1035
|
-
assert(await Article.try(missing, { with: { author:
|
|
1039
|
+
await throws(assert, Code.record_not_found, () => Article.get(missing, { with: { author: Author } }));
|
|
1040
|
+
assert(await Article.try(missing, { with: { author: Author } })).undefined();
|
|
1036
1041
|
});
|
|
1037
1042
|
$rel("get/try: missing id (+ parent)", async (assert) => {
|
|
1038
1043
|
const missing = "00000000-0000-4000-8000-000000000000";
|
|
1039
1044
|
//const missing = `missing-${Date.now()}-${Math.random()}`;
|
|
1040
|
-
await throws(assert, Code.record_not_found, () => Author.get(missing, { with: { articles:
|
|
1045
|
+
await throws(assert, Code.record_not_found, () => Author.get(missing, { with: { articles: Article, profile: Profile } }));
|
|
1041
1046
|
assert(await Author.try(missing, {
|
|
1042
|
-
with: { articles:
|
|
1047
|
+
with: { articles: Article, profile: Profile },
|
|
1043
1048
|
})).undefined();
|
|
1044
1049
|
});
|
|
1045
1050
|
$user("try: does not swallow invalid options", async (assert) => {
|
|
@@ -1353,6 +1358,7 @@ export default (db) => {
|
|
|
1353
1358
|
return Author.find({
|
|
1354
1359
|
with: {
|
|
1355
1360
|
articles: {
|
|
1361
|
+
store: Article,
|
|
1356
1362
|
select: {}, // must be array
|
|
1357
1363
|
},
|
|
1358
1364
|
},
|
|
@@ -1364,19 +1370,60 @@ export default (db) => {
|
|
|
1364
1370
|
return Author.find({
|
|
1365
1371
|
with: {
|
|
1366
1372
|
articles: {
|
|
1373
|
+
store: Article,
|
|
1367
1374
|
limit: -1, // must be uint
|
|
1368
1375
|
},
|
|
1369
1376
|
},
|
|
1370
1377
|
});
|
|
1371
1378
|
});
|
|
1372
1379
|
});
|
|
1380
|
+
$rel$("with: rejects store with wrong table", async (assert) => {
|
|
1381
|
+
// runtime check
|
|
1382
|
+
await throws(assert, Code.relation_table_mismatch, () => {
|
|
1383
|
+
return Author.find({
|
|
1384
|
+
with: {
|
|
1385
|
+
// Profile.table === "profile", expected "article"
|
|
1386
|
+
articles: Profile,
|
|
1387
|
+
},
|
|
1388
|
+
});
|
|
1389
|
+
});
|
|
1390
|
+
});
|
|
1391
|
+
$rel$("with: wrong table store is a type error", async (assert) => {
|
|
1392
|
+
await throws(assert, Code.relation_table_mismatch, () => {
|
|
1393
|
+
return Author.find({
|
|
1394
|
+
with: {
|
|
1395
|
+
// @ts-expect-error — Profile.table === "profile", expected "article"
|
|
1396
|
+
articles: Profile,
|
|
1397
|
+
},
|
|
1398
|
+
});
|
|
1399
|
+
});
|
|
1400
|
+
});
|
|
1401
|
+
test.case("with: missing store in query object is a type error", async (assert) => {
|
|
1402
|
+
await throws(assert, Code.relation_store_required, () => {
|
|
1403
|
+
return Author.find({
|
|
1404
|
+
with: {
|
|
1405
|
+
// @ts-expect-error — store is required in WithQuery
|
|
1406
|
+
articles: { where: { title: "foo" } },
|
|
1407
|
+
},
|
|
1408
|
+
});
|
|
1409
|
+
});
|
|
1410
|
+
});
|
|
1411
|
+
test.case("with: missing store throws at runtime", async (assert) => {
|
|
1412
|
+
await throws(assert, Code.relation_store_required, () => {
|
|
1413
|
+
return Author.find({
|
|
1414
|
+
with: {
|
|
1415
|
+
articles: { where: { title: "foo" } },
|
|
1416
|
+
},
|
|
1417
|
+
});
|
|
1418
|
+
});
|
|
1419
|
+
});
|
|
1373
1420
|
$rel("one relation returns null when FK missing", async (assert) => {
|
|
1374
1421
|
const author_id = "4d0996db-bda9-4f95-ad7c-7075b10d4ba6";
|
|
1375
1422
|
const orphan = await Article.insert({
|
|
1376
1423
|
title: "Orphan",
|
|
1377
1424
|
author_id,
|
|
1378
1425
|
});
|
|
1379
|
-
const got = await Article.get(orphan.id, { with: { author:
|
|
1426
|
+
const got = await Article.get(orphan.id, { with: { author: Author } });
|
|
1380
1427
|
assert(got.author).null();
|
|
1381
1428
|
});
|
|
1382
1429
|
$user("get/try: happy path", async (assert) => {
|
|
@@ -1426,7 +1473,7 @@ export default (db) => {
|
|
|
1426
1473
|
$user$("inject invalid identifier (table name)", async (assert) => {
|
|
1427
1474
|
await throws(assert, Code.identifier_invalid, () => {
|
|
1428
1475
|
store({
|
|
1429
|
-
|
|
1476
|
+
table: "users; DROP TABLE users",
|
|
1430
1477
|
db,
|
|
1431
1478
|
schema: {
|
|
1432
1479
|
id: key.primary(p.uuid),
|
|
@@ -1457,7 +1504,11 @@ export default (db) => {
|
|
|
1457
1504
|
$rel("with + select (no id)", async (assert) => {
|
|
1458
1505
|
const rows = await Author.find({
|
|
1459
1506
|
select: ["name"],
|
|
1460
|
-
with: {
|
|
1507
|
+
with: {
|
|
1508
|
+
articles: {
|
|
1509
|
+
store: Article, select: ["title"], sort: { title: "asc" }
|
|
1510
|
+
},
|
|
1511
|
+
},
|
|
1461
1512
|
sort: { name: "asc" },
|
|
1462
1513
|
});
|
|
1463
1514
|
// no id leaked
|
|
@@ -1471,7 +1522,7 @@ export default (db) => {
|
|
|
1471
1522
|
$rel("with: relation fields are decoded (URL)", async (assert) => {
|
|
1472
1523
|
const [first] = await Author.find({ where: { name: "John" } });
|
|
1473
1524
|
const john = await Author.get(first.id, {
|
|
1474
|
-
with: { profile:
|
|
1525
|
+
with: { profile: Profile },
|
|
1475
1526
|
});
|
|
1476
1527
|
assert(john.profile).not.null();
|
|
1477
1528
|
// this is the whole point: driver must unbind it
|
|
@@ -1483,40 +1534,34 @@ export default (db) => {
|
|
|
1483
1534
|
const [row] = await Article.find({ select: ["id"] });
|
|
1484
1535
|
const got = await Article.get(row.id, {
|
|
1485
1536
|
select: ["title"], // intentionally omit author_id
|
|
1486
|
-
with: { author:
|
|
1537
|
+
with: { author: Author },
|
|
1487
1538
|
});
|
|
1488
1539
|
assert("author_id" in got).false(); // stripped
|
|
1489
1540
|
assert(got.author).not.null();
|
|
1490
1541
|
});
|
|
1491
1542
|
$rel("with: joined relations decode URL fields (base + rel)", async (assert) => {
|
|
1492
|
-
const ParentSchema = {
|
|
1493
|
-
id: key.primary(p.uuid),
|
|
1494
|
-
name: p.string,
|
|
1495
|
-
url: p.url.optional(),
|
|
1496
|
-
};
|
|
1497
|
-
const ChildSchema = {
|
|
1498
|
-
id: key.primary(p.uuid),
|
|
1499
|
-
parent_id: key.foreign(p.uuid),
|
|
1500
|
-
url: p.url.optional(),
|
|
1501
|
-
};
|
|
1502
1543
|
const Parent = store({
|
|
1503
|
-
|
|
1544
|
+
table: "j_parent",
|
|
1504
1545
|
db,
|
|
1505
|
-
schema:
|
|
1506
|
-
|
|
1507
|
-
|
|
1546
|
+
schema: {
|
|
1547
|
+
id: key.primary(p.uuid),
|
|
1548
|
+
name: p.string,
|
|
1549
|
+
url: p.url.optional(),
|
|
1550
|
+
children: relation.many({ table: "j_child", by: "parent_id" }),
|
|
1508
1551
|
},
|
|
1509
1552
|
});
|
|
1510
1553
|
const Child = store({
|
|
1511
1554
|
db,
|
|
1512
|
-
|
|
1513
|
-
schema:
|
|
1514
|
-
|
|
1515
|
-
|
|
1555
|
+
table: "j_child",
|
|
1556
|
+
schema: {
|
|
1557
|
+
id: key.primary(p.uuid),
|
|
1558
|
+
parent_id: key.foreign(p.uuid),
|
|
1559
|
+
url: p.url.optional(),
|
|
1560
|
+
parent: relation.one({ table: "j_parent", by: "parent_id", reverse: true }),
|
|
1516
1561
|
},
|
|
1517
1562
|
});
|
|
1518
|
-
await Parent.
|
|
1519
|
-
await Child.
|
|
1563
|
+
await Parent.create();
|
|
1564
|
+
await Child.create();
|
|
1520
1565
|
try {
|
|
1521
1566
|
const p0 = await Parent.insert({
|
|
1522
1567
|
name: "P0",
|
|
@@ -1530,7 +1575,7 @@ export default (db) => {
|
|
|
1530
1575
|
const [got] = await Parent.find({
|
|
1531
1576
|
where: { id: p0.id },
|
|
1532
1577
|
select: ["url"],
|
|
1533
|
-
with: { children:
|
|
1578
|
+
with: { children: Child },
|
|
1534
1579
|
});
|
|
1535
1580
|
assert(got).defined();
|
|
1536
1581
|
// pk must not leak
|
|
@@ -1548,15 +1593,15 @@ export default (db) => {
|
|
|
1548
1593
|
assert(c0.url.href).equals("https://example.com/joined");
|
|
1549
1594
|
}
|
|
1550
1595
|
finally {
|
|
1551
|
-
await Child.
|
|
1552
|
-
await Parent.
|
|
1596
|
+
await Child.drop();
|
|
1597
|
+
await Parent.drop();
|
|
1553
1598
|
}
|
|
1554
1599
|
});
|
|
1555
1600
|
$rel("find: limit applies to parent rows, not joined rows", async (assert) => {
|
|
1556
1601
|
const authors = await Author.find({
|
|
1557
1602
|
limit: 2,
|
|
1558
1603
|
sort: { name: "asc" },
|
|
1559
|
-
with: { articles:
|
|
1604
|
+
with: { articles: Article },
|
|
1560
1605
|
});
|
|
1561
1606
|
assert(authors.length).equals(2);
|
|
1562
1607
|
});
|
|
@@ -1572,19 +1617,6 @@ export default (db) => {
|
|
|
1572
1617
|
for (const c of BAD_WHERE_COLUMN) {
|
|
1573
1618
|
bad_where(c.label, () => ({ base: c.base, with: c.with }), c.expected);
|
|
1574
1619
|
}
|
|
1575
|
-
test.case("store: relation name conflicts with field throws", async (assert) => {
|
|
1576
|
-
await throws(assert, Code.relation_conflicts_with_field, async () => store({
|
|
1577
|
-
name: "conflict",
|
|
1578
|
-
db,
|
|
1579
|
-
schema: {
|
|
1580
|
-
id: key.primary(p.uuid),
|
|
1581
|
-
articles: p.string,
|
|
1582
|
-
},
|
|
1583
|
-
relations: {
|
|
1584
|
-
articles: relation.many(ArticleSchema, "author_id"),
|
|
1585
|
-
},
|
|
1586
|
-
}));
|
|
1587
|
-
});
|
|
1588
1620
|
// introspect: returns null for non-existent table
|
|
1589
1621
|
test.case("schema: introspect non-existent", async (assert) => {
|
|
1590
1622
|
assert(await db.schema.introspect("no_such_table")).null();
|
|
@@ -1643,7 +1675,7 @@ export default (db) => {
|
|
|
1643
1675
|
$store("schema: introspect after create", User, async (assert) => {
|
|
1644
1676
|
// insert a record so MongoDB has something to sample
|
|
1645
1677
|
await User.insert({ name: "test", age: 1, lastname: "test" });
|
|
1646
|
-
const types = await db.schema.introspect(User.
|
|
1678
|
+
const types = await db.schema.introspect(User.table, User.pk);
|
|
1647
1679
|
assert(types).not.null();
|
|
1648
1680
|
if (types !== null) {
|
|
1649
1681
|
assert(types.id).includes("uuid");
|
|
@@ -1658,7 +1690,7 @@ export default (db) => {
|
|
|
1658
1690
|
add: { email: "string" }, drop: [], rename: [],
|
|
1659
1691
|
});
|
|
1660
1692
|
const MigratedUser = store({
|
|
1661
|
-
|
|
1693
|
+
table: "user",
|
|
1662
1694
|
db,
|
|
1663
1695
|
schema: {
|
|
1664
1696
|
id: key.primary(p.uuid),
|
|
@@ -1684,7 +1716,7 @@ export default (db) => {
|
|
|
1684
1716
|
add: {}, drop: [], rename: [["lastname", "surname"]],
|
|
1685
1717
|
});
|
|
1686
1718
|
const MigratedUser = store({
|
|
1687
|
-
|
|
1719
|
+
table: "user",
|
|
1688
1720
|
db,
|
|
1689
1721
|
schema: {
|
|
1690
1722
|
id: key.primary(p.uuid),
|
|
@@ -1704,7 +1736,7 @@ export default (db) => {
|
|
|
1704
1736
|
add: { email: "string" }, drop: ["lastname"], rename: [],
|
|
1705
1737
|
});
|
|
1706
1738
|
const MigratedUser = store({
|
|
1707
|
-
|
|
1739
|
+
table: "user",
|
|
1708
1740
|
db,
|
|
1709
1741
|
schema: {
|
|
1710
1742
|
id: key.primary(p.uuid),
|
|
@@ -1719,7 +1751,7 @@ export default (db) => {
|
|
|
1719
1751
|
assert(types.lastname).undefined();
|
|
1720
1752
|
});
|
|
1721
1753
|
const UUIDTest = store({
|
|
1722
|
-
|
|
1754
|
+
table: "uuid_test",
|
|
1723
1755
|
db,
|
|
1724
1756
|
schema: {
|
|
1725
1757
|
id: key.primary(p.uuid),
|
|
@@ -1746,5 +1778,114 @@ export default (db) => {
|
|
|
1746
1778
|
add: { email: "string" }, drop: [], rename: [],
|
|
1747
1779
|
}));
|
|
1748
1780
|
});
|
|
1781
|
+
$user("find: $in on string field", async (assert) => {
|
|
1782
|
+
const rows = await User.find({
|
|
1783
|
+
where: { name: { $in: ["Donald", "Ryan"] } },
|
|
1784
|
+
sort: { name: "asc" },
|
|
1785
|
+
});
|
|
1786
|
+
assert(rows.length).equals(2);
|
|
1787
|
+
assert(rows[0].name).equals("Donald");
|
|
1788
|
+
assert(rows[1].name).equals("Ryan");
|
|
1789
|
+
});
|
|
1790
|
+
$user("find: $in on number field", async (assert) => {
|
|
1791
|
+
const rows = await User.find({
|
|
1792
|
+
where: { age: { $in: [30, 40] } },
|
|
1793
|
+
sort: { age: "asc" },
|
|
1794
|
+
});
|
|
1795
|
+
assert(rows.length).equals(3);
|
|
1796
|
+
assert(rows.map(r => r.age)).equals([30, 40, 40]);
|
|
1797
|
+
});
|
|
1798
|
+
$user("find: $in on uuid field", async (assert) => {
|
|
1799
|
+
const [donald] = await User.find({ where: { name: "Donald" } });
|
|
1800
|
+
const [ryan] = await User.find({ where: { name: "Ryan" } });
|
|
1801
|
+
const rows = await User.find({
|
|
1802
|
+
where: { id: { $in: [donald.id, ryan.id] } },
|
|
1803
|
+
sort: { name: "asc" },
|
|
1804
|
+
});
|
|
1805
|
+
assert(rows.length).equals(2);
|
|
1806
|
+
assert(rows.map(r => r.name)).equals(["Donald", "Ryan"]);
|
|
1807
|
+
});
|
|
1808
|
+
$user("find: $in with single value", async (assert) => {
|
|
1809
|
+
const rows = await User.find({
|
|
1810
|
+
where: { name: { $in: ["Donald"] } },
|
|
1811
|
+
});
|
|
1812
|
+
assert(rows.length).equals(1);
|
|
1813
|
+
assert(rows[0].name).equals("Donald");
|
|
1814
|
+
});
|
|
1815
|
+
$user("find: $in with no matches returns empty", async (assert) => {
|
|
1816
|
+
const rows = await User.find({
|
|
1817
|
+
where: { name: { $in: ["Nobody", "Ghost"] } },
|
|
1818
|
+
});
|
|
1819
|
+
assert(rows.length).equals(0);
|
|
1820
|
+
});
|
|
1821
|
+
$user$("find: $in with empty array throws", async (assert) => {
|
|
1822
|
+
await throws(assert, Code.operator_empty_in, () => {
|
|
1823
|
+
return User.find({ where: { name: { $in: [] } } });
|
|
1824
|
+
});
|
|
1825
|
+
});
|
|
1826
|
+
$user$("find: $in on boolean field throws", async (assert) => {
|
|
1827
|
+
await throws(assert, Code.operator_unknown, () => {
|
|
1828
|
+
return Type.find({ where: { boolean: { $in: [true, false] } } });
|
|
1829
|
+
});
|
|
1830
|
+
});
|
|
1831
|
+
$user$("find: $in on blob field throws", async (assert) => {
|
|
1832
|
+
await throws(assert, Code.operator_unknown, () => {
|
|
1833
|
+
return Type.find({ where: { blob: { $in: [] } } });
|
|
1834
|
+
});
|
|
1835
|
+
});
|
|
1836
|
+
$user("find: $in combined with other where criteria", async (assert) => {
|
|
1837
|
+
const rows = await User.find({
|
|
1838
|
+
where: {
|
|
1839
|
+
name: { $in: ["Donald", "Ryan", "Ben"] },
|
|
1840
|
+
age: { $lt: 40 },
|
|
1841
|
+
},
|
|
1842
|
+
sort: { name: "asc" },
|
|
1843
|
+
});
|
|
1844
|
+
assert(rows.length).equals(1);
|
|
1845
|
+
assert(rows[0].name).equals("Donald");
|
|
1846
|
+
});
|
|
1847
|
+
$user("find: offset skips records", async (assert) => {
|
|
1848
|
+
const all = await User.find({ sort: { name: "asc" } });
|
|
1849
|
+
const offset = await User.find({
|
|
1850
|
+
sort: { name: "asc" }, offset: 2, limit: all.length - 2
|
|
1851
|
+
});
|
|
1852
|
+
assert(offset.length).equals(all.length - 2);
|
|
1853
|
+
assert(offset[0].name).equals(all[2].name);
|
|
1854
|
+
});
|
|
1855
|
+
$user("find: offset 0 is same as no offset", async (assert) => {
|
|
1856
|
+
const without = await User.find({ sort: { name: "asc" } });
|
|
1857
|
+
const with_zero = await User.find({
|
|
1858
|
+
sort: { name: "asc" }, offset: 0, limit: without.length
|
|
1859
|
+
});
|
|
1860
|
+
assert(with_zero.length).equals(without.length);
|
|
1861
|
+
assert(with_zero.map(r => r.name)).equals(without.map(r => r.name));
|
|
1862
|
+
});
|
|
1863
|
+
$user("find: offset with limit", async (assert) => {
|
|
1864
|
+
const sort = { name: "asc" };
|
|
1865
|
+
const all = await User.find({ sort });
|
|
1866
|
+
const page = await User.find({ sort, offset: 1, limit: 2 });
|
|
1867
|
+
assert(page.length).equals(2);
|
|
1868
|
+
assert(page[0].name).equals(all[1].name);
|
|
1869
|
+
assert(page[1].name).equals(all[2].name);
|
|
1870
|
+
});
|
|
1871
|
+
$user("find: offset beyond total returns empty", async (assert) => {
|
|
1872
|
+
const rows = await User.find({ offset: 100, limit: 10 });
|
|
1873
|
+
assert(rows.length).equals(0);
|
|
1874
|
+
});
|
|
1875
|
+
$user$("find: negative offset throws", async (assert) => {
|
|
1876
|
+
await throws(assert, Code.offset_invalid, () => {
|
|
1877
|
+
return User.find({ offset: -1 });
|
|
1878
|
+
});
|
|
1879
|
+
});
|
|
1880
|
+
$user$("find: non-integer offset throws", async (assert) => {
|
|
1881
|
+
await throws(assert, Code.offset_invalid, () => {
|
|
1882
|
+
return User.find({ offset: 1.5 });
|
|
1883
|
+
});
|
|
1884
|
+
});
|
|
1885
|
+
$user$("find: offset without limit throws", async (assert) => {
|
|
1886
|
+
await throws(assert, Code.offset_requires_limit, () => {
|
|
1887
|
+
return User.find({ offset: 1 });
|
|
1888
|
+
});
|
|
1889
|
+
});
|
|
1749
1890
|
};
|
|
1750
1891
|
//# sourceMappingURL=test.js.map
|