@casekit/orm 0.0.1-alpha.15 → 0.0.1-alpha.16

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 (215) hide show
  1. package/lib/orm.query.test.d.ts +2 -0
  2. package/lib/orm.query.test.d.ts.map +1 -0
  3. package/lib/orm.query.test.js +22 -0
  4. package/lib/orm.query.test.js.map +1 -0
  5. package/package.json +3 -6
  6. package/src/Connection.ts +65 -0
  7. package/src/errors.ts +18 -0
  8. package/src/index.ts +16 -0
  9. package/src/logger.ts +3 -0
  10. package/src/migrate/commands/implode.ts +46 -0
  11. package/src/migrate/index.ts +1 -0
  12. package/src/migrate/migrator.ts +24 -0
  13. package/src/migrate/sql/createExtensionsSql.test.ts +26 -0
  14. package/src/migrate/sql/createExtensionsSql.ts +16 -0
  15. package/src/migrate/sql/createForeignKeyConstraintSql.test.ts +50 -0
  16. package/src/migrate/sql/createForeignKeyConstraintSql.ts +44 -0
  17. package/src/migrate/sql/createSchemasSql.test.ts +81 -0
  18. package/src/migrate/sql/createSchemasSql.ts +15 -0
  19. package/src/migrate/sql/createTableSql.properties.ts +38 -0
  20. package/src/migrate/sql/createTableSql.test.ts +74 -0
  21. package/src/migrate/sql/createTableSql.ts +53 -0
  22. package/src/migrate/sql/createUniqueConstraintSql.ts +27 -0
  23. package/src/migrate/sql/dropSchemasSql.ts +15 -0
  24. package/src/migrate/sql/dropTableSql.ts +13 -0
  25. package/src/orm.query.test.ts +28 -0
  26. package/src/orm.ts +370 -0
  27. package/src/pull/index.ts +1 -0
  28. package/src/pull/introspect/getForeignKeys.ts +64 -0
  29. package/src/pull/introspect/getPrimaryKeys.ts +26 -0
  30. package/src/pull/introspect/getTables.ts +51 -0
  31. package/src/pull/introspect/getUniqueConstraints.ts +39 -0
  32. package/src/pull/parse/parseCreateUniqueIndexStatement.test.ts +14 -0
  33. package/src/pull/parse/parseCreateUniqueIndexStatement.ts +19 -0
  34. package/src/pull/pull.ts +78 -0
  35. package/src/pull/render/renderModel.test.ts +144 -0
  36. package/src/pull/render/renderModel.ts +141 -0
  37. package/src/pull/render/renderModelsIndex.ts +24 -0
  38. package/src/pull/render/renderRelations.ts +77 -0
  39. package/src/pull/render/renderRelationsIndex.ts +24 -0
  40. package/src/pull/types/ColumnMeta.ts +10 -0
  41. package/src/pull/types/ForeignKey.ts +6 -0
  42. package/src/pull/types/PrimaryKey.ts +6 -0
  43. package/src/pull/types/UniqueConstraint.ts +8 -0
  44. package/src/pull/util/format.ts +17 -0
  45. package/src/pull/util/quote.ts +7 -0
  46. package/src/pull/util/unquote.ts +9 -0
  47. package/src/queries/clauses/IncludeClause.ts +39 -0
  48. package/src/queries/clauses/LateralByClause.ts +4 -0
  49. package/src/queries/clauses/ReturningClause.ts +7 -0
  50. package/src/queries/clauses/SelectClause.ts +7 -0
  51. package/src/queries/clauses/WhereClause.ts +16 -0
  52. package/src/queries/clauses/helpers/OptionalColumn.ts +18 -0
  53. package/src/queries/clauses/helpers/OptionalParams.ts +14 -0
  54. package/src/queries/clauses/helpers/RequiredColumn.ts +8 -0
  55. package/src/queries/clauses/helpers/RequiredParams.ts +14 -0
  56. package/src/queries/clauses/include/IncludedRelationModel.ts +13 -0
  57. package/src/queries/clauses/include/IncludedRelationName.ts +11 -0
  58. package/src/queries/clauses/include/IncludedRelationQuery.ts +20 -0
  59. package/src/queries/clauses/where/buildWhereClause.ts +121 -0
  60. package/src/queries/clauses/where/buildWhereClauses.test.ts +145 -0
  61. package/src/queries/clauses/where/buildWhereClauses.ts +45 -0
  62. package/src/queries/clauses/where/operators.ts +13 -0
  63. package/src/queries/clauses/where/types/WhereClauseValue.ts +39 -0
  64. package/src/queries/count/buildCount.ts +77 -0
  65. package/src/queries/count/countToSql.ts +66 -0
  66. package/src/queries/count/tests/count.test.ts +35 -0
  67. package/src/queries/count/types/BaseCountParams.ts +11 -0
  68. package/src/queries/count/types/CountBuilder.ts +45 -0
  69. package/src/queries/count/types/CountParams.ts +27 -0
  70. package/src/queries/count.ts +33 -0
  71. package/src/queries/create/buildCreate.ts +67 -0
  72. package/src/queries/create/createResultSchema.ts +24 -0
  73. package/src/queries/create/createToSql.ts +44 -0
  74. package/src/queries/create/tests/createMany.varied-keys.test.ts +28 -0
  75. package/src/queries/create/tests/createOne.test-d.ts +116 -0
  76. package/src/queries/create/tests/createOne.test.ts +197 -0
  77. package/src/queries/create/types/BaseCreateManyParams.ts +7 -0
  78. package/src/queries/create/types/BaseCreateOneParams.ts +7 -0
  79. package/src/queries/create/types/CreateManyParams.ts +15 -0
  80. package/src/queries/create/types/CreateManyResult.ts +17 -0
  81. package/src/queries/create/types/CreateOneParams.ts +22 -0
  82. package/src/queries/create/types/CreateOneResult.ts +17 -0
  83. package/src/queries/createMany.ts +38 -0
  84. package/src/queries/createOne.ts +27 -0
  85. package/src/queries/delete/buildDelete.ts +56 -0
  86. package/src/queries/delete/deleteResultSchema.ts +23 -0
  87. package/src/queries/delete/deleteToSql.ts +48 -0
  88. package/src/queries/delete/tests/deleteOne.test.ts +108 -0
  89. package/src/queries/delete/types/BaseDeleteParams.ts +9 -0
  90. package/src/queries/delete/types/DeleteManyResult.ts +16 -0
  91. package/src/queries/delete/types/DeleteOneResult.ts +16 -0
  92. package/src/queries/delete/types/DeleteParams.ts +12 -0
  93. package/src/queries/deleteMany.ts +33 -0
  94. package/src/queries/deleteOne.ts +32 -0
  95. package/src/queries/find/buildFind.ts +138 -0
  96. package/src/queries/find/findResultSchema.ts +32 -0
  97. package/src/queries/find/findToSql.test.ts +123 -0
  98. package/src/queries/find/findToSql.ts +141 -0
  99. package/src/queries/find/getIncludedManyToManyRelations.ts +49 -0
  100. package/src/queries/find/getIncludedOneToManyRelations.ts +44 -0
  101. package/src/queries/find/tests/findMany.include.test.ts +107 -0
  102. package/src/queries/find/tests/findMany.limit.test-d.ts +75 -0
  103. package/src/queries/find/tests/findMany.limit.test.ts +176 -0
  104. package/src/queries/find/tests/findMany.nullable-relations.test.ts +127 -0
  105. package/src/queries/find/tests/findMany.orderBy.test-d.ts +84 -0
  106. package/src/queries/find/tests/findMany.orderBy.test.ts +184 -0
  107. package/src/queries/find/tests/findMany.select.test-d.ts +117 -0
  108. package/src/queries/find/tests/findMany.select.test.ts +188 -0
  109. package/src/queries/find/tests/findMany.too-deep.test-d.ts +154 -0
  110. package/src/queries/find/tests/findMany.where.test-d.ts +85 -0
  111. package/src/queries/find/tests/findMany.where.test.ts +76 -0
  112. package/src/queries/find/tests/middleware.find.where.test.ts +467 -0
  113. package/src/queries/find/types/BaseFindParams.ts +18 -0
  114. package/src/queries/find/types/FindBuilder.ts +73 -0
  115. package/src/queries/find/types/FindManyParams.ts +24 -0
  116. package/src/queries/find/types/FindManyResult.ts +13 -0
  117. package/src/queries/find/types/FindOneParams.ts +17 -0
  118. package/src/queries/find/types/FindOneResult.ts +50 -0
  119. package/src/queries/findMany.ts +134 -0
  120. package/src/queries/findOne.ts +30 -0
  121. package/src/queries/middleware/Middleware.ts +53 -0
  122. package/src/queries/middleware/ValuesMiddleware.ts +9 -0
  123. package/src/queries/middleware/WhereMiddleware.ts +16 -0
  124. package/src/queries/update/buildUpdate.ts +76 -0
  125. package/src/queries/update/tests/updateOne.test.ts +118 -0
  126. package/src/queries/update/types/BaseUpdateParams.ts +10 -0
  127. package/src/queries/update/types/UpdateManyResult.ts +16 -0
  128. package/src/queries/update/types/UpdateOneResult.ts +16 -0
  129. package/src/queries/update/types/UpdateParams.ts +14 -0
  130. package/src/queries/update/types/UpdateValues.ts +12 -0
  131. package/src/queries/update/updateResultSchema.ts +23 -0
  132. package/src/queries/update/updateToSql.ts +43 -0
  133. package/src/queries/updateMany.ts +33 -0
  134. package/src/queries/updateOne.ts +33 -0
  135. package/src/queries/util/hasConditions.test.ts +36 -0
  136. package/src/queries/util/hasConditions.ts +14 -0
  137. package/src/queries/util/rowToObject.test.ts +76 -0
  138. package/src/queries/util/rowToObject.ts +13 -0
  139. package/src/queries/util/tableAlias.test.ts +20 -0
  140. package/src/queries/util/tableAlias.ts +10 -0
  141. package/src/schema/populate/composeMiddleware.ts +51 -0
  142. package/src/schema/populate/populateConfiguration.ts +48 -0
  143. package/src/schema/populate/populateModel.ts +98 -0
  144. package/src/schema/populate/suggestedColumnSchema.ts +62 -0
  145. package/src/schema/types/base/BaseColumn.ts +10 -0
  146. package/src/schema/types/base/BaseConfiguration.ts +14 -0
  147. package/src/schema/types/base/BaseModel.ts +12 -0
  148. package/src/schema/types/base/BaseModels.ts +3 -0
  149. package/src/schema/types/base/BaseOrm.ts +8 -0
  150. package/src/schema/types/base/BaseRelation.ts +19 -0
  151. package/src/schema/types/base/BaseRelations.ts +3 -0
  152. package/src/schema/types/constraints/ForeignKey.ts +13 -0
  153. package/src/schema/types/constraints/UniqueConstraint.ts +9 -0
  154. package/src/schema/types/helpers/ColumnName.ts +6 -0
  155. package/src/schema/types/helpers/ColumnType.ts +23 -0
  156. package/src/schema/types/helpers/Columns.ts +3 -0
  157. package/src/schema/types/helpers/HasDefault.ts +11 -0
  158. package/src/schema/types/helpers/IsNullable.ts +7 -0
  159. package/src/schema/types/helpers/IsProvided.ts +8 -0
  160. package/src/schema/types/helpers/IsSerial.ts +10 -0
  161. package/src/schema/types/helpers/ModelName.ts +6 -0
  162. package/src/schema/types/loose/LooseColumnDefinition.ts +80 -0
  163. package/src/schema/types/loose/LooseModelDefinition.ts +47 -0
  164. package/src/schema/types/loose/LooseModelDefinitions.ts +3 -0
  165. package/src/schema/types/loose/LooseRelationDefinition.ts +10 -0
  166. package/src/schema/types/loose/LooseRelationsDefinition.ts +8 -0
  167. package/src/schema/types/loose/LooseRelationsDefinitions.ts +7 -0
  168. package/src/schema/types/postgres/DataType.ts +65 -0
  169. package/src/schema/types/relations/ManyToManyRelation.ts +15 -0
  170. package/src/schema/types/relations/ManyToOneRelation.ts +14 -0
  171. package/src/schema/types/relations/OneToManyRelation.ts +12 -0
  172. package/src/schema/types/strict/ColumnDefinition.ts +81 -0
  173. package/src/schema/types/strict/ModelDefinition.ts +47 -0
  174. package/src/schema/types/strict/ModelDefinitions.ts +3 -0
  175. package/src/schema/types/strict/RelationDefinition.ts +13 -0
  176. package/src/schema/types/strict/RelationsDefinition.ts +8 -0
  177. package/src/schema/types/strict/RelationsDefinitions.ts +7 -0
  178. package/src/schema/validate/validateConfiguration.ts +9 -0
  179. package/src/schema/validate/validateModel.ts +32 -0
  180. package/src/sql/SQLStatement.test.ts +112 -0
  181. package/src/sql/SQLStatement.ts +86 -0
  182. package/src/sql/index.ts +2 -0
  183. package/src/sql/sql.ts +47 -0
  184. package/src/test/db/index.ts +27 -0
  185. package/src/test/db/models/foo.model.ts +24 -0
  186. package/src/test/db/models/foo.relations.ts +4 -0
  187. package/src/test/db/models/post.model.ts +34 -0
  188. package/src/test/db/models/post.relations.ts +21 -0
  189. package/src/test/db/models/tenant.model.ts +14 -0
  190. package/src/test/db/models/tenant.relations.ts +22 -0
  191. package/src/test/db/models/tenantUser.model.ts +23 -0
  192. package/src/test/db/models/tenantUser.relations.ts +15 -0
  193. package/src/test/db/models/user.model.ts +29 -0
  194. package/src/test/db/models/user.relations.ts +38 -0
  195. package/src/test/db/models.ts +15 -0
  196. package/src/test/db/relations.ts +13 -0
  197. package/src/test/gen/column.ts +39 -0
  198. package/src/test/gen/index.ts +2 -0
  199. package/src/test/gen/model.ts +58 -0
  200. package/src/test/gen/sqldate.ts +8 -0
  201. package/src/test/globalSetup.ts +6 -0
  202. package/src/test/seed/index.ts +75 -0
  203. package/src/test/util/withRollback.ts +18 -0
  204. package/src/test/util/withTransaction.ts +19 -0
  205. package/src/types/ColumnName.ts +6 -0
  206. package/src/types/ColumnType.ts +20 -0
  207. package/src/types/Configuration.ts +21 -0
  208. package/src/types/ModelType.ts +20 -0
  209. package/src/types/util/DeepRequired.ts +7 -0
  210. package/src/types/util/DisallowExtraKeys.ts +5 -0
  211. package/src/types/util/NonEmptyArray.ts +1 -0
  212. package/src/types/util/Simplify.ts +8 -0
  213. package/src/util/ensureArray.ts +4 -0
  214. package/src/util/interleave.test.ts +35 -0
  215. package/src/util/interleave.ts +8 -0
@@ -0,0 +1,134 @@
1
+ import { dropRight, get, groupBy, set } from "lodash-es";
2
+ import hash from "object-hash";
3
+ import { BaseConfiguration } from "src/schema/types/base/BaseConfiguration";
4
+
5
+ import { Connection } from "../Connection";
6
+ import { OrmError } from "../errors";
7
+ import { logger } from "../logger";
8
+ import { ensureArray } from "../util/ensureArray";
9
+ import { buildFind } from "./find/buildFind";
10
+ import { findToSql } from "./find/findToSql";
11
+ import { getIncludedManyToManyRelations } from "./find/getIncludedManyToManyRelations";
12
+ import { getIncludedOneToManyRelations } from "./find/getIncludedOneToManyRelations";
13
+ import { BaseFindParams } from "./find/types/BaseFindParams";
14
+ import { rowToObject } from "./util/rowToObject";
15
+
16
+ export const findMany = async (
17
+ conn: Connection,
18
+ config: BaseConfiguration,
19
+ m: string,
20
+ query: BaseFindParams,
21
+ ) => {
22
+ const builder = buildFind(config, m, query);
23
+ const statement = findToSql(config, builder);
24
+ logger.info({
25
+ message: "Executing query",
26
+ sql: statement.text,
27
+ values: statement.values,
28
+ });
29
+
30
+ if (process.env.ORM_VERBOSE_LOGGING) {
31
+ console.log(statement.text);
32
+ console.log(statement.values);
33
+ }
34
+
35
+ const results = await conn
36
+ .query(statement)
37
+ .then((result) => result.rows.map(rowToObject(builder.columns)));
38
+
39
+ const fetchIncludedOneToManyRelations = getIncludedOneToManyRelations(
40
+ config,
41
+ m,
42
+ query,
43
+ ).map(({ model, relation, query, path }) => {
44
+ const pk = config.models[model].primaryKey;
45
+ const fk = ensureArray(relation.foreignKey);
46
+ const lateralBy = fk.map((c, index) => ({
47
+ column: c,
48
+ values: results.map((result) =>
49
+ get(result, [...dropRight(path, 1), pk[index]]),
50
+ ),
51
+ }));
52
+ return findMany(conn, config, relation.model, {
53
+ ...query,
54
+ for: builder.for,
55
+ lateralBy,
56
+ }).then((subqueryResults) => {
57
+ const lookup = groupBy(subqueryResults, (result) => {
58
+ return hash(fk.map((c) => result[c]));
59
+ });
60
+ for (const result of results) {
61
+ const parent =
62
+ path.length === 1
63
+ ? result
64
+ : get(result, dropRight(path, 1));
65
+ if (parent !== undefined) {
66
+ const key = hash(pk.map((c) => get(parent, c)));
67
+ set(result, path, lookup[key] ?? []);
68
+ }
69
+ }
70
+ });
71
+ });
72
+
73
+ const fetchIncludedManyToManyRelations = getIncludedManyToManyRelations(
74
+ config,
75
+ m,
76
+ query,
77
+ ).map(({ model, relation, query, path }) => {
78
+ const joinFrom = Object.entries(config.relations[model]).find(
79
+ ([, rel]) => rel.type === "1:N" && rel.model === relation.through,
80
+ )?.[0];
81
+
82
+ const joinTo = Object.entries(config.relations[relation.through]).find(
83
+ ([, rel]) => rel.type === "N:1" && rel.model === relation.model,
84
+ )?.[0];
85
+
86
+ if (joinFrom === undefined || joinTo === undefined) {
87
+ throw new OrmError("Both sides of a N:N relation must be defined", {
88
+ data: { joinFrom, joinTo, query },
89
+ });
90
+ }
91
+
92
+ const pk = config.models[model].primaryKey;
93
+ const fk = ensureArray(relation.foreignKey);
94
+ const lateralBy = fk.map((c, index) => ({
95
+ column: c,
96
+ values: results.map((result) =>
97
+ get(result, [...dropRight(path, 1), pk[index]]),
98
+ ),
99
+ }));
100
+
101
+ return findMany(conn, config, relation.through, {
102
+ select: ensureArray(relation.foreignKey),
103
+ include: { [joinTo]: query },
104
+ for: builder.for,
105
+ lateralBy,
106
+ }).then((subqueryResults) => {
107
+ const lookup = groupBy(subqueryResults, (result) => {
108
+ return hash(fk.map((c) => result[c]));
109
+ });
110
+ for (const result of results) {
111
+ const parent =
112
+ path.length === 1
113
+ ? result
114
+ : get(result, dropRight(path, 1));
115
+
116
+ if (parent !== undefined) {
117
+ const key = hash(pk.map((c) => get(parent, c)));
118
+ set(
119
+ result,
120
+ path,
121
+ (lookup[key] ?? []).map((r) => r[joinTo] ?? []),
122
+ );
123
+ }
124
+ }
125
+ });
126
+ });
127
+
128
+ await Promise.all([
129
+ ...fetchIncludedOneToManyRelations,
130
+ ...fetchIncludedManyToManyRelations,
131
+ ]);
132
+
133
+ return results;
134
+ };
@@ -0,0 +1,30 @@
1
+ import { BaseConfiguration } from "src/schema/types/base/BaseConfiguration";
2
+
3
+ import { Connection } from "../Connection";
4
+ import { OrmError } from "../errors";
5
+ import { BaseFindParams } from "./find/types/BaseFindParams";
6
+ import { findMany } from "./findMany";
7
+
8
+ export const findOne = async (
9
+ conn: Connection,
10
+ config: BaseConfiguration,
11
+ m: string,
12
+ query: BaseFindParams,
13
+ ) => {
14
+ const results = await findMany(conn, config, m, { ...query, limit: 2 });
15
+
16
+ if (results.length === 0) {
17
+ throw new OrmError("FindOne found zero records", {
18
+ model: [m, config.models[m]],
19
+ data: query,
20
+ });
21
+ }
22
+ if (results.length > 1) {
23
+ throw new OrmError("FindOne found too many records", {
24
+ model: [m, config.models[m]],
25
+ data: query,
26
+ });
27
+ }
28
+
29
+ return results[0];
30
+ };
@@ -0,0 +1,53 @@
1
+ import { BaseConfiguration } from "../../schema/types/base/BaseConfiguration";
2
+ import { BaseOrm } from "../../schema/types/base/BaseOrm";
3
+ import { BaseDeleteParams } from "../delete/types/BaseDeleteParams";
4
+ import { BaseUpdateParams } from "../update/types/BaseUpdateParams";
5
+ import { ValuesMiddleware } from "./ValuesMiddleware";
6
+ import { WhereMiddleware } from "./WhereMiddleware";
7
+
8
+ export type Middleware = {
9
+ where?: WhereMiddleware;
10
+ values?: ValuesMiddleware;
11
+ find?: {
12
+ where?: WhereMiddleware;
13
+ };
14
+ create?: {
15
+ values?: ValuesMiddleware;
16
+ };
17
+ update?: {
18
+ set?: ValuesMiddleware;
19
+ where?: WhereMiddleware;
20
+ };
21
+ count?: {
22
+ where?: WhereMiddleware;
23
+ };
24
+ delete?: {
25
+ where?: WhereMiddleware;
26
+ deleteOne?: (
27
+ params: BaseDeleteParams,
28
+ meta: {
29
+ config: BaseConfiguration;
30
+ model: string;
31
+ deleteOne: (
32
+ params: BaseDeleteParams,
33
+ ) => ReturnType<BaseOrm["deleteOne"]>;
34
+ updateOne: (
35
+ params: BaseUpdateParams,
36
+ ) => ReturnType<BaseOrm["updateOne"]>;
37
+ },
38
+ ) => unknown;
39
+ deleteMany?: (
40
+ params: BaseDeleteParams,
41
+ meta: {
42
+ config: BaseConfiguration;
43
+ model: string;
44
+ deleteMany: (
45
+ params: BaseDeleteParams,
46
+ ) => ReturnType<BaseOrm["deleteOne"]>;
47
+ updateMany: (
48
+ params: BaseUpdateParams,
49
+ ) => ReturnType<BaseOrm["updateOne"]>;
50
+ },
51
+ ) => unknown;
52
+ };
53
+ };
@@ -0,0 +1,9 @@
1
+ import { BaseConfiguration } from "../../schema/types/base/BaseConfiguration";
2
+
3
+ export type ValuesMiddleware = (
4
+ values: Record<string, unknown>,
5
+ meta: {
6
+ model: string;
7
+ config: BaseConfiguration;
8
+ },
9
+ ) => Record<string, unknown>;
@@ -0,0 +1,16 @@
1
+ import { BaseConfiguration } from "../../schema/types/base/BaseConfiguration";
2
+ import { ModelName } from "../../schema/types/helpers/ModelName";
3
+ import { LooseModelDefinitions } from "../../schema/types/loose/LooseModelDefinitions";
4
+ import { WhereClause } from "../clauses/WhereClause";
5
+
6
+ export type WhereMiddleware = (
7
+ where:
8
+ | WhereClause<LooseModelDefinitions, ModelName<LooseModelDefinitions>>
9
+ | undefined,
10
+ meta: {
11
+ model: string;
12
+ config: BaseConfiguration;
13
+ },
14
+ ) =>
15
+ | WhereClause<LooseModelDefinitions, ModelName<LooseModelDefinitions>>
16
+ | undefined;
@@ -0,0 +1,76 @@
1
+ import { BaseConfiguration } from "src/schema/types/base/BaseConfiguration";
2
+
3
+ import { OrmError } from "../../errors";
4
+ import { ModelName } from "../../schema/types/helpers/ModelName";
5
+ import { LooseModelDefinitions } from "../../schema/types/loose/LooseModelDefinitions";
6
+ import { WhereClause } from "../clauses/WhereClause";
7
+ import { tableAlias } from "../util/tableAlias";
8
+ import { BaseUpdateParams } from "./types/BaseUpdateParams";
9
+
10
+ export type UpdateBuilder = {
11
+ tableIndex: number;
12
+ table: { table: string; model: string; alias: string; schema: string };
13
+ where: WhereClause<LooseModelDefinitions, ModelName<LooseModelDefinitions>>;
14
+ set: { name: string; value: unknown }[];
15
+ returning: { name: string; path: string; alias: string }[];
16
+ };
17
+
18
+ export const buildUpdate = (
19
+ config: BaseConfiguration,
20
+ m: string,
21
+ params: BaseUpdateParams,
22
+ _tableIndex = 0,
23
+ ): UpdateBuilder => {
24
+ const builder: UpdateBuilder = {
25
+ tableIndex: _tableIndex,
26
+ table: {
27
+ table: config.models[m].table,
28
+ schema: config.models[m].schema,
29
+ model: m,
30
+ alias: tableAlias(_tableIndex++),
31
+ },
32
+ where: config.middleware.update?.where
33
+ ? config.middleware.update.where(params.where, {
34
+ config,
35
+ model: m,
36
+ })!
37
+ : params.where!,
38
+ set: [],
39
+ returning: [],
40
+ };
41
+ let colIndex = 0;
42
+ const model = config.models[m];
43
+ const set = config.middleware.update?.set
44
+ ? config.middleware.update.set(params.set, {
45
+ config,
46
+ model: m,
47
+ })
48
+ : params.set;
49
+
50
+ if (set.length === 0) {
51
+ throw new OrmError("No updates provided for update operation", {
52
+ data: { m, model, params },
53
+ });
54
+ }
55
+
56
+ if (Object.keys(builder.where).length === 0) {
57
+ throw new OrmError("No where clause provided for update operation", {
58
+ data: { m, model, params },
59
+ });
60
+ }
61
+
62
+ for (const [k, v] of Object.entries(set)) {
63
+ builder.set.push({
64
+ name: model.columns[k]["name"],
65
+ value: v,
66
+ });
67
+ }
68
+ for (const f of params.returning ?? []) {
69
+ builder.returning.push({
70
+ name: model.columns[f]["name"],
71
+ path: f,
72
+ alias: `${builder.table.alias}_${colIndex++}`,
73
+ });
74
+ }
75
+ return builder;
76
+ };
@@ -0,0 +1,118 @@
1
+ import { describe, expect, test } from "vitest";
2
+
3
+ import { OrmError } from "../../../errors";
4
+ import { db } from "../../../test/db";
5
+ import { seed } from "../../../test/seed";
6
+ import { $like } from "../../clauses/where/operators";
7
+
8
+ describe("updateOne", () => {
9
+ test("it updates a single record in the database", async () => {
10
+ await db.transact(
11
+ async (db) => {
12
+ const { users } = await seed(db, {
13
+ users: [
14
+ {
15
+ username: "Stewart House",
16
+ tenants: [{ name: "Popova Park", posts: 3 }],
17
+ },
18
+ ],
19
+ });
20
+
21
+ const user = users["Stewart House"];
22
+
23
+ const updated = await db.updateOne("user", {
24
+ set: { username: "Stewart Home" },
25
+ where: { username: "Stewart House" },
26
+ returning: ["id", "username"],
27
+ });
28
+
29
+ expect(updated.id).toEqual(user.id);
30
+ expect(updated.username).toEqual("Stewart Home");
31
+
32
+ const results = await db.findMany("user", {
33
+ select: ["id", "username"],
34
+ where: { username: { [$like]: "Stewart %" } },
35
+ });
36
+
37
+ expect(results).toEqual([updated]);
38
+ },
39
+ { rollback: true },
40
+ );
41
+ });
42
+
43
+ test("if no records are updated, it throws an error and rolls back to before the update", async () => {
44
+ await db.transact(
45
+ async (db) => {
46
+ const { users } = await seed(db, {
47
+ users: [
48
+ {
49
+ username: "Stewart House",
50
+ tenants: [{ name: "Popova Park", posts: 3 }],
51
+ },
52
+ ],
53
+ });
54
+
55
+ const user = users["Stewart House"];
56
+
57
+ await expect(
58
+ db.updateOne("user", {
59
+ set: { username: "Stewart Home" },
60
+ where: { username: "Stewart Wrong" },
61
+ returning: ["id", "username"],
62
+ }),
63
+ ).rejects.toEqual(new OrmError("No rows updated"));
64
+
65
+ const result = await db.findOne("user", {
66
+ select: ["id", "username"],
67
+ where: { username: "Stewart House" },
68
+ });
69
+
70
+ expect(result).toEqual(user);
71
+ },
72
+ { rollback: true },
73
+ );
74
+ });
75
+
76
+ test("if multiple records are updated, it throws an error and rolls back to before the update", async () => {
77
+ await db.transact(
78
+ async (db) => {
79
+ await seed(db, {
80
+ users: [
81
+ {
82
+ username: "Stewart House",
83
+ tenants: [{ name: "Popova Park", posts: 3 }],
84
+ },
85
+ {
86
+ username: "Stewart Home",
87
+ tenants: [{ name: "Popova Park", posts: 3 }],
88
+ },
89
+ ],
90
+ });
91
+
92
+ await expect(
93
+ db.updateOne("user", {
94
+ set: { joinedAt: new Date("2021-01-04") },
95
+ where: { username: { [$like]: "Stewart %" } },
96
+ returning: ["id", "username"],
97
+ }),
98
+ ).rejects.toEqual(
99
+ new OrmError(
100
+ "More than one updated row for updateOne, rolling back",
101
+ ),
102
+ );
103
+
104
+ const result = await db.findMany("user", {
105
+ select: ["id", "username", "joinedAt"],
106
+ where: { username: { [$like]: "Stewart %" } },
107
+ orderBy: ["username"],
108
+ });
109
+
110
+ expect(result.map((u) => u.joinedAt)).not.toEqual([
111
+ new Date("2021-01-04"),
112
+ new Date("2021-01-04"),
113
+ ]);
114
+ },
115
+ { rollback: true },
116
+ );
117
+ });
118
+ });
@@ -0,0 +1,10 @@
1
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
2
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
3
+ import { NonEmptyArray } from "../../../types/util/NonEmptyArray";
4
+ import { WhereClause } from "../../clauses/WhereClause";
5
+
6
+ export type BaseUpdateParams = {
7
+ set: Record<string, unknown | null>;
8
+ where: WhereClause<LooseModelDefinitions, ModelName<LooseModelDefinitions>>;
9
+ returning?: NonEmptyArray<string>;
10
+ };
@@ -0,0 +1,16 @@
1
+ import { ColumnType } from "../../../schema/types/helpers/ColumnType";
2
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
3
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
4
+ import { ReturningClause } from "../../clauses/ReturningClause";
5
+ import { UpdateParams } from "./UpdateParams";
6
+
7
+ export type UpdateManyResult<
8
+ Models extends LooseModelDefinitions,
9
+ M extends ModelName<Models>,
10
+ P extends UpdateParams<Models, M>,
11
+ > =
12
+ P["returning"] extends ReturningClause<Models[M]>
13
+ ? Readonly<{
14
+ [C in P["returning"][number]]: ColumnType<Models, M, C>;
15
+ }>[]
16
+ : number;
@@ -0,0 +1,16 @@
1
+ import { ColumnType } from "../../../schema/types/helpers/ColumnType";
2
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
3
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
4
+ import { ReturningClause } from "../../clauses/ReturningClause";
5
+ import { UpdateParams } from "./UpdateParams";
6
+
7
+ export type UpdateOneResult<
8
+ Models extends LooseModelDefinitions,
9
+ M extends ModelName<Models>,
10
+ P extends UpdateParams<Models, M>,
11
+ > =
12
+ P["returning"] extends ReturningClause<Models[M]>
13
+ ? Readonly<{
14
+ [C in P["returning"][number]]: ColumnType<Models, M, C>;
15
+ }>
16
+ : number;
@@ -0,0 +1,14 @@
1
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
2
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
3
+ import { ReturningClause } from "../../clauses/ReturningClause";
4
+ import { WhereClause } from "../../clauses/WhereClause";
5
+ import { UpdateValues } from "./UpdateValues";
6
+
7
+ export type UpdateParams<
8
+ Models extends LooseModelDefinitions,
9
+ M extends ModelName<Models>,
10
+ > = {
11
+ set: UpdateValues<Models, M>;
12
+ where: WhereClause<Models, M>;
13
+ returning?: ReturningClause<Models[M]>;
14
+ };
@@ -0,0 +1,12 @@
1
+ import { ColumnName } from "../../../schema/types/helpers/ColumnName";
2
+ import { ColumnType } from "../../../schema/types/helpers/ColumnType";
3
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
4
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
5
+ import { Simplify } from "../../../types/util/Simplify";
6
+
7
+ export type UpdateValues<
8
+ Models extends LooseModelDefinitions,
9
+ M extends ModelName<Models>,
10
+ > = Simplify<{
11
+ [C in ColumnName<Models[M]>]?: ColumnType<Models, M, C>;
12
+ }>;
@@ -0,0 +1,23 @@
1
+ import { BaseConfiguration } from "src/schema/types/base/BaseConfiguration";
2
+ import { ZodSchema, z } from "zod";
3
+
4
+ import { BaseUpdateParams } from "./types/BaseUpdateParams";
5
+
6
+ export const updateResultSchema = (
7
+ config: BaseConfiguration,
8
+ m: string,
9
+ params: BaseUpdateParams,
10
+ ) => {
11
+ if (!params.returning) return z.number();
12
+
13
+ const obj: Record<string, ZodSchema<unknown>> = {};
14
+
15
+ params.returning?.forEach((s) => {
16
+ const col = config.models[m].columns[s];
17
+ obj[s] = col.nullable
18
+ ? col.zodSchema.nullish().transform((x) => x ?? null)
19
+ : col.zodSchema;
20
+ });
21
+
22
+ return z.object(obj);
23
+ };
@@ -0,0 +1,43 @@
1
+ import pgfmt from "pg-format";
2
+
3
+ import { BaseConfiguration } from "../../schema/types/base/BaseConfiguration";
4
+ import { SQLStatement, sql } from "../../sql";
5
+ import { buildWhereClauses } from "../clauses/where/buildWhereClauses";
6
+ import { UpdateBuilder } from "./buildUpdate";
7
+
8
+ export const updateToSql = (
9
+ config: BaseConfiguration,
10
+ m: string,
11
+ builder: UpdateBuilder,
12
+ ): SQLStatement => {
13
+ const { table, where, set, returning } = builder;
14
+
15
+ const frag = sql`UPDATE %I.%I AS %I\nSET`.withIdentifiers(
16
+ table.schema,
17
+ table.table,
18
+ table.alias,
19
+ );
20
+
21
+ const fields = set.map((field) => {
22
+ return sql`\n %I = ${field.value}`.withIdentifiers(field.name);
23
+ });
24
+
25
+ frag.push(sql.splat(fields, ",\n"));
26
+
27
+ frag.push("\nWHERE\n ");
28
+ frag.push(buildWhereClauses(config, table, where));
29
+
30
+ if (returning.length > 0) {
31
+ frag.push("\nRETURNING\n");
32
+ frag.push(
33
+ returning
34
+ .map((r) =>
35
+ pgfmt(` %I.%I as %I`, table.alias, r.name, r.alias),
36
+ )
37
+ .join(",\n"),
38
+ );
39
+ }
40
+ frag.push(";\n");
41
+
42
+ return frag;
43
+ };
@@ -0,0 +1,33 @@
1
+ import { BaseConfiguration } from "src/schema/types/base/BaseConfiguration";
2
+
3
+ import { Connection } from "../Connection";
4
+ import { logger } from "../logger";
5
+ import { buildUpdate } from "./update/buildUpdate";
6
+ import { BaseUpdateParams } from "./update/types/BaseUpdateParams";
7
+ import { updateToSql } from "./update/updateToSql";
8
+ import { rowToObject } from "./util/rowToObject";
9
+
10
+ export const updateMany = async (
11
+ conn: Connection,
12
+ config: BaseConfiguration,
13
+ m: string,
14
+ params: BaseUpdateParams,
15
+ ) => {
16
+ const builder = buildUpdate(config, m, params);
17
+ const statement = updateToSql(config, m, builder);
18
+ logger.info({
19
+ message: "Executing update",
20
+ sql: statement.text,
21
+ values: statement.values,
22
+ });
23
+
24
+ if (process.env.ORM_VERBOSE_LOGGING) {
25
+ console.log(statement.text);
26
+ console.log(statement.values);
27
+ }
28
+
29
+ const result = await conn.query(statement);
30
+ return params.returning
31
+ ? result.rows.map(rowToObject(builder.returning))
32
+ : result.rowCount ?? 0;
33
+ };
@@ -0,0 +1,33 @@
1
+ import { BaseConfiguration } from "src/schema/types/base/BaseConfiguration";
2
+
3
+ import { Connection } from "../Connection";
4
+ import { OrmError } from "../errors";
5
+ import { BaseUpdateParams } from "./update/types/BaseUpdateParams";
6
+ import { updateMany } from "./updateMany";
7
+
8
+ export const updateOne = async (
9
+ conn: Connection,
10
+ config: BaseConfiguration,
11
+ m: string,
12
+ params: BaseUpdateParams,
13
+ ) => {
14
+ return await conn.transact(async (conn) => {
15
+ const results = await updateMany(conn, config, m, params);
16
+
17
+ const updatedCount =
18
+ typeof results === "number" ? results : results?.length ?? 0;
19
+
20
+ if (updatedCount === 0) {
21
+ throw new OrmError("No rows updated", { data: { m, params } });
22
+ }
23
+
24
+ if (updatedCount > 1) {
25
+ throw new OrmError(
26
+ "More than one updated row for updateOne, rolling back",
27
+ { data: { m, params, updatedCount } },
28
+ );
29
+ }
30
+
31
+ return typeof results === "number" ? results : results[0];
32
+ });
33
+ };