@casekit/orm2 0.0.0-20250322230249

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 (275) hide show
  1. package/build/builders/buildCount.d.ts +23 -0
  2. package/build/builders/buildCount.js +63 -0
  3. package/build/builders/buildCount.test.d.ts +1 -0
  4. package/build/builders/buildCount.test.js +144 -0
  5. package/build/builders/buildCreate.d.ts +5 -0
  6. package/build/builders/buildCreate.js +28 -0
  7. package/build/builders/buildCreate.test.d.ts +1 -0
  8. package/build/builders/buildCreate.test.js +47 -0
  9. package/build/builders/buildDelete.d.ts +5 -0
  10. package/build/builders/buildDelete.js +28 -0
  11. package/build/builders/buildDelete.test.d.ts +1 -0
  12. package/build/builders/buildDelete.test.js +108 -0
  13. package/build/builders/buildFind.d.ts +8 -0
  14. package/build/builders/buildFind.js +185 -0
  15. package/build/builders/buildFind.test.d.ts +1 -0
  16. package/build/builders/buildFind.test.js +263 -0
  17. package/build/builders/buildUpdate.d.ts +5 -0
  18. package/build/builders/buildUpdate.js +34 -0
  19. package/build/builders/buildUpdate.test.d.ts +1 -0
  20. package/build/builders/buildUpdate.test.js +130 -0
  21. package/build/builders/buildWhere.d.ts +6 -0
  22. package/build/builders/buildWhere.js +63 -0
  23. package/build/builders/buildWhere.test.d.ts +1 -0
  24. package/build/builders/buildWhere.test.js +154 -0
  25. package/build/builders/types.d.ts +87 -0
  26. package/build/builders/types.js +1 -0
  27. package/build/connection.d.ts +31 -0
  28. package/build/connection.js +206 -0
  29. package/build/index.d.ts +10 -0
  30. package/build/index.js +5 -0
  31. package/build/operators.d.ts +59 -0
  32. package/build/operators.js +44 -0
  33. package/build/orm.count.d.ts +14 -0
  34. package/build/orm.count.js +22 -0
  35. package/build/orm.createMany.d.ts +5 -0
  36. package/build/orm.createMany.js +26 -0
  37. package/build/orm.createOne.d.ts +5 -0
  38. package/build/orm.createOne.js +34 -0
  39. package/build/orm.d.ts +81 -0
  40. package/build/orm.deleteMany.d.ts +5 -0
  41. package/build/orm.deleteMany.js +26 -0
  42. package/build/orm.deleteOne.d.ts +5 -0
  43. package/build/orm.deleteOne.js +32 -0
  44. package/build/orm.findMany.d.ts +8 -0
  45. package/build/orm.findMany.js +64 -0
  46. package/build/orm.findOne.d.ts +5 -0
  47. package/build/orm.findOne.js +20 -0
  48. package/build/orm.js +243 -0
  49. package/build/orm.restrict.d.ts +6 -0
  50. package/build/orm.restrict.js +52 -0
  51. package/build/orm.updateMany.d.ts +5 -0
  52. package/build/orm.updateMany.js +29 -0
  53. package/build/orm.updateOne.d.ts +5 -0
  54. package/build/orm.updateOne.js +35 -0
  55. package/build/sql/countToSql.d.ts +3 -0
  56. package/build/sql/countToSql.js +11 -0
  57. package/build/sql/countToSql.test.d.ts +1 -0
  58. package/build/sql/countToSql.test.js +218 -0
  59. package/build/sql/createToSql.d.ts +3 -0
  60. package/build/sql/createToSql.js +27 -0
  61. package/build/sql/createToSql.test.d.ts +1 -0
  62. package/build/sql/createToSql.test.js +186 -0
  63. package/build/sql/deleteToSql.d.ts +3 -0
  64. package/build/sql/deleteToSql.js +15 -0
  65. package/build/sql/deleteToSql.test.d.ts +1 -0
  66. package/build/sql/deleteToSql.test.js +93 -0
  67. package/build/sql/findToSql.d.ts +3 -0
  68. package/build/sql/findToSql.js +33 -0
  69. package/build/sql/findToSql.test.d.ts +1 -0
  70. package/build/sql/findToSql.test.js +409 -0
  71. package/build/sql/updateToSql.d.ts +3 -0
  72. package/build/sql/updateToSql.js +16 -0
  73. package/build/sql/updateToSql.test.d.ts +1 -0
  74. package/build/sql/updateToSql.test.js +165 -0
  75. package/build/sql/util.d.ts +11 -0
  76. package/build/sql/util.js +36 -0
  77. package/build/sql/util.test.d.ts +1 -0
  78. package/build/sql/util.test.js +163 -0
  79. package/build/tests/connection.test.d.ts +1 -0
  80. package/build/tests/connection.test.js +304 -0
  81. package/build/tests/datatypes.test.d.ts +1 -0
  82. package/build/tests/datatypes.test.js +239 -0
  83. package/build/tests/operators.test.d.ts +1 -0
  84. package/build/tests/operators.test.js +125 -0
  85. package/build/tests/orm.count.middleware.test.d.ts +1 -0
  86. package/build/tests/orm.count.middleware.test.js +132 -0
  87. package/build/tests/orm.count.test-d.d.ts +1 -0
  88. package/build/tests/orm.count.test-d.js +60 -0
  89. package/build/tests/orm.count.test.d.ts +1 -0
  90. package/build/tests/orm.count.test.js +151 -0
  91. package/build/tests/orm.createMany.middleware.test.d.ts +1 -0
  92. package/build/tests/orm.createMany.middleware.test.js +63 -0
  93. package/build/tests/orm.createMany.test-d.d.ts +1 -0
  94. package/build/tests/orm.createMany.test-d.js +131 -0
  95. package/build/tests/orm.createMany.test.d.ts +1 -0
  96. package/build/tests/orm.createMany.test.js +392 -0
  97. package/build/tests/orm.createOne.middleware.test.d.ts +1 -0
  98. package/build/tests/orm.createOne.middleware.test.js +54 -0
  99. package/build/tests/orm.createOne.test-d.d.ts +1 -0
  100. package/build/tests/orm.createOne.test-d.js +113 -0
  101. package/build/tests/orm.createOne.test.d.ts +1 -0
  102. package/build/tests/orm.createOne.test.js +268 -0
  103. package/build/tests/orm.deleteMany.middleware.test.d.ts +1 -0
  104. package/build/tests/orm.deleteMany.middleware.test.js +77 -0
  105. package/build/tests/orm.deleteMany.test-d.d.ts +1 -0
  106. package/build/tests/orm.deleteMany.test-d.js +179 -0
  107. package/build/tests/orm.deleteMany.test.d.ts +1 -0
  108. package/build/tests/orm.deleteMany.test.js +394 -0
  109. package/build/tests/orm.deleteOne.middleware.test.d.ts +1 -0
  110. package/build/tests/orm.deleteOne.middleware.test.js +61 -0
  111. package/build/tests/orm.deleteOne.test-d.d.ts +1 -0
  112. package/build/tests/orm.deleteOne.test-d.js +179 -0
  113. package/build/tests/orm.deleteOne.test.d.ts +1 -0
  114. package/build/tests/orm.deleteOne.test.js +360 -0
  115. package/build/tests/orm.findMany.includeManyToMany.test.d.ts +1 -0
  116. package/build/tests/orm.findMany.includeManyToMany.test.js +335 -0
  117. package/build/tests/orm.findMany.includeManyToOne.test.d.ts +1 -0
  118. package/build/tests/orm.findMany.includeManyToOne.test.js +286 -0
  119. package/build/tests/orm.findMany.includeOneToMany.test.d.ts +1 -0
  120. package/build/tests/orm.findMany.includeOneToMany.test.js +530 -0
  121. package/build/tests/orm.findMany.middleware.test.d.ts +1 -0
  122. package/build/tests/orm.findMany.middleware.test.js +66 -0
  123. package/build/tests/orm.findMany.offsetLimit.test.d.ts +1 -0
  124. package/build/tests/orm.findMany.offsetLimit.test.js +108 -0
  125. package/build/tests/orm.findMany.orderBy.test.d.ts +1 -0
  126. package/build/tests/orm.findMany.orderBy.test.js +304 -0
  127. package/build/tests/orm.findMany.select.test.d.ts +1 -0
  128. package/build/tests/orm.findMany.select.test.js +278 -0
  129. package/build/tests/orm.findMany.test-d.d.ts +1 -0
  130. package/build/tests/orm.findMany.test-d.js +374 -0
  131. package/build/tests/orm.findMany.where.test.d.ts +1 -0
  132. package/build/tests/orm.findMany.where.test.js +383 -0
  133. package/build/tests/orm.findOne.middleware.test.d.ts +1 -0
  134. package/build/tests/orm.findOne.middleware.test.js +57 -0
  135. package/build/tests/orm.findOne.test-d.d.ts +1 -0
  136. package/build/tests/orm.findOne.test-d.js +377 -0
  137. package/build/tests/orm.findOne.test.d.ts +1 -0
  138. package/build/tests/orm.findOne.test.js +247 -0
  139. package/build/tests/orm.restrict.test-d.d.ts +1 -0
  140. package/build/tests/orm.restrict.test-d.js +105 -0
  141. package/build/tests/orm.restrict.test.d.ts +1 -0
  142. package/build/tests/orm.restrict.test.js +259 -0
  143. package/build/tests/orm.transact.test.d.ts +1 -0
  144. package/build/tests/orm.transact.test.js +48 -0
  145. package/build/tests/orm.updateMany.middleware.test.d.ts +1 -0
  146. package/build/tests/orm.updateMany.middleware.test.js +72 -0
  147. package/build/tests/orm.updateMany.test.d.ts +1 -0
  148. package/build/tests/orm.updateMany.test.js +210 -0
  149. package/build/tests/orm.updateOne.middleware.test.d.ts +1 -0
  150. package/build/tests/orm.updateOne.middleware.test.js +62 -0
  151. package/build/tests/orm.updateOne.test.d.ts +1 -0
  152. package/build/tests/orm.updateOne.test.js +209 -0
  153. package/build/tests/util/db.d.ts +1571 -0
  154. package/build/tests/util/db.js +10 -0
  155. package/build/tests/util/logger.d.ts +19 -0
  156. package/build/tests/util/logger.js +40 -0
  157. package/build/types/BaseFindParams.d.ts +1 -0
  158. package/build/types/BaseFindParams.js +1 -0
  159. package/build/types/CountParams.d.ts +16 -0
  160. package/build/types/CountParams.js +1 -0
  161. package/build/types/CountParams.test-d.d.ts +1 -0
  162. package/build/types/CountParams.test-d.js +89 -0
  163. package/build/types/CreateManyParams.d.ts +11 -0
  164. package/build/types/CreateManyParams.js +1 -0
  165. package/build/types/CreateManyParams.test-d.d.ts +1 -0
  166. package/build/types/CreateManyParams.test-d.js +83 -0
  167. package/build/types/CreateManyResult.d.ts +5 -0
  168. package/build/types/CreateManyResult.js +1 -0
  169. package/build/types/CreateManyResult.test-d.d.ts +1 -0
  170. package/build/types/CreateManyResult.test-d.js +22 -0
  171. package/build/types/CreateOneParams.d.ts +11 -0
  172. package/build/types/CreateOneParams.js +1 -0
  173. package/build/types/CreateOneParams.test-d.d.ts +1 -0
  174. package/build/types/CreateOneParams.test-d.js +56 -0
  175. package/build/types/CreateOneResult.d.ts +5 -0
  176. package/build/types/CreateOneResult.js +1 -0
  177. package/build/types/CreateOneResult.test-d.d.ts +1 -0
  178. package/build/types/CreateOneResult.test-d.js +23 -0
  179. package/build/types/CreateValues.d.ts +6 -0
  180. package/build/types/CreateValues.js +1 -0
  181. package/build/types/CreateValues.test-d.d.ts +1 -0
  182. package/build/types/CreateValues.test-d.js +60 -0
  183. package/build/types/DeleteManyResult.d.ts +5 -0
  184. package/build/types/DeleteManyResult.js +1 -0
  185. package/build/types/DeleteManyResult.test-d.d.ts +1 -0
  186. package/build/types/DeleteManyResult.test-d.js +23 -0
  187. package/build/types/DeleteOneResult.d.ts +5 -0
  188. package/build/types/DeleteOneResult.js +1 -0
  189. package/build/types/DeleteOneResult.test-d.d.ts +1 -0
  190. package/build/types/DeleteOneResult.test-d.js +23 -0
  191. package/build/types/DeleteParams.d.ts +7 -0
  192. package/build/types/DeleteParams.js +1 -0
  193. package/build/types/DeleteParams.test-d.d.ts +1 -0
  194. package/build/types/DeleteParams.test-d.js +46 -0
  195. package/build/types/FindParams.d.ts +22 -0
  196. package/build/types/FindParams.js +1 -0
  197. package/build/types/FindParams.test-d.d.ts +1 -0
  198. package/build/types/FindParams.test-d.js +107 -0
  199. package/build/types/FindResult.d.ts +13 -0
  200. package/build/types/FindResult.js +1 -0
  201. package/build/types/FindResult.test-d.d.ts +1 -0
  202. package/build/types/FindResult.test-d.js +30 -0
  203. package/build/types/IncludeClause.d.ts +5 -0
  204. package/build/types/IncludeClause.js +1 -0
  205. package/build/types/IncludeClause.test-d.d.ts +1 -0
  206. package/build/types/IncludeClause.test-d.js +69 -0
  207. package/build/types/Middleware.d.ts +51 -0
  208. package/build/types/Middleware.js +37 -0
  209. package/build/types/OptionalValues.d.ts +4 -0
  210. package/build/types/OptionalValues.js +1 -0
  211. package/build/types/OptionalValues.test-d.d.ts +1 -0
  212. package/build/types/OptionalValues.test-d.js +12 -0
  213. package/build/types/OrderByClause.d.ts +9 -0
  214. package/build/types/OrderByClause.js +1 -0
  215. package/build/types/OrderByClause.test-d.d.ts +1 -0
  216. package/build/types/OrderByClause.test-d.js +44 -0
  217. package/build/types/RequiredValues.d.ts +4 -0
  218. package/build/types/RequiredValues.js +1 -0
  219. package/build/types/RequiredValues.test-d.d.ts +1 -0
  220. package/build/types/RequiredValues.test-d.js +22 -0
  221. package/build/types/RestrictModels.d.ts +23 -0
  222. package/build/types/RestrictModels.js +1 -0
  223. package/build/types/RestrictModels.test-d.d.ts +1 -0
  224. package/build/types/RestrictModels.test-d.js +44 -0
  225. package/build/types/ReturningClause.d.ts +3 -0
  226. package/build/types/ReturningClause.js +1 -0
  227. package/build/types/ReturningClause.test-d.d.ts +1 -0
  228. package/build/types/ReturningClause.test-d.js +15 -0
  229. package/build/types/SelectClause.d.ts +3 -0
  230. package/build/types/SelectClause.js +1 -0
  231. package/build/types/SelectClause.test-d.d.ts +1 -0
  232. package/build/types/SelectClause.test-d.js +15 -0
  233. package/build/types/UpdateManyResult.d.ts +5 -0
  234. package/build/types/UpdateManyResult.js +1 -0
  235. package/build/types/UpdateManyResult.test-d.d.ts +1 -0
  236. package/build/types/UpdateManyResult.test-d.js +15 -0
  237. package/build/types/UpdateOneResult.d.ts +5 -0
  238. package/build/types/UpdateOneResult.js +1 -0
  239. package/build/types/UpdateOneResult.test-d.d.ts +1 -0
  240. package/build/types/UpdateOneResult.test-d.js +15 -0
  241. package/build/types/UpdateParams.d.ts +9 -0
  242. package/build/types/UpdateParams.js +1 -0
  243. package/build/types/UpdateParams.test-d.d.ts +1 -0
  244. package/build/types/UpdateParams.test-d.js +29 -0
  245. package/build/types/UpdateValues.d.ts +6 -0
  246. package/build/types/UpdateValues.js +1 -0
  247. package/build/types/UpdateValues.test-d.d.ts +1 -0
  248. package/build/types/UpdateValues.test-d.js +33 -0
  249. package/build/types/WhereClause.d.ts +29 -0
  250. package/build/types/WhereClause.js +1 -0
  251. package/build/types/WhereClause.test-d.d.ts +1 -0
  252. package/build/types/WhereClause.test-d.js +137 -0
  253. package/build/util/getIncludedToManySubqueries.d.ts +12 -0
  254. package/build/util/getIncludedToManySubqueries.js +63 -0
  255. package/build/util/getIncludedToManySubqueries.test.d.ts +1 -0
  256. package/build/util/getIncludedToManySubqueries.test.js +121 -0
  257. package/build/util/getLateralJoinValues.d.ts +4 -0
  258. package/build/util/getLateralJoinValues.js +30 -0
  259. package/build/util/getLateralJoinValues.test.d.ts +1 -0
  260. package/build/util/getLateralJoinValues.test.js +99 -0
  261. package/build/util/hasClauses.d.ts +1 -0
  262. package/build/util/hasClauses.js +9 -0
  263. package/build/util/hasClauses.test.d.ts +1 -0
  264. package/build/util/hasClauses.test.js +15 -0
  265. package/build/util/makeTableAlias.d.ts +1 -0
  266. package/build/util/makeTableAlias.js +10 -0
  267. package/build/util/makeTableAlias.test.d.ts +1 -0
  268. package/build/util/makeTableAlias.test.js +18 -0
  269. package/build/util/resultSchema.d.ts +10 -0
  270. package/build/util/resultSchema.js +42 -0
  271. package/build/util/rowToObject.d.ts +8 -0
  272. package/build/util/rowToObject.js +10 -0
  273. package/build/util/rowToObject.test.d.ts +1 -0
  274. package/build/util/rowToObject.test.js +68 -0
  275. package/package.json +67 -0
@@ -0,0 +1,23 @@
1
+ /**
2
+ * NB. Lots of ! in this file which maybe isn't great but I think for these
3
+ * builders, where we can be fairly confident due to the type-checking at the edges
4
+ * that things are defined, it's better than the alternative of loads of
5
+ * redundant checks for undefined. We've done all the checks we need at the
6
+ * stage of normalizing the model, we can now assume everything is wired up
7
+ * correctly.
8
+ *
9
+ * Also this is a file in which there's a lot of mutation and dependency
10
+ * on the order in which things are done, which is not ideal but is what
11
+ * it is. Refactor and/or add to/change with care.
12
+ */
13
+ import { NormalizedConfig } from "@casekit/orm2-config";
14
+ import { ModelDefinitions, OperatorDefinitions } from "@casekit/orm2-schema";
15
+ import { WhereClause } from "../types/WhereClause.js";
16
+ import { CountBuilder } from "./types.js";
17
+ type BaseCountParams = {
18
+ where?: WhereClause<ModelDefinitions, OperatorDefinitions, string>;
19
+ include?: Record<string, BaseCountParams>;
20
+ for?: "update" | "no key update" | "share" | "key share";
21
+ };
22
+ export declare const buildCount: (config: NormalizedConfig, modelName: string, query: BaseCountParams, path?: string[], tableIndex?: number) => CountBuilder;
23
+ export {};
@@ -0,0 +1,63 @@
1
+ import { hasClauses } from "#util/hasClauses.js";
2
+ import { makeTableAlias } from "#util/makeTableAlias.js";
3
+ import { buildWhere } from "./buildWhere.js";
4
+ /**
5
+ * Builds a base CountBuilder, with only columns from the main table.
6
+ * Joins will be added later.
7
+ */
8
+ const buildBaseCount = (config, m, query, tableIndex = 0) => {
9
+ const model = config.models[m];
10
+ if (!model)
11
+ throw new Error(`Model "${m}" not found`);
12
+ const table = {
13
+ schema: model.schema,
14
+ name: model.table,
15
+ alias: makeTableAlias(tableIndex++),
16
+ model: m,
17
+ };
18
+ const where = hasClauses(query.where)
19
+ ? buildWhere(config, table, query.where)
20
+ : null;
21
+ return {
22
+ table,
23
+ where,
24
+ joins: [],
25
+ for: query.for,
26
+ tableIndex,
27
+ };
28
+ };
29
+ export const buildCount = (config, modelName, query, path = [], tableIndex = 0) => {
30
+ const model = config.models[modelName];
31
+ if (!model)
32
+ throw new Error(`Model "${modelName}" not found`);
33
+ const builder = buildBaseCount(config, modelName, query, tableIndex);
34
+ for (const [r, subquery] of Object.entries(query.include ?? {})) {
35
+ const relation = model.relations[r];
36
+ if (relation.type !== "N:1") {
37
+ continue;
38
+ }
39
+ const joinBuilder = buildCount(config, relation.model, subquery, [...path, r], builder.tableIndex);
40
+ builder.joins.push({
41
+ path: [...path, r],
42
+ relation: r,
43
+ table: joinBuilder.table,
44
+ where: joinBuilder.where,
45
+ type: "INNER",
46
+ columns: relation.from.columns.map((fk, i) => ({
47
+ from: {
48
+ table: builder.table.alias,
49
+ name: fk,
50
+ },
51
+ to: {
52
+ table: joinBuilder.table.alias,
53
+ name: relation.to.columns[i],
54
+ },
55
+ })),
56
+ });
57
+ // update the parent builder's tableIndex with the one from the
58
+ // joinBuilder, so that we don't have overlapping table indices
59
+ builder.tableIndex = joinBuilder.tableIndex;
60
+ builder.joins.push(...joinBuilder.joins);
61
+ }
62
+ return builder;
63
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,144 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { sql } from "@casekit/sql";
3
+ import { createTestDB } from "../tests/util/db.js";
4
+ import { buildCount } from "./buildCount.js";
5
+ describe("buildCount", () => {
6
+ const { db } = createTestDB();
7
+ test("builds basic count query", () => {
8
+ const result = buildCount(db.config, "post", {});
9
+ expect(result).toEqual({
10
+ table: {
11
+ schema: "orm",
12
+ name: "post",
13
+ alias: "a",
14
+ model: "post",
15
+ },
16
+ where: null,
17
+ joins: [],
18
+ for: undefined,
19
+ tableIndex: 1,
20
+ });
21
+ });
22
+ test("builds count query with N:1 relation include", () => {
23
+ const result = buildCount(db.config, "post", {
24
+ where: { id: 1 },
25
+ include: {
26
+ author: {
27
+ where: {},
28
+ },
29
+ },
30
+ });
31
+ expect(result).toEqual({
32
+ table: {
33
+ schema: "orm",
34
+ name: "post",
35
+ alias: "a",
36
+ model: "post",
37
+ },
38
+ where: sql `"a"."id" = ${1}`,
39
+ joins: [
40
+ {
41
+ relation: "author",
42
+ table: {
43
+ schema: "orm",
44
+ name: "user",
45
+ alias: "b",
46
+ model: "user",
47
+ },
48
+ where: null,
49
+ type: "INNER",
50
+ path: ["author"],
51
+ columns: [
52
+ {
53
+ from: { table: "a", name: "author_id" },
54
+ to: { table: "b", name: "id" },
55
+ },
56
+ ],
57
+ },
58
+ ],
59
+ for: undefined,
60
+ tableIndex: 2,
61
+ });
62
+ });
63
+ test("builds count query with where clause", () => {
64
+ const result = buildCount(db.config, "post", {
65
+ where: {
66
+ title: "Test Post",
67
+ },
68
+ });
69
+ expect(result).toEqual({
70
+ table: {
71
+ schema: "orm",
72
+ name: "post",
73
+ alias: "a",
74
+ model: "post",
75
+ },
76
+ where: sql `"a"."title" = ${"Test Post"}`,
77
+ joins: [],
78
+ for: undefined,
79
+ tableIndex: 1,
80
+ });
81
+ });
82
+ test("builds count query with nested includes and where clauses", () => {
83
+ const result = buildCount(db.config, "post", {
84
+ where: {
85
+ title: "Test Post",
86
+ },
87
+ include: {
88
+ author: {
89
+ where: {
90
+ name: "John",
91
+ },
92
+ },
93
+ },
94
+ });
95
+ expect(result).toEqual({
96
+ table: {
97
+ schema: "orm",
98
+ name: "post",
99
+ alias: "a",
100
+ model: "post",
101
+ },
102
+ where: sql `"a"."title" = ${"Test Post"}`,
103
+ joins: [
104
+ {
105
+ relation: "author",
106
+ table: {
107
+ schema: "orm",
108
+ name: "user",
109
+ alias: "b",
110
+ model: "user",
111
+ },
112
+ where: sql `"b"."name" = ${"John"}`,
113
+ type: "INNER",
114
+ path: ["author"],
115
+ columns: [
116
+ {
117
+ from: { table: "a", name: "author_id" },
118
+ to: { table: "b", name: "id" },
119
+ },
120
+ ],
121
+ },
122
+ ],
123
+ for: undefined,
124
+ tableIndex: 2,
125
+ });
126
+ });
127
+ test("builds count query with for parameter", () => {
128
+ const result = buildCount(db.config, "post", {
129
+ for: "update",
130
+ });
131
+ expect(result).toEqual({
132
+ table: {
133
+ schema: "orm",
134
+ name: "post",
135
+ alias: "a",
136
+ model: "post",
137
+ },
138
+ where: null,
139
+ joins: [],
140
+ for: "update",
141
+ tableIndex: 1,
142
+ });
143
+ });
144
+ });
@@ -0,0 +1,5 @@
1
+ import { NormalizedConfig } from "@casekit/orm2-config";
2
+ import { ModelDefinitions, ModelName } from "@casekit/orm2-schema";
3
+ import { CreateManyParams } from "#types/CreateManyParams.js";
4
+ import { CreateBuilder } from "./types.js";
5
+ export declare const buildCreate: (config: NormalizedConfig, modelName: string, query: CreateManyParams<ModelDefinitions, ModelName<ModelDefinitions>>, path?: string[], tableIndex?: number) => CreateBuilder;
@@ -0,0 +1,28 @@
1
+ import { uniq } from "es-toolkit";
2
+ import { makeTableAlias } from "#util/makeTableAlias.js";
3
+ export const buildCreate = (config, modelName, query, path = [], tableIndex = 0) => {
4
+ const model = config.models[modelName];
5
+ if (!model)
6
+ throw new Error(`Model "${modelName}" not found`);
7
+ const fields = uniq(query.values.flatMap(Object.keys));
8
+ const table = {
9
+ schema: model.schema,
10
+ name: model.table,
11
+ alias: makeTableAlias(tableIndex++),
12
+ model: modelName,
13
+ };
14
+ const columns = fields.map((f) => model.fields[f].column);
15
+ const values = query.values.map((v) => fields.map((f) => v[f] ?? null));
16
+ const returning = query.returning?.map((f, index) => ({
17
+ name: model.fields[f].column,
18
+ alias: `${table.alias}_${index}`,
19
+ path: [...path, f],
20
+ })) ?? [];
21
+ return {
22
+ table,
23
+ columns,
24
+ values,
25
+ returning,
26
+ onConflict: query.onConflict,
27
+ };
28
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { createTestDB } from "../tests/util/db.js";
3
+ import { buildCreate } from "./buildCreate.js";
4
+ describe("buildCreate", () => {
5
+ const { db } = createTestDB();
6
+ test("should build a create query", () => {
7
+ const result = buildCreate(db.config, "user", {
8
+ values: [{ id: 1, name: "John Doe", email: "john@example.com" }],
9
+ });
10
+ expect(result).toEqual({
11
+ table: {
12
+ schema: "orm",
13
+ name: "user",
14
+ alias: "a",
15
+ model: "user",
16
+ },
17
+ columns: ["id", "name", "email"],
18
+ values: [[1, "John Doe", "john@example.com"]],
19
+ returning: [],
20
+ onConflict: undefined,
21
+ });
22
+ });
23
+ test("should throw an error if model is not found", () => {
24
+ expect(() => buildCreate(db.config, "NonExistentModel", {
25
+ values: [
26
+ { id: 1, name: "John Doe", email: "john@example.com" },
27
+ ],
28
+ })).toThrow('Model "NonExistentModel" not found');
29
+ });
30
+ test("should handle returning fields", () => {
31
+ const result = buildCreate(db.config, "user", {
32
+ values: [{ id: 1, name: "John Doe", email: "john@example.com" }],
33
+ returning: ["id", "name"],
34
+ });
35
+ expect(result.returning).toEqual([
36
+ { name: "id", alias: "a_0", path: ["id"] },
37
+ { name: "name", alias: "a_1", path: ["name"] },
38
+ ]);
39
+ });
40
+ test("should handle onConflict", () => {
41
+ const result = buildCreate(db.config, "user", {
42
+ values: [{ id: 1, name: "John Doe", email: "john@example.com" }],
43
+ onConflict: { do: "nothing" },
44
+ });
45
+ expect(result.onConflict).toEqual({ do: "nothing" });
46
+ });
47
+ });
@@ -0,0 +1,5 @@
1
+ import { NormalizedConfig } from "@casekit/orm2-config";
2
+ import { ModelDefinitions, ModelName, OperatorDefinitions } from "@casekit/orm2-schema";
3
+ import { DeleteParams } from "#types/DeleteParams.js";
4
+ import { DeleteBuilder } from "./types.js";
5
+ export declare const buildDelete: (config: NormalizedConfig, modelName: string, query: DeleteParams<ModelDefinitions, OperatorDefinitions, ModelName<ModelDefinitions>>, path?: string[], tableIndex?: number) => DeleteBuilder;
@@ -0,0 +1,28 @@
1
+ import { makeTableAlias } from "#util/makeTableAlias.js";
2
+ import { hasClauses } from "../util/hasClauses.js";
3
+ import { buildWhere } from "./buildWhere.js";
4
+ export const buildDelete = (config, modelName, query, path = [], tableIndex = 0) => {
5
+ const model = config.models[modelName];
6
+ if (!model)
7
+ throw new Error(`Model "${modelName}" not found`);
8
+ const table = {
9
+ schema: model.schema,
10
+ name: model.table,
11
+ alias: makeTableAlias(tableIndex++),
12
+ model: modelName,
13
+ };
14
+ if (!hasClauses(query.where)) {
15
+ throw new Error("Delete queries must have a where clause");
16
+ }
17
+ const where = buildWhere(config, table, query.where);
18
+ const returning = query.returning?.map((f, index) => ({
19
+ name: model.fields[f].column,
20
+ alias: `${table.alias}_${index}`,
21
+ path: [...path, f],
22
+ })) ?? [];
23
+ return {
24
+ table,
25
+ where,
26
+ returning,
27
+ };
28
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,108 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { sql } from "@casekit/sql";
3
+ import { createTestDB } from "../tests/util/db.js";
4
+ import { buildDelete } from "./buildDelete.js";
5
+ describe("buildDelete", () => {
6
+ const { db } = createTestDB();
7
+ test("builds basic delete query with where clause", () => {
8
+ const result = buildDelete(db.config, "user", {
9
+ where: { id: 1 },
10
+ });
11
+ expect(result).toEqual({
12
+ table: {
13
+ schema: "orm",
14
+ name: "user",
15
+ alias: "a",
16
+ model: "user",
17
+ },
18
+ where: sql `"a"."id" = ${1}`,
19
+ returning: [],
20
+ });
21
+ });
22
+ test("builds delete query with returning clause", () => {
23
+ const result = buildDelete(db.config, "user", {
24
+ where: { id: 1 },
25
+ returning: ["id", "name"],
26
+ });
27
+ expect(result).toEqual({
28
+ table: {
29
+ schema: "orm",
30
+ name: "user",
31
+ alias: "a",
32
+ model: "user",
33
+ },
34
+ where: sql `"a"."id" = ${1}`,
35
+ returning: [
36
+ {
37
+ name: "id",
38
+ alias: "a_0",
39
+ path: ["id"],
40
+ },
41
+ {
42
+ name: "name",
43
+ alias: "a_1",
44
+ path: ["name"],
45
+ },
46
+ ],
47
+ });
48
+ });
49
+ test("throws error when model not found", () => {
50
+ expect(() => buildDelete(db.config, "nonexistentModel", {
51
+ where: { id: 1 },
52
+ })).toThrow('Model "nonexistentModel" not found');
53
+ });
54
+ test("throws error when where clause is empty", () => {
55
+ expect(() => buildDelete(db.config, "user", {
56
+ where: {},
57
+ })).toThrow("Delete queries must have a where clause");
58
+ });
59
+ test("throws error when where clause is undefined", () => {
60
+ expect(() => buildDelete(db.config, "user", {
61
+ // @ts-expect-error testing runtime behavior with invalid input
62
+ where: undefined,
63
+ })).toThrow("Delete queries must have a where clause");
64
+ });
65
+ test("handles complex where clauses", () => {
66
+ const result = buildDelete(db.config, "user", {
67
+ where: {
68
+ id: 1,
69
+ name: "test",
70
+ email: { $like: "%@example.com" },
71
+ },
72
+ });
73
+ expect(result).toEqual({
74
+ table: {
75
+ schema: "orm",
76
+ name: "user",
77
+ alias: "a",
78
+ model: "user",
79
+ },
80
+ where: expect.any(Object), // SQL statement object
81
+ returning: [],
82
+ });
83
+ });
84
+ test("uses correct path for nested fields in returning clause", () => {
85
+ const result = buildDelete(db.config, "user", {
86
+ where: { id: 1 },
87
+ returning: ["id", "name"],
88
+ }, ["parent"]);
89
+ expect(result.returning).toEqual([
90
+ {
91
+ name: "id",
92
+ alias: "a_0",
93
+ path: ["parent", "id"],
94
+ },
95
+ {
96
+ name: "name",
97
+ alias: "a_1",
98
+ path: ["parent", "name"],
99
+ },
100
+ ]);
101
+ });
102
+ test("uses provided table index for alias generation", () => {
103
+ const result = buildDelete(db.config, "user", {
104
+ where: { id: 1 },
105
+ }, [], 5);
106
+ expect(result.table.alias).toBe("f");
107
+ });
108
+ });
@@ -0,0 +1,8 @@
1
+ import { NormalizedConfig } from "@casekit/orm2-config";
2
+ import { ModelDefinition, OperatorDefinitions } from "@casekit/orm2-schema";
3
+ import { FindParams } from "#types/FindParams.js";
4
+ import { FindBuilder } from "./types.js";
5
+ export declare const buildFind: (config: NormalizedConfig, modelName: string, query: FindParams<Record<string, Required<ModelDefinition>>, OperatorDefinitions, string>, lateralBy?: {
6
+ field: string;
7
+ values: unknown[];
8
+ }[], path?: string[], tableIndex?: number) => FindBuilder;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * NB. Lots of ! in this file which maybe isn't great but I think for these
3
+ * builders, where we can be fairly confident due to the type-checking at the edges
4
+ * that things are defined, it's better than the alternative of loads of
5
+ * redundant checks for undefined. We've done all the checks we need at the
6
+ * stage of normalizing the model, we can now assume everything is wired up
7
+ * correctly.
8
+ *
9
+ * Also this is a file in which there's a lot of mutation and dependency
10
+ * on the order in which things are done, which is not ideal but is what
11
+ * it is. Refactor and/or add to/change with care.
12
+ */
13
+ import { pickBy, uniq } from "es-toolkit";
14
+ import { max, min } from "es-toolkit/compat";
15
+ import { getField, } from "@casekit/orm2-config";
16
+ import { hasClauses } from "#util/hasClauses.js";
17
+ import { makeTableAlias } from "#util/makeTableAlias.js";
18
+ import { buildWhere } from "./buildWhere.js";
19
+ /**
20
+ * Builds a base FindBuilder, with only columns from the main table.
21
+ * Joins will be added later.
22
+ */
23
+ const buildBaseFind = (config, m, query, lateralBy = [], path = [], tableIndex = 0) => {
24
+ const model = config.models[m];
25
+ if (!model)
26
+ throw new Error(`Model "${m}" not found`);
27
+ let colIndex = 0;
28
+ const table = {
29
+ schema: model.schema,
30
+ name: model.table,
31
+ alias: makeTableAlias(tableIndex++),
32
+ model: m,
33
+ };
34
+ const columns = uniq([
35
+ ...query.select,
36
+ // we always select the primary key even if it's not explicitly
37
+ // requested so we can use it for wiring up relationships
38
+ ...model.primaryKey.map((pk) => pk.field),
39
+ // we also select any foreign keys we're doing a lateral join on
40
+ ...lateralBy.map((l) => l.field),
41
+ ]).map((f) => {
42
+ const field = getField(model, f);
43
+ return {
44
+ table: table.alias,
45
+ name: field.column,
46
+ alias: `${table.alias}_${colIndex++}`,
47
+ path: [...path, f],
48
+ };
49
+ });
50
+ const where = hasClauses(query.where)
51
+ ? buildWhere(config, table, query.where)
52
+ : null;
53
+ return {
54
+ table,
55
+ columns,
56
+ where,
57
+ joins: [],
58
+ for: query.for,
59
+ orderBy: [],
60
+ offset: query.offset,
61
+ limit: query.limit,
62
+ tableIndex,
63
+ };
64
+ };
65
+ const collectManyToOneQueries = (model, query) => {
66
+ // this slightly complex chain gets all the N:1 relations
67
+ // we're querying. as well as the contents of the include
68
+ // key, we make sure to include a join to any relations
69
+ // we're ordering by (we'll use them later).
70
+ // relations we're ordering by are identified by being a
71
+ // model name and field name separated by a dot.
72
+ // this is a tiny bit brittle in the case where people
73
+ // create a model or field name that contains a dot,
74
+ // but that would be a v weird thing to do as they are
75
+ // both javascript identifiers so would be a real pain
76
+ // to write in code. so i think it's probably fine to
77
+ // not support it.
78
+ return pickBy({
79
+ ...query.include,
80
+ ...query.orderBy
81
+ // deal with the fact that an order by column
82
+ //can be a single value or an array with a direction
83
+ ?.map((ob) => (typeof ob === "string" ? ob : ob[0]))
84
+ // get only the order by columns that are on relation tables
85
+ .filter((ob) => ob.includes("."))
86
+ // get the relation model name
87
+ .map((ob) => ob.split(".")[0])
88
+ // check it's not already something we're joining to
89
+ .filter((f) => !(f in (query.include ?? {})))
90
+ // if not, add a query to it with an empty select
91
+ // (ok because we always select the primary key)
92
+ .reduce((acc, f) => ({ ...acc, [f]: { select: [] } }), {}),
93
+ },
94
+ // finally select only N:1 relations as these
95
+ // are the only kind we join to directly
96
+ (_query, relation) => model.relations[relation].type === "N:1");
97
+ };
98
+ const buildOrderBy = (config, model, query, builder) => {
99
+ return (query.orderBy?.map((ob) => {
100
+ const field = typeof ob === "string" ? ob : ob[0];
101
+ const direction = typeof ob === "string" ? "ASC" : ob[1];
102
+ if (field.includes(".")) {
103
+ const [relation, relationField] = field.split(".");
104
+ const join = builder.joins.find((j) => j.relation === relation);
105
+ const joinedModel = config.models[model.relations[join.relation].model];
106
+ return {
107
+ column: {
108
+ table: join.table.alias,
109
+ name: joinedModel.fields[relationField].column,
110
+ },
111
+ direction: direction.toUpperCase() === "DESC" ? "DESC" : "ASC",
112
+ };
113
+ }
114
+ else {
115
+ return {
116
+ column: {
117
+ table: builder.table.alias,
118
+ name: getField(model, field).column,
119
+ },
120
+ direction: direction.toUpperCase() === "DESC" ? "DESC" : "ASC",
121
+ };
122
+ }
123
+ }) ?? []);
124
+ };
125
+ export const buildFind = (config, modelName, query, lateralBy = [], path = [], tableIndex = 0) => {
126
+ const model = config.models[modelName];
127
+ if (!model)
128
+ throw new Error(`Model "${modelName}" not found`);
129
+ const builder = buildBaseFind(config, modelName, query, lateralBy, path, tableIndex);
130
+ const manyToOneQueries = collectManyToOneQueries(model, query);
131
+ for (const [r, subquery] of Object.entries(manyToOneQueries)) {
132
+ const relation = model.relations[r];
133
+ if (relation.type !== "N:1") {
134
+ throw new Error(`Unexpected relation type: ${relation.type}. Should be N:1`);
135
+ }
136
+ const joinBuilder = buildFind(config, relation.model, subquery, [], [...path, r], builder.tableIndex);
137
+ builder.joins.push({
138
+ relation: r,
139
+ path: [...path, r],
140
+ type: relation.optional && !subquery.where ? "LEFT" : "INNER",
141
+ table: joinBuilder.table,
142
+ where: joinBuilder.where,
143
+ orderBy: joinBuilder.orderBy,
144
+ columns: relation.from.columns.map((fk, i) => ({
145
+ from: {
146
+ table: builder.table.alias,
147
+ name: fk,
148
+ },
149
+ to: {
150
+ table: joinBuilder.table.alias,
151
+ name: relation.to.columns[i],
152
+ },
153
+ })),
154
+ });
155
+ // update the parent builder's tableIndex with the one from the
156
+ // joinBuilder, so that we don't have overlapping table indices
157
+ builder.tableIndex = joinBuilder.tableIndex;
158
+ builder.columns.push(...joinBuilder.columns);
159
+ builder.joins.push(...joinBuilder.joins);
160
+ // this is admittedly a bit weird,
161
+ // we wouldn't expect N:1 relations to specify
162
+ // ordering, skipping, and limiting, and typescript
163
+ // prevents users from doing this, but
164
+ // we rely on them being present as part of the N:N
165
+ // implementation
166
+ builder.limit = min([builder.limit, joinBuilder.limit]);
167
+ builder.offset = max([builder.offset, joinBuilder.offset]);
168
+ }
169
+ if (lateralBy.length > 0) {
170
+ builder.lateralBy = {
171
+ outerAlias: makeTableAlias(builder.tableIndex++),
172
+ innerAlias: makeTableAlias(builder.tableIndex++),
173
+ primaryKeys: lateralBy.map(({ field, values }) => ({
174
+ column: getField(model, field).column,
175
+ type: getField(model, field).type,
176
+ values,
177
+ })),
178
+ };
179
+ }
180
+ builder.orderBy = [
181
+ ...buildOrderBy(config, model, query, builder),
182
+ ...builder.joins.flatMap((j) => j.orderBy ?? []),
183
+ ];
184
+ return builder;
185
+ };
@@ -0,0 +1 @@
1
+ export {};