@primate/core 0.6.3 → 0.7.1

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.
Files changed (187) hide show
  1. package/lib/private/App.d.ts +76 -149
  2. package/lib/private/App.js +22 -6
  3. package/lib/private/Flags.d.ts +5 -3
  4. package/lib/private/Flags.js +4 -2
  5. package/lib/private/app/Facade.d.ts +77 -156
  6. package/lib/private/app/Facade.js +4 -1
  7. package/lib/private/build/App.d.ts +4 -2
  8. package/lib/private/build/App.js +13 -4
  9. package/lib/private/build/client/index.js +24 -10
  10. package/lib/private/build/client/plugin/routes.d.ts +4 -0
  11. package/lib/private/build/client/plugin/routes.js +77 -0
  12. package/lib/private/build/hook.js +14 -6
  13. package/lib/private/build/index.d.ts +3 -2
  14. package/lib/private/build/index.js +9 -15
  15. package/lib/private/build/preclient/index.d.ts +3 -0
  16. package/lib/private/build/preclient/index.js +69 -0
  17. package/lib/private/build/preclient/plugin/routes.d.ts +4 -0
  18. package/lib/private/build/preclient/plugin/routes.js +44 -0
  19. package/lib/private/build/server/index.js +8 -5
  20. package/lib/private/build/server/plugin/assets.js +3 -3
  21. package/lib/private/build/server/plugin/native-addons.js +6 -8
  22. package/lib/private/build/server/plugin/node-imports.js +5 -7
  23. package/lib/private/build/server/plugin/route-client.d.ts +4 -0
  24. package/lib/private/build/server/plugin/route-client.js +111 -0
  25. package/lib/private/build/server/plugin/route.js +3 -10
  26. package/lib/private/build/server/plugin/virtual-pages.js +3 -3
  27. package/lib/private/build/server/plugin/virtual-routes.d.ts +2 -1
  28. package/lib/private/build/server/plugin/virtual-routes.js +27 -8
  29. package/lib/private/build/server/plugin/wasm.js +3 -2
  30. package/lib/private/build/shared/intercept.d.ts +4 -0
  31. package/lib/private/build/shared/intercept.js +29 -0
  32. package/lib/private/client/Data.d.ts +1 -1
  33. package/lib/private/client/boot.js +2 -2
  34. package/lib/private/client/create-form.d.ts +11 -1
  35. package/lib/private/client/create-form.js +21 -3
  36. package/lib/private/client/index.d.ts +2 -2
  37. package/lib/private/client/navigate.d.ts +1 -0
  38. package/lib/private/client/navigate.js +7 -6
  39. package/lib/private/client/submit.d.ts +2 -1
  40. package/lib/private/client/submit.js +8 -7
  41. package/lib/private/client/{http.d.ts → transport.d.ts} +3 -3
  42. package/lib/private/client/{http.js → transport.js} +7 -9
  43. package/lib/private/config/schema.d.ts +5 -13
  44. package/lib/private/config/schema.js +1 -5
  45. package/lib/private/db/DB.d.ts +3 -1
  46. package/lib/private/db/MemoryDB.d.ts +5 -2
  47. package/lib/private/db/MemoryDB.js +33 -19
  48. package/lib/private/db/SQLDB.d.ts +23 -0
  49. package/lib/private/db/SQLDB.js +2 -0
  50. package/lib/private/db/errors.d.ts +74 -7
  51. package/lib/private/db/errors.js +31 -7
  52. package/lib/private/db/migrate/apply.js +8 -9
  53. package/lib/private/db/migrate/bundle.js +2 -2
  54. package/lib/private/db/migrate/create.js +18 -20
  55. package/lib/private/db/migrate/status.js +9 -10
  56. package/lib/private/db/migrate/store.d.ts +3 -3
  57. package/lib/private/db/migrate/store.js +5 -5
  58. package/lib/private/db/test.js +256 -115
  59. package/lib/private/db/testSQL.d.ts +50 -0
  60. package/lib/private/db/testSQL.js +196 -0
  61. package/lib/private/errors.d.ts +66 -9
  62. package/lib/private/errors.js +37 -16
  63. package/lib/private/frontend.d.ts +4 -4
  64. package/lib/private/frontend.js +11 -8
  65. package/lib/private/i18n/errors.d.ts +7 -1
  66. package/lib/private/i18n/errors.js +1 -1
  67. package/lib/private/i18n/index/types.d.ts +1 -1
  68. package/lib/private/i18n/locale.d.ts +1 -1
  69. package/lib/private/i18n/module.js +6 -5
  70. package/lib/private/index.d.ts +10 -2
  71. package/lib/private/index.js +13 -1
  72. package/lib/private/logger.d.ts +24 -0
  73. package/lib/private/logger.js +66 -0
  74. package/lib/private/module/Setup.d.ts +3 -0
  75. package/lib/private/module/create.d.ts +2 -1
  76. package/lib/private/module/create.js +4 -0
  77. package/lib/private/paths.d.ts +2 -3
  78. package/lib/private/paths.js +6 -12
  79. package/lib/private/request/ContentType.d.ts +3 -0
  80. package/lib/private/request/ContentType.js +2 -0
  81. package/lib/private/request/RequestBag.d.ts +2 -18
  82. package/lib/private/request/RequestBag.js +4 -16
  83. package/lib/private/request/RequestBody.d.ts +20 -28
  84. package/lib/private/request/RequestBody.js +63 -86
  85. package/lib/private/request/RequestFacade.d.ts +2 -2
  86. package/lib/private/request/handle.js +2 -2
  87. package/lib/private/request/parse.js +2 -4
  88. package/lib/private/request/route.js +15 -8
  89. package/lib/private/response/binary.d.ts +2 -2
  90. package/lib/private/response/binary.js +6 -6
  91. package/lib/private/response/error.js +6 -2
  92. package/lib/private/response/json.js +2 -2
  93. package/lib/private/response/null.d.ts +3 -0
  94. package/lib/private/response/null.js +6 -0
  95. package/lib/private/response/redirect.js +4 -3
  96. package/lib/private/response/respond.js +4 -4
  97. package/lib/private/response/sse.js +2 -2
  98. package/lib/private/response/text.js +2 -2
  99. package/lib/private/route/ContentTypeMap.d.ts +10 -0
  100. package/lib/private/route/ContentTypeMap.js +2 -0
  101. package/lib/private/route/Handler.d.ts +3 -2
  102. package/lib/private/route/NarrowedRequest.d.ts +23 -0
  103. package/lib/private/route/NarrowedRequest.js +2 -0
  104. package/lib/private/route/Options.d.ts +6 -1
  105. package/lib/private/route/Path.d.ts +2 -2
  106. package/lib/private/route/hook.d.ts +3 -1
  107. package/lib/private/route/hook.js +1 -2
  108. package/lib/private/route/router.d.ts +7 -11
  109. package/lib/private/route/router.js +13 -20
  110. package/lib/private/route.client.d.ts +36 -0
  111. package/lib/private/route.client.js +8 -0
  112. package/lib/private/route.d.ts +21 -5
  113. package/lib/private/route.js +21 -5
  114. package/lib/private/serve/App.d.ts +1 -2
  115. package/lib/private/serve/App.js +64 -58
  116. package/lib/private/serve/Init.d.ts +2 -0
  117. package/lib/private/serve/dev-module.js +2 -3
  118. package/lib/private/serve/index.js +5 -6
  119. package/lib/private/server/TAG.d.ts +1 -1
  120. package/lib/private/server/TAG.js +1 -1
  121. package/lib/private/session/index.d.ts +1 -1
  122. package/lib/private/session/index.js +3 -2
  123. package/lib/private/session/module.js +3 -3
  124. package/lib/private/session/schema.d.ts +3 -3
  125. package/lib/private/session/schema.js +4 -3
  126. package/lib/private/store/ExtractRelation.d.ts +7 -0
  127. package/lib/private/store/ExtractRelation.js +2 -0
  128. package/lib/private/store/ExtractSchema.d.ts +10 -0
  129. package/lib/private/{orm → store}/ForeignKey.d.ts +1 -1
  130. package/lib/private/store/Init.d.ts +13 -0
  131. package/lib/private/store/Init.js +2 -0
  132. package/lib/private/{orm/store.d.ts → store/Store.d.ts} +50 -50
  133. package/lib/private/{orm/store.js → store/Store.js} +163 -107
  134. package/lib/private/store/StoreInput.d.ts +11 -0
  135. package/lib/private/store/key.d.ts +8 -0
  136. package/lib/private/store/key.js +8 -0
  137. package/lib/private/{orm → store}/parse.d.ts +3 -3
  138. package/lib/private/{orm → store}/parse.js +7 -2
  139. package/lib/private/store/relation.d.ts +29 -0
  140. package/lib/private/store/relation.js +26 -0
  141. package/lib/private/store.d.ts +24 -0
  142. package/lib/private/store.js +10 -0
  143. package/lib/public/db/errors.d.ts +1 -1
  144. package/lib/public/db/errors.js +1 -1
  145. package/lib/public/db/testSQL.d.ts +2 -0
  146. package/lib/public/db/testSQL.js +2 -0
  147. package/lib/public/db.d.ts +1 -0
  148. package/lib/public/index.d.ts +1 -0
  149. package/lib/public/index.js +1 -1
  150. package/lib/public/response.d.ts +6 -6
  151. package/lib/public/response.js +4 -1
  152. package/lib/public/route.client.d.ts +2 -0
  153. package/lib/public/route.client.js +2 -0
  154. package/lib/public/store.d.ts +2 -0
  155. package/lib/public/store.js +2 -0
  156. package/package.json +24 -17
  157. package/lib/private/bye.d.ts +0 -3
  158. package/lib/private/bye.js +0 -4
  159. package/lib/private/log.d.ts +0 -20
  160. package/lib/private/log.js +0 -47
  161. package/lib/private/orm/ExtractSchema.d.ts +0 -9
  162. package/lib/private/orm/StoreInput.d.ts +0 -10
  163. package/lib/private/orm/key.d.ts +0 -8
  164. package/lib/private/orm/key.js +0 -8
  165. package/lib/private/orm/relation.d.ts +0 -43
  166. package/lib/private/orm/relation.js +0 -26
  167. package/lib/private/request/Verb.d.ts +0 -4
  168. package/lib/private/request/Verb.js +0 -2
  169. package/lib/private/request/verbs.d.ts +0 -3
  170. package/lib/private/request/verbs.js +0 -12
  171. package/lib/private/route/wrap.d.ts +0 -2
  172. package/lib/private/route/wrap.js +0 -12
  173. package/lib/public/log.d.ts +0 -2
  174. package/lib/public/log.js +0 -2
  175. package/lib/public/orm/key.d.ts +0 -2
  176. package/lib/public/orm/key.js +0 -2
  177. package/lib/public/orm/relation.d.ts +0 -2
  178. package/lib/public/orm/relation.js +0 -2
  179. package/lib/public/orm/store.d.ts +0 -2
  180. package/lib/public/orm/store.js +0 -2
  181. package/lib/public/request/verbs.d.ts +0 -2
  182. package/lib/public/request/verbs.js +0 -2
  183. /package/lib/private/{orm → store}/ExtractSchema.js +0 -0
  184. /package/lib/private/{orm → store}/ForeignKey.js +0 -0
  185. /package/lib/private/{orm → store}/PrimaryKey.d.ts +0 -0
  186. /package/lib/private/{orm → store}/PrimaryKey.js +0 -0
  187. /package/lib/private/{orm → store}/StoreInput.js +0 -0
@@ -1,7 +1,8 @@
1
1
  import { Code } from "#db/errors";
2
- import key from "#orm/key";
3
- import relation from "#orm/relation";
4
- import store from "#orm/store";
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
- assert(error.code).equals(code);
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
- name: "post",
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
- name: "user",
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
- name: "user_n",
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
- name: "user_b",
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
- name: "type",
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
- name: "select",
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
- name: "author",
203
- schema: AuthorSchema,
204
- relations: {
205
- articles: relation.many(ArticleSchema, "author_id"),
206
- profile: relation.one(ProfileSchema, "author_id"),
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
- name: "article",
222
- schema: ArticleSchema,
223
- relations: {
224
- author: relation.one(AuthorSchema, "author_id", { reverse: true }),
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
- name: "profile",
230
- schema: ProfileSchema,
231
- relations: {
232
- author: relation.one(AuthorSchema, "author_id", { reverse: true }),
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.table.create();
235
+ await s.create();
238
236
  try {
239
237
  await body(assert);
240
238
  }
241
239
  finally {
242
- await s.table.delete();
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.table.create();
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.table.delete();
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.table.create();
272
- await Article.table.create();
273
- await Profile.table.create();
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.table.delete();
300
- await Article.table.delete();
301
- await Author.table.delete();
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
- name: "manual_user",
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: true,
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: true } });
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: true } });
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: true } });
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: true },
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: true } });
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: true } });
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: true,
912
- profile: true,
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: true,
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: true } }));
1035
- assert(await Article.try(missing, { with: { author: true } })).undefined();
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: true, profile: true } }));
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: true, profile: true },
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: true } });
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
- name: "users; DROP TABLE users",
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: { articles: { select: ["title"], sort: { title: "asc" } } },
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: true },
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: true },
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
- name: "j_parent",
1544
+ table: "j_parent",
1504
1545
  db,
1505
- schema: ParentSchema,
1506
- relations: {
1507
- children: relation.many(ChildSchema, "parent_id"),
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
- name: "j_child",
1513
- schema: ChildSchema,
1514
- relations: {
1515
- parent: relation.one(ParentSchema, "parent_id", { reverse: true }),
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.table.create();
1519
- await Child.table.create();
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: true },
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.table.delete();
1552
- await Parent.table.delete();
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: true },
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.name, User.pk);
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
- name: "user",
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
- name: "user",
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
- name: "user",
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
- name: "uuid_test",
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